546 变量提升

**栈内存、作用域、执行上下文【执行环境】**

当浏览器开辟出供代码执行的栈内存后,代码并没有自上而下立即执行,而是继续做了一些事情:把当前作用域中所有带var、function关键字的进行提前的声明和定义 =>变量提升机制 【预解析】

  • 带var的只是提前声明(declare): “var a;” ,如果只声明,没有赋值,默认值是undefined
  • 带function的不仅声明,而且还定义了(defined): “a=13”定义其实就是赋值,准确来说就是让变量和某个值进行关联

带var和不带var的区别

//  => 在全局作用域下的区别
/*
 * 不带var的:相当于给全局对象window设置了一个属性a 【而不是变量。】
 *    window.a = 13;
 */
a = 13;
console.log(a); //  => window.a

/*
 * 栈内存变量存储空间
 *    b
 * 带var的:是在全局作用域下声明了一个变量b(全局变量),但是在全局下声明的变量也同样相当于给window增加了一个对应的属性(只有全局作用域具备这个特点) 【即是变量,又是window的属性。】
 */
var b = 14; //  => 创建变量b & 给window设置了属性b
console.log(b); //  => 14
console.log(window.b); //  => 14

console.log(a); // undefined
var a = 12;
var b = a;
b = 13;
console.log(a); // => 12


// ---------------------


console.log(sum(10, 20));  // 30
function sum(n, m) {
  return n + m;
}


// ---------------------


//  函数表达式方式,由于使用VAR来创建SUM,变量提升阶段只会声明变量,不会赋值,所以此时函数在前面执行,函数是没有值的,不能执行(真实项目中这种方式最常用,因为它操作严谨)
console.log(sum); // =>undefined
// sum(10, 20); // =>Uncaught TypeError: sum is not a function
var sum = function (n, m) {
  return n + m;
};
// let sum = (n, m) => n + m;
console.log(sum(10, 20));

/*
 * 变量提升:在当前上下文中(全局/私有/块级),JS代码自上而下执行之前,浏览器会提前处理一些事情(可以理解为词法解析的一个环节,词法解析一定发生在代码执行之前)
 *    会把当前上下文中所有带VAR、FUNCTION关键字的进行提前的声明或者定义
 *       var a=10;
 *       声明declare:var a;
 *       定义defined:a=10;
 *    带VAR的只会提前的声明
 *    带FUNCTION会提前的声明 + 定义
 */


/*
 * 代码执行之前: 全局上下文中的变量提升
 *    var a;   默认值是undefined
 */
console.log(a); // => undefined
var a = 12; // => 创建值12  不需要在声明a了(变量提升阶段完成了,完成的事情不会重新处理) a=12赋值
a = 13; //全局变量a=13
console.log(a); // => 13


// ------------------------------------------


/* 
 * 全局上下文中的变量提升
 *   func=函数   函数在这个阶段赋值都做了
 */
func();
function func() {
  var a = 12;
  console.log('OK');
}


// ------------------------------------------


func(); // => Uncaught TypeError: func is not a function
var func = function () {
  // 真实项目中建议用函数表达式创建函数,因为这样在变量提升阶段只会声明FUNC,不会赋值
  console.log('OK');
};
func();


// ------------------------------------------


var func = function AAA() {
  // 把原本作为值的函数表达式 匿名函数“具名化”(虽说是起了名字,但是这个名字不能在外面访问  => 也就是不会在当前当下文中创建这个名字)
  // 当函数执行,在形成的私有上下文中,会把这个具名化的名字做为私有上下文中的变量(值就是这个函数)来进行处理
  console.log('OK');
  console.log(AAA); // => 当前函数
  AAA(); // 递归调用, 而不用严格模式下都不支持的 arguments.callee 了
};
// AAA();  // => Uncaught ReferenceError: AAA is not defined
func();


// ------------------------------------------


/*
 * EC(G)变量提升 
 */
console.log(a); // => Uncaught ReferenceError: a is not defined
a = 13;
console.log(a);


// ------------------------------------------


