Skip to content
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

Open
jawil opened this issue Feb 26, 2017 · 39 comments

Comments

@jawil
Copy link
Owner

jawil commented Feb 26, 2017

觉得本人写的不算很烂的话,可以登录关注一下我的GitHub博客,新手写东西写的不好之处,还望见谅,毕竟水平有限,写东西只为交流提高,一起学习,还望大神多加指点,指出纰漏,和提出宝贵的意见,博客会坚持写下去。

今天同学去面试,做了两道面试题,全部做错了,发过来给我看,我一眼就看出来了,因为这种题我做过,至于为什么结果是那样,我也之前没有深究过,他问我为什么,我也是一脸的懵逼,不能从根源上解释问题的原因,所以并不能完全让他信服。今天就借着这个机会深扒一下,如果没有耐心可以点击右上角,以看小说的心态看技术文章,走马观花,不加思考,这样的量变并不能带来质的改变。花上10+分钟认真阅读我相信你会受益匪浅,没收获你买把武昌火车站同款菜刀砍我😄。因为我是写完这篇博客再回头写这段话的,在写的过程中也学到了很多,所以在此分享一下共同学习。

登高自卑,与君共勉。

下面一起看看这道题,同学微信发给我截图:

如果看的不太清楚,我把代码敲一遍,给大家看看:

var name = "jay"; //一看这二逼就是周杰伦的死忠粉
var person = {
    name: "kang",
    pro: {
        name: "Michael",
        getName: function() {
            return this.name;
        }
    }
};
console.log(person.pro.getName());
var pepole = person.pro.getName;
console.log(pepole());

这里我就不卖关子了,不少童鞋也应该遇到过做过类似的题目,就是考察this,我们先看看答案:

console.log(person.pro.getName());//Michael
console.log(pepole());//jay

第一个很简单,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声明的全局变量namewindow.name是相同的作用;但是你只只知其然,而不知其所以然,学深一门语言就是要有刨根问底的精神,打破砂锅问到底,知其然还要知其所以然

我们就先验证一下,那个this到底是不是window对象吧。我们把代码稍微调整一下,输出this

var name = "jay"; //一看这二逼就是周杰伦的死忠粉
var person = {
    name: "kang",
    pro: {
        name: "Michael",
        getName: function() {
            console.log(this);
            return this.name;
        }
    }
};
console.log(person.pro.getName());
var pepole = person.pro.getName;
console.log(pepole());

看看控制台输出,确实没错就是window对象。

再来看看var name声明的name和window.name是否相等呢?

var name;
console.log(name===window.name)

确实是一样的,类型和值没有任何的不同。

好滴,那么你说this就是window对象,至于为什么是这样你也不清楚,是否永远是这样呢?我们看看这段代码输出又会是咋样呢?

'use strict';
var name = "jay"; //一看这二逼就是周杰伦的死忠粉
var person = {
    name: "kang",
    pro: {
        name: "Michael",
        getName: function() {
            console.log(this);
            return this.name;
        }
    }
};
console.log(person.pro.getName());
var pepole = person.pro.getName;
console.log(pepole());

还会是跟上面一样的结果吗?我们拭目以待.

看到结果没:Cannot read property 'name' of undefined,这是什么意思想必大家已经很清楚了,此时的this成了undefined了,undefined当然也就没有name这个属性,所以浏览器报错了。那么为什么会这样呢?

同样换种写法再来看看这段代码输出什么呢?

var name = "jay";
var person = {
    name : "kang",
    getName : function(){
     return function(){
        return this.name;
     };
    }
};
console.log(person.getName()());

控制台自己输出一下看看,我想此时你的心情一定是这样的:

在弄明白这些问题之前,我们先弄清楚全局环境下的thisvar声明的全局变量window对象之间的联系与区别:
先看四个简单的例子对比,均在js非严格模式测试,也就是没有声明'use strict':
demo1:

var name="jawil";
console.log(name);
console.log(window.name)
console.log(this.name)

demo2:

name="jawil";
console.log(name);
console.log(window.name)
console.log(this.name)

