中阶魔法 - 提升 Hoisting

前情提要

艾草:「我们今天来提升一下吧!」

「不是每天都在提升魔力总量了吗?」

艾草:「不一样唷,今天的提升更特别,我特别提倡身心灵的提升。」

(艾草一说完,我发现身体开始往上升了起来。)

「咦咦,啊啊,修但几勒,不是这种提升吧,我怕高~~~~~~呜啊啊啊啊」

艾草:「看样子这次的提升也会是你难忘的回忆呢!ಠﭛಠ」


前言

ReferenceError: __ is not defined、undefined 差异

is not defined

因为记忆体里没有变数 a,自然会显示找不到变数 a

console.log(a); //Uncaught ReferenceError: a is not defined

undefined

a 变数已宣告但尚未赋值时,undefined 为预设代入的值,表示值还没被定义。

var a;  
console.log(a);//undefined

Hoisting

理解了以上两者的差异後,来想想在变数宣告前先 console.log(a),会跑出哪个结果呢?

console.log(a);
var a = 10;  
  1. ReferenceError: a is not defined
  2. undefined

如果依照程序码由上往下跑的特性,我们可能会猜测是答案 1,但正确解答为 2.undefined!这其中的原因便是 Hoisting。

Hoisting 为 JavaScript 独有的现象,代表变数的宣告和函式的宣告会在创造阶段就被放入记忆体。

让我们复习一下执行环境吧!


执行环境 Execution Context:

JavaScript在运行前会先建立执行环境,第一个被建立的是全域执行环境 (Global Execution Context),而每个 function 也都会有自己独立的执行环境,会由下依序往上叠加。

那我们把环境建立好後,当然就是开始动工啦!

其实执行环境在创建後区分以下两阶段运行:

第一阶段:创造阶段 (Creation Phase)

进入创造阶段时,JavaScript 会开始创造 Variable Object(VO),所谓的创建 VO 指的是会先搜寻此作用领域(scope) 内拥有的变数、函式,找到後会先将这些被宣告的变数、函式储存到记忆体,这就是提升的幕後黑手!

值得注意的点是需将变数分为宣告与赋值两阶段,仅会提升宣告阶段,不提升赋值阶段。

第二阶段:执行阶段 (Execute Phase)

在此阶段会依造程序码一行一行去执行。

回到一开始的程序码,console.log(a) 显示 undefined 便是因为 a 在创造阶段 JavaScript 创造 VO 时,已经被存入记忆体空间了,而 a 的值尚未被赋予 10 这个值,是因为仅会提升宣告阶段,不提升赋值阶段。

小结:JavaScript 会在 EC 里创造阶段时先创造 VO ,这就是 Hoisting 的原因。


变数宣告 Hoisting

为何特别提到变数的宣告会在编译阶段就被放入记忆体呢?

console.log(a);
var a = 10;

我们可能会以为结果会是 10,但结果却是 undefined

那是因为在创造时会将已被宣告的变数先建立新的记忆体空间,但跑程序码的顺序还是由上往下。

可以理解为:

var varHoistingTest; //创造时提前建立 a 变数的记忆体空间
console.log(varHoistingTest);//undefined
a = 10;

既然是变数的宣告都会在创造阶段被放入记忆体那不论写 varletconst 应该都是一样的吧?

console.log(varHoistingTest); //undefined
var varHoistingTest;

//let、const 的 Hosting 效果差不多,所以先用let来测试
console.log(letHoistingTest); //ReferenceError: letHoistingTest is not defined
let letHoistingTest;

咦!结果居然不一样难道是 letconst 没有被提升吗?

NONONO,事情可没这麽简单!

var a = 10;
function letTest(){
  console.log(a)
//ReferenceError: Cannot access 'a' before initialization at letTest
  let a; 
}
letTest()

如果今天 let 没有被提升的话,在查询时应该会果断回覆 10,但却显示了还没执行到 a 之前不能使用这个变数!

