11.变量提升

90 阅读10分钟

1.变量提升:在当前上下文中(全局/私有/块级),JS代码自上而下执行之前,浏览器会提前处理一些事情(可以理解为词法解析的一个环节,词法解析一定发生在代码执行之前)

* 会把当前上下文中所带VAR/FUNCTION关键字进行提前的声明和定义
	* var a=10
      * 声明declare: var a
      * 定义defined: a=10
* 带var的只会提前声明
* 带function会提前声明+定义

var

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

function

/**
 * 全局上下文,变量提升(自上而下的过程,var和function没有优先级之分)
 * func= 0x000(函数);scope:G
 * 
 */
// // 创建函数
// // 1.直接function声明
// func()
// function func() {
//   var a = 12
//   console.log('OK')
// }

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

// 3.表达式赋值方式-匿名函数具名化(可以内部调用,替代arguments.callee)
// func() // => Uncaught TypeError: func is not a function
var func = function AAA() {
  // 把原本作为值的函数表达式匿名函数“具名化”(虽说起了名字,但是这个名字不能再外面访问=》也就是不会再当前上下文中创建这个名字)
  // 当函数执行,再形成的私有上下文中,会把这个具名化的名字作为私有上下文中的变量(值是这个函数)来进行处理
  console.log('OK')
  // console.log(AAA) // => 当前函数
  // AAA() // 递归调用,而不用严格模式下都不支持的 arguments.callee了。*注意:死循环此处直接这样写,递归不能终止,会造成循环嵌套,内存溢出
}
// AAA() // => Uncaught ReferenceError: AAA is not defined
func()

// 匿名函数具名化,可以内部调用,替代arguments.callee
// setTimeout(function func () {
//   func()
// },1000)

var

/**
 * 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)
/*
Ok
Uncaught ReferenceError: Cannot access 'a' before initialization
*/

2.相互映射VO/GO:全局变量对象VO(G),全局对象属性GO

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

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

3.大括号(判断体,循环体,代码块)中的变量提升(新老浏览器不同)

3.1

/**
 * EC(G):全局上下文中的变量提升
 *  不论条件是否成立,都要进行变量提升(细节点:条件中带function的在新版本浏览器中只会提前声明,不会提前赋值了)
 *  「老版本<=ie10」
 *    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

/*
undefined undefined
undefined
*/

3.2 重复声明处理

/**
 * 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 = function () { console.log(3) } // var fn不用处理了,但是赋值再变量提升阶段没处理过,此时需要处理 fn=window.fn=>3
fn() // 3
function fn() { console.log(4) }
fn() // 3
function fn() { console.log(5) }
fn() // 3

/*
5
5
5
3
3
3
*/

3.3

/**
 * EC(G)
 * vo
 *  foo=1
 * up:
 *  var foo
 *  bar=0x000;scope:EC(G)
 * run:
 *  
 */

var foo = 1
function bar() {
  /**
   * EC(bar)
   * ao
   *  foo=10
   * scope: bar,G
   * this:window
   * arg: -
   * up:
   *  var foo
   */
  if (!foo) { // => undefined
    var foo = 10
  }
  console.log(foo) // => 10
}
bar()

/*
10
*/

3.4 声明后未赋值的情况

/**
 * EC(G)
 * vo
 *  foo=1
 * up
 *  var foo
 *  bar=0x000;scope:G
 * run
 *  
 */
var foo = 1
function bar() {
  /**
   * EC(bar)
   * ao
   *  foo=undefined
   * scope: bar,G
   * this: window
   * arg: -
   * up:
   *  foo
   */
  if (false) { // run阶段,没有重新赋值
    var foo = 10
  }
  console.log(foo) // => undefined
}
bar()
/*
undefined
*/

VO(G) 全局变量对象「全局上下文中声明的变量」 window GO全局对象「浏览器默认开辟的一个堆内存,存储供JS调用的各种API」 console.log(a) // 首先看VO(G)中是否有,无则看G),也无报错:a is not defined

// 在全局上下文中,VO和GO区别
var n = 10
// 老机制: VO(G)中声明一个n的全局变量,赋值10;特殊:全局上下文中 && 基于var/function声明的变量,也相当于给GO新增属性;并且之前存在映射关系「一个修改,另外一个也跟着修改」
// 新版本机制:全局上下文中 && 基于var/function声明的变量,直接都当做GO的属性存储起来
console.log(n) // 10
console.log(window.n) // 10

m = 10; // 给GO设置一个属性m -> window.m =10

// 基于let/const声明的变量,只是给VO(G)中设置全局变量,和GO没有任何关系
let n = 10;
console.log(n) // 10
console.log(window.n) // undefined

// debugger:chrome浏览器中查看SCOPE(script;GO)

1.

console.log(a, b, c);
var a = 12,
    b = 13,
    c = 14;
function fn(a) {
    console.log(a, b, c);
    a = 100;
    c = 200;
    console.log(a, b, c);
}
b = fn(10);
console.log(a, b, c);
/*
  10 13 14
  100 13 200
  12 undefined 200
*/

2.

