【进阶3-2期】JavaScript深入之重新认识箭头函数的this

23次阅读

共计 4580 个字符,预计需要花费 12 分钟才能阅读完成。

我们知道 this 绑定规则一共有 5 种情况:

1、默认绑定(严格 / 非严格模式)
2、隐式绑定
3、显式绑定
4、new 绑定
5、箭头函数绑定

其实大部分情况下可以用一句话来概括,this 总是指向调用该函数的对象。
但是对于箭头函数并不是这样,是根据外层(函数或者全局)作用域(词法作用域)来决定 this。
对于箭头函数的 this 总结如下:

箭头函数不绑定 this,箭头函数中的 this 相当于普通变量。
箭头函数的 this 寻值行为与普通变量相同,在作用域中逐级寻找。
箭头函数的 this 无法通过 bind,call,apply 来直接修改(可以间接修改)。
改变作用域中 this 的指向可以改变箭头函数的 this。
eg. function closure(){()=>{//code}},在此例中,我们通过改变封包环境 closure.bind(another)(),来改变箭头函数 this 的指向。

题目 1
/**
* 非严格模式
*/

var name = ‘window’

var person1 = {
name: ‘person1’,
show1: function () {
console.log(this.name)
},
show2: () => console.log(this.name),
show3: function () {
return function () {
console.log(this.name)
}
},
show4: function () {
return () => console.log(this.name)
}
}
var person2 = {name: ‘person2’}

person1.show1()
person1.show1.call(person2)

person1.show2()
person1.show2.call(person2)

person1.show3()()
person1.show3().call(person2)
person1.show3.call(person2)()

person1.show4()()
person1.show4().call(person2)
person1.show4.call(person2)()





正确答案如下:
person1.show1() // person1,隐式绑定,this 指向调用者 person1
person1.show1.call(person2) // person2,显式绑定,this 指向 person2

person1.show2() // window,箭头函数绑定,this 指向外层作用域,即全局作用域
person1.show2.call(person2) // window,箭头函数绑定,this 指向外层作用域,即全局作用域

person1.show3()() // window,默认绑定,这是一个高阶函数,调用者是 window
// 类似于 `var func = person1.show3()` 执行 `func()`
person1.show3().call(person2) // person2,显式绑定,this 指向 person2
person1.show3.call(person2)() // window,默认绑定,调用者是 window

person1.show4()() // person1,箭头函数绑定,this 指向外层作用域,即 person1 函数作用域
person1.show4().call(person2) // person1,箭头函数绑定,
// this 指向外层作用域,即 person1 函数作用域
person1.show4.call(person2)() // person2
最后一个 person1.show4.call(person2)() 有点复杂,我们来一层一层的剥开。

1、首先是 var func1 = person1.show4.call(person2),这是显式绑定,调用者是 person2,show4 函数指向的是 person2。
2、然后是 func1(),箭头函数绑定,this 指向外层作用域,即 person2 函数作用域

首先要说明的是,箭头函数绑定中,this 指向外层作用域,并不一定是第一层,也不一定是第二层。
因为没有自身的 this,所以只能根据作用域链往上层查找,直到找到一个绑定了 this 的函数作用域,并指向调用该普通函数的对象。
题目 2
这次通过构造函数来创建一个对象,并执行相同的 4 个 show 方法。
/**
* 非严格模式
*/

var name = ‘window’

function Person (name) {
this.name = name;
this.show1 = function () {
console.log(this.name)
}
this.show2 = () => console.log(this.name)
this.show3 = function () {
return function () {
console.log(this.name)
}
}
this.show4 = function () {
return () => console.log(this.name)
}
}

var personA = new Person(‘personA’)
var personB = new Person(‘personB’)

personA.show1()
personA.show1.call(personB)

personA.show2()
personA.show2.call(personB)

personA.show3()()
personA.show3().call(personB)
personA.show3.call(personB)()

personA.show4()()
personA.show4().call(personB)
personA.show4.call(personB)()





正确答案如下:
personA.show1() // personA,隐式绑定,调用者是 personA
personA.show1.call(personB) // personB,显式绑定,调用者是 personB

personA.show2() // personA,首先 personA 是 new 绑定,产生了新的构造函数作用域,
// 然后是箭头函数绑定,this 指向外层作用域,即 personA 函数作用域
personA.show2.call(personB) // personA,同上

personA.show3()() // window,默认绑定,调用者是 window
personA.show3().call(personB) // personB,显式绑定,调用者是 personB
personA.show3.call(personB)() // window,默认绑定,调用者是 window

personA.show4()() // personA,箭头函数绑定,this 指向外层作用域,即 personA 函数作用域
personA.show4().call(personB) // personA,箭头函数绑定,call 并没有改变外层作用域,
// this 指向外层作用域,即 personA 函数作用域
personA.show4.call(personB)() // personB,解析同题目 1,最后是箭头函数绑定,
// this 指向外层作用域,即改变后的 person2 函数作用域
题目一和题目二的区别在于题目二使用了 new 操作符。

使用 new 操作符调用构造函数,实际上会经历一下 4 个步骤:

创建一个新对象;
将构造函数的作用域赋给新对象(因此 this 就指向了这个新对象);
执行构造函数中的代码(为这个新对象添加属性);
返回新对象。

上期思考题解
依次给出 console.log 输出的数值。
var num = 1;
var myObject = {
num: 2,
add: function() {
this.num = 3;
(function() {
console.log(this.num);
this.num = 4;
})();
console.log(this.num);
},
sub: function() {
console.log(this.num)
}
}
myObject.add();
console.log(myObject.num);
console.log(num);
var sub = myObject.sub;
sub();
答案有两种情况,分为严格模式和非严格模式。

严格模式下,报错。TypeError: Cannot read property ‘num’ of undefined

非严格模式下,输出:1、3、3、4、4

解答过程:
var num = 1;
var myObject = {
num: 2,
add: function() {
this.num = 3; // 隐式绑定 修改 myObject.num = 3
(function() {
console.log(this.num); // 默认绑定 输出 1
this.num = 4; // 默认绑定 修改 window.num = 4
})();
console.log(this.num); // 隐式绑定 输出 3
},
sub: function() {
console.log(this.num) // 因为丢失了隐式绑定的 myObject,所以使用默认绑定 输出 4
}
}
myObject.add(); // 1 3
console.log(myObject.num); // 3
console.log(num); // 4
var sub = myObject.sub;// 丢失了隐式绑定的 myObject
sub(); // 4
内容来自评论区:【进阶 3 - 1 期】JavaScript 深入之史上最全 –5 种 this 绑定全面解析
本期思考题
分别给出 console.log 输出的内容。
var obj = {
say: function () {
function _say() {
console.log(this);
}
console.log(obj);
return _say.bind(obj);
}()
}
obj.say()
参考
从这两套题,重新认识 JS 的 this、作用域、闭包、对象
进阶系列目录

【进阶 1 期】调用堆栈
【进阶 2 期】作用域闭包
【进阶 3 期】this 全面解析
【进阶 4 期】深浅拷贝原理
【进阶 5 期】原型 Prototype
【进阶 6 期】高阶函数
【进阶 7 期】事件机制
【进阶 8 期】Event Loop 原理
【进阶 9 期】Promise 原理
【进阶 10 期】Async/Await 原理
【进阶 11 期】防抖 / 节流原理
【进阶 12 期】模块化详解
【进阶 13 期】ES6 重难点
【进阶 14 期】计算机网络概述
【进阶 15 期】浏览器渲染原理
【进阶 16 期】webpack 配置
【进阶 17 期】webpack 原理
【进阶 18 期】前端监控
【进阶 19 期】跨域和安全
【进阶 20 期】性能优化
【进阶 21 期】VirtualDom 原理
【进阶 22 期】Diff 算法
【进阶 23 期】MVVM 双向绑定
【进阶 24 期】Vuex 原理
【进阶 25 期】Redux 原理
【进阶 26 期】路由原理
【进阶 27 期】VueRouter 源码解析
【进阶 28 期】ReactRouter 源码解析

交流
进阶系列文章汇总:https://github.com/yygmind/blog,内有优质前端资料,欢迎领取,觉得不错点个 star。
我是木易杨,网易高级前端工程师,跟着我每周重点攻克一个前端面试重难点。接下来让我带你走进高级前端的世界,在进阶的路上,共勉!

正文完
 0