js知识梳理四

变量提升

当栈内存(作用域)形成,JS代码自上而下执行之前,浏览器首先会把所有带 “VAR”/“FUNCTION” 关键词的进行提前 “声明” 或者 “定义” ,这种预先处理机制称之为 “变量提升”

声明(declare):var a (默认值undefined)
定义(defined):a=12 (定义其实就是赋值操作)

变量提升阶段

  • 带“VAR”的只声明未定义
  • 带“FUNCTION”的声明和赋值都完成了
  • 变量提升只发生在当前作用域(例如:开始加载页面的时候只对全局作用域下的进行提升,因为此时函数中存储的都是字符串而已)
  • 在全局作用域下声明的函数或者变量是“全局变量”,同理,在私有作用域下声明的变量是“私有变量” [带VAR/FUNCTION的才是声明]
1
2
console.log(a);//=>undefined
var a = 12;

变量带var声明和直接声明

在全局作用域下声明一个变量,也相当于给WINDOW全局对象设置了一个属性,变量的值就是属性值(私有作用域中声明的私有变量和WINDOW没啥关系)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
console.log(a);//=>undefined
console.log(window.a);//=>undefined
console.log('a' in window); //=>TRUE 在变量提升阶段,在全局作用域中声明了一个变量A,此时就已经把A当做属性赋值给WINDOW了,只不过此时还没有给A赋值,默认值UNDEFINED in:检测某个属性是否隶属于这个对象
var a = 12;//=>全局变量值修改,WIN的属性值也跟着修改
console.log(a);//=>全局变量A 12
console.log(window.a);//=>WINDOW的一个属性名A 12

a = 13;
console.log(window.a);//=>13

window.a = 14;
console.log(a);//=>14
//=>全局变量和WIN中的属性存在 “映射机制”

//=>不加VAR的本质是WIN的属性
*/

1
2
3
4
5
6
7
8
9
10
11
12
13
console.log(a, b);//=>undefined undefined
var a = 12,
b = 12;

function fn() {
console.log(a, b);//=>undefined 12
var a = b = 13;
/*var a=13; b=13;*/
console.log(a, b);//=>13 13
}

fn();
console.log(a, b);//=>12 13

在当前作用域下,不管条件是否成立都要进行变量提升

  • =>带VAR的还是只声明
  • =>带FUNCTION的在老版本浏览器渲染机制下,声明和定义都处理,但是为了迎合ES6中的块级作用域,新版浏览器对于函数(在条件判断中的函数),不管条件是否成立,都只是先声明,没有定义,类似于VAR

