一, 前言
this 指向, apply,call,bind 的区别是一个经典的面试问题, 同时在项目中会经常使用到的原生的 JS 方法. 同时也是 ES5 中的众多坑的一个. ES6 中可能会极大的避免了 this 产生的错误, 有时候需要维护老的项目还是有必要了解一下 this 的指向和 apply,call,bind 三者的区别.
二, this 的指向
在 ES5 中, 其实 this 的指向, 始终坚持一个原理: this 永远指向最后一个调用它的那个对象.
首先我们看一个栗子 1:
- var name = "windowsName";
- function a() {
- var name = "Cherry";
- console.log(this.name); // windowsName
- console.log("inner:" + this); // inner: Windows
- }
- a();
- console.log("outer:" + this) // outer: Windows
输出 windowsName, 是因为 "this 永远指向最后调用它的那个对象", 我们看到调用 a 的地方 a(), 前面没有调用的对象那么就是全局对象 Windows, 就是全局对象调用 a(), 相当于 Windows.a().
如果使用严格模式, 全局对象就是 undefined, 会报错 name of undefined
栗子 2:
- var name = "windowsName";
- var a = {
- name: "Cherry",
- fn : function () {
- console.log(this.name); // Cherry
- }
- }
- a.fn();
在这个栗子中, 函数 fn 是对象 a 调用的, 所以 console 是 a 中的 name
栗子 3:
- var name = "windowsName";
- var a = {
- name: "Cherry",
- fn : function () {
- console.log(this.name); // Cherry
- }
- }
- Windows.a.fn();
这个栗子中, 记住 "this 永远指向最后一个调用它的那个对象", 调用 fn 的对象有 Windows,a, 但是最后调用 fn 是 a 对象, 所以 this 指向对象 a 中的 name.
栗子 4:
- var name = "windowsName";
- var a = {
- // name: "Cherry",
- fn : function () {
- console.log(this.name); // undefined
- }
- }
- Windows.a.fn();
为啥 undefined, 调用 fn 的对象有: Windows,a, 最后一个调用 fn 是 a, 但是 a 中没有对那么进行定义, 也不会继续向上一个对象寻找 this.name, 而是直接输出 undefined, 所以 this.name 为 undefined.
栗子 5(比较坑):
- var name = "windowsName";
- var a = {
- name : null,
- // name: "Cherry",
- fn : function () {
- console.log(this.name); // windowsName
- }
- }
- var f = a.fn;
- f();
这个栗子比较坑, 为啥 不是 null, 因为虽然将 a 对象的 fn 方法赋值给变量 f, 但是没有调用,"this 永远执行最后一个调用 ta 的那个对象", 由于刚刚的 f 没有调用, 所以 fn()最后仍然是被 Windows 调用的, 所以 this 指向的也就是 Windows.
注意: this 的指向并不是在创建的时候可以确定, 在 ES5 中, 永远都是 this 永远指向最后调用它的那个对象.
栗子 6:
- var name = "windowsName";
- function fn() {
- var name = 'Cherry';
- innerFunction();
- function innerFunction() {
- console.log(this.name); // windowsName
- }
- }
- fn()
三, 怎样改变 this 的指向
改变 this 的指向, 我总结以下的方法:
(1)使用 ES6 中箭头函数
(2)函数内部使用_this = this
(3)使用 apply,call,bind 方法
(4)new 实例化一个对象
举个栗子 7:
- var name = "windowsName";
- var a = {
- name : "Cherry",
- func1: function () {
- console.log(this.name)
- },
- func2: function () {
- setTimeout( function () {
- this.func1()
- },100);
- }
- };
- a.func2() // this.func1 is not a function
在这个栗子中, 不使用箭头函数情况下, 会报错的, 因为最后调用 setTimeout 的对象时 Windows, 但是在 Windows 并没有 func1 函数.
我们改变 this 的指向这一节将吧这个栗子作为 demo 进行改造.
1,ES6 中的箭头函数
众所周知, ES6 的箭头函数是可以避免 ES5 中 this 的坑, 箭头函数的 this 始终指向函数定义时候的 this, 而并不是执行时候. 箭头函数需要记住这句话:"箭头函数没有 this 绑定, 必须通过查找作用域来决定其值, 如果箭头函数被非箭头函数包含, 则 this 的绑定的是最近一层非箭头函数的 this, 否则, this 为 undefined"
栗子 8:
- var name = "windowsName";
- var a = {
- name : "Cherry",
- func1: function () {
- console.log(this.name)
- },
- func2: function () {
- setTimeout( () => {
- this.func1()
- },100);
- }
- };
- a.func2() // Cherry
2, 在函数内部使用_this = this
在不使用 ES6 中, 那么这种方式应该是最简单的不会出错的方式, 我们先将调用这个函数的对象保存在变量_this 中, 然后在函数中都使用这个_this, 这样_this 就不会改变了.
栗子 9:
- var name = "windowsName";
- var a = {
- name : "Cherry",
- func1: function () {
- console.log(this.name)
- },
- func2: function () {
- var _this = this;
- setTimeout( function() {
- _this.func1()
- },100);
- }
- };
- a.func2() // Cherry
在 func2 中, 首先设置 var _this = this, 这里 this 是调用 func2 的对象 a, 为了防止在 func2 中的 setTimeout 被 Windows 调用而导致的在 setTimeout 中的 this 为 Windows. 我们将 this 赋值给一个变量_this, 这样在 func2 中我们使用_this 就是指向对象 a 了.
3, 使用 apply
栗子 10:
- var a = {
- name : "Cherry",
- func1: function () {
- console.log(this.name)
- },
- func2: function () {
- setTimeout( function () {
- this.func1()
- }.apply(a),100);
- }
- };
- a.func2() // Cherry
在栗子中, apply()方法调用一个函数, 其具有一个指定的 this 值, 以及作为一个数组 (或者类似数组的对象) 提供的参数, fun.apply(thisArg, [argsArray])
thisArg: 在 fun 函数运行时指定的 this 值. 指定 this 的值并不一定是函数执行时真正的 this 值, 如果是原始值的 this 会指向该原始值的自动包装对象.
argsArray: 一个数组或者类数组对象, 其中的数组元素将作为单独的参数传给 fun 函数. 参数为 null 或者 undefined, 则表示不需要传入任何参数.
4, 使用 call
栗子 11:
- var a = {
- name : "Cherry",
- func1: function () {
- console.log(this.name)
- },
- func2: function () {
- setTimeout( function () {
- this.func1()
- }.call(a),100);
- }
- };
- a.func2() // Cherry
在栗子中, call()方法调用一个函数, 其具有一个指定的 this 值, 以及若干个参数列表, fun.call(thisArg, arg1, arg2, ...)
thisArg: 在 fun 函数运行时指定的 this 值. 指定 this 的值并不一定是函数执行时真正的 this 值, 如果是原始值的 this 会指向该原始值的自动包装对象.
arg1, arg2, ...: 若干个参数列表
5, 使用 bind
栗子 12:
- var a = {
- name : "Cherry",
- func1: function () {
- console.log(this.name)
- },
- func2: function () {
- setTimeout( function () {
- this.func1()
- }.bind(a)(),100);
- }
- };
- a.func2() // Cherry
在栗子中, bind()方法创建一个新的函数, 当被调用时, 将其 this 的关键字设置为提供的值, 在调用新函数时, 在任何提供一个给定的参数序列.
bind 创建了一个新函数, 必须手动去调用.
四, apply,call,bind 区别
1,apply 和 call 的区别
apply 和 call 基本类似, 他们的区别只是传入的参数不同. apply 传入的参数是包含多个参数的数组, call 传入的参数是若干个参数列表.
栗子 13:
- var a ={
- name : "Cherry",
- fn : function (a,b) {
- console.log( a + b);
- console.log( this.name );
- }
- }
- var b = a.fn;
- b.apply(a,[1,2]) // 3 Cherry
栗子 14:
- var a ={
- name : "Cherry",
- fn : function (a,b) {
- console.log( a + b);
- console.log( this.name );
- }
- }
- var b = a.fn;
- b.call(a,1,2) // 3 Cherry
2,bind 和 apply,call 区别
bind 方法会创建一个新的函数, 当被调用的时候, 将其 this 关键字设置为提供的值, 我们必须手动去调用.
- var a ={
- name : "Cherry",
- fn : function (a,b) {
- console.log( a + b);
- console.log( this.name );
- }
- }
- var b = a.fn;
- b.bind(a,1,2)() //3 //Cherry
来源: https://www.cnblogs.com/chengxs/p/10554578.html