Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

重学js —— 索引集合之Array原型对象属性 #120

Open
lizhongzhen11 opened this issue Jun 20, 2020 · 0 comments
Open

重学js —— 索引集合之Array原型对象属性 #120

lizhongzhen11 opened this issue Jun 20, 2020 · 0 comments
Labels
js基础 Good for newcomers 重学js 重学js系列 规范+MDN

Comments

@lizhongzhen11
Copy link
Owner

lizhongzhen11 commented Jun 20, 2020

Array 原型对象属性

本篇偏长,大部分看MDN即可

  • 即固有对象 %ArrayPrototype%
  • 属于 数组怪异对象
  • "length" 属性,其初始值为 0,属性描述符为 { [[Writable]]: true, [[Enumerable]]: false, [[Configurable]]: false }
  • [[Prototype]] 内置插槽,其值为 %Object.prototype%

会改变原数组的方法:

Array.prototype.concat ( ...arguments )

// 有趣的是这个特点,同时传入元素和一维数组,会把一维数组解构,我老忘记这个特点
var alpha = ['a', 'b', 'c'];
var alphaNumeric = alpha.concat(1, [2, 3]);
console.log(alphaNumeric); // ['a', 'b', 'c', 1, 2, 3]

[1].concat(2, [3], [[4]]) // [1, 2, 3, [4]]

// 配合 reduce 可以对多维数组进行平铺
const flat = (arr) => arr.reduce((prev, next) => prev.concat(
  Array.isArray(next) ? flat(next) : next
), [])

flat([1, [2], [[3, [4, [5]]]]]) // [1, 2, 3, 4, 5]
  1. 定义 O? ToObject(this 值)
  2. 定义 A? ArraySpeciesCreate(O, 0)
  3. 定义 n 为 0
  4. 定义 itemsList,其第一个元素是 O 且其后续元素是传递给此函数调用的参数,按从左到右的顺序排列
  5. items 不为空时,重复以下步骤
    1. items 中移除第一个元素,且定义 E 为该元素的值
    2. 定义 spreadable? IsConcatSpreadable(E)
    3. 如果 spreadabletrue
      1. 定义 k 为 0
      2. 定义 len? LengthOfArrayLike(E)
      3. 如果 n + len > 253 - 1,抛 TypeError 异常
      4. 重复 k < len,重复以下步骤,
        1. 定义 P! ToString(k)
        2. 定义 exists? HasProperty(E, P)
        3. 如果 existstrue
          1. 定义 subElement? Get(E, P)
          2. 执行 ? CreateDataPropertyOrThrow(A, ! ToString(k), subElement)
        4. n 设置为 n + 1
        5. k 设置为 k + 1
    4. 否则,
      1. 注意:E 作为单个元素而不是扩展元素被添加
      2. 如果 n ≥ 253 - 1,抛 TypeError 异常
      3. 执行 ? CreateDataPropertyOrThrow(A, ! ToString(k), E)
      4. n 设置为 n + 1
  6. 执行 ? Set(A, "length", n, true)
  7. 返回 A

Array.prototype.copyWithin ( target, start [ , end ] )

  • 看MDN即可
  • 会改变原数组,不改变长度;它是用来移动Array或者TypedArray数据的高性能方法

Array.prototype.entries ( )

  • MDN
  • 即固有对象 %ArrayProto_entries%
  1. 定义 O? ToObject(this 值)
  2. 返回 CreateArrayIterator(O, key+value)

Array.prototype.every ( callbackfn [ , thisArg ] )

  • MDN
  • 即固有对象 %ArrayProto_forEach%
  • every 遍历的元素范围在第一次调用 callback 之前就已确定了。遍历过程中不能改变数组!!!
  1. 定义 O? ToObject(this 值)
  2. 定义 len? LengthOfArrayLike(O)
  3. 如果 callbackfn 不可调用,抛 TypeError 异常
  4. 定义 k 为 0
  5. k < len,重复以下步骤
    1. 定义 Pk! ToString(k)
    2. 定义 kPresent? HasProperty(O, Pk)
    3. 如果 kPresenttrue
      1. 定义 kValue? Get(O, Pk)
      2. 定义 testResult! ToBoolean(? Call(callbackfn, thisArg, « kValue, k, O »))
      3. 如果 testResultfalse,返回 false
    4. k 设置为 k + 1
  6. 返回 true

Array.prototype.fill ( value [ , start [ , end ] ] )

