JS中的变量提高机制

在当前执行上下文中(不管是全局,仍是函数执行私有的),JS代码自上而下执行以前,首先会默认把全部带VARFUNCTION关键字的进行声明或者定义。javascript

  • VAR的只是提早声明
  • FUNCTION的会提早声明+定义

思惟导图

1、先简单了解两个语法

var n = 100为例,咱们以前讲过的操做步骤应该是:java

    1. 建立值100存储到栈内存中(引用数据类型首先建立堆,存完数据后,把堆的地址存放到栈中)
    1. 建立一个变量 var a;
    1. 让变量和值关联在一块儿

这是咱们以前说过的建立变量并赋值的过程;面试

  • 然而在这个过程当中,咱们所谓的建立变量的专业描述叫作=>变量声明(declare)
  • 让变量和值关联在一块儿的专业描述叫作 =>变量定义(defined)

上面咱们所说的是咱们常规的建立变量并赋值的操做。浏览器

那当咱们只声明变量时,也就是var a;可是没有给变量赋值(也就是未定义),因此默认值是undefined未定义 (这也是undefined的由来)bash

2、理解变量提高的含义

为了更好的理解变量提高,咱们仍是得先从浏览器的机制提及:函数

一、浏览器运行机制

  • 一、为了可以让代码执行,浏览器首先会造成一个执行环境栈ECStack(Execution Context Stack)
    • 栈内存的做用:
      • 1.代码执行
      • 2.基本数据值存储在栈内存中
    • 堆内存做用:(本次咱们用不到,只是提到栈内存时,天然提一下堆内存)
      • 1.存储引用数据类型的值

    这里咱们不在过多讲解堆栈内存,前面已经专门讲过。ui

有了栈内存代码就能够自上而下的执行了,只不过刚开始,是要把全局下的代码先执行;spa

  • 二、开始执行全局下的代码,就会造成一个全局执行上下文EC(GLOBAL 简写 为G)(上下文就是上文下文加一块儿就是环境的意思);code

    • 至关于在栈内存中造成了一个小空间,用来执行全局下的代码;
    • 之后咱们的函数执行也会造成一个这样的小空间,用来执行函数中的代码,这个咱们一会说;

    一、2 两步总体原理:咱们的目的是为了执行代码,因此围绕代码执行引起的下列操做:cdn

    • ==> 浏览器加载页面,想让代码执行,首先会造成一个栈内存(执行环境栈 ECStack);而后开始让代码准备执行;
    • ==> 最开始要执行的必定是全局下的代码,
    • ==> 此时造成一个全局代码的执行环境(全局执行上下文 EC(G))
    • ==> 造成以后,把EC(G)压缩到栈内存中去执行(进栈);每个函数的执行也是这样操做的...
    • ==> 有些上下文在代码执行完成后,会从栈内存中移除去(出栈),可是有些状况是不能移出去的(例如:全局上下文就不能移除...);
    • ==> 在下一次有新的执行上下文进栈的时候,会把以前没有移除的都放到栈内存的底部,让最新要执行的在顶部执行
  • 三、对应代码会在本身所属的执行上下文中执行,而这个环境中有一个存放变量的地方:变量对象(VO/AO)

    • 全局下存储变量的地方叫作:全局变量对象VO(G) ->全称Variable Object (Global)
      • 所谓的变量对象,就是专门为了存储当前环境下要建立的变量的地方

在咱们以前的理解,建立完一系列的环境后,就能够执行代码了,然而并无,在代码执行以前还有一系列的操做要作;例如:词法解析 => 形参赋值 => 变量提高...等好多好多事情;词法解析和形参赋值咱们后面会说,如今咱们先来理解:变量提高

二、变量提高

变量提高:就是在当前上下文中,JS代码执行以前要作的事情;首先会默认把全部带VARFUNCTION关键字的进行声明或者定义。

  • VAR的只是提早声明
  • FUNCTION的会提早声明+定义

咱们如下题为例

