zoukankan      html  css  js  c++  java
  • 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 
    

  • 相关阅读:
    修复 Visual Studio Error “No exports were found that match the constraint”
    RabbitMQ Config
    Entity Framework Extended Library
    Navisworks API 简单二次开发 (自定义工具条)
    NavisWorks Api 简单使用与Gantt
    SQL SERVER 竖表变成横表
    SQL SERVER 多数据导入
    Devexpress GridControl.Export
    mongo DB for C#
    Devexress XPO xpPageSelector 使用
  • 原文地址:https://www.cnblogs.com/jianjie/p/13831452.html
Copyright © 2011-2022 走看看