/*
 * EC(G)变量提升:只有VAR/FUNCTION会变量提升(ES6中的LET和CONST不会) 
 */
// 这里不是词法解析错误
console.log('OK'); // => 'OK'
console.log(a); // => Uncaught ReferenceError: Cannot access 'a' before initialization  不能在LET声明之前使用变量
let a = 12;
a = 13;
console.log(a);


// ------------------------------------------


/*
 * 基于“VAR或者FUNCTION”在“全局上下文”中声明的变量(全局变量)会“映射”到GO(全局对象window)上一份,作为他的属性;而且接下来是一个修改,另外一个也会跟着修改;
 */
var a = 12;
console.log(a); // => 12  全局变量
console.log(window.a); // => 12 映射到GO上的属性a

window.a = 13;
console.log(a); // => 13 映射机制是一个修改另外一个也会修改


// ------------------------------------------


/*
 * EC(G): 全局上下文中的变量提升
 *    不论条件是否成立,都要进行变量提升
 *    细节点:条件中带function的,在新版本浏览器中只会提前声明,不会再提前的赋值了
 *   [老版本]
 *      var a;
 *      func = 函数; 【老版本的函数,声明、赋值都提前。】
 *   [新版本]
 *      var a;   全局上下文中声明一个a也相当于 window.a
 *      func;
 */

console.log(a, func); // => undefined undefined
if (!("a" in window)) {
  // => "a" in window:检测a是否为window的一个属性   !TRUE  =>  FALSE
  var a = 1;

  function func() { }
}
console.log(a); // => undefined


// ------------------------------------------


/*
 * EC(G)变量提升
 *    fn => 1
 *       => 2
 *    var fn;  已经声明过了,不再重复声明,只需重新赋值
 *       => 4
 *       => 5
 * 全局上下文中有一个全局变量fn, 值是输出5的函数(此时window.fn => 5)
 */
fn(); // => 5
function fn() { console.log(1); }  // => 不再处理,变量提升阶段搞过了
fn(); // => 5
function fn() { console.log(2); }
fn(); // => 5
// => var fn不用再处理了,但是赋值在变量提升阶段没处理过,此处需要处理  fn = window.fn => 3
var fn = function () { console.log(3); }
fn(); // => 3
function fn() { console.log(4); }
fn(); // => 3
function fn() { console.log(5); }
fn(); // => 3


// ------------------------------------------


var foo = 1;
function bar() {
  if (!foo) {
    var foo = 10;
  }
  console.log(foo); // 10
}
bar();


// ------------------------------------------


var a = 0;
if (true) {
  a = 1;
  function a() { };
  a = 21;
  console.log(a); // 21
}
console.log(a); // 1


// ------------------------------------------


var a = 0;
if (true) {
  a = 1; // => Uncaught ReferenceError: Cannot access 'a' before initialization
  let a = 10;
  a = 21;
  console.log(a)
}
console.log(a);


// ------------------------------------------


// Uncaught SyntaxError: Identifier 'a' has already been declared
console.log(a);
{
  // 【块级作用域,function提升,a被声明过。】
  console.log(a);
  var a = 10;
  function a() { }
  console.log(a);
}
console.log(a);

变量提升练习题1

* 在变量提升阶段,遇到大括号、判断体等,不论条件是否成立,都要进行变量提升

* 起作用的:var、function

*

* IE低版本浏览器(<= IE10):

* 都会进行变量提升,而且函数依然是 声明 + 定义 都完成了

* 高版本浏览器:

* 虽然也会进行变量提升,但是对于函数只声明,不会再定义了

*

* ES6新语法规范中,存在块级上下文(除对象的大括号中,出现let、const、function,都会把当前大括号形成一个私有的跨级上下文) => 兼容高版本浏览器

*

* 现在的浏览器很悲催,因为既要兼容低版本语法规范,还要适应高版本语法规范,所以对于有冲突的规范,会采用一些取中间的方式....

console.log(foo) // undefined
{
  function foo() { } // 【是这句代码之前的所有操作都提升,这句代码不提升,不包括这句代码】
  foo = 1;
  console.log(foo); // => 1
}
console.log(foo); // => 函数foo 


