[前言] 在整理闭包笔记的时候,对函数部分笔记的简单整理和理解。
函数
我们都知道函数都有自己的私有的作用域,通常情况下函数以外地方是不能访问函数中的变量的,但是函数内部是可以访问其外层的作用域。
函数只取决于被定义的环境,而非被调用时产生的环境。严谨地说,即语法定义时所产生的作用域就是 词法作用域,因而当词法分析器处理代码的时候将会保持作用域不变。
当退出函数的时候,函数中的局部变量就会随即销毁。
对此,JS权威指南中有一句很精辟的描述:JavaScript中的函数运行在它们被定义的作用域里,而不是它们被执行的作用域里.
Execution Context(运行期上下文)、Activation Object(活动对象)
当开始执行函数时,会创建一个 Execution Context的内部对象,该对象定义了函数运行时的作用域环境,可理解为一个记录当前执行的方法 外部描述信息的对象,记录所执行方法的类型,名称,参数和活动对象(activeObject)。(注意这里要和函数创建时的作用域链对象[[scope]]区分,这是两个不同的作用域链对象,这样分开我猜测一是为了保护[[scope]],二是为了方便根据不同的运行时环境控制作用域链。函数每执行一次,都会创建单独的Execution Context,也就相当于每次执行函数前,都把函数的作用域链复制了一份到当前的Execution Context中)
此时,在Execution Context的作用域链的顶部会插入一个新的对象,叫做 Activation Object,可理解为一个记录当前执行的方法 内部执行信息的对象,记录内部变量集(variables)、内嵌函数集(functions)、实参(arguments)、作用域链(scopeChain)等执行所需信息,其中内部变量集(variables)、内嵌函数集(functions)是直接从第一步建立的语法分析树复制过来的
当函数执行结束之后,就会销毁Execution Context,也就会销毁Execution Context的作用域链,当然也就会销毁Activation Object(但如果存在闭包,Activation Object就会以另外一种方式存在,这也是闭包产生的真正原因,具体的我们稍后讨论。)。
同名变量和形参都引用同一个内存地址
我们先现在来看一个栗子
你的答案是 300 300 100 300 200 100 200 吗?
但正确答案是
// funcA内
300
300
100
100 // 差别在这里,不是300
// funcB内
100
200
这是因为,在一个方法中,同名的实参,形参和变量之间是引用关系,也就是Activation Object中 同名变量和形参都引用同一个内存地址。
现在我们简单构造Execution Context和Activation Object的伪代码来理解一下~
运行期上下文
12345678910111213141516171819var ExecutionContext ={window:{type: "global",name: "global",body: ActiveObject.window},funcA:{type: "function",name: "funcA",body: ActiveObject.funcA,scopeChain: this.window.body},funcB:{type: "function",name: "funcB",body: ActiveObject.funcB,scopeChain: this.funcA.body},}活动对象
123456789101112131415161718192021222324252627282930window.ActiveObject={variables:{x:{value:1}},functions:{funcA:this.funcA},};funcA.ActiveObject={variables:{i:{value:100}},functions:{funcB: SyntaxTree.funcB},parameters:{ // 形参i: this.variables.i ,j: {value: 400} // 重点},arguments:[this.parameters.i,this.parameters.j] // 实参};funcB.ActiveObject={variables:{k:{value:200}},functions:{},parameters:{},arguments:[]}}
在上文中的ActiveObject对象伪代码分析我们可以看到,parameters中i是指向variables中的i,也就是说形参中的i和局部变量i指向同一块存储空间。
注意的是,每个函数被调用的时候都会生成独立的AO,上面的伪代码仅用于理解。
变量查找规则是首先在当前执行环境的 ActiveObject 中寻找,没找到,则顺着执行环境中属性 ScopeChain 指向的 ActiveObject 中寻找,一直到 Global Object(window) ,方法内变量的生存周期取决于方法实例是否存在活动引用,如没有就销毁活动对象。
最后
以上仅为部分笔记的整理,如果有理解错的地方欢迎指正~