图片 9

javascript 闭包

闭包:能够访问函数作用域的变量的函数。我们认真来分析这句话,它有三点(1.闭包是定义在函数中的函数
2.闭包访问包含函数的变量
3.即使包含函数执行完了,被闭包引用的变量也得不到释放)

js 作用域链&内存回收&变量&闭包

闭包主要涉及到js的几个其他的特性:作用域链,垃圾(内存)回收机制,函数嵌套,等等

一、作用域链:函数在定义的时候创建的,用于寻找使用到的变量的
值的一个索引,而他内部的规则是,把函数自身的本地变量放在最前面,把自身的父级函数中的变量放在其次,把再高一级函数中的变量放在更后面,以此类推直至
全局对象为止.当函数中需要查询一个变量的值的时候,js解释器会去作用域链去查找,从最前面的本地变量中先找,如果没有找到对应的变量,则到下一级的链
上找,一旦找到了变量,则不再继续.如果找到最后也没找到需要的变量,则解释器返回undefined。

二、内存回收机制:一个函数在执行开始的时候,会给其中定义的变
量划分内存空间保存,以备后面的语句所用,等到函数执行完毕返回了,这些变量就被认为是无用的了.对应的内存空间也就被回收了.下次再执行此函数的时候,
所有的变量又回到最初的状态,重新赋值使用.但是如果这个函数内部又嵌套了另一个函数,而这个函数是有可能在外部被调用到的.并且这个内部函数又使用了外
部函数的某些变量的话.这种内存回收机制就会出现问题.如果在外部函数返回后,又直接调用了内部函数,那么内部函数就无法读取到他所需要的外部函数中变量
的值了.所以js解释器在遇到函数定义的时候,会自动把函数和他可能使用的变量(包括本地变量和父级和祖先级函数的变量(自由变量))一起保存起来.也就
是构建一个闭包,这些变量将不会被内存回收器所回收,只有当内部的函数不可能被调用以后(例如被删除了,或者没有了指针),才会销毁这个闭包,而没有任何
一个闭包引用的变量才会被下一次内存回收启动时所回收。

三、局部变量&全局变量

1、全局global)变量的作用域是全局的,在Javascript中处处有定义;而函数内部声明的变量是局部local)变量,其作用域是局部性的,只在函数体内部有定义,每次执行该函数时都会创建和破坏该变量。

2、全局变量作用域中使用变量可以不用var语句,但在声明局部变量是一定要使用var语句,否则会视为对全局变量的引用。

3、

图片 1

var scope =
“local”;
声明的变量在整个checkScope函数作用域内都有效,因此第一个document.write(scope);执行的时scope引用的是局部变量,而此时局部变量scope尚未定义,所以输出”undefined”。好的编程习惯是将所有的变量声明集中起来放在函数的开头。document.write(window.scope)//输出global

全局变量总是存在于运行期上下文作用域链的最末端,因此在标识符解析的时候,查找全局变量是最慢的。所以,在编写代码的时候应尽量少使用全局变
量,尽可能使用局部变量。一个好的经验法则是:如果一个跨作用域的对象被引用了一次以上,则先把它存储到局部变量里再使用(document、
window等)。

在执行JavaScript代码的过程中,当遇到一个标识符,就会根据标识符的名称,在执行上下文Execution
Context)的作用域链中进行搜索。从作用域链的第一个对象该函数的Activation
Object对象)开始,如果没有找到,就搜索作用域链中的下一个对象,如此往复,直到找到了标识符的定义。如果在搜索完作用域中的最后一个对象,也就是
全局对象Global
Object)以后也没有找到,则会抛出一个错误,提示用户该变量未定义undefined)。这是在ECMA-262标准中描述的函数执行模型和标识
符解析Identifier Resolution)的过程。

由ECMA-262标准第三版定义,该内部属性包含了函数被创建的作用域中对象的集合,这个集合被称为函数的作用域链,它决定了哪些数据能被函数访问。作用域第一个对象始终是当前执行代码所在环境的变量对象

function a(x,y){

var b=x+y;

return b;

}

在函数a创建的时候它的作用域链填入全局对象,全局对象中有所有全局变量

图片 2

var tatal=a(5,10);

执行此函数时会创建一个称为“运行期上下文(execution
context)”的内部对象,运行期上下文定义了函数执行时的环境。值按照它们出现在函数中的顺序被复制到运行期上下文的作用域链中。它们共同组成了一
个新的对象,叫“活动对象(activation
object)”,该对象包含了函数的所有局部变量、命名参数、参数集合以及this,然后此对象会被推入作用域链的前端,当运行期上下文被销毁,活动对
象也随之销毁。

图片 3

ECMAScript变量可能包含两种不同数据类型的值:基本类型值和引用类型值。基本类型值指的是那些保存在栈内存中的简单数据段,即这种值
完全
保存在内存中的一个位置。而引用类型值是指那些保存堆内存中的对象,意思是变量中保存的实际上只是一个指针,这个指针指向内存中的另一个位置,该位置保存
对象。

5种基本数据类型:Undefined、Null、Boolean、
Number和String。这5种基本数据类型的值在内存中分别占有固定大小的空间,因此可以把它们的值保存在栈内存。

