新手秒懂 - 高逼格解释变量提高

做者废话

最近为了将来去大城市面试特地从新稳固基础知识javascript

面试多了也会发现,不少都会涉及到平时工做中不去关心的问题, 接来下会不定时地像一样的朋友们分享日常工做中不会接触却又常被问到的面试知识点前端

变量提高?

这个问题试问刚毕业的前端小白都能侃侃而谈,咱们以两道很常见的又很基础的面试题一步一步揭开它的面纱。(做为初学者的我常常搞混)java

function fun () {}
    var fun = 'fuck bitch'
    console.log(fun) //???
复制代码
console.log(fun) //???
    function fun () {}
    var fun = 'fuck bitch'
复制代码

大佬都会说,太简单了。git

  1. 首先,第一题输出的是funck bitch,这不涉及变量提高,只是同名的变量产生了覆盖。(注意: 这发生在执行阶段)
  2. 而后,第二道题, 输出ƒ () {} , 具体的缘由,会谈到你们都知道的变量提高知识

咱们习惯将 var a = 2; 看做一个声明,而实际上 JavaScript 引擎并不这么认为。它将 var a 和 a = 2 看成两个单独的声明,第一个是编译阶段的任务,而第二个则是执行阶段的任务。github

这意味着不管做用域中的声明出如今什么地方,都将在代码自己被执行前 首先 进行处理。 能够将这个过程形象地想象成全部的声明(变量和函数)都会被“移动”到各自做用域的最顶端,这个过程被称为提高。面试

声明自己会被提高,而包括函数表达式的赋值在内的赋值操做并不会提高。浏览器

参考地址函数

如此能够解释 fun 没有报错的缘由,但不少跟我同样的初学者就会问为什么输出 ƒ () {} 而不是 undefined 或者 fuck bitchpost

对于这样的问题,按照我以前的理解。在我眼里,执行代码其实就是ui

function fun () {}
console.log(fun) // --> fun(){}
fun = 'fuck bitch'
复制代码

但若是问我为啥会是这样的,我就说不出来个因此然了 (果真仍是太菜了。。)

为什么会产生变量提高??

不知有没有同窗想过这样的问题,原本通常只是为了应付面试而去瞄一眼说出个因此然就能够的。但做为要成为将来资深的秃头披风大佬,这样作是远远不够的。

这就涉及到javascript语言中执行上下文之变量对象的知识了

image

当 JavaScript 代码执行一段可执行代码(executable code)时,会建立对应的执行上下文(execution context)。

image

一个执行上下文的生命周期能够分为两个阶段。

  1. 建立阶段

在这个阶段中,执行上下文会分别建立变量对象,创建做用域链,以及肯定this的指向。

  1. 代码执行阶段

建立完成以后,就会开始执行代码,这个时候,会完成变量赋值,函数引用,以及执行其余代码。

变量对象(Variable Object)

变量对象的建立,依次经历了如下几个过程。

  1. 创建arguments对象。检查当前上下文中的参数,创建该对象下的属性与属性值。
  2. 检查当前上下文的函数声明,也就是使用function关键字声明的函数。在变量对象中以函数名创建一个属性,属性值为指向该函数所在内存地址的引用。若是函数名的属性已经存在,那么该属性将会被新的引用所覆盖。(常说的函数优先被提高, 且同名会产生覆盖)
  3. 检查当前上下文中的变量声明(未声明的会报错 not defined),每找到一个变量声明,就在变量对象中以变量名创建一个属性,属性值为undefined。若是该变量名的属性已经存在,为了防止同名的函数被修改成undefined,则会 直接跳过 (注意: 跳过是在建立阶段,跟第一题相对应,不要搞混),原属性值 不会被修改

对照两道题,结合描述,咱们就能够了解到具体执行原理:

function fun () {}
    var fun = 'fuck bitch'
    console.log(fun) // 'fuck bitch'
复制代码

由于 fun = 'fuck bitch' 是在执行上下文的执行过程当中运行的,而不会产生覆盖,跳过操做是在建立阶段,所以输出结果天然会是fuck bitch

而第二道题

console.log(fun) // fun () {}
    function fun () {}
    var fun = 'fuck bitch'
复制代码

由于 function 是优先被提高, 而接下来的变量 fun 由于同名而不会在建立阶段产生覆盖,因此输出 fun () {}。 具体以下图相同。

// 上例的执行顺序为

// 首先将全部函数声明放入变量对象中
function fun () {}

// 其次将全部变量声明放入变量对象中,可是由于fun已经存在同名函数,所以此时会跳过undefined的赋值
// var fun = undefined;

// 而后开始执行阶段代码的执行
console.log(fun); // function fun
fun = 'fuck bitch';
复制代码

这样一解释,是否是以为本身逼格瞬间上升了一个档次 ?!

深刻拓展

还不用太兴奋,咱们为了加深理解,换个例子继续一步一步来详细地讲。

function test() {
    console.log(a);
    console.log(foo());

    var a = 1;
    function foo() {
        return 2;
    }
}

test();
复制代码

VO AND AO

在函数上下文, 咱们使用活动对象(activation object, AO)来表示变量对象。

未进入执行阶段以前,变量对象(VO)中的属性都不能访问!可是进入执行阶段以后,变量对象(VO)转变为了活动对象(AO),里面的属性都能被访问了,而后开始进行执行阶段的操做。

它们其实都是同一个对象,只是处于执行上下文的不一样生命周期。

按照上例:
// 建立过程
testEC = {
    // 变量对象
    VO: {},
    scopeChain: {}
}

AO = {
    arguments: {...},  //注:在浏览器的展现中,函数的参数可能并非放在arguments对象中,这里为了方便理解,我作了这样的处理
    foo: <foo reference> // 表示foo的地址引用 a: undefined } 复制代码
// 执行阶段
VO ->  AO   // Activation Object
AO = {
    arguments: {...},
    foo: <foo reference>, a: 1, this: Window } 复制代码

按此来讲, 上例的执行顺序应该是:

function test() {
    function foo() {
        return 2;
    }
    var a = undefined;
    console.log(a);
    console.log(foo());
    a = 1;
}
test();
复制代码

接下来增长难度:

// demo2
function test() {
    console.log(1, foo);
    console.log(2, bar);

    var foo = 'Hello';
    console.log(3, foo);
    var bar = function () {
        return 'world';
    }

    function foo() {
        return 'hello';
    }
}

test(); // ??? 
复制代码

可转换为

// demo2
function test() {
    function foo() {
        return 'hello';
    }
    // 其次将全部变量声明放入变量对象中,可是由于foo已经存在同名函数,所以此时会跳过undefined的赋值
    // var foo = undefined;
    
    var bar = undefined;
    
    console.log(1, foo);
    console.log(2, bar);

    var foo = 'Hello';
    
    console.log(3, foo);
    
    bar = function () {
        return 'world';
    }
}

test(); 
// 1 ƒ foo() {
// return 'hello';
// }
// 2 undefined
// 3 "Hello"
复制代码

到此,即是我想向你们分享的内容,本小白菜鸡打算明年年初向上海发起进攻,也但愿能有个好的结果,努力,奋斗。💪💪

参考文献

变量对象详解

深刻之变量对象

相关文章
相关标签/搜索