再谈JS——JS性能提高之解除引用

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/AngelLover2017/article/details/85269637

前言

 其实在V8引擎下,JavaScript的性能已经得到大幅度提高,这里探讨的是在理论层面,具体一点就是JS的垃圾回收机制,可以提高Javascript性能的一种途径或者方式,也算是一篇读后总结吧,这里参考了《JS高程第三版》的第四章有关JS内存管理的讲解。

正文

 我们先来看看,如何做可以在理论上提高JS的性能,我们来看两段代码:

代码段A

function createPerson(name){     
	var localPerson = new Object();     
	localPerson.name = name;     
	return localPerson; 
} 
 
var globalPerson = createPerson("Nicholas"); 

代码段B

function createPerson(name){     
	var localPerson = new Object();     
	localPerson.name = name;     
	return localPerson; 
} 
 
var globalPerson = createPerson("Nicholas"); 
 
// 手工解除 globalPerson 的引用 
 
globalPerson = null; 

 我们看到代码片A和代码片B的唯一区别就在于,代码B在最后将globalPerson变量置为了null,这里是手工解除globalPerson的引用,以让变量指向的内存空间脱离执行环境,也就是在全局环境下不再需要globalPerson变量,这样在JS下一次的自动回收中可以立即回收释放对应的内存,我们下面来探讨一下Javascript的内存管理,以方便理解这里的手动解除引用的意思

Javascript的内存管理

 在讲解Javascript的内存自动回收机制前,我们先要对Javascript中一个重要的概念做一个前提性的了解,那就是执行环境和作用域

作用域链

 不同于Java,C这类语言的块作用域概念,在Javascript中是没有块作用域概念的,我们知道在Java,C中在{}内的变量是局部变量,在作用域外无法访问;当然在Javascript中肯定是有全局和局部变量之分的,要不然是不可能写出来JS函数的(会出现变量被污染的严重问题),那Javascript中是如何做到作用域的控制以及全局与局部变量的区分的呢?
 答案是通过变量对象的作用域链,变量对象是Javascript执行环境中在程序运行时维护的一个不可调用的变量,这个变量保存着执行环境中所有的变量、函数,而每一个变量对象,在程序代码执行时会同时生成对应于变量对象的作用域链,这个作用域链的目的就是保证在执行环境内变量和函数可以按序访问

执行环境(execution context,为简单起见,有时也称为“环境”)是 JavaScript中最为重要的一个概 念。执行环境定义了变量或函数有权访问的其他数据,决定了它们各自的行为。每个执行环境都有一个 与之关联的变量对象(variable object),环境中定义的所有变量和函数都保存在这个对象中。虽然我们 编写的代码无法访问这个对象,但解析器在处理数据时会在后台使用它。

 那么作用域链是如何做到区分全局和局部变量的呢?我们还以上面的代码为例(我们暂时不管解除引用):

function createPerson(name){     
 var localPerson = new Object();     
 localPerson.name = name;     
 return localPerson; 
} 
 
var globalPerson = createPerson("Nicholas"); 

这段代码,会生成怎样的作用域链呢?如下图
在这里插入图片描述

我们的程序运行时,如何识别变量就是通过从作用域前端向后不断回溯搜索的过程,当在作用域链上找到一个符合名称的变量则搜索会立即停止,并返回找到的变量。这里,window是浏览器内置的最外围的执行环境中的对象,这是作用域的最后端,而作用域的前端则是程序运行时需要搜索作用域链的地方。比如,我们浏览器解释JS时执行到了createPerson函数中的localPerson.name = name;,此时需要回溯作用域链里找到localPerson变量的定义,于是在作用域链上找到了localPerson,解释器将停止搜索并返回这个定义,于是这里的localPerson就会被解释为createPerson函数内的局部变量,我们对代码进行一个小的修改:

function createPerson(name){     
 localPerson = new Object();     
 localPerson.name = name;     
 return localPerson; 
}
var globalPerson = createPerson("Nicholas"); 

去掉var后,localPerson会被定义为一个全局执行环境中的变量,此时搜索作用域链会在全局作用域链中搜索,并返回全局变量的定义

由于JS这种作用域链搜索的特性,导致了一些会令人迷惑的作用域范围,比如下面的代码:

var a = true;
if(a){
var b = “这是一个全局执行环境下的变量定义”;
}
console.log(b) //打印 “这是一个全局执行环境下的变量定义”

这里和Java,C中不同,JS中变量的作用域范围不是依据{},而是依据执行环境中的作用域链

垃圾自动回收机制

 这一点有点像Java中的垃圾回收,程序员不用密切关注内存的使用,而更关注与使用语言本身。JS中的垃圾回收机制是基于我们上面说的作用域链的,其实实现的方式也很简单,就是不断循环的去搜索作用域链释放掉已经不使用或者消亡的变量所占用的内存。这里就产生了一个问题,如何知道执行环境中的变量不再使用或者已经消亡?浏览器的实验一般有两种方式:标记清除引用计数 。我们这里简单介绍一下这两种方式。

标记清除

这种方式是通过一个标识,当变量或函数被定义时,将标记记为"进入环境",理论上讲进入环境的变量,只要在执行流里就不能被销毁,因为在执行流的任何地方都又可能使用到变量;同理,当变量已经离开执行流时,将标记记为"离开环境";这里只是标记,变量所占用内存并没有立即回收,而是要等下一次循环时,才能将标记为"离开环境"的变量的内存回收

这里也启发我们,不要滥用全局变量,因为这对JS回收内存不友好

引用计数

引用计数相对巧妙些,这个是对引用类型的值而言的,我们知道在JS中引用类型的值的复制是引用复制,复制的是地址,比如:

var objectA = new Object();
var objectB = objectA;
objectA.name = "angellover";
console.log(objectB.name);//输出 ”angellover“

这里的objectAobjectB指向的是同一个堆内存,所以当改变objectAobjectB也会被改变。
而这里的引用计数,就是说当引用类型的值被指向某个变量时,引用计数加一;同理,当引用类型的值与某个变量不再关联时,则引用计数减一;这样,当引用计数再次达到0时,就会被认为此内存不再被需要,在下次垃圾回收循环中会被释放

现在浏览器中的实现方法主要是标记清除,因为引用计数的方法不能解决循环引用的问题,循环引用这里不再讲解,只要知道有这样的问题就好

为什么解除引用可以提高JS性能

 通过上面的讲解,其实你应该已经知道了答案,globalPerson = null;是为了断开变量与内存之间的联系,这样当下一次垃圾回收循环中就会释放掉;这里globalPerson是全局执行环境中的变量,如果不进行解除引用,则在整个执行流中都不会释放掉对应的内存,如果JS文件不大的话还好,当JS文件很大的时候性能的问题就会体现出来

对于全局变量,当不再使用时,使用null赋值是一个良好的习惯

另外,在浏览器中内存是比较珍贵的资源,对于操作系统而言,分配给桌面应用程序的内存一般比浏览器要高,这是因为害怕浏览器随着视窗增多,过大占用内存导致一些其他问题,所以考虑如何提高JS性能还是一件值得探讨的问题

猜你喜欢

转载自blog.csdn.net/AngelLover2017/article/details/85269637