闭包,作用域链


闭包,作用域链

  1. 变量对象

(什么是变量对象 -如果变量与执行上下文相关,那变量自己应该知道它的数据存储在哪里,并且知道如何访问。这种机制称为变量对象(variable object)。

有这样一个对象,存储着变量和对应的值。这个对象就叫做变量对象。)

(变量对象(缩写为VO)是一个与执行上下文相关的特殊对象,它存储着在上下文中声明的以下内容:

变量 (var, 变量声明);

函数声明 (FunctionDeclaration, 缩写为FD);

函数的形参)

变量对象VO (变量初始化过程的一般行为)

╠══> 全局上下文变量对象GlobalContextVO

╚══> 函数上下文变量对象FunctionContextVO

变量对象有什么作用

(存储着变量和对应的值。我们就可以知道如何访问这些变量和值。)

如何使用变量对象

在程序中,执行程序的时候,就可以看见变量对象,这样就可以知道如何访问这些变量和值。

同时我们也知道变量对象在每次进入上下文时创建,并填入初始值,值的更新出现在代码执行阶段。

  1. 活动对象

什么是活动对象 – 活动对象是进入函数中时对应的变量对象。

变量对象不可以直接访问,要通过活动对象才能访问。也就是对象激活了,才能访问。

  1. 作用域链

什么是作用域链

作用域链有什么用

  1. try/catch

  2. 闭包

什么是闭包

为什么使用闭包

闭包提供了哪些特性

如何使用闭包

变量对象

什么是变量对象

变量对象:Variable Object, 是一个特殊对象,存储着以下内容:

变量 (var, 变量声明);

函数声明 (FunctionDeclaration, 缩写为FD);

函数的形参

为什么要使用变量对象

JavaScript编程的时候总避免不了声明函数和变量,以成功构建我们的系统,但是解释器是如何并且在什么地方去查找这些函数和变量呢,去变量对象中去找函数和变量。这样我们就可以访问到我们定义的函数和变量了。

什么时候会用到变量对象

