Javascript变量提升以及函数提升

引入

下面的代码变片段会如何输出?

// 代码段1
function func1() {
    var a = 1;
    function a() {}
    console.log(a);
}
func1();
  
// 代码段2
function func2() {
    var a;
    function a() {}
    console.log(a);
}
func2();

运行后,控制台第一个console.log(a)会输出1,而第二个console.log(2)则会输出函数a。结果如下:

[Running] node "/Users/ermao/Development/javascript_code/homework/demo.js"
1
[Function: a]

[Done] exited with code=0 in 0.044 seconds

Javascript运行时,会经过三个阶段^[《JavaScript预编译过程理解
节选]:

  1. 语法分析阶段:语法分析很简单,就是引擎检查你的代码有没有什么低级的语法错误;
  2. 预编译阶段:预编译简单理解就是在内存中开辟一些空间,存放一些变量与函数 ;
  3. 解释执行阶段:解释执行顾名思义便是执行代码了;

Javascript具体执行过程:

  1. 页面产生便创建了GO全局对象(Global Object)(也就是window对象);
  2. 第一个脚本文件加载;
  3. 脚本加载完毕后,分析语法是否合法;
  4. 开始预编译
    • 查找变量声明,作为GO属性,值赋予undefined;
    • 查找函数声明,作为GO属性,值赋予函数体;

【注意】

  • 预编译阶段发生变量声明和函数声明,没有初始化行为(赋值),匿名函数不参与预编译 ;
  • 只有在解释执行阶段才会进行变量初始化 ;
  1. 预编译两个小规则
    1. 函数声明整体提升—(具体点说,无论函数调用和声明的位置是前是后,系统总会把函数声明移到调用前面)
    2. 变量 声明提升—(具体点说,无论变量调用和声明的位置是前是后,系统总会把声明移到调用前,注意仅仅只是声明,所以值是undefined)
  2. 预编译前奏
    1. imply global 即任何变量,如果未经声明就赋值,则此变量就位全局变量所有。(全局域就是Window)
    2. 一切声明的全局变量,全是window的属性;var a=12;等同于Window.a = 12;
    3. 函数预编译发生在函数执行前一刻;

变量提升

本小节将注重体会Javascript预编译阶段,变量如何创建以及赋值的。如:

// 定义两个变量
var num1 = 50;
var num2 = 100;

在Javascript眼中代码是这样运行的。

// 第一步:预编译阶段,将变量申明(定义)放在作用域最前面。
var num1;
var num2;
// 第二步:执行赋值操作
num1 = 50;
num2 = 100;

再通过一个例子理解下:

function func(){
    var m = 10;
    console.log(m);
    console.log(n);
    var n = 20;
}
func();

其实在Javascript眼中他是这样运行的:

function func(){
    var m;
    var n;
    m = 10;
    console.log(m);
    console.log(n);
    n = 20;
}

此时控制台输出:

[Running] node "/Users/ermao/Development/javascript_code/homework/demo.js"
10
undefined

[Done] exited with code=0 in 0.049 seconds

下面通过两个函数同时运行,比较下控制台输出结果:

function func(){
    var m = 10;
    console.log(m);
    console.log(n);
    var n = 20;
}
func();

console.log();

function funcT(){
    var m;
    var n;
    m = 10;
    console.log(m);
    console.log(n);
    n = 20;
}

funcT();

此时控制台输出结果如下所示:

[Running] node "/Users/ermao/Development/javascript_code/homework/demo.js"
10
undefined

10
undefined

[Done] exited with code=0 in 0.045 seconds

两个函数的执行结果完全相同。所以 Javascript 并不是在定义一个变量的时候,声明完成之后立即赋值,而是把所有用到的变量全部声明之后,再到变量的定义的地方进行赋值,变量的声明的过程就是变量的提升。

变量在声明提升的时候,是全部提升到作用域的最前面,一个接着一个的。但是在变量赋值的时候就不是一个接着一个赋值了,而是赋值的位置在变量原本定义的位置。原本js定义变量的地方,在js运行到这里的时候,才会进行赋值操作,而没有运行到的变量,不会进行赋值操作。

所以变量的提升,提升的其实是变量的声明,而不是变量的赋值。

函数提升

首先还是可以通过一个例子体会下函数提升的过程是怎样的。

function func(){
    console.log("func运行了");
}

// 申明前调用函数funcT();
funcT();

// 申明后调用函数func();
func();

function funcT(){
    console.log("funcT运行了");
}

此时控制台输出:

[Running] node "/Users/ermao/Development/javascript_code/homework/demo.js"
funcT运行了
func运行了

[Done] exited with code=0 in 0.046 seconds