console.log(a);
console.log(func);
var a = 10;
function func(){
    console.log(b);
    var b=20;
    console.log(b);
}
func();
console.log(a);
console.log(func); 
复制代码
  • 第一步:找到带VAR和先声明,找到带FUNCTION的声明+定义
    • 一、var a ;
    • 二、function func(){...};(按照函数建立的步骤,即声明又定义)
      • 1).建立堆内存,把函数体内的代码看成字符串存储进堆内存中;
      • 2).把堆内存的十六进制地址存进栈内存;
      • 3).建立变量func与十六进制地址关联;
  • 第二步:代码开始自上而下执行
    • 一、conlose.log(a):=> 此时在当前执行上下文中,已经声明过a可是并无赋值,因此打印结果为undefined
    • 二、console.log(func):=> 此时当前上下文中已经声明了func变量,而且已经赋值,因此打印func的结果为f func(){...}
    • 三、var a = 10:=> 以前咱们在变量提高阶段,已经完成了var a操做(浏览器是又懒惰性的,作完了这件事,不会在作第二遍了),因此此时只建立了一个值10,没有在建立变量a;把10与以前在变量提高阶段声明的变量a关联;
    • 四、function func(){...}:=> 上面咱们说了,同一件事情浏览器不会作两便;很明显以前在变量提高阶段已经作过这件事了,因此此步就会跳过;直接进行下一步;
    • 五、func():=> 让函数执行;
      • 目的:也是执行以前存储到堆内存中的代码,既然是要执行代码;就要造成一个执行上下文;这个上下文就叫作私有上下文EC(func随便起的名字)
      • 1).浏览器处理过程:
        • 造成后也一样要压缩进入执行环境栈中执行,
        • 此时,执行环境栈中已有的全局执行上下文就会向下压缩(给函数的执行上下文腾出执行空间)
        • 私有上下文中存储变量的地方叫作:私有变量对象AO(func1随便起的名字) ->全称Active Object;
      • 2).函数执行过程:
        • 第一步:变量提高:找到带VAR和先声明,找到带FUNCTION的声明+定义
          • var b
          • 没有带function
        • 第二步:函数执行
          • console.log(b):=> 此时b以声明为定义,因此是undefined;
          • var b=20:=> 同全局同样,跳过var b,直接建立值,并与之声明的b关联;
          • console.log(b):=> 此时,b已经被赋值,因此结果为20;
          • 函数执行完成
      • 3).浏览器处理:
        • 函数执行完成,出栈,
        • 执行环境栈中空间被释放(之后再讲,这里先这样写),
        • 以前被压缩到下面的全局执行上下文又回到原来的位置;
        • 继续执行全局下的代码;
    • 六、console.log(a):=> 此时a的值为10;
    • 七、console.log(func):=> 此时func依然指向堆内存所对应的十六进制地址,因此打印结果为f func(){...};

3、避免变量提高在函数执行时的不严谨性(不是重点简单介绍)

上面咱们简单的理解了变量提高的含义;

  • 那么咱们思考一下这个题,是否会报错呢?
func();
function func(){
    console.log('OK');
}
+ 答案是不会报错
复制代码
func();
var func = function (){
    console.log('OK');
}

+ 答案是会报错:
    +  变量提高阶段:var func; 只定义不赋值 (默认值是undefined)
    + //=> undefined() =>Uncaught TypeError: func is not a function 
复制代码

对比上面两题的区别,咱们得出结论:

  • 函数表达式方式建立函数,在变量提高阶段,并不会给函数赋值,这样只有代码执行的过程当中,函数赋值后才能够执行(不能在赋值以前执行) =>符合严谨的执行逻辑 =>真实项目中推荐的方式
  • 为了保证语法的规范性,JS中本来建立的匿名函数,咱们最好设置一个名字,可是这个名字在函数外面仍是没法使用的,只能在本函数体中使用(通常也不用) 例如:
var func = function anonymous() {
	// console.log(anonymous); //=>当前这个函数
};
// console.log(anonymous); //=>Uncaught ReferenceError: anonymous is not defined
func();

//================================================

var func = function func() {
	console.log(func); //=>函数名func
};
console.log(func); //=>变量func,存储的值是函数
func();
复制代码

4、变量提高在条件判断下的处理

截止目前(注意是目前),咱们的上下文只有两种:

  • => 全局上下文
  • => 函数执行产生的私有上下文

一、全局上下文中带VAR

/* * 全局上下文中的变量提高: * [VO(G) 全局变量对象] * var n; 不论IF条件是否成立都进行(只要不在函数里面,带VAR的都要变量提高) * 不管是IF仍是FOR中的VAR都进行提高; */
console.log(n); //=>undefined
if (1 > 1) { //=>条件不成立
	var n = 100;
}
console.log(n); //=>undefined
复制代码

