ES5中只分为全局作用域和函数作用域java作用域,也就是说for,if,while等语句是不会创建作用域的。ES6(let,const)除外。
几个概念
执行环境:也称为执行期上下文,当函数执行时,会创建一个称为执行期上下文的内部对象。一个执行期上下文定义了一个函数执行时的环境,函数每次执行时对应的执行上下文都是独一无二的,所以多次调用一个函数会导致创建多个执行上下文,当函数执行完毕,执行上下文被销毁。它定义了变量或函数有权访问的其他数据,决定了它们的各自行为。
执行环境分为全局执行环境和函数执行环境(这个名字我自己取的),其中在浏览器中全局执行环境是一个对象。
活动对象(AO)与变量对象(OV): 活动对象也就是前面说的执行期上下文的内部对象,每个函数执行的时候都会创建这样一个活动对象,当函数函数还未执行完毕的时候,又进入到其他的作用域中,那么这个活动对象就变为变量对象(OV),比如说在一个函数里面执行另一个函数,另一个函数会创建一个活动对象,而当前的变为变量对象。当另一个函数执行完毕,它的执行上下文被销毁,返回上一个函数,那么上一个函数的变量对象又变为活动对象。可以理解为当前执行环境的执行期上下文的内部对象称为活动对象,否者称为变量对象。另外变量对象和活动对象都存储了本级环境中所定义的变量和函数,函数的话还包括参数
预编译:在进入全局环境和函数环境之前就会进行
预编译步骤:
1、创建AO对象(活动对象 或者说执行期上下文) 如果是全局环境就叫做window对象或者Go对象
2、查找形参和变量声明,值为
3、将实参和形参相统一,值为实参里面的值值为实参里面的值
4、找函数声明,属性值为声明时候的属性值
[scope]:每个函数都是一个对象,对象中有些属性我们可以访问,但有些不可以,这些属性仅供引擎存取,[scope]就是其中一个。[scope]指的就是我们所说的作用域,其中存储了前面说的执行期上下文的集合,这个属性是在函数被定义的时候就创建
作用域链:[scope]中所存储的执行期上下文对象的集合,这个集合呈链式链接,我们把这种链式链接叫做作用域链。
寻找变量的过程就是从变量作用域链开始查找的,如果在当前的变量作用域没找到,那么就去上一个变量作用域里面去早
作用域链创建的过程:
预编译
先来理解一下预编译,看例子:
var a = 10
代码语言:javascript复制 b = 20
function test (a, b) {
console.log(a, b, test1, d)
var a = 10
console.log(a)
if (b) {
var c = 20
}
console.log(a, b, test1, d)
function test1 () {
}
var d = function () {
}
console.log(a, b, test1, d)
}
第一步 全局环境下预编译
进入全局环境并在执行全局环境代码之前进行预编译,跟着我前面说步骤
1、生成GO对象
GO: {}
2、查找形参和变量声明(这里不是函数,因此没有查找形参这一步),值为
GO: { a: } // 注意这里因为b是没有声明的所以不会被放到GO这个活动对象里面
3、将实参和形参相统一,值为实参里面的值值为实参里面的值(在全局环境依然没有这一步)
4、找函数声明,属性值为声明时候的属性值
GO: { a: , test: () {} }
第二步 全局环境下执行代码
先执行 a = 10 ,注意因为声明的步骤我们已经在预编译的时候执行过了,所以这里就只是赋值操作,此时的GO对象是
GO: { a: 10, test: () {} }
再执行 b = 10 ,注意这里由于没有声明预编译的时候没有被放到GO对象里面去,因此在这行代码之前访问这个对象会报错,当执行到这个代码的时候会被放到GO对象里面去,此时的GO对象为
GO: { a: 10, test: () {} , b: 10}
第三步 函数环境下预编译
现在该执行test函数了,进入到一个新的执行环境 ,在执行函数代码之前会进行预编译
1、生成AO对象
AO: {}
2、查找形参和变量声明, 值为
AO: { a: , b: , c: , d: }
3、将实参和形参相统一,值为实参里面的值
AO: { a: 1, b: , c: , d: }
4、找函数声明,属性值为声明时候的属性值
AO: { a: 1, b: , c: , d: , test1: () {} }
第四步 函数环境下执行代码
依旧和在全局环境下执行代码一样,执行赋值语句,下面就说当执行到某个.log() 的时候它的AO对象是什么
第一个.log()的时候AO对象为
AO: { a: 1, b: , c: , d: , test1: () {} }
第二个.log()的时候AO对象为
AO: { a: 10, b: , c: , d: , test1: () {} }
第三个.log()的时候AO对象为
AO: { a: 10, b: , c: , d: , test1: () {} }
第三个.log()的时候AO对象为
AO: { a: 10, b: , c: , d: () {}, f: () {} }
注意: 在预编译的时候,像let和const这种块级作用域的,如果放到if语句里面,是不会被添加到AO或者GO对象里面的,像上面的例子中if里面如果换成let或者const的话,预编译阶段是不会被添加进去的
作用域链
看例子:
第一步:a函数被定义,创建[scope]
上面函数被定义的时候,[scope]会把当前函数所在的上下文放入这个对象中。需要注意的时候,函数存储被定义时的上下文的时候java作用域,只是存储的是一个引用,而不是副本,正是因为这样,才能形成作用域链,当函数在本函数的AO对象找不到的时候,就沿着本函数的[scope]存储的上一个作用域的变量对象的引用到上一个作用域里面去早。
第二步,执行函数
执行函数的时候,会先发生预编译,然后会把AO对象放入到这个[scope]中
第三步:函数b在函数中是被定义了的,在执行预编译的时候,会为它创建一个[scope]属性,里面存储了b函数当前所在的上下文,也就是a的[scope]。
第四步,执行b函数
和a函数执行一样,依然创建一个AO对象放入
作用域链就是这样形成的,每个函数都有一个[scope]里面储存了运行期上下文的集合。寻寻找变量的过程,就是沿着作用域链从上到下寻找,找不到就在最上面一层定义,也就是在global object里面成为全局变量,也就是我们常说的变量不定义就使用会成为全局变量。
---------------------
本文共 1709 个字数,平均阅读时长 ≈ 5分钟