《JavaScript高级程序设计》- 第四章:变量、作用域与内存

博客

zyzcos.gitee.io

第四章:变量、作用域与内存

4.1 原始值与引用值

ECMAScript变量包含两种不同类型的数据:原始值引用值;

分类组成访问方式
原始值Undefined、Null、Boolean、Number、String和Symbol六种简单数据按值访问【操作的就是存储的`实际值`】
引用值由多个值构成的对象按引用访问【操作的是该对象的引用,通过引用操作对象】
4.1.1 动态属性

对于原始值而言:不能拥有属性,虽然不会报错,但是无效。

  let person = 'zyzc';
  person.age = 20;
  console.log(person.age);  // undefined;

对于引用值而言:可以随时添加、修改和删除属性

  let person = new Object();
  person.age = 18;

原始值使用new来定义,可以拥有引用值的行为。

  let person = new String('zyzc');
  person.age = 18;
  console.log(person.age);  // 18
  console.log(typeof person); // Object
4.1.2 复制值

对于原始值而言:会被复制到新变量的位置

对于引用值而言:会将引用值从一个变量赋值给另一个变量,然后通过引用共享同一个对象。

  let obj1 = new Object();
  obj1.name = 'zyzc';

  let obj2 = obj1;  // 进行复制
  console.log(obj2.name); //'zyzc'
4.1.3 传递参数

在ECMAScript中,所有函数的传参都是按值传递的。【个人理解为复制副本】

对于原始值而言:

  let count = 20;
  function add(num){
    num +=10;
    return num;
  }
  let result = add(count);
  console.log(result);    // 30
  console.log(count);     // 20;

对于引用值而言:

  let personA = new Object();
  function setName(obj){
    obj.name = 'zyzc';
  }
  console.log(personA.name);  // zyzc

难道这个和C++那样,传的是指针吗?

  let personA = new Object();
  function setName(obj){
    obj.name = 'zyzc';
    obj = new Object();
    obj.name = 'hi';
  }
  console.log(personA.name);  // zyzc

显然,如果是传指针的话,personA的值也会跟着改变为’hi’;

4.1.4 确定类型

原始值:使用typeof可以确定类型

引用值:使用instanceof可以确定是什么类型的对象

4.2 执行上下文与作用域

4.2.1 什么是上下文

上下文决定了可以访问那些数据,可以表现什么行为;而每个上下文都关联着一个变量对象,该变量对象包括了上下文中定义的所有变量函数;【最外层的上下文是全局上下文,一般指的是window对象

4.2.2 上下文怎么工作?

每个函数,都有自己的上下文;当代码流入函数的时候,函数的上下文压到上下文栈中,当函数执行完毕之后,函数的上下文会弹出上下文栈

4.2.3 什么是作用域链?

上下文代码执行的时候,会创建变量对象作用域链;该作用域链决定各级上下文访问变量、函数的顺序。当前作用的上下文变量对象总会在作用域链最前端。而全局上下文总会在作用域链最后端

4.2.4 举个例子说明上下文与作用域链
  /*
        这里的上下文:全局上下文
        这里的作用域链:全局变量对象
        这里可以访问的变量:color
        这里可以访问的函数:changeColor
        这里不可以访问的变量:anotherColor、tempColor、anotherColor。因为作用域链上找不到。
    */
  var color = "blue";
  function changeColor() {
    let anotherColor = "red";
    /*
        这里的上下文:anotherColor上下文
        这里的作用域链:changeColor变量对象 ——> 全局变量对象
        这里可以访问的变量:color
        这里可以访问的函数:swapColors
        这里不可以访问的变量:tempColor、anotherColor。因为作用域链上找不到。
    */
    function swapColors(){
      /*
        这里的上下文:swapColors上下文
        这里的作用域链:swapColors变量对象 ——> changeColor变量对象 ——> 全局变量对象
        这里可以访问的变量:color、tempColor、anotherColor
      */
      let tempColor = anotherColor;   // 当使用到anotherColor变量时,在swapColors变量对象中寻找。若找不到,就顺着作用域链,向后找。直到找到或者未找到为止。
      anotherColor = color;
      color = tempColor;
    }

    swapColors();
  }

  changeColor();

寻找变量是顺着作用域链最前端【自身作用域的变量对象】最后端【全局作用域的变量对象】、由内层到外层地寻找的。

4.2.5 作用域链增强

某些语句会在作用域链前端临时添加一个上下文.

  • try/catch中的catch: 会创建一个新的变量对象,其中包含要抛出的错误对象的声明
  • with:会创建一个包含关联对象的变量对象

4.3 垃圾回收

JavaScript是使用垃圾回收的语言,即由执行环境负责管理内存、内存分配和闲置资源回收.

4.3.1 垃圾回收基本思路
  1. 确定那个变量不会使用
  2. 释放其占用的内存
  3. 周而复始的执行

显然,确定那个变量不会使用是一个不可判定的问题,所以不能使用算法来解决.只能每隔一段时间,执行一次垃圾回收。

