这又是一个面试经典问题~/(ㄒoㄒ)/~~也是 ES5中众多坑中的一个,在 ES6 中可能会极大避免 this 产生的错误,可是为了一些老代码的维护,最好仍是了解一下 this 的指向和 call、apply、bind 三者的区别。面试
本文首发于个人我的网站:cherryblog.site/windows
在 ES5 中,其实 this 的指向,始终坚持一个原理:this 永远指向最后调用它的那个对象,来,跟着我朗读三遍:this 永远指向最后调用它的那个对象,this 永远指向最后调用它的那个对象,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复制代码
这个相信你们都知道为何 log 的是 windowsName,由于根据刚刚的那句话“this 永远指向最后调用它的那个对象”,咱们看最后调用 a
的地方 a();
,前面没有调用的对象那么就是全局对象 window,这就至关因而 window.a()
;注意,这里咱们没有使用严格模式,若是使用严格模式的话,全局对象就是 undefined
,那么就会报错 Uncaught TypeError: Cannot read property 'name' of undefined
。bash
再看下这个例子:
例 2:app
var name = "windowsName";
var a = {
name: "Cherry",
fn : function () {
console.log(this.name); // Cherry
}
}
a.fn();复制代码
在这个例子中,函数 fn 是对象 a 调用的,因此打印的值就是 a 中的 name 的值。是否是有一点清晰了呢~函数
咱们作一个小小的改动:
例 3:网站
var name = "windowsName";
var a = {
name: "Cherry",
fn : function () {
console.log(this.name); // Cherry
}
}
window.a.fn();复制代码
这里打印 Cherry 的缘由也是由于刚刚那句话“this 永远指向最后调用它的那个对象”,最后调用它的对象仍然是对象 a。ui
咱们再来看一下这个例子:
例 4:this
var name = "windowsName";
var a = {
// name: "Cherry",
fn : function () {
console.log(this.name); // undefined
}
}
window.a.fn();复制代码
这里为何会打印 undefined
呢?这是由于正如刚刚所描述的那样,调用 fn 的是 a 对象,也就是说 fn 的内部的 this 是对象 a,而对象 a 中并无对 name 进行定义,因此 log 的 this.name
的值是 undefined
。
这个例子仍是说明了:this 永远指向最后调用它的那个对象,由于最后调用 fn 的对象是 a,因此就算 a 中没有 name 这个属性,也不会继续向上一个对象寻找 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();复制代码
这里你可能会有疑问,为何不是 Cherry
,这是由于虽然将 a 对象的 fn 方法赋值给变量 f 了,可是没有调用,再接着跟我念这一句话:“this 永远指向最后调用它的那个对象”,因为刚刚的 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()复制代码
读到如今了应该可以理解这是为何了吧(o゚▽゚)o。
改变 this 的指向我总结有如下几种方法:
_this = this
apply
、call
、bind
例 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 进行改造。
众所周知,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复制代码
_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(指向变量 a)
赋值给一个变量 _this
,这样,在 func2
中咱们使用 _this
就是指向对象 a 了。
使用 apply、call、bind 函数也是能够改变 this 的指向的,原理稍后再讲,咱们先来看一下是怎么实现的:
例 10:
var a = {
name : "Cherry",
func1: function () {
console.log(this.name)
},
func2: function () {
setTimeout( function () {
this.func1()
}.apply(a),100);
}
};
a.func2() // Cherry复制代码
例 11:
var a = {
name : "Cherry",
func1: function () {
console.log(this.name)
},
func2: function () {
setTimeout( function () {
this.func1()
}.call(a),100);
}
};
a.func2() // Cherry复制代码
例 12:
var a = {
name : "Cherry",
func1: function () {
console.log(this.name)
},
func2: function () {
setTimeout( function () {
this.func1()
}.bind(a)(),100);
}
};
a.func2() // Cherry复制代码
刚刚咱们已经介绍了 apply、call、bind 都是能够改变 this 的指向的,可是这三个函数稍有不一样。
在 MDN 中定义 apply 以下;
apply() 方法调用一个函数, 其具备一个指定的this值,以及做为一个数组(或相似数组的对象)提供的参数
语法:
fun.apply(thisArg, [argsArray])
其实 apply 和 call 基本相似,他们的区别只是传入的参数不一样。
call 的语法为:
fun.call(thisArg[, arg1[, arg2[, ...]]])复制代码
因此 apply 和 call 的区别是 call 方法接受的是若干个参数列表,而 apply 接收的是一个包含多个参数的数组。
例 13:
var a ={
name : "Cherry",
fn : function (a,b) {
console.log( a + b)
}
}
var b = a.fn;
b.apply(a,[1,2]) // 3复制代码
例 14:
var a ={
name : "Cherry",
fn : function (a,b) {
console.log( a + b)
}
}
var b = a.fn;
b.call(a,1,2) // 3复制代码
咱们先来将刚刚的例子使用 bind 试一下
var a ={
name : "Cherry",
fn : function (a,b) {
console.log( a + b)
}
}
var b = a.fn;
b.bind(a,1,2)复制代码
咱们会发现并无输出,这是为何呢,咱们来看一下 MDN 上的文档说明:
bind()方法建立一个新的函数, 当被调用时,将其this关键字设置为提供的值,在调用新函数时,在任何提供以前提供一个给定的参数序列。
因此咱们能够看出,bind 是建立一个新的函数,咱们必需要手动去调用:
var a ={
name : "Cherry",
fn : function (a,b) {
console.log( a + b)
}
}
var b = a.fn;
b.bind(a,1,2)() // 3复制代码
==================================== 更新==============================
看到留言说,不少童靴不理解为何 例 6 的 innerFunction 和 例 7 的 this 是指向 window 的,因此我就来补充一下 JS 中的函数调用。
例 6:
var name = "windowsName";
function fn() {
var name = 'Cherry';
innerFunction();
function innerFunction() {
console.log(this.name); // windowsName
}
}
fn()复制代码
例 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复制代码
函数调用的方法一共有 4 种
好比上面的 例 1:
例 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复制代码
这样一个最简单的函数,不属于任何一个对象,就是一个函数,这样的状况在 JavaScript 的在浏览器中的非严格模式默认是属于全局对象 window 的,在严格模式,就是 undefined。
但这是一个全局的函数,很容易产生命名冲突,因此不建议这样使用。
因此说更多的状况是将函数做为对象的方法使用。好比例 2:
例 2:
var name = "windowsName";
var a = {
name: "Cherry",
fn : function () {
console.log(this.name); // Cherry
}
}
a.fn();复制代码
这里定义一个对象 a
,对象 a
有一个属性(name
)和一个方法(fn
)。
而后对象 a
经过 .
方法调用了其中的 fn 方法。
而后咱们一直记住的那句话“this 永远指向最后调用它的那个对象”,因此在 fn 中的 this 就是指向 a 的。
若是函数调用前使用了 new 关键字, 则是调用了构造函数。
这看起来就像建立了新的函数,但实际上 JavaScript 函数是从新建立的对象:
// 构造函数:
function myFunction(arg1, arg2) {
this.firstName = arg1;
this.lastName = arg2;
}
// This creates a new object
var a = new myFunction("Li","Cherry");
a.lastName; // 返回 "Cherry"复制代码
这就有要说另外一个面试经典问题:new 的过程了,(ಥ_ಥ)
这里就简单的来看一下 new 的过程吧:
伪代码表示:
var a = new myFunction("Li","Cherry");
new myFunction{
var obj = {};
obj.__proto__ = myFunction.prototype;
var result = myFunction.call(obj,"Li","Cherry");
return typeof result === 'obj'? result : obj;
}复制代码
因此咱们能够看到,在 new 的过程当中,咱们是使用 call 改变了 this 的指向。
在 JavaScript 中, 函数是对象。
JavaScript 函数有它的属性和方法。
call() 和 apply() 是预约义的函数方法。 两个方法可用于调用函数,两个方法的第一个参数必须是对象自己在 JavaScript 严格模式(strict mode)下, 在调用函数时第一个参数会成为 this 的值, 即便该参数不是一个对象。
在 JavaScript 非严格模式(non-strict mode)下, 若是第一个参数的值是 null 或 undefined, 它将使用全局对象替代。
这个时候咱们再来看例 6:
例 6:
var name = "windowsName";
function fn() {
var name = 'Cherry';
innerFunction();
function innerFunction() {
console.log(this.name); // windowsName
}
}
fn()复制代码
这里的 innerFunction() 的调用是否是属于第一种调用方式:做为一个函数调用(它就是做为一个函数调用的,没有挂载在任何对象上,因此对于没有挂载在任何对象上的函数,在非严格模式下 this 就是指向 window 的)
而后再看一下 例 7:
例 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复制代码
这个简单一点的理解能够理解为“匿名函数的 this 永远指向 window”,你能够这样想,仍是那句话this 永远指向最后调用它的那个对象,那么咱们就来找最后调用匿名函数的对象,这就很尴尬了,由于匿名函数名字啊,笑哭,因此咱们是没有办法被其余对象调用匿名函数的。因此说 匿名函数的 this 永远指向 window。
若是这个时候你要问,那匿名函数都是怎么定义的,首先,咱们一般写的匿名函数都是自执行的,就是在匿名函数后面加 ()
让其自执行。其次就是虽然匿名函数不能被其余对象调用,可是能够被其余函数调用啊,好比例 7 中的 setTimeout。