JavaScript执行前的秘书——预编译

7,916次阅读
没有评论

共计 3096 个字符,预计需要花费 8 分钟才能阅读完成。

随着对 js 一步步地深入,预编译——神奇的 js 规则出现了。预编译有函数有的预编译和全局的预编译,相信在读完这篇文章后,以后在面试官恶心你的时候,可以从容应对。

我们来举个JavaScript 执行前的秘书——预编译

var a=1
console.log(a);
function foo(){
    var a=1;
    console.log(a);
}
foo()

这两段代码输出值都是 1,
那下面这段呢

var a = 2;
function foo(a){
    var a = 1;
    function a(){}
    console.log(a);
}
foo(3)

这一段就要让你思考了
揭晓答案

JavaScript 执行前的秘书——预编译

所以,函数传参,内部声明,赋值调用应该谁先谁后呢

接下来就来解析这个魔术了

声明提升

在 js 里声明提升遵循下面两个规则

  • 变量声明,声明提升
  • 函数声明,整体提升

想象一下,JavaScript 引擎在执行任何代码之前,会先做一次全面的“彩排”,这就是所谓的“声明提升”(Hoisting)。在这个阶段,它会扫描整个剧本(即代码),寻找所有的变量声明和函数声明,并将它们“提”到当前作用域的最顶部,但需要注意的是,只有声明会被提升,初始化操作依然保持原位。

变量声明提升

无论是使用 varlet 还是 const(注意letconst有块级作用域,行为稍有不同),其名称会被提升至作用域顶部,值默认为undefined。这意味着你可以在声明之前访问这些变量,尽管它们此时还未被赋值。例如上面给的代码

var a
console.log(a);// 输出 undefined
a=1
console.log(a);// 输出1

函数声明提升

整个函数体都会被提升,这意味着你可以在声明之前调用函数,而不会遇到引用错误。这种机制允许了函数的自我调用和递归调用成为可能。

foo()// 先调用函数

function foo(){
    var a=1;
    console.log(a);// 输出 1 
}
function foo(){
    var a=1;
    console.log(a);// 输出 1 
}
foo()// 后调用函数

在 js 中,每当运行一个程序时,就会按一下步骤解析代码

1. 创建函数的执行上下文对象 AO {Activation Object}
2. 找形参和变量声明,将形参和变量名作为 AO 的属性,值为 undefined
3. 将实参和形参统一
4. 在函数体内找函数声明,将函数名作为 AO 的属性名,值赋予函数体

我们就用下面代码作分析

function fn(a){
    console.log(a);
    var a =123;
    console.log(a);
    function a(){}
    console.log(a);
    var b = function(){}
    console.log(b);
    function d(){}
    var d = a
    console.log(d);
}
fn(1);

创建 AO
找形参和变量声明为 AO 的属性, 值为 undefined

AO{
    a:undefined,
    b:undefined,
    d:undefined,
}

统一形参和实参, 函数声明提升调用函数,将 1 赋值给 a
AO 中的变量更新

AO{
    a:1,
    b:undefined,
    d:undefined,
}

在函数体内找函数声明,将函数名作为 AO 的属性名,值赋予函数体

function fn(a){
    console.log(a);
    var a =123;
    console.log(a);
    function a(){}
    console.log(a);
    var b = function(){}
    console.log(b);
    function d(){}
    var d = a
    console.log(d);
}
fn(1);

AO 更新

AO{
    a:function a(){},
    b:undefined,
    d:function d(){},
}

最后开始运行,根据 AO 中的值来按顺序输出

function fn(a){
    console.log(a);// 输出 function a(){}
    var a =123;
    console.log(a);// 上一条代码把 123 赋值给了 a,输出 123
    function a(){}
    console.log(a);// 输出 123
    var b = function(){}
    console.log(b);//function(){}函数体赋值给了 b,输出 function(){}
    function d(){}
    var d = a     // 把 a 的值 123 赋给 d 
    console.log(d);// 输出 123
}
fn(1);

最后 AO 的变化如下

AO:{
    a:undefined 1  function a(){} 123,
    b:undefined function b(){},
    d:undefined function d(){} 123,
    a:function(){}
}

这就是函数的预编译

和函数预编译比全局的预编译优先级更高,在全局作用域中,预编译同样默默进行,只是这里的主角换成了“全局执行上下文对象”GO(Global Object)。这个对象代表了整个 JavaScript 程序的顶级作用域。

其过程分三步

  1. 创建全局执行上下文对象 GO
  2. 找变量声明,变量名作为 GO 的属性名,值为 undefined
  3. 在全局找函数声明,函数名作为 GO 的属性名,值为函数体

了解了函数预编译,全局预编译就很容易理解了

var global = 100
function fn(){
    console.log(global);
}
fu()

在全局中创建 GO 对象, 找变量声明,值为 undefined,在全局找函数声明值为函数体

GO{
    global:undefined,
    fu():function fun(){}
}

下一步就是创建 AO 对象,执行函数预编译,由于函数中没有变量,则为没有属性,global 从全局中找值,输出 100

GO{
    global:undefined
    fu():function fun(){}
}
var global = 100
function fn(){
    console.log(global);//100
}
AO{

}
fu()

如果在函数中添加 global 变量

global = 100
function fu(){
    console.log(global);
     global = 200
    console.log(global);
    var global = 300
 }
 fu()
 console.log(global);
 var global;

那么 GO 应该为

GO:{
    global:undefined 100 ,
    fu():function fn(){},
}

AO 应该是

AO:{
    global:undefined 200 300,
}

所以最后的代码应该是

// GO:{
//     global:undefined 100 ,
//     fu():function fn(){},
// }
global = 100
function fu(){
    console.log(global);// 输出 undefined
    global = 200
    console.log(global);// 输出 200
    var global = 300
}
// AO:{
//     global:undefined 200 300,
// }
fn()
console.log(global);// 输出 100
var global;

最后来对 js 中预编译的方法做总结分析

在 js 预编译中
系统会按照预编译的顺序,为程序开辟空间,先全局预编译入栈,后函数预编译入栈,执行完后出栈,最后完成执行,这就可以保证程序的安全性,不会进入死循环的状态
重点强调,函数执行上下文要在全局上下文中寻找参数时,必须先经过词法环境,才能到达变量环境
就如下图所示

JavaScript 执行前的秘书——预编译

JavaScript 执行前的秘书——预编译

这就是 js 中的神奇规则——预编译,在许多面试中,HR 经常会出以上毫无观感却能运行的恶心代码,只要对 js 的运行规律了如指掌,就可以从容应对这种问题,并且能够深入本质,就可以在同一批面试里超越 90% 的面试者,成为的最后赢家。

    正文完
     0
    Yojack
    版权声明:本篇文章由 Yojack 于2024-09-20发表,共计3096字。
    转载说明:
    1 本网站名称:优杰开发笔记
    2 本站永久网址:https://yojack.cn
    3 本网站的文章部分内容可能来源于网络,仅供大家学习与参考,如有侵权,请联系站长进行删除处理。
    4 本站一切资源不代表本站立场,并不代表本站赞同其观点和对其真实性负责。
    5 本站所有内容均可转载及分享, 但请注明出处
    6 我们始终尊重原创作者的版权,所有文章在发布时,均尽可能注明出处与作者。
    7 站长邮箱:laylwenl@gmail.com
    评论(没有评论)