JS数组中那些你知道或不知道的

论坛 期权论坛 期权     
奇舞周刊   2019-6-9 21:28   3327   0
JS数组中那些你知道或不知道的
  • 首发:krissarea.gitee.io
  • 作者:陈大鱼头
  • github: KRISACHAN
[h1]JS中的Array[/h1]
ecma-262中的定义:Array对象是一种特殊对象,它会对数组索引属性键进行特殊处理。
每个Array对象都有一个不可配置的length属性,其最大值是2 - 1。
[h2]Array()[/h2]
当且仅当不带参数调用Array构造函数时,此描述才适用。
执行过程:
  • 定义 numberOfArgs 传递给此函数的调用的实参数量;
  • 断言: numberOfArgs 为 0;
  • 如果 NewTarget 为 undefined ,就设置 newTarget 为 活动函数对象(active-function-object,正在运行的执行上下文的函数组件) ,并且让 newTarget 成为 NewTarget ;
  • 原型 proto 怎么办?通过原生方法
    1. GetPrototypeFromConstructor(newTarget,"%ArrayPrototype%")
    复制代码
    来构造;
  • 返回原生方法
    1. ArrayCreate(0,proto)
    复制代码

鱼头注:NewTarget是啥?NewTarget是原生Class FunctionCallbackInfo(函数调用的callback上下文的信息)内的一个不变量,用来定义构造调用时的返回值(new.target)。
[h2]Array(len)[/h2]
当且仅当使用一个参数调用Array构造函数时,此描述才适用。
执行过程:
1.定义 numberOfArgs 为传递给此函数的调用的实参数量;
2.断言: numberOfArgs 为1;
3.如果 NewTarget 为 undefined ,就设置 newTarget 为 活动函数对象 ,并且让 newTarget 成为 NewTarget ;
4.原型 proto 怎么办?通过原生方法
  1. GetPrototypeFromConstructor(newTarget,"%ArrayPrototype%")
复制代码
来构造;
5.然后定义array 为
  1. ArrayCreate(0,proto)
复制代码

6.如果 len 的类型不是个Number,则:
        a.定义 defineStatus 为CreateDataProperty(array, "0", len);
        b.断言:defineStatus为真;
        c.让 intLen(初始化长度) 为1。
7.或者:
        a.定义intLen为ToUint32(len)(原生方法,将len转换成0到2 - 1之间的整数值);
        b.如果intLen不等于len,抛出RangeError异常。
8.执行Set(array, "length", intLen, true)(原生方法,给对象的属性赋值);
9.返回array。
[h2]Array(...items)[/h2]
当且仅当使用至少两个参数调用Array构造函数时,此描述才适用。
执行过程:
1.定义 numberOfArgs 为传递给此函数的调用的实参数量;
2.断言: numberOfArgs 大于等于2;
3.如果 NewTarget 为 undefined ,就设置 newTarget 为 活动函数对象 ,并且让 newTarget 成为 NewTarget ;
4.原型 proto 怎么办?通过原生方法
  1. GetPrototypeFromConstructor(newTarget,"%ArrayPrototype%")
复制代码
来构造;
5.然后定义array 为
  1. 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。
[h2]empty[/h2]
上面的三种情况以上便是构造Array()时不同情况的具体实现。但是我们从上面的断言可以知道,构造结果有可能为真,有可能为假。还有是定义指定长度数组时会出现什么事呢?
在V8源码 3.28.71(node0.12.18)中 Array 有个CloneElementAt的方法。定义如下:
在指定索引处克隆元素时,如果克隆失败,则返回一个空句柄(任何原因)。
从这句话我们可以知道,当我们构造一个指定长度的 Array 时,由于有长度,所以会开辟相应下标的空间,但是因为该下标并没有元素,所以就会返回empty,任何原因构造数组元素失败时,都会返回一个empty。
示例如下:
    1. var arr = new Array(10);
    复制代码
    1. arr // [empty × 10]
    复制代码
[h2]以上总结[/h2]上面是 ECMA 上的定义以及 V8 源码的容错处理,其实简单来说就是:
调用
  1. Array(args)