如果赋给变量的是一个引用类型的值,则必须在堆内存中为这个值分配空间。由于这种值的大小不固定,因此不能把它们保存到栈内存中。但内存地址的
大小
是固定的,因此可以将内存地址保存在栈内存中。这样,当查询引用类型的变量时,就可以首先从栈中读取内存地址,然后再“顺藤摸瓜”地找到保存在堆中的值。

保存在栈内存中的每个值,分别占据着固定大小的空间,可以按照顺序来访问它们。如果栈内存中保存的是一块内存的地址,则这个值就像是一个指向对象在堆内存中位置的指针。保存在堆内存中的数据不是按顺序访问的,因为每个对象所需要的空间并不相等。

当从一个变量向另一个变量复制引用类型的值时,同样也会将储存在栈中的值复制一份放到为新变量分配的空间中。不同的是,这个值的副本实际上是一个指针,而这个指针指向存储在堆中的一个对象。复制操作结束后,两个变量实际上将引用同一个对象。因此,改变其中一个变量,就会影响到另一个变量。

typeof操作符是确定一个变量是字符串、数值、布尔
值,还是undefined基本数据类型的最佳工具。检测引用类型的值时,ECMAScript提供了instanceof操作符。

四、闭包

只要存在调用内部函数的可能,JavaScript就需要保留被引用的函数。而且JavaScript运行时需要跟踪引用这个内部函数的所有变量,直到最后一个变量废弃,JavaScript的垃圾收集器才能释放相应的内存空间红色部分是理解闭包的关键)。

闭包最大用处有两个,一个是可以读取函数内部的变量,另一个就是让这些变量的值始终保持在内存中。

 图片 4

使用闭包的注意点

1)由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露。解决方法是,在退出函数之前,将不使用的局部变量全部删除。

2)闭包会在父函数外部,改变父函数内部变量的值。所以,如果你把父函数当作对象object)使用,把闭包当作它的公用方法Public
Method),把内部变量当作它的私有属性private
value),这时一定要小心,不要随便改变父函数内部变量的值。

闭包一些例子:

图片 5

图片 6


图片 7


作用域链变量amp;全局变量
1、全局global)变量的作用域是全局的,在Javascript中处处有定义;而函数内部声明的变量是局部local)变量,其…

这里我们主要是对第三个特点理解,js语言有自己的一套内存回收机制,一般情况下局部变量和对象使用完后就会被系统自动回收,不需要我们理会,一般函数执行完内存将会释放,因为没有变量指向该函数的内存空间了,但是函数的定义在,所以当我们再次执行的时候,相当于在栈内存中,我们重新定义一个变量指向堆内存的一个空间,而该堆内存间将函数定义拷贝到自己的空间里,但是碰到闭包的情况这些变量和对象是不被回收的,我们直接来看例子吧图片 8image.png

function fn(){ var a = 1; //第一种情况 function fn1(){ //这个fn1算闭包么,其实这个看自己的理解,你可以把它理解成为一个闭包 //只不过这个闭包没有访问外部函数的变量 ,那么也将不会存在闭包的第三个特点(闭包引用的变量不被释放问题) //只看这个函数是符合闭包的定义的,但是写这种函数并没有意义, //既然fn1和外面的函数没有关系,那我们何不直接把它提出来写称另一个函数呢 } //第二种情况 function fn2(){ console.log; a = 3; //这个fn2算闭包么,这个方法的理解同上,该函数访问了外部函数的局部变量,但是并不会导致内存引用变量不被释放问题 //该函数的作用就是将外部函数的变量值改变了,同理这个函数也没有意义, //既然只是单纯改变外部函数的局部变量的话,那我们可以直接将这个函数体拿出来放在外部函数体里面就好了 } //第三种情况 return function(){ a++; console.log; //该函数将符合闭包的所有情况了,导致内存不被释放的原因是在全局下 //我们有afn一直引用该闭包,该闭包一直引用着外部函数的a变量 //因为函数也是一个对象,如果对象一直被引用的话,内存将得不到释放,当我们把afn = null的话,将释放内存 } //第四种情况 var dom = document.getElementById; dom.onclick = function(){ /*这里函数即使是空的也会导致内存泄漏 这里发生了两种情况 1.dom.onclick这个函数隐式的调用了dom(dom.onclick函数中的this就是dom)导致监听事件不手动清除的话会一直存在内存中, 也就是为什么我们onclick事件执行过之后,在次点击仍然可以发生监听事件,这里发生了类似于对象的属性引用了带对象造成了引用计数不可能为0 2.监听事件不可能是局部作用域的,就算该函数fn执行完之后 ,该fn函数内存被释放,我们继续执行点击事件,依然会发生响应,正常情况下fn内存如果被释放了, 里面的函数或者是变量应该都被回收了,所以onclick事件是注册在全局作用域下的 而在onclick事件内部引用了外部函数f'n变量dom,所以导致该外部函数fn的内存将得不到释放 我们把dom = null之后也就是onclick事件不再引用该函数fn的变量dom,使其fn内存得到释放 最后:这里函数内部的dom和全局作用的监听事件onclick无关,我们即使在该函数将其置为null也只是把该函数内部对btn元素的引用置为null, 但是全局的onclick事件还存在的,比如我们这个onclick事件写到全局并把dom = null;但是并不影响我们点击事件的发生如下图 dom2中提供了removeEventListener来移除监听事件*/ } dom = null; fn1;}var afn = fn; //2afn(); //3afn = null;

图片 9image.png

发表评论

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