JavaScript预编译

200 阅读6分钟

1、预编译

1.1、JS的两次扫描

  1. 预编译:第一次扫描\前置扫描
  2. 解释执行:二次扫描

1.2、脚本步骤

  1. JS引擎创建全局对象Golobal Object(window)执行期上下文
  2. JS引擎加载脚本文件
  3. JS引擎脚本预编译
    • 找出所有变量的声明,按照变量加入全局对象,如果已经存在,则忽略不运行赋值
    • 找出函数声明,按照函数名加入全局对象,如果已经存在同名变量或者函数,替换
    • 非声明不予理睬

1.3、脚本预编译例子

1.3.1 例子1:没有var/let/const的变量认为是全局变量,不参与预编译

全局变量情况

<script> 
// 没有var的变量,全部认为是window的全局变量,不参与预编译! 
console.log(aa); // 报错`Uncaught ReferenceError: aa is not defined`
aa=5;  // 即使在函数中,aa也是全局变量、是运行时,不是定义时。 
console.log(aa); // 5
</script>
  • 情况:第一个输出结果:Uncaught ReferenceError: aa is not defined
  • 原因:预编译什么都不做;在执行中第一个console.log打印aa的时候aa没有被定义,所以直接崩掉

var声明变量的情况

console.log(aa); //undefined
var aa=5; 
console.log(aa); //5
  • 原因:
    • 预编译:生成了全局对象GO、然后开始扫描找到变量和函数声明并加入全局对象中,非声明不理睬
    • 解释执行:因为在预编译中aa已经加入GO对象,但并没有初始化,所以第一个console为undefined;再走一行aa被赋值成5,最后一行console结果是5

小思考

这种机制有个好处,函数可以在后面的位置声明,同时可以再前面的位置使用

1.3.2 例子2:即使在函数中,aa=5也是全局变量

函数内存在全局变量,不调用函数的情况

// 即使在函数中,a也是全局变量、是运行时,不是定义时。 
function test(){ 
  a=5; 
} 
console.log(a);
  • 情况:Uncaught ReferenceError: a is not defined
  • 原因:1、预编译的时候扫到了test这个函数,但没有扫到a。 2、执行的时候test没有调用,console.log的时候a没有定义所以出错

函数内存在全局变量,调用函数的情况

<script> 
//即使在函数中,a也是全局变量、是运行时,不是定义时。 
test(); 
function test(){ 
  a=5; 
} 
console.log(a); //5 
</script>
  • 结果:5
  • 原因:
      1. 预编译:扫到了test函数声明,没有扫到a。
      1. 执行test函数,函数进行预编译,函数预编译没有结果。执行时将a放到了window上,成全局变量并赋值为5,函数销毁。执行console.log时结果等于5。

1.3.3 例子3: 脚本中所有变量声明,在脚本的预编译阶段完成,所有变量的声明与实际的书写位置无关

console.log(aa); //undefined
var aa=5; 
console.log(aa); //5

1.3.4 例子4:脚本中,所有函数声明,在脚本的预编译阶段完成,所有函数的声明与实际的书写位置无关。

<script> 
console.log(haha); 
function haha(){ 
  console.log('h1'); 
} 
</script>
  • 结果:haha(){ console.log('h1'); }

1.3.5 例子5:脚本中如果函数变量同名那么函数会覆盖变量,与位置无关,变量无法覆盖函数!

<script> 
console.log(haha); 
var haha="123"; 
function haha(){ console.log('h1'); } 
var haha="321"; 
</script>
  • 结果:haha(){ console.log('h1'); }

1.3.6 例子6:脚本中,后面的函数声明会覆盖前面的函数声明,并且忽略参数

<script> 
console.log(haha); 
function haha(a){ console.log('haha1'); } 
function haha(a,b){ console.log('haha2'); }  
</script>
  • 结果:haha2

1.4、函数步骤

  1. 创建活动对象AO(Active Object)(上下文)
  2. 预编译
    • scope chain[作用域链]
    • 初始化arguments
    • 初始化形参,将arguments中的值赋值给形参
    • 找出所有的变量声明,按照变量名加入AO,若存在则忽略
    • 找出所有函数声明,按照函数名加入AO,若存在同名变量或函数就替换
    • this初始化

1.5、函数预编译例子

1.5.1 函数中,所有变量声明,在函数的预编译阶段完成,所有变量的声明与实际的书写位置无关【同脚本结果】

function f(){ 
  console.log(aa); // undefined
  var aa=5; 
  console.log(aa); // 5
} 
f();

1.5.2 函数中,所有函数声明,在函数的预编译阶段完成,所有函数的声明与 实际的书写位置无关【同脚本结果】

function f(){ 
  console.log(haha);
  function haha(){
    console.log('h1'); 
  }
} 
f();

