深入理解Javascript中的作用域链和闭包

var array = []; array.length = 10000000;//(一千万) for(var i=0,length=array.length;i<length;i++){ array[i] = 'hi'; } var t1 = +new Date(); for(var i=0,length=array.length;i<length;i++){ } var t2 = +new Date(); console.log(t2-t1); //以下是连续5次的运行时间 //168+158+170+159+165 = 820(ms)

我们再看下面一段代码, 测试环境为 chrome 52.0.2743.116 (64-bit):

var t1 = +new Date(); (function(){//闭包 for(var i=0,length=array.length;i<length;i++){ //array.push(i); } })(); var t2 = +new Date(); console.log(t2-t1); //以下是连续5次的运行时间: //8+6+8+7+6 = 35(ms)

计算一下: 820/35 = 23 效率提升大致20倍. 实际上, 在 Firefox 及 Safari 对 for有做底层优化的情况下, 仍然有4~6倍的性能提升. 这是为什么呢?

我们注意到两段代码最大的区别就是, 第二段代码使用了匿名函数包裹for循环. 我们将在后面讲到, 请耐心阅读.

作用域

所谓作用域, 指的是, 变量在声明它们的函数体以及这个函数体嵌套的任意函数体内都是有定义的.

js中只有函数作用域

众所周知, JS中并没有块作用域, 只有函数作用域. 如下:

for(var i=0;i<10;i++){ ; } console.log(i);//10 function f(){ var a = 123; } f(); console.log(a);//a is not defined

因此 js 中只有一种局部作用域, 即函数作用域.

使用 var 声明变量

通常我们知道, js 作为一种弱类型语言, 声明一个变量只需要var保留字, 如果在函数中不使用 var 声明变量, 该变量将提升为全局变量, 进而脱离函数作用域, 如下:

function f(){ b = 123; } f(); console.log(b);//123

此时相对于前面使用var声明的 a 变量, b 变量被提升为全局变量, 在函数作用域外依然可以访问.

既然在函数作用域内不使用 var 声明变量, 会将变量提升为全局变量, 那么在全局下, 不使用var, 会怎么样呢?

//全局下不使用var声明,该变量依然是全局变量 c = "hello scope"; console.log(c);//hello scope console.log(window.c);//hello scope //查看c变量的属性 console.log(Object.getOwnPropertyDescriptor(window, 'c'));//Object {value: "hello scope", writable: true, enumerable: true, configurable: true} ,此时c变量可赋值,可列举,可配置 //试着删除c变量 delete c;//true 表示c变量被成功删除 console.log(c);//c is not defined console.log(window.c);//undefined //使用var声明后再删除d变量 var d = 1; console.log(Object.getOwnPropertyDescriptor(window, 'd'));//Object {value: 1, writable: true, enumerable: true, configurable: false} ,此时d变量可赋值,可列举,但不可配置 delete d;//false 表示d变量删除失败 console.log(d);//1 console.log(window.d);//1

综上, 有如下规律:

不使用var保留字声明变量, 变量提升为全局变量, 而不论变量处于哪种作用域;

如果不使用var声明, 该变量便可配置, 即可被 delete 保留字删除, 删除后该变量便不可访问; 如果使用var声明, 该变量便不可配置, 即不能被 delete 保留字删除;

只要是全局变量都可以直接访问, 也可使用 “window.变量名” 来访问, 不管该变量是不是通过var来声明的;

JS中的作用域链

函数对象和其它对象一样,拥有可以通过代码访问的属性和一系列仅供JavaScript引擎访问的内部属性。其中一个内部属性是[[Scope]],由ECMA-262标准第三版定义,该内部属性包含了函数被创建的作用域中对象的集合,这个集合被称为函数的作用域链,它决定了哪些数据能被函数访问。

我们先看一个栗子:

var e = "hello"; function f(){ e = "scope chain"; var g = = "good"; }

以上作用域链的图如下所示:

深入理解Javascript中的作用域链和闭包

函数执行时, 在函数 f 内部会生成一个 active object 和 scope chain. JavaScript引擎内部对象会放入 active object中, 外部的 e 变量处于scope chain的第二层, index=1, 而内部的g变量处于scope chain的顶层, index=0, 因此访问g变量总比访问e变量来的快些.

闭包

聊到作用域, 就不得不说闭包, 那么, 什么是闭包?

“官方”的解释是:闭包是一个拥有许多变量和绑定了这些变量的环境的表达式(通常是一个函数),因而这些变量也是该表达式的一部分。

这是什么意思呢, 简单来说就是:

函数执行时返回内部私有函数, 或者通过其他方式将内部私有函数保留在外(比如说通过将其内部私有函数的引用赋值外部变量), 从而阻止该函数内部作用域等被执行引擎回收.

在函数外部通过访问暴露在外的函数内部私有函数, 从而具有访问函数内部私有作用域的效果, 就是闭包.

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wypddg.html