【Day06】提升(Hoisting)

我们在进到主题前先来看一段程序码,随後在开发人员工具中观察执行过程

function doSomething(){
    var mom = '老妈';
}

doSomething();

首先是函式执行前,这没特别的问题,继续往下看

执行 doSomething() 後,

我们可以看到在 doSomething 作用域中 mom 值为 undefined

再往下一部时,能看到 mom 被赋予 '老妈' 这个值

我们再来看另一个范例

var ming = '小明';
console.log(ming);

此时会回传 '小明'

如果将 console.log() 放在前面

console.log(ming);
var ming = '小明';

这时就会回传 undefined

当我们将宣告的变数 ming 拿掉时,

console.log(ming);

此时会出现 ReferenceError: ming is not defined

为何会有这样的结果呢?

那是因为程序码在执行时会分为两个阶段

首先会先宣告变数,之後才将值赋予到变数中

如下所示

var ming;

ming = '小明';

console.log(ming);

此时结果也是 '小明'

而为什麽现有这种情况产生,就该进到我们的主题了

提升(Hoisting)

在前几天的文章中有提到执行环境,

而在建立执行环境时有两个阶段

分别为创造环境执行这两个阶段

在创造环境时会将所有变数提出来,在记忆体上建立一个空间,

到了执行阶段时,才将时赋予到变数中,

在创造环境把记忆体空间准备好的流程,称为提升(Hoisting)

用以下程序码当范例

var a = 'weiwei';

我们先假设记忆体是一对的,

左边放 key,右边放值

在创造环境中,

记忆体左边会代入 a 这个变数,

而在创造环境中,还不会把值赋予到变数中,

因此记忆体右边会是 undefined

到了执行阶段时,

才将值代入变数中,

因此记忆体右边会是 'weiwei'

但如果使用函式陈述式宣告一个变数时,

在创造环境时就会将整个函式载入进去,

和变数宣告有些许不同

以图表示

在创造环境时,

变数 a 的值还没被载入,但函式的值已经被载入进去了,

而变数 a 要到执行阶段才会将值代入

我们来看范例

范例一

我们来看文章开头范例

var ming = '小明';

console.log(ming);

程序码在运行时,会解析为下方的结果

var ming;  // 创造阶段

ming = '小明';  // 执行

console.log(ming);

宣告变数就称为创造阶段,

而开始赋予值之後称为执行阶段

范例二

函式陈述式范例

function callName() {
    console.log('呼叫 weiwei')
}

callName();

此时会回传 '呼叫 weiwei'

当我们把 callName() 放在函式前面时

callName();

function callName() {
    console.log('呼叫 weiwei')
}

此时的结果也是 '呼叫 weiwei'

为何会这样呢?

我们来拆解这段程序码

// 创造阶段
function callName() {
    console.log('呼叫 weiwei')
}

// 执行
callName();

因为在创造阶段时,

其记忆体空间就已经包含完整内容,

所以就算在宣告函式之前执行函式,

也会获得相同的结果

范例三

函式表达式范例

var callName = function() {
    console.log('呼叫 weiwei')
}

callName();

此时函式结果为 '呼叫 weiwei'

但当我们把 callName() 移到宣告函式前面时

callName();

var callName = function() {
    console.log('呼叫 weiwei')
}

此时会出现 callName is not a function

这时我们使用 console.log()callName 的值是什麽

console.log(callName);

var callName = function() {
    console.log('呼叫 weiwei')
}

这时会看到 undefined

我们来解析这段程序码

// 创造阶段
var callName;

// 执行
callName = function() {
    console.log('呼叫 weiwei')
}

在创造阶段会先准备 callName 这个变数的记忆体空间,

但还没被赋予值,

到了执行阶段时,才将函式赋予到 callName 这个变数上,

因此如果要运行函式表达式中的函式时,

必须在赋予完值後,才能运行该函式

范例四

function callName() {
    console.log('呼叫 weiwei 1');
}

var callName = function() {
    console.log('呼叫 weiwei 2');
}

callName();

该程序码结果为 '呼叫 weiwei 2'

当我们将函式的表达式与陈述式互换位置时,

var callName = function() {
    console.log('呼叫 weiwei 2');
}

function callName() {
    console.log('呼叫 weiwei 1');
}

callName();

此时结果也是 '呼叫 weiwei 2'

为何会这样呢?

那是因为在创造阶段时,函式是优先的

我们来解析这段程序码

// 函式优先
// 创造阶段
function callName() {
    console.log('呼叫 weiwei 1');
}
var callName
// 执行
callName = function() {
    console.log('呼叫 weiwei 2');
}
callName();

在创造阶段因为函式优先所以函式放前面,变数放後面

而在执行阶段时,变数被另一个函式盖掉,因此会显示 '呼叫 weiwei 2'

如果将 callName() 往前移

// 函式优先
// 创造阶段
function callName() {
    console.log('呼叫 weiwei 1');
}
var callName
// 执行
callName();
callName = function() {
    console.log('呼叫 weiwei 2');
}

这时结果会呈现 '呼叫 weiwei 1'

范例五

callName();
function callName() {
    console.log(ming);
}
var ming = '小明';

该范例结果为 undefined

为何不会出现错误呢?

我们来解析这端程序码

// 创造阶段
function callName() {
    console.log(ming);
}
var ming;
// 执行
callName();
ming = '小明';

在创造阶段 callName() 中的 ming 会向外查找全域中的值,

而全域中的 ming 此时为 undefined

因此到了执行阶段执行 callName() 时会回传 undefined

范例六

function callName() {
    console.log('小明')
}

callName();  // 第一次执行

function callName() {
    console.log('weiwei')
}

callName();  // 第二次执行

此时两个结果都是 'weiwei'

为何会这样呢?

我们来看解析

// 创造阶段
function callName() {
    console.log('小明')
}
function callName() {
    console.log('weiwei')
}
// 执行
callName();  // 第一次执行
callName();  // 第二次执行

因为後面的结果会覆盖掉前面的,

因此显示的结果都是 'weiwei'

范例七

whoName();

function whoName() {
    if (name) {
        name = 'weiwei';
    }
}

var name = '小明';
console.log(name);

此时结果为 '小明'

来看解析

// 创造阶段
function whoName() {
    if (name) {
        name = 'weiwei';
    }
}
var name;
// 执行
whoName();
name = '小明';
console.log(name);

在创造阶段中 name 的值为 nudefined

在执行阶段中,

whoName() 不管 name 值是什麽,

都会被後面的 name = '小明' 给覆盖掉,

因此结果会显示 '小明'

以上为今天的内容,感谢观看


<<:  如何用 PHP 检查字串是否为合法的日期?

>>:  [CSS] Flex/Grid Layout Modules, part 1

DAY12 Kotlin基础 函式

欸!?这个不是在 hello world 的时候讲过了ㄇ?! 对。 其实函式还是有其他东西可以讲解的...

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

最近公司的EIP专案有个需求。主管在签核一览表里会点击要签核的单子另开一个视窗,需求单位希望主管签完...

Leetcode: 100. Same Tree

有两个二元树,怎麽检查两个树是不是一样的。 思路 一路Traversal下去 程序码 class S...

{CMoney战斗营} 的第十二周 # 认识SQL

开始进入分组授课的第二周, 这周的课程包括共同领域的资料结构Stack & Queue及後端...

Day 12 - Semigroup I

Definition of a Semigroup 一个集合(Set)或称型别(Type) 有 co...