1.5.3 函数中,如果变量和函数同名,那么,函数将覆盖变量【同脚本结果】

<script> 
function f(){
  console.log(haha); 
  var haha=123; 
  function haha(){ 
    console.log('h1'); 
  }
} 
f(); 
</script>

1.5.4 函数中,只有函数能够覆盖变量,变量无法覆盖函数。 【同脚本结果】

<script> 
function f(){
  console.log(haha); 
  function haha(){ 
    console.log('h1'); 
  } 
  var haha=123; 
} 
f();
</script>

1.5.5 函数中,后面的函数声明会覆盖前面的函数声明,并且,忽略参数

function f(){ 
  console.log(haha); 
  function haha(a){ 
    console.log('haha1'); 
  } 
  function haha(a,b){ 
    console.log('haha2'); 
  } 
} 
f();

1.5.6 当函数预编译后,遇到需要访问的变量或函数,优先考虑自己AO中定义的变量和函数;如果找不到,才会在其定义的上一层AO中寻找,直到到达GO

var scope ="global"; 
function f(){ 
  console.log(scope); //undefined 
  var scope="local";        
  console.log(scope); //local 
} 
f(); 
console.log(scope); //global 
  • 分析:
    • 1、脚本预编译GO:里面有scope=undefined和函数f = function。
    • 2、执行时GO环境下scope赋值“global”。t:执行
    • 3、生成t-AO:
      • 预编译:扫描到了scope变量赋值undefined
      • 执行时:函数内部第一个scope是undefined,然后是local,紧接着函数作用销毁。

2、练习

练习1

console.log(scope); // undefined
var scope = 'global';              
function t(){                      
  var scope = 't-local';             
  function t2(){                     
    console.log(scope); // undefined
    var scope = 't2-local';            
    console.log(scope); // t2-local
  }                                  
  t2();                              
  console.log(scope); // t-local
}                                  
t();                               
console.log(scope); // global
  1. GO:
    • scope:undefined -> global;
    • t:function
  2. t-AO:
    • scope:undefined -> t-local; [退出函数时 t-AO被摧毁]
    • t2:function
  3. t2-AO:
    • scope:undefined -> t2-local; [退出函数时 t2-AO被摧毁]

练习2

function test(x,x) {
    console.log(x); // function
    x = 5;
    console.log(arguments); // [12,5]
    function x() {}
}
test(12,13);
  1. Go: test:function
  2. test-AO:
    • 初始化arguments:[12,13]
    • 绑定x:arguments[1]:13 (x:第一次绑12、第二次绑13)->【x:arguments[1]:13】
    • 预编译:
      • x:function ->【x:arguments[1]:function】
    • 执行:x:5【在执行x=5时】
      • x:arguments[1]:5

练习3

b = 'cba';
function a(a, a){
	console.log(a); // function
	console.log(b); // undefined
	var b = 'abc';  

	a();
	function a(){
		console.log(a);  // function
		console.log(b); // abc
	}
}
a(5,10);
  1. GO:(this:window)
预编译执行
afunction进入
bcba
  1. a-AO: (this:window) | | 参数 | 参数绑定 | 预编译 | 执行 | --- | --- | --- | --- | --- | | | [5,10] | a-参数[1] | | | b | | | undefined | abc | | a | | | function(此时参数[1]由于绑定也变成function) | 进入 |

  2. a.a-AO:(this:window) 到上一级查找

练习3

var str = 'aaa';
str += 1;
var test = typeof(str);
if(test.length == 6){
	test.newproperty = 'string';
//	var obj = new String(test);
//	obj.newproperty = 'string';
//	摧毁 obj
}
console.log(test.newproperty); // undefined

练习4

var x = 1, y = z = 0;       // x,y,z:undefined       1,0,0   1,4,4
function add(n){            // add:function
	return n = n + 1;
}
y = add(x);
function add(n){           // add:function(n+3)
	return n = n + 3;
}
z = add(x);

练习5: 哪个可以输出:[1,2,3,4,5]

function foo(x){ // 可以
	console.log(arguments);
	return x;
}
foo(1,2,3,4,5);

function foo(x){ // 不可以,不报错不输出;函数声明;数组表达式
	console.log(arguments);
	return x;
}[1,2,3,4,5]

function foo(x){ // 不可以
	console.log(arguments);
	return x;
}(1,2,3,4,5);

(function foo(x){ // 可以
	console.log(arguments);
	return x;
})(1,2,3,4,5);

练习6:

function test(x, y, a){
	arguments[2] = 10;
	alert(a); // 10
}
test(1,2,3);

扩展:

  1. 编译与链接:代码-编译->块结构-链接->exe
  2. 预编译:扫描函数声明到‘名字表’
  3. 脚本:没有函数,没遇到函数前的脚本
  4. 函数声明:function <函数名>([<形参>]){函数体}