变量对象本节讲的是变量对象。都是干货( ̄▽ ̄)变量对象是函数运行时数据的集合,存放的是context中定义的变量和函数,不同函数的变量对象略有不同。还是从上下文上来说,javascript引擎在执行一个函数的时候,会把一个上下文压入上下文栈中。上下文包括:name-variableobject(VO,variableobject)当前函数定义的变量、函数、参数作用域链(Scopechain)源代码定义时形成的作用域链这个伪代码://全局的伪代码contextwindowEC={VO:Window,scopeChain:{},this:Window}作用域链为当前函数提供了上层可访问变量和函数的有序集合;这为函数提供了运行时对象环境;变量对象提供当前函数定义时的变量和函数;上下文生成中包含作用域链的形成,其指向原理在前面的章节中已经梳理过了。javascript中主要有全局上下文和函数上下文。先了解函数调用时上下文栈的变化。上下文栈状态当一个函数被调用时,一个新的上下文会被添加到上下文栈的顶部,然后函数内部的代码块就会运行。因此,执行上下文或函数的生命周期分为上下文创建阶段和函数运行阶段。上下文堆栈状态和可变对象属性在不同阶段会有所不同。示例代码:functionfoo(a){varb=1functionc(){console.log(a+b)//100}c()}foo(99)执行上下文创建阶段在这个阶段,上下文对象将被生成,并创建一个变量对象,创建一个作用域链,并确定this的指向。说千言万语,不如展示一张图。foo函数上下文创建后的栈状态示意图:注:此时函数中的表达式和语句并没有执行,变量对象的属性值被设置为初始值根据规则。运行阶段上下文生成后,进入函数运行阶段。按照代码顺序依次执行函数中的代码(变量赋值、表达式计算、语句执行、其他函数调用等)。在此阶段访问或设置变量对象(活动对象)的属性值。foo函数中第一行代码varb=1执行时,此时的栈状态:以此类推,每次执行函数表达式,都会在执行上下文中获取标识符的值,而运算后的结果将保存在指定的标识符中。因此,执行上下文提供了类似寄存器概念的函数来管理数据。当函数执行时,相应的执行上下文被销毁。JavaScript执行器按源码顺序返回父函数或跳转到其他函数。函数上下文在函数上下文中,我们使用激活对象(activationobject,AO)来表示变量对象。活动对象和变量对象其实是一回事,只是变量对象在规范或者引擎实现上,在JavaScript环境下是访问不到的。只有进入一个执行上下文时,才会访问这个执行上下文的变量对象。激活,所以称为激活对象,只能访问被激活的变量对象,即激活对象上的各种属性。活动对象也是在进入函数上下文时创建的,活动对象是变量对象的一个??激活状态。所以当你把变量对象和活动对象混淆时不要紧,因为它们本质上对我们理解函数调用的细节没有影响。大部分时候我也是直接用可变对象来表达。变量对象的创建变量对象的创建主要是声明和初始化标识符值类型,遵循以下三个原则:生成参数对象。检查当前上下文的形参,生成属性和属性值对象(key:value)。当形式参数未赋值时,属性值设置为undefined。在变量对象上创建函数索引。查看当前作用域内定义的函数,在变量对象中以函数名作为key,以函数所在的内存地址作为value建立索引。如果函数名已经存在于变量对象中,则函数名对应的函数将被新函数替换。所以函数可以重复定义,以后定义的函数会覆盖之前定义的函数。声明变量。查看当前作用域内定义的变量,将变量名key和undefined作为value的内部变量挂载到变量对象中。如果新声明的变量名与已经声明的形参名或函数名相同,则该声明将被丢弃。因此,需要注意的是,函数声明的优先级高于变量声明。一旦一个函数声明占用了某个标识符,如果后续的变量声明使用了之前使用过的函数标识符,则该变量声明将无效。栗子一:functionfoo(){functiontoo(){}vartooconsole.log(typeoftoo)}foo()//function//变量too的声明无效我们对上面的栗子稍微修改一下,栗子二:functionfoo(){functiontoo(){}vartoo=1console.log(typeoftoo)}foo()//number//变量too的值类型是number变量too的值类型是number,看到这个栗子大家可能会一头雾水,因为按照上面3条规则,too的第二次声明应该是无效的,too的类型应该是function。其实too的值类型确实是上下文创建阶段的一个函数。由于javascript是动态弱类型语言,在上下文执行阶段,vartoo=1本质上是给too赋值,发生隐式类型转换,所以在执行阶段,too变成了number类型。在es6语法中,不再推荐使用var来声明变量,而是使用let来声明局部变量,从语法层面强行避免了重复的变量声明,这样栗子2中的情况会直接报错。再次修改上面的栗子,进一步探索:functionfoo(){functiontoo(){}console.log(typeoftoo)//functionvartoo=1console.log(typeoftoo)//number}foo()fooWhen函数运行时,它会先打印'function',然后打印'number'。首先,当表达式console.log(typeoftoo)被执行时,标识符too在上下文创建阶段被初始化为一个函数。vartoo=1执行后,标识符too被赋值1,所以第二个console.log(typeoftoo)的输出是number。另一个例子:functionfoo(){console.log(a)console.log(bar)vara=1functionbar(){return2}}foo()//undefind//?bar(){//return2//}上下文创建阶段解析完函数中的代码块后,会在变量对象中添加'a'和'bar'两个标识符,并填入相应的值,结束上下文的创建阶段和进入foo函数的执行阶段。在执行阶段,foo函数体第一行的表达式要求打印出a的值。由于console.log(a)之前没有对a进行任何赋值操作,按照规则,此时a的值为undefind,所以输出'undefind'。函数体中的第二行要求打印出bar的值。根据规则,标识符'bar'对应一个函数,所以'bar'的值是一个函数实体,在console.log(bar)之前没有给bar赋值,所以打印的是函数。创建变量对象后,函数运行前的变量对象是这样的://VO是VariableObject的缩写,即变量对象VO={arguments:{...},bar:
