在计算机科学中,数据存储的位置关系到代码执行过程当中数据的检索速度,有一个经典的问题即为:经过改变数据的存储位置来得到最佳的读写性能。前端
字面量
字面量只表明自身,不存储在特定的位置。JavaScript中的字面量有:字符串,数字,布尔值,对象,数组,函数,正则表达式,以及null&undefined。
字面量是用于表达源代码中一个固定值的表示法,例如:string str="hello world";
hello world为字面量正则表达式
本地变量
开发人员使用关键字var定义的数据存储单元编程
数组元素
存储在JavaScript数组对象内部,以数字做为索引,这里注意和本地变量的区别,
var arr = new Array();
arr为本地变量,arr[0]为一个数组元素数组
对象成员
储存在JavaScript对象内部,以字符串做为索引浏览器
每一种数据存储的位置都有不一样的读写消耗,通常而言:性能优化
从一个字面量中存取数据的性能约等于局部变量闭包
数组元素和对象成员成本较高,高出多少由浏览器决定函数
做用域概念是理解JavaScript的关键所在,不只仅从性能,还包括从功能的角度。做用域对JavaScript有不少影响,从肯定哪些变量能够被函数访问,到肯定
this
的赋值。性能
做用域链function
能够理解为一个“制造机器的机器”,那么咱们能够这样理解:每个JavaScript函数都是一个function对象的实例。
那么function
对象和其余对象同样,拥有能够编程访问的属性和一系列不一样经过代码访问而仅供JavaScript引擎存取的内部属性。优化
内部属性之Scope
先放一个Scope的有趣解释;
Scope属性包含了一个函数被建立时的做用域中的对象的集合,这个集合被称为做用域链,它决定哪些数据能被函数访问。
函数做用域中的每一个对象被称为一个可变对象,每一个可变对象都以“键值对”的形式存在。
当一个函数建立后,它的做用域链会被建立此函数的做用域中可访问的数据对象所填充。
我说下本身的理解:做用域、做用域链、内置属性(Scope)其实能够类比权限、管理组、全局管理员,做用域中的对象以键值对的形式存在,成为可变对象,做用域链用来链接做用域和Scope,而Scope就好像一种专门管理全局对象的全局管理员;
举一个例子:
咱们先建立一个函数:
function add(num1,num2){ var sun = num1 + num2; return sum; }
这里咱们建立了一个add()函数,当他被建立的时候,在这个函数的内置属性Scope所包含的做用域链中插入一个对象变量,这个全局对象表明着全部在全局范围内定义的变量。 改全局对象包含像window,navigator,document等;
当咱们来执行上面的函数又会发生什么呢?
假如执行以下代码:
var total = add(5,10);
此时函数会建立一个称为 执行环境 或者叫 执行上下文 (execution context)的内部对象。
一个execution context定义了一个函数执行时的环境。
函数每次执行时对应的execution context都是独一无二的,因此屡次调用同一个函数就会建立多个不同的execution context
当函数执行完毕,execution context就会被销毁
每一个execution context都有本身的做用域链,用于解析标识符。
当execution context被建立时,它的做用域链初始化为当前运行函数的Scope属性中的对象,这些值按照他们出如今函数中的顺序,被复制到执行环境的做用域链中。
前面这个过程完成以后,一个“活动对象”也为execution context建立好了,该对象做为函数运行时的变量对象,包含了全部的局部变量,参数集合以及this。
而后这个对象被推入做用域链最前端。
当execution context被销毁,活动对象也随之销毁。
咱们在执行过程当中是怎样使用做用域链的呢?
在函数执行过程当中,没遇到一个变量,都会经历一次标识符解析过程以决定从哪里获取或存储数据。标识符解析是性能开销的,即有代价的!解析标识符实际上就是搜索execution context的做用域链,来匹配同名的标识符。
搜索过程从做用域链头部开始,即做用域链中的数字越小越优先,这意味着,一个标识符所在的位置越深,它的读写速度越慢
函数中读写局部变量老是最快的,而读写全局变量老是最慢的
在查找过程当中,若是找到,就使用这个标识符对应的变量,若没找到,则继续查找下一个对象,若整个搜索过程都没有找到匹配的对象,那么这个标识符将被视为未定义的
正是这个搜索过程影响了性能
在没有优化JavaScript引擎的浏览器中,尽量使用局部变量,一个好的经验法则就是:若是某个跨做用域的值在函数中被引用一次以上,那么就把它存储在局部变量里。
咱们来看一个例子:
function initUI(){ var bd = document.body, links = document.getElementsByTagName("a"), i = 0, len = links.length; while(i<len){ update(links[i++]); } document.getElementById("btn").onclick = function(){ start(); }; bd.className = "active"; }
咱们看到上面这个函数用了三次document对象,而很不巧,他又是个全局变量,搜索document须要遍历整个做用域链,那么如今有一种解决方案来减小对性能的影响:先将全局变量的引用存储在一个局部变量里面,而后用这个局部变量来替代全局变量;
那么上述代码能够重写为:
function initUI(){ var doc = document, bd = doc.body, links = doc.getElementsByTagName("a"), i = 0, len = links.length; while(i<len){ update(links[i++]); } doc.getElementById("btn").onclick = function(){ start(); }; bd.className = "active"; }
咱们将访问document的次数由三次变成了一次,若是这个访问次数足够大的话,那么咱们的性能将获得极大的改善!
学到这里,我认为改善标识符的解析性能能够从提升解析速度和减小使用次数两方面入手,前者经过优化JavaScript引擎来进行,后者咱们在编程过程当中能够进行实践,二者的前提都是搜索可以正确进行!
通常来讲,一个execution context的做用域链是不会被改变的,可是在JavaScript中有两个语句是能够在执行时临时改变做用域链的,为动态做用域。
NO.1 With语句
With语句用来在做用域链中新建立一个变量对象,这个可变对象包含了参数指定的对象的全部属性。先看看With在编程中怎么使用:
function initUI(){ with (document){ var bd = body, links = getElementsByTagName("a"), i = 0, len = links.length; while(i<len){ update(links[i++]); } getElementById("btn").onclick = function(){ start(); }; bd.className = "active"; } }
从代码中能够很直观看到,它也只在全局对象中执行一次搜索,从而避免了屡次书写document,可是这样会更加高效吗?
咱们来看看执行with
语句时,做用域链中发生了什么:
当执行with
语句时,它的execution context被临时改变了,一个新的对量对象被建立,它包含了参数指定的对象的全部属性,而且这个对象被推入了做用域链的首位;
在上面的例子中,经过把document
对象传递给with
语句,一个包含了document对象全部属性的新的可变变量被置于做用域的头部,这样就出现了一个问题:我访问document对象的属性很是快,可是当我想访问活动对象(也就是局部变量)或者全局对象的属性时,个人解析标识符速度反而下降了,因此,在减小全局对象属性这方面的性能优化,将document储存在一个局部变量中比用with语句改变做用域链更加可靠!
NO.2 try-catch语句 try-catch
语句中的catch字句也具备临时改变做用域链的效果。
当try代码块中发生错误,执行过程会自动跳转到catch字句,而后将异常对象推入一个变量对象并置于做用域的首位,也就是说,在catch代码块内部,函数全部的局部变量都会放在第二个做用域链对象中,可是,一旦catch代码块执行完毕,做用域链就会返回到以前的状态。
try-catch 语句不该该被用来解决JavaScript错误,若是某个错误重现率很高,最好是尽快修复。其实做用域链的改变是发生在catch代码块执行的过程当中,那么咱们若是在catch代码块内没有对局部变量和全局变量的访问,就可使catch字句对性能的影响最小化!
这种思想的一种实现方法就是将错误委托给一个函数来处理!
闭包是JavaScript最强大的特性之一,它容许函数访问局部做用域以外的数据,可是闭包在使用过程种可能会致使性能问题。
咱们先来看一个闭包的例子:
function assignEvents(){ var id = "xdi9592"; document.getElementById("btn").onclick = function(){ saveDocument(id); }; }
assignEvents()函数给一个DOM元素设置事件处理函数,这个事件处理函数就是一个闭包,它在assignEvents()执行时建立,而且能够访问所属做用域的id变量。
如图所示,当assignEvents()函数执行时,一个包含了变量id以及其余数据的活动对象被建立,这个活动对象成为execution context做用域链中的第一个对象,紧接着就是全局对象,而后闭包被建立,而且它的Scope属性被初始化为这些对象。
至此,出现了第一个问题:内存问题。
通常来讲,一个函数执行完了以后,函数做用域链中的活动对象会随着execution context一块儿被销毁,可是引入了闭包以后,因为引用仍然存在于闭包的Scope属性中,因此此时活动对象无法被销毁,这意味着脚本中闭包与非闭包函数相比,须要更多的内存开销。
而后在闭包代码执行时,又会建立一个闭包的execution context,它的做用域链与自身Scope中所引用的两个相同的做用域链对象一块儿被初始化,而后建立一个闭包的活动对象,而且放在首位;
能够看到闭包内代码所用的id & savaDocument,他们的位置分列2,3,就里就是咱们在使用闭包过程当中所须要关注的性能点:在频繁地访问跨做用域的标识符的时候,每次访问都会带来性能损失。
最后划一下重点:将经常使用的跨做用域变量存储到局部变量中,而后直接经过局部变量来访问,是一个可行的方法。
--END--