二、全局上下文中带FUNCTION

-1)[IE 10及之前以及谷歌等浏览器低版本状态下]:function func(){ ... } 声明+定义都处理了

-2)[最新版本的浏览器中,机制变了]:function func; 用判断或者循环等包裹起来的函数,在变量提高阶段,不论条件是否成立,只会先声明

/* * 全局上下文中的变量提高: * [VO(G) 全局变量对象] * [IE 10及之前以及谷歌等浏览器低版本状态下] * function func(){ ... } 声明+定义都处理了 * * [最新版本的浏览器中,机制变了] * function func; 用判断或者循环等包裹起来的函数,在变量提高阶段,不论条件是否成立,此处只会先声明 */
console.log(func); //=>undefined
if (1 === 1) {
	// 此时条件成立,进来的第一件事情仍是先把函数定义了(迎合ES6中的块做用域) => func=function(){ .... }
	console.log(func); //=>函数
	function func() {
		console.log('OK');
	}
	console.log(func); //=>函数
}
console.log(func); //=>函数
复制代码

一、在当前执行上下文中,无论条件是否成立,变量提高是有效的

二、[IE 10及之前以及谷歌等浏览器低版本状态下]:function func(){ ... } 声明+定义都处理了

三、[最新版本的浏览器中,机制变了]:function func; 用判断或者循环等包裹起来的函数,在变量提高阶段,不论条件是否成立,只会先声明 重点在写一遍强调:

5、变量提高的两道经典面试题

一、写出结果

// 浏览器有一个特征:作过的事情不会从新再作第二遍(例如:不会重复声明)
/* * 全局上下文中的变量提高 * fn = function(){ 1 } 声明+定义 * = function(){ 2 } * var fn; 声明这一步不处理了(已经声明过了) * = function(){ 4 } * = function(){ 5 } * 结果:声明一个全局变量fn,赋的值是 function(){ 5 } */
fn(); //=>5
function fn(){ console.log(1); }  //=>跳过(变量提高的时候搞过了)
fn(); //=>5
function fn(){ console.log(2); }  //=>跳过
fn(); //=>5
var fn = function(){ console.log(3); } //=>var fn; 这一步跳过,可是赋值这个操做在变量提高阶段没有搞过,须要执行一次 => fn = function(){ 3 }
fn(); //=>3
function fn(){ console.log(4); } //=>跳过
fn(); //=>3
function fn(){ console.log(5); } //=>跳过
fn(); //=>3
复制代码

二、分别写出在低版本浏览器和高版本浏览器下的输出结果

低版本

/* * 低版本浏览器(包含IE10及之内) * 全局上下文中变量提高 * 没有 */
f=function (){return true;};//给GO中设置一个属性 f = function () {return true;}
g=function (){return false;};//给GO中设置一个属性 g = function () {return false;}
(function () {
	/* * 自执行函数执行,造成一个私有的执行上下文 * [变量提高] * function g(){return true;} */
	// 条件解析:
	// g() => 私有的G执行 TRUE
	// []==![] => []==false => 0==0 => TRUE
    if (g() && [] == ![]) { //=>条件成立
        f = function () {return false;} //f不是本身私有的,则向上查找,属于全局对象中的f,此处是把全局对象中的 f = function () {return false;}
        function g() {return true;} //跳过(变量提高处理过了)
    }
})();
console.log(f()); //=>FALSE
console.log(g()); //=>FALSE 这个G找全局的(函数里面的G是本身私有的)
复制代码

高版本

/* * 高版本浏览器 * 全局上下文中变量提高:没有 */
f=function (){return true;};//给GO中设置一个属性 f = function () {return true;}
g=function (){return false;};//给GO中设置一个属性 g = function () {return false;}
(function () {
	/* * 自执行函数执行,造成一个私有的执行上下文 * [变量提高] * function g; 高版本浏览器中,在判断和循环中的函数,变量提高阶段只声明不定义 */
	// 条件解析:
	// g() => undefined() => Uncaught TypeError: g is not a function 下面操做都不在执行了
    if (g() && [] == ![]) {
        f = function () {return false;} 
        function g() {return true;}
    }
})();
console.log(f());
console.log(g());
复制代码
相关文章
相关标签/搜索