预编译
函数内部的预编译:函数执行之前要进行的步骤
-
AO activation object 活跃对象,函数上下文
在全局GO执行的时候,函数执行前一刻,预编译形成的
函数执行完成后,AO是要销毁的,所以每次执行一次函数都会有一个新的 AO
-
行程过程/步骤:
-
寻找形参和变量声明(变量声明的提升问题)
-
实参值赋值给形参
-
找函数声明,赋值
-
执行函数
代码中理解:
function test(a){ // 形参STEP1 console.log(a); // f a(){} 此时还没赋值a var a = 1; // 变量声明STEP1.5 console.log(a); // 1 function a(){} // 函数声明STEP3 console.log(a); //1 此时a已经赋值了 var b = function(){} // 变量声明STEP1.5 console.log(b); // f(){} function d(){} // 函数声明STEP3 } test(2); // 实参赋值给形参STEP2 /* AO = { a : undefined (STEP1) -> 2 (STEP2)-> function a(){} (STEP3) -> 1 (STEP4), b : undefined(STEP1) -> function(){} (STEP1.5) -> f(){}(STEP4), d : function d(){} (STEP3) }*/
-
函数外部的预编译
-
GO global object 全局上下文 GO===window
-
行程过程/步骤:
-
变量声明
-
函数声明
-
执行
代码中理解:
var a = 1; function a(){ console.log(2); } console.log(a); // 1 //GO = { // a : undefined -> f a(){console.log(2)} // -> 1(执行时赋值) //} console.log(a , b); // f a(){} undefined !!注意,运行到这一行的时候还没有赋值,不要搞错运行顺序; function a (){} var b = function(){}
-
GO 和 AO 一起分析:
跟着题目一起分析,巩固知识,再也不怕面试问到啦。
-
题目一:
var b = 3; console.log(a); // f a(a){console.log(a);... } function a(a){ console.log(a); // f a(){} var a = 2; console.log(a); // 2 function a (){} var b = 5; console.log(b); // 5 } a(1); // GO = { // b: undefined -> 3, // a: f a(){} -> f a(a){console.log(a);... f a(){...}} //} // AO = { // a :undefined -> 1 -> f a(){} -> // 2, // b :undefined -> 5, //}
-
题目二:
a = 1; function test(){ console.log(a); // undefined // 注意!:函数体内AO中有 a 的声明,所以不会去外部找 a 所以打印undefined a = 2; console.log(a); // 2 var a = 3; console.log(a); // 3 } test(); var a; // GO = { // a: undefined -> 1, // test: f test(){...} //} // AO = { // a : udnefined -> 2 -> 3 // //}
-
题目三:
function test(){ console.log(b); // undefined if (a){ var b = 2; } c = 3; console.log(c); // 3 } var a; test(); a = 1; console.log(a); // 1 // GO = { // a : undefined -> 1, // test : f test(){...}, // c : 3, //} // AO = { // 注意:不要管if语句,因为还没有执行,不要管条件是否符合,只要有声明都算 // b : undefined //}
-
题目四:
function test(){ return a; // f a(){} //注意: 函数体内有声明 a 所以全局中的a它不会先考虑 return后面的不会执行 a = 1; function a(){} var a = 2; } console.log(test()); // f a(){} // GO = { // test : f test(){...}, // a : undefined, //} // AO ={ // a : undefined -> f a(){}, //}
-
题目五:
function test(){ a = 1; function a(){} var a = 2; return a; // 2 } cosnole.log(test()); // 2 // GO = { // test : f test(){...}, // a : undefined -> 1, //} // AO = { // a : undefined -> f a(){} -> 2, //}
-
题目六:
a = 1; function test(e){ function e(){} arguments[0] = 2; console.log(e); // 2 if (a){ // 注意!!函数内的 a 为 undefined var b = 3; } var c; a = 4; // 赋给函数内的 a 要记得a已经在函数内提前声明了,会先找内部的a var a; console.log(b); // undefined f = 5; console.log(c); // undefined console.log(a); // 4 } var a; test(1); console.log(a); // 1 外层的 console.log(f); // 5 函数内部声明的全局变量 // GO = { // a : undefined -> 1, // test : f test(){...}, // f : undefined -> 5 //} // AO = { // e : undefined -> 1 -> f e(){} -> 2, // b : undefined, // c : undefined, // a : undefined -> 4, //}
作用域,作用域链
其实函数也是一种对象类型 引用类型 引用值
例如:以下方式可以看出
test.name
函数名,test.length
函数形参个数等属性
在JS中,对象中有一些属性是我们无法访问的,可以理解为JS 引擎内部固有的隐式属性,私有属性,研究意义在于了解其原理。
下面了解一些JS中的隐式属性:
-
[[scope]]
-
是函数创建时生成的一个JS内部的隐式属性;
-
是函数存储作用域链的容器;
- 作用域链Scope Chain:AO,函数的执行期上下文/GO,全局的执行期上下文,【函数执行完成后,AO是要销毁的,所以每次执行一次函数都会有一个新的 AO】
也就是说,AO 是一个即时的存储容器;它并不是长期存在的,而是根据函数的执行周期而存在的
- 作用域链Scope Chain:AO,函数的执行期上下文/GO,全局的执行期上下文,【函数执行完成后,AO是要销毁的,所以每次执行一次函数都会有一个新的 AO】
-
-
作用域链
每个函数在定义的时候,会创建作用域,作用域中存在作用域链,而且会在作用域链里面存储 GO
- 一开始GO被作用域链的第0位所指,但当函数到被执行的前一刻,预编译生成AO时,此时的GO会被它自身的AO挤下去,并被作用域链的第1位所指,自身的AO才是排在对顶端的位置被第0位所指,(这样也说明了为什么函数最先着自己的东西,而不是外层的东西)
- 在函数体内,查找变量是到a函数存储的作用域链中,从顶端开始一次向下查找
- 一个规律:外层执行时,内部被定义
在代码中理解:
```js
function a(){
function b(){
function c(){}
c();
}
b();
}
a();
// GO执行:
// 外部开始执行,函数开始被定义:
// a 定义:a.[[scope]] -> 0 :GO
// a 执行:a.[[scope]] -> 0 :a -> AO
// 1 :GO
// b 定义:b.[[scope]] -> 0 :a -> AO
// 1 :GO
// b 执行:b.[[scope]] -> 0 :b -> AO
// 1 :a -> AO
// 2 :GO
// c 定义:c.[[scope]] -> 0 :b -> AO
// 1 :a -> AO
// 2 :GO
// c 执行:c.[[scope]] -> 0 :c -> AO
// 1 :b -> AO
// 2 :a -> AO
// 3 :GO
// c 结束:c.[[scope]] -> 0 :b -> AO (c -X> AO)
// 1 :a -> AO
// 2 :GO
// b 结束:b.[[scope]] -> 0 :a -> AO (b -X> AO)
// 1 :GO
// c.[[scope]] X
// a 结束:a.[[scope]] -> 0 :GO (a -X> AO)
// b.[[scope]] X
// 回到最初a定义的时候的环境
```
```js
function test1(){
function test2(){
var b = 2;
console.log(a);
}
var a = 1;
return test2();
}
var c = 3;
var test3 = test1();
test3();
```
执行上下文、进栈
闭包
相关概念:
-
当内部函数被返回到外部并保存时,一定会产生闭包,闭包会产生原来的作用域链(AO)不释放;【回顾知识点:函数执行后AO会被销毁】
-
所以,过度地使用闭包可能会导致内存的泄露的这种现象,或者加载过慢;
-
闭包会产生私有变量,外界无法访问闭包内的变量,只有拽住这个AO的函数才能访问;
试着理解以下代码:
function breadMgr(num){ var breadNum = arguments[0] || 10; function supply(){ breadNum += 10; console.log(breadNum); } function sale(){ breadNum--; console.log(breadNum); } return [supply,sale]; } var breadMgr = breadMgr(50); breadMgr[0](); // 50 + 10 = 60 breadMgr[1]();// 60 - 1 = 59 // function sunSched(){ var sunSched = ''; var operation = { setSched: function(thing){ sunSched = thing; }, showSched: function(){ console.log("My schedule on Sunday is " + sunSched); } } return operation; } var sunSched = sunSched(); sunSched.setSched('reading'); sunSched.showSched(); // "My schedule on Sunday is reading"