前言: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得学!