壹 ❀ 引

在本文之前我已经花了两个篇幅专门介绍了JavaScript中的闭包与this,正好今早地铁上看到了两道面试题,试着做了下发现挺有意思,所以想单独写一篇文章来记录解析过程。若你对于闭包与this有所了解,不妨先看自己的理解是否正确,若你对于这部分知识欠缺,还是建议先阅读我前面的两篇文章,链接在下:

一篇文章看懂JS闭包,都要2020年了,你怎么能还不懂闭包?

js 五种绑定彻底弄懂this,默认绑定、隐式绑定、显式绑定、new绑定、箭头函数绑定详解

那么本文开始。

 贰 ❀ 题一

/*非严格模式*/

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此时指向obj2
window.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);

我们打印obj对象并查看sayName方法,可以看到并不是一个闭包:

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

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

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

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

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

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

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

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

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

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

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

 肆 ❀ 总

那么到这里两道题分析完毕,我们来做个总结。

题一与题二虽然都是对象,但通过new创建出来的对象与对象直接量还是有所区别,这一点就体现在了对象中的箭头函数中。相比普通对象,new操作符的对象保存了构造函数上下文中的this指向,导致箭头函数并不会指向window。

箭头函数相比普通函数,箭头函数的this比较吃软饭,外层上下文中的this指向谁它便指向谁,同时我们无法直接修改箭头函数的this。而普通函数的this可以被隐式,显式多种手段修改,并满足一定优先级。

我们了解到构造函数得到的实例对象所包含的函数严格意义上并不是闭包,虽然它与闭包非常相似。

如果大家对于解析有所疑问,欢迎留言,我会第一时间回复,那么本文就写到这里。

最后补一个,如果大家对于new一个函数的过程有疑虑,建议阅读博主这篇文章 js new一个对象的过程,实现一个简单的new方法

js 从两道面试题加深理解闭包与箭头函数中的this的更多相关文章

  1. 两道面试题,带你解析Java类加载机制

    文章首发于[博客园-陈树义],点击跳转到原文<两道面试题,带你解析Java类加载机制> 在许多Java面试中,我们经常会看到关于Java类加载机制的考察,例如下面这道题: class Gr ...

  2. 【转】两道面试题,带你解析Java类加载机制(类初始化方法 和 对象初始化方法)

    本文转自 https://www.cnblogs.com/chanshuyi/p/the_java_class_load_mechamism.html 关键语句 我们只知道有一个构造方法,但实际上Ja ...

  3. 你所不知道的库存超限做法 服务器一般达到多少qps比较好[转] JAVA格物致知基础篇:你所不知道的返回码 深入了解EntityFramework Core 2.1延迟加载(Lazy Loading) EntityFramework 6.x和EntityFramework Core关系映射中导航属性必须是public? 藏在正则表达式里的陷阱 两道面试题,带你解析Java类加载机制

    你所不知道的库存超限做法 在互联网企业中,限购的做法,多种多样,有的别出心裁,有的因循守旧,但是种种做法皆想达到的目的,无外乎几种,商品卖的完,系统抗的住,库存不超限.虽然短短数语,却有着说不完,道不 ...

  4. js中的this和箭头函数中的this

    一.ES6 允许使用"箭头"(=>)定义函数. // var f = v => v;// 上面的箭头函数等同于: // var f = function(v) {// ...

  5. 深入理解ES6箭头函数中的this

    简要介绍:箭头函数中的this,指向与一般function定义的函数不同,比较容易绕晕,箭头函数this的定义:箭头函数中的this是在定义函数的时候绑定,而不是在执行函数的时候绑定. 1.何为定义时 ...

  6. 【js基础修炼之路】— 深入浅出理解闭包

    之前对于闭包的理解只是很肤浅的,只是浮于表面,这次深究了一下闭包,下面是我对闭包的理解. 什么是闭包? 引用高程里的话 => 闭包就是有权访问另一个作用域中变量的函数,闭包是由函数以及创建该函数 ...

  7. [转]从两道经典试题谈C/C++中联合体(union)的使用

    宋宝华 21cnbao sweek@21cn.com 试题一:编写一段程序判断系统中的CPU是Little endian还是Big endian模式? 分析: 作为一个计算机相关专业的人,我们应该在计 ...

  8. Java中创建String的两道面试题及详解

    我们知道创建一个String类型的变量一般有以下两种方法: String str1 = "abcd"; String str2 = new String("abcd&qu ...

  9. 三. var let const的理解 以及 立即执行函数中的使用 以及 for循环中的例子

    一. 立即执行函数 windows中有个name属性,name='' '' var 如果我们用var name 去声明,那就会改变windows中name的值(因为我们不是在函数作用域中声明的,所以会 ...

随机推荐

  1. 【SSL题解报告】没有上司的舞会

    题目: 题目描述 某大学有N个职员,编号为1~N.他们之间有从属关系,也就是说他们的关系就像一棵以校长为根的树,父结点就是子结点的直接上司.现在有个周年庆宴会,宴会每邀请来一个职员都会增加一定的快乐指 ...

  2. css优先级 中文版MDN补充翻译

    原文地址:https://developer.mozilla.org/zh-CN/docs/Web/CSS/Specificity css的MDN中文版,这一页是讲css的优先级的. 读到文章的最后, ...

  3. Python正则表达式,看完这篇文章就够了...#华为云&#183;寻找黑马程序员#【华为云技术分享】

    版权声明:本文为博主原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明. 本文链接:https://blog.csdn.net/devcloud/article/detai ...

  4. 转:Spring事务管理

    spring是SSH中的管理员,负责管理其它框架,协调各个部分的工作.今天一起学习一下Spring的事务管理.Spring配置文件中关于事务配置总是由三个组成部分,分别是DataSource.Tran ...

  5. Oracle触发器用法--基础教学

    1.触发器简介 触发器的定义就是说某个条件成立的时候,触发器里面所定义的语句就会被自动的执行.因此触发器不需要人为的去调用,也不能调用.然后,触发器的触发条件其实在你定义的时候就已经设定好了.这里面需 ...

  6. HDU1561 The more ,The better (树形背包Dp)

    ACboy很喜欢玩一种战略游戏,在一个地图上,有N座城堡,每座城堡都有一定的宝物,在每次游戏中ACboy允许攻克M个城堡并获得里面的宝物.但由于地理位置原因,有些城堡不能直接攻克,要攻克这些城堡必须先 ...

  7. HDU-1027Ignatius and princess II

    Now our hero finds the door to the BEelzebub feng5166. He opens the door and finds feng5166 is about ...

  8. Python计算IV值

    更多大数据分析.建模等内容请关注公众号<bigdatamodeling> 在对变量分箱后,需要计算变量的重要性,IV是评估变量区分度或重要性的统计量之一,python计算IV值的代码如下: ...

  9. windows程序设计03_读取utf8文件

    这里用到的读取utf8文件的思路特别朴素.先把utf8文件按char读取到内存里.因为utf8是变长的,为了处理方便,在内存里把char转化成wchar_t,这样一个字符就是一个wchar_t.把ut ...

  10. 用FPGA设计LCD 转 VGA

    这个东西其实是在上一个冬天就做完了,而且似乎已经产业化了,当时是为一位朋友做的,这个朋友再卖给产业化的人,就像流于俗套的故事一样,这个朋友拿到了钱,不过不像项目开始时说的那样与我有关.想想多年前一起吃 ...