整理自:
- https://zhuanlan.zhihu.com/p/23804247
- https://zhuanlan.zhihu.com/p/25991271
- https://juejin.im/post/5aa1eb056fb9a028b77a66fd#comment
若要理解this的指向,首先搞清楚JS中的函数。JS中的函数有两种形式:传统函数与ES6中新增的箭头函数。
一、JS中函数的写法
1. 常规函数的写法
ES6之前,JS中的函数由function关键字、params参数和{函数体}组成。为与箭头函数区别,将其称为常规函数,函数有两种写法:
<!--声明式-->
function f(name){
console.log(name)
}
f('haha');
<!--表达式-->
let f2 = function (name){
console.log(name)
}
f2('hehe');
2. 箭头函数写法
ES6箭头函数的引入,使函数的写法变的更加简洁,但在书写上要遵循一定的规则。
规则一:箭头函数只能用赋值式写法,不能用声明式写法
let f = (name)=>{
console.log(name)
}
f('haha');
规则二:如果参数只有一个,可以不加括号,如果没有参数或者参数多于一个就需要加括号
<!--只有一个参数,省略参数括号-->
let f = name => {
console.log(name)
}
f('haha');
<!--参数多于一个,必须加上括号-->
let f = (name,age)=>{
console.log(name+":"+age)
}
f('haha',10);
规则三:如果函数体只有一句话,可以不加花括号
let f = name => console.log(name)
f('haha');
规则四:return和{}同在
const add = (p1, p2) => p1 + p2
add(10, 25)
<!--下面的情况会报错-->
const add = (p1, p2) => return p1 + p2
二、初步了解this
- this 就是你call一个函数时,传入的第一个参数。
- 如果你的函数调用形式不是call形式,请按照「转换代码」将其转换为 call 形式。
三、理解常规函数中的this
this是使用call方法调用函数时传递的第一个参数,它可以在函数调用时修改,在函数没有调用的时候,this的值是无法确定。
JS(ES5)里有三种函数调用形式:
func(p1, p2)
obj.child.method(p1, p2)
func.call(context, p1, p2) // 先不讲 apply
一般,初学者都知道前两种形式,而且认为前两种形式「优于」第三种形式。但是第三种调用方式,才是正常调用方式,其余两种都是语法糖,可以等价地变为call形式。
func(p1, p2) 等价于
func.call(undefined, p1, p2)
obj.child.method(p1, p2) 等价于
obj.child.method.call(obj.child, p1, p2)
即,函数调用只有一种形式
func.call(context, p1, p2)
==this就是上面代码中的context。==
==this就是call一个函数时传入的context。==
1. 纯粹的函数调用
下面看一个例子:
function f(name){
console.log(name)
console.log(this)
}
f('haha')//调用函数的简写形式
f.call(undefined,'haha')//完整形式
call方法接受的第一个参数就是this,此处默认传入undefined。 按理说,函数执行之后log出来的this会是undefined吗?
实际输出结果为:
haha
Window对象
这是由于浏览器里有一条规则:
如果你传的 context 就 null 或者 undefined,那么 window 对象就是默认的 context(严格模式下默认 context 是 undefined)
如果希望这里的this不是window
f.call(obj,'haha')//里面的this就是obj对象了
2. 对象中函数的调用
下面看一个例子:
const obj = {
name: 'Jerry',
greet: function() {
console.log(this.name)
}
}
obj.greet() //第一种调用方法,语法糖
obj.greet.call(obj) //第二种调用方法,完整的调用方式
第二种方法厉害的地方在于它可以手动指定this。
手动指定this
const obj = {
name: 'Jerry',
greet: function() {
console.log(this.name)
}
}
obj.greet.call({name: 'Spike'}) //打出来的是 Spike
[]语法
function fn (){ console.log(this) }
var arr = [fn, fn2]
arr[0]() // 这里面的 this 又是什么呢?
可以把 arr0想象为arr.0(),虽然后者的语法错了,但是形式与转换代码里的obj.child.method(p1,p2)对应上了,于是就可以愉快的转换了:
arr[0]()
假想为 arr.0()
然后转换为 arr.0.call(arr)
那么里面的 this 就是 arr 了 :)
3. 构造函数中的this
构造函数里的this稍微有点特殊,每个构造函数在new之后都会返回一个对象,这个对象就是this,也就是context上下文。
function Test() {
this.name = 'Tom'
}
let p = new Test()
console.log(typeof p) //object
console.log(p.name) // Tom
4. window.setTimeout()和window.setInterval()中函数的调用
window.setTimeout()和window.setInterval()的函数中的this有些特殊,里面的this默认是window对象。
其实回调函数是在全局范围内调用的,形式同纯粹函数调用形式,其context为undefined,所以默认是window对象,
5. Event handler 中的this
btn.addEventListener('click' ,function handler(){
console.log(this) // 请问这里的 this 是什么
})
this都是由call或apply指定的,只要找到handler被调用时的代码就可以找到this了。但是addEventListener是浏览器内置的方法,无法看到源码,不过MDN中进行了说明:
通常来说this的值是触发事件的元素的引用,这种特性在多个相似的元素使用同一个通用事件监听器时非常让人满意。
当使用 addEventListener() 为一个元素注册事件的时候,句柄里的 this 值是该元素的引用。其与传递给句柄的 event 参数的 currentTarget 属性的值一样。
所以,可以假想handler调用时:
// 当事件被触发时
handler.call(event.currentTarget, event)
// 那么 this 是什么不言而喻
currentTarget指向监听事件注册到的元素,target指向实际出发事件的元素
四、箭头函数中的this
==所有的箭头函数都没有自己的this,都指向外层==
《ES6 标准入门》中指出:“箭头函数”的this,总是指向定义时所在的对象,而不是运行时所在的对象。
也可以说是,总是指向所在函数运行时的this
箭头函数没有自身的this绑定,例如
var foo=()=> {
console.log(this);
}
使用babel编译成ES5形式
var foo = function foo() {
console.log(undefined);
};
在函数执行前绑定this的时候,传入的thisArguments会被直接忽略,也就是说箭头函数本身没法修改this,所以对this的访问永远是它继承外部上下文的this。
例如:
var foo=function (){
var f=()=>{console.log(this)}
f.call({name:'test'})
}
foo.call({name:'outer'})
输出结果为:
{name:'outer'}
babel的编译结果:
var foo = function foo() {
var _this = this;
var f = function f() {
console.log(_this);
};
f.call({ name: 'test' });
};
上面的结果说明了call没有改变this的指向,因为从babel的编译结果来看,箭头函数中的this就不是自己的this,而是引用的外部this
以babel的实现来说,在箭头函数内部没有this引用的时候,默认编译成:
var f = function(v) {
return v;
};
如果箭头函数内部使用了this,就会被编译成
function test() {
var _this = this;
var f = function f(v) {
return _this.a;
};
}
所以说,全程没有绑定这回事。箭头函数的this指向的是定义时所在的对象。
function foo() {
return () => {
return () => {
return () => {
console.log("id:", this.id);
};
};
};
}
var f = foo.call({id: 1});
var t1 = f.call({id: 2})()();//id:1
var t2 = f().call({id: 3})();//id:1
var t3 = f()().call({id: 4});//id:1
上面代码的输出结果都是id:1,为什么会出现这种情况?
使用babel将函数foo转换成ES5形式
"use strict";
function foo() {
var _this = this;
return function () {
return function () {
return function () {
console.log("id:", _this.id);
};
};
};
}
验证了箭头函数没有自身的this绑定,其this只是一个引用了外部this的_this变量
五、多层对象嵌套里函数的this
参考的文章中,提出了问题:箭头函数里的this是和外层保持一致的,但是如果这个外层有好多层,那它是和哪层保持一致呢?
function test(){
const obj = {
a: function() { console.log(this) },
b: {
c: () => {console.log(this)}
}
}
}
babel编译后的结果
function test() {
var _this = this;
var obj = {
a: function a() {
console.log(this);
},
b: {
c: function c() {
console.log(_this);
}
}
};
}
结果显示,多层对象嵌套里箭头函数的this指向的是最外层对象所在的上下文中的this。