谈谈Js的变量提升及函数提升

昨天和大家介绍了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”);
}
}

相信经过几个例子的分析,变量提升和函数提升想必大家都有了一定了解了吧。