D5 - 你不知道 Combo : 前菜 Hoisting

前言

cookie(); 

function cookie(){
  console.log('I love Oreo')
}

上面这段程序码可以成功印出 I love Oreo ,寻找答案的时候会发现,哦,原来这叫 Hoisting!

但,你知道在 ECMA 中根本查不到 Hoisting 吗
哪尼,那网路的 Hoisting 文难道都是误人子弟?!

不不不,应该说 Hoisiting 是一个比喻说法,将背後复杂的运作原理简化解释,在宣告前印出结果这个现象,像是 code 被 Hoisting 提升了一样,说法没有错只是不完整而已。

要从头开始解释 Hoisting 现象的原理,必须从 JavaScript 的编译 开始,接着是 ScopeExecution Context 等,这些才是真正的原貌,想要深入理解 JavaScript 的话必须从这里着手。
各位美食家们,一起来客这道 你不知道 Combo 吧!菜色应该有 2-3 道,饱足感满满(绝对不是为了凑篇幅 XD)

今天先上前菜,复习一下 JavaScript 中 那些 Hoisting 现象 及 错误讯息差异

那些称为 hoisting 的行为

W3School 是这麽解释 hoisting 的

Hoisting is JavaScript's default behavior of moving all declarations to the top of the current scope (to the top of the current script or the current function)

Hoisting 为 JavaScript 的预设行为,将所有宣告提升到所在范围的顶端( 这里的范围可能是 全域的顶端 或是 function 的顶端)。

变数

console.log(a); // undefined
console.log(b); // ReferenceError: Cannot access 'b' before initialization
console.log(c); // ReferenceError: Cannot access 'c' before initialization
console.log(d); // ReferenceError: d is not defined

var a = 4;
let b = 5;
const c = 6;
d = 7;

首先,先来看看上面这段程序码,在开始时要求印出变数 a、b、c、d,再使用 varletconst 分别宣告 a,b,c 并个别赋值,最後 d 不进行宣告直接赋值。

结果显示:

  • 使用 var 宣告的 a 印出 undefined
  • letconst 宣告的 b 、 c 抛出错误讯息 can't access before initialization
  • 没有宣告的 d 抛出错误讯息 d is not defined

有趣了,出现三种不同结果,看来宣告是关键 (背後一道柯南闪电)
这些现象称为 hoisting,规则是

  1. 变数的宣告会被 hoisting,但须区分是 var 还是 let const 宣告
  2. 变数的 初始化 Initialization 不会被 hoisting

先来个名词解释 初始化 Initialization 是什麽?

将一个什麽都还不是的变数赋予值,像是 b=5 这个动作就是所谓的初始化 Initilization,简单来说,就是对变数第一次赋值,从无到有的过程称为「初始化」。

再更白话的解释这两点,可以想像 JavaScript 在正式执行 code 以前,会先全部扫描一次,当看到使用 varletconst 关键字宣告变数时,会先将这些变数名称记起来,但并不会记下变数的值,所以 console.log(a) 时,因为扫描过一次知道有 a 这个变数,但还不知道它的值,所以印出 undefined。

而 ES6 之後因为要求先宣告重要性,所以经由letconst 宣告的变数会显示错误讯息 Cannot access 'b' before initialization 无法在变数初始化以前取得值,提醒「我知道你想要印出变数,但他们还没有被记录到值,顺序换一下吧!」

而最後的 d,因为根本没被宣告,一开始扫描时不会记下这个变数,所以印出错误讯息 - 变数 not defined 「嘿,请问你 d 是哪位?」

Function

接下来讲讲 function 的 Hoisting 行为,首先要先能区分 Function DeclarationFunction Expression
(英文表示最精准,仿间各种中文翻译:陈述式 表达式 宣告式 运算式???到底叫啥我头都痛了)

名词解释

直接以 function 开头并为它命名,基本上都称为 Function Declaration 或称为 Function Statement

function oreo(){
  return 'I love Oreo 3000'
}

而 Function Expression 会将 function 赋予到一个变数内,通常以匿名函式 或 箭头函式形式出现

let oreo = function (){
  return 'I love Oreo 3000'
}

//或箭头函式型态
let oreo = () => 'I love Oreo 3000'

function 的 Hoisting

好的,那接下来来看看 function 的 Hoisting 效果

oreo(); // 'Oreo is my forever love'
twix(); // TypeError: twix is not a function
ritz(); // ReferenceError: Cannot access 'ritz' before initialization

function oreo() {
  console.log('Oreo is my forever love') 
}

var twix = () => {
  console.log('Twix is sooo good!')
}

let ritz = function () {
  return 'Ritz is much better'
}

以上定义了三个 function,分别是 Function Declaration 下的 oreo 、Function Expression 下的 twix ( var 宣告)及 ritz ( let 宣告)

执行後出现三个不同结果:

  • oreo 成功印出字串 'Oreo is my forever love'
  • var 宣告的 twix 跑出错误讯息 TypeError: twix is not a function
  • let 宣告的 ritz 跑出错误讯息 ReferenceError: Cannot access 'ritz' before initialization

总结 function 的 Hoisting 规则:

  1. 只有以 Function Declaration 创建的 function 才会被 Hoisting
  2. Function Expression

只要记得,只有 Function Declaration 有 Hoisting 效果,依照当初的设计考量,这是为了提升 function 互相呼叫的灵活度

在此篇 Two words about “hoisting” 的文章作者,在 Twitter tag 了 JavaScript 之父 Brendan Eich 关於 Hoisting 的设计由来, Brendan Eich 也回覆了 Function Declaration 的 Hoisting 是为了 更方便的递回效果 及 解决程序码的顺序问题

Brendan Eich Twitter 回覆截图

这个设计在 JavaScript 内是非常方便的,它避免了在一团乱糟糟的 code 下寻找呼叫的入口,可以在最开始时先呼叫 function,程序码看起来更乾净,也不管 function 写在哪,呼叫就取得到。

而至於为何 Function Expression 无法呢?
他的型态是将 function 存入变数,同先前提到的变数 Hoisting 行为,只有名称会被记住,值并不会被 Hoisting,
所以对一个 var 宣告的变数使用 () 小括号呼叫,就是对 undefined 呼叫,是根本上的语法错误,所以显示 TypeError

let 宣告的 ritz 因为尚未取得值,错误讯息一样跑出 Cannot access 'rits' before initialization.

结语

以上就是 Hoisting 在 JavaScript 中的行为,明天来看看引擎端会怎麽解读这些 code

Reference:

大量参考 [你所不知道的JS] by Kyle Simpson
Twitter 截图来源JavaScript: 变量提升和函数提升
Huli大师的超专业解释文 我知道你懂 hoisting,可是你了解到多深?
Two words about "hoisting"
MDN
W3School


<<:  跨网域传值的神队友——window.postMessage

>>:  Day07:部门与工程团队间协作的技巧(上)

[Day14] Storybook - Colors & Typography

Storybook 除了可以为元件攥写 Story 以外,也可以攥写纯内容的说明文件,不过纯内容的说...

从零开始学3D游戏设计:入门程序实作 Part.4 冷却时间确认(debouncing)

这是 Roblox 从零开始系列,入门章节的第十个单元,在这个单元你将学会如何去为陷阱加上执行确认,...

selenium爬虫:使用xpath

from selenium import webdriver import openpyxl imp...

量化交易30天 Day28 - 投资组合概念(八) CAPM实际应用

量化交易30天 本系列文章是纪录一位量化交易新手的学习过程,除了基础的Python语法不说明,其他...

Day28 我还是视觉动物

Integration with pivot table and chart 承续昨天所列的第一点...