第八章 变量、作用域、内存

2019-02-01  本文已影响0人  尺间天涯

变量及作用域

​ JavaScript的变量是松散型(一个变量可以改变类型)。

  1. 基本类型和引用类型的值

    ECMA变量可能包含了两种不同类型的值:基本类型(栈内存),这种值完全保存在内存中的一个位置。引用类型(堆内存),这种值保存实际上是一个指针,也就是一个指针指向内存中的另一个位置,该位置保存对象。

    将一个值赋给变量的时候,解析器必须确定这个值的类型是基本类型,还是引用类型。基本类型有以下集中:Undefined、Boolean、Null、Number和String。这些类型再内存中分别占有固定大小的空间,他们的值保存在栈空间,我们通过按值访问。

    一些语言中字符串用对象表示,但是ECMAscript的字符串是基本类型。

    如果赋值是引用类型的值,则必须在堆内存中分配空间。由于这种值大小不确定,所以不能将其保存在栈内存中。但内存地址大小是固定的,因此可以将内存地址保存在栈内存中。当查询引用类型的变量时,先从栈中读取内存地址,然后通过地址找到堆内存的值。这种叫做按引用访问。

neicun.png
  1. 动态属性

    引用类型可以添加属性方法,但是基本类型不可以。

    let obj = {
        name: 'xiaoming'
    };
    console.log(obj.name); // xiaoming
    
    let str = 'xiaoming';
    str.age = 18;
    console.log(str.age); // undefined
    
  2. 复制变量值

    再变量赋值方面,基本类型和引用类型不同。基本类型复制的是值,引用类型复制的是地址。可就是说,复制的都是栈内存中的数据。

    基本类型

    不想画图,于是想到这样一个栗子来说他们不一样。

    let str1 = 'haha';
    let str2 = str1;
    console.log(str1 === str2);
    // true 基本类型 复制的是值。
    let str3 = 'haha';
    console.log(str1 === str3);
    //true 基本类型比较的是值
    
    let str1 = '2019';
    let str2 = str1;
    
    str1 = 'nihao';
    console.log(str1, str2);    // nihao 2019
    

    值类型复制结束后,改变一个变量,不会影响另外一个变量。

    引用类型
    let obj1 = {a: 1, b: 2};
    let obj2 = obj1;
    console.log(obj1 === obj2); // true
    // 引用类型地址不同,即便数据相同也不全等
    let obj3 = {a: 1, b: 2};
    console.log(obj1 === false); // true
    

    引用类型复制:

    let obj1 = {a: 1, b: 2};
    let obj2 = obj1;
    
    obj2.name = '2019';
    console.log(obj1);  // {a: 1, b: 2, name: "2019"}
    console.log(obj2);  // {a: 1, b: 2, name: "2019"}
    

    引用类型复制的是地址,他们其实都是一个变量,所以给一个变量增加属性,会影响另外一方。注意:但是重新赋值给这个变量,会指向新的地址,不会影响。

  3. 传递参数

    ECMAscritp中所有的函数的参数都是按值传递的,也就是说参数不会有按引用类型传递,虽然变量有基本类型和引用类型之分。

    function foo(num) {     // 按值传递
        num += 10;
        return num;
    }
    let num = 50;
    console.log(foo(num));  // 60
    console.log(num);
    // 50 如果是按引用传递,那么函数里的num会替换全局的num 也就是说上面应
    // 该是60
    

    以上代码传递的是基本类型,而函数num是一个局部变量,和外面的num没有任何关系。

    下面来一个参数作为引用类型的栗子:

    function foo(obj) {
        obj.name = 'xiaoming';  
        // 传递的是引用类型的参数 但是是按值传递的地址
    }
    let obj = {};
    
    foo(obj);
    console.log(obj.name);
    

    如果按引用传递的话,函数里的变量应该是全局变量,如果PHP参数加上&符号表示引用传递。而js函数内的变量都是独立作用域,都是局部变量。以上两段代码涉及到作用域的问题下面再说。可以大致理解为,js传参,都是在按值传递,所谓的按引用类型传递也是在传递保存有引用类型数据地址的值,所以按引用类型传递和按引用传递不是一回事,也没有按引用传递。

  4. 检测类型

    typeof方法 这里不再重复。

    通常我们并不想知道它是不是Object,因为Array、null、RegExp都是Object。那么我们如何检测类型呢?

    • instanceof

      基本类型用instanceof,会返回false。除非用new String()这种方法产生的基本类型,可以正常判断。

      console.log([1, 2, 3] instanceof Array);
      console.log(/^\w+/ instanceof RegExp);
      console.log({} instanceof Object);
      //以上都是true
      
      let str1 = 'hello';
      console.log(str1 instanceof String);    //false
       //字符串类型比较特别,它与基本类型相似,但是又是不可变引用类型。
      let str2 = new String('hello');
      console.log(str2 instanceof String);    //true
      
  5. 执行环境及作用域

    执行环境实JavaScript中的一个重要概念。执行环境定义了变量或函数有权访问的其他数据,决定了它们各自的行为。

    全局执行环境是最外围的执行环境。在web浏览器中,全局环境被认为是window对象。因此所有的全局变量和函数都是作为window对象的属性和方法创建。

    var num = 10;
    function foo() {
        console.log(num);           //全局变量即window的属性
        console.log(window.num);    // 函数可以访问全局作用域
    }
    window.foo();   //全局函数即window方法
    

    如果用const或者let定义变量,window.num为undefined。因为他们定义的变量不再window中,这点以后再看。

    当执行环境所有的代码执行完毕后,该环境被销毁,保存在其中的所有变量及函数也随之销毁。如果是全局环境下,需要程序执行完毕,或者网页被关闭才会销毁。

    每个执行环境都有一个与之关联的变量对象,就好比全局的window可以调用变量和属性一样。局部环境也有一个类似window的变量对象,环境中定义的所有变量和函数都保存在这个对象中。(我们无法访问这个变量对象,但是解析器会处理数据时后台使用它)。

    函数的局部作用域里的变量替换全局变量,但作用域仅限在函数体内这个局部环境。(函数参数也是局部变量)

    var num = 10;
    function foo() {
        var num = 20;
        console.log(num);   // 20局部
        console.log(window.num);    // 10全局
    }
    foo();
    
    var num = 10;
    function foo(num) {
        console.log(num);    // 20
    }
    foo(20);
    
  6. 没有块级作用域

    if语句没有块级作用域

    if(true) {
        var num = 10;
    }
    console.log(num); //10
    

    for语句没有块级作用域

    for(var i = 0; i < 10; i++) {
        var num = 20;
    }
    console.log(i); //10
    console.log(num);   //20
    

    函数中声明变量如果省略var是全局变量。(这种写法不会使用到)

  7. 变量会沿作用链向上查找

  8. 内存问题

    JavaScript具有自动垃圾回收机制,也就是说,执行环境会负责管理代码执行过程中使用的内存。开发人员不用关心内存使用问题,所需内存的分配以及无用内存的回收完全实现了自动管理。

    这种垃圾回收机制原理很简单:找出那些不再继续使用的变量,然后释放其占用的内存。为此,垃圾收集器会按照固定的时间间隔(或代码执行中预定的收集时间),周期性地执行这一操作。

    局部变量的正常生命周期

    局部变量只在函数执行的过程中存在。而在这个过程中,会为局部变量在栈(或堆)内存上分配相应的空间,以便存储它们的值。函数执行结束,局部变量就没有必要存在了,因此可以释放它们的内存。

    var obj = {
        name: 'Lee'
    };
    obj = null;          //删除对象引用,等待垃圾收集器回收
    
上一篇下一篇

猜你喜欢

热点阅读