JS变量提升

变量提升:函数声明和变量声明总是会被解释器悄悄地"提升"到方法体的最顶部。

我们习惯将var a = 1;看做是一个声明,而实际上javascript引擎并不这么认为。它将var a;和a = 1看做是两个单独的声明,第一个是编译阶段的任务,而第二个则是执行阶段的任务。

这意味着无论作用域中的声明出现在什么地方,都将在代码本身被执行前首先进行处理,可以将这个过程形象地想象成所有的声明(变量和函数)都会被“移动”到各自作用域的最顶端,这个过程被称为提升。

javascript中ES5的var、function,ES6的function *、let、const、class都会存在提升现象,不同的是,var,function,function*的声明会在提升时进行初始化赋值为 undefined,因此访问这些变量的时候,不会报ReferenceError异常,而使用let,const,class声明的变量,被提升后不会被初始化,这些变量所处的状态被称为暂时性死区(temporal dead zone),此时如果访问这些变量会抛出ReferenceError异常,看上去就像没被提升一样。

js的执行环境和作用域

执行环境

执行环境定义了变量和函数有权访问的其他数据,决定了他们各自的行为。每个执行环境都有与之对应的变量对象(variable object),此对象保存着环境中定义的所有变量和函数。我们无法通过代码来访问变量对象,但是解析器在处理数据时会在后台使用到它。

 -全局执行环境

全局执行环境是最外围的一个执行环境,根据ECMAscript实现所在的宿主环境不同,表示执行环境的对象也不同。在web浏览器中,我们可以认为它是window对象,因此所有的全局变量和函数都是作为window对象的属性和方法创建的。代码载入浏览器时,全局环境被创建,应用程序退出,如关闭网页或者浏览器时,全局执行环境被销毁。

 -函数执行环境

 每个函数都有自己的执行环境,当执行流进入一个函数时,函数的环境就被推入一个环境栈中,当函数执行完毕后,栈将其环境弹出,把控制权返回给之前的执行环境。

作用域

在ES5中,js只有两种形式的作用域:全局作用域和函数作用域。

 -全局作用域

 全局对象的作用域,任意地方都可以访问到(如果没有被函数作用域覆盖)。在ES6中,新增了一个块级作用域(最近的大括号涵盖的范围),但是仅限于let方式申明的变量。

 -函数作用域

  整个函数范围

变量提升

var的变量提升

ES6之前我们一般使用var来声明变量,提升简单来说就是把我们所写的类似于var a = 123;这样的代码,声明提升到它所在作用域的顶端去执行,到我们代码所在的位置来赋值。

function test () {
    console.log(a);  //undefined
    var a = 123; 
};
test();

上述代码a的结果是undefined,它的实际执行顺序如下:

function test () {
    var a;
    console.log(a);
    a = 123;
}
test();

再来一个:

console.log(a); // undefined
var a = 1;
console.log(a); // 1
// console.log(b); // ReferenceError: b is not defined

为了显示ab的区别,打印了一下未声明的变量b,其抛出了一个ReferenceError异常,而a并未抛出异常,其实对a的定义并赋值类似于以下的操作,将a的声明提升到作用域最顶端,然后再执行赋值操作。

var a;
console.log(a); // undefined
a = 1;
console.log(a); // 1

所以,JavaScript 中,变量可以在使用后声明,也就是变量可以先使用再声明。

下面来看一道经典面试题:

console.log(v1); //undefined
var v1 = 100;
function foo() {
    console.log(v1); //undefined
    var v1 = 200;
    console.log(v1); //200
}
foo();
console.log(v1); //100

let的变量提升

关于这个问题目前有所分歧,但在ES6的文档中出现了var/let hoisting字样,也就是说官方文档说明letvar一样,都存在变量提升,我觉得能够接受的说法是:

let 的「创建」过程被提升了,但是初始化没有提升。

var 的「创建」和「初始化」都被提升了。

function 的「创建」「初始化」和「赋值」都被提升了。

函数提升

javascript中不仅仅是变量声明有提升的现象,函数的声明也是一样;具名函数的声明有两种方式:1. 函数声明式    2. 函数字面量式(实质为变量+匿名函数)

//函数声明式
function bar () {}
//函数字面量式 
var foo = function () {}

函数声明式的提升现象和变量提升略有不同,函数声明会将声明与赋值都提前,也就是整个函数体都会被提升到作用域顶部。

bar();//1
function bar () {
  console.log(1);
}

执行顺序相当于:

function bar () {
  console.log(1);
}
bar();//1

函数字面量式的声明和变量提升的结果是一样的,函数表达式只会提升变量的声明,函数只是一个具体的值,本质上是变量提升并将一个匿名函数对象赋值给变量。

test(); //test is not a function
var test = function(){
    console.log("不可以被提升");
}

优先级

函数和变量同名的时候,变量未赋值的情况下,函数声明优先级高于变量声明。ps:参数的优先级别大于变量声明(函数声明>参数>变量声明)

函数和变量同名的时候,变量赋值的情况下,变量高于函数声明。

foo(); //1
 
var foo = 'a';
 
function foo () {
    console.log(1);
}
 
foo = function () {
    console.log(2);
}

foo(); //2

相当于:

var foo; // 变量声明

function foo () { //函数声明
    console.log(1);
}

foo(); //1
 
foo = 'a';
 
foo = function () { // 变量赋值
    console.log(2);
}

foo(); //2

可见JS引擎执行的顺序是  变量声明  函数声明  变量赋值

注意

下面这段代码,在低版本的浏览器中,函数提升不会被条件判断所控制,输出2;但是在高版本的浏览器中会报错,所以应该尽可能避免在块内部声明函数

foo(); //低版本:2  //高版本: Uncaught TypeError: foo is not a function
 
var a = true;
 
if(a){
    function foo () { console.log(1); }
}else{
    function foo () { console.log(2); }
} 

最后,再来看一个题:

function f() {
    console.log(typeof f); //function
    // var f = 3;
    f = 3;
   console.log(typeof f); //number
};

f();

var s = function s() {
    console.log(typeof s); //function
    // var s = 3;
    s = 3;
    console.log(typeof s); //function 
};

s();

上述代码中,函数f是具名函数,函数s是函数表达式。具名函数中,可以在函数内部改变函数名,而函数表达式,如果有函数名,则它的函数名只能作用在其自身作用域中,且不可改变函数名。

 

posted @ 2020-12-30 19:29  奥利奥ALA  阅读(304)  评论(0编辑  收藏  举报