前言:JS执行三步走
众所周知JavaScript是解释性语言,主要特点为:解释一行执行一行
JS执行三步走:语法分析,预编译,解释执行
语法分析:在代码执行之前对代码进行通篇检查,以排除一些低级错误
预编译:代码执行的前一刻 ==> 在内存中开辟一些空间,存放一些变量与函数
解释执行:执行代码
JavaScript预编译
在讲解JS预编译之前得先了解两个东西,什么函数声明?什么是变量声明?
//JS两种常见的函数声明方式
function a(){}
var a=function(){}
//JS常见的变量声明方式
var a=10;
1.未经声明的对象直接复制,直接归window所有
2.全局上的任何变量,即使声明了也归window所有
3.window就是全局
function a(){
var a=10; //声明了,但是在函数体内,是局部变量
b=10; //没有声明,归window所有
}
var c=10; //全局上的变量,声明了归window所有
这样解释的话应该就好懂多了~
①函数声明整体提升 ②变量声明提升 ③预编译
先来讲讲变量声明提升
// 变量声明可以看成两部分:声明操作(var a)+赋值操作(a=10)
// 声明操作会在编译阶段进行,声明操作会被提升到执行环境的顶部,值是undefined(未初始化)
// 赋值操作会留在原地等待执行操作
var a = 2;
function foo() {
console.log(a); // undefined
var a = 10;
console.log(a); // 10
}
foo();
// 上面的代码相当于
var a = 2;
function foo() {
var a;
console.log(a); // undefined
a = 10;
console.log(a); // 10
}
foo()
预编译发生在函数执行的前一刻做了这些事情:
1.创建AO对象 (Activation Object) 作用域/执行上下文
2.找形参和变量声明,将变量和形参名作为AO属性名,值为undefined
3.将实参值和形参统一
4.在函数体里面找函数声明,值赋予函数体
这一块我迷迷糊糊的理解了,但是记录下来应该会理解的更加透彻
先来一个简单点的
function fn(z){
console.log(z);
console.log(a);
var a=123;
console.log(a);
function a(){}
console.log(a);
console.log(b);
var b=function b(){}
console.log(b);
function d(){}
}
fn(1);
我们来分析一下,第一步创建AO对象。找形参和变量声明,将变量和形参名作为AO属性名,值为undefined。上面这块代码中fn(1) ==> 1是实参 function(a){...} ==> a是形参
AO{
z:undefined,
a:undefined,
b:undefined,
d:undefined
}
紧接着实参值和形参统一,在函数体里面找函数声明值赋予函数体,所以AO发生变化
a先是被提升AO值为undefiend,最后又因为函数声明提升值赋予函数体,解释的好像有点绕,多看几遍就差不多了!
AO{
z:1,
a:function a(){...},
b:undefined,
d:function d(){...}
}
这里有一个注意点,大家会好奇,b为什么会是undefined,大家再看看前面讲的,变量声明提升值为undefined,b只是值赋予一个函数体,但是在预编译环节,它不是一个函数声明,所以值还是undefined!!!
这样预编译就完成了,然后我们来看看代码执行会发生什么?
function fn(z){
console.log(z); //1
console.log(a); //AO==>a:function a(){...}
//拿AO里面的值
var a=123;
//a被赋值 所以AO里面的a也会变为123
console.log(a); //123
function a(){}
console.log(a); //123
//AO里面的值为123
console.log(b); //undefined
//AO里面的b为undefiend
var b=function b(){}
//前面把b赋值了 所以b的AO也会发生变化
console.log(b);//function b(){...}
function d(){}
}
fn(1);
看了这么多,大家差不多应该能够理解。预编译是在代码执行的前一刻执行。代码执行实质是拿预编译的内容。上面的第一个console.log(a);因为没有任何赋值,所以会拿预编译的结果,a是一个函数体。后面一行a被赋值123。所以第二次打印a的结果的时候显示的就是123了。
看完了AO对象,其实GO对象就更好理解了。GO对象同理,指向的是window对象。其实window对象也就是GO对象的意思了,只是少了形参和实参统一的步骤罢了
1.生成了一个GO对象 Global Object
2.找形参和变量声明,将变量和形参名作为AO属性名,值为undefined
3.在函数体里面找函数声明,值赋予函数体
还是来一道题,一起来分析一下!
function test(){
var a=b=123;
console.log(window.a); //undefined
console.log(window.b); //123
}
test();
先生成GO后生成AO。上面这个仔细分析一下a是一个声明变量。而b并没有声明就赋值归GO所有。所以window上找a自然找不到返回undefined,而b返回123。
在来一个小知识点,下面是一个函数,有形参,实参。
function gouge(love){
console.log(love); // I love GouGe
arguments[0]=123;
console.log(love); // 123
}
gouge("I love GouGe");
当实参传入"I love GouGe",这个时候打印输出形参会返回实参的值。因为前面也说过预编译阶段会将实参值和形参统一。紧接着第二次调用前,我们通过rguments[0]将形参改为了123,所以这个时候我们调用他会返回123。
最后的最后再来一道恶心的题,弄懂这道题预编译应该就真的懂了!
a=100;
function demo(e){
function e(){}
arguments[0]=2;
console.log(e); //2
if(a){
var b=123;
function c(){
//猪都能做出来
//if里面定义一个function函数语法不通过
//以前的浏览器可以
}
}
var c;
a=10;
var a;
console.log(b); //undefined
f=123;
console.log(c); //undefined
console.log(a); //10
}
var a;
demo(1);
console.log(a); //100
console.log(f); //123
我还是把GO和AO写出来吧~
GO{
a:100,
demo:function(e){...},
f:123
}
AO{
e:2,
b:undefined,
c:undefined,
a:10
}
切记!按照规则一步步来!a实际上是变量声明,尽管a的赋值在前面,在预编译阶段变量声明还是会提到前面来。还有if语句,在执行if之前a是没有进行赋值的,所以if语句不会往里面走。
大家有不懂的地方欢迎提出一起交流学习,当然文章难免也会出现一些错误,欢迎大家评论区指出~
这个预编译过程在所有的javascript 解释器里是一样的吗?
学习了
学习了,谢谢
推荐你了解下es6的语法
嗯嗯 趋势来看ES6得学!