JavaScript 的提升
JavaScript 的提升
最近在回顾 JavaScript 的基础。
于是碰到了 提升 这个概念
关于,提升,一直有印象的,就是 var
这个变量提升。
提示:
每个作用域都会有“提升”操作,提升操作会将变量提升到作用域的顶部。
var
的提升
使用 var
声明的变量,会在所有的作用域中提升。
基本
1
2var a = 'a001'
console.log(a) // a001先打印,接着声明,赋值
1
2console.log(b) // undefined. ReferenceError: b is not defined
var b = 'b001'在编译器中等价于
1
2
3var b
console.log(b) // undefined. ReferenceError: b is not defined
b = 'b001'重复声明并赋值
1
2
3var c = 'c001'
var c = 'c002'
console.log(c) // c002但是你假如对使用
let
/const
声明的变量进行了重复声明,那么就会报错。1
2
3let c1 = 'c1001'
let c1 = 'c1002' // SyntaxError: Identifier 'c1' has already been declared
console.log(c1)1
2
3const c2 = 'c2001'
const c2 = 'c2002' // SyntaxError: Identifier 'c2' has already been declared
console.log(c2)
先“赋值”,再声明
1
2
3d = 'd001'
var d
console.log(d) // d001仅“赋值”,就会变成 window 上的变量
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15e = 'e001'
console.log(e) // e001。若后续同一作用域下代码无 `var e`,那这 `e` 便是 window.e 了。
// or
function f() {
e = 'e002'
}
f()
console.log(e) // e002。 跟上面的 `e`,是相同的情况
// but
if (false) {
e = 'e003'
}
console.log(e) // undefined。 跟上面的两个 `e`,是不同的情况。因为不存在变量提升,代码也没有执行 if 块里的在函数中
1
2
3
4
5
6function g() {
var h = 'h001'
console.log('in function', h) // h001
}
g()
console.log('in window', h) // undefined。因为函数内部的变量,不会提升到作用域的顶部,因为该变量会在调用函数之后随即被销毁掉。
函数的提升
或许是好久没看概念,这两天看书发现:
函数里也是有“提升”的。
但仅限于函数声明会有提升,而函数表达式是不会提升的。
函数声明会在任何代码执行之前先被读取并添加到执行上下文。这个过程叫作函数声明提升( function declaration hoisting)。在执行代码时, JavaScript 引擎会先执行一遍扫描,把发现的函数声明提升到源代码树的顶部。因此即使函数定义出现在调用它们的代码之后,引擎也会把函数声明提升到顶部。
– 《JavaScript 高级程序设计(第 4 版)》P297
具体表现为
1 | // 没问题 |
但是把上述的函数声明改为等价的函数表达式,那么执行的时候就会出错。
1 | console.log(sum(10, 10)) // Uncaught ReferenceError: sum is not defined |
哪怕是你换成用 var
声明的函数,也会出错。
1 | console.log(sum(10, 10)) // Uncaught ReferenceError: sum is not defined |
除了“函数什么时候真正有定义”这个区别之外,这两种写法是等价的。
提升的优先级
重复的声明,将被忽略掉
先看点简单的例子
1 | foo() |
上述代码执行的结果是:1
在编译器中,被理解为:
1 | function foo() { |
注:
foo()
这里的foo
只是一个函数名,不是函数声明。函数声明是包含函数名的。在此也可以理解为是个变量,函数变量。跟之后的var foo
中的foo
是相同的,同名变量。所以,后面的var foo
将被忽略掉。因为是重复的声明。
函数声明会被提升到变量声明之前
再来看看这个例子
1 | console.log('a -1', a) // a -1 f a() |
重复的声明,将被忽略掉
变量的值,只会在重新赋值后才会被改写。而重复的声明,会被忽略,并不会影响什么。
援引自 tc39/ecma262 中的阐述:14.3.2 Variable Statement
A variable defined by a VariableDeclaration with an Initializer is assigned the value of its Initializer’s AssignmentExpression when the VariableDeclaration is executed, not when the variable is created.
变量的第一次声明,会被默认赋值为
undefined
。假如接着赋值,那么就会覆盖掉默认值。var,let 只声明,不赋值,默认值为
undefined
。- 除非是显式的赋值,否则变量的值,在非第一次执行变量声明时,是不会被赋值的。是无效的,会被编译器忽略掉。
const 必须声明并赋值(初始化),否则报错。
只声明,不赋值。是无效的声明。
只声明,或再打印它: const 声明中缺少初始化器。
1
2
3
4const b // SyntaxError: Missing initializer in const declaration
// or
const b
console.log(b) // SyntaxError: Missing initializer in const declaration当使用这个变量去赋值给其他变量的时候,会提示未定义
1
2const b
const c = b // ReferenceError: b is not defined
在 JavaScript 引擎里,
1 | var a = 2 |
被看作两个单独的语句
1 | var a // 编译阶段的任务 |
这意味着无论作用域中的声明出现在什么地方, 都将在代码本身被执行前首先进行处理。可以将这个过程形象地想象成所有的声明((var)变量和函数(声明)) 会被“移动” 到各自作用域的最顶端, 这个过程被称为提升。
声明本身会被提升, 而包括函数表达式的赋值在内的赋值操作并不会提升。
要注意避免重复声明, 特别是当普通的 var 声明和函数声明混合在一起的时候, 否则会引起很多危险的问题!
《你不知道的 JavaScript(上卷)》 4.4 小结