深入系列

JavaScript深入系列预计写十五篇左右,旨在帮大家捋顺JavaScript底层知识,重点讲解如原型、作用域、执行上下文、变量对象、this、闭包、按值传递、call、apply、bind、new、继承等难点概念,与罗列它们的用法不同,这个系列更注重通过写demo,捋过程、模拟实现,结合ES规范等方法来讲解。

所有文章和demo都可以在github上https://github.com/mqyqingfeng/Blog找到。如果有错误或者不严谨的地方,请务必给予指正,十分感谢。如果喜欢或者有所启发,欢迎star,对作者也是一种鼓励。

本系列:

  1. JavaScirpt 深入之从原型到原型链
  2. JavaScript
    深入之词法作用域和动态作用域
  3. JavaScript 深入之执行上下文栈
  4. JavaScript 深入之变量对象

    1 赞 1 收藏
    评论

bet36365注册送奖金 1

必刷题

接下来,看这道刷题必刷,面试必考的闭包题:

var data = []; for (var i = 0; i 3; i++) { data[i] = function () {
console.log(i); }; } data[0](); data[1](); data[2]();

1
2
3
4
5
6
7
8
9
10
11
var data = [];
 
for (var i = 0; i  3; i++) {
  data[i] = function () {
    console.log(i);
  };
}
 
data[0]();
data[1]();
data[2]();

答案是都是 3,让我们分析一下原因:

当执行到 data[0] 函数之前,此时全局上下文的 VO 为:

globalContext = { VO: { data: […], i: 3 } }

1
2
3
4
5
6
globalContext = {
    VO: {
        data: […],
        i: 3
    }
}

当执行 data[0] 函数的时候,data[0] 函数的作用域链为:

data[0]Context = { Scope: [AO, globalContext.VO] }

1
2
3
data[0]Context = {
    Scope: [AO, globalContext.VO]
}

data[0]Context 的 AO 并没有 i 值,所以会从 globalContext.VO 中查找,i
为 3,所以打印的结果就是 3。

data[1] 和 data[2] 是一样的道理。

所以让我们改成闭包看看:

var data = []; for (var i = 0; i 3; i++) { data[i] = (function (i) {
return function(){ console.log(i); } })(i); } data[0](); data[1]();
data[2]();

1
2
3
4
5
6
7
8
9
10
11
12
13
var data = [];
 
for (var i = 0; i  3; i++) {
  data[i] = (function (i) {
        return function(){
            console.log(i);
        }
  })(i);
}
 
data[0]();
data[1]();
data[2]();

当执行到 data[0] 函数之前,此时全局上下文的 VO 为:

globalContext = { VO: { data: […], i: 3 } }

1
2
3
4
5
6
globalContext = {
    VO: {
        data: […],
        i: 3
    }
}

跟没改之前一模一样。

当执行 data[0] 函数的时候,data[0] 函数的作用域链发生了改变:

data[0]Context = { Scope: [AO, 匿名函数Context.AO globalContext.VO]
}

1
2
3
data[0]Context = {
    Scope: [AO, 匿名函数Context.AO globalContext.VO]
}

匿名函数执行上下文的AO为:

匿名函数Context = { AO: { arguments: { 0: 1, length: 1 }, i: 0 } }

1
2
3
4
5
6
7
8
9
匿名函数Context = {
    AO: {
        arguments: {
            0: 1,
            length: 1
        },
        i: 0
    }
}

data[0]Context 的 AO 并没有 i 值,所以会沿着作用域链从匿名函数
Context.AO 中查找,这时候就会找 i 为 0,找到了就不会往 globalContext.VO
中查找了,即使 globalContext.VO 也有 i
的值(值为3),所以打印的结果就是0。

data[1] 和 data[2] 是一样的道理。

函数创建

《JavaScript深入之词法作用域和动态作用域》中讲到,函数的作用域在函数定义的时候就决定了。

这是因为函数有一个内部属性[[scope]],当函数创建的时候,就会保存所有父变量对象到其中,你可以理解[[scope]]就是所有父变量对象的层级链。(注意:[[scope]]并不代表完整的作用域链!)

