ecma-262 中的定义:Array 对象是一种特殊对象,它会对数组索引属性键进行特殊处理。 每个 Array 对象都有一个不可配置的 length 属性,其最大值是 2³² - 1。
当且仅当使用一个参数调用 Array 构造函数时,此描述才适用。
执行过程:
1.定义 numberOfArgs 为传递给此函数的调用的实参数量; 2.断言:numberOfArgs 为1; 3.如果 NewTarget 为 undefined ,就设置 newTarget 为 活动函数对象 ,并且让 newTarget 成为 NewTarget ; 4.原型 proto 通过原生方法来构造; 5.然后定义array 为 ArrayCreate(0,proto)
; 6.如果 len 的类型不是个Number,则: a.定义 defineStatus 为 CreateDataProperty(array, "0", len); b.断言:defineStatus为真; c.让 intLen(初始化长度) 为 1。
7.或者: a.定义 intLen 为 ToUint32(len)
b.如果 intLen 不等于len,抛出RangeError异常。
8.执行Set(array, "length", intLen, true)(原生方法,给对象的属性赋值); 9.返回array。
当且仅当使用至少两个参数调用Array构造函数时,此描述才适用。
执行过程:
1.定义 numberOfArgs 为传递给此函数的调用的实参数量; 2.断言: numberOfArgs 大于等于2; 3.如果 NewTarget 为 undefined ,就设置 newTarget 为 活动函数对象 ,并且让 newTarget 成为 NewTarget ; 4.原型 proto 通过原生方法来构造; 5.然后定义array 为 ArrayCreate(numberOfArgs,proto)
; 6.定义 k 为 0; 7.定义 items为 正序传入参数的 零源(zero-origined) 列表; 8.重复,当 k 小于 numberOfArgs a.定义 Pk 为 ToSting(k); b.定义 itemK 为 item[k]; c.定义 defineStatus 为CreateDataProperty(array, Pk, itemK); d.断言:defineStatus为真; e.k 加1。 9.断言: array的 length 值为 numberOfArgs; 10.返回 array。
以上便是构造Array()时不同情况的具体实现。但是我们从上面的断言可以知道,构造结果有可能为真,有可能为假。还有是定义指定长度数组时会出现什么事呢?
在 V8源码 中 Array 有个 CloneElementAt 的方法。定义如下:
在指定索引处克隆元素时,如果克隆失败,则返回一个空句柄(任何原因)。
从个定义可知,当我们构造一个指定长度的 Array 时,由于有长度,所以会开辟相应下标的空间,但是因为该下标并没有元素,所以就会返回 empty,任何原因构造数组元素失败时,都会返回一个 empty。
示例如下:
var arr = new Array(10);arr // [empty × 10]
上面是 ECMA 上的定义以及 V8 源码的容错处理,其实简单来说就是:
调用 Array(args)
时:
proto
;args
的类型;undefined
,则直接返回创建数组的原生方法 ArrayCreate
;number
,则用原生方法 Set
创建 args
长度的数组,并通过原生方法 CloneElementAt
来创建 args 个empty 作为数组元素,如果 args
大于 2³² - 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
对象转换为布尔值的规则如下表:
返回 false
。
对象转换为数字的规则如下表:
返回 NaN
。
对象转换为字符串的规则如下表:
返回 "undefined"
。
所以通过上面的转换规则,我们是否能够轻松地看懂以下的隐式转换呢?
[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 的言论,其实以前的浏览器的确是这种情况。
现如今(2019)结果又会是如何呢?
以下代码测试环境为:Chrome 73.0.3683 / Windows 10 0.0.0
现代的浏览器性能优化已经做得比以前好很多了,再加上电子设备本身的硬件也越来越好,所以代码块的性能不是我们首要的考虑因素。我们应该优化的是我们表达式是否清晰明了,是否适合后期维护或拓展。