/**
 * EC(G)变量提升
 * var i;
 * A = 0x000; [[scope]]:EC(G)
 * var y
 * B = 0x001; [[scope]]:EC(G)
 */
var i = 0;
function A() {
  /**
   * EC(A)私有上下文
   *  scope:<EC(A),EC(G)>
   *  param: 
   *  变量提升:
   *    var i
   *    x = 0x002;[[scope]]:EC(A)
   */
  var i = 10;
  function x() {
    /**
     * EC(A)私有上下文
     *  scope:<EC(X1),EC(A)>
     *  param:
     *  变量提升:
     */
    /**
     * EC(A)私有上下文
     *  scope:<EC(X2),EC(A)>
     *  param:
     *  变量提升:
     */
    console.log(i);
  }
  return x; // return 0x002
}
var y = A(); // y=0x002
y(); // 10
function B() {
  /**
   * EC(B)私有上下文
   *  scope:<EC(B),EC(G)>
   *  param:
   *  变量提升:
   *    var i
   */
  var i = 20;
  y(); // 10
}
B();


/*
10
10
*/

3.

/**
 * EC(G)
 * var a
 * var obj
 * fn = 0x000; [[scope]]:EC(G)
 */
var a = 1;
var obj = {
  name: "tom"
} // obj=ox001
function fn() {
  /**
   * EC(FN)
   * scope: FN,G
   * up:var a2
   */
  var a2 = a; // 私有a2=1
  obj2 = obj;
  /**
   * 全局找,也没有的话
   *  情况1:输出obj2,如console.log(obj2):,直接报错
   *  情况2:赋值:相当于给window设置一个obj2属性,并赋值 window.obj2=0x0001
   */
  a2 = a; // 私有a2=1
  obj2.name = "jack"; // 遇到obj2,但是发现全局没有这个变量,它会继续再看windwo下是否有这个属性,如果window下也没有这个属性,报错位(未定义变量) -》把0x001的name改为‘jack’
}
fn();
console.log(a); // =》1
console.log(obj); // => {name:'jack'}


/*
1
{ name: 'jack' }
*/

4.

/**
 * EC(G) up
 * var a
 * fn = 0x000; scope:ec(G)
 */
var a = 1;
function fn(a) {
  /**
   * EC(fn)
   * scope: fn,G
   * param: a =10
   * up: var a「不会重复声明,重新赋值」
   *  a=0x001;scope:EC(fn)
   */
  console.log(a) // => 函数
  var a = 2; // 私有a=2
  function a() { }
  console.log(a) // => 2
}
fn(a); // fn(1)
console.log(a) // => 1


/*
[Function: a]
2
1
*/

5.1

/**
 * EC(G) up:
 *  var a
 *  fn 0x000; [[scope]]:EC(G)
 */
console.log(a); // undefined
var a = 12;
function fn() {
  /**
  * EC(fn) 
  *   scope: fn
  *   param:
  *   up:
  *     var a
  */
  console.log(a); // => undefined
  var a = 13;
}
fn();
console.log(a); // => 12
/*
undefined
undefined
12
*/

5.2

/**
 * EC(G) up:
 *  var a
 *  fn 0x000; [[scope]]:EC(G)
 */
console.log(a); // => undefined
var a = 12;
function fn() {
  /**
  * EC(fn)
  *   scope: fn,G
  *   param:
  *   up:
  */
  console.log(a); // => 12
  a = 13;
}
fn();
console.log(a); // => 13

5.3

/**
 * EC(G) up:
 *  fn 0x000; [[scope]]:EC(G)
 */
console.log(a); // =>  a is not defined
a = 12;
function fn() {
  console.log(a);
  a = 13;
}
fn();
console.log(a);

/*
  报错:a is not defined
*/

6.

/**
 * EC(G) up
 * var foo
 * 
 */
var foo = 'hello';
(function (foo) {
  /**
   * EC(AN)
   * scope: AN,G
   * param: foo='hello'
   * up: var foo;
   */
  console.log(foo); // => 'hello'
  var foo = foo || 'world'; // A&&B,A假返回A,A真返回B,同时存在&&优先级高于||
  console.log(foo); // => 'hello'
})(foo); // 自执行函数执行,把foo的值hello当做实参传进来
console.log(foo); // => 'hello'

/*
hello
hello
hello
*/

7.1

// 新版本浏览器「兼容ES3/ES5,也需要兼容ES6,所以产生一些非常变态的机制」
// 除函数对象等大括号外,在其他的大括号(判断体,循环体,代码块)中出现let/const/function(仅var无效),则会单独形成块级私有上下文
// 出现再其他大括号中的function,不再是声明+定义,而只是声明
/**
 * 老版本浏览器「不支持块级上下文」
 * EC(G) up
 * foo=undefined
 * 被
 */
