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

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

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

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

三、局部变量&全局变量

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

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

3、

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创建的时候它的作用域链填入全局对象,全局对象中有所有全局变量

var tatal=a(5,10);

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

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

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

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

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

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

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

四、闭包

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

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

使用闭包的注意点

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

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

闭包一些例子:

上面代码在页面加载后就会执行,当i的值为4的时候,判断条件不成立,for循环执行完毕,但是因为每个span的onclick方法这时候为内部函数,所以i被闭包引用,内存不能被销毁,i的值会一直保持4,直到程序改变它或者所有的onclick函数销毁(主动把函数赋为null或者页面卸载)时才会被回收。这样每次我们点击span的时候,onclick函数会查找i的值(作用域链是引用方式),一查等于4

时间: 2024-12-17 08:34:50

js 作用域链&内存回收&变量&闭包的相关文章

js作用域链中变量提前的问题

js訪问变量是从内到外,这条作用域链上面的每一个活动变量也是从内到外的.比方一个函数,首先由arguments和函数内部声明的变量,然后是外层的能訪问的变量.直至最后window全局对象.当出了这个函数,函数内部声明的活动对象就会销毁,所以外部滴根本无法訪问函数内部声明的对象的.之所以说js会把全部的变量提前也是针对不同的作用域的,在最外面.则是把全部的全局变量和全局的函数声明提前,在函数内部,则是先把函数内部声明的变量和函数提前

Js作用域链及变量作用域

要理解变量的作用域范围就得先理解作用域链 用var关键字声明一个变量时,就是为该变量所在的对象添加了一个属性. 作用域链:由于js的变量都是对象的属性,而该对象可能又是其它对象的属性,而所有的对象都是window对象的属性,所以这些对象的关系可以看作是一条链 链头就是变量所处的对象,链尾就是window对象 看下面的代码: function t() { var a; function t2() { var b; } } js中函数也是对象,所以变量a所在的对象是t,t又在window对象中,所以

【动画演示】:JS 作用域链不在话下

作者:Lydia Hallie译者:前端小智来源:dev 点赞再看,养成习惯 本文 GitHub https://github.com/qq44924588... 上已经收录,更多往期高赞文章的分类,也整理了很多我的文档,和教程资料.欢迎Star和完善,大家面试可以参照考点复习,希望我们一起有点东西. 本篇我们来看看啥是作用域以及作用域链,首先,来看看下面的代码: const name = "Lydia" const age = 21 const city = "San Fr

JS作用域链

Js作用域与执行环境: 1. 作用域链: <1>用途:保证对执行环境有权访问的所有变量和函数的有序访问. <2>构成: a.作用域链的前端,始终都是当前执行代码所在环境的变量对象(如果这个环境是函数,则将其活动对象作为变量对象,活动对象在最开始时只包含一个变量,即arguments对象): b.下一个变量对象来自包含(外部)环境: c.最后一个对象,为全局执行环境的变量对象. <3>标识符解析:是沿着作用域链一级一级地搜索标识符的过程,始终从作用域链的前端开始,然后逐级

[js]作用域链查找规则获取值和设置值

作用域链查找规则获取值和设置值 <script> /** 1.作用域链查找规则 私有作用域出现的一个变量不是私有的,则往上一级作用域查找,上级作用域没有则继续向上级查找,一直找到window为止,如果window也没有了? 1)如果是获取值,则报错 2)如果是设置值,则相当于给window设置了一个属性 2.js代码一旦报错,则不往下执行了. */ function fn() { console.log(total); //获取值 total = 100; //设置值 } fn(); cons

js作用域链以及全局变量和局部变量

> [带var] > 在当前作用于中声明了一个变量,如果当前是全局作用域,也相当于给全局作用域设置了一个属性叫做a ```javascript //=>变量提升:var a; <=>window.a=undefined; console.log(a);//undefined var a = 12; console.log(a);//12 console.log(window.a);//window['a']在'全局作用域'中,我们声明了一个变量,相当于全局对象window增加

关于js作用域链,以及闭包中的坑

eg:链式作用域,想在外部读取blogName的值得方法 <script>var authorName="山边小溪";function doSomething(){    var blogName="梦想天空"; function innerSay(){        console.log(blogName+"1");    }       innerSay();  }doSomething() </script> 在上

Js 作用域链(是指AO链)

1:参数 2:局部变量声明 3:函数声明 * 函数声明与函数表达式的区别 表达式必有返回值,把返回值(即匿名函数)赋给了一个变量. 此时 就是普通的赋值过程. ①.js并不是一句一句顺序执行的,先进行词法分析 This, 与 arguments 当一个函数运行的时候,函数内部能引用的变量有这么几种 AO.arguments.this 对于 arguments和this, 每个函数都有自己独有的arguments和this, 且不进行链式查找 arguments是什么? 答:1:arguments

js作用域链 js没有块级作用域

arguments和函数内定义的变量或函数->父级->下一个父级->.....->全局环境中的变量或函数 if(true){ var a=1; } console.log(a);js没有块级作用域, 变量a直接添加到当前的执行环境中.  java有块级作用域,if语句结束后会销毁if中定义的变量