前言:
我作为一个js还没进门的小白来说,常常用自己的思维区写代码的时候会出现各种各样的问题,要么运行不了,要么就是结果和预期的不一样。当我认识到了浏览器的编译原理和作用域关系的时候,代码运行不出来这种低级错误就不会再有了,这也能更好地形成一个代码的编写规范。这也是我为什么写这篇文章的原因。
我们看看下面这个代码:
function fn(a){
console.log(a)
var a = 200
console.log(a)
function a(){}
console.log(a)
var b = function(){}
console.log(b)
function d(){}
var d = a
console.log(d)
}
fn(1)
是不看得眼花缭乱?
让我们用函数体内预编译的四个步骤,手动编译一下就清楚了
#发生在函数体内执行之前 四部曲
1. 创建一个AO对象(Activation object)
2. 找形参和变量声明,将变量声明和形参作为AO的属性名,值为undefined
3. 将实参和形参统一
4. 在函数体内找函数声明,将函数名作为AO对象的属性名,值赋予函数体
经过完上述的四个步骤就可以由上到下逐句执行
第一步我们先创建一个AO对象
AO:{}
第二步我们再到函数体中找到变量的声明和形参放到AO中,值未undefined
此处需要注意:var a = 200被分为两句,声明var a;在进行赋值a = 200。
AO:{a:undefined,
b:undefined,
d:undefined,
}
第三步将实参形参统一
a被赋上了1
AO:{a:1
b:undefined
d:undefined
}
第四步找函数声明,将函数名作为AO对象的属性名,值赋予函数体
AO:{a:function a(){}
b:undefined
d:function(){}
}
a的值变成了函数体;d的值也变成了函数体
最后我们开始执行
第一行打印了a:function a(){};
第二行给a赋值:a = 200;
第三行打印了a:200;
第四行函数不执行;
第五行打印了a:200;
第六行赋值在AO中给b赋值:b = function(){};
第七行打印b:function(){};
第八行函数不执行;
第九行给d赋值:d = a;
第十行打印d:200;
附上结果图:
所以我们看见在js编写规范中顺序排列还是非常重要的。
那么在全局下的执行前预编译是怎么样的呢?
-
创建全局对象GO(global object)
-
找全局变量声明,将变量声明作为GO的属性名,值为undefind
-
在全局找函数声明,将函数名作为GO对象的属性名,值赋予函数体 (比函数体内少一个形参统一)
这里就不一一介绍啦,
看到这里的同学可以去尝试全局的预编译
上面我们提到了,函数的作用域就是AO(Activation object)对象;全局的作用域是一个GO(global object)对象。
当函数执行的时候,会创建一个执行期上下文的内部对象叫AO对象,一个执行期上下文定义了一个函数执行时的环境,函数每次执行时对应的执行上下文都是独一无二的,所以多次调用一个函数会导致函数创建多个执行期上下文,当函数执行完毕它所产生的执行期上下文会被销毁。
每个函数被创建的时候会附带一个[[scope]]属性,这就是该函数作用域的属性,其中存储了运行期上下文的集合。当调用这个函数的时候后,会创建AO对象来储存它的参数属性并保存在作用域链上,同事会创建一个更长的表示函数来调用作用域的链。当函数嵌套时,调用外部函数,所产生的作用域链式不同的。
属于隐藏式属性无法访问的。在[[scope]]中呈链式连接的。我们把这种链式连接叫做作用域链。
var d = 20
function a() {
var c = 10
console.log(d)
function b() {
e = 30
console.log(c)
}
b()
}
a()
当执行到函数a的时候
当执行到函数b的时候
函数b中的console.log(c)在b的AO对象中找不到时,会在函数a的AO对象中查找,在作用域链中是向下查找。
为什么外层无法访问内层函数的作用域?
外层无法访问内层函数的作用域,因为在内层函数执行玩后该函数的AO对象会进行内存回收。