变量提升
函数声明跟普通的变量会被提升到作用域的顶部。
注意,是函数声明哦,函数表达式是不会提升函数的
console.log(foo);
var foo = function(){
console.log("哈哈哈");
} // undefined
console.log(foo);
function foo(){
console.log("哈哈哈");
} // Function foo
函数跟变量的提升规则有些许的不同。
- 当变量提升时,将变量的声明给提升,提升的同时给变量赋值为undefined。
- 当函数提升时,会将整个函数体给提升到顶部。
- 当函数碰到之前已有任何声明时,会进行覆盖操作。
- 当变量碰到之前已有变量声明时,同样会进行覆盖操作。但是假如碰到已经声明的函数声明,则不会进行覆盖操作。即函数的优先值>变量
var f = 5;
function f() {
console.log("Hello from function f");
}
console.log(f);//Function f
这时候我们就能解出下面这道经典的面试题了
var foo = function () {
console.log('foo1');
}
foo(); // foo1
var foo = function () {
console.log('foo2');
}
foo(); // foo2
我们来详细说一说代码的执行过程:
- 首先创建全局执行上下文
- 碰到foo变量声明将foo进行变量提升,值为undfined。
- 接着又碰到了foo的变量声明,将上面的声明给覆盖。
- 编译完成,开始执行代码
- 左查找foo并给foo进行赋值操作。
- 执行foo
- 给foo进行赋值操作,覆盖之前值。
- 执行foo
function foo() {
console.log('foo1');
}
foo(); // foo2
function foo() {
console.log('foo2');
}
foo(); // foo2
执行过程:
- 首先创建全局执行上下文
- 碰到函数声明,将foo整个函数进行变量提升。同时将函数声明放进全局执行上下文的词法环境中
- 接着给第二个foo进行声明,将上面的函数声明给覆盖。
- 编译完成,开始执行代码
- 执行foo
- 执行foo
词法作用域
因为 JavaScript 采用的是词法作用域,函数的作用域在函数定义的时候就决定了。
而与词法作用域相对的是动态作用域,函数的作用域是在函数调用的时候才决定的。
让我们认真看个例子就能明白之间的区别:
//词法作用域
var a = '你好'
function foo(){
var a = 1;
bar();
}
function bar(){
console.log(a);
}//你好
//动态作用域
var a = '你好'
function foo(){
var a = 1;
bar();
}
function bar(){
console.log(a);
}// 1
词法作用域这里打印的是你好而不是1,因为bar的作用域在定义时就决定了,当bar内查找不到变量时会查找全局变量作用域。
作用域查找会在找到第一个匹配的标识符时停止。在多层的嵌套作用域中可以定义同名的 标识符,这叫作“遮蔽效应”(内部的标识符“遮蔽”了外部的标识符)。抛开遮蔽效应, 作用域查找始终从运行时所处的最内部作用域开始,逐级向外或者说向上进行,直到遇见 第一个匹配的标识符为止。
全局变量会自动成为全局对象(比如浏览器中的window对象)的属性,因此 可以不直接通过全局对象的词法名称,而是间接地通过对全局对象属性的引 用来对其进行访问。** window.a** 通过这种技术可以访问那些被同名变量所遮蔽的全局变量。但非全局的变量 如果被遮蔽了,无论如何都无法被访问到。