复制代码
时:
  • 用原生方法
    1. GetPrototypeFromConstructor
    复制代码
    生成原型
    1. proto
    复制代码

  • 判断
    1. args
    复制代码
    的类型;
  • 如果为
    1. undefined
    复制代码
    ,则直接返回创建数组的原生方法
    1. ArrayCreate
    复制代码

  • 如果为
    1. number
    复制代码
    ,则用原生方法
    1. Set
    复制代码
    创建
    1. args
    复制代码
    长度的数组,并通过原生方法
    1. CloneElementAt
    复制代码
    来创建 args 个empty 作为数组元素,如果
    1. args
    复制代码
    大于 2 - 1 的话,会报错;
  • 如果为其他类型,则把
    1. args
    复制代码
    变成数组元素,并用 原生方法
    1. CreateDataProperty
    复制代码
    创建参数,然后返回创建数组的原生方法
    1. ArrayCreate
    复制代码

[h1]类型转换[/h1]
类型转换是一个经常出现在一些网上常见面试题或者奇技淫巧中的内容。那么关于数组的类型转换,又是怎样的呢?
首先我们要知道,在 JS 中类型转换只有三种情况,分别是:
  • 转换为布尔值
  • 转换为数字
  • 转换为字符串
[h2]转换为原始类型[/h2]对象在转换类型的时候,会执行原生方法ToPrimitive。
其算法如下:
  • 如果已经是 原始类型,则返回当前值;
  • 如果需要转 字符串 则先调用
    1. toSting
    复制代码
    方法,如果此时是 原始类型 则直接返回,否则再调用
    1. valueOf
    复制代码
    方法并返回结果;
  • 如果不是 字符串,则先调用
    1. valueOf
    复制代码
    方法,如果此时是 原始类型 则直接返回,否则再调用
    1. toString
    复制代码
    方法并返回结果;
  • 如果都没有 原始类型 返回,则抛出 TypeError类型错误。
当然,我们可以通过重写
  1. Symbol.toPrimitive
复制代码
来制定转换规则,此方法在转原始类型时调用优先级最高。
    1. // 下面例子来自YCK的小册
    复制代码
    1. const data = {
    复制代码
    1.   valueOf () {
    复制代码
    1.     return 1;
    复制代码
    1.         },
    复制代码
    1.   toString () {
    复制代码
    1.     return '1';
    复制代码
    1.         },
    复制代码
    1.   [Symbol.toPrimitive]() {
    复制代码
    1.     return 2;
    复制代码
    1.   }
    复制代码
    1. };
    复制代码
    1. data + 1 // 3
    复制代码
[h2]转换为布尔值[/h2]对象转换为布尔值的规则如下表:
参数类型结果Undefined返回
  1. false
复制代码
。Null返回
  1. false
复制代码
。Boolean返回 当前参数。Number如果参数为
  1. +0
复制代码
  1. -0
复制代码
  1. NaN
复制代码
,则返回
  1. false
复制代码
;其他情况则返回
  1. true
复制代码
。String如果参数为空字符串,则返回
  1. false
复制代码
;否则返回
  1. true
复制代码
。Symbol返回
  1. true
复制代码
。Object返回
  1. true
复制代码
。[h2]转换为数字[/h2]对象转换为数字的规则如下表:
参数类型结果Undefined返回
  1. NaN
复制代码
。NullReturn +0.Boolean如果参数为
  1. true
复制代码
,则返回
  1. 1
复制代码
  1. false
复制代码
则返回
  1. +0
复制代码
。Number返回当前参数。String先调用 ToPrimitive,再调用 ToNumber,然后返回结果。Symbol抛出
  1. TypeError
复制代码
错误。Object先调用 ToPrimitive,再调用 ToNumber,然后返回结果。[h2]转换为字符串[/h2]对象转换为字符串的规则如下表:
参数类型结果Undefined返回
  1. "undefined"
复制代码
。Null返回
  1. "null"
复制代码
。Boolean如果参数为
  1. true
复制代码
,则返回
  1. "true"
复制代码
;否则返回
  1. "false"
复制代码
。Number调用 NumberToString,然后返回结果。String返回 当前参数。Symbol抛出
  1. TypeError
