var
声明了一个变量,同时可以初始化该变量。
|
|
单 var 模式
在函数的顶部使用一个单独的 var
语句是非常推荐的一种模式,它有如下一些好处:
- 在同一个位置可以查找到函数所需的所有变量
- 避免当在变量声明之前使用这个变量时产生的逻辑错误
- 提醒你不要忘记声明变量,顺便减少潜在的全局变量
- 代码量更少(输入更少且更易做代码优化)
单 var
模式看起来像这样:
|
|
单 var 衍生的误区
|
|
可能有人就会疑惑,为什么这里的 param1
是局部变量,而 param2
却是全局变量呢?
=
运算从右向左合并,上一段代码其实是这样运行的:
|
|
未声明变量
给一个非声明变量赋值会隐式创建一个全局变量(全局object的一个属性)。
这句话大家应该都很熟悉,即便你是刚入门的新手;但也有人不完全赞同这句话,他们感觉它不符合ECMAScript的基本变量的定义,它只算是全局属性;那我们就来细细探讨一下声明变量与未声明变量的区别:
- 声明变量的作用域限制在其声明位置的上下文中,而非声明变量总是全局的。
声明变量在任何代码执行前创建,而非声明变量只有在执行赋值操作的时候才会被创建。
12345console.log(a); // Uncaught ReferenceErrorconsole.log(b); // 2a = 1;var b = 2;声明变量是它所在上下文环境的不可配置属性(non-configurable property),非声明变量是可配置的(例如非声明变量可以被删除)。
1234567var a =1;b = 1;console.log(delete a); // falseconsole.log(delete b); // trueconsole.log(this.a); // 1console.log(this.b); // Uncaught ReferenceError
所以,使用未经声明的变量会带来很多潜在的代码危险,也建议大家尽量不要使用;而且严格模式下会抛出错误。
提升
先看一段代码:
|
|
很多人可能认为这段代码输出的是 undefined
;因为开始 a
被初始化为2,接着又被重新声明并被默认赋值为 undefined
。
再看下一段代码:
|
|
这段代码可能又会有人认为它要报错了,因为在执行 console.log
前 a
尚未声明。
其实刚才两段代码会分别输出 2
和 undefined
,那又是为什么呢? 在引擎解释代码之前,编译器会预先对其进行编译,编译工作的其中一块就是将所有的声明预先进行处理。 当你看到 var a = 2
时,你以为直接就是将 2
赋值给 a
,其实它会分成两个阶段进行,第一阶段就是进行 var a
编译(在当前作用域代码执行前); 第二阶段就是执行 a = 2
(等待js执行到代码所在行); 所以第一段代码真实的执行顺序是这样的:
|
|
第一阶段就是编译,第二阶段是执行;这就是 变量提升。
类似地,第二段是这样处理的:
|
|
提升规则
我们先来看看会进行提升的都有哪些,稍后再进行解释:
- 函数声明,函数声明优先级最高,忽略重复的变量和形参声明
- 形参,比变量声明优先,忽略重复的变量声明
- 变量声明
※ 提升只会在其自身执行上下文中进行。什么?不知道执行上下文?下篇文章会介绍!
形参
|
|
上面 foo
在执行时,会先进行 隐式的声明赋值 操作 var a = 2
,然后在执行函数中的代码段;怎么证明呢?我们来改一下上面的代码:
|
|
如果它没有这个隐式的声明,这里的 a
应该是全局变量(属性),函数体外就可以访问到。
因为它的这一特性,我之前一直以为形参和变量一模一样,只声明不赋值(其实默认赋值undefined),只不过执行时它的代码在函数体所有代码之上,后来发现其实不然。 形参声明的同时会接受实参的赋值,如果没有对应实参,赋值为undefined 。
|
|
如果按照我原先的理解,上面这段代码,在所有声明提升之后,执行 console.log(a);
之前会有一个 a = 2
的赋值,最后输出的就不会是 function
而是 2
。所以很明显,形参声明的同时就已经接收了实参的赋值,但优先级不高被函数声明覆盖了。
函数声明
注意我们这里写的是 函数声明 ,不包含 函数表达式 。
|
|
这一段代码可以正常执行,因为 foo
函数的声明已经被提升了。
|
|
上面这段代码可以看出来函数表达式不会让函数得到提升。而 a()
为什么会提示Uncaught TypeError呢,因为上面代码实际是这样处理的:
|
|
执行到 a()
的时候 a
还只是被默认赋值为 undefined
,并不包含对函数的引用。
还要提到的一点就是,函数 foo
只能在其自身作用域内被发现。
函数声明比变量声明优先,忽略重复的变量声明
|
|
上面第一个 console.log(a);
会输出 function a() {}
是因为函数声明优先,并且忽略了后面同名变量的声明。第二个 console.log(a);
输出 2
是因为执行代码时不会忽略初始化操作。
代码的实际处理情况是:
|
|
如果是同名函数的声明则会对其进行覆盖,所以js没有函数重载。
练习
|
|
答案
|
|