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

302 阅读4分钟

变量、作用域与内存

1.1、原始值与引用值

Javascript不允许直接访问内存位置,不能直接操作对象所在的内存空间,这样操作的对象一般就是引用,而非对象本身。

1.2、动态属性

非null对象都可以动态添加属性。

   let o = new Object();
   let s = new String();
   o.name = "xxx";
   s.age = 19;
   console.log(o.name);// xxx
   console.log(s.age);// 19

1.3、复制值

原始值赋值到另一个变量,则原始值被复制到新变量的位置。两变量独立的,互不干扰。

引用值赋值给另一个变量,则复制的值只是一个指针,指向存储在堆内存中的对象,两变量实际上指向的都是同一个对象。

这跟java一样,也是存储在堆内存。

1.4、参数传递

ECMAScript中所有函数的参数都是按值传递,跟java不同。

      function setName(obj){
        obj.name = "NiNa"
        obj = new Object();
        obj.name = "rora";
      }
      let person = new Object();
      setName(person);
      console.log(person.name);

以上这个例子你说按引用传递也没有问题,因为obj = new Object();就是一个局部对象。

1.5、分不清的typeof 和instanceof

typeof主要用来判断一个变量是否为原始类型,因为typeof对于null或者对象都是object。

instanceof 主要用来判断具体是什么类型的对象。

      function setName(obj) {}
      console.log(typeof setName); // function
      console.log(setName instanceof Function);// true

二 执行上下文与作用域

使用var定义的全局变量和函数都会成为window对象的属性和方法。

      function setName(obj) {
        console.log("test")
      }
      window.setName(null); // test
      var a = "xxxx";
      console.log(window.a);// xxxx

作用域链增强:比如with语句和try/catch语句的catch块,kotlin的with估计就是抄袭这里的with吧,语法糖是一样的。

with语句中声明的var 变量会成为函数上下文的一部分,可以作为函数值被返回。

       function buildUrl(){
         let a = "xx";
         with(a){
            var b = a + "yy"
         }
         return b;
       }
       console.log(buildUrl()); // xxyy

var 可以声明多次,let、const不允许声明多次。

如果一个变量未经声明就被初始化了则会自动添加到全局上下文。小心这种很容易导致内存泄露!!!

        function test() {
            let a = "xx";
            b = a+"yy";
            return b;
        }
        console.log(test());// xxyy
        console.log(b);// xxyy
        console.log(window.b);// xxyy

常量const 用于Object的话,想让Object的成员也不支持修改,可以使用Object.freeze({});

        const o3 = Object.freeze({
            "name":"aaa"
        });
        o3.name = 'bb';
        console.log(o3.name); // aa

三 垃圾回收

javascript使用垃圾回收的语言,是由执行环境在负责代码执行时管理内存。跟java类似。

判断一个对象是否是垃圾:可达性分析和引用计数(循环引用hold不住)。

标记清除:最常用, 内存碎片太多就用标记整理。(怎么判断一个对象是垃圾,就跟java里面一样用GCroot 链条,对象不可达就是垃圾了,可以被清除。)

标记整理、复制算法、分代收集:(新生代 --复制算法,老年代--标记清除和标记整理)

并发标记-清除算法:并发垃圾回收算法,用于减少垃圾回收对应用程序的影响。它通过在应用程序运行时并发执行标记和清除阶段来实现。

3.1、内存优化细节

3.1.1、通过const和let声明,块作用域,可能更早的让垃圾回收程序介入。
3.1.2、V8 javascript引擎的隐藏类和删除操作

隐藏类就是V8在将解释后的javascript代码编译为实际的机器码时会利用“隐藏类”,如果多个实例对象共享一个隐藏类,则算优化,不共享则会创建多个隐藏类。

不共享的场景

a、动态添加属性(其实可以直接在构造函数就预添加属性)

b、动态删除属性。(其实直接置null该属性,不要调用delete obj.name即可);

3.1.3 内存泄露
  1. 意外的全局声明,就是没有用var、let、const等修饰的变量,就自动搞成window的属性了。

  2. 变量捕捉 (kt也有这种)

function outerFunction() {
  let outerVar = 10;
  function innerFunction() {
    console.log(outerVar); // 内部函数捕捉了外部函数的 outerVar
  }
  return innerFunction;
}
const capturedFunction = outerFunction();
capturedFunction(); // 输出 10

你能想到capturedFunction只要存在,这个outerVar如果是超大字符串的话,那么内存就一直占用着。

3.1.4 静态分配与对象池

静态分配 就是指定size的数组保存一定数量的对象。但是javascript中的数组size是可以动态改变的,这个跟java不一样呢。

对象池复用对象,如果对象占用内存大的话,频繁创建是要避免的,此时可以用对象池。