// 这个方法我不怎么熟,所以一度误解,我误以为它是给每个位置填充不同数据的,
// 后来我又单纯的认为它是给所有位置填充相同数据的,
// 其实它可以给特定位置填充
[1, 2, 3].fill(4, 1, 2); // [1, 4, 3]
[1, 2, 3].fill(4, -3, -2); // [4, 2, 3]
[1, 2, 3].fill(4, NaN, NaN); // [1, 2, 3]
[1, 2, 3].fill(4, 3, 5); // [1, 2, 3]
  1. 定义 O? ToObject(this 值)
  2. 定义 len? LengthOfArrayLike(O)
  3. 定义 relativeStart? ToInteger(start)
  4. 如果 relativeStart < 0,定义 kmax((len + relativeStart), 0);否则,定义 kmin(relativeStart, len)
  5. 如果 endundefined,定义 relativeEndlen;否则,定义 relativeEnd? ToInteger(end)
  6. 如果 relativeEnd < 0,定义 finalmax((len + relativeEnd), 0);否则,定义 finalmin(relativeEnd, len)
  7. k < final 时,重复以下步骤,
    1. 定义 Pk! ToString(k)
    2. 执行 ? Set(O, Pk, value, true)
    3. k 设置为 k + 1
  8. 返回 O

Array.prototype.filter ( callbackfn [ , thisArg ] )

  • 看MDN即可
  • 返回 新数组
  • filter 遍历的元素范围在第一次调用 callback 之前就已确定了。遍历过程中不能改变数组!!!

Array.prototype.find ( predicate [ , thisArg ] )

  • 看MDN即可
  • 返回数组中满足提供的测试函数的第一个元素,不是返回数组,这点我有时候记不牢

Array.prototype.findIndex ( predicate [ , thisArg ] )

Array.prototype.flat ( [ depth ] )

  • MDN
  • 这个方法我不怎么熟,一直以为能平铺多维数组,后来发现的确可以,但是要传 嵌套数组的结构深度
  • 会移除数组中的空项
  • 不能直接平铺树形数据结构,属性数据结构就是类似 {name: 'root', value: '0', children: []} 这种,这种结构平铺没法去提供统一的方法,毕竟对象的属性名都是自定的,不过可以自己写,可以看 concat 中的代码
[1, [2]].flat() // [1, 2]
[1, [2], [3, [4]]].flat() // [1, 2, 3, [4]] 
[1, [2], [3, [4]]].flat(2) // [1, 2, 3, 4] 
// Infinity 牛逼
[1, [2], [3, [4]]].flat(Infinity) // [1, 2, 3, 4] 
  1. 定义 O? ToObject(this 值)
  2. 定义 sourceLen? LengthOfArrayLike(O)
  3. 定义 depthNum 为 1
  4. 如果 depth 不是 undefined
    1. depthNum 设置为 ? ToInteger(depth)
  5. 定义 A? ArraySpeciesCreate(O, 0)
  6. 执行 ? FlattenIntoArray(A, O, sourceLen, 0, depthNum)
  7. 返回 A

Array.prototype.flatMap ( mapperFunction [ , thisArg ] )

  • MDN
  • 第一次看到这个方法时,误以为它跟Map结构有关,其实不是的!!!它起这个名字是因为它算是 Array.prototype.mapArray.prototype.flat 的结合体!!!
  • 它固定只能平铺一层嵌套,看下面的规范,调用 FlattenIntoArray 时固定depth为1
// 也可以用它写个平铺树形数据结构的方法
const flatTree = (treeArr) => {
  return treeArr.flatMap(item => {
     return item.children ? [item, ...flatTree(item.children)] : [item]
  })
}

const tree = [
  {
    name: '1',
    children: [
      {name: '11'},
      {
        name: '12', 
        children: [ 
          {name: '121'}, 
          {name: '122', children: [
            {name: '1221'}
          ]} 
        ]
      }
    ]
  },
  {name: '2'},
  {
    name: '3',
    children: [
      {
        name: '31', 
        children: [
          {
            name: '311', 
            children: [
              {name: '3111'}
            ]
          }
        ]
      }
    ]
  }
]

flatTree(tree)
  1. 定义 O? ToObject(this 值)
  2. 定义 sourceLen? LengthOfArrayLike(O)
  3. 如果 mapperFunction 不可调用,抛 TypeError 异常
  4. 定义 A? ArraySpeciesCreate(O, 0)
  5. 执行 ? FlattenIntoArray(A, O, sourceLen, 0, 1, mapperFunction, thisArg)
  6. 返回 A

Array.prototype.forEach ( callbackfn [ , thisArg ] )

  • MDN
  • foreach 遍历的元素范围在第一次调用 callback 之前就已确定了。遍历过程中不能改变数组!!!
// 曾经在苏宁时犯过一个错误,误以为遍历时可以改变原数组,真菜
let arr = [1, 2, 3]
arr.forEach((item, index) => item += 1 )

// 我TM当时下意识的说会改变,草草草
  1. 定义 O? ToObject(this 值)
  2. 定义 len? LengthOfArrayLike(O)
  3. 如果 callbackfn 不可调用,抛 TypeError 异常
  4. 定义 k 为 0
  5. k < len,重复以下步骤
    1. 定义 Pk! ToString(k)
    2. 定义 kPresent? HasProperty(O, Pk)
    3. 如果 kPresenttrue
      1. 定义 kValue? Get(O, Pk)
      2. 执行 ? Call(callbackfn, thisArg, « kValue, k, O »)
    4. k 设置为 k + 1
  6. 返回 undefined

