- 做者:陈大鱼头
- github: KRISACHAN
ecma-262中的定义:Array对象是一种特殊对象,它会对数组索引属性键进行特殊处理。javascript
每一个Array对象都有一个不可配置的length属性,其最大值是232 - 1。html
当且仅当不带参数调用Array构造函数时,此描述才适用。
执行过程:java
GetPrototypeFromConstructor(newTarget, "%ArrayPrototype%")
来构造;ArrayCreate(0, proto)
。鱼头注:NewTarget是啥?NewTarget是原生Class FunctionCallbackInfo(函数调用的callback上下文的信息)内的一个不变量,用来定义构造调用时的返回值(new.target)。node
当且仅当使用一个参数调用Array构造函数时,此描述才适用。
执行过程:git
GetPrototypeFromConstructor(newTarget, "%ArrayPrototype%")
来构造;ArrayCreate(0, proto)
;若是 len 的类型不是个Number,则:github
或者:面试
当且仅当使用至少两个参数调用Array构造函数时,此描述才适用。
执行过程:算法
GetPrototypeFromConstructor(newTarget, "%ArrayPrototype%")
来构造;ArrayCreate(numberOfArgs, proto)
;重复,当 k 小于 numberOfArgs数组
上面的三种状况以上即是构造Array()时不一样状况的具体实现。可是咱们从上面的断言能够知道,构造结果有可能为真,有可能为假。还有是定义指定长度数组时会出现什么事呢?
在V8源码 3.28.71(node0.12.18)中 Array 有个CloneElementAt的方法。定义以下:浏览器
在指定索引处克隆元素时,若是克隆失败,则返回一个空句柄(任何缘由)。
从这句话咱们能够知道,当咱们构造一个指定长度的 Array 时,因为有长度,因此会开辟相应下标的空间,可是由于该下标并无元素,因此就会返回empty,任何缘由构造数组元素失败时,都会返回一个empty。
示例以下:
var arr = new Array(10); arr // [empty × 10]
上面是 ECMA 上的定义以及 V8 源码的容错处理,其实简单来讲就是:
调用 Array(args)
时:
GetPrototypeFromConstructor
生成原型 proto
;args
的类型;undefined
,则直接返回建立数组的原生方法 ArrayCreate
;number
,则用原生方法 Set
建立 args
长度的数组,并经过原生方法 CloneElementAt
来建立 args 个 empty 做为数组元素,若是args
大于 232 - 1 的话,会报错;args
变成数组元素,并用 原生方法 CreateDataProperty
建立参数,而后返回建立数组的原生方法 ArrayCreate
。类型转换是一个常常出如今一些网上常见面试题或者奇技淫巧中的内容。那么关于数组的类型转换,又是怎样的呢?
首先咱们要知道,在 JS 中类型转换只有三种状况,分别是:
对象在转换类型的时候,会执行原生方法ToPrimitive。
其算法以下:
toSting
方法,若是此时是 原始类型 则直接返回,不然再调用valueOf
方法并返回结果;valueOf
方法,若是此时是 原始类型 则直接返回,不然再调用toString
方法并返回结果;固然,咱们能够经过重写Symbol.toPrimitive
来制定转换规则,此方法在转原始类型时调用优先级最高。
// 下面例子来自YCK的小册 const data = { valueOf () { return 1; }, toString () { return '1'; }, [Symbol.toPrimitive]() { return 2; } }; data + 1 // 3
对象转换为布尔值的规则以下表:
参数类型 | 结果 |
---|---|
Undefined | 返回 false 。 |
Null | 返回 false 。 |
Boolean | 返回 当前参数。 |
Number | 若是参数为+0 、-0 或NaN ,则返回 false ;其余状况则返回 true 。 |
String | 若是参数为空字符串,则返回 false ;不然返回 true 。 |
Symbol | 返回 true 。 |
Object | 返回 true 。 |
对象转换为数字的规则以下表:
参数类型 | 结果 |
---|---|
Undefined | 返回 NaN 。 |
Null | Return +0. |
Boolean | 若是参数为 true ,则返回 1 ;false 则返回 +0 。 |
Number | 返回当前参数。 |
String | 先调用 ToPrimitive,再调用 ToNumber,而后返回结果。 |
Symbol | 抛出 TypeError 错误。 |
Object | 先调用 ToPrimitive,再调用 ToNumber,而后返回结果。 |
对象转换为字符串的规则以下表:
参数类型 | 结果 |
---|---|
Undefined | 返回 "undefined" 。 |
Null | 返回 "null" 。 |
Boolean | 若是参数为 true ,则返回 "true" ;不然返回 "false" 。 |
Number | 调用 NumberToString,而后返回结果。 |
String | 返回 当前参数。 |
Symbol | 抛出 TypeError 错误。 |
Object | 先调用 ToPrimitive,再调用 ToString,而后返回结果。 |
因此经过上面的转换规则,咱们是否可以轻松地看懂如下的隐式转换呢?
[1,2,3] + {a: 1, b: 2} // "1,2,3[object Object]" [1,2,3] + 1 // "1,2,31" [1,2,3] + true // "1,2,3true" [1,2,3] + undefined // "1,2,3undefined" [1,2,3] + null // "1,2,3null" [1,2,3] + '123' // "1,2,3123" [1,2,3] + Symbol('biu') // "Uncaught TypeError"
因此各位是否理解上述隐式转换的答案呢?
JS数组自带了不少的方法,在现代工程化数据驱动的理念下,这些方法都是很是重要的。
forEach
是 Array 方法中比较基本的一个,做用也很简单,与for
,就是遍历,循环。不一样的是,forEach
能够选择自定义上下文环境。例子以下:
var arr1 = [1, 2, 3]; var arr2 = [5, 6, 7]; arr1.forEach(function (e, i, a) { console.log(e, this); // this为arr2 }, arr2);
可是若是forEach
的回调函数是用箭头函数定义的,那么就没法改变它本来指向的上下文环境。例子以下:
var arr1 = [1, 2, 3]; var arr2 = [5, 6, 7]; arr1.forEach((e, i, a) => { console.log(e, this); // this为window对象 }, arr2);
因此若是咱们要实现如下这个功能:
<!-- 点击当前li时,当前li文字变色,其他兄弟li变回默认颜色 --> <ul> <li class="1">1</li> <li class="2">2</li> <li class="3">3</li> <li class="4">4</li> <li class="5">5</li> </ul> <script> 'use strict'; var ul = document.querySelector('ul'); ul.onClick = event => { var cls = event.target.className; ul.querySelectorAll('li').forEach(el => { el.style.color = (cls === el.className ? '#FFF' : '#FF0'); }); }; </script>
在ES6之前的环境中,若是直接用for
循环,会出现只能获取到最后一个元素的问题,可是用forEach
则没有这个问题。
Array ES5 API reduce
跟reduceRight
,能够轻松实现并归元素的功能,例子以下:
若是咱们须要实现一个这样的对象:
{ a: 1, b: 2, c: 3 ... };
那么用reduce就会变得很简单:
var newArr = 'a,b,c,d,e,f'.split(',').reduce((acc, cur, idx) => { let o = {}; if (Object.prototype.toString.call(acc) !== '[object Object]') { o[cur] = idx; } else { let newO = {}; newO[cur] = idx; o = { ...acc, ...newO, }; }; return o; }, 'a');
上面演示了经过JS数组API实现的一些功能,因此与for
循环比性能如何呢?
var arr = new Array(100); arr.forEach(data => { console.log(data); }); for (var i = 0; i < arr.length; ++i) { console.log(arr[i]); };
因此哪一个更耗时间呢?
在公布结果以前,其实网上一直流传着for循环性能比forEach性能好,考虑性能少用forEach的言论,其实之前的浏览器也是这种状况。
详情能够看知乎的这篇评论:https://www.zhihu.com/question/54637225/answer/140362071
性能对好比下:
如下代码测试环境为:Chrome 55.0.2883 / Windows 7 0.0.0
因此在9012年的现在,结果又会是如何呢?
如下代码测试环境为:Chrome 73.0.3683 / Windows 10 0.0.0
经过上面的对比,结果已经很明显了,咱们要知道,现代的浏览器性能优化已经作得比之前好不少了,再加上电子设备自己的硬件也愈来愈好,因此代码块的性能不是咱们首要的考虑因素。
在跟同行沟通的过程当中,常常会看到有人为了扣那么一个两个表达式的性能而烦恼,实际上是这是没有任何须要,缘由也如上,咱们应该优化的是咱们表达式是否清晰明了,是否适合后期维护或拓展。
若是你喜欢探讨技术,或者对本文有任何的意见或建议,很是欢迎加鱼头微信好友一块儿探讨,固然,鱼头也很是但愿能跟你一块儿聊生活,聊爱好,谈天说地。
鱼头的微信号是:krisChans95
也能够扫码添加好友,备注“SF”就行