壹 ❀ 引
在本文之前我已经花了两个篇幅专门介绍了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的更多相关文章
-
两道面试题,带你解析Java类加载机制
文章首发于[博客园-陈树义],点击跳转到原文<两道面试题,带你解析Java类加载机制> 在许多Java面试中,我们经常会看到关于Java类加载机制的考察,例如下面这道题: class Gr ...
-
【转】两道面试题,带你解析Java类加载机制(类初始化方法 和 对象初始化方法)
本文转自 https://www.cnblogs.com/chanshuyi/p/the_java_class_load_mechamism.html 关键语句 我们只知道有一个构造方法,但实际上Ja ...
-
你所不知道的库存超限做法 服务器一般达到多少qps比较好[转] JAVA格物致知基础篇:你所不知道的返回码 深入了解EntityFramework Core 2.1延迟加载(Lazy Loading) EntityFramework 6.x和EntityFramework Core关系映射中导航属性必须是public? 藏在正则表达式里的陷阱 两道面试题,带你解析Java类加载机制
你所不知道的库存超限做法 在互联网企业中,限购的做法,多种多样,有的别出心裁,有的因循守旧,但是种种做法皆想达到的目的,无外乎几种,商品卖的完,系统抗的住,库存不超限.虽然短短数语,却有着说不完,道不 ...
-
js中的this和箭头函数中的this
一.ES6 允许使用"箭头"(=>)定义函数. // var f = v => v;// 上面的箭头函数等同于: // var f = function(v) {// ...
-
深入理解ES6箭头函数中的this
简要介绍:箭头函数中的this,指向与一般function定义的函数不同,比较容易绕晕,箭头函数this的定义:箭头函数中的this是在定义函数的时候绑定,而不是在执行函数的时候绑定. 1.何为定义时 ...
-
【js基础修炼之路】— 深入浅出理解闭包
之前对于闭包的理解只是很肤浅的,只是浮于表面,这次深究了一下闭包,下面是我对闭包的理解. 什么是闭包? 引用高程里的话 => 闭包就是有权访问另一个作用域中变量的函数,闭包是由函数以及创建该函数 ...
-
[转]从两道经典试题谈C/C++中联合体(union)的使用
宋宝华 21cnbao sweek@21cn.com 试题一:编写一段程序判断系统中的CPU是Little endian还是Big endian模式? 分析: 作为一个计算机相关专业的人,我们应该在计 ...
-
Java中创建String的两道面试题及详解
我们知道创建一个String类型的变量一般有以下两种方法: String str1 = "abcd"; String str2 = new String("abcd&qu ...
-
三. var let const的理解 以及 立即执行函数中的使用 以及 for循环中的例子
一. 立即执行函数 windows中有个name属性,name='' '' var 如果我们用var name 去声明,那就会改变windows中name的值(因为我们不是在函数作用域中声明的,所以会 ...
随机推荐
-
MiniTwitter记住密码等功能实现
一.SharedPreferences的用法:(相关实现功能的只是了解) 由于SharedPreferences是一个接口,而且在这个接口里没有提供写入数据和读取数据的能力.但它是通过其Editor接 ...
-
singleton和prototype
public class testScope { @Test public void test() { //默任singleton ApplicationContext ctx= new ClassP ...
-
Myeclipse报PermGen space异常的问题
最好用的方法: 1)在myeclipse中windos——>preference——>Myeclipse——>servers——>Tomcat——>JDK(Optiona ...
-
CC2540开发板学习笔记(三)&mdash;&mdash;外部中断
一.实验内容 通过外部中断方式依次按下按键S1控制LED1的亮灭 二.实验过程 1.电路原理图同上 2.中断的概念 比如说我们在执行main函数时,突然来了个指令.优先级比现在执行的main还高,那我 ...
- Xib文件的使用
-
HIP-HOP 漫画家 Skottie Young
http://blog.sina.com.cn/s/blog_67e59b160100v9ib.html 以上是部分预览图! 下载地址: 29 MB= 127 张 yy语 ...
-
恢复oracle 11g 的System及sys用户的密码
进入E:\app\orcl\product\11.2.0\dbhome_1\database目录下找到PWDorcl.ora备份后删除文件,orcl是数据库的实例名 以管理员身份打开cmd,执行 or ...
-
pandas 基本操作
1. 一维数据结构Series a. 概念:Series 是pandas 的一维数据结构,有重要的两个属性 index 和values b. 初始化: 可以通过 python 的 Lis ...
-
将map中的值赋值给一个java对象
Map tag=new HashMap(); tag.put("001"," 张三"); tag.put("002","李四&qu ...
-
Chrome Debugger 温故而知新:上下文环境
最早是在IOS开发中看到过这种调试方式.在无意间发现Chrome Debugger也可以.直接上图: 解释:默认的控制台想访问变量.都是只能访问全局的.但当我们用debugger; 断点进入到内部时, ...