You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
//全局作用域functionfunc(){//作用域Avara="coffe";(function(){//作用域B。一个IIFE形式的函数,把不想公开的内容隐藏起来vara="1891";varb="b";//这里可以放有很多其他要对外隐藏的内容:变量或者函数//……//…...console.log(a);})();//>> 1891console.log(a);//>> coffeconsole.log(b);//>> Uncaught ReferenceError: b is not defined}
上述代码中,用一个 IIFE 加匿名函数的写法,把变量 b 隐藏起来,函数外面就没法访问它,函数内部可以访问到它。在任何时候都尽量用匿名函数把要调试的代码片段包起来,然后用 IIFE 的形式立即执行。
块级作用域(ES6添加)
ES6 规定,在某个花括号对 {} 的内部用 let 关键字声明的变量或函数拥有块级作用域。
处于向后(backward)兼容的考虑,在块级作用域中声明的函数依然可以在作用域外部引用;如果需要函数只在块级作用域中起作用,应该用 let 关键字写成函数的表达式形态,具体看下面这段代码。
{functionfunc(){//函数声明return1;}}console.log(func());//>> 1// 等同于下面这段代码{varfunc=function(){//未使用let关键字的函数表达式return1;}}console.log(func());//>> 1// 在花括号对 {} 内部由 let 关键字声明的函数,才是真正的处于块级作用域内部{letfunc=function(){return1;}}console.log(func());//>> func is not defined
(function(){for(vari=0;i<100;i++){//……很多行代码}functionfunc(){//……很多行代码}//……很多行代码console.log(i);//>> 100})();// 循环里面的 i 在循环完毕后就没有用了,但并没有被回收掉,而是一直存在的“垃圾”变量,污染了当前的环境。而用 let 声明变量,事后这种垃圾变量会很快被回收掉(function(){for(leti=0;i<100;i++){//……很多行代码}functionfunc(){//……很多行代码}//……很多行代码console.log(i);//>> ReferenceError})();
作用域(scope)
作用域即函数或变量的可见区域。即函数或者变量不在这个区域内就无法访问到。
函数作用域
用函数形式以
function(){...}
类似的代码包起来的...
(省略号)区域,即函数作用域上述代码中,用一个 IIFE 加匿名函数的写法,把变量
b
隐藏起来,函数外面就没法访问它,函数内部可以访问到它。在任何时候都尽量用匿名函数把要调试的代码片段包起来,然后用 IIFE 的形式立即执行。块级作用域(ES6添加)
ES6 规定,在某个花括号对
{}
的内部用 let 关键字声明的变量或函数拥有块级作用域。处于向后(backward)兼容的考虑,在块级作用域中声明的函数依然可以在作用域外部引用;如果需要函数只在块级作用域中起作用,应该用
let
关键字写成函数的表达式形态,具体看下面这段代码。为什么要引进块级作用域
var
声明的变量有副作用:声明提前其次,
var
声明变量有污染综上,应该使用
let
,尽量避免使用var
,除非想定义一个全局变量。执行上下文(Execution Context)
定义
执行上下文就是当前 JavaScript 代码被解析和执行时所在的环境,也叫作执行环境;它是一个抽象概念。
JavaScript 中运行任何的代码都是在执行上下文中运行,在该执行上下文的创建阶段,变量对象、作用域链、this 指向会分别被确定。
类型
window
对象;2.将this
指针指向这个全局对象。一个程序中只能存在一个全局执行上下文eval
执行上下文:运行在eval
函数中的代码也获得了自己的执行上下文,ES6 之后不再推荐使用eval
函数执行上下文的生命周期
执行上下文的生命周期包括三个阶段:创建阶段 => 执行阶段 => 回收阶段。
创建阶段
当函数被调用,但未执行任何其内部代码之前,会做以下三件事:
arguments
,提升函数声明和变量声明(变量的声明提前依赖于var
关键字)this
指向执行阶段
创建完成之后,就会开始执行代码,在这个阶段,会完成变量赋值、函数引用以及执行其他代码。
回收阶段
函数调用完毕后,函数出栈,对应的执行上下文也出栈,等待垃圾回收器回收执行上下文。
画图理解过程
执行上下文栈
上述代码执行上下文入栈出栈的全过程如图所示:
结合代码与流程图可知:
变量对象(Variable Object,VO)
变量对象是一个类似于容器的对象,与作用域链、执行上下文息息相关,存储了在上下文中定义的变量和函数声明。
不同执行上下文中的变量对象稍有不同,具体看看全局上下文的变量对象和函数上下文的变量对象。
函数执行上下文中的变量对象
创建过程总共有三个阶段:
arguments
对象。检查当前执行上下文中的参数,建立该对象下的属性与属性值function
关键字声明的函数),在变量对象中以函数名建立一个属性,属性值为指向该函数所在内存地址的引用。如果该属性之前已经存在,那么该属性将会被新的引用所覆盖undefined
。如果该变量名的属性已经存在,为了防止同名的函数被修改为undefined
,则会直接跳过,原属性值不会被修改当执行上下文进入执行阶段后,变量对象会变为活动对象(Active Object,AO)。此时原先声明的变量会被赋值。变量对象和活动对象都是指同一个对象,只是处于执行上下文的不同阶段。
通过伪代码来表示变量对象和活动对象
未进入执行上下文的执行阶段之前,变量对象中的属性都不能访问。但是进入执行阶段后,变量对象被激活转变为了活动对象,里面的属性可以被访问了,然后开始进行执行阶段的操作。
全局执行上下文的变量对象
全局执行上下文的变量对象是
window
对象。全局执行上下文的生命周期,与程序的生命周期一致,只要程序运行不结束(比如关掉浏览器窗口),全局执行上下文就会一直存在。其他所有的执行上下文,都能直接访问全局执行上下文里的内容。
作用域链(Scope Chain)
多个作用域对应的变量对象串联起来组成的链表就是作用域链,这个链表是以引用的形式保持对变量对象的访问。作用域链保证了当前执行上下文对符合访问权限的变量和函数的有序访问。
当查找变量时,会先从当前上下文的变量对象中查找,如果没有找到,就会从父级(词法层面上的父级)执行上下文的变量对象中查找,一直找到全局上下文的变量对象(即全局对象)。
下面以一个函数的创建和激活两个时期来说明作用域链是如何创建和变化的。
函数创建
JavaScript 采用词法作用域。函数的作用域在函数定义的时候就决定了。
函数有一个内部属性
[[scope]]
,当函数创建的时候,就会保存所有父变量对象到其中,你可以理解[[scope]]
就是所有父变量对象的层级链,但是注意:[[scope]]
并不代表完整的作用域链。以下面代码为例:
函数创建时,各自的
[[scope]]
为:函数激活
当函数激活时,进入函数上下文,创建 VO/AO 后,就会将活动对象添加到作用域链的前端。
这时候执行上下文的作用域链,我们命名为
Scope
:至此,作用域链创建完毕。
流程梳理
以下面代码为例,总结一下函数执行上下文中作用域链和变量对象的创建过程:
执行过程如下:
foo
函数被创建,保存作用域链到内部属性[[scope]]
foo
函数,创建foo
函数执行上下文并入栈foo
函数并不会立即执行,而是进入函数执行上下文的创建阶段[[scope]]
属性创建作用域链foo
作用域链顶端The text was updated successfully, but these errors were encountered: