listview 每行后面的小箭头_两道面试题加深理解闭包与箭头函数中的this

本文详细解析了JavaScript中的this绑定规则,包括隐式绑定、显式绑定、箭头函数的this特点,以及new操作符如何影响this。通过实例分析fn1、fn2、fn3、fn4等函数的this指向,揭示了构造函数创建对象时内部作用域的访问特性,讨论了闭包与new操作的关系,并探讨了this与闭包的微妙差异。
摘要由CSDN通过智能技术生成

题一

/*非严格模式*/var name = 'window'var obj1 = {    name: '听风是风',    fn1: function () {        console.log(this.name)    },    fn2: () => console.log(this.name),    fn3: function () {        return function () {            console.log(this.name)        }    },    fn4: function () {        return () => console.log(this.name)    }}var obj2 = {    name: '行星飞行'};obj1.fn1();//?obj1.fn1.call(obj2);//?obj1.fn2();//?obj1.fn2.call(obj2);//?obj1.fn3()();//?obj1.fn3().call(obj2);//?obj1.fn3.call(obj2)();//?obj1.fn4()();//?obj1.fn4().call(obj2);//?obj1.fn4.call(obj2)();//?

答案就不统一贴了,大家可以自己输出,这里直接开始解析:

第一个输出听风是风,fn1调用前有一个obj1,this为隐式绑定指向obj1,因此读取到obj1的name属性。

第二个输出行星飞行,在介绍this的文章中已经提到,显式绑定优先级高于隐式绑定,所以此时的this指向obj2,读取了obj2的name属性。

第三个输出window,在介绍this一文中我们已经知道箭头函数并没有自己的this,它的this指向由上层执行上下文中的this决定,那为什么上层执行上下文是window呢?

我在介绍JavaScript执行上下文的文章中已经提到,JavaScript中的上下文分为全局执行上下文,函数执行上下文与eval执行上下文(eval不作考虑)。而不管是全局上下文或函数上下文的创建,大致都包含了确认this指向,创建词法环境,创建变量环境三步。

185f960c54702084e8892c4223daeb44.png

也就是说,this属于上下文中的一部分,很明显对象obj1并不是一个函数,它并没有权利创建自己的上下文,所以没有自己的this,那么它的外层是谁呢?当然是全局window啦,所以这里的this指向window。

第四个输出window,在this介绍一文中已经提到,箭头函数的this由外部环境决定,且一旦绑定无法通过call,apply或者bind再次改变箭头函数的this,所以这里虽然使用了call方法但依旧无法修改,所以this还是指向window。

第五个输出window,这个在闭包一文中已经提到了这个例子,obj1.fn3()()其实可以改写成这样:

var fn = obj1.fn3();fn();

先执行了fn3方法,返回了一个闭包fn,而fn执行时本质上等同于window.fn(),属于this默认绑定,所以this指向全局对象。

第六个输出行星飞行,同样是先执行fn3返回一个闭包,但闭包执行时使用了call方法修改了this,此时指向obj2,这行代码等同于:

var fn = obj1.fn3();fn.call(obj2);//显式绑定

第七个输出window,obj1.fn3.call(obj2)()修改一下其实是这样,fn被调用时本质上还是被window调用:

var fn = obj1.fn3.call(obj2);window.fn();//默认绑定

第八个输出听风是风,fn4同样是返回一个闭包,只是这个闭包是一个箭头函数,所以箭头函数的this参考fn4的this即可,很明显此次调用fn4的this指向obj1。

var fn = obj1.fn4();window.fn();//无法改变箭头函数this

第九个输出听风是风,改写代码其实是这样,显式绑定依旧无法改变箭头函数this:

var fn = obj1.fn4();fn.call(obj2);//显式绑定依旧无法改变this

第十个输出行星飞行,前文已经说了,虽然无法直接改变箭头函数的this,但可以通过修改上层上下文的this达到间接修改箭头函数this的目的:

var fn = obj1.fn4.call(obj2);//fn4的this此时指向obj2window.fn();//隐式绑定无法改变箭头函数this,this与fn4一样

OK,题目一解析完毕,我们接着看题目二,其实没有太大区别,只是两个对象是以构造函数创建罢了。

题二