4.3.2 垃圾回收的策略
  • 标记清理:当进行垃圾回收的时候,通过变量的标记,来判定是否需要销毁并回收内存.标记的方法有很多:

    1. 当变量进入上下文的时候,反转某一位;然后离开上下文的时候,再进行反转.从而达到标记的效果。
    2. 维护两个表:在上下文表、不在上下文表;
  • 引用计数:声明变量并赋值的时候,值的引用数为1;如果同一个值又被赋给另外一个变量,则引用数+1;若保存对该值的引用的变量被其他值覆盖,则引用数-1;当运行垃圾回收程序的时候,会将引用数为0的值清理

现在的浏览器采用的大多是标记清理,因为引用计数存在循环引用问题:

  function bugTest(){
    let objectA = new Object();
    let objectB = new Object();

    objectA.tail = objectB;
    objectB.head = objectA;
  }

虽然说两者相互引用确实不应该被清除,但是一旦代码不在bugTest的上下文中后,这两个对象还是会一直存在,不被清理。

4.4 性能与内存管理

4.4.1 什么会影响性能?
  • 如果内存中分配了很多变量,则可能会造成性能的损失。
  • 如果垃圾回收程序的执行时间太长,也可能会造成性能的损失。
4.4.2 内存管理

在JavaScript中,因为具有垃圾回收机制,所以开发者通常无需关心内存管理。

  1. 如果需要优化内存占用,最佳的手段就是:保证在执行代码的时候,只保存有必要的数据。如果数据不再必要,可以通过将其设置为null,从而释放引用,这也叫做解除引用

  2. 通过const和let声明提升性能:这两个关键字声明的变量,可能让垃圾回收程序尽早介入,并尽早释放并回收内存。

  3. 隐藏类和删除操作:V8引擎,为了追踪对象的属性特征,会创建隐藏类并与对象关联起来。其中,隐藏类会占用空间;

      function Animal(){
        this.name = 'dog';
      }
      // dog1 和 dog2 共享一个隐藏类
      let dog1 = new Animal();
      let dog2 = new Animal();  
    
      // 但是进行属性添加后,
      dog2.sex = 'male'; // dog1 和 dog2 关联的隐藏类就不一样了。从而增加了内存占用
    

    为了避免以上先创建再补充而导致的问题,应当在构造函数一次性声明所需属性;

      function Animal(sex){
        this.name = 'dog';
        this.sex = sex;
      }
      // dog1 和 dog2 共享一个隐藏类
      let dog1 = new Animal();
      let dog2 = new Animal("male"); 
    
      // 当不需要该对象的时候,应该这样操作
      dog2 = null;  // 解除引用
    
  4. 对象池与静态分配

    为了提升JavaScript的性能,最后只能压榨浏览器了。此中的关键就是:减少浏览器执行垃圾回收的次数,从而保住因释放内存而损失的性能。

    决定浏览器什么时候运行垃圾回收程序的一个标准就是:对象的更替速度,如果很多对象被初始化、又一下子超出作用域,则会调度垃圾回收程序。

    优化前:每次调用这个函数,都会创建一个新的Vector,若多次调用,则会导致对象的更替速度变快。

      function addVector(a,b) {
        let resultant = new Vector();
        resultant.x = a.x + b.x;
        resultant.y = a.y + b.y;
        return resultant;
      }
    

    优化后:当多次调用的时候,由于没有发生对象的初始化,垃圾回收探测就不会发现有对象更替

      function addVector(a,b,resultant) {
        resultant.x = a.x + b.x;
        resultant.y = a.y + b.y;
        return resultant;
      }
      // 创建一个对象池,用来管理可回收的对象。
      const vectorPool = {
        v1:new Object(),
        v2:new Object(),
        v3:new Object()
      };
    
      vectorPool.v1.x = 10;
      vectorPool.v1.y = 5;
    
      vectorPool.v2.x = 10;
      vectorPool.v2.y = 5;
    
      addVector(vectorPool.v1,vectorPool.v2,vectorPool.v3);
    
      console.log(vectorPool.v3.x + '' + vectorPool.v3.y);  // 20 10
    
      // 解除引用
      v1 = null;
      v2 = null;
      v3 = null;
    

除了使用对象池,也可以使用数组进行静态分配。

但是数组的大小是动态可变的,为了避免数组大小变化的时候,引来垃圾回收;所以要一开始就预定好够用的数组;

4.5 总结:

  1. 原始值引用值有如下特点:
  • 原始值,大小固定,存储在栈内存上。
  • 引用值是对象,存储在堆内存上。
  • 包含引用值得变量,实际是一个指向对象的指针
  • 引用值的复制,就是指针的复制
  • typeof确定原始类型;instanceof确定引用类型。
  1. 关于上下文(作用域)
  • 上下文决定变量的生命周期
  • 执行上下文分为:全局上下文函数上下文块级上下文
  • 代码流入一个新上下文,都会创建一个作用域链,用于搜索变量和函数
  • 作用域的搜索,是自内层到外层的,所以全局作用域只能访问全局上下文的变量和函数。
  • 变量的执行上下文,用于确定什么时候释放内存。
  1. 关于垃圾回收程序
  • 离开作用域的值,会被标记为可回收,等待垃圾回收程序执行期间被删除。
  • 主流的垃圾回收是标记清理
  • 某些旧版本IE仍会使用引用计数
  • 解除变量是一个很好的习惯。既可以消除循环引用,也可以帮助垃圾回收。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值