类(伪)数组
- arguments
- 元素集合/节点集合
- …
伪数组:是结构和数组相似「以数字作为索引、索引从零开始、逐级递增、具备length属性」,但是其**proto** 没有指向 Array.prototype,所以其并不是 Array 类的实例 - 不能直接使用 Array.prototype 上提供的方法
- 伪数组 instanceof Array -> false
在JS中有很多 “伪(类)xxx” 的概念
- 伪(类)数组
- 对于数组来讲,它的结构特征:数字做为索引、索引从0开始逐级递增、具备length属性记录其长度
- 每一个数组都是 Array 类的实例,其 proto 指向 Array.prototype「所以其可以直接使用Array原型对象上的 push/pop/forEach… 等方法」
- 伪数组在结构上和数组几乎一模一样,但是其 proto 并不指向 Array.prototype「所以不能直接调用数组提供的方法」,常见的伪数组有:arguments、元素集合、节点集合…
- 伪(类)promise「或者 thenable」
- 基于 new Promise 所创造出来的实例,被称为标准的 promise 实例,其拥有 状态 和 值,也可以调用 Promise.prototype 上的 then/catch/finally 这些方法!
- 我们可以遵照 PromiseA+规范,自己创造一个同样具备 状态和值,也具备 then 方法的实例,此实例被称为“类promise 或 thenable”
- …
在我们平时的开发中,我们往往会出现这样的需求:让伪数组调用数组的方法,去实现相应的功能,此时就需要我们对伪数组做一些特殊的处理,而这个过程,有人把其叫做 “鸭式辨型 / 鸭子类型”!
特殊的处理方案
- 把需要借用的方法,赋值给伪数组的私有属性
- 原理:首先要保证伪数组可以访问到这个方法;然后把方法执行,让方法中的this变为要操作的伪数组;因为伪数组和数组的“结构几乎一模一样”,所以操作数组的那些代码,“对于伪数组也基本上都生效”;
let obj = { 0: 10, 1: 20, length: 2 }obj.push(30) //报错:obj.push is not a functionobj.push = Array.prototype.pushobj.push(30) //正常处理
基于上述原理的分析,我们只需要把数组的方法执行,“让方法中的this变为伪数组”,这样就相当于伪数组直接借用数组的方法,去实现相应的效果了!
let obj = { 0: 10, 1: 20, length: 2 }Array.prototype.push.call(obj,30)[].push.call(obj,30)----[].forEach.call(obj,(item,index)=>{...})...
其实我们还可以直接修改伪数组的原型指向,让其指向 Array.prototype,这样数组的所有方法,伪数组都可以直接调用了!!
let obj = { 0: 10, 1: 20, length: 2 }// obj.__proto__=Array.prototypeObject.setPrototypeOf(obj, Array.prototype)
把伪数组直接转换为数组后,再去进行相应的操作即可
let obj = { 0: 10, 1: 20, length: 2 }let arr = Array.from(obj)arr = [...obj]arr = [].slice.call(obj,0)...
鸭式辨型有一个很重要的前提:伪数组的结构以及一些操作,几乎和数组一模一样(或者说对数组的这些操作「迭代、判断、取值等等」,可以无缝衔接到伪数组上) 数组原型上的这些方法,并非是伪数组不能用(只要能让方法执行,让方法中的this指向伪数组,则内部操作的那些代码对伪数组一样可以进行操作),而是伪数组基于原型链查找机制,找不到Array.prototype上的这些方法,才导致的不能用!
let arr = [10, 20, 30]let obj = {0: 10,1: 20,2: 30,length: 3,forEach: Array.prototype.forEach}Object.setPrototypeOf(obj, Array.prototype)
Array.prototype.forEach = function forEach(callback, context) {if (typeof callback !== 'function') throw new TypeError('callback必须是一个函数')let len = +this.length,k = 0if (isNaN(len)) throw new TypeError('迭代的数据需要是一个数组/伪数组')while (k < len) {if (this.hasOwnProperty(k)) {// 规避稀疏数组let item = this[k],index = kcallback.call(context, item, index)}k++}}obj.forEach((item, index) => {console.log(item, index)})Array.prototype.forEach.call(obj, (item, index) => {console.log(item, index)})
Array.prototype.push = function push(...params) {// this->操作的数组/伪数组 params->[...]包含向末尾新增的内容for (let i = 0; i < params.length; i++) {let item = params[i]this[this.length] = item// this.length++}return this.length}let arr = [10, 20]let len = arr.push(1000, 2000, 3000)console.log(len, arr)//================================================================let obj = {2: 3, //13: 4, //2length: 2, //3(4)push: Array.prototype.push}obj.push(1)// this[this.length] = item this.length++// obj[2] = 1 obj.length++obj.push(2)// this[this.length] = item this.length++// obj[3] = 2 obj.length++console.log(obj)
