console.log(a);
var a = 1
如上面代码中可以看出,由于JS是从上到下一行行的执行,因此很多人看到这一串代码的时候,会认为由于log之前没有定义a,因此会输出underfind,的确,这段代码最终输出的结果确实是underfind,但是并不是没有定义a,而是在输出之前没有给a赋值的原因。那会有人说,不是JS是从上到下一行行的执行的嘛?定义a在输出的后面塞?那为什么最后确实先定义了a呢?其实这中间存在着变量提升
1.变量提升
引擎会在解释JavaScript代码之前首先对齐进行编译,编译过程中的一部分工作就是找到所有的声明,并用合适的作用域将他们关联起来,这也正是词法作用域的核心内容。其实简单的来说,变量提升就是在编译的过程中,将变量的声明和函数的声明提升至对应作用域的顶端,即会优先执行。就拿下面的代码来说。其实在真正编译的过程中,代码的执行顺序并不是我们所写的这样,它会优先的将变量的定义,函数的声明给优先的执行。
//编译前
a = 1
console.log(a);
var a
//编译后
var a
a = 1
console.log(a);
那么下面这段代码也应该就很明白了,第一次log出来的就是underfind,因为在它之前就只是先声明了a,而并没有给a赋值,导致a为underfind
console.log(a);
var a = "我是函数外面的a";
var foo = function(){
console.log(a);
var a = "我是函数里面的a";
}
foo();
2.函数的变量提升
2.1函数声明式
在JS中不只是变量存在着变量提升,其实函数也存在。如下面的一段代码中可以看出,我们在函数定义前调用了函数,但是并没有报错,且在浏览器中还执行并打印了123,难道说在JS中我们可以先使用函数,后在定义函数吗?这不违背了函数中的先定义后使用???
其实并没有违背,只是其中的函数提升帮助我们做了这件事。我们这样写之所以不会报错,是因为浏览器在编译的过程中,会优先的将定义的函数或者变量在其定义域中优先执行,因此即使我们把定义的函数写到前面还是后面,在编译的过程中,都是在最前面优先编译,后再执行对应的调用过程,因此即使采取下面这种写法,也不会出现报错
test()
function test(){
console.log(123);
}
2.2函数表达式
函数表达式是声明函数的另一种写法,那它会不会存在着这种用法呢?,如下面代码所示,但是我们发现在执行函数的时候,会出现我所标记的两种报错。那又是为什么呢?的确,函数提升只是提升声明式的函数,不能提升表达式的函数。因此下面的代码其实在编译中的顺序就为如下,因为此时的foo2只是声明了,并没有给他赋值,因此foo2()就会出现相关的错误,就会出现不是一个方法。
console.log(foo2); // undefined
foo2(); // TypeError: foo2 is not a function
var foo2 = function () {
console.log("foo2");
}
编译后
var foo2
console.log(foo2); // undefined
foo2(); // TypeError: foo2 is not a function
foo2 = function () {
console.log("foo2");
}
3.先后顺序
我们可以从下面两个代码中可以看出,我们定义了两个foo2(),然后改变其调用的位置,无论是第一种还是第二种,都会出现 我是第一个foo2 因此我们可以看出函数提升的优先级会高于变量提升的优先级,否则就不会输出foo2 而是报错 foo2不是个函数。通过这个案例可以看出函数提升的优先级会高于变量提升的优先级
但是我们一旦在后面调用,无论是那一种,如果是重名了的函数,那么调用的函数执行的是最后一次定义函数的内容,就如下面顺序的话,那么调用 foo2 执行打印后的结果是我是第二个foo2
foo2();
function foo2() {
console.log("第一个foo2");
}
var foo2 = function () {
console.log("第二个foo2");
}
foo2();
function foo2() {
console.log("第一个foo2");
}
var foo2 = function () {
console.log("第二个foo2");
}
4.let和const
在ES6中更推荐我们使用let 或者const来代替var来声明函数或者变量,那么其中存在着变量提升吗?,如下,我们会惊奇的发现,会出现报错,大致的意思就是 使用let和const声明的变量我们需要在赋值后才能进行访问,即不能在赋值前进行访问。因此其实尽量采取let或者const来声明相关的变量和函数
console.log(a);
let a = 1
console.log(f22);
const f22 = function(){
console.log(123);
}
5.一个小小的案例
var foo = function (x, y) {
return x - y
}
function foo(x, y) {
return x + y
}
var sum = foo(1, 2)
console.log(sum);
编译后的结果如下 结果为-1。你是否做过了呢?????
function foo(x,y){
return x+y
}
var foo
var sum
//覆盖了之前定义的函数
foo = function(x,y){
return x-y
}
sum = foo(1,2)
console.log(sum);
总结
JS在编译的过程中首先会处理函数声明,其次会处理变量声明,如果变量名称跟已经声明的形式参数或函数相同,则变量声明不会干扰已经存在的这类属性。
最后留下一个思考题,答案可以在评论区中发表出来,一起学习学习(思考题取自于其他博客)
console.log(person)
console.log(fun)
var person = 'jack'
console.log(person)
function fun () {
console.log(person)
var person = 'tom'
console.log(person)
}
fun()
console.log(person)