demo3:

window.name="jawil";
console.log(name);
console.log(window.name)
console.log(this.name)

demo4:

this.name="jawil";
console.log(name);
console.log(window.name)
console.log(this.name)

其实这四个demo是一个意思,输出的结果没有任何差别,为什么没有差别呢?因为他们在同一个环境,也就是全局环境下:
我们换一种在不同的环境下执行这段代码看一看结果:
demo5:

var name="jawil";
var test={
    name:'jay',
    getName:function(){
    console.log(name);
    console.log(window.name)
    console.log(this.name)
    }
}
test.getName();

最后结果一次输出为:

console.log(name);//jawil
console.log(window.name)//jawil
console.log(this.name)//jay

因为此处的this不再指向全局对象了,所以结果肯定不同,我们先来看看全局对象全局环境下的this,暂不考虑其他环境下的this

那么又有人会问什么是全局环境,什么又是全局对象,全局对象该怎么理解?

题外话

其实我们看技术文章,总觉得似懂非懂,一知半解,不是看不懂代码,而是因为很多时候我们对一些概念没有比较深入的了解,但是也没有去认真继续下去考究,这也不能怪我们,毕竟开发时候不太深入这些概念对我们业务也没啥影响,但是我发现我自己写东西时候,不把概念说清楚,总不能让人信服和彻底明白你讲的是什么玩意,我想写博客最大的好处可以让自己进一步提高,更深层次的理解你所学过的东西,你讲的别人都看不懂,你确认你真的懂了吗?

说到全局环境,我们就会牵扯到另一个概念那就是执行环境和函数的作用域

既然扯到这么深,就顺便扯扯执行环境和作用域,这些都是js这门语言的重点和难点,没有一定的沉淀很难去深入探讨这些东西的.

函数的每次调用都有与之紧密相关的作用域和执行环境。从根本上来说,作用域是基于函数的,而执行环境是基于对象的(例如:全局执行环境即全局对象window)。

我们还是先说一说全局对象吧,因为全局执行环境是基于全局对象的。

JavaScript 全局对象

全局属性和函数可用于所有内建的 JavaScript 对象。

全局对象描述

  1. 全局对象是预定义的对象,作为 JavaScript 的全局函数和全局属性的占位符。通过使用全局对象,可以访问所有其他所有预定义的对象、函数和属性。全局对象不是任何对象的属性,所以它没有名称。
  1. 在顶层 JavaScript 代码中,可以用关键字 this 引用全局对象。但通常不必用这种方式引用全局对象,因为全局对象是作用域链的头,这意味着所有非限定性的变量和函数名都会作为该对象的属性来查询。例如,当JavaScript 代码引用 parseInt() 函数时,它引用的是全局对象的 parseInt 属性。全局对象是作用域链的头,还意味着在顶层 JavaScript 代码中声明的所有变量都将成为全局对象的属性。
  1. 全局对象只是一个对象,而不是类。既没有构造函数,也无法实例化一个新的全局对象。
  1. 在 JavaScript 代码嵌入一个特殊环境中时,全局对象通常具有环境特定的属性。实际上,ECMAScript 标准没有规定全局对象的类型,JavaScript 的实现或嵌入的 JavaScript 都可以把任意类型的对象作为全局对象,只要该对象定义了这里列出的基本属性和函数。例如,在允许通过 LiveConnect 或相关的技术来脚本化 Java 的 JavaScript 实现中,全局对象被赋予了这里列出的 java 和 Package 属性以及 getClass() 方法。而在客户端 JavaScript 中,全局对象就是 Window 对象,表示允许 JavaScript 代码的 Web 浏览器窗口。

例子

在 JavaScript 核心语言中,全局对象的预定义属性都是不可枚举的,所有可以用 for/in 循环列出所有隐式或显式声明的全局变量,如下所示:
上一篇博客我就讲到遍历对象属性的三种方法:

for-in循环、Object.keys()以及Object.getOwnPropertyNames()不同的区别,想要了解可以细看我这篇博客:传送门

