上一节说了执行上下文,这节咱们就乘胜追击来搞搞闭包!头疼的东西让你不再头疼!
变量对象
变量对象是根据(Variable Object) 来翻译过来的,也可以翻译成可变对象, 就是保存变量的对象,活动对象,闭包对象都保存着变量,因此也可以称为变量对象。
注:这里解释下,是因为各个书中对这几个名词的使用,搞的好远。
执行上下文
是根据(Execution Context)翻译过来的,也可译为执行环境。
在函数执行时,就会首先创建执行上下文来运行代码。
活动对象
是根据(Activation Object)翻译过来的,也可译为激活的对象。
在函数执行时,创建的变量对象,不仅含有变量,还有特殊的this,arguments。
闭包
是根据(closure) 翻译过来的,也可译为 闭合,使结束等,我认为可以理解为 封闭的环境。
把当前作用域外的环境封闭起来,以备 其他作用域环境使用。
总结:好多名词都是英译过来的,看原著或根据上下文来理解才是这些单词真正的意思,只可意会不可言传。。。
其实,就是引用类型 和 基本类型的 区别。
<br/>
<br/>
基本类型时,变量保存的是数据,引用类型时,变量保存的是内存地址。参数传递,就是把变量保存的值 复制给 参数。
<br/>
JavaScript 具有自动垃圾收集机制,执行环境会负责管理代码执行过程中使用的内存。函数中,正常的局部变量和函数声明只在函数执行的过程中存在,当函数执行结束后,就会释放它们所占的内存(销毁变量和函数)。
而js 中 主要有两种收集方式:
- 标记清除(常见) //给变量标记为“进入环境” 和 “离开环境”,回收标记为“离开环境”的变量。
- 引用计数 // 一个引用类型值,被赋值给一个变量,引用次数加1,通过变量取得引用类型值,则减1,回收为次数为0 的引用类型值。
知道个大概情况就可以了,《JavaScript高级程序设计 第三版》 4.3节 有详解,有兴趣,可以看下。.
<br/>
在 JavaScript 中, 作用域(scope,或译有效范围)顾名思义就是变量和函数的作用范围(可访问范围)。
- 作用域可以实体化为一个 可变对象(Variable Object 变量对象)
- JavaScript中的作用域有:全局作用域和局部作用域(函数作用域)。ES6 新增了块级作用域
全局作用域(Global Scope)
(1)不在任何函数内定义的变量就具有全局作用域。(非严格模式下)
(2)实际上,JavaScript默认有一个全局对象window,全局作用域的变量实际上被绑定到window的一个属性。
局部作用域(Local Scope)
(1)JavaScript的作用域是通过函数来定义的,在一个函数中定义的变量只对这个函数内部可见,称为函数(局部)作用域。
块级作用域
块级作用域指在If语句,switch语句,循环语句等语句块中定义变量,这意味着变量不能在语句块之外被访问。
每个函数都有一个[[scope]] 的内部属性(可以通过console.dir(fn),来查看),它保存着作用域链(一个对象数组),而作用域链中是一个个可变对象(Variable Object 变量对象)(一个保存当前作用域中用到的变量,函数等的对象)。当函数创建时,一个代表全局环境的可变对象会被插入到作用域的第一个位置。该全局可变对象保存着window,navigator,document 等。
例如如下 声明一个全局函数:
当函数执行时,会创建执行上下文(执行环境),随后创建一个执行上下文对象,它有自己的作用链。刚开始,它会用函数自身的 [[scope]] 中的作用域链初始化自己(也就是复制)。
随后一个活动对象被创建(也可以说是变量对象,可变对象),它保存着当前函数作用域里的变量,arguments,this 等。最后,该活动对象会被推到执行上下文的作用域链的最前端。
注:执行上下文(执行环境)在函数执行完毕后就会被销毁,里面的作用域链,变量,函数,活动对象,this 等也会一同销毁。
在函数执行过程中,每遇到一个变量,都会经历一次标识符解析过程以决定从那里获取存储数据。该过程搜索执行环境的作用域链,查找同名的标识符。搜索过程从作用域链头部开始也就是当前运行的作用域。如果找到,就使用这个标识符对应的变量;如果没找到,继续搜索作用域链中的下一个对象。搜索过程会持续进行,直到找到标识符,若无法搜索到匹配的对象,那么标识符将被视为未定义的。
当函数嵌套时,例如有一个A函数,内部有个v1 的变量,有一个B函数,B 中使用了v1 变量。这时,为了让 B 执行时,能访问 v1(其实就是为了形成作用域链),会有以下两个变化:
- 形成一个闭包函数,生成一个闭包对象 A,包含了 B 中用到 v1 变量
- 在B 闭包函数的[[scope]] 属性中 推入 闭包对象A。
注:可以通过 debugger 来在谷歌浏览器控制台里看。具体怎么用,可以自行百度。
现在来分析一下过程:
1、首先,A 函数执行,一开始 它的[[scope]] 内的作用域链中只有全局的可变对象,然后 创建一个执行上下文对象,有一个作用域链,根据 [[scope]] 复制来 来初始化自己。
2、创建 A 函数的活动对象,并推到 执行上下文对象的作用域链中。
3、当发现 B 中用到 v1 时,B 就会变成一个封闭的函数(闭包函数),然后,生成一个关于A 函数的封闭对象(闭包对象),保存着 v1(因为它存在于A,在B中使用)。随后,把这个封闭的对象推到 B 函数 的[[scope]] 作用域链中。
注:这时,B 函数还没有执行。至于什么机制导致js 能够发现未执行的函数内使用了 A 函数内的变量,目前的知识还得不到答案。
4、当B 函数执行时,创建执行上下文,创建执行上下文对象,初始化执行上下文对象的作用域链(复制B 函数的[[scope]] 属性)。
5、随后创建一个活动对象,并推到 执行上下文对象的作用域链中。
这样,B 在执行时,就可以访问 v1 了,因为在一个作用域链中。
下面来总结下作用链的变化过程:
- 全局下的 A函数执行时,内部的[[scope]] 保存的作用域链只有一个全局的变量对象。创建A 函数的执行上下文对象,根据 [[scope]] 复制初始化 A函数的 执行上下文对象的作用域链。
- 创建 A 函数的活动对象,推到 A函数执行上下文对象的作用域链前端。
- 当发现 A 函数内部(不管层级多深)有 一个函数使用了 A函数内的 变量或函数。
- 则 A 内(不管层级多深)所有函数 都会形成 闭包函数。
- 然后创建一个关于 A 的闭包对象,对象内含有被使用的变量或函数(通过复制)。
- 最后把该闭包对象 推到 所有闭包函数的 [[scope]] 内。
可以得出以下结论:
- 有两个作用域链,一个存与函数的[[scope]] 中,用来保存作用域,以备执行上下文对象初始化自身作用域链。
- 执行上下文对象中的作用域链,会添加活动函数,作用域链的查找,查的就是这条作用域链。(一般我们说的作用域链就是指这条)
- 活动函数只会存在于执行上下文对象的作用域链中。
- 有闭包函数和闭包对象,闭包函数的[[scope]] 保存闭包对象,而闭包对象,封闭的是 父或祖级函数作用域中的变量或对象。
- 闭包函数的存在是因为 执行上下文环境 会在执行完后销毁,而其中的作用域链,活动对象,变量等等就丢失了,通过闭包函数 就可以保存着作用域链,而链中的变量对象又保存着变量,函数等。
来一个难一点的例子,大家可以先自己分析分析。
根据上面分析,可以得出各个函数的[[scope]]:
- A 只有一个全局变量对象
- B 和 E 有两个变量对象,关于 A 的闭包对象,全局变量对象。
- C 和 D 有三个变量对象,关于 B 的闭包对象,关于 A 的闭包对象,全局变量对象。
通过 debugger 来单步调试,无非就是能看到每个执行环境内的作用域链中 含有 活动对象。
有兴趣的可以试试。
至于闭包的内存泄漏,这里面牵扯到 js 的垃圾回收机制。不过可以看到,[[scope]] 中保存着 变量,如果 该变量 占的内存不被释放,一旦这样的情况过多,内存占用过大,就会造成内存泄漏 和 性能问题。
一般说的闭包指的都是闭包函数。
引用高程(《JavaScript高级程序设计》)中关于闭包说法:
闭包是指有权访问另一个函数作用域中变量的函数
通过上面说的那么多,你品,你细品。。。
<br/>
我认为就是为了形成作用域链。你品,你细品。。。
再来个有趣经典的例子:
是不是跟你想的不一样?其实,这个例子重点就在setTimeout函数上,这个函数的第一个参数接受一个函数作为回调函数,这个回调函数并不会立即执行,它会在当前代码执行完,并在给定的时间后执行。这样就导致了上面情况的发生。
注:这里用一个函数包裹起来了,这样,你可以通过 debugger,会发现,这里也形成闭包了。闭包函数是每一个匿名函数,闭包对象是是关于timer 的,保存着变量 。
可以下面对这个例子进行变形,可以有助于你的理解把:
正因为,里的第一个函数不会立即执行,当这段代码执行完之后, 已经 被赋值为了(等于时,进入循环,最后又加了),所以 这时再执行 的回调函数,读取 的值,回调函数作用域内没有i,向上读取,上级作用域内的值就是了。但是 ,是立即执行的,所以,每次读的 值 都是对的。
这时候,就需要再用个闭包函数来保存每个循环时 不同的值。
你可能在别处,或者自己想到了下面这种解法:
这个例子不仅利用了闭包,而且还利用了立即执行函数 来模拟 函数作用域 来解决的。
做下变形,你再看看:
其实这道题,知道的 关键词,估计也想到了另一个解法:
我不知道,大家有没有疑惑啊,为啥使用了块级作用域就可以了呢。反正我当初就纠结了半天。
18年 11月 2日修正:
这个答案的关键就在于 块级作用域的规则了。它让声明的变量只在内有效,外部是访问不了的。
做下变形,这个是为了方便理解的,事实并非如此:
当for 的 内使用 时,for 循环就存在两个作用域, 括号里的父作用域,和 中括号里的 子作用域。
每次循环都会创建一个 子作用域。保存着父作用域传来的值,这样,每个子作用域内的值都是不同的。当setTimeout 的匿名函数执行时,自己的作用域没有 的值,向上读取到了该 子作用域 的 值。因此每次的值才会不一样。
你要是喜欢折腾,你会发现,块级作用域的表现跟函数作用域一样,子作用域中使用它的变量,它也会形成一个块级对象,被写入到 函数的 [[scope]] 中。
到此这篇vb名词解释变量(名词解释 变量)的文章就介绍到这了,更多相关内容请继续浏览下面的相关推荐文章,希望大家都能在编程的领域有一番成就!版权声明:
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若内容造成侵权、违法违规、事实不符,请将相关资料发送至xkadmin@xkablog.com进行投诉反馈,一经查实,立即处理!
转载请注明出处,原文链接:https://www.xkablog.com/bcyy/78699.html