(代码:由以下代码引出变量问题,这时就用到了变量对象

var a = 10; // 全局上下文中的变量

(function () {

var b = 20; // function上下文中的局部变量

})();

alert(a); // 10

alert(b); // 全局变量 ‘b’ 没有声明

如何使用变量对象

分析上面的例子, 从普通角度说一下。a 是全局变量,b是局部变量,所以访问不到b。

(代码:

var a = 10;

function test(x) {

var b = 20;

};

test(30);

对应的变量对象是:

// 全局上下文的变量对象

VO(globalContext) = {

a: 10,

test: <reference to function>

};

// test函数上下文的变量对象

VO(test functionContext) = {

x: 30,

b: 20

};

)

不同执行上下文中的变量对象

抽象变量对象VO (变量初始化过程的一般行为)

╠══> 全局上下文变量对象GlobalContextVO

║ (VO === this === global)

╚══> 函数上下文变量对象FunctionContextVO

       (VO === AO, 并且添加了\<arguments\>和\<formal parameters\>)

全局上下文中的变量对象

全局对象(Global object) 是在进入任何执行上下文之前就已经创建了的对象;

这个对象只存在一份,它的属性在程序中任何地方都可以访问,全局对象的生命周期终止于程序退出那一刻。

举例:全局变量到处都可以访问

var a = 10;

function variableTest () {

    document.write(a);

}

document.write(a);

document.write(‘<br>’);

variableTest ();

函数上下文中的变量对象

(代码解释什么是变量对象

var a = 10;

function test(x) {

var b = 20;

};

test(30);

// test函数上下文的变量对象

VO(test functionContext) = {

x: 30,

b: 20

};

这个就是函数上下文中的变量对象

活动对象

在函数执行上下文中,VO是不能直接访问的,此时由活动对象(activation object,缩写为AO)扮演VO的角色。

作用域链

抽象变量对象VO (变量初始化过程的一般行为)

╠══> 全局上下文变量对象GlobalContextVO

║ (VO === this === global)

╚══> 函数上下文变量对象FunctionContextVO

       (VO === AO, 并且添加了\<arguments\>和\<formal parameters\>)

实际上这个图就可以看成是作用域链。

先去搜索函数上下文变量对象(此时由活动对象扮演VO的角色。),如果没搜到要使用的变量或者函数,那么就继续向上搜索,比如搜索全局上下文对象。就这样一层一层搜索,知道搜索到要使用的变量或者函数为止。

我们结合一些例子还理解作用域链。

(代码:

var x = 10;

function foo() {

var y = 20;

function bar() {

alert(x + y);

}

return bar;

}

foo()(); // 30

)

(代码:

var x = 10;

function foo() {

var y = 20;

function bar() {

var z = 30;

alert(x +  y + z);

}

bar();

}

foo(); // 60

(代码:

name=’lwy’;

function t(){

var name='tlwy';

function s(){

    var name='slwy';

    console.log(name);

}

function ss(){

    console.log(name);

}

s();

ss();

}

t();

)

闭包

什么是闭包

JS closure : A ‘closure’ is an expression (typically a function) that can have free variables together with an environment that binds those variables (that ‘closes’ the expression).

所谓’闭包’,指的是一个拥有许多变量和绑定了这些变量的环境的表达式(通常是一个函数),因而这些变量也是该表达式的一部分。

闭包就是能够读取其他函数内部变量的函数。由于在Javascript语言中,只有函数内部的子函数才能读取局部变量,因此可以把闭包简单理解成’定义在一个函数内部的函数’。所以,在本质上,闭包就是将函数内部和函数外部连接起来的一座桥梁。

为什么要使用闭包

保护函数内的变量安全。以最开始的例子为例,函数a中i只有函数b才能访问,而无法通过其他途径访问到,因此保护了i的安全性。

在内存中维持一个变量。依然如前例,由于闭包,函数a中i的一直存在于内存中,因此每次执行c(),都会给i自加1。

以上两点是闭包最基本的应用场景,很多经典案例都源于此。

计数器困境

 设想下如果你想统计一些数值,且该计数器在所有函数中都是可用的。你可以使用全局变量,函数设置计数器递增:

 var counter = 0;

function add() {
counter += 1;
}

add();
add();
add();

// 计数器现在为 3

计数器数值在执行 add() 函数时发生变化。

但问题来了,页面上的任何脚本都能改变计数器,即便没有调用 add() 函数。

如果我在函数内声明计数器,如果没有调用函数将无法修改计数器的值:

 function add() {
var counter = 0;
counter += 1;

}

add();
add();
add();

// 本意是想输出 3, 但事与愿违,输出的都是 1 !

以上代码将无法正确输出,每次我调用 add() 函数,计数器都会设置为 1。

JavaScript 内嵌函数可以解决该问题。

所有函数都能访问全局变量。

实际上,在 JavaScript 中,所有函数都能访问它们上一层的作用域。

JavaScript 支持嵌套函数。嵌套函数可以访问上一层的函数变量。

该实例中,内嵌函数 plus() 可以访问父函数的 counter 变量:

 function add() {
var counter = 0;
function plus() {counter += 1;}
plus();
return counter;

}

如果我们能在外部访问 plus() 函数,这样就能解决计数器的困境。

我们需要闭包。

如何使用闭包

JavaScript 闭包

var add = function () {

    var counter = 0;

    var aaa = 1;

    function plus() {

            counter += 1;

            console.log('counter: ' + counter);

    }

    return plus;

};

var result = add();

result();

result();

result();

// 计数器为 3

result可以作为一个函数使用。非常棒的部分是它可以访问函数上一层作用域的计数器

这个叫作 JavaScript 闭包。 它使得函数拥有私有变量变成可能。

计数器受匿名函数的作用域保护,只能通过 plus 方法修改。

闭包是可访问上一层函数作用域里变量的函数,即便上一层函数已经关闭。

使用闭包的注意点:

  • 由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露。