var variables = "";

for (var name in this)
{
variables += name + "<br />";
}

document.write(variables);

再回过头来谈谈执行环境和函数的作用域

一开始要明白的

  • 首先,我们要知道执行环境和作用域是两个完全不同的概念。
  • 函数的每次调用都有与之紧密相关的作用域和执行环境。从根本上来说,作用域是基于函数类型的(当然函数也是对象,这里我们细分一下),而执行环境是基于对象类型的(例如:全局执行环境即window对象)。
  • 换句话说,作用域涉及到所被调用函数中的变量访问,并且不同的调用场景是不一样的。执行环境始终是this关键字的值,它是拥有当前所执行代码的对象的引用。每个执行环境都有一个与之关联的变量对象,环境中定义的所有变量和函数都保存在这个对象中。虽然我们编写的代码无法访问这个对象,但解析器在处理数据时会在后台使用它。

一些概念

1. 执行环境(也称执行上下文–execution context)

首先来说说js中的执行环境,所谓执行环境(有时也称环境)它是JavaScript中最为重要的一个概念。执行环境定义了变量或函数有权访问的其他数据 ,决定了它们各自的行为。而每个执行环境都有一个与之相关的变量对象,环境中定义的所有变量和函数都保存在这个对象中。

当JavaScript解释器初始化执行代码时,它首先默认进入全局执行环境,从此刻开始,函数的每次调用都会创建一个新的执行环境。

每个函数都有自己的执行环境。当执行流进入一个函数时,函数的环境就会被推入一个环境栈中(execution stack)。在函数执行完后,栈将其环境弹出,把控制权返回给之前的执行环境。ECMAScript程序中的执行流正是由这个便利的机制控制着。执行环境可以分为创建和执行两个阶段。在创建阶段,解析器首先会创建一个变量对象(variable object,也称为活动对象activation object),它由定义在执行环境中的变量、函数声明、和参数组成。在这个阶段,作用域链会被初始化,this的值也会被最终确定。在执行阶段,代码被解释执行。

1.1可执行的JavaScript代码分三种类型:
  1. Global Code,即全局的、不在任何函数里面的代码,例如:一个js文件、嵌入在HTML页面中的js代码等。
  2. Eval Code,即使用eval()函数动态执行的JS代码。
  3. Function Code,即用户自定义函数中的函数体JS代码。

不同类型的JavaScript代码具有不同的Execution Context

Demo:

<script type="text/javascript">
    function Fn1(){
        function Fn2(){
            alert(document.body.tagName);//BODY
            //other code...
        }
        Fn2();
    }
    Fn1();
    //code here
</script>


特别说明:图片来自于笨蛋的座右铭博客

1.2执行环境小结

当javascript代码被浏览器载入后,默认最先进入的是一个全局执行环境。当在全局执行环境中调用执行一个函数时,程序流就进入该被调用函数内,此时JS引擎就会为该函数创建一个新的执行环境,并且将其压入到执行环境堆栈的顶部。浏览器总是执行当前在堆栈顶部的执行环境,一旦执行完毕,该执行环境就会从堆栈顶部被弹出,然后,进入其下的执行环境执行代码。这样,堆栈中的执行环境就会被依次执行并且弹出堆栈,直到回到全局执行环境。
此外还要注意一下几点:

  • 单线程
  • 同步执行
  • 唯一的全局执行环境
  • 局部执行环境的个数没有限制
  • 每次某个函数被调用,就会有个新的局部执行环境为其创建,即使是多次调用的自身函数(即一个函数被调用多次,也会创建多个不同的局部执行环境)。
2. 作用域(scope)

