昨天和大家介绍了Js中的作用域及与其相关的概念,由于这些知识都是相互联系的,所以今天咱紧接着说说Js中的提升。
变量提升、函数提升
大家都知道Js是解释型语言,但是并不是真正的在运行时从上往下逐句解析执行。
Js引擎在对Js代码进行解释执行前,会对Js代码进行预解析,在预解析阶段,会将以关键字var 和 function 开头的语句块提前(当前作用域)进行处理,即:将变量或函数的声明提前。
例:
// 函数提升
func();
function func () {
alert("123");
}
输出结果: “123”
等价于:
function func () {
alert("123");
}
func();
// 变量提升
alert(a);
var a = 1;
输出结果: undefined
等价于:
var a; // 声明变量
alert(a); // 变量声明后未进行初始化和赋值操作看,所以弹出undefined
a = 1;
此处为何只说var和function这两个关键字呢?想必看过ES6的同学应该都知道,如果区块中存在let和const命令,这个区块对这些命令声明的变量,从一开始就形成了封闭作用域。凡是在声明之前就使用这些变量,就会报错。
下面讲讲一些复杂提升的情况
函数同名
func();
function func () {
alert("This is the first");
}
func();
function func () {
alert("This is the last");
}
输出结果: “This is the last”
等价于:
function func () {
alert("This is the first");
}
function func () {
alert("This is the last");
}
func();
func();
变量与函数同名
alert(foo);
function foo () {}
var foo = 2;
输出结果:ƒ foo() {}
原因:当出现变量声明与函数同名时,只会对函数声明进行提升,变量会被忽略。因此上面代码等价于:
function foo () {}
alert(foo);
var foo = 2;
为了让大家更好的理解函数提升,咱来了解一下函数表达式。
Js定义函数的方式
1.函数声明
function functionName(arg1,arg2){
}
注:会发生函数提升
2.函数表达式
var functionName = function(arg1,arg2){
}
注:
- 该方式创建的函数叫匿名函数。匿名函数的执行环境具有全局性,所以其this对象通常指向windows。
- 函数表达式不会发生函数提升,故使用前必须赋值。
例:
sayHi();
var sayHi = function() {
alert("hi");
}
输出结果会报错。
等价于:
var sayHi;
sayHi();
sayHi = function() {
alert("hi");
}
理解函数提升的关键,就是理解函数声明与函数表达式之间的区别。
下面看几个稍复杂的例子:
例1:
if("a" in window) {
var a = 10;
}
console.log(a);
输出结果: 10
解析:Js中没有块级作用域,a会被js引擎提前声明,所以a就被注册到了全局变量中去了。
等价于:
var a;
if("a" in window) {
a = 10;
}
console.log(a);
例2:
var foo = 1;
function bar() {
if(!foo){
var foo = 10;
}
console.log(foo);
}
bar();
输出结果: 10
等价于:
var foo;
function bar() {
var foo;
if(!foo){
foo = 10;
}
console.log(foo);
}
foo = 1;
bar();
(函数声明和变量声明都会提升)
例3:
function Foo() {
getName = function(){ alert(1);}
return this;
}
Foo.getName = function(){ alert(2);}
Foo.prototype.getName = function(){ alert(3);}
var getName = function(){ alert(4);}
function getName(){ alert(5);}
Foo.getName(); // 2
getName(); // 4
Foo().getName(); // 1
getName(); // 1
解析:
以上代码等价于:
function Foo() {
getName = function(){ alert(1);}
return this;
}
var getName;
function getName(){ alert(5);}
Foo.getName = function(){ alert(2);}
Foo.prototype.getName = function(){ alert(3);}
getName = function(){ alert(4);}
Foo.getName(); // 2
getName(); // 4
Foo().getName(); // 1
getName(); // 1
前两个比较简单,我们只说后两个。
1.Foo().getName(); 首先,执行Foo(),它会在其内部寻找getName的声明,未找到就到全局中寻找,发现全局中存在var getName; 且getName = function(){ alert(4);} 所以在函数内继续执行getName = function(){ alert(1);},全局变量名getName被覆盖。最后返回window。所以等价于 window.getName() 等价于 getName()。
2.所以经过Foo().getName();再调用getName();时,还是1。
最后,值得注意的是,条件式函数声明是否提升,取决于浏览器。现在大多数标准都是不提升的。
例:
foo();
if(true){
function foo(){
console.log(“123”);
}
}
相信经过几个例子的分析,变量提升和函数提升想必大家都有了一定了解了吧。