07月13, 2017

聊聊JS与内存(一)

最近在准备校招生的课程在重新撸红皮书啦,看到JS 类型的时候感觉有必要写点东西总结一下,嗯,于是就开始写了。。。。

ECMAScript标准类型

到目前为止,JS有7种标准类型: 其中6中基本类型:

  • Undefined
  • Null
  • Boolean
  • String
  • Number
  • Symbol

和一个引用类型Object

alt

通常我们检测基本类型通过typeof这个方法。

JS引用类型

在ECMScript中,引用类型是一种数据结构,用于将数据和功能组织在一起。


JS有7种引用类型: Object,Array,Date,RegExp,Boolean,Number,String

基本类型值 VS 引用类型值

将一个值赋值给变量,解析器必须确定这个值是基本类型值还是引用类型值。

基本类型值 引用类型值
简单的数据字段 可能由多个值构成的对象
栈存储 堆存储
按值访问 按引用访问

注:

  1. 与其他语言不同,JS不允许直接访问内存中的位置,也就是说不能直接操作对象的内存空间。操作对象时,其实是操作对象的引用。
  1. 引用类型值按引用访问在表达上的更正:当复制保存的对象的某个变量时,操作对象引用,当为对象添加属性时,操作对象本身。

说晕了吧?来,举例子就明白了!

var a = 20;
var b = 'abc';
var c = true;
var d = {m:20}

alt

可以看到,a b c变量的值为基本类型值,所以直接存储在栈内存中,变量大小由计算机来分配。

而d变量的值是引用类型值,所以我们可以看到在栈存储中d变量存储的并不是对象值本身,而是引用对象的地址。换言之,是将d变量的指针指向堆存储的对象。而堆存储变量的大小是由开发人员决定的。

所以当我们要获取对象值的时候,实际上我们要首先获取该对象的地址引用,然后再从堆内存中获取我们需要的数据。

变量复制与内存的关系

我们首先来看基本类型的复制:

var a = 20;
var b = a;
b = 30;

运行这样一段代码后,看下图,代码中每一句代表一张图:

alt

可以看到,当执行将a赋值给b时,其实计算机为b创建了新的存储空间,也就是说a b变量的存储是彼此独立的,所以在我们改变b的值时,a并不会受到影响。

接下来是引用类型的复制:

浅复制:

var m = {
    a:20,
    b:30
};
var n = m;
n.a = 15;
console.log(m.a)

猜猜看结果是什么?结果是15

我们来结合内存看看原理:

alt

看到图,是不是已经明白一半了。

当我们声明并赋值一个变量n时,会将n的指针指向堆内存中的某个对象,这个对象存储着我们希望赋给n的值,当进行简单的=赋值时,其实是将n变量的引用地址复制给m变量,所以可以看到n m变量指向了堆内存中的同一个对象,所以也就不难理解,为什么改变n的属性后,m也会发生改变。

深度复制

在实际应用中,往往我们需要m n是彼此独立的,换言之,希望他们引用不同的堆内存中的对象,这是就需要对对象进行深度复制,以达到这样的内存效果:

alt

可以看到,m n变量的引用地址发生了变化,也就是说我们为n开辟了新的堆内存空间,指向了新的对象,只不过对象的属性与m相同而已。

实现深度复制并不复杂,我们简单介绍一下:

1.JSON.parse(JSON.stringify(obj))

利用JSON的方式将对象序列化后再进行深复制

有没有发现Demo1近乎完美的解决了我们的问题,可是如果对象的属性值是个function会怎样?通过Demo2可以发现进行复制的时候并没有将function复制到n当中。

所以当序列化对象不符合json格式的时候,这个方法就会出问题了。

2.通过递归实现:

简单说一下原理,递归算法的根本在于什么时候停止递归,显然当当前的obj的类型不再是object类型时就返回值,剩下的就是根据当前传入的obj的类型进行判断和分别处理啦!

关于垃圾回收&内存优化

正如之前我们提到的,基本类型值会存储在栈中,由计算机来分配内存,所以也不难想象,在变量使用之后,也会由计算机进行回收。

但引用类型值是由开发人员人为开辟的内存,所以字段当对象不再被使用的时候,需要开发人自己释放内存,所谓解铃还须系铃人嘛。

好在javascript是一个用户体验很好的语言,自带垃圾回收机制,也就是说当变量不再被使用时,js会通过垃圾回收机制来为我们释放内存,不需要开发人员过多的关注内存的问题。

简单介绍一下垃圾回收机制的两种策略:

  1. 标记清除

标记清除是最常用的策略,简单来说就是:垃圾收集器在运行时会将存储在内存中的所有变量加上标记,然后将环境中的变量和被环境中变量引用的变量去掉标记。接下来所有被再次标记的变量就会视为准备删除的变量。

  1. 引用计数

引用计数的算法是跟踪记录每个值被引用的次数,当声明变量并赋值时,则这个值的引用次数就是1,当这个值被赋给另一个值时,引用次数-1。接下来垃圾收集器会回收所有标记为0的变量。

我们怎么结合垃圾回收机制进行内存优化呢?

这里我们更多的关注引用类型值的优化,是因为计算机本身无法对堆内存进行自动回收,所以,当我们创建一个对象后,不进行手动释放内存时,垃圾回收器会在一定时间后,替我们处理无用的对象所占用的内存

而如果我们执行很复杂的逻辑时,短时间内可能会创建很多个Object(比如js实现的动画),这时候在内存方面所做的优化就是我们可以在使用完对象后,手动解除引用,进而可以更好的帮助垃圾回收器进行快速回收:

var o = {a:10}
//解除引用
o = null

注意,我这里讲述的是如何优化内存,所以即使我们不去手动解除引用,其实垃圾回收器也是会帮我们处理的啦!锦上添花啦!你理解就好啦!!我讲完啦!!拜拜!

本文链接:https://www.imwineki.cn/post/jstype.html

-- EOF --

Comments

评论加载中...

注:如果长时间无法加载,请针对 disq.us | disquscdn.com | disqus.com 启用代理。