重名问题

  • 带VAR和FUNCTION关键字声明相同的名字,这种也算是重名了(其实是一个FN,只是存储值的类型不一样
  • 2.关于重名的处理:如果名字重复了,不会重新的声明,但是会重新的定义(重新赋值)[不管是变量提升还是代码执行阶段皆是如此]

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
     fn();//=>4
    function fn() {console.log(1);}
    fn();//=>4
    function fn() {console.log(2);}
    fn();//=>4
    var fn=100;//=>带VAR的在提升阶段只把声明处理了,赋值操作没有处理,所以在代码执行的时候需要完成赋值 FN=100
    fn();//=>100() Uncaught TypeError: fn is not a function
    function fn() {console.log(3);}
    fn();
    function fn() {console.log(4);}
    fn();

    let/const

       在ES6中基于LET/CONST等方式创建变量或者函数,不存在变量提升机制,这种方式声明的全局变量也不再与windo存在映射关系。

       在相同的作用域中,基于LET不能声明相同名字的变量(不管用什么方式在当前作用域下声明了变量,再次使用LET创建都会报错)

       虽然没有变量提升机制,但是在当前作用域代码自上而下执行之前,浏览器会做一个重复性检测(语法检测):自上而下查找当前作用域下所有变量,一旦发现有重复的,直接抛出异常,代码也不会在执行了(虽然没有把变量提前声明定义,但是浏览器已经记住了,当前作用域下有哪些变量)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
     let a = 10,
    b = 10;
    let fn = function () {
    console.log(a, b);//=>Uncaught ReferenceError: a is not defined
    // =>基于LET创建变量,会把大部分{}当做一个私有的块级作用域(类似于函数的私有作用域),在这里也是重新检测语法规范,看一下是否是基于新语法创建的变量,如果是按照新语法规范来解析
    let a = b = 20;
    /*
    * let a=20;
    * b=20; //=>把全局中的 b=20
    */
    console.log(a, b);
    };
    fn();
    console.log(a, b);

    全局和私有变量

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
     /*
    * 变量提升:
    * var a; var b; var c;
    * fn = xxx...
    */
    var a = 12,
    b = 13,
    c = 14;

    function fn(a) {
    /*
    * 形参赋值
    * a = 12
    *
    * 变量提升
    * var b;
    *
    * =>在私有作用域中,只有以下两种情况是私有变量
    * A:声明过的变量(带VAR/FUNCTION)
    * B:形参也是私有变量
    *
    * 剩下的都不是自己私有的变量,都需要基于作用域链的机制向上查找
    */
    console.log(a, b, c);//=>12 undefined 14(C是全局的)
    var b = c = a = 20;
    /*
    var b=20;
    c=20; =>把全局的C修改为20
    a=20;
    */
    console.log(a, b, c);//=>20*3
    }

    fn(a);//=>把FN执行(小括号中是实参:值) =>执行FN把全局变量A的值12当做实参传递给函数的形参 =>fn(12)
    console.log(a, b, c);//=>12 13 20
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
     var ary = [12, 23];

    function fn(ary) {
    console.log(ary); // [12,23]
    ary[0] = 100;
    ary = [100];
    ary[0] = 0;
    console.log(ary); // [0]
    }

    fn(ary);
    console.log(ary); //[100,23]

    上级作用域

          当前函数执行,形成一个私有作用域A,A的上级作用域是谁,和他在哪执行的没有关系,和他在哪创建(定义)的有关系,在哪创建的,它的上级作用域就是谁

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    //  function fn() {
    // //=>arguments:实参集合
    // //=>arguments.callee:函数本身FN
    // //=>arguments.callee.caller:当前函数在哪执行的,CALLER就是谁(记录的是它执行的宿主环境),在全局下执行CALLER的结果是NULL
    // console.log(arguments.callee.caller);
    // }
    var n = 10;
    function fn() {
    var n = 20;
    function f() {
    n++;
    console.log(n);
    }
    f();
    return f;
    }
    var x = fn(); //21
    x(); // 22
    x(); // 23
    console.log(n); // 10

    JS中的内存分为堆内存和栈内存

    • 堆内存:存储引用数据类型值(对象:键值对 函数:代码字符串)
    • 栈内存:提供JS代码执行的环境和存储基本类型值
    • 堆内存释放: 让所有引用堆内存空间地址的变量赋值为null即可(没有变量占用这个堆内存了,浏览器会在空闲的时候把它释放掉)
    • 一般情况下,当函数执行完成,所形成的私有作用域(栈内存)都会自动释放掉(在栈内存中存储的值也都会释放掉),但是也有特殊不销毁的情况:

      1、函数执行完成,当前形成的栈内存中,某些内容被栈内存以外的变量占用了,此时栈内存不能释放,即闭包

      2、全局栈内存只有在页面关闭的时候才会被释放掉
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      var i = 2;
      function fn() {
      i += 2; // i 为全局变量会有影响,值得注意
      return function (n) {
      console.log(n + (--i));
      }
      }
      var f=fn();// i=4
      f(2);//5 i=3
      f(3);//5 i= 2
      fn()(2);//5 i=3
      fn()(3);// 7 i=4
      f(4);//7 i=3

闭包

  • 函数执形成一个私有的作用域,保护里面的私有变量不受外界的干扰,这种保护机制称之为“闭包”
    一般大家认为,形成一个不销毁的私有作用域(私有栈内存)才是闭包
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    //=>闭包:柯理化函数
    function fn() {
    return function () {

    }
    }
    var f = fn();
    */

    /*
    //=>闭包:惰性函数
    var utils = (function () {
    return {

    }
    })();

闭包项目实战应用

  • 真实项目中为了保证JS的性能(堆栈内存的性能优化),应该尽可能的减少闭包的使用(不销毁的堆栈内存是耗性能的)

    • 闭包具有“保护”作用:保护私有变量不受外界的干扰。
    • 闭包具有“保存”作用:形成不销毁的栈内存,把一些值保存下来,方便后面的调取使用

    基于构造函数创建自定义类

    • 在普通函数执行的基础上“new xxx()”,这样就不是普通函数执行了,而是构造函数执行,当前的函数名称之为“类名”,接收的返回结果是当前类的一个实例
    • 自己创建的类名,最好第一个单词首字母大写
    • 这种构造函数设计模式执行,主要用于组件、类库、插件、框架等的封装,平时编写业务逻辑一般不这样处理
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
       function Fn() {

      }

      // Fn();//=>普通函数执行
      var f = new Fn();//=>Fn是类 f是类的一个实例
      var f2 = new Fn();//=>f2也是Fn的一个实例,f2和f是独立分开的,互不影响

      /*
      * JS中创建值有两种方式
      * 1.字面量表达式
      * 2.构造函数模式
      */
      // var obj = {};//=>字面量方式
      // var obj = new Object();//=>构造函数模式
      // //=>不管是哪一种方式创造出来的都是Object类的实例,而实例之间是独立分开的,所以 var xxx={} 这种模式就是JS中的单例模式

      //=>基本数据类型基于两种不同的模式创建出来的值是不一样的
      //> 基于字面量方式创建出来的值是基本类型值
      //> 基于构造函数创建出来的值是引用类型
      //->NUM2是数字类的实例,NUM1也是数字类的实例,它只是JS表达数字的方式之一,都可以使用数字类提供的属性和方法
      // var num1 = 12;
      // var num2 = new Number(12);
      // console.log(typeof num1);//=>"number"
      // console.log(typeof num2);//=>"object"
-------------本文结束感谢您的阅读-------------