/*非严格模式*/var name = 'window'function Person(name) {  this.name = name;  this.fn1 = function () {    console.log(this.name);  };  this.fn2 = () => console.log(this.name);  this.fn3 = function () {    return function () {      console.log(this.name)    };  };  this.fn4 = function () {    return () => console.log(this.name);  };};var obj1 = new Person('听风是风');console.dir(obj1);var obj2 = new Person('行星飞行');obj1.fn1();obj1.fn1.call(obj2);obj1.fn2();obj1.fn2.call(obj2);obj1.fn3()();obj1.fn3().call(obj2);obj1.fn3.call(obj2)();obj1.fn4()();obj1.fn4().call(obj2);obj1.fn4.call(obj2)();

我们开始解析第二题:

第一个输出听风是风,与第一题一样,这里同样是隐式绑定,this指向new出来的对象obj1。

第二个输出行星飞行,显式绑定,this指向obj2。

第三个你是不是觉得是window,很遗憾,这里的箭头函数指向了obj1,输出听风是风。

哎?不对啊,第一题同样是访问对象中的箭头函数,由于对象没有上下文,所以指向全局window,怎么到这里就不是全局了,new 出来的obj1与我们直接创建的对象有何区别?这就得从new一个函数发生了什么与闭包概念说起,我们先来看个简单的例子1:

function Fn(){    var name = '听风是风';    this.sayName = function () {        console.log(name);    };};var obj = new Fn();obj.sayName();//?

请问obj.sayName能否访问到构造函数中的name属性?答案是能,这里的sayName方法其实就是一个闭包,它访问了外层函数Fn中的自由变量name,并在new过程中由构造函数Fn返回,我们可以尝试打印obj并查看sayName方法:

bb38a234b7305122e888ba297efec850.png

可以看到在scopes字段中保存了一个closure闭包,因为它的存在,返回的闭包obj.sayName才能继续访问此变量。

而我们知道new一个构造函数时,其实可以理解为就是新建了一个对象,并将构造器属性以及构造函数原型都赋予给了此对象,并最终返回,我们简单模拟其实是这样,例子2:

function Fn(){    var name = '听风是风';    var obj = {};    obj.sayName = function () {        console.log(name);    };    return obj;};var obj = Fn();

同样是打印返回的obj查看sayName方法,可以看到也存在闭包:

cf91dfd5b09fd70784734d72f95cdc31.png

那我们回顾到上面的箭头函数,是不是用闭包就能解释通,返回的箭头函数同样保存了构造函数的上下文,而箭头函数的this指向由上层上下文中的this决定,构造函数在new的过程中this指向了obj1,于是箭头函数的this同样也指向了obj1。

让我们回顾一遍什么是闭包?闭包是使用了外层作用域自由变量的函数,很遗憾,JavaScript似乎并未将构造器属性归为自由变量,所以这里并不能用闭包解释,看这个例子3:

function Fn(){    this.name = '听风是风';    this.sayName = function () {        console.log(this.name);    };};var obj = new Fn();console.log(obj);

不知道大家有没有理解我想表达的观点,在上面展示的例子1例子2中,返回的函数如果是访问name这样的变量就构成了闭包,但例子3中访问this.name这类构造器属性却不构成闭包。

即便如此,我们通过前面三个小例子已经证明了new操作返回的对象有权访问构造函数内部作用域,同理,对象中的箭头函数一样可访问,这种关系类似于闭包却又不是闭包,希望大家多多体会。(若大家无法很好理解还是直接当成闭包吧)

花了比较大的篇幅解释第三个,第三个说清楚了后面的都好展开了。

那么第四个输出听风是风,我们改写代码其实是这样:

var arrowFn = obj1.fn2;//箭头函数this指向obj1arrowFn.call(obj2);//箭头函数this无法直接改变

第五个输出window,与题一相同,返回闭包本质上被window调用,this被修改。

第六个输出行星飞行,返回闭包后利用call方法显式绑定指向obj2。

第七个输出window,返回闭包还是被window调用。

第八个输出听风是风,返回闭包是箭头函数,this同样会指向obj1,虽然返回后也是window调用,但箭头函数无法被直接修改,还是指向obj1。

第九个输出听风是风,箭头函数无法被直接修改。

第十个输出行星飞行,箭头函数可通过修改外层作用域this指向从而达到间接修改的目的。

原文:https://www.cnblogs.com/echolun/p/11969938.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值