复制代码
错误。Object先调用 ToPrimitive,再调用 ToString,然后返回结果。[h2]数组的类型转换[/h2]所以通过上面的转换规则,我们是否能够轻松地看懂以下的隐式转换呢?
    1. [1,2,3] + {a: 1, b: 2} // "1,2,3[object Object]"
    复制代码
    1. [1,2,3] + 1 // "1,2,31"
    复制代码
    1. [1,2,3] + true // "1,2,3true"
    复制代码
    1. [1,2,3] + undefined // "1,2,3undefined"
    复制代码
    1. [1,2,3] + null // "1,2,3null"
    复制代码
    1. [1,2,3] + '123' // "1,2,3123"
    复制代码
    1. [1,2,3] + Symbol('biu') // "Uncaught TypeError"
    复制代码
所以各位是否理解上述隐式转换的答案呢?
[h1]关于API使用的一些经验与思考[/h1]
JS数组自带了很多的方法,在现代工程化数据驱动的理念下,这些方法都是非常重要的。
[h2]loops[/h2]
  1. forEach
复制代码
是 Array 方法中比较基本的一个,作用也很简单,与
  1. for
复制代码
,就是遍历,循环。不同的是,
  1. forEach
复制代码
可以选择自定义上下文环境。例子如下:
    1. var arr1 = [1, 2, 3];
    复制代码
    1. var arr2 = [5, 6, 7];
    复制代码
    1. arr1.forEach(function (e, i, a) {
    复制代码
    1.   console.log(e, this); // this为arr2
    复制代码
    1. }, arr2);
    复制代码
但是如果
  1. forEach
复制代码
的回调函数是用箭头函数定义的,那么就无法改变它原本指向的上下文环境。例子如下:
    1. var arr1 = [1, 2, 3];
    复制代码
    1. var arr2 = [5, 6, 7];
    复制代码
    1. arr1.forEach((e, i, a) => {
    复制代码
    1.   console.log(e, this); // this为window对象
    复制代码
    1. }, arr2);
    复制代码
所以如果我们要实现以下这个功能:
    1. [/code]
    2. [*][code]
    复制代码
    1.     1
    复制代码
    1.     2
    复制代码
    1.     3
    复制代码
    1.     4
    复制代码
    1.     5
    复制代码
    1. [/code]
    2. [*][code]
    复制代码
    1.     'use strict';
    复制代码
    1.     var ul = document.querySelector('ul');
    复制代码
    1.     ul.onClick = event => {
    复制代码
    1.         var cls = event.target.className;
    复制代码
    1.         ul.querySelectorAll('li').forEach(el => {
    复制代码
    1.             el.style.color = (cls === el.className ? '#FFF' : '#FF0');
    复制代码
    1.         });
    复制代码
    1.     };
    复制代码
    1. [/code]
    2. [/list]在ES6以前的环境中,如果直接用 [code]for
    复制代码
    循环,会出现只能获取到最后一个元素的问题,但是用
    1. forEach
    复制代码
    则没有这个问题。
    [h2]reduce[/h2]Array ES5 API
    1. reduce
    复制代码
    1. reduceRight
    复制代码
    ,可以轻松实现并归元素的功能,例子如下:
    如果我们需要实现一个这样的对象:
      1. {
      复制代码
      1.     a: 1,
      复制代码
      1.     b: 2,
      复制代码
      1.     c: 3
      复制代码
      1.     ...
      复制代码
      1. };
      复制代码
    那么用reduce就会变得很简单:
      1. var newArr = 'a,b,c,d,e,f'.split(',').reduce((acc, cur, idx) => {
      复制代码
      1.     let o = {};
      复制代码
      1.     if (Object.prototype.toString.call(acc) !== '[object Object]') {
      复制代码
      1.         o[cur] = idx;
      复制代码
      1.     } else {
      复制代码
      1.         let newO = {};
      复制代码
      1.         newO[cur] = idx;
      复制代码
      1.         o = {
      复制代码
      1.             ...acc,
      复制代码
      1.             ...newO,
      复制代码
      1.         };
      复制代码
      1.     };
      复制代码
      1.     return o;
      复制代码
      1. }, 'a');
      复制代码
    [h2]性能[/h2]上面演示了通过JS数组API实现的一些功能,所以与
    1. for
    复制代码
    循环比性能如何呢?
      1. var arr = new Array(100);
      复制代码

      1. arr.forEach(data => {
      复制代码
      1.   console.log(data);
      复制代码
      1. });
      复制代码


    • [code]for (var i = 0; i
分享到 :
0 人收藏
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

积分:25
帖子:5
精华:0
期权论坛 期权论坛
发布
内容

下载期权论坛手机APP