一、前言
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: Window
}
a();
console.log("outer:" + this) // outer: Window
输出 windowsName,是因为“this 永远指向最后调用它的那个对象”,我们看到调用 a 的地方 a(),前面没有调用的对象那么就是全局对象 window,就是全局对象调用 a(),相当于 window.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
}
}
window.a.fn();
这个栗子中,记住“this 永远指向最后一个调用它的那个对象”,调用 fn 的对象有 window,a,但是最后调用 fn 是 a 对象,所以 this 指向对象 a 中的 name。
栗子 4:
var name = "windowsName";
var a = {
// name: "Cherry",
fn : function () {console.log(this.name); // undefined
}
}
window.a.fn();
为啥 undefined,调用 fn 的对象有:window,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() 最后仍然是被 window 调用的,所以 this 指向的也就是 window。
注意: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 的对象时 window,但是在 window 并没有 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 被 window 调用而导致的在 setTimeout 中的 this 为 window。我们将 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