1、作用域
作用域定义了如何查找变量的位置 ,即确定当前执行上下文中变量和其他资源的作用范围。(本质上就是能访问到定义变量 的范围)。作用域包括:全局作用域 、函数作用域 和块级作用域。
全局作用域
在代码的任何地方都能访问到的变量所拥有的作用域。
1、最外层函数和在最外层函数外面定义的变量
javascriptvar a = 1; // 全局作用域 function func() { var b = 2; // 函数作用域 } console.log(a) // 输出1 console.log(func) // 输出 [Function: func] console.log(b) // ReferenceError: b is not defined2、所有末定义直接赋值的变量自动声明为拥有全局作用域
javascriptfunction func() { var a = b = 1; } // a 为函数作用域 b由于未使用var定义,在编译过程中会在全局定义编译结果类似下方代码 var b; function func() { b = 1; var a = b; } console.log(a) // ReferenceError: a is not defined console.log(b) // 输出13、所有window对象的属性拥有全局作用域
一般情况下,window 对象的内置属性都拥有全局作用域,例如window.name、window.location、window.top等。
函数作用域
在函数内部定义的变量,拥有函数作用域。函数作用域仅在定义变量的函数内部访问到,在变量所在函数外部及全局作用域中无法访问到该变量。如下方代码中的变量 a。
javascriptfunction func() { var a = 12; console.log(a) // 输出 12 } console.log(a) // ReferenceError: a is not defined
块级作用域
在es6 中引入了let和const来申明变量。而在 **{ }**中使用let或const声明变量,那么这个 { }括住的变量就形成了一个块级作用域。所声明的变量在指定块的作用域外无法被访问。
javascriptif(true) { const a = 12; // 块级作用域 console.log(a) // 输出 12 } console.log(a) // ReferenceError not defined* 注意 *
- 内部作用域 可访问到 外部作用域 ,反之不可以
- 函数调用时会执行上下文,创建一个对象 AO:{} (表示函数作用域对象)
- 每个函数都会有自己的函数作用域属性 [[scope]] 这个隐式属性:只供给引擎访问的属性,其中存储了执行期上下文的集合
2、作用域链
指的是作用域查找的线路,即 [[scope]] 中所存储的执行期上下文对象的集合 ,这个集合呈链式连接,我们把这种链式链接叫做作用域链 。(也就是说,在访问一个变量时,首先会在自身范围内查找,如果没有找到的话,会一层层的向上查找 ,直到找到全局作用域,若此时还没有找到,则会返回not defined 报错,而这一层层的关系就是所谓的作用域链)
javascriptvar a = 1; function func1() { var b = 2; function func2() { var c = 3; console.log(a) // 输出 1 console.log(b) // 输出 2 console.log(c) // 输出 3 } console.log(a) // 输出 1 console.log(b) // 输出 2 console.log(c) // 输出 ReferenceError not defined } console.log(a) // 输出 1 console.log(b) // 输出 ReferenceError not defined console.log(c) // 输出 ReferenceError not defined
3、预编译
在 JS 代码执行之前,会对 JS 代码进行编译,编译之后,再从上到下依次执行。
编译步骤:词法分析(词法单元)、语法解析(抽象语法树)、代码生成
对于本章节来说,我们需要注意的是发生在编译阶段的 申明提升。
- 在编译时将变量的声明,提升到当前作用域的顶端
- 函数声明整体提升(会提升到最前面)
如下方代码示例:
javascriptfoo() function foo(){ console.log(a);//undefined var a=1 } var b=2 //foo()函数执行前进行编译,编译时相当于如下 foo(); b=2; function foo(){ console.log(a);//undefined var a=1 }在 JavaScript 程序执行前,会先创建全局执行上下文 (Global Execution Context),这个过程又分为两大阶段 :创建阶段(又称变量环境/词法环境的初始化)和执行阶段。
- 创建阶段
全局对象创建:为全局作用域创建一个全局对象,并生成全局执行上下文
词法环境和变量环境的建立:
函数声明 :例如 function func(){...} 。在创建阶段,JavaScript 引擎会把整个函数体作为一个对象存储,并将标识符 func 和函数对象的引用存储到全局环境中。这样,调用 func() 时就可以直接找到该函数。
变量声明(var 声明):
1、 var 属于变量声明表达式 (Variable Declaration Expression)。在创建阶段,所有通过 var 声明的变量都会在当前的环境记录中被创建,并初始化为 undefined 。这一过程称为"变量提升"(Hoisting)
2、 在全局上下文中,会为 var b 创建一个标识符b ,初始值为 undefined (赋值操作 b = 2 会在执行阶段完成)。
注意 :在同一作用域内,如果有函数声明和变量声明同名,函数声明 的优先级高于 var 声明 。并且后续的 var 声明不会覆盖已经存在的绑定(只影响赋值部分)。
- 进入函数 func 的执行上下文
当执行 func() 时,会创建一个新的执行上下文,这个上下文同样有创建 和执行两个阶段:
1 、在 创建阶段 内:
函数内部的变量声明: var a 被处理,创建一个绑定(内部环境记录中添加标识符 a ),初始值为 undefined 。这就意味着即使代码中 var a = 1 在 console.log(a) 之后写,变量 a 已经存在,但其值目前还是 undefined 。
函数内部的其他声明: 如果有其他函数声明、参数等,同样会被处理,但本例中只有 var a 。
javascript// 全局执行上下文(创建阶段): var b; // 创建变量 b, 初始值 undefined function foo(){ // 创建函数 foo,函数体整体存储 var a; // 在 foo 的上下文中创建变量 a, 初始值 undefined console.log(a); // 此时 a 为 undefined a = 1; // 赋值操作 } // 执行阶段(全局): foo(); // 调用 foo() 时进入 foo 的执行上下文 b = 2; // 全局变量 b 赋值为 2
4、闭包
- 闭包的定义
闭包 是由捆绑起来(封闭的)的函数 和函数周围状态 (词法环境 )的引用组合而成。换言之,闭包让函数能访问它的外部作用域。在 JavaScript 中,闭包会随着函数的创建而同时创建。
当函数可以记住并访问所在的词法作用域时,就产生了闭包 。即使函数是在当前词法作用域之外执行。(闭包 :在一个作用域中可以访问另一个函数内部的局部变量的函数)
- 闭包的形成
javascript
function func() {
var a = 1;
return function() {
console.log(a)
}
}
var fn = func();
fn()
func() 函数的执行结果返回给 fn ,而此时由于变量 a 还在使用,因而没有被销毁,然后执行 fn() 函数。这样,我们就能在外部作用域访问到函数内部作用域的变量。这个就是闭包。
闭包的形成条件:
- 函数嵌套
- 内部函数引用外部函数的局部变量
- 闭包的作用
- 可以读取函数内部的变量
- 可以使变量的值长期保存在内存中,生命周期比较长。
- 可用来实现 JS 模块( JQuery 库等)
JS 模块 是具有特定功能的 JS 文件,将所有的数据和功能都封装在一个函数内部(私有的),只向外暴露一个包含多个方法的对象或函数,模块的使用者,只需要通过模块暴露的对象调用方法来实现对应的功能。
javascript(function() { var a = 1; function test() { return a; } window.module = {a, test}; // 向外暴露 })()
- 闭包的特性
- 每个函数都是闭包,函数能够记住自己定义时所处的作用域,函数走到了哪,定义时的作用域就到了哪。
- 内存泄漏
内存泄漏 就是一个对象在你不需要它的时候仍然存在。所以不能滥用闭包。当我们使用完闭包后,应该将引用变量置为 null 。
javascriptfunction outer(){ var num = 0; return function add(){ num++; console.log(num); }; } var func1 = outer(); func1(); // 1 func1(); // 2 [没有被释放,一直被占用] var func2 = outer(); func2(); // 1 [重新引用函数时,闭包是新的] func2(); // 2