hoisting 变量提升:Javascript 引擎在执行代码的时候会把所有的函数及变量的声明提升到当前作用域的最顶部,实际执行的代码是不包含任何函数定义和变量声明的。
变量赋值会执行两个操作,首先编译器会在当前作用域中声明一个变量,然后运行时引擎会在作用域中查找该变量,如果能找到会对它赋值。
回调函数不会提前声明和定义,自执行函数不会提前声明和定义。
JS解释器在执行一个代码块之前,首先会扫描这一段代码以确定和定义在这个代码块中使用的变量,然后移除代码中所有的变量和函数声明后再执行代码。就是说无论变量声明在哪里,都相当于在最前面声明了这个变量。
函数的声明优先于变量的声明。
var
关键字定义的变量可以重复声明,形参可以重名,函数可以重复声明。因为JavaScript中没有重载。- 重名时初始值的优先级顺序是:函数声明>形参>arguments>变量声明 。
- 在代码中最后一次定义的一个函数;
- 最右边的一个形参;
- 特殊变量arguments;
- 其他声明的变量,都为undefined。
- 重名时初始值的优先级顺序是:函数声明>形参>arguments>变量声明 。
console.log(a); // undefined
var a=1;
console.log(a); // 1
function fn()
{
console.log(a); // undefined
var a = 2;
var a;
console.log(a); // 2
}
fn();
由于js的变量提升,实际上上例中的代码是按照以下顺序来执行的:
var a; // 变量提升,全局作用域范围内,此时只是声明,并没有赋值
console.log(a); // undefined
a = 1; // 赋值
console.log(a); // 1
function fn()
{
var a;
// var a; // 对于变量这个声明其实是被忽略的
//=================以上是被提升的部分===============
console.log(a); // undefined
a = 2;
console.log(a); // 2
}
fn();
- 在函数中,第一次输出 a 并没有使用的全局变量 a,而是本作用域中后面声明的a,但是由于没有给其赋值,所以是其值为undefined。
- 第二次输出 a 并没有输出 undefined,因为通过变量声明提升,无论是第一次声明还是第二次声明这两次声明都在整个作用域的最前面,重复的声明则忽略,初始值undefined。所以说第二次声明并不会导致
a = undefined
。
如果使用let
和const
重复声明同一个变量是会报错的。
函数声明也会被提升,初始值为最后一次声明的那个函数。
js中创建函数有两种方式:函数声明式和函数字面量式。只有函数声明才存在函数提升。
console.log(f1); // function f1() {}
console.log(f2); // undefined
function f1() {}
var f2 = function() {}
由于js的变量提升,实际上上例中的代码是按照以下顺序来执行的:
function f1() {} // 函数提升,整个代码块提升到文件的最开头 console.log(f1);
console.log(f2);
var f2 = function() {} //并不是函数声明,仅仅一个生成一个匿名函数,赋值给f2
若重复声明函数
function f() { return "Global";}
function fn()
{
console.log(f()); // last
function f(){ return "first"; }
function f(){ return "second"; }
function f(){ return "last"; }
// var f = function () { return "Anonymous function"; }
// 并不是函数声明,仅仅生成一个匿名函数
}
fn();
- 最后一行注释掉的代码,并不是函数声明,仅仅生成一个匿名函数,赋值给f,如果去掉注释,执行结果不变。
- 对于函数声明,Javascript 依然会进行变量声明提升,而且会给其赋一个初始的值,这个初始值是最后一次声明的那个函数,所以程序输出是last。
JavaScript 中,变量可以先使用再声明。
fn(1);
function fn(a)
{
b = 10;
console.log(a,b);
var b;
}
由于js的变量提升,上述代码不会产生任何的异常,也不会生成全局变量 b。
变量重名
形参重名
function fn(a,a,a)
{
console(a);
}
fn(1,2,3); // 输出 3
fn(1,2); // 输出 undefined
fn(1); // 输出 undefined
可见,在函数中形参的的初始值是最右边的形参对应的实参值,如果实参不存在则为undefined。
函数与形参重名
function fn(a)
{
function a() {}
console.log(typeof a); // function
}
fn(1);
函数与形参重名的时候变量的初始值是函数。
一个形参是”arguments”
function fn(arguments)
{
console.log(arguments); // 1
}
fn(1);
一个参数是 arguments,那么arguments 的初始值是对应的那个实参。
在变量中声明一个 “arguments”
function fn()
{
var arguments;
console.log(typeof arguments); // object
}
fn();
arguments 的初始值就是在 Javascript 中定义的 object ,而不是重新声明的那个。
PS:在ES6之前,JavaScript没有块级作用域( {…} 即为一个块级作用域),只有全局作用域和函数作用域。
最后,附上一个例子分析,来自:js全局变量为何在函数中取不到值
var a = 0;
function b(c){
console.log(a); //undefined
var a= 1;
arguments[0] = 2;
console.log(c); //2
console.log(a); //1
}
b(3);
console.log(a);//0
- js代码的执行顺序,先声明后执行,此外它会优先在当前的作用域里面找。
- 上面的代码执行顺序是这样的
- 声明变量 a
- 声明函数 b
- 执行赋值 a =0
- 调用 b(3),进入函数 b里面执行,这个时候 又会在 b函数里面 开辟新的内存空间,同样的也是,先声明,后执行从上往下,里面的过程是这样的:
- 声明变量 c
- 声明变量 a
- 执行赋值 c =3 ,在执行
console.log(a);
这里注意 a 在 b 函数里面已经声明了,所以它不会去外面找。而 b 函数里面的这个 a 只是声明了,没有赋值,所以打印为 undefined。
var a;
function b(c){
console.log(a);
var a= 1;
arguments[0] = 2;
console.log(c);
console.log(a);
}
a=0;
b(3);