题一
/*非严格模式*/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指向,创建词法环境,创建变量环境三步。
也就是说,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方法:
可以看到在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方法,可以看到也存在闭包:
那我们回顾到上面的箭头函数,是不是用闭包就能解释通,返回的箭头函数同样保存了构造函数的上下文,而箭头函数的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