在刚入门开始学 this 的时候,我自己对 this 的指向是非常迷惑的,感觉总是抓不到重点。同时,对 this 指向的系统分析对于小白来说也很难搞懂。
在经过一段时间的总结后,大概弄明白了不同情况下 this 的指向,这里就来给大家总结一波,尽量用通俗简单的语言讲明白 this 的指向情况。
前置知识
this 是什么?
普遍概念:JavaScript 的关键字,当前环境执行上下文对象的属性。
能读懂的概念:this 就是你 call 一个函数时,传入的第一个参数。也可以理解成 this 就是 call 的第一个参数。
函数调用的3种形式
f(p1, p2); //等价于 function.call(undefined, p1, p2)
obj.child.method(p1, p2);// 等价于obj.child.method.call(obj.child, p1, p2)
f.call(context, p1, p2); // 这种调用形式,才是完整的
this 就是以上代码中的 context ,就是 call 一个函数时候传的 context 。
this指向的几种情况
在分析以下几种情况的时候,可以结合 fn.call() ,就会得出答案,不需要刻意去记忆。
第一种:f(p1, p2)
function f(){ console.log(this) }
f()
//在此代码中,f() === f.call()
以上实例中,打印出来的结果为 window。为什么呢?因为浏览器规定:
如果你传的 context 是 null 或 undefined,那么 window 对象就是默认的 context(严格模式下默认 context 是 undefined)
如果想改变这里 this 的值,可以使用如下代码:
f.call(obj) // 里面的this变成了obj
第二种:obj.child.method(p1,p2)
var obj = { foo: function(){ console.log(this) } }
obj.foo()
将 obj.foo() 转换为 obj.foo.call(obj) ,这里的 this 即为 obj。
第三种:包含[]
arr[0]()这里面的 this 又是什么呢?
function fn (){ console.log(this) }
var arr = [fn, fn2]
arr[0]()
可以把 arr[0] 假想成 arr.0() ,这个形式和前面提到的 obj.child.method(p1,p2) 类似,可以直接转换了。arr[0]() - arr.0() - arr.0.call(arr),易得 this === arr。
第四种:箭头函数没有 this
箭头函数里并没有 this,如果你在箭头函数里看到 this,你直接把它当作箭头函数外面的 this 即可。外面的 this 是什么,箭头函数里面的 this 就还是什么,因为箭头函数本身不支持 this。
第五种:EventHandler
在添加事件监听时,也会遇到需要获取 this 的情况:
button.addEventListener('click' ,function handler(){
console.log(this)
})
通过前面的 call 方法不能直接判断 this 是什么,我们需要找到 handler 调用时的代码。查找 MDN 文档,可以看到这样一段描述:
当使用
addEventListener()为一个元素注册事件的时候,句柄里的 this 值是该元素的引用。其与传递给句柄的 event 参数的currentTarget 属性的值一样。
转换成可以理解的代码如下:
handler.call(event.currentTarget, event)
可以得出其中的 this 就是 event.currentTarget 。
常见面试题解析
通过以上方法,我们可以确定大部分情况下的 this 指向,这里放几道经典的面试题,来给大家巩固一下。
第一题
var a = {
name:"里面的name",
sayName: function(){
console.log("this.name = " + this.name)
}
}
var name = "外面的name";
function sayName(){
var sss = a.sayName;
sss();
// this.name = ???
// 解析:改成call调用的形式,sss.call(undefined)没有传参,this就是window,因此window.name = 外面的name
a.sayName();
//this.name = ???
// 解析:a.sayName.call(a),this就是a,this = 里面的name
(a.sayName)();
//this.name = ???
// 解析:js中加不加括号效果都一样,因此相当于(a.sayName).call(a),this=a,可得this=里面的name
(b = a.sayName)();
//this.name = ???
//解析:括号和上面的效果一样,也就是此题可以转换成b.call(undefined),那么this=window,window.name=外面的name
}
sayName();
// 解析:sayName.call(),this就是window,window.name = 外面的name
第二题
var length = 10;
function fn(){
console.log(this.length)
}
var obj = {
length: 5,
method: function(fn){
fn() arguments[0]();
}
};
obj.method(fn, 1)
// 提问:输出结果是什么?
使用代码转换方法解析题目如下:
var length = 10;
function fn(){
console.log(this.length)
}
var obj = {
length: 5,
method: function(fn){
fn() //fn.call(undefined),this = window,window.length = 10
arguments[0](); //arguments.0.call(arguments),this=arguments,也就是[fn, 1],两个数的数组,结果为2
}
};
obj.method(fn, 1)
// obj.method.call(obj, fn, 1),this = obj
// 结合window.length和[fn, 1],可得本次输出结果为10、2