JavaScript-作用域与闭包的见解

316 阅读5分钟

JavaScript三座大山:1.原型与原型链;2.作用域及闭包;3.异步和单线程。这次本文谈谈我对作用域及闭包的理解,如何一步步挖走这个代码路上的大山之一。

一.作用域(scope)

1.作用域是一个不可理喻的熊孩子?

作用域,不仅仅是变量和函数的存储的空间,同时它自带一些规则来规范引擎如何找到这些变量和函数。作用域也可以很好的保护其中的变量和函数,避免它们被随意访问和修改。在ES6到来之前,作用域分为全局作用域和函数作用域。

2.在哪一时间、是怎么出生的?

首先,预编译在开始时会创建一个全局作用域GO(Global Object)

  • 第一步:记录下全局里的所有变量声明,变量名为key,value为undefined存储在对象里;
  • 第二步:找到所有的函数声明,函数名为key,函数体为value存储在对象里。
function foo () {
	function a () {
    	var aa = 123;
    }
    var b = 100;
    a();
    console.log(b)
 }
var glob = 100;
foo()
GO: {
	glob : undefined,
    foo: function foo () {...}
}

遇到函数需要执行的前一刻,会给执行的工作创建一个新的作用域AO(Activation Object)

  • 第一步将函数内所有的形参和变量声明储存到ao对象中,value为undefined;
  • 第二步:将形参和实参进行统一;
  • 第三步:所有的函数声明的函数名作为ao对象中的key,函数整体内容作为value,存储到ao对象中。

这就是除了全局作用域外其他函数作用域产生的时间及如何产生的。

AO: {
	b : undefined,
    a : function a () {...}
}

二.作用域链(scopr-chain)

在程序预编译时,执行代码前一刻产生的一个个作用域以链式链接的方式呈现,叫做作用域链,引擎在查找作用域里的变量、函数,会从该所处的作用域一级级向上查找父级作用域,而不会在它下一级的作用域去查找。上面的示例,全局作用域下存在一个AO这个子作用域,形成了一个简单的作用域链。

三.闭包(closure)

初学的时候,只知道“闭包”两个字,却不知道在我们code的时候,见到的很多东西就是闭包。搞懂了闭包,就有一种“就它叫闭包?”的错愕感呢。不过在弄懂闭包之前,需要明白JavaScript存在回收机制,以节省内存,在一行代码执行完以后则会进行回收。

回收,不是无效化这行代码,而是收掉这行代码产生的作用域,直到下次这行代码再次被执行,会重新产生一个新的作用域。只要这行代码还没执行完,由它产生的作用域也不会消失!!这就是闭包产生的原因。

那么有没有办法能让代码处于没被执行结束的状态呢?聪明的读者肯定是能想到欸,有的,也是很常见的一种,就是赋值。

function a(){
    function b () {
        var bbb = 234
        console.log(aaa) //看上去应该报错,实际上有值,这就是闭包
    }
    var aaa = 123
    return b  // b 出生在a里面,但是被保存出去了
}
var demo = a()  //先demo声明 后执行函数a()
demo()
GO: {
    demo: undefined,
    a: function a () {...}  
}
aAO: {
    aaa: undefined,
    b: function b() {...}
}
bAO:{
    bbb: undefined
}

函数a()执行之后,把aAO里的函数b返回给GO下的dem。按理说执行完aAO就应该被回收,但是全局作用域下的demo被赋值为函数b,它的执行保证了a处于执行未完成的状态从而aAO未被回收,所以aAO作用域里产生的变量和函数都能被子作用域bAO访问、打印出aaa的值,这就是闭包的魅力!

1.闭包让人觉得“熊”的原因

对于初学者,因为它的抽象以及js原理知识的匮乏,弄不懂便觉得厌烦,很正常。当你下定决心弄懂,逐渐在学习前端的道路并上正轨,明白它的出生机制。成也作用域,败也作用域,这种基因上带来的缺陷,只有高手能把它玩弄于股掌之间。

正因为作用域的未被回收,造就了闭包,同时作用域占用了内存,闭包的过多创建,若未能及时回收,最终会造成内存泄露

2.闭包另一方面觉得可爱的缘由

它的产生,让人们有了更多的启发

    1. 实现公有变量(企业的模块化开发),如:函数累加器;
    1. 做缓存(父级作用域下产生两个同级作用域,即使一个被回收,另外一个也已经缓存);
    1. 私有化变量(封装);
    1. 模块化开发, 防止污染全局变量。这些都是很常见地被运用在代码中。

四.最后

以上都是最近学习所感,记录下自己代码路上的一点见解心得。往往在理解一个东西的时候,需要理解别的知识点,这个知识点又要用到别的知识,最终回到原理机制与基础,学无止境吧,希望永远怀着一颗学徒的心。