前言
在经过了一轮面试挫败以后,我发现了一个问题:
通常大公司对你的底层基本功非常感兴趣,他们根本不在乎你能够使用多少个前端框架。因为真正的好公司都是自己开发框架的。
今天我们要像discovery节目一样深入前端开发底层的知识,揭露js es5中那些常见的疑难杂症以及解决办法。让你搞清楚为什么这些大厂喜欢考你这些问题,然而这些问题背后又隐藏了什么历史巨坑,让龙哥给你层层揭开真相!
历史谜案!变量提升
不知道你又没有发现,在js中var是一个非常有意思的关键字。
有意思到,它根本都不按照编程语言的逻辑去执行—从上到下执行。什么意思,我TM还没有声明这个变量的时候,我居然就可以用了。
console.log(name); //dangyunlong
var name = "dangyunlong"
这是非常残酷的一个事实。说明es5中通过var来声明变量,这个变量无论在当前作用域的什么位置,它都会被提到作用域的最顶部。
console.log(name);//报错
let name = 1;
console.log(name);//1
当然,这个问题在es6中已经通过let关键字,解决了。
那么,仅仅就这样而已吗?当然不是!
我们再更进一步,把这个例子写的稍微复杂一点,给它增加一个作用域。
var name = 2;
(function(){
console.log(name);//2
})()
首先我们普及两个知识点:
1.js中没有块作用域,只有全局和函数作用域。
2.自执行匿名函数,也叫做立即执行函数,无论你写在页面的任何位置,它都会先于所有代码执行。
ok,然后我们再回来看这个例子。自执行匿名函数先于代码执行,可是,var的name还是存在。这说明var的权限是更高,这里打印出来的是全局的name。
懵逼的开始!
看完了上面的例子,咱们再来看这个:
(function(){
console.log(num);//undefined
var num = 1;
})()
这里你感觉num应该打印出来1吗,那你就错了,这里打印出来undefined。
但是我们上面说,var出来的值,再任何位置都可以用啊?为什么在函数体中不能先使用再声明呢?
龙哥猜测,这可能跟我们在函数体内声明变量,该变量成为局部变量有关系,
(function(){
console.log(num);//undefined
var num = 1;
console.log(num);//1
})()
放到下面就可以打印出来了。
来道面试题做做
var name = "tom";
(function(){
if(typeof name==="undefined"){
name = "jack";
console.log("goodbye"+name);
}else{
console.log("hello"+name);
}
})()
这道题应该打印什么?
看过前面的例子我们就知道了,应该打印hello tom。
我们来解释一下,首先,全局里面有var的变量,全局的肯定最大,无论在哪都能访问到。所以typeof name肯定就不是undefined。于是就进入了判断的else环节,所以打印出来hello tom。
然后龙哥把这道题升级了:
var name = "dangyunlong";
(function(){
console.log(name); //第一个
var name = "lilei";
name = "gaoyuanyuan"
})()
console.log(name); //第二个
function fun(){
name = "shiliujie"
}
fun();
console.log(name); //第三个
请问这三个console.log分别打印什么。
解:
1.第一个我们跟前面一题的道理一样,打印undefined。
2.第二个打印dangyunlong。有的同学可能会问,第二个不是打印gaoyuanyuan吗?注意,如果没有它上面的var name = “lilei”它确实会打印gaoyuanyuan。但是有了var,表示当前的name已经指函数体的局部变量name了,而不是全局那个。
3.第三个打印shiliujie。道理跟第二个一样,因为这次函数体内修改的是全局变量的值,并没有声明局部变量!
历史谜案!函数声明提升!
先来补充一个小知识:
什么是函数声明和函数表达式。
//函数表达式
var fun1 = function(){
console.log("我是fun1");
}
//函数声明
function fun2(){
console.log("我是fun2");
}
来看这俩哥们。
咱们也不卖关子了,这俩在执行上面没有任何区别。是一模一样的。
那么问题就来了,难道这个也存在提升的问题吗?是的。
//函数表达式
var fun1 = function(){
console.log("我是fun1");
}
fun1(); //我是fun1
//函数声明
function fun2(){
console.log("我是fun2");
}
fun2(); //我是fun2
这样运行是没问题的,但是,请看这种写法:
fun1(); //fun1 is not a function
//函数表达式
var fun1 = function(){
console.log("我是fun1");
}
fun2(); //我是fun2
//函数声明
function fun2(){
console.log("我是fun2");
}
函数表达式直接报错,但是,函数声明可以运行。
这TM就太厉害了。我们使用var的关键字出来的函数没有被提升,但是直接function却提升了,这是怎么回事?
其实这里面有一个误区,就是除了变量会提升以外,函数也会被提升。
如果我们直接声明一个函数,他也一样会提升到顶部。但是,如果我们以变量形式声明一个函数表达式,它其实走的是变量提升:
fun1(); //fun1 is not a function
//函数表达式
var fun1 = function(){
console.log("我是fun1");
}
//执行顺序
var fun1;
fun1();
fun1 = function(){
console.log("我是fun1");
}
这样的话,会报fun1不是function,因为变量提上去了以后,变量并没有被赋值。然后我们又执行了fun1();
所以这里就直接报错了。fun1这时候还不是函数,所以肯定是报这个错误了。
最后总结
所以龙哥帮大家总结除了以下结论,以后再做题的时候,一定要注意:
1.如果题中有函数,先看是否在函数内部使用了var。如果使用了,这里面的赋值就是指局部变量。局部变量没有提升的问题,如果先打印,会报undefined。
2.函数中没有var直接赋值的,修改的是全局的值。
3.全局变量无论在什么地方声明,都会提升到作用域的顶部。所以即使在开头就打印,打印出来的也是var的值。
4.函数声明有提升的问题,函数表达式走的是变量提升,所以提前运行会报这个方法不是一个function。
到此,提升问题全部浮出水面,最好的解决办法当然是通过let。这个提升的问题一定要注意,否则很有可能你无意之中修改了全局数据,导致溢出污染。
网友评论