内存管理篇
1.Chrome浏览器的V8 JavaScript引擎
在初始化对象的时候,V8引擎会创建一个隐藏类,随后在程序运行过程中每次增减属性,就会创建一个新的隐藏类或者查找之前已经创建好的隐藏类。每个隐藏类都会记录对应属性在内存中的偏移量,从而在后续再次调用的时候能更快地定位到其位置。
比如如下代码:
function Article(){ this.title='Inauguration Ceremony Features Kazoo Band' ; } let a1=new Article(); let a2=new Article();
V8会在后台配置,让这两个类实例共享相同的隐藏类,因为这两个实例共享的同一个构造函数和原型。假设之后又添加了下面这行代码:
a2.author='Jake';
此时两个Article实例就会对应两个不同的隐藏类。根据这种操作的频率和隐藏类的大小,这有可能对性能产生明显影响。
当然,解决方案就是避免JavaScript的“先创建再补充”式的动态属性赋值,并在构造函数中一次性声明所有的属性,如下图所示:
function Article(opt_author){ this.title=''Inauguration Ceremony Features Kazoo Band''; this.author=opt_author; } let a1=new Article(); let a2=new Article('Jake');
这样,两个实例基本上就一样了(不考虑hasOwnProperty的返回值),因此可以共享一个隐藏类,从而带来潜在的性能提升。不过要记住,使用delete关键字会导致生成相同的隐藏类片段。看一下这个例子:
function Article(){ this.title='Inauguration Ceremony Features Kazoo Band'; this.author='Jake'' } let a1=new Article(); let a2=new Article(); delete a1.author;
代码结束之后,即使两个实例使用了同一个构造函数,它们也不再共享同一个隐藏类。动态删除属性与动态添加属性导致的后果一样。最佳实践是把不想要的属性设置为null。这样可以保持隐藏类不变和继续共享,同时也能达到删除引用值供垃圾回收程序回收的效果。比如:
function Article(){ this.titile='Inauguiration Ceremony Features Kazoo Band ' this.author='Jake'; ' } let a1=new Article(); let a2=new Article(); a1.author=null;
2.内存泄漏
1)意外声明全局变量
function setName(){ name='Jake'; }
2)定时器操作造成的内存泄漏
let name='Jake'; setInterval(()=>{console.log(name)},100);
只要定时器一直运行,回调函数中引用的name就会一直占用内存。
3)闭包造成的内存泄漏
let outer =Function(){ let name='Jake'; return function(){ return name; } }
这回导致分配给name的内存被泄漏。以上代码创建了一个内部闭包,只要outer函数存在就不能清理name,因为闭包一直再引用着它。假如name的内容很大,那就可能是个大问题。
3.静态分配与对象池
浏览器判断何时进行垃圾回收程序的一个标准就是对象更替的速度。如果又很多对象被初始化,然后一下子又都超出了作用域,那么浏览器就会采用更激进的方式调度垃圾回收程序运行,这样会影响性能。比如下方例子:
functio addVector(a,b){ let resulttant=new Vector(); resultant.x=a.x+b.x; resultant.y=a.y+b.y; return resultant; }
调用这个歌函数会在堆上创建一个新对象,然后修改它,最后再返给调用者。如果这个矢量对象的生命周期很短,那么它很快就会失去对它的引用,成为可以被回收的值。假如这个矢量加法函数频繁被调用,那么垃圾回收调度程序会发现这里对象更替的速度很快,从而会更频繁地安排垃圾回收。
该问题的解决方法是不要动态创建矢量对象,比如可以修改上面的函数让他使用一个已有的矢量对象:
functio addVector(a,b,resultant){ resultant.x=a.x+b.x; resultant.y=a.y+b.y; return resultant; }
不过这个行为是在其他地方实例化矢量参数resultant,但这个函数的行为没有变。那么在哪里创建矢量可以不让垃圾回收调度程序盯上呢?
其中一种策略是使用对象池,在初始化的某一时刻,可以创建一个对象池,用来管理一组可以回收的对象。应用程序可以向这个对象池请求一个对象、设置其属性、使用它。然后再操作完成后再把它还给对象池。由于没有发生对象初始化,垃圾回收探测就不会发现有对象更替,,因此垃圾回收程序就不会那么频繁地运行,下面是一个对象池的伪实现:
//vectorPool是已有的对象池 let v1=vectorPool.allocate(); let v2=vectorPool.allocate(); let v3=vectorPool.allocate(); v1.x=10; v1.y=5; v2.x=-3; v2.y=-6; addVector(v1,v2,v3); console.log([v3.x,v3.y]); //[7,-1] vectorPool.free(v1); vectorPool.free(v2); vectorPool.free(v3); //如果对象有属性引用了其他对象 //则这里也需要把这些属性设置为null v1=null; v2=null; v3=null;
如果对象池只按需分配矢量 (在对象不存在时创建新的,对象存在时候则复用存在的),那么这个实现本质上是一个贪婪算法,有单调增长但为静态的内存。这个对象池必须使用某种结构维护所有对象,数组是比较好的选择。不过,使用数组来实现,必须留意不要招致额外的垃圾回收。比如下面例子:
let vectorList=new Array(100); let vector=new Vector(); vectorList.push(vector);
原创文章,作者:kepupublish,如若转载,请注明出处:https://blog.ytso.com/268306.html