美文网首页
第八章 变量、作用域、内存

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

作者: 尺间天涯 | 来源:发表于2019-02-01 20:16 被阅读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;          //删除对象引用,等待垃圾收集器回收
      

    相关文章

      网友评论

          本文标题:第八章 变量、作用域、内存

          本文链接:https://www.haomeiwen.com/subject/kgadsqtx.html