其实变数 varletconst 都是有被提升的,差别在 var 预设会初始化一个值 undefined,而 letconst 并没有这个设定,且在初始化前它们不能够被使用,如果被使用会导致 Temporal Dead Zone(TDZ) 时间死区。

小结:只要是变数都有被提升,差别在 var 会自动初始化并赋予值 underfind,但 letconst 不会,在未对它们进行初始化前强行使用会导致时间死区TDZ。


函式 Hoisting

这里复习一下定义函式的方式:

函式表达式 Function expression,通常会把它存成一个变数:

functionExpression(); //ReferenceError: functionExpression is not defined

var  functionTest = function functionExpression(){
	console.log("我没有被提升");
}

可以将程序码拆解成以下的范例,因为 VO 只提升宣告阶段,所以变数已存入记忆体空间,但变数 = function 这件事尚未被执行到,所以呼叫时会显示型态错误,并不是 function

var  functionTest;
console.log(functionTest);//underfind
functionTest(); //TypeError: functionTest is not a function
functionTest = function functionExpression(){
	console.log("我没有被提升");
}

函式陈述式 Function declaration,仅陈述状态,function 放在最前方的写法:

functionDeclaration();

function functionDeclaration() {
  console.log('我被提升了');
}

在函式陈述式(函式宣告)时,我们即使提前呼叫程序码,依然能执行 function 内容,函式陈述式有 Hoisting 的效果。

argumentsTest(10);    //20

function argumentsTest(number) {
  return number + number;
}

带入在函式陈述式里的参数也会有提升的效果。

小结:函式陈述式及函式参数有提升的效果,但函式表达式没有。


Hoisting 优先顺序大比拼

现在我们已经知道变数函式宣告都会有提升的效果了,那它们是谁优先呢?

变数 PK 函式


console.log(hoistingFirst);
var hoistingFirst = 123456789;
function hoistingFirst() {};

检查时会回传的是 function 而不是 undefined ,结果为 function 获胜!


总结

  • JavaScript 会在执行环境创造阶段时提前准备记忆体空间给宣告的变数、函式
  • 变数仅会提升宣告阶段
  • 使用 var 宣告的变数,会先初始化被赋予undefined
  • 使用 letconst 宣告的变数,提前取用会导致时间死区TDZ
  • 函式有两种定义方式,仅会提升函式陈述式
    • 函式表达式
    • 函式陈述式
  • 函式陈述式的提升优先於变数的提升

参考文献

https://blog.techbridge.cc/2018/11/10/javascript-hoisting/
https://medium.com/digital-dance/javascript执行环境-execution-context-简介-672185ed6bf4
https://blog.alexdevero.com/temporal-dead-zone-in-javascript/?ref=webdesignernews.com#declaration-and-initialization-differences-between-var-let-and-const
https://wcc723.github.io/javascript/2017/12/16/javascript-hoisting/
https://ithelp.ithome.com.tw/articles/10191549


<<:  TailwindCSS 从零开始 - 价目表卡片实战 - 进阶卡片样式

>>:  Keras的权重产生以及其介绍

【HTML】【CSS】关於空白压缩

【前言】 本系列为个人前端学习之路的学习笔记,在过往的学习过程中累积了很多笔记,如今想藉着IT邦帮忙...

Android Curv Gradient 曲线渐层2-优化篇

前言 延续前篇Android Curv Gradient 曲线渐层 过了一个月...终於改好啦!!!...

Day24-操作DOM

前言 在React中通常我们并不会直接操作到DOM元素。 但有些情况反而需要操作DOM元素,来使使用...

Day 12: 验收测试、测试策略 (待改进中... )

「验收测试的目的是沟通、澄清及精确化。从专业开发人员的眼光来看,与业务方、测试方协同工作,确保大家...

参与"在MCU 上全面建构AI能力" 9/10 心得

今晚参与了"MakerPro社群媒体平台"举办的 在MCU 上全面建构AI能力 ...