思维导图
推荐阅读地址 掘金
变量提升机制
一.什么是变量提升?
- 变量提升示例
/* 你应该见过下面的类似代码,那你知道这是为什么*/
console.log(a) // undefined
var a = 10
定义:变量提升是当栈内存作用域形成时,JS代码执行前,浏览器会将带有var, function
关键字的变量提前进行声明 declare(值默认就是 undefined),定义 defined(就是赋值操作),这种预先处理的机制就叫做变量提升机制也叫预定义。
在变量提升阶段:带var
的只声明还没有被定义,带function
的已经声明和定义。所以在代码执行前有带var
的就提前声明,比如这里的a
就赋值成undefined
,在代码执行过程中遇到创建函数的代码
浏览器会直接跳过。不考虑变量提升阶段的 js 运行机制相关参考 JS 运行机制基础版
讲解示例
var a =12
var b = a
b = 1
function sum(x, y) {
var total = x + y
return total
}
sum(1, 2)
PS: 函数在调用时创建执行上下文对象还有其他关键的步骤作用域创建,this指向等这些内容放在后面文章讲,这样的机制有点类似变量提升。下面的函数创建过程都会被按作类似于变量提升来理解。
变量提升只发生在当前作用域。比如:在页面开始加载时,只有全局作用域发生变量提升,这时候的函数中存储的都是代码字符串。
二. 带 var 和不带 var 的区别
- 全局作用域中不带
var
声明变量虽然也可以但是建议带上var
声明变量,不带var
的相当于给window对象设置一个属性罢了。 - 私有作用域(函数作用域),带
var
的是私有变量。不带var
的是会向上级作用域查找,如果上级作用域也没有那么就一直找到 window 为止,这个查找过程叫作用域链
。 - 全局作用域中使用
var
申明的变量会映射到 window 下成为属性。
a = 12 // == window.a
console.log(a) // 12
console.log(window.a) // 12
var a = b =12 // 这里的 b 也是不带 var 的。
/* 相当于*/
var a = 12;
b = 12
思考题
- 问下面分别输出什么?
// 1
console.log(a, b)
var a =12, b ='林一一'
function foo(){
// 2
console.log(a, b)
// 3
var a = b =13
console.log(a, b)
}
foo()
console.log(a, b)
/* 输出:
undefined undefined
undefined "林一一"
13 13
12 13
*/
- 2.. 问下面的结果和上面的有何不同?
console.log(a, b)
var a =12, b = '林一一'
function foo(){
console.log(a, b)
// var a =b =13
console.log(a, b)
}
foo()
// 4
console.log(a, b)
/* 输出:
undefined undefined
12 "林一一"
12 "林一一"
12 "林一一
*/
解答
上面的思考题不知道你都对了没,下面让我来解答,详情看图思路:1处的 a, b 其实就是window下面的属性为 undefined。在函数内部由于变量提升机制
a
带var
一开始就是 undefined,b
不带var
将向上级作用域查找,找到全局作用域下的林一一
所以2处打印出来的就是undefined "林一一"
。随后a =13,window.b =13
,即原来b='林一一'
变成了b=13
,打印出13, 13
,最后第4处打印处12, 13
。所以结合流程图,很明显知道答案
- 问题3,再来看一道,问下面答案是什么?
a = 0
function foo(){
var a =12;
b = '林一一'
console.log('b' in window)
console.log(a, b)
}
foo()
console.log(b)
console.log(a)
/* 输出
true
12 "林一一"
林一一
0
/
思路:这是比较简单的一道题,需要注意的是函数内的 b 没有带var
,b 会一直向上查找到 window 下,发现 window 下也没有就直接给 window 设置了一个属性window.b = '林一一'
,同理全局下的a
也一样。
- 问题4,问下面答案是什么?和问题3有什么区别
function foo(){
console.log(a)
a =12;
b = '林一一'
console.log('b' in window)
console.log(a, b)
}
foo()
/* 输出
Uncaught ReferenceError: a is not defined
/
思路:问题4和问题3的主要区别在于第一个console.log(a)
处,因为a
不在函数作用域内,就会向上找window
下的作用域,发现也没有就会直接抛出引用错误 ReferenceError
- 经典面试题
fn();
console.log(v1);
console.log(v2);
console.log(v3);
function fn(){
var v1 = v2 = v3 = 2019;
console.log(v1);
console.log(v2);
console.log(v3);
}
/*输出
2019
2019
2019
Uncaught ReferenceError: v1 is not defined
/
思路:和问题4类似,不做分析
三. 等号左边下的变量提升
- 函数左边的变量提升
print()
function print(){
console.log('林一一')
}
print()
很显然上面都输出了 林一一
,因为带 function 的已经进行了变量提升
- 匿名函数下的带
=
的变量提升
print()
var print = function() {
console.log('林一一')
}
print()
/*输出
Uncaught TypeError: print is not a function
/
思路:同样由于变量提升机制带var
的 print 是一开始值是undefined
,所以 print() 这时还不是一个函数,所以报出 类型错误TypeError
四. 条件判断下的变量提升
- if else 条件判断下的变量提升
console.log(a)
if(false){
var a = '林一一'
}
console.log(a)
/* 输出
undefined
undefinedN
/
在当前作用域中不管条件是否成立都会进行变量提升
if else 条件判断下函数变量提升的坑
新版浏览器中,在条件判断块级作用域之外使用条件内函数
console.log(print()) if(true){ function print() { console.log('林一一') } } console.log(print()) /* 输出 undefined 林一一 undefined /
- 新版浏览器中,在条件判断块级作用域中使用条件内函数
if(true) { console.log(print()) // ??? function print() { console.log('林一一') } } console.log(print()) /* 输出 林一一 undefined 林一一 /
思路:{}
大括号属于块级作用域,在if else
中带function
的函数同样也会先被声明和定义所以条件判断中的print()
可以直接使用
思考题
- 题目1,if判断语句中的变量提升
if(!("value" in window)){
var value = 2019;
}
console.log(value);
console.log('value' in window);
/* 输出
undefined
true
/
思路:和上面所说的一样,不管条件是否成立带var
的变量提升,当前在全局作用域value
就是window
的属性,所以结果显而易见输出undefined 和 true
五. 重名问题下的变量提升
- 函数名和
var
声明的变量重名
var fn = 12
function fn() {
console.log('林一一')
}
console.log(window.fn)
fn()
/* 输出
* 12
* Uncaught TypeError: fn is not a function
/
思路:带var
声明的和带function
声明的其实都是在 window 下的属性,也就是重名了,根据变量提升的机制,JS 代码自上而下执行时此时的fn
还只是fn = 12
,所以fn() == 12()
又是一个类型错误 TypeError
- 变量重名在变量提升阶段会重新定义也就是重新赋值
console.log('1',fn())
function fn(){
console.log(1)
}
console.log('2',fn())
function fn(){
console.log(2)
}
console.log('3',fn())
var fn = '林一一'
console.log('4',fn())
function fn(){
console.log(3)
}
/* 输出
* 3
* 1 undefined
* 3
* 2 undefined
* 3
* 3 undefined
* Uncaught TypeError: fn is not a function
/
思路:同样由于变量提升机制,fn
会被多次重新赋值最后赋值的地址值(假设为oxfffee)为最后一个函数,所以调用fn
都只是在调用最后一个函数输出都是3
, 代码执行到var fn = '林一一'
,所以fn() 其实 == 林一一()
导致类型错误 TypeError
思考题
- 腾讯的一道变量提升的面试题
var a=2;
function a() {
console.log(3);
}
console.log(typeof a);
/* 输出
* number
/
思路:这是一道比较简单的变量提升题,JS 代码自上而下执行时,a
被赋值成 2,输出就是number
型
- 2.. 再来一道面试题
console.log(fn);
var fn = 2019;
console.log(fn);
function fn(){}
/* 输出
fn(){}
2019
/
思路:这也是重名下的一道面试题,在变量提升阶段fn
由变量值声明为undefined
被修改定义为fn函数本身 fn(){}
,所以第一个输出就是fn(){}
,第二个输出fn
由被赋值成fn=12
输出12
参考
JavaScript面试题分析之变量提升和执行上下文
推荐
推荐阅读地址 掘金
感谢阅读,我是林一一,下次见
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。