当代码在一个环境中执行时,会创建变量对象的一个作用域链(scope chain。作用域链的用途是保证对执行环境有权访问的所有变量和函数的有序访问。

作用域链包含了执行环境栈中的每个执行环境对应的变量对象.
通过作用域链,可以决定变量的访问和标识符的解析。
注意:全局执行环境的变量对象始终都是作用域链的最后一个对象。

在访问变量时,就必须存在一个可见性的问题(内层环境可以访问外层中的变量和函数,而外层环境不能访问内层的变量和函数)。更深入的说,当访问一个变量或调用一个函数时,JavaScript引擎将不同执行环境中的变量对象按照规则构建一个链表,在访问一个变量时,先在链表的第一个变量对象上查找,如果没有找到则继续在第二个变量对象上查找,直到搜索到全局执行环境的变量对象即window对象。这也就形成了Scope Chain的概念。


特别说明:图片来自于笨蛋的座右铭博客

作用域链图,清楚的表达了执行环境与作用域的关系(一一对应的关系),作用域与作用域之间的关系(链表结构,由上至下的关系)。
Demo:

var color = "blue";
function changeColor(){
  var anotherColor = "red";
  function swapColors(){
    var tempColor = anotherColor;
    anotherColor = color;
    color = tempColor;
    // 这里可以访问color, anotherColor, 和 tempColor
  }
  // 这里可以访问color 和 anotherColor,但是不能访问 tempColor
  swapColors();
}
changeColor();
// 这里只能访问color
console.log("Color is now " + color);

上述代码一共包括三个执行环境:全局执行环境、changeColor()的局部执行环境、swapColors()的局部执行环境。

  • 全局环境有一个变量color和一个函数changecolor();
  • changecolor()函数的局部环境中具有一个anothercolor属性和一个swapcolors函数,当然,changecolor函数中可以访问自身以及它外围(即全局环境)中的变量;
  • swapcolor()函数的局部环境中具有一个变量tempcolor。在该函数内部可以访问上面的两个环境(changecolor和window)中的所有变量,因为那两个环境都是它的父执行环境。

上述代码的作用域链如下图所示:

从上图发现。内部环境可以通过作用域链访问所有的外部环境,但是外部环境不能访问内部环境中的任何变量和函数。
标识符解析(变量名或函数名搜索)是沿着作用域链一级一级地搜索标识符的过程。搜索过程始终从作用域链的前端开始,然后逐级地向后(全局执行环境)回溯,直到找到标识符为止。

3.执行环境与作用域的区别与联系

执行环境为全局执行环境和局部执行环境,局部执行环境是函数执行过程中创建的。
作用域链是基于执行环境的变量对象的,由所有执行环境的变量对象(对于函数而言是活动对象,因为在函数执行环境中,变量对象是不能直接访问的,此时由活动对象(activation object,缩写为AO)扮演VO(变量对象)的角色。)共同组成。
当代码在一个环境中执行时,会创建变量对象的一个作用域链。作用域链的用途:是保证对执行环境有权访问的所有变量和函数的有序访问。作用域链的前端,始终都是当前执行的代码所在环境的变量对象。

4.小练习
<script type="text/javascript">
(function(){
    a= 5;
    console.log(window.a);//undefined
    var a = 1;//这里会发生变量声明提升
    console.log(a);//1
})();
</script>

window.a之所以是undefined,是因为var a = 1;发生了变量声明提升。相当于如下代码:

<script type="text/javascript">
(function(){
    var a;//a是局部变量
    a = 5;//这里局部环境中有a,就不会找全局中的
    console.log(window.a);//undefined
    a = 1;//这里会发生变量声明提升
    console.log(a);//1
})();
</script>

更多关于变量提升和执行上下文详细解说这里就不多少了,不然越扯越深,有兴趣可以看看这篇图解,浅显易懂:
前端基础进阶(二):执行上下文详细图解

相信大家看到这里,也很累了,但是也有收获,大概有了一些深刻印象,对概念也有一些比较深入的理解了。
这里我就稍微总结一下,上面讲了一些什么,对接下来的解析应该有很大的帮助。

1. 浏览器的全局对象是window
2. 全局执行环境即window对象所创建的,局部执行环境是函数执行过程中创建的。
3. 全局对象,可以访问所有其他所有预定义的对象、函数和属性。
4. 当javascript代码被浏览器载入后,默认最先进入的是一个全局执行环境。
5. 明白了执行上下文和作用域的一些概念,知道其中的运行机制和原理。

我们再回头看看这两个demo比较,我们解释清楚这个demo执行的结果。
demo1:

var name="jawil";
console.log(name);//jawil
console.log(window.name)//jawil
console.log(this.name)//jawill

demo2:

name="jawil";
console.log(name);//jawil
console.log(window.name)//jawil
console.log(this.name)//jawil

好,从例子看以看出,这两个name都是全局属性,未通过var声明的变量a和通过var声明的变量b,都可以通过this和window访问到.

我们可以在控制台打印出windowd对象,发现name成了window对象的一个属性:

var name="jawil";
console.log(window);
name2="test";
console.log(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的区别 你真的懂吗.

该回头了,好累😫,再来看看这道面试题:

var name = "jay"; //一看这二逼就是周杰伦的死忠粉
var person = {
    name: "kang",
    pro: {
        name: "Michael",
        getName: function() {
            return this.name;
        }
    }
};
console.log(person.pro.getName());
var pepole = person.pro.getName;
console.log(pepole());

最后就成了为什么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。

再来看一个例子,来加深理解这段话:

var a = 20;
function getA() {
    return this.a;
}
var foo = {
    a: 10,
    getA: getA
}
console.log(foo.getA());  // 10

灵机一动,再来一个。如下例子。

function foo() {
    console.log(this.a)
}

function active(fn) {
    fn(); // 真实调用者,为独立调用
}

var a = 20;
var obj = {
    a: 10,
    getA: foo
}
active(obj.getA);

这个例子提示一下,关于函数参数的传递赋值问题。
JS是按值传递还是按引用传递?
这里我就不多做解答了,大家自行揣摩。

以上关于this解答来自波同学的引用,我这里就偷了个懒在,直接拿来引用。
原文地址:前端基础进阶(五):全方位解读this

最后把知道面试题梳理一下:

console.log(person.pro.getName());//Michael
var pepole = person.pro.getName;
console.log(pepole());//jay

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

@jawil jawil changed the title 一道JS面试题所引发的"血案",透过现象寻本质,再从本质看现象 一道JS面试题所引发的"血案",透过现象寻本质,再从本质看现象 Feb 26, 2017
@Jason95Supreme
Copy link

上次看完波同学的this讲解,这次看你的又加深了印象

@jawil
Copy link
Owner Author

jawil commented Feb 28, 2017

学习本来就是一个重复然后加深的过程

@shenguili2
Copy link

写的真好,文笔好,思路也清晰

@MrZWH
Copy link

MrZWH commented Feb 28, 2017

吐血,本来以为十分钟看的完.........怎么样才能有耐心的看完一篇技术文呢?

@jawil
Copy link
Owner Author

jawil commented Feb 28, 2017

少看小说,多读书,哈哈😄@MrZWH

@ChiaJune
Copy link

写得很清楚,很赞,感谢。先收藏起来,多看几次。

@zywthj
Copy link

zywthj commented Feb 28, 2017

彩虹螺旋懵逼

@xgfone
Copy link

xgfone commented Mar 1, 2017

实在不明白,像 JS 中的 var、this 等产生的坑,还有闭包,这似乎是每个 JS 开发者都应该知道吧,但经常看到一些这样的面试题,而很多同学都不知道。

对于任何一门编程语言,不要相信那些 Hello 样例,那只是争取你到其阵营的银弹,真正实战中,那些 Hello 没有任何实用。看了这些多前端面试题,觉得至少有一部人还处在 Hello 阶段。

我不知道这是不是前端与后端思维的不同所造成的。

PS:前几天看到一篇文章,一个前端转后端后,说是思维不同造成了一些障碍,原本没深以为意,但这两天和前端的交流中,确实碰到了。

PS:我的主要工作是做后端的,但也学过 ES3、ES5、ES6、NodeJS、React、ReactNative(部分)、Redux、VueJS(部分)。。。,觉得上述问题,只要买一本 JS 方面的书,基本都会讲到。但还是有很多人不懂,不知道是没看,还是没看。

PS:以上只是一家之言。

@yhhwpp
Copy link

yhhwpp commented Mar 3, 2017

作用域链图画的感觉有点儿误导人
每个函数都有自己的执行环境,而执行环境又与变量对象对应关联。
代码执行时,会创建变量对象的作用域链。

我自己更改了下图片,如下
2010101700034040

@jawil
Copy link
Owner Author

jawil commented Mar 3, 2017

感谢反馈提出自己的见解,github是这样回复吗?试一试@yhhwpp

@yhhwpp
Copy link

yhhwpp commented Mar 3, 2017

@jawil 我的理解是这样的:
创建函数时,会创建一个预先包含全局变量的作用域,这个作用域被保存在内部的[[Scope]]属性中,调用函数时,会为函数创建一个执行环境,然后复制函数的[[Scope]]属性的对象构建起执行环境的作用域。这个时候该函数执行环境对应的作用域链才生成。所有,随着执行环境的不同,对应的作用域链也是不同的。

并且
作用域链本质上是一个指向变量对象的指针列表,它只引用但不实际包含变量对象。

原作者的意思表述的没问题,但是草图画的令人费解。所以,我又重新画了张图。
2010101700034040

@jawil
Copy link
Owner Author

jawil commented Mar 3, 2017

建议楼主读读ECMASCript5.1规范(点击查看),搜搜可执行代码与执行环境,对于这一块因为理论描述的太抽象,鄙人也没有过深的见解,楼主可以看看里面关于作用域和执行环境的解读再来验证你的理解是否有偏差,并且对于作用域链本质上是一个指向变量对象的指针列表这句话该怎么理解?@yhhwpp

@joe223
Copy link

joe223 commented Mar 3, 2017

又是一篇用心的博客,再来点个赞

@finch001
Copy link

finch001 commented Mar 6, 2017

最近刚从后端转前端 看了一下JS语法,吐槽点有点多,但是我刚看了下 文中练习4的解释
"window.a之所以是undefined,是因为var a = 1;发生了变量声明提升。" 这句明显不对,所谓的变量hoist 其实不过是底层引擎 首先扫描了var 并将其默认赋值为undefined, 然后再开始顺序执行JS代码,这里之所以为undefined只是单纯顶层全局变量没有定义这个变量, 跟函数内变量没有关系的。

@finch001
Copy link

finch001 commented Mar 6, 2017

关于this的指向 其实很简单 , 因为this的指向只与函数的调用模式有关

  1. 当一个函数被保存为一个对象的属性值的时候,这个方法被立刻调用时this默认是被绑定到该对象。但是问题就出在this的绑定其实是发生在调用的时候,延迟绑定使得函数可以对this复用,这也是奇怪的地方,第一个在于立刻被执行 所以绑定到了对象上,第二个延迟绑定 此时的运行环境在全局,所以this被复用指向了global。

  2. 当一个函数并非一个对象的属性值的时候, 此时this默认被绑定到全局对象,这明显是语言设计有问题,其他语言默认应该是内部函数被调用,this 应该仍然绑定到外部函数的this变量(真的让人想吐槽)。

3.如果是以构造器模式调用,则this会被绑定到新的对象 在ES6新语法中 construct()方法中 this都是默认绑向新对象。
4. Apply Bind Call 中this的指向则与方法中传入的this有关,纯函数式编程 这点我觉得比Java强很多。

@xdwxls
Copy link

xdwxls commented Jun 5, 2017

博主 有个问题请教下 一个函数中有个参数是异步的,用这个参数的时候怎么等待它加载完成啊? 我知道用promise 但是具体怎么写?怎么实现 伪代码怎么写?有写的文章推荐看看吗

@jawil
Copy link
Owner Author

jawil commented Jun 5, 2017

@xdwxls
Copy link

xdwxls commented Jun 8, 2017

@jawil 博主看到一个列子
var m = 12;
var a = {
m : 123,
f1: {
m : 23,
test: function(){
var t = function(){
console.log(this.m)
}
t();
}
}

}
a.f1.test();

为什么输出是12 难道这个也是独立调用??帮忙解释解释

@jawil
Copy link
Owner Author

jawil commented Jun 8, 2017

var t = function(){
console.log(this.m)
}
t();
在非严格模式下,执行这句的时候,this就已经是window了。

关于this的去研究这篇文章JavaScript深入之从ECMAScript规范解读this

@xdwxls

@zhangjun620
Copy link

虽然全知道答案,但是硬让我解释的话我是说不了这么一长串的,而且第一个例子是很简单的,不需要这么大篇幅为后面做铺垫的,还是向楼主学习

@fulvaz
Copy link

fulvaz commented Aug 13, 2017

所以有了let...

另外推荐本书:You don‘t konw javascript

@gosongs
Copy link

gosongs commented Oct 12, 2017

@fulvaz know

@weihaonan
Copy link

如果按楼主的解释,那这个应该是多少

function active(fn) {
fn(); // 真实调用者,为独立调用
}

var a = 20;
var obj = {
a: 10,
getA: function(){
console.log(this.a);
}
}
active(obj.getA);

@jawil
Copy link
Owner Author

jawil commented Nov 24, 2017

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
Copy link

楼主终于出现了~

@jawil
Copy link
Owner Author

jawil commented Nov 24, 2017

专心做业务,以后写写业务方面的感受,顺便发发邀请码😂。@mqyqingfeng

@weihaonan
Copy link

@jawil 那这个就跟函数参数传递方式没关系了吧,这个fn()无论如何都是全局调用的

@jawil
Copy link
Owner Author

jawil commented Nov 24, 2017

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 大神细细道来,我可能理解有误

@weihaonan
Copy link

楼主太谦虚了, 最近在看你博客受益匪浅 ,让我这毕业几年的情何以堪啊

@AngellinaZ
Copy link

(function(){
    var a;//a是局部变量
    a = 5;//这里局部环境中有a,就不会找全局中的  
    console.log(window.a);//undefined
    a = 1;//这里会发生变量声明提升
    console.log(a);//1
})();
console.log(window.a) //undefined  

请问大大,
这里 a = 5 既然时全局的,那理应添加到 window 对象下,可最后一句 console.log(window.a) 为什么时undefined 。 查看了window对象里并没有 a : 5 , 望解疑

@jawil
Copy link
Owner Author

jawil commented Feb 9, 2018

@AngellinaZ 谁告诉你a是全局的? var a =1会把a提升到最上面,当a=5赋值时候能够找到这个a的声明,所以a不会挂载到window对象上去。

@AngellinaZ
Copy link

get谢谢

@sunnynudt
Copy link

先慢慢看,感觉很不错,谢谢。

@lin-123
Copy link

lin-123 commented Apr 10, 2018

javascript 语言精粹对于this这一块讲的很清晰。 https://github.com/lin-123/Javascript/blob/master/Function.md。

不多对于函数内嵌的函数this为什么会指向全局,这个感觉很怪异。 用《js语言精粹》作者的话说,这是一个语言上的错误。倘若语言设计正确,那么当内部函数被调用的时候this应该指向外部函数的this,而非全局

不知大家怎么看

@ParadeTo
Copy link

有点啰嗦

@DoryDM
Copy link

DoryDM commented Apr 17, 2019

学习了!服服服

@zaxtseng
Copy link

精益求精,受益颇深

@fnlearner
Copy link

感觉就是this的指向问题,只有在调用的时候才会确定this的指向,谁调用方法this就是谁的

@yusilong
Copy link

javascript 语言精粹对于this这一块讲的很清晰。 https://github.com/lin-123/Javascript/blob/master/Function.md。

不多对于函数内嵌的函数this为什么会指向全局,这个感觉很怪异。 用《js语言精粹》作者的话说,这是一个语言上的错误。倘若语言设计正确,那么当内部函数被调用的时候this应该指向外部函数的this,而非全局

不知大家怎么看

我觉得不是,当程序开始了,系统就会实例一个对象,window对象,他是个对象,this就是window对象。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests