JS数组中那些你知道或不知道的
栏目: JavaScript · 发布时间: 5年前
内容简介:当且仅当不带参数调用Array构造函数时,此描述才适用。执行过程:鱼头注:NewTarget是啥?NewTarget是原生Class FunctionCallbackInfo(函数调用的callback上下文的信息)内的一个不变量,用来定义构造调用时的返回值(new.target)
当且仅当不带参数调用Array构造函数时,此描述才适用。
执行过程:
- 定义 numberOfArgs 传递给此函数的调用的实参数量;
- 断言: numberOfArgs` 为 0;
- 如果 NewTarget 为 undefined ,就设置 newTarget 为 活动函数对象(active-function-object,正在运行的执行上下文的函数组件) ,并且让 newTarget 成为 NewTarget ;
- 原型 proto 怎么办?通过原生方法
GetPrototypeFromConstructor(newTarget, "%ArrayPrototype%")
来构造; - 返回原生方法
ArrayCreate(0, proto)
。
鱼头注:NewTarget是啥?NewTarget是原生Class FunctionCallbackInfo(函数调用的callback上下文的信息)内的一个不变量,用来定义构造调用时的返回值(new.target)
Array(len)
当且仅当使用一个参数调用Array构造函数时,此描述才适用。
执行过程:
- 定义 numberOfArgs 为传递给此函数的调用的实参数量;
- 断言: numberOfArgs 为1;
- 如果 NewTarget 为 undefined ,就设置 newTarget 为 活动函数对象 ,并且让 newTarget 成为 NewTarget ;
- 原型 proto 怎么办?通过原生方法
GetPrototypeFromConstructor(newTarget, "%ArrayPrototype%")
来构造; - 然后定义 array 为
ArrayCreate(0, proto)
; - 如果 len 的类型不是个 Number ,则:
- 定义 defineStatus 为 CreateDataProperty(array, "0", len) ;
- 断言: defineStatus 为真;
- 让**intLen(初始化长度)**为1。
- 或者:
- 定义 intLen 为 ToUint32(len)(原生方法,将len转换成0到2 32 -1之间的整数值) ;
- 如果 intLen 不等于 len ,抛出 RangeError 异常。
- 执行 Set(array, "length", intLen, true)(原生方法,给对象的属性赋值) ;
- 返回 array 。
Array(...items)
当且仅当使用至少两个参数调用Array构造函数时,此描述才适用。
执行过程:
- 定义 numberOfArgs 为传递给此函数的调用的实参数量;
- 断言: numberOfArgs 大于等于2;
- 如果 NewTarget 为 undefined ,就设置 newTarget 为 活动函数对象 ,并且让 newTarget 成为 NewTarget ;
- 原型 proto 怎么办?通过原生方法
GetPrototypeFromConstructor(newTarget, "%ArrayPrototype%")
来构造; - 然后定义 array 为
ArrayCreate(numberOfArgs, proto)
; - 定义 k 为0;
- 定义 items 为 正序传入参数的**零源(zero-origined)**列表;
- 重复,当 k 小于 numberOfArgs
- 定义 Pk 为 ToSting(k) ;
- 定义 itemK 为 item[k] ;
- 定义 defineStatus 为 CreateDataProperty(array, Pk, itemK) ;
- 断言: defineStatus 为真;
- k 加1。
- 断言: array 的 length 值为 numberOfArgs ;
- 返回 array 。
empty
上面的三种情况以上便是构造Array()时不同情况的具体实现。但是我们从上面的断言可以知道,构造结果有可能为真,有可能为假。还有是定义指定长度数组时会出现什么事呢?
在 V8源码 3.28.71(node0.12.18) 中 Array 有个 CloneElementAt 的方法。定义如下:
在指定索引处克隆元素时,如果克隆失败,则返回一个空句柄(任何原因)。
从这句话我们可以知道,当我们构造一个指定长度的 Array 时,由于有长度,所以会开辟相应下标的空间,但是因为该下标并没有元素,所以就会返回 empty ,任何原因构造数组元素失败时,都会返回一个 empty 。
示例如下:
var arr = new Array(10); arr // [empty × 10] 复制代码
类型转换
类型转换是一个经常出现在一些网上常见面试题或者奇技淫巧中的内容。那么关于数组的类型转换,又是怎样的呢?
首先我们要知道,在 JS 中类型转换只有三种情况,分别是:
- 转换为布尔值
- 转换为数字
- 转换为字符串
转换为原始类型
对象在转换类型的时候,会执行原生方法 ToPrimitive 。
其算法如下:
- 如果已经是 原始类型 ,则返回当前值;
- 如果需要转 字符串 则先调用
toSting
方法,如果此时是 原始类型 则直接返回,否则再调用valueOf
方法并返回结果; - 如果不是 字符串 ,则先调用
valueOf
方法,如果此时是 原始类型 则直接返回,否则再调用toString
方法并返回结果; - 如果都没有 原始类型 返回,则抛出 TypeError 类型错误。
当然,我们可以通过重写 Symbol.toPrimitive
来制定转换规则,此方法在转原始类型时调用优先级最高。
// 下面例子来自YCK的小册 let a = { valueOf() { return 0 }, toString() { return '1' }, [Symbol.toPrimitive]() { return 2 } } 1 + a // => 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 | If argument is true, return "true" .If argument is false, return "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" 复制代码
所以各位是否理解上述隐式转换的答案呢?
关于API使用的一些经验与思考
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自带了可递归的方法,就是 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
循环比性能如何呢?
以下代码测试环境为: Chrome 73.0.3683 / Windows 10 0.0.0
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 的言论,其实以前的浏览器也是这种情况。
详情可以看知乎的这篇评论: www.zhihu.com/question/54…
所以在9012年的如今,结果又会是如何呢?
通过上面的对比,结果已经很明显了,我们要知道,现代的浏览器性能优化已经做得比以前好很多了,再加上电子设备本身的硬件也越来越好,所以代码块的性能不是我们首要的考虑因素。
在跟同行沟通的过程中,经常会看到有人为了扣那么一个两个表达式的性能而烦恼,其实是这是没有任何必要,原因也如上,我们应该优化的是我们表达式是否清晰明了,是否适合后期维护或拓展。
以上所述就是小编给大家介绍的《JS数组中那些你知道或不知道的》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:- 25个你不得不知道的数组reduce高级用法
- 数组对象根据对象中指定的属性去重?你知道多少
- C语言指针数组和数组指针
- 数组 – 如何在Swift中将数组拆分成两半?
- 菜鸡的算法修炼:数组(旋转数组的最小数字)
- 交换数组元素,使得数组的和的差最小
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。