作用域
通俗来讲,作用域是一个变量或函数的作用范围。作用域在函数定义时,就已经确定了。目的是为了提高程序的可靠性,减少命名冲突。
作用域的分类
- 全局作用域
- 局部作用域(函数作用域)
根据作用域的不同,变量可以分为两类: 全局变量和局部变量
全局变量
- 在全局作用域下声明的变量,叫「全局变量」。在全局作用域的任何一地方,都可以访问这个变量。
- 在全局作用域下,使用 var 声明的变量是全局变量。
- 在函数内不使用 var 声明的变量也是全局变量(不建议这么用)。
局部变量
- 定义在函数作用域的变量,叫「局部变量」。
- 在函数内部,使用 var 声明的变量是局部变量。
- 函数的形参也是属于局部变量。
从执行效率来看全局变量和局部变量: - 全局变量:只有浏览器关闭时才会被销毁,比较占内存。
- 局部变量:当其所在的代码块运行结束后,就会被销毁,比较节约内存空间。
作用域链
只有函数可以制造作用域结构, 那么只要是代码,就至少有一个作用域, 即全局作用域。凡是代码中有函数,那么这个函数就构成另一个作用域。如果函数中还有函数,那么在这个作用域中就又可以诞生一个作用域。将这样的所有的作用域列出来,可以有一个结构: 函数内指向函数外的链式结构。就称作作用域链。
- 当在函数作用域操作一个变量时,它会先在自身作用域中寻找,如果有就直接使用(就近原则)。如果没有则向上一级作用域中寻找,直到找到全局作用域;如果全局作用域中依然没有找到,则会报错。
- 在函数中要访问全局变量可以使用window对象。(比如说,全局作用域和函数作用域都定义了变量a,如果想访问全局变量,可以使用window.a)
- 全局作用域在页面打开时创建,在页面关闭时销毁。
- 在全局作用域中有一个全局对象window,它代表的是一个浏览器的窗口,由浏览器创建,我们可以直接使用。全局作用域创建的变量都会作为window对象的属性保存。比如在全局作用域内写 var a = 100,这里的 a 等价于 window.a。创建的函数都会作为window对象的方法保存。
预解析
JavaScript代码的执行是由浏览器中的JavaScript解析器来执行的。JavaScript解析器执行JavaScript代码的时候,分为两个过程:预解析过程和代码执行过程
预解析过程
- 把变量的声明提升到当前作用域的最前面,只会提升声明,不会提升赋值。
- 把函数的声明提升到当前作用域的最前面,只会提升声明,不会提升调用。
- 先提升var,在提升function
变量的声明提前
使用var关键字声明的变量( 比如 var a = 1),会在所有的代码执行之前被声明(但是不会赋值),但是如果声明变量时不是用var关键字(比如直接写a = 1),则变量不会被声明提前。
所以,既然JS中存在变量提升的现象,那么,在实战开发中,为了避免出错,建议先声明一个变量,然后再使用这个变量。
函数的声明提前
使用函数声明的形式创建的函数function foo(){},会被声明提前。也就是说,整个函数会在所有的代码执行之前就被创建完成。所以,在代码顺序里,我们可以先调用函数,再定义函数。
fn1(); // 虽然 函数 fn1 的定义是在后面,但是因为被提前声明了, 所以此处可以调用函数
function fn1() {
console.log('上边儿调用的时候,函数声明已经被提前了');
}
使用函数表达式创建的函数var foo = function(){},不会被声明提前,所以不能在声明前调用。
函数作用域
- 函数中,使用var关键字声明的变量,会在函数中所有的代码执行之前被声明。
- 函数中,没有var声明的变量都是全局变量,而且并不会提前声明。
注意,定义形参就相当于在函数作用域中声明了变量。
function fun(e) { // 这个函数中,因为有了形参 e,此时就相当于在函数内部的第一行代码里,写了 var e;
console.log(e);
}
函数预编译步骤
预编译四部曲:
- 函数在运行的瞬间,生成一个执行期上下文 (Active Object),简称AO;
- 分析参数
2.1 函数接收形式参数,添加到AO的属性,并且这个时候值为undefine,例如AO.age=undefined;
2.2 接收实参,添加到AO的属性,覆盖之前的undefined; - 分析变量声明,如var age;或var age=23;
3.1 如果上一步分析参数中AO还没有age属性,则添加AO属性为undefined,即AO.age=undefined;
3.2 如果AO上面已经有age属性了,则不作任何修改; - 分析函数的声明,如果有function age(){};把函数赋给AO.age ,覆盖上一步分析的值;
执行期上下文:
当函数执行时,会创建一个称为执行期上下文的内部对象。一个执行期上下文定义了一个函数执行时的环境,函数每次执行时对应的执行上下文都是独一无二的,所以多次调用一个函数会导致多个执行上下文,当函数执行完毕,它所产生的执行上下文被销毁