举个例子:

function foo() { function bar() { … } }

1
2
3
4
5
function foo() {
    function bar() {
        …
    }
}

函数创建时,各自的[[scope]]为:

foo.[[scope]] = [ globalContext.VO ]; bar.[[scope]] = [
fooContext.AO, globalContext.VO ];

1
2
3
4
5
6
7
8
foo.[[scope]] = [
  globalContext.VO
];
 
bar.[[scope]] = [
    fooContext.AO,
    globalContext.VO
];

JavaScript 深入之闭包

2017/05/21 · JavaScript
· 闭包

原文出处: 冴羽   

前言

在《JavaScript深入之执行上下文栈》中讲到,当JavaScript代码执行一段可执行代码(executable
code)时,会创建对应的执行上下文(execution context)。

对于每个执行上下文,都有三个重要属性:

  • 变量对象(Variable object,VO)
  • 作用域链(Scope chain)
  • this

今天重点讲讲作用域链。

深入系列

JavaScript深入系列目录地址:https://github.com/mqyqingfeng/Blog

JavaScript深入系列预计写十五篇左右,旨在帮大家捋顺JavaScript底层知识,重点讲解如原型、作用域、执行上下文、变量对象、this、闭包、按值传递、call、apply、bind、new、继承等难点概念。

如果有错误或者不严谨的地方,请务必给予指正,十分感谢。如果喜欢或者有所启发,欢迎star,对作者也是一种鼓励。

本系列:

  1. JavaScirpt 深入之从原型到原型链
  2. JavaScript
    深入之词法作用域和动态作用域
  3. JavaScript 深入之执行上下文栈
  4. JavaScript 深入之变量对象
  5. JavaScript 深入之作用域链
  6. JavaScript 深入之从 ECMAScript 规范解读
    this
  7. JavaScript 深入之执行上下文

    1 赞 1 收藏
    评论

bet36365注册送奖金 2

作用域链

《JavaScript深入之变量对象》中讲到,当查找变量的时候,会先从当前上下文的变量对象中查找,如果没有找到,就会从父级(词法层面上的父级)执行上下文的变量对象中查找,一直找到全局上下文的变量对象,也就是全局对象。这样由多个执行上下文的变量对象构成的链表就叫做作用域链。

下面,让我们以一个函数的创建和激活两个时期来讲解作用域链是如何创建和变化的。

定义

MDN 对闭包的定义为:

闭包是指那些能够访问自由变量的函数。

那什么是自由变量呢?

自由变量是指在函数中使用的,但既不是函数参数也不是函数的局部变量的变量。

由此,我们可以看出闭包共有两部分组成:

闭包 = 函数 + 函数能够访问的自由变量

举个例子:

var a = 1; function foo() { console.log(a); } foo();

1
2
3
4
5
6
7
var a = 1;
 
function foo() {
    console.log(a);
}
 
foo();

foo 函数可以访问变量 a,但是 a 既不是 foo 函数的局部变量,也不是 foo
函数的参数,所以 a 就是自由变量。

那么,函数 foo + foo 函数访问的自由变量 a 不就是构成了一个闭包嘛……

还真是这样的!

所以在《JavaScript权威指南》中就讲到:从技术的角度讲,所有的JavaScript函数都是闭包。

咦,这怎么跟我们平时看到的讲到的闭包不一样呢!?

别着急,这是理论上的闭包,其实还有一个实践角度上的闭包,让我们看看汤姆大叔翻译的关于闭包的文章中的定义:

ECMAScript中,闭包指的是:

  1. 从理论角度:所有的函数。因为它们都在创建的时候就将上层上下文的数据保存起来了。哪怕是简单的全局变量也是如此,因为函数中访问全局变量就相当于是在访问自由变量,这个时候使用最外层的作用域。
  2. 从实践角度:以下函数才算是闭包:
    1. 即使创建它的上下文已经销毁,它仍然存在(比如,内部函数从父函数中返回)
    2. 在代码中引用了自由变量

接下来就来讲讲实践上的闭包。

发表评论

电子邮件地址不会被公开。 必填项已用*标注

网站地图xml地图