此时两个函数都能成功执行。与变量提升不同的是,函数的提升是一步完成的。不同于变量提升是分成两步运行的,第一步是变量声明的提升,第二步是变量的赋值。函数的提升是直接将整个函数整体提升到作用域的最开始位置。

变量提升与函数提升的顺序

依旧通过例子体会:

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

按照代码的执行顺序,从上而下,理论上控制台应该是先输出undefined1a(){}。但是实际控制台输出如下:

[Running] node "/Users/ermao/Development/javascript_code/homework/demo.js"
# 先输出的是函数
[Function: a]
# 再输出1;
1
# 最后还是输出1
1

[Done] exited with code=0 in 0.051 seconds

为什么会这样输出呢?其实在JS眼中,代码是这样的

// 先发生变量提升,此时a如果能输出的话,肯定是输出undefined。
var a;
// 函数提升覆盖了定义的变量a的undefined,此时a;
function a(){
    
}
// 提升结束后,此时遇到了第一个console.log()。故此时输出函数a
console.log(a);
// 带代码运行到var a=1时,a=1覆盖了函数提升阶段a的函数申明。
a = 1;
// 当代码运行到第二个console.log()的时候,故输出1;
console.log(a);
// 因为函数申明提前了,所以无法再去覆盖a的值。
// 所以第三个console.log()输出的仍然是1。
console.log(a);

函数申明与函数表达式的顺序

上面讲解了变量提升,以及函数提升,下面再来理解前节中所提:函数表达式<span style="color:red;font-weight:bolder">必须先定义方可调用!</span>函数申明<span style="color:red;font-weight:bolder">则既可先定义再调用,也可先调用再定义。</span>这句话的意思。

仍然先看例子,下面的控制台如何输出?

console.log(testFunc);

console.log(func);

testFunc();

func();

function testFunc(){
    console.log("testFunc()执行了");
}

var func = function (){
    console.log("func()执行了");
}

控制台输出:

[Running] node "/Users/ermao/Development/javascript_code/homework/demo.js"
[Function: testFunc]
undefined
testFunc()执行了
/Users/ermao/Development/javascript_code/homework/demo.js:7
func();
^

# 这个地方就报错了,无法执行
TypeError: func is not a function
    at Object.<anonymous> (/Users/ermao/Development/javascript_code/homework/demo.js:7:1)
    at Module._compile (internal/modules/cjs/loader.js:945:30)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:962:10)
    at Module.load (internal/modules/cjs/loader.js:798:32)
    at Function.Module._load (internal/modules/cjs/loader.js:711:12)
    at Function.Module.runMain (internal/modules/cjs/loader.js:1014:10)
    at internal/main/run_main_module.js:17:11

[Done] exited with code=1 in 0.047 seconds

从控制台输出可以看出,Javascript眼中代码是这样运行的:

// 整个提升过程
var func; // 先提升了变量
// 再提升函数
function testFunc(){
    console.log("testFunc()执行了");
}

// 代码执行阶段
console.log(testFunc);  // 此时输出函数体
console.log(func);      // 此时代码并没有运行到赋值阶段,所以还能输出undefined

// 运行函数
testFunc();             // 函数可以正常运行
func();                 // 无法运行,因为在提升阶段是指当做变量提升了,并没有赋值。
                        // 所以func不能当做函数来运行
                        
// 当执行到func 赋值时,为时已晚。报错后,无法继续运行了
func = function(){
    console.log("func()执行了");
}

<span style="text-indent:2em;font-weight:bolder;color:red">就是说,函数提升仅适用于函数声明,而不适用于函数表达式。</span>简而言之,函数表达式在预编译提升阶段是当做变量进行提升的,并没有对变量进行赋值。

那么箭头函数能成功运行吗?请参考下面的代码:

console.log(testFunc);

console.log(func);

testFunc();

func();

function testFunc(){
    console.log("testFunc()执行了");
}

var func = () => {
    console.log("func()执行了");
}

控制台输出如下所示,原因呢?

[Running] node "/Users/ermao/Development/javascript_code/homework/tempCodeRunnerFile.js"
[Function: testFunc]
undefined
testFunc()执行了
/Users/ermao/Development/javascript_code/homework/tempCodeRunnerFile.js:7
func();
^

TypeError: func is not a function
    at Object.<anonymous> (/Users/ermao/Development/javascript_code/homework/tempCodeRunnerFile.js:7:1)
    at Module._compile (internal/modules/cjs/loader.js:945:30)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:962:10)
    at Module.load (internal/modules/cjs/loader.js:798:32)
    at Function.Module._load (internal/modules/cjs/loader.js:711:12)
    at Function.Module.runMain (internal/modules/cjs/loader.js:1014:10)
    at internal/main/run_main_module.js:17:11

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

推荐阅读更多精彩内容