console.log(foo) // => undefined
{
  /**
   * 形成新的私有块级上下文
   * EC(B)
   * scope: B,G
   * 无this,arguement,形参赋值
   * up:
   *  foo(声明+定义)=0x0000; scope: B
   */
  console.log(foo) // => 函数
  function foo() { } // 按常理变量提升阶段搞过了,此处应该直接跳过,但是它被两家宠幸(全局声明过,私有声明+定义过)过,所以有特殊机制:把当前这一行代码,之前foo的操作,都映射给全局一份,但是之后再对foo的操作,都认为是自己私有的。
  foo = 1; // 只是把私有的变量foo改为1
  console.log(foo) // => 1
}
console.log(foo); // => 函数
/*
undefined
[Function: foo]
1
[Function: foo]
*/

7.2

/**
 * EC(G) up
 * function foo;
 * function foo;
 */
console.log(foo); // => undefined
{
  /**
   * EC(B) 
   * up
   *  foo = 0x000
   *  foo = 0x001
   */
  console.log(foo) // => 0x001
  function foo() { 0 } // 特殊:之前foo的操作都给全局一份,全局foo=0x0001
  console.log(window.foo) // 0x0001
  foo = 1; // 私有的foo=1
  function foo() { 1 } // 特殊:之前foo的操作都给全局一份,全局foo=1
  console.log(foo) // => 1
}
console.log(foo); // => 1
/**
  ƒ foo() { 1 }
  ƒ foo() { 1 }
  1
  1
 */

7.3

{
  function foo() { }
  foo = 1;
  function foo() { } // 特殊:之前foo操作都给全局一份,foo=1
  foo = 2;
}
console.log(foo); // => 1

8.1

/**
 * EC(G)
 * vo
 *  x = 1
 *  func=0x000;scope:EC(G)
 * up
 *  var x
 *  func=0x000;scope:EC(G)
 */

var x = 1;
function func(x, y = function anonymous1() { x = 2 }) {
  /**
   * EC(func)
   * ao:
   *  x = 5
   *    = 3
   *    = 2
   *  y = 0x001:scope:func
   * scope: func,G
   * arg:
   *  x = 5
   *  y = 0x001:scope:func
   * run
   *  x = 3
   */
  x = 3;
  y()
  /**
   * EC(y)
   * ao: -
   * scope:func,G
   * arg: -
   * up: -
   * run:
   *  x = 2
   */
  console.log(x) // 2
}
func(5)
console.log(x) // 1

/*
2
1
*/

8.2 原理同8.3

/**
 * 原理:
 * 1.如果函数应用了“形参赋值默认值”「不论赋值为啥,也不论是否传实参」
 * 2.并且函数体出现了基于var/let/const等生命变量的方式(特殊:let/const声明的变量不能和形参一样,否则报重复声明的错误)
 * 
 * =》 这样除了函数执行,会产生一个私有上下文外「步骤运行到形参赋值后就结束了」
 * =》 会把函数整体及其中的代码当做一个“全新的块级上下文”
 */
/**
 * EC(G)
 * vo
 *  x = 1
 *  func=0x000;scope:EC(G)
 * up
 *  var x
 *  func=0x000;scope:EC(G)
 */

var x = 1;
function func(x, y = function anonymous1() { x = 2 }) {
  /**
   * EC(func)
   * ao:
   * scope: func,G
   * arg:
   *  x = 5
   *    = 2 // 0x0001执行
   *  y = 0x001:scope:func
   * 满足变态机制,到此结束--------------------
   * EC(B)私有块级上下文
   * vo(B)
   *  x = 5 // 默认值,因为名字一致,从上级同步5过来
   *    = 3 // 本地执行代码,更改私有变量,不影响上级的同名变量
   * scope:B,func
   * up
   *  var x
   * run
   *  var x = 3
   *  y() // 不是自己私有的,是上级上下文EC(func)中的
   *  console.log(x) // => 3
   */
  var x = 3;
  y()
  /**
   * EC(y)
   * ao: -
   * scope:y,func
   * arg: -
   * up: -
   * run:
   *  x = 2
   */
  console.log(x) // => 3
}
func(5)
console.log(x) // 1

/*
3
1
*/

8.3 带function变态机制

/**
 * EC(G)
 * vo
 *  x = 1
 *  func=0x000;scope:EC(G)
 * up
 *  var x
 *  func=0x000;scope:EC(G)
 */
var x = 1;
function func(x, y = function anonymous1() { x = 2 }) {
  /**
   * EC(func)
   * ao:
   * scope: func,G
   * arg:
   *  x = 5
   *  y = 0x001:scope:func
   * 
   * 满足变态机制,到此结束--------------------
   * EC(B)私有块级上下文
   * ao(B)
   *  x = 5
   *    = 3 // x=3 执行
   *    = 4 // 0x002执行
   *  y = 0x001:scope:func
   *    = 0x002:scope:B
   * scope:B,func
   * up
   *  var x
   *  var y
   * run
   *  var x = 3
   *  var y = function anonymous2() { x = 4 }
   *  y() // 不是自己私有的,是上级上下文EC(func)中的
   *  console.log(x) // => 3
   */
  var x = 3;
  var y = function anonymous2() { x = 4 }
  y();
  /**
   * EC(y)
   * ao: -
   * scope:y,func
   * arg: -
   * up: -
   * run:
   *  x = 4
   */
  console.log(x) // => 4
}
func(5)
console.log(x) // =》 1

/*
4
1
*/