变量提升

JavaScript变量提升

前言

JavaScript中的作用域是js中比较重要的一部分,也是大多数面试中必考的内容,我们有必要更加深入的了解下js中作用域。

看一个栗子

仔细阅读以下JavaScript代码,你觉得运行结果会是什么呢?是 1 还是2?

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

不是1,也不是2,答案却是是undefined.

为什么会产生这个让人意外的结果呢?我们得来看下js中的预解析。

JavaScript预解析

JavaScript在浏览器中运行的过程分为两个阶段预解析阶段 执行阶段,在JavaScript引擎对JavaScript代码进行执行之前,需要进行预先处理,然后再对处理后的代码进行执行。

我们平时书写的JavaScript代码并不是JavaScript执行的代码(V8引擎读取一行执行一行这种理解是错误的),它需要预解释后,再由引擎进行执行.

具体的解释过程涉及到浏览器内核的技术不属于前端领域,不过我们可以浅显的理解一下V8在处理JavaScript的一般过程:

以上例中的var a = 2;为例,我们一般人的理解为声明了一个值为2的变量a,但是在JavaScript引擎处理时却分为了两个步骤:

  1. 读取var a后,在当前作用域中查找是否有相同声明,如果没有就在当前作用域集合中创建一个名为a的变量,否则忽略此声明继续进行解析.
  2. 接下来,V8引擎会处理a = 2的赋值操作,首先会询问当前作用域中是否有名为a的变量,如果有进行赋值,否则继续向上级作用域询问.

JavaScript执行环境

我们上面提到的所谓javascript预解释正是创建函数的执行环境(又称“执行上下文”),只有搞定了javascript的执行环境我们才能搞清楚一段代码在执行过后为什么产生这样的结果。

我们用一段伪代码表示创立的执行环境

executionContextObj = {
    'scopeChain': { /* 变量对象 + 所有父级执行上下文中的变量对象 */ },
    'variableObject': { /*  函数参数 / 参数, 内部变量以及函数声明 */ },
    'this': {}
}

作用域链(scopeChain)包括下面提到的变量对象(variableObject)和所有父级执行上下文中的变量对象.

变量对象(variableObject)是与执行上下文相关的数据作用域,一个与上下文相关的特殊对象,其中存储了在上下文中定义的变量和函数声明:

  • 变量
  • 函数声明
  • 函数的形参

在有了这些基板概念之后我们可以梳理一下js引擎创建执行的过程:

  • 创建阶段
    • 创建Scope chain
    • 创建variableObject
    • 设置this
  • 执行阶段
    • 变量的值、函数的引用
    • 执行代码

而变量对象的创建细节如下:

  • 根据函数的参数,创建并初始化arguments object
  • 扫描函数内部代码,查找函数声明(Function declaration)
    • 对于所有找到的函数声明,将函数名和函数引用存入变量对象中
    • 如果变量对象中已经有同名的函数,那么就进行覆盖
  • 扫描函数内部代码,查找变量声明(Variable declaration)
    • 对于所有找到的变量声明,将变量名存入变量对象中,并初始化为"undefined"
    • 如果变量名称跟已经声明的形式参数或函数相同,则变量声明不会干扰已经存在的这类属性

变量提升

正是由于以上的处理,产生了大家熟知的JavaScript中的变量提升,具体以上代码的执行过程如以下伪代码所示:

// global context
executionContextObj = {
    'scopeChain': { ... },
    'variableObject': { a: undefined, f: pointer to function f() },
    'this': {...}
}
...
}//首先在全局执行环境中声明了变量a以及函数f,此时a虽然被声明,但是尚未赋值
x = 1;
function f() {
    executionContextObj {
    'scopeChain': { ... },
    'variableObject': {
    arguments: {},
    a: undefined
        },
    'this': {...}
    }
    //内部词法环境中声明了变量a,此时a虽然被声明,但是尚未赋值
    console.log(a);//此时a需要被被打印出来,在作用域内寻找a变量赋值,于是被赋值undefined
    a = 2;
}

我们可以明显看到,a变量在预解释阶段已经被赋值undefined,在执行阶段js是自上而下单线执行,当console.log(a)执行之时,a=2还没有被执行,a变量的值便是预处理阶段被赋予的undefined,

函数声明与函数表达式

我们看到,在编译器处理阶段,除了被var声明的变量会有变量提升这一特性之外,函数也会产生这一特性,但是函数声明与函数表达式两种范式创建的函数却表现出不同的结果.

我们先看一个实例,运行以下代码

f();
g();
//函数声明
function f() {
    console.log('f');
}
//函数表达式
var g = function() {
    console.log('g');
};

f成功被打印出来,而g函数出现了类型错误,这是什么原因呢?

executionContextObj = {
    'scopeChain': { ... },
    'variableObject': { f: pointer to function f(), g: undefined},
    'this': {...}
}

f();
g();
//函数声明
function f() {
    console.log('f');
}
//函数表达式
var g = function() {
    console.log('g');
};

我们看到,在预解释阶段函数声明的f是被指向了正确的函数得以执行,而函数表达式g被赋予undefined,undefined无法被当作函数执行因此报错g is not a function.

冲突处理

通常情况下我们不会将同一变量变量重复声明,但是出现了类似情况后,编译器会如何处理这些冲突呢?

  1. 变量之间冲突

执行以下函数:

var a = 3;
var a = 4;
console.log(a);

结果显而易见,后声明变量值覆盖前者的值

  1. 函数之间冲突
f();
function f() {
    console.log('f');
}

function f () {
    console.log('g');
};

结果同变量冲突,后者覆盖前者.

3.函数与变量之间冲突

console.log(f);

function f() {
    console.log('f');
}
var f ='g';

结果如下,函数声明将覆盖变量声明

[Function: f]

ES6中的let

在ES6中出现了两个最新的声明语法letconst,我们以let为例,进行测试看看与var的区别.

function f() {
  console.log(a);
  let a = 2;
}
f(); // ReferenceError: a is not defined

这段代码直接报错显示未定义,letconst拥有类似的特性,阻止了变量提升,当代码执行到console.log(a)时,执行换将中a还从未被定义,因此产生了错误.


©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,847评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,208评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,587评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,942评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,332评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,587评论 1 218
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,853评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,568评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,273评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,542评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,033评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,373评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,031评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,073评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,830评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,628评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,537评论 2 269