一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第1天,点击查看活动详情。
js 预编译是什么
先看一段代码
// 函数
function fn(a, c) {
console.log(a, "a1");
var a = 123;
console.log(a, "a2");
console.log(c, "c1");
// 函数声明
function a() {}
console.log(a, "a3");
console.log(b, "b1");
// 函数表达式
var b = function () {};
console.log(b, "b2");
// 函数声明
console.log(d, "d");
function d() {}
function c() {}
console.log(c, "c2");
}
//调用函数
fn(1, 2);
你知道上面代码的执行结果,如果你不复制代码到浏览器中看结果,那么我相信你已经理解了预编译,也就不用再看下去了,如果你不知道结果,那么你可以尝试看下去。相信你会有所收获。
什么是预编译
预编译分为全局预编译和局部预编译,上面的代码就是局部预编译,全局预编译发生在页面加载完成时候执行,而局部预编译发生在函数执行前。
全局预编译步骤
- 创建 GO 对象(全局对象)
- 找变量声明,将变量名作为 GO 属性名,值为 undefined
- 查找函数声明,作为 GO 属性,值赋值给函数体
局部预编译步骤
- 创建 AO 对象(执行上下文)
- 找形参和变量声明,将变量和形参作为 AO 属性名,值为 undefined
- 将实参值和形参值统一
- 在函数体里面找函数声明,值赋值给函数体
逐步分析代码
// 开始会创建 AO 局部编译对象
AO:{
}
// 然后查找形参和变量声明作为 AO的属性并且赋值为undefined
// 因为 function d(){} 是函数体 要到最后一步才查找
AO:{
a:undefined,
c:undefined,
b:undefined,
}
// 将形参和实参的值统一 赋值给AO对应的属性
AO:{
a:1,
c:2,
b:undefined
}
// 在函数体里找函数声明,值赋值给函数体(如果函数声明和变量一样,那么会覆盖变量声明)
// 因为 b 是函数表达式,不是函数声明,所以此时b还是undefined
AO:{
a:function a(){},
c:function c(){},
b:undefined,
d:function d(){}
}
// 执行到这里接下来就要开始 js解释执行,也就是逐行执行代码
// 函数
function fn(a, c) {
console.log(a, "a1");
var a = 123;
console.log(a, "a2");
console.log(c, "c1");
// 函数声明
function a() {}
console.log(a, "a3");
console.log(b, "b1");
// 函数表达式
var b = function () {};
console.log(b, "b2");
// 函数声明
console.log(d, "d");
function d() {}
function c() {}
console.log(c, "c2");
}
//调用函数
fn(1, 2);
// 预编译结果
AO:{
a:function a(){},
c:function c(){},
b:undefined,
d:function d(){}
}
// 首先执行console.log(a,'a1')
function a(){} "a1"
// 接下来 a赋值变成123 AO 的属性 a就变成了123
// 打印a2
123 "a2"
// 打印c1
function c(){} "c1"
// 打印a3 此时AO属性的 a值依然是123
123 "a3"
// 打印 b1 此时b是undefined
undefined "b1"
// 这个时候函数 function (){} 赋值给b 所以AO属性的b 为 function(){}
// 打印 b2
function(){} 'b2'
// 打印 d
function d(){} "d"
// 打印c2
function c(){} "c2"
最终答案
全局预编译
全局预编译根据相对应的步骤逐步分析即可
作用域
作用域
作用域是指程序源代码中定义变量的区域。
作用域分为全局作用域和函数作用域
看一段代码来了解下全局作用域和函数作用域
// 全局作用域
var a = 1;
function foo() {
// 函数作用域
console.log(a);
}
function bar() {
// 函数作用域
var a = 2;
foo();
}
bar();
词法作用域(静态作用域)和动态作用域
看上边代码,会输出什么呢?
词法作用域结果:从 foo 内部查找局部变量 a,没有就根据书写位置继续向上查找 也就是 a=1,所以输出 1 动态作用域结果:从 foo 内部查找局部变量 a,没有继续向上查找,就是从调用的函数作用查找,找到 a=2,多以打印 2
而 js 是采用的词法作用域,所以 在 js 环境下会输出 1
作用域的浅层次理解
1.全局作用域
-
全局作用域在页面打开时创建,页面关闭时销毁
-
编写在 script 中的变量和函数,作用域为全局,在任何地方都可以访问到
-
在全局作用域中有全局对象 window,代表一个浏览器窗口,由浏览器创建,可以直接调用
-
全局作用域中声明的变量和函数会作为 window 对象的属性和方法保存
2.函数作用域
-
调用函数时,函数作用域被创建,函数执行完毕,函数作用域被销毁
-
每调用一次函数就会创建一个新的函数作用域,他们之间相互独立
-
在函数作用域中可以访问到全局作用域的变量,在函数外无法访问到函数作用域内的变量
-
在函数作用域访问变量,函数会在自身作用域查找,没有找到则会到函数的上一级作用域查找,直到全局作用域,找不到则返回 null
作用域的深层次理解
- 执行期的上下文
- 当函数代码执行的前期,会创建一个执行期上下文的内部对象 AO (作用域)
- 这个内部对象是预编译的时候创建出来的,因为当函数被调用的时候,会先进行预编译
- 在全局代码执行的前期会创建一个执行期的上下文对象 GO
- 预编译
- 预编译参考第一篇文章,有详细解释
作用域链
看一段代码
var a = 1;
function fn() {
var b = 2;
function bar() {
console.log(a + b);
}
return bar();
}
fn();
当我们执行 bar 函数时,我们会在 bar 内部查找 a 和 b 变量,如果没有,那么我们会继续向上查找,到 fn 的作用域查找,这个时候找到 b,这个时候我们还差一个变量 a 继续向上查找,一直会到最后的全局作用域,如果全局作用域没有,那么返回 null,这个过程形成的链就是作用域链