Array.prototype.includes ( searchElement [ , fromIndex ] )

  • 看MDN
  • 内部用 SameValueZero 算法判定是否相等
  • 第二个参数 fromIndex(从该索引位置开始查找) 容易忽略,不看规范和MDN我还真不知道有第二个参数。。。
  • 值得注意的是,如果 fromIndex 为负,则用数组长度 + fromIndex 得到新的索引位置 k,如果 k 还是为负,那直接让 k = 0 从0开始查

Array.prototype.indexOf ( searchElement [ , fromIndex ] )

  • 看MDN即可
  • includes 一样,第二个参数fromIndex容易被忽略
  • 内部使用 严格相等 算法判断,和 includes 不同

Array.prototype.join ( separator )

Array.prototype.keys ( )

Array.prototype.values ( )

  • 看MDN
  • %ArrayProto_values% 固有对象

Array.prototype.lastIndexOf ( searchElement [ , fromIndex ] )

Array.prototype.map ( callbackfn [ , thisArg ] )

  • 看MDN
  • 返回新数组,不改变原数组
  • map 方法处理数组元素的范围是在 callback 方法第一次调用之前就已经确定了。
// 结合 parseInt 的经典面试题
["1", "2", "3"].map(parseInt); // [1, NaN, NaN]

Array.prototype.pop ( )

  • MDN
  • 改变原数组
  • 返回被删除的那个元素!!!为空时返回 undefined!!!

Array.prototype.push ( ...items )

  • MDN
  • 改变原数组
  • 返回数组的新长度!!!
  1. 定义 O? ToObject(this 值)
  2. 定义 len? LengthOfArrayLike(O)
  3. 定义 itemsList,其元素是函数调用时传给函数的参数按从左到右顺序排列
  4. 定义 argCountitems 中的元素数量
  5. 如果 len + argCount > 253 - 1,抛 TypeError 异常
  6. items 不为空时,重复以下步骤
    1. items 中移除第一个元素,定义 E 为该元素的值
    2. 执行 ? Set(O, ! ToString(len), E, true)
    3. len 设置为 len + 1
  7. 执行 ? Set(O, "length", len, true)
  8. 返回 len

Array.prototype.reduce ( callbackfn [ , initialValue ] )

Array.prototype.reduceRight ( callbackfn [ , initialValue ] )

Array.prototype.reverse ( )

Array.prototype.shift ( )

  • 看MDN
  • 改变原数组
  • 返回被删除的元素,没有的话返回 undefined

Array.prototype.slice ( start, end )

Array.prototype.some ( callbackfn [ , thisArg ] )

  • 看MDN
  • some() 遍历的元素的范围在第一次调用 callback. 前就已经确定了

Array.prototype.sort ( comparefn )

  • MDN
  • 改变原数组
  • 根据comparefn返回结果与0比较来确定排序,即comparefn应该返回一个 Number 而不是布尔值

Array.prototype.splice ( start, deleteCount, ...items )

  • 看MDN
  • 改变原数组
  • 返回被修改内容

Array.prototype.unshift ( ...items )

  • 看MDN
  • 改变原数组
  • 返回修改后的数组长度

Array.prototype.toString ( )

  1. 定义 array? ToObject(this 值)
  2. 定义 func? Get(array, "join")
  3. 如果 func 不可调用,将 func 设置为内置函数 %Object.prototype.toString%
  4. 返回 ? Call(func, array)

2020-07-27 补充

高级前端面试小程序 js基础第11题

var obj = {
  '2': 3,
  '3': 4,
  'length': 2,
  'splice': Array.prototype.splice,
  'push': Array.prototype.push
}
obj.push(1)
obj.push(2)
console.log(obj)

// 输出
{
  '2': 1,
  '3': 2,
  'length': 4,
  'splice': Array.prototype.splice,
  'push': Array.prototype.push
}

// chrome控制台输出不展开时是这样的
Object(4) [empty × 2, 1, 2, splice: ƒ, push: ƒ]

其实这个对象属于类数组对象,通过上面 push 可得知,方法内部会无脑 ? Set(O, ! ToString(len), E, true) ,它才不会管你索引 01 处是不是有值,它只会默认将数组对象当前长度作为索引并设置相应的值!

Daily-Interview-Question 第 160 题

这个问题我在做 非同凡想优惠券 淘客小程序对接苏宁时也遇到过,其实 forEach 也好,map 也好都是高级函数,本质是 函数嵌套函数!虽然这题 forEach 内用了 async,可是 forEach 函数本身去调用内部嵌套函数时并没有 await,所以这里的 async 只是用来迷惑我们的,它本质还是立即遍历去执行内部嵌套的函数,所以最终都是大约 1s后打印输出。

2020-08-05 补充

Daily-Interview-Question 第 43 题:使用 sort() 对数组 [3, 15, 8, 29, 102, 22] 进行排序,输出结果,这个问题很常见。

几行代码解决树结构平铺

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
js基础 Good for newcomers 重学js 重学js系列 规范+MDN
Projects
None yet
Development

No branches or pull requests

1 participant