Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
yygmind opened this issue Dec 4, 2018 · 8 comments
Open

Comments

@yygmind
Copy link
Owner

yygmind commented Dec 4, 2018

本期的主题是this全面解析,本计划一共28期,每期重点攻克一个面试重难点,如果你还不了解本进阶计划,文末点击查看全部文章。

如果觉得本系列不错,欢迎点赞、评论、转发,您的支持就是我坚持的最大动力。


上篇文章详细的分析了各种this的情况,看过之后对this的概念就很清晰了,没看过的去看看。

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

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

其实大部分情况下可以用一句话来概括,this总是指向调用该函数的对象

但是对于箭头函数并不是这样,是根据外层(函数或者全局)作用域(词法作用域)来决定this。

对于箭头函数的this总结如下:

  1. 箭头函数不绑定this,箭头函数中的this相当于普通变量。

  2. 箭头函数的this寻值行为与普通变量相同,在作用域中逐级寻找。

  3. 箭头函数的this无法通过bind,call,apply来直接修改(可以间接修改)。

  4. 改变作用域中this的指向可以改变箭头函数的this。

  5. 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),这是显式绑定,调用者是person2show4函数指向的是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个步骤:

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

上期思考题解

依次给出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。

我是木易杨,网易高级前端工程师,跟着我每周重点攻克一个前端面试重难点。接下来让我带你走进高级前端的世界,在进阶的路上,共勉!

@Wayne6677
Copy link

真棒

@zhangenming
Copy link

有那么复杂么, 我认为就一句话, 箭头函数没有"自己"的this, this的值是父级函数的值

@lily000000
Copy link

@DoubleBlock
Copy link

插眼

@Ray-56
Copy link

Ray-56 commented Apr 12, 2019

思考题:
这里其实可以看成下面这样:

function _say() {
    console.log(this);
}
console.log(obj);
var obj = {
    say: _say.bind(obj)
}
obj.say();

输出undefined window

如有误请指正

@youling4438
Copy link

有些不理解,先标记一下

@kangenpingmai
Copy link

不太明白上期思考题,第一个为什么打印1,那个立即执行函数不是在 myObject.add的域中嘛

@ree-file
Copy link

不太明白上期思考题,第一个为什么打印1,那个立即执行函数不是在 myObject.add的域中嘛

在我的理解里:像add是由myObject这个对象调用。所以add的this指向myObject。立即执行函数没有对象去调用它所以执行window。就像setTimeout那样

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

9 participants