关于javascript基本类型的思考(望指正)

532 阅读7分钟

基本类型和引用类型(上)

1. 在内存中存储位置不同

  • 原始类型(基础类型): 占用空间固定(以后值都不会发生变化的意思),保存在栈中.(栈: 缺乏灵活性,因为存储的数据大小和生存期必须是确定的)
  • 对象(引用类型):占用空间不固定(以后你可以给一个对象增加或者减少属性)。(堆:保存在堆中,存取速度比较慢)

原始类型和对象在堆栈存储

Tips: 缓存:cpu一级缓存 > 二级缓存 > 三级缓存 > 内存 > 硬盘

栈(stack)为自动分配的内存空间,它由系统自动释放;使用一级缓存,被调用时通常处于存储空间中,调用后立即释放。(后进先出,压栈处理) 堆(heap) 则是动态分配内存,大小不一定亦不会自动释放。使用二级缓存,生命周期与虚拟机的GC算法有关

1. 栈内存

当一个方法执行时,每个方法都会建立自己的内存栈,将方法定义的变量放入这块内存栈中,随着方法的结束,这个方法的内存栈也将自然销毁。 栈中存储的是 基础变量和一些对象的引用变量,基础变脸的值存储在栈中,指向堆中的对象或数组的地址存储在栈中 所以修改引用类型,就会影响到指向这个地址的引用变量

栈的使用规则

var a = 3;z执行时会在栈中创建一个变量为a引用,然后再栈中查找是否有3这个值,如果没有找到那就将3当进来,接着处理var b=3,创建b,搜索栈中是否有3这个值,发现有就将3给b a===b===3,因为a和b指针都指向 3 ,如果a=4,如果没有就将4存起来,如果有了就将a指向这个存储地址

2. 堆内存

当我们创建一个对象的时候,这个对象江北保存到运行时数据区中,以便反复利用(创建对象的成本通常较大),这个运行数据区就是堆内存。堆内存中的对象不会随方法的结束而销毁,即使 方法结束后,这个对象还可能被另一个引用变量所引用(方法参数传递的时候就很常见),只有一个对象没有任何引用变量引用他时,系统的垃圾回收机制才会在核实的时候回收他

堆的使用规则

当创建数组时,就会在堆内存中创建一个数组对象,并且在栈内存中创建一个对数组的引用。对象里面的基本类型都存放在栈中。如果数组里面元素是原始类型,如果改变数组的值还是在改变栈中的值 堆存储的对象,不需要程序代码的显示释放的,堆是由自动的垃圾回收来负责的,** 每种浏览器js的解释引擎有不同的自动回收方式** 但都追寻一个基本原则: 如果栈中不存在对堆中的某个对象 的引用,那就认为该对象已经不再需要,在垃圾回收时就会请除该对象占用的内存空间。 如果不需要 的对象,应该将引用释放掉,以利于垃圾回收,提高程序的性能。释放对象的引用最常用的方法是将其赋值为null

*** 这里来个插曲 ***

介绍几个垃圾回收机制

  • 离开作用的值将被自动标记为可以回收
  • ‘标签清除’是目前主流的垃圾收集算法,是给当前不用的值加上标记,然后再回收其内存
  • 引用计数 方法是记录所有被引用的值,但是对于代码中循环引用时,算法就会导致一直无法释放内存,主流引擎不使用
  • 解除变量引用不仅可以消除循环引用现象,而且可有及时进行垃圾回收。一般的方法将变量设为null

2.将原始类型和对象拆开来看

javaScript 中 ‘值’ 可以分为两大类:原始类型和对象

原始类型

  • boolean -> true 和 false
  • null -> 用type of检验null数据类型时为Object,但它不是对象,这是JS的一个bug(在 JS 的最初版本中使用的是 32 位系统,为了性能考虑使用低位存储变量的类型信息,000 开头代表是对象然而 null 表示为全零,所以将它错误的判断为 object 。)
  • undefined (变量初始化的时候都会初始化为undefined)
  • number JavaScript中的所有数字都是浮点数,没有整数
  • string
  • symbol(es6)符号类型 可以作为对象的值
  • bigint 也是个基础数值类型,可以表示任意进度的整数,可以安全的存储和操作大整数

对象

  1. 原始值包装类型: Boolean、Number、String
  2. 普通对向 方法 正则 日期对象等等

临时包装(Auto-Boxing) string基础类型值得特点

思考问题为什么字符串有length属性 和 字符串对象的相关方法,而且原始字符串上还能调用constructor方法

var pet = "dog";
console.log(pet.length); // 3
pet === "dog"; // true

当调用length的时候发生了一件 Auto-Boxing的过程 '1'.toString() 过程是怎么样的呢

  • 创建String实例
  • 调用实例方法
  • 执行完方法立即销毁这个实例

number是浮点数,问题 类似 0.1 + 0.2 不等于 0.3 关于计算损失精度问题

因为js数字运算都是先转化二进制算完之后转化为十进制, 0.1 转化为二进制

0.0 0011 0011 0011 0011 0011 0011 ... (0011循环) 0.2 转化为二进制

0.0011 0011 0011 0011 0011 0011 0011 ... (0011循环)

3.赋值,浅拷贝,深拷贝

基本类型,赋值,浅拷贝,深拷贝时都是复制基本类型的值给新的变量,之后另个变量不相互影响,原始类型在栈中使用也可以看得出来原因 对于对象:

  • 赋值后两个变量指向同一个地址,一个变量改变时,另一个也同样改变
  • 浅拷贝得到一个新的变量,这个与之前的已经不是指向同一个变量,改变时不会使原数据中的原始类型一同改变,但会改变元数据中引用类型数据
  • 深拷贝 复制一个对象给另一个变量

数组浅拷贝:slice(),concat(),Array.from(),以及es6的解构 对象常用的浅拷贝方法Object.assign(),es6解构 也可以自己实现一个浅拷贝

var obj = { a:1, arr: [2,3] };
  var shallowObj = shallowCopy(obj);
  var shallowCopy = function(obj) {
      // 只拷贝对象
      if (typeof obj !== 'object') return;
      // 根据obj的类型判断是新建一个数组还是对象
      var newObj = obj instanceof Array ? [] : {};
      // 遍历obj,并且判断是obj的属性才拷贝
      for (var key in obj) {
          if (obj.hasOwnProperty(key)) {
              newObj[key] = obj[key];
          }
      }
      return newObj;
  }
  ```
  深拷贝:JSON.parse(JSON.stringify(obj))满足90%开发场景,数组可以使用map filter
>undefined、任意的函数以及 symbol 值,在序列化过程中会被忽略(出现在非数组对象的属性值中时)或者被转换成 null(出现在数组中时)。
自己实现

```js
  var deepCopy = function(obj) {
  if (typeof obj !== 'object') return;
  var newObj = obj instanceof Array ? [] : {};
  for (var key in obj) {
      if (obj.hasOwnProperty(key)) {
          newObj[key] = typeof obj[key] === 'object' ? deepCopy(obj[key]) : obj[key];
      }
  }
  return newObj;
  }

参数的传递

函数的参数传递都是按值传递,原始类型传的是变量的值,对象的传递是自己在栈内存中的指针值。

判断方法

基本类型使用 typeof 引用类型用 instanceof

特别注意 typeof null 为 ‘object’ null instanceOf Object 是 true

为什么说函数是特殊对象

  1. 因为函数可以被调用
  2. js借鉴语言有了schema语言的特点,函数是一等公民。 a. 将一个函数赋值给一个变量 (函数表达式) b. 还可以将函数作为一个参数传递给另一个函数(函数编程) c. 可以使一个函数返回另外一个函数(形成闭包的重要因素)

还要不要讲,我还可以来个半小时的.......