// -------------


// 全局变量提升: function foo;
console.log(foo) // undefined
{
  /*
   * 私有的块级上下文的变量提升:
   *    function foo() {alert('1')}
   *    function foo() {alert('2')} 【保留的】
   */
  console.log(foo); // => alert('2')的 函数foo 
  function foo() { alert('1') }
  foo = 1;
  // 会把当前代码之前对于foo的操作映射到全局一份 【是这句代码之前的所有操作都提升,这句代码不提升,不包括这句代码】
  function foo() { alert('2') }
}
console.log(foo); // => 1 


// -------------


// 全局  function foo;
{
  // 私有 
  // function foo() {alert('1')}  
  // function foo() {alert('2')} 【保留】
  function foo() { alert('1') }
  foo = 1;
  function foo() { alert('2') } //把之前的操作映射给全局一份
  foo = 2;
  console.log(foo); // => 2
}
console.log(foo); // => 1 

变量提升练习题2

* 如果同时符合了这两个条件:

* 1. 形参有赋值默认值

* 2. 函数体中有声明过变量 var、function...

* 此时的函数执行会形成两个上下文

* 1. 私有的上下文

* 2. 函数体所在大括号中的块级上下文 【在私有上下文中,用var、let、const等声明的变量属于块级上下文。】

* => 函数体中遇到一个变量,我们首先看是否为块上下文中的变量,如果是,接下来都操作块上下文中的变量,和私有没关系;如果不是,操作的是私有的或者全局的...

/*
 * 全局上下文
 *   var x; 
 *   function func(x,y...){...};
 */
var x = 1;
function func(x, y = function anonymous1() { x = 2 }) {
  /*
   * 私有上下文EC(1) 
   *   形参赋值:x = 5,  y = anonymous1
   *   变量提升:--
   */
  x = 3;  // 私有x = 3
  y();
  /* 
   * anonymous1()  私有向下文EC(2)  
   *    形参赋值:--
   *    变量提升:--
   * x = 2  让EC(1)上级上下文中的x=2
   */
  console.log(x); // => 2
}
func(5);
console.log(x); // => 1 


// ------------------------------------


/*
 * 如果同时符合了这两个条件:
 *   1. 形参有赋值默认值
 *   2. 函数体中有声明过变量 var、function...
 * 此时的函数执行会形成两个上下文
 *   1. 私有的上下文
 *   2. 函数体所在大括号中的块级上下文 【在私有上下文中,用var、let、const等声明的变量属于块级上下文。】
 *    => 函数体中遇到一个变量,我们首先看是否为块上下文中的变量,如果是,接下来都操作块上下文中的变量,和私有没关系;如果不是,操作的是私有的或者全局的...
 */
var x = 1;
function func(x, y = function anonymous1() { x = 2 }) {
  // 【形参赋值】私有上下文 x = 5,y = anonymous1 【这里可以把私有上下文当做块级上下文的上级上下文】
  // 块级上下文 x = 5 【x的初始值是5】
  console.log(x); // => 5 【块级】
  var x = 3; // 块级中的 x = 3 
  y(); // x = 2 私有中的 x = 2 【因为没有用var、let等重新声明y,所以y是上级的私有上下文的y。】
  console.log(x); // => 3
}
func(5);
console.log(x); // => 1 


// ------------------------------------


var x = 1;
function func(x, y = function anonymous1() { x = 2 }) {
  // 私有上下文 x = 5,y = anonymous1 
  // 块级上下文 x = 5,y = anonymous1,在还没有执行到指定代码之前,存储的值和私有上下文中的值是一样的
  var x = 3;  // 块级上下文x=3
  var y = function anonymous2() { x = 4 };  // 块级上下文y = anonymous2
  y(); // anonymous2()  x = 4  块级上下文中的x = 4
  console.log(x); // => 4
}
func(5);
console.log(x); // => 1 

原文地址:https://www.cnblogs.com/jianjie/p/13831452.html