-
Notifications
You must be signed in to change notification settings - Fork 838
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
一道JS面试题所引发的"血案",透过现象寻本质,再从本质看现象 #3
Comments
上次看完波同学的this讲解,这次看你的又加深了印象 |
学习本来就是一个重复然后加深的过程 |
写的真好,文笔好,思路也清晰 |
吐血,本来以为十分钟看的完.........怎么样才能有耐心的看完一篇技术文呢? |
少看小说,多读书,哈哈😄@MrZWH |
写得很清楚,很赞,感谢。先收藏起来,多看几次。 |
彩虹螺旋懵逼 |
实在不明白,像 JS 中的 var、this 等产生的坑,还有闭包,这似乎是每个 JS 开发者都应该知道吧,但经常看到一些这样的面试题,而很多同学都不知道。 对于任何一门编程语言,不要相信那些 Hello 样例,那只是争取你到其阵营的银弹,真正实战中,那些 Hello 没有任何实用。看了这些多前端面试题,觉得至少有一部人还处在 Hello 阶段。 我不知道这是不是前端与后端思维的不同所造成的。 PS:前几天看到一篇文章,一个前端转后端后,说是思维不同造成了一些障碍,原本没深以为意,但这两天和前端的交流中,确实碰到了。 PS:我的主要工作是做后端的,但也学过 ES3、ES5、ES6、NodeJS、React、ReactNative(部分)、Redux、VueJS(部分)。。。,觉得上述问题,只要买一本 JS 方面的书,基本都会讲到。但还是有很多人不懂,不知道是没看,还是没看。 PS:以上只是一家之言。 |
感谢反馈提出自己的见解,github是这样回复吗?试一试@yhhwpp |
@jawil 我的理解是这样的: 并且 |
建议楼主读读ECMASCript5.1规范(点击查看),搜搜可执行代码与执行环境,对于这一块因为理论描述的太抽象,鄙人也没有过深的见解,楼主可以看看里面关于作用域和执行环境的解读再来验证你的理解是否有偏差,并且对于作用域链本质上是一个指向变量对象的指针列表这句话该怎么理解?@yhhwpp |
又是一篇用心的博客,再来点个赞 |
最近刚从后端转前端 看了一下JS语法,吐槽点有点多,但是我刚看了下 文中练习4的解释 |
关于this的指向 其实很简单 , 因为this的指向只与函数的调用模式有关
3.如果是以构造器模式调用,则this会被绑定到新的对象 在ES6新语法中 construct()方法中 this都是默认绑向新对象。 |
博主 有个问题请教下 一个函数中有个参数是异步的,用这个参数的时候怎么等待它加载完成啊? 我知道用promise 但是具体怎么写?怎么实现 伪代码怎么写?有写的文章推荐看看吗 |
@jawil 博主看到一个列子 } 为什么输出是12 难道这个也是独立调用??帮忙解释解释 |
var t = function(){ 关于this的去研究这篇文章JavaScript深入之从ECMAScript规范解读this |
虽然全知道答案,但是硬让我解释的话我是说不了这么一长串的,而且第一个例子是很简单的,不需要这么大篇幅为后面做铺垫的,还是向楼主学习 |
所以有了let... 另外推荐本书:You don‘t konw javascript |
@fulvaz know |
如果按楼主的解释,那这个应该是多少 function active(fn) { var a = 20; |
function active(fn) {
fn(); // 真实调用者,为独立调用
}
var a = 20;
var obj = {
a: 10,
getA: function(){
console.log(this.a);
}
}
active(obj.getA); 这个肯定是20,当active(obj.getA)执行时候,内部其实有一个赋值的过程,也就是fn = obj.getA,当fn执行时候,这时候的this已经是全局window对象了 |
楼主终于出现了~ |
专心做业务,以后写写业务方面的感受,顺便发发邀请码😂。@mqyqingfeng |
@jawil 那这个就跟函数参数传递方式没关系了吧,这个fn()无论如何都是全局调用的 |
var a = 20;
var obj = {
a: 10,
getA: function(){
console.log(this.a);
}
}
active();
function active() {
obj.getA()
} obj.getA() 就不是全局调用了么。 你可以看看这打印的结果: var a = 20;
var obj = {
a: 10,
getA: function(){
console.log(this.a);
}
}
active(obj.getA);
function active(fn) {
console.log(fn === obj.getA )
fn(); // 真实调用者,为独立调用
} 再来看看这个 var a = 20;
var obj = {
a: 10,
getA: function(){
console.log(this.a);
}
}
active();
function active() {
this.obj.getA()
} 两个结果一样吧,关于this这个比较抽象,可以让 @mqyqingfeng 大神细细道来,我可能理解有误 |
楼主太谦虚了, 最近在看你博客受益匪浅 ,让我这毕业几年的情何以堪啊 |
(function(){
var a;//a是局部变量
a = 5;//这里局部环境中有a,就不会找全局中的
console.log(window.a);//undefined
a = 1;//这里会发生变量声明提升
console.log(a);//1
})();
console.log(window.a) //undefined 请问大大, |
@AngellinaZ 谁告诉你a是全局的? var a =1会把a提升到最上面,当a=5赋值时候能够找到这个a的声明,所以a不会挂载到window对象上去。 |
get谢谢 |
先慢慢看,感觉很不错,谢谢。 |
javascript 语言精粹对于this这一块讲的很清晰。 https://github.com/lin-123/Javascript/blob/master/Function.md。 不多对于函数内嵌的函数this为什么会指向全局,这个感觉很怪异。 用《js语言精粹》作者的话说, 不知大家怎么看 |
有点啰嗦 |
学习了!服服服 |
精益求精,受益颇深 |
感觉就是this的指向问题,只有在调用的时候才会确定this的指向,谁调用方法this就是谁的 |
我觉得不是,当程序开始了,系统就会实例一个对象,window对象,他是个对象,this就是window对象。 |
觉得本人写的不算很烂的话,可以登录关注一下我的GitHub博客,新手写东西写的不好之处,还望见谅,毕竟水平有限,写东西只为交流提高,一起学习,还望大神多加指点,指出纰漏,和提出宝贵的意见,博客会坚持写下去。
今天同学去面试,做了两道面试题,全部做错了,发过来给我看,我一眼就看出来了,因为这种题我做过,至于为什么结果是那样,我也之前没有深究过,他问我为什么,我也是一脸的懵逼,不能从根源上解释问题的原因,所以并不能完全让他信服。今天就借着这个机会深扒一下,如果没有耐心可以点击右上角,以看小说的心态看技术文章,走马观花,不加思考,这样的量变并不能带来质的改变。花上10+分钟认真阅读我相信你会受益匪浅,没收获你买把武昌火车站同款菜刀砍我😄。因为我是写完这篇博客再回头写这段话的,在写的过程中也学到了很多,所以在此分享一下共同学习。
登高自卑,与君共勉。
下面一起看看这道题,同学微信发给我截图:

如果看的不太清楚,我把代码敲一遍,给大家看看:
这里我就不卖关子了,不少童鞋也应该遇到过做过类似的题目,就是考察this,我们先看看答案:
第一个很简单,this就是指向person.pro的引用,那么this.name就是person.pro.name,于是第一个就是输出Michael,再来看看第二个就蹊跷了,和第一个明明是一样的方法,为什么输出的结果是jay呢?
既然我们知道结果是jay了,反着推理一步步来,不难推出调用people()这个方法时候的this.name就相当于和var name = "jay",var声明的全局变量和全局环境下的this的变量有什么联系呢?;那么这个this到底是什么,总得是一个具体东西吧?
我们一步步分析,this.name这个this有一个name属性,很明显就是一个对象,那具体是什么对象呢?this的指向是在函数被调用的时候确定的,于是有人说就是Window对象,没错是没错,确实是Window对象,然后var name声明的全局变量name和window.name是相同的作用;但是你只只知其然,而不知其所以然,学深一门语言就是要有刨根问底的精神,打破砂锅问到底,知其然还要知其所以然。
我们就先验证一下,那个this到底是不是window对象吧。我们把代码稍微调整一下,输出this。
看看控制台输出,确实没错就是window对象。
再来看看var name声明的name和window.name是否相等呢?
确实是一样的,类型和值没有任何的不同。
好滴,那么你说this就是window对象,至于为什么是这样你也不清楚,是否永远是这样呢?我们看看这段代码输出又会是咋样呢?
还会是跟上面一样的结果吗?我们拭目以待.
看到结果没:Cannot read property 'name' of undefined,这是什么意思想必大家已经很清楚了,此时的this成了undefined了,undefined当然也就没有name这个属性,所以浏览器报错了。那么为什么会这样呢?
同样换种写法再来看看这段代码输出什么呢?
控制台自己输出一下看看,我想此时你的心情一定是这样的:
在弄明白这些问题之前,我们先弄清楚全局环境下的this,var声明的全局变量和window对象之间的联系与区别:
先看四个简单的例子对比,均在js非严格模式测试,也就是没有声明'use strict':
demo1:
demo2:
demo3:
demo4:
其实这四个demo是一个意思,输出的结果没有任何差别,为什么没有差别呢?因为他们在同一个环境,也就是全局环境下:
我们换一种在不同的环境下执行这段代码看一看结果:
demo5:
最后结果一次输出为:
因为此处的this不再指向全局对象了,所以结果肯定不同,我们先来看看全局对象和全局环境下的this,暂不考虑其他环境下的this。
那么又有人会问什么是全局环境,什么又是全局对象,全局对象该怎么理解?
题外话
其实我们看技术文章,总觉得似懂非懂,一知半解,不是看不懂代码,而是因为很多时候我们对一些概念没有比较深入的了解,但是也没有去认真继续下去考究,这也不能怪我们,毕竟开发时候不太深入这些概念对我们业务也没啥影响,但是我发现我自己写东西时候,不把概念说清楚,总不能让人信服和彻底明白你讲的是什么玩意,我想写博客最大的好处可以让自己进一步提高,更深层次的理解你所学过的东西,你讲的别人都看不懂,你确认你真的懂了吗?
说到全局环境,我们就会牵扯到另一个概念那就是执行环境和函数的作用域
既然扯到这么深,就顺便扯扯执行环境和作用域,这些都是js这门语言的重点和难点,没有一定的沉淀很难去深入探讨这些东西的.
函数的每次调用都有与之紧密相关的作用域和执行环境。从根本上来说,作用域是基于函数的,而执行环境是基于对象的(例如:全局执行环境即全局对象window)。
我们还是先说一说全局对象吧,因为全局执行环境是基于全局对象的。
JavaScript 全局对象
全局属性和函数可用于所有内建的 JavaScript 对象。
全局对象描述
例子
在 JavaScript 核心语言中,全局对象的预定义属性都是不可枚举的,所有可以用 for/in 循环列出所有隐式或显式声明的全局变量,如下所示:
上一篇博客我就讲到遍历对象属性的三种方法:
for-in
循环、Object.keys()
以及Object.getOwnPropertyNames()
不同的区别,想要了解可以细看我这篇博客:传送门再回过头来谈谈执行环境和函数的作用域
一开始要明白的
一些概念
1. 执行环境(也称执行上下文–execution context)
首先来说说js中的执行环境,所谓执行环境(有时也称环境)它是JavaScript中最为重要的一个概念。执行环境定义了变量或函数有权访问的其他数据 ,决定了它们各自的行为。而每个执行环境都有一个与之相关的变量对象,环境中定义的所有变量和函数都保存在这个对象中。
当JavaScript解释器初始化执行代码时,它首先默认进入全局执行环境,从此刻开始,函数的每次调用都会创建一个新的执行环境。
每个函数都有自己的执行环境。当执行流进入一个函数时,函数的环境就会被推入一个环境栈中(execution stack)。在函数执行完后,栈将其环境弹出,把控制权返回给之前的执行环境。ECMAScript程序中的执行流正是由这个便利的机制控制着。执行环境可以分为创建和执行两个阶段。在创建阶段,解析器首先会创建一个变量对象(variable object,也称为活动对象activation object),它由定义在执行环境中的变量、函数声明、和参数组成。在这个阶段,作用域链会被初始化,this的值也会被最终确定。在执行阶段,代码被解释执行。
1.1可执行的JavaScript代码分三种类型:
不同类型的JavaScript代码具有不同的Execution Context
Demo:
特别说明:图片来自于笨蛋的座右铭博客
1.2执行环境小结
当javascript代码被浏览器载入后,默认最先进入的是一个全局执行环境。当在全局执行环境中调用执行一个函数时,程序流就进入该被调用函数内,此时JS引擎就会为该函数创建一个新的执行环境,并且将其压入到执行环境堆栈的顶部。浏览器总是执行当前在堆栈顶部的执行环境,一旦执行完毕,该执行环境就会从堆栈顶部被弹出,然后,进入其下的执行环境执行代码。这样,堆栈中的执行环境就会被依次执行并且弹出堆栈,直到回到全局执行环境。
此外还要注意一下几点:
2. 作用域(scope)
当代码在一个环境中执行时,会创建变量对象的一个作用域链(scope chain。作用域链的用途是保证对执行环境有权访问的所有变量和函数的有序访问。
作用域链包含了执行环境栈中的每个执行环境对应的变量对象.
通过作用域链,可以决定变量的访问和标识符的解析。
注意:全局执行环境的变量对象始终都是作用域链的最后一个对象。
在访问变量时,就必须存在一个可见性的问题(内层环境可以访问外层中的变量和函数,而外层环境不能访问内层的变量和函数)。更深入的说,当访问一个变量或调用一个函数时,JavaScript引擎将不同执行环境中的变量对象按照规则构建一个链表,在访问一个变量时,先在链表的第一个变量对象上查找,如果没有找到则继续在第二个变量对象上查找,直到搜索到全局执行环境的变量对象即window对象。这也就形成了Scope Chain的概念。
特别说明:图片来自于笨蛋的座右铭博客
作用域链图,清楚的表达了执行环境与作用域的关系(一一对应的关系),作用域与作用域之间的关系(链表结构,由上至下的关系)。
Demo:
上述代码一共包括三个执行环境:全局执行环境、changeColor()的局部执行环境、swapColors()的局部执行环境。
上述代码的作用域链如下图所示:

从上图发现。内部环境可以通过作用域链访问所有的外部环境,但是外部环境不能访问内部环境中的任何变量和函数。
标识符解析(变量名或函数名搜索)是沿着作用域链一级一级地搜索标识符的过程。搜索过程始终从作用域链的前端开始,然后逐级地向后(全局执行环境)回溯,直到找到标识符为止。
3.执行环境与作用域的区别与联系
执行环境为全局执行环境和局部执行环境,局部执行环境是函数执行过程中创建的。
作用域链是基于执行环境的变量对象的,由所有执行环境的变量对象(对于函数而言是活动对象,因为在函数执行环境中,变量对象是不能直接访问的,此时由活动对象(activation object,缩写为AO)扮演VO(变量对象)的角色。)共同组成。
当代码在一个环境中执行时,会创建变量对象的一个作用域链。作用域链的用途:是保证对执行环境有权访问的所有变量和函数的有序访问。作用域链的前端,始终都是当前执行的代码所在环境的变量对象。
4.小练习
window.a之所以是undefined,是因为var a = 1;发生了变量声明提升。相当于如下代码:
更多关于变量提升和执行上下文详细解说这里就不多少了,不然越扯越深,有兴趣可以看看这篇图解,浅显易懂:
前端基础进阶(二):执行上下文详细图解
相信大家看到这里,也很累了,但是也有收获,大概有了一些深刻印象,对概念也有一些比较深入的理解了。
这里我就稍微总结一下,上面讲了一些什么,对接下来的解析应该有很大的帮助。
1. 浏览器的全局对象是window
2. 全局执行环境即window对象所创建的,局部执行环境是函数执行过程中创建的。
3. 全局对象,可以访问所有其他所有预定义的对象、函数和属性。
4. 当javascript代码被浏览器载入后,默认最先进入的是一个全局执行环境。
5. 明白了执行上下文和作用域的一些概念,知道其中的运行机制和原理。
我们再回头看看这两个demo比较,我们解释清楚这个demo执行的结果。
demo1:
demo2:
好,从例子看以看出,这两个name都是全局属性,未通过var声明的变量a和通过var声明的变量b,都可以通过this和window访问到.
我们可以在控制台打印出windowd对象,发现name成了window对象的一个属性:
这是其实一个作用域和上下文的问题。在JavaScript中,this指向当前的上下文,而var定义的变量值在当前作用域中有效。JavaScript有两种作用域,全局作用域和局部作用域。局部作用域就是在一个函数里。var关键字使用来在当前作用于中创建局部变量的,而在浏览器中的JavaScript全局作用域中使用var语句时,会把申明的变量挂在window上,而全局作用域中的this上下文恰好指向的又是window,因此在全局作用域中var申明的变量和window上挂的变量,即this可访问的变量有间接的联系,但没有直接联系,更不是一样的。
上面的分析我们知道了,全局变量,全局环境下的this,还有全局对象之间的关系了,具体总结一下就是:
1. 全局环境的this会指向全局对象window,此时this===window;
2. 全局变量会挂载在window对象下,会成为window下的一个属性。
3. 如果你没有使用严格模式并给一个未声明的变量赋值的话,JS会自动创建一个全局变量。
那么用var声明的全局变量赋值和未声明的全局变量赋值到底有什么不同呢?这里不再是理解理解这道面试题的重点,想深入探究可以看看这篇文章:javascript中加var和不加var的区别 你真的懂吗.
该回头了,好累😫,再来看看这道面试题:

最后就成了为什么person.pro.getName()的this是person.pro而pepole()的this成了window对象。这里我们就要了解this的运行机制和原理。
在这里,我们需要得出一个非常重要一定要牢记于心的结论,this的指向,是在函数被调用的时候确定的。也就是执行上下文被创建时确定的。因此我们可以很容易就能理解到,一个函数中的this指向,可以是非常灵活的。
在一个函数上下文中,this由调用者提供,由调用函数的方式来决定。
如果调用者函数,被某一个对象所拥有,那么该函数在调用时,内部的this指向该对象。如果函数独立调用,那么该函数内部的this,则指向undefined。但是在非严格模式中,当this指向undefined时,它会被自动指向全局对象。
person.pro.getName()中,getName是调用者,他不是独立调用,被对象person.pro所拥有,因此它的this指向了person.pro。而pepole()作为调用者,尽管他与person.pro.getName的引用相同,但是它是独立调用的,因此this指向undefined,在非严格模式,自动转向全局window。
再来看一个例子,来加深理解这段话:
灵机一动,再来一个。如下例子。
这个例子提示一下,关于函数参数的传递赋值问题。
JS是按值传递还是按引用传递?
这里我就不多做解答了,大家自行揣摩。
以上关于this解答来自波同学的引用,我这里就偷了个懒在,直接拿来引用。
原文地址:前端基础进阶(五):全方位解读this
最后把知道面试题梳理一下:
person.pro.getName()中,getName是调用者,他不是独立调用,被对象person.pro所拥有,因此它的this指向了person.pro,所以this.name=person.pro.name="Michael";
而pepole()作为调用者,尽管他与person.pro.getName的引用相同,但是它是独立调用的,因此this指向undefined,在非严格模式,自动转向全局window。
这道题实在非严格模式下,所以this指向了window,又因为全局变量挂载在window对象下,所以this.name=window.name=“jay”
完毕~写的有点啰嗦,只是尽量想说明白,讲清一些概念的东西,反正我是收获很多,你呢?
参考文章:
JavaScript 全局对象
原生JS执行环境与作用域深入理解
理解Javascript_12_执行模型浅析
前端基础进阶(二):执行上下文详细图解
前端基础进阶(五):全方位解读this
The text was updated successfully, but these errors were encountered: