基本类型和引用类型(上)
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 也是个基础数值类型,可以表示任意进度的整数,可以安全的存储和操作大整数
对象
- 原始值包装类型: Boolean、Number、String
- 普通对向 方法 正则 日期对象等等
临时包装(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
为什么说函数是特殊对象
- 因为函数可以被调用
- js借鉴语言有了schema语言的特点,函数是一等公民。 a. 将一个函数赋值给一个变量 (函数表达式) b. 还可以将函数作为一个参数传递给另一个函数(函数编程) c. 可以使一个函数返回另外一个函数(形成闭包的重要因素)
还要不要讲,我还可以来个半小时的.......