- 浏览: 965045 次
- 性别:
- 来自: 上海
文章分类
最新评论
-
sscsacdsadcsd:
mike8625 写道react还要自己的一些标签 还得编译 ...
对于React体系的一点想法 -
mike8625:
说的都是给大公司听的,国内很多还是小公司,做个小项目, 说实话 ...
关于国内前端和JS技术发展的乱想 -
mike8625:
react还要自己的一些标签 还得编译 编译吧浏览器端说还慢 ...
对于React体系的一点想法 -
u012814086:
下意识想到了Golang
JavaScript语句后应该加分号么? -
xueduanyang:
我是水羊,年轻的时候觉得只要有好斧子就能做成好产品,各种产品都 ...
关于国内前端和JS技术发展的乱想
今天看了微软JScript官方blog上去年的两篇文章:
http://blogs.msdn.com/jscript/archive/2008/03/25/performance-optimization-of-arrays-part-i.aspx
http://blogs.msdn.com/jscript/archive/2008/04/08/performance-optimization-of-arrays-part-ii.aspx
讲的是IE8对Array性能的改进,其中也解释了过去JScript对Array的内部实现及导致的性能问题。
我狗狗了一下,似乎没有中文文章介绍过,所以我就写一篇来稍作介绍——不过并非翻译——微软的人总不好意思自己骂自己——而我素来要痛打落水狗的。
首先,目前JScript中数组操作是很慢滴,有多慢呢?我们引用一下JScript团队第一篇blog的数据:
此段代码,IE7用了7秒,而IE8仅仅用时18毫秒。
如果你看了第一篇blog中所解释的原因,请注意
——其实它在忽悠你,真正的原因在第二篇里。
我这里有有个更加简单的测试代码:
你可以改变SIZE和count的值来观察。
基本上,pop()方法或者任何导致length减小的操作,都是非常耗费的——大体上与数组实际包含元素个数成正比。
这才是真正的性能杀手。
而且这一操作所要遍历的元素甚至包括非正整数索引(而只有索引为uint32才会被认为是数组元素):
可以发现结果也是基本与元素个数成正比。但是比前面要快一些。原因后面解释。
之所以有这样的结果,是因为length减小时,引擎不仅仅是改变了length的值,而且要删除所有索引大于等于新的length的元素。
本来这件事情可以这样做(我相信大多数程序员的第一感觉也就是这样做):
如果确实这样做的话,性能绝不会如此糟糕(不信你可以写个自己的MyArray来试验一下——用JS写出来的居然比built-in的C实现还要快)。
但是,JScript团队的程序员,据说认为JS的Array其实是稀疏数组,或者说就是一个hash——理论上也确实如此,所以采用了大概是这样的算法(以JS来示意):
其中toUint32(s)将一个32位无符号整数的十进制字符串表达转为数字,如果转换失败则返回-1。【注意,此toUint32与parseInt不同,传入'0123'、'123.00'、'+123'、'1e4'等都会转换失败。】
这段代码遍历所有的key(因为Array本质上是把数字索引转换为字符串索引来保存的),如果这个key可以被转回无符号整数(也就是数字索引),则看看它是否大于指定的长度,大于的话就删掉。
好了,现在我们就不难理解为什么性能会与元素个数成正比了。假如一个50000个元素的数组,pop()了一下之后,居然也要遍历50000个元素!如果是在一个循环中不断pop()可想而知会有多慢。
那么为什么我们第二个例子会快一些呢?
这是因为我们的字符串索引是字母"i"开头的,而toUint32的实现,应该会一个字符一个字符的解析,当第一个字母是i时已然可知转换失败,直接返回了。以下是也以JS示意:
你也可以把前面第二段代码中的:
arr['i' + i] = i;
换为
arr[i + '00000000x'] = i
结果就会发现耗费时间更长了,因为toUint32必须解析到最后一个字符才知道转换失败。
【注:这里可以优化,因为uint32十进制最长是10个字符,所以可以先判断string的长度,超过10直接失败——问题是从测试结果来看,JScript实际的实现并没有做此优化】
那么JScript的开发者为什么要采用这样一个算法呢?
其实,如果从稀疏数组的角度看,这个算法是可行的,而且在某些条件下性能很好,比如对于以下:
var a = new Array(100000)
a.length = 10
在这次对length的改变中,上述算法飞快的完成,因为这个数组没并有什么实际元素可遍历!
所以JScript的开发者或许是特意做了这样的优化算法!!
然而讽刺的是,这本质上是一个极其愚蠢的优化!!
通常我们在用Array的时候,很少拿它当稀疏数组用!因为如果我们确实要用一个稀疏数组,我们完全可以直接用普通对象(即hash)。
就算我们经常使用稀疏数组——诚然,在一个稀疏数组时,前面所描述的最显白的算法,可能并不高效,但是稍加分析就可以看出,那样的算法要出现性能问题,只能是如下形式的代码:
其中每个arrays[i]都是稀疏数组。
这样的代码我是没有见过。
对于单个稀疏数组来说,要出现性能问题,除非你这样:
然而几乎没有人会写这样的代码
——而且 array.length=0 这句造成性能问题的语句根本是多余的。
归根到底,既然是一个稀疏数组,则通常不会进行遍历,更没有理由执行 array.length = 0 之类的语句,如果要抛弃一个数组,直接 array = null 即可。
相反,普通(非稀疏)的数组,就是用来遍历的。因此很容易写出这样的代码:
或者
所以我所能得出的结论是,JScript的Array.length的算法实现实在非常愚蠢。幼稚程度堪比我们诟病的白痴的垃圾回收策略。
当然,比起垃圾到极点的垃圾回收算法给我们造成的麻烦,这个length的问题还是可以有workaround的,不必干等IE8那样复杂到狂出bug(见第一篇blog下面的comments)的设计。比如IE8 beta1的platform performance improvements白皮书里就提到,有人自己造轮子:
一个用js代码自造的轮子,居然比c写的built-in函数跑的还要快,真是让人汗颜。
另外一个更简便的方式,就是记住这个简单的最佳实践:
少去改length!特别不要在循环中做pop()或length--之类的操作。如果要减,等到循环结束,一次性减到底就好了!
BTW,我们也要注意shift()、unshift()、splice()之类的操作,因为这些操作可能会改变大量数组元素的索引——按照稀疏数组的实现,显然会比单单改length更慢上许多!如果你真的需要shift()、unshift(),那还是自制一个类似上面的轮子吧(提示:上面代码的start属性就是为此准备的)。
http://blogs.msdn.com/jscript/archive/2008/03/25/performance-optimization-of-arrays-part-i.aspx
http://blogs.msdn.com/jscript/archive/2008/04/08/performance-optimization-of-arrays-part-ii.aspx
讲的是IE8对Array性能的改进,其中也解释了过去JScript对Array的内部实现及导致的性能问题。
我狗狗了一下,似乎没有中文文章介绍过,所以我就写一篇来稍作介绍——不过并非翻译——微软的人总不好意思自己骂自己——而我素来要痛打落水狗的。
首先,目前JScript中数组操作是很慢滴,有多慢呢?我们引用一下JScript团队第一篇blog的数据:
var arrObj = new Array(); var count = 10000; var before, after; for(var i = 0; i < count; i ++) { arrObj.push(i); } before = new Date(); for(var i = 0; i < count; i ++) { arrObj.pop(); } after = new Date(); alert(after- before);
此段代码,IE7用了7秒,而IE8仅仅用时18毫秒。
如果你看了第一篇blog中所解释的原因,请注意
——其实它在忽悠你,真正的原因在第二篇里。
我这里有有个更加简单的测试代码:
var SIZE = 10000, count = 10000; var arr = new Array(SIZE + count); for(var i = 0; i < SIZE; i++) { arr[i] = i; // 注意后文将替换本语句来观察效果 } var start = new Date(); for(var i = 0; i < count; i++) { arr.length-- } var end = new Date(); alert(end - start);
你可以改变SIZE和count的值来观察。
基本上,pop()方法或者任何导致length减小的操作,都是非常耗费的——大体上与数组实际包含元素个数成正比。
这才是真正的性能杀手。
而且这一操作所要遍历的元素甚至包括非正整数索引(而只有索引为uint32才会被认为是数组元素):
var SIZE = 10000, count = 10000; var arr = new Array(SIZE + count); for(var i = 0; i < SIZE; i++) { arr['i' + i] = i; } var start = new Date(); for(var i = 0; i < count; i++) { arr.length-- } var end = new Date(); alert(end - start);
可以发现结果也是基本与元素个数成正比。但是比前面要快一些。原因后面解释。
之所以有这样的结果,是因为length减小时,引擎不仅仅是改变了length的值,而且要删除所有索引大于等于新的length的元素。
本来这件事情可以这样做(我相信大多数程序员的第一感觉也就是这样做):
function get_length() { return this._length } function set_length(len) { if (len < 0 || len > 0xffffffff) throw RangeError() if (len > this._length) { this._length = len; return } while (len < this._length) { delete this[--this._length] } }
如果确实这样做的话,性能绝不会如此糟糕(不信你可以写个自己的MyArray来试验一下——用JS写出来的居然比built-in的C实现还要快)。
但是,JScript团队的程序员,据说认为JS的Array其实是稀疏数组,或者说就是一个hash——理论上也确实如此,所以采用了大概是这样的算法(以JS来示意):
function get_length() { return this._length } function set_length(len) { if (len < 0 || len > 0xffffffff) throw RangeError() if (len < this._length) { for (var k in this) { var i = toUint32(k) if (i >= len) delete(this[k]) } } this._length = len }
其中toUint32(s)将一个32位无符号整数的十进制字符串表达转为数字,如果转换失败则返回-1。【注意,此toUint32与parseInt不同,传入'0123'、'123.00'、'+123'、'1e4'等都会转换失败。】
这段代码遍历所有的key(因为Array本质上是把数字索引转换为字符串索引来保存的),如果这个key可以被转回无符号整数(也就是数字索引),则看看它是否大于指定的长度,大于的话就删掉。
好了,现在我们就不难理解为什么性能会与元素个数成正比了。假如一个50000个元素的数组,pop()了一下之后,居然也要遍历50000个元素!如果是在一个循环中不断pop()可想而知会有多慢。
那么为什么我们第二个例子会快一些呢?
这是因为我们的字符串索引是字母"i"开头的,而toUint32的实现,应该会一个字符一个字符的解析,当第一个字母是i时已然可知转换失败,直接返回了。以下是也以JS示意:
function toUint32(s) { if (s == '') return -1 if (s.charCodeAt(0) == 48 && s.length > 1) return -1 var n = 0, d for (var i = 0; i < s.length; i++) { n *= 10 d = s.charCodeAt(i) - 48 if (d < 0 || d > 9) return -1 n += d if (n >= 0xffffffff) return -1 } return n }
你也可以把前面第二段代码中的:
arr['i' + i] = i;
换为
arr[i + '00000000x'] = i
结果就会发现耗费时间更长了,因为toUint32必须解析到最后一个字符才知道转换失败。
【注:这里可以优化,因为uint32十进制最长是10个字符,所以可以先判断string的长度,超过10直接失败——问题是从测试结果来看,JScript实际的实现并没有做此优化】
那么JScript的开发者为什么要采用这样一个算法呢?
其实,如果从稀疏数组的角度看,这个算法是可行的,而且在某些条件下性能很好,比如对于以下:
var a = new Array(100000)
a.length = 10
在这次对length的改变中,上述算法飞快的完成,因为这个数组没并有什么实际元素可遍历!
所以JScript的开发者或许是特意做了这样的优化算法!!
然而讽刺的是,这本质上是一个极其愚蠢的优化!!
通常我们在用Array的时候,很少拿它当稀疏数组用!因为如果我们确实要用一个稀疏数组,我们完全可以直接用普通对象(即hash)。
就算我们经常使用稀疏数组——诚然,在一个稀疏数组时,前面所描述的最显白的算法,可能并不高效,但是稍加分析就可以看出,那样的算法要出现性能问题,只能是如下形式的代码:
for(var i=0;i<arrays.length;i++) { arrays[i].length = 0 }
其中每个arrays[i]都是稀疏数组。
这样的代码我是没有见过。
对于单个稀疏数组来说,要出现性能问题,除非你这样:
for(var i=0;i<count;i++) { array = new Array(50000) ... array.length = 0 }
然而几乎没有人会写这样的代码
——而且 array.length=0 这句造成性能问题的语句根本是多余的。
归根到底,既然是一个稀疏数组,则通常不会进行遍历,更没有理由执行 array.length = 0 之类的语句,如果要抛弃一个数组,直接 array = null 即可。
相反,普通(非稀疏)的数组,就是用来遍历的。因此很容易写出这样的代码:
while (array.length > 0) { var e = array.pop() // do sth for e }
或者
while (array.length > 0) { var e = array[array.length - 1] // do sth for e array.length-- }
所以我所能得出的结论是,JScript的Array.length的算法实现实在非常愚蠢。幼稚程度堪比我们诟病的白痴的垃圾回收策略。
当然,比起垃圾到极点的垃圾回收算法给我们造成的麻烦,这个length的问题还是可以有workaround的,不必干等IE8那样复杂到狂出bug(见第一篇blog下面的comments)的设计。比如IE8 beta1的platform performance improvements白皮书里就提到,有人自己造轮子:
//user defined array type called 'prototypeArray' var prototypeArray = function() { this._array = []; this.length = 0; this.start = 0; this.end = 0; } //push method for user defined array type 'prototypeArray' prototypeArray.prototype.push = function() { var l = arguments.length; for (var i=0;i<l;i++){ this.length++; this._array[this.end++] = arguments[i]; } } //pop method for user defined array type 'prototypeArray' prototypeArray.prototype.pop = function() { var obj = null; if (this.length>0){ this.length--; obj = this._array[--this.end]; delete this._array[this.end]; } return obj; } //creating an object of user defined array type 'prototypeArray' var myArray = new prototypeArray(); //accessing push and pop methods of user defined array type 'prototypeArray' myArray.push("Test String") myArray.pop();
一个用js代码自造的轮子,居然比c写的built-in函数跑的还要快,真是让人汗颜。
另外一个更简便的方式,就是记住这个简单的最佳实践:
少去改length!特别不要在循环中做pop()或length--之类的操作。如果要减,等到循环结束,一次性减到底就好了!
BTW,我们也要注意shift()、unshift()、splice()之类的操作,因为这些操作可能会改变大量数组元素的索引——按照稀疏数组的实现,显然会比单单改length更慢上许多!如果你真的需要shift()、unshift(),那还是自制一个类似上面的轮子吧(提示:上面代码的start属性就是为此准备的)。
发表评论
-
论ES6模块系统的静态解析
2013-03-14 04:56 15952本文是Dave Herman的《Stati ... -
如何创建一个JavaScript裸对象
2012-08-27 02:11 8120所谓裸对象,即 naked object ,是指没有原型(sp ... -
JavaScript语句后应该加分号么?
2012-06-19 03:10 14474这是一个老生常谈的问 ... -
shim是应该抛异常还是应该fail silently?
2011-08-11 17:26 5672玉伯发布了es5-safe模块 ... -
7月30日的广州演讲视频和Slides
2011-08-01 23:38 32007月30日在W3CTech广州站活动上的演讲,题目是:ECMA ... -
如何判断一个函数的意图是被用作构造器,也就是可视为“类”
2011-07-21 13:55 2975前提是不要求做什么特殊标记。只是最大可能的猜测函数的作用大概是 ... -
关于国内前端和JS技术发展的乱想
2011-07-19 18:53 33355玉伯在我的一条微博后面写了一些(和主题不是很相关但)非常值得思 ... -
Module与Trait的比较
2011-08-12 12:50 4056最近我多次提及module和trait。 粗看,我们可以发现 ... -
如何将let结构(block scope)转换到当前的JavaScript代码
2011-07-12 17:24 3034本文是对如何将let结构转换到ES3代码的补充。 首先,原文 ... -
JavaScript的未来方向之观察
2011-07-12 02:53 8385最近每次去杭州,都有 ... -
我为什么力挺NodeJS
2011-07-04 00:27 0之前在参加CNodeJS社区在 ... -
JS之父再谈JS历史(续完)
2010-12-31 04:20 3460又到年底,我觉得是时候还债了。自开blog来,我出了不少“太监 ... -
我为什么是DC黑续,兼答Tin
2010-04-27 14:29 0我同意安全是一个重要问题。我不同意的是把所谓安全放到凌驾于其他 ... -
我为什么是DC黑─Why I disagree with Douglas Crockford
2010-04-26 17:51 11050参加完了QCon北京大会, ... -
写对正则:一行代码,速度差50倍
2009-05-12 03:43 60232009-05-11 A lesson of ... -
JavaScript的EOS(分号)问题
2009-05-08 16:24 5793在http://bbs.51js.com/viewthre ... -
JavaScript五大关键字
2009-05-06 17:53 4778近期做语法高亮项目的副产品,是统计了一下几个主流JS工具包中各 ... -
curry和partial的差别
2009-03-28 00:15 372551js上asfman翻译了http://ejohn.org/ ... -
Eval is Evil , With evil too
2009-03-27 18:39 0with的问题: 2009-3-27 17:12:40 ... -
IE全局变量的Dissociative Identity Disorder(人格分裂症)
2009-03-16 02:47 14749最近,小麦提出了一个疑惑: 小麦 写道最后介绍一个我也搞不明白 ...
相关推荐
JScript提供了一系列内置对象,如Date对象用于处理日期和时间,Math对象包含数学常量和函数,Array对象用于处理数组,String对象则处理字符串操作。此外,还有全局函数,如eval()用于执行字符串作为JavaScript代码,...
对象则用于封装数据和方法,JScript支持内置对象(如Math、Date、Array等)和自定义对象。理解函数的声明、调用以及对象的创建和使用是进阶学习的关键。 3. **事件处理** 在Web开发中,JScript通过监听和处理用户...
2. **内置对象**:JScript提供了多个内置对象,如Math对象用于数学运算,Date对象处理日期和时间,Array对象处理数组,String对象处理字符串,Error对象用于异常处理。 3. **函数和方法**:例如,eval()函数可以...
**JScript详解** JScript,全称为Microsoft JScript,是由微软公司开发的一种脚本语言,主要应用于Web开发,与JavaScript...了解和掌握JScript的基本知识,对于理解早期Web技术以及处理遗留的IE相关问题具有重要意义。
1. **ECMAScript 5 支持**:JScript 8.0对ECMAScript 5规范有了更全面的支持,包括新增的函数、对象和语法特性,如`Array.prototype.forEach`、`Object.create`、`JSON.parse/.stringify`等。 2. **闭包与作用域**...
基本数据类型包括数字(Number)、字符串(String)、布尔值(Boolean)等,还有引用类型如对象(Object)和数组(Array)。 2. **函数和方法**:函数是可重复使用的代码块,可以接收参数并返回值。JScript.NET中的...
2. **对象和原型**:JScript支持面向对象编程,包括内置对象(如Array、Date、Math等)、自定义对象以及原型链的概念,这有助于理解对象间的继承关系。 3. **事件处理**:JScript常用于网页交互,手册会详细介绍...
- **内置对象**:包括全局对象、Math 对象、Date 对象、String 对象、Array 对象等,提供了丰富的数学计算、日期操作、字符串处理和数组管理功能。 - **函数对象**:函数在 JScript 8.0 中是第一类对象,可以作为...
2. **函数和对象**:讲解了如何定义和调用函数,以及内置对象如Array、Date、Math等的使用方法。 3. **JScript的特殊特性**:比如ActiveXObject,这是JScript特有的,用于在脚本中创建和操控COM组件,使得JScript...
此外,JScript还提供了丰富的内置对象,如Date、Math、Array等,用于处理日期、数学计算和数组操作。 函数是JScript中的核心组成部分,它们可以封装代码并重复使用。函数表达式和函数声明是两种定义函数的方式,...
2. **函数与对象**:掌握如何创建和使用函数,以及理解JavaScript的核心对象,如Date对象、Math对象和Array对象。 3. **DOM操作**:通过JScript与文档对象模型(DOM)交互,可以动态地改变网页内容,实现页面元素的...
JScript 8.0 包含了丰富的内置函数,如Math对象提供的数学函数(如sqrt、random),String对象的方法(如substring、indexOf、replace),以及Array对象的方法(如push、pop、slice)。此外,还有DOM操作函数,如...
14. 性能优化:理解JScript的运行机制,避免全局变量、减少DOM操作和使用缓存等方法提升程序性能。 通过阅读和研究《JScript技术手册》,开发者可以系统地学习JScript的各个方面,熟练掌握其语法和特性,为ASP开发...
JScript也可以在服务器端运行,通过Node.js环境,可以创建高性能的网络应用和服务。 9. **与其他技术的交互** JScript可以与CSS(样式表)和HTML(结构语言)紧密结合,共同构建网页。同时,它也能通过JSON...
2. **函数与对象**:如何定义和调用函数,以及内置对象(如Math、Date、Array等)的使用。 3. **DOM操作**:通过JScript操纵HTML文档对象模型,包括元素的选择、添加、删除和属性修改。 4. **事件处理**:绑定和...
通过研究JScript源代码,我们可以深入了解引擎如何实现垃圾回收机制、函数作用域、原型链、异步编程模型如回调、Promise和async/await,以及如何处理各种内置对象如Array、Date和RegExp。JScript的源码对于理解V8或...
4. **数组和集合**:Array对象的方法,如push、pop、shift、unshift、slice、splice等,以及Map、Set等ES6新增集合类型。 5. **字符串处理**:字符串的方法,如concat、indexOf、substring、replace、split等。 6....
该方法在 JScript 中的行为与 ES3 中相同,但在某些情况下可能进行了优化以提高性能。 ##### 18. 逻辑或运算符(Disjunction): §15.10.2.3 JScript 支持 ES3 中定义的逻辑或运算符,并可能引入了更高效的求值...
处理NodeList时还需注意的是,每次页面DOM结构发生变化时,NodeList都会重新计算其元素,这可能会导致性能问题。因此,在处理大量DOM操作时,将NodeList转换成数组可以提高效率,因为我们只需要计算一次长度。 在...