推荐阅读地址
掘金 github
github 求 start😄😄
前言: 大家好,我是林一一,这是一篇关于变量提升的面试题及其概念,每一道题都基本使用画图的方式讲解来保证大家能理解的更深。让我们来开始阅读吧。
思维导图
变量提升机制
一.什么是变量提升?
- 变量提升示例
/* 你应该见过下面的类似代码,那你知道这是为什么*/
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 */
-
问下面的结果和上面的有何不同?
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
型
- 再来一道面试题
console.log(fn); var fn = 2019; console.log(fn); function fn(){} /* 输出 fn(){} 2019 /
思路:这也是重名下的一道面试题,在变量提升阶段
fn
由变量值声明为undefined
被修改定义为fn函数本身 fn(){}
,所以第一个输出就是fn(){}
,第二个输出fn
由被赋值成fn=12
输出12
参考
结束
感谢阅读,我是林一一,下次见