类(伪)数组
  • arguments
  • 元素集合/节点集合

  • 伪数组:是结构和数组相似「以数字作为索引、索引从零开始、逐级递增、具备length属性」,但是其**proto** 没有指向 Array.prototype,所以其并不是 Array 类的实例
  • 不能直接使用 Array.prototype 上提供的方法
  • 伪数组 instanceof Array -> false
    在JS中有很多 “伪(类)xxx” 的概念
  1. 伪(类)数组
  • 对于数组来讲,它的结构特征:数字做为索引、索引从0开始逐级递增、具备length属性记录其长度
  • 每一个数组都是 Array 类的实例,其 proto 指向 Array.prototype「所以其可以直接使用Array原型对象上的 push/pop/forEach… 等方法」
  • 伪数组在结构上和数组几乎一模一样,但是其 proto 并不指向 Array.prototype「所以不能直接调用数组提供的方法」,常见的伪数组有:arguments、元素集合、节点集合…
  1. 伪(类)promise「或者 thenable」
  • 基于 new Promise 所创造出来的实例,被称为标准的 promise 实例,其拥有 状态 和 值,也可以调用 Promise.prototype 上的 then/catch/finally 这些方法!
  • 我们可以遵照 PromiseA+规范,自己创造一个同样具备 状态和值,也具备 then 方法的实例,此实例被称为“类promise 或 thenable”
  • 在我们平时的开发中,我们往往会出现这样的需求:让伪数组调用数组的方法,去实现相应的功能,此时就需要我们对伪数组做一些特殊的处理,而这个过程,有人把其叫做 “鸭式辨型 / 鸭子类型”!

特殊的处理方案
  1. 把需要借用的方法,赋值给伪数组的私有属性
  • 原理:首先要保证伪数组可以访问到这个方法;然后把方法执行,让方法中的this变为要操作的伪数组;因为伪数组和数组的“结构几乎一模一样”,所以操作数组的那些代码,“对于伪数组也基本上都生效”;
    1. let obj = { 0: 10, 1: 20, length: 2 }
    2. obj.push(30) //报错:obj.push is not a function
    3. obj.push = Array.prototype.push
    4. obj.push(30) //正常处理
  1. 基于上述原理的分析,我们只需要把数组的方法执行,“让方法中的this变为伪数组”,这样就相当于伪数组直接借用数组的方法,去实现相应的效果了!

    1. let obj = { 0: 10, 1: 20, length: 2 }
    2. Array.prototype.push.call(obj,30)
    3. [].push.call(obj,30)
    4. ----
    5. [].forEach.call(obj,(item,index)=>{
    6. ...
    7. })
    8. ...
  2. 其实我们还可以直接修改伪数组的原型指向,让其指向 Array.prototype,这样数组的所有方法,伪数组都可以直接调用了!!

    1. let obj = { 0: 10, 1: 20, length: 2 }
    2. // obj.__proto__=Array.prototype
    3. Object.setPrototypeOf(obj, Array.prototype)
  3. 把伪数组直接转换为数组后,再去进行相应的操作即可

    1. let obj = { 0: 10, 1: 20, length: 2 }
    2. let arr = Array.from(obj)
    3. arr = [...obj]
    4. arr = [].slice.call(obj,0)
    5. ...

    鸭式辨型有一个很重要的前提:伪数组的结构以及一些操作,几乎和数组一模一样(或者说对数组的这些操作「迭代、判断、取值等等」,可以无缝衔接到伪数组上) 数组原型上的这些方法,并非是伪数组不能用(只要能让方法执行,让方法中的this指向伪数组,则内部操作的那些代码对伪数组一样可以进行操作),而是伪数组基于原型链查找机制,找不到Array.prototype上的这些方法,才导致的不能用!

  1. let arr = [10, 20, 30]
  2. let obj = {
  3. 0: 10,
  4. 1: 20,
  5. 2: 30,
  6. length: 3,
  7. forEach: Array.prototype.forEach
  8. }
  9. Object.setPrototypeOf(obj, Array.prototype)
  1. Array.prototype.forEach = function forEach(callback, context) {
  2. if (typeof callback !== 'function') throw new TypeError('callback必须是一个函数')
  3. let len = +this.length,
  4. k = 0
  5. if (isNaN(len)) throw new TypeError('迭代的数据需要是一个数组/伪数组')
  6. while (k < len) {
  7. if (this.hasOwnProperty(k)) {
  8. // 规避稀疏数组
  9. let item = this[k],
  10. index = k
  11. callback.call(context, item, index)
  12. }
  13. k++
  14. }
  15. }
  16. obj.forEach((item, index) => {
  17. console.log(item, index)
  18. })
  19. Array.prototype.forEach.call(obj, (item, index) => {
  20. console.log(item, index)
  21. })
  1. Array.prototype.push = function push(...params) {
  2. // this->操作的数组/伪数组 params->[...]包含向末尾新增的内容
  3. for (let i = 0; i < params.length; i++) {
  4. let item = params[i]
  5. this[this.length] = item
  6. // this.length++
  7. }
  8. return this.length
  9. }
  10. let arr = [10, 20]
  11. let len = arr.push(1000, 2000, 3000)
  12. console.log(len, arr)
  13. //================================================================
  14. let obj = {
  15. 2: 3, //1
  16. 3: 4, //2
  17. length: 2, //3(4)
  18. push: Array.prototype.push
  19. }
  20. obj.push(1)
  21. // this[this.length] = item this.length++
  22. // obj[2] = 1 obj.length++
  23. obj.push(2)
  24. // this[this.length] = item this.length++
  25. // obj[3] = 2 obj.length++
  26. console.log(obj)