怎么把一个对象当做数组应用?
咱们晓得在 JS 中对象和数组的操作形式是不一样的,然而咱们能够通过封装,给对象加一层包装器,让它能够和数组领有同样的应用形式。咱们次要借助Object.keys()
、Object.values()
、Object.entries()
、Proxy
。
Object.keys
看一下 MDN 上的解释:
Object.keys() 办法会返回一个由一个给定对象的本身可枚举属性组成的数组,数组中属性名的排列程序和失常循环遍历该对象时返回的程序统一。
也就是 Object.keys
能够获取对象的所有属性名,并生成一个数组。
var obj = {a: 0, b: 1, c: 2};
console.log(Object.keys(obj));
// console: ['a', 'b', 'c']
Object.values
看一下 MDN 上的解释:
Object.values()办法返回一个给定对象本身的所有可枚举属性值的数组,值的程序与应用 for…in 循环的程序雷同 ( 区别在于 for-in 循环枚举原型链中的属性)。
Object.values()
返回一个数组,元素是对象上找到的可枚举属性值。
var obj = {foo: 'bar', baz: 42};
console.log(Object.values(obj));
// ['bar', 42]
Object.entries
看一下 MDN 上的解释:
Object.entries()办法返回一个给定对象本身可枚举属性的键值对数组,其排列与应用 for…in 循环遍历该对象时返回的程序统一(区别在于 for-in 循环还会枚举原型链中的属性)。
Object.entries()
返回一个数组,元素是由属性名和属性值组成的数组。
const obj = {foo: 'bar', baz: 42};
console.log(Object.entries(obj));
// [['foo', 'bar'], ['baz', 42] ]
Proxy
Proxy
是 JS 最新的对象代理形式,用于创立一个对象的代理,从而实现基本操作的拦挡和自定义(如属性查找、赋值、枚举、函数调用等)。
应用 Proxy
能够封装对象的原始操作,在执行对象操作的时候,会通过 Proxy
的解决,这样咱们就能够实现数组操作命令。
根底 get 示例
const handler = {get: function(obj, prop) {return prop in obj ? obj[prop] : 37;
}
}
const p = new Proxy({}, handler);
p.a = 1;
p.b = undefined;
console.log(p.a, p.b)
console.log('c' in p, p.c)
以上示例的中,当对象中不存在属性名时,默认返回值为 37
无操作转发代理
应用 Proxy
包装原生对象生成一个代理对象 p,对代理对象的操作会转发到原生对象上。
let target = {};
let p = new Proxy(target, {});
p.a = 37; // 操作转发到指标
console.log(target.a); // 37. 操作曾经被正确地转发
咱们要实现一下几个函数:forEach
、map
、filter
、reduce
、slice
、find
、findKey
、includes
、keyOf
、lastKeyOf
。
实现数组函数
forEach
数组中的 forEach
函数定义:arr.forEach(callback(currentValue [, index [, array]])[, thisArg])
数组中的 forEach
须要传入一个函数,函数的第一个参数是以后操作的元素值,第二个参数是以后操作的元素索引,第三个参数是正在操作的对象。
对于对象,咱们将参数定为:currentValue、key、target。咱们能够应用 Object.keys
来遍历对象。
Object.keys(target).forEach(key => callback(target[key], key, target))
这里须要 target
和callback
参数,咱们通过函数封装一下
function forEach(target, callback) {Object.keys(target).forEach(key => callback(target[key], key, target))
}
这样咱们就能够应用以下形式调用:
const a = {a: 1, b: 2, c: 3}
forEach(a, (v, k) => console.log(`${k}-${v}`))
// a-1
// b-2
// c-3
通过 Proxy
封装:
const handler = {get: function(obj, prop) {return forEach(obj)
}
}
const p = new Proxy(a, handler)
p.forEach((v, k) => console.log(`${k}-${v}`))
以上形式当然是不行的,咱们次要看最初一句,其执行形式和数组的 forEach
完全相同,咱们在调用 Proxy
封装的对象时,获取数据时,会调用 get
函数,第一个参数为原生对象,第二个参数为属性名 -forEach
,在这里就要批改咱们的 forEach
函数了。
首先 p.forEach
的参数是一个函数,因而咱们代理对象的返回值须要接管一个函数作为参数,因而批改如下:
function forEach(target) {return (callback) => Object.keys(target).forEach(key => callback(target[key], key, target))
}
因而实现代码为:
function forEach(target) {return (callback) => Object.keys(target).forEach(key => callback(target[key], key, target))
}
const handler = {get: function(obj, prop) {return forEach(obj)
}
}
const a = {a: 1, b: 2, c: 3}
const p = new Proxy(a, handler)
p.forEach((v, k) => console.log(`${k}-${v}`))
// a-1
// b-2
// c-3
咱们应该把以上代码封装为模块,不便对外应用:
const toKeyedArray = (obj) => {
const methods = {forEach(target) {return (callback) => Object.keys(target).forEach(key => callback(target[key], key, target));
}
}
const methodKeys = Object.keys(methods)
const handler = {get(target, prop) {if (methodKeys.includes(prop)) {return methods[prop](target)
}
return Reflect.get(...arguments)
}
}
return new Proxy(obj, handler)
}
const a = {a: 1, b: 2, c: 3}
const p = toKeyedArray(a)
p.forEach((v, k) => console.log(`${k}-${v}`))
以上是 forEach
的实现和封装,其余函数的实现形式相似。
全副源码如下:
const toKeyedArray = (obj) => {
const methods = {forEach(target) {return (callback) => Object.keys(target).forEach(key => callback(target[key], key, target));
},
map(target) {return (callback) =>
Object.keys(target).map(key => callback(target[key], key, target));
},
reduce(target) {return (callback, accumulator) =>
Object.keys(target).reduce((acc, key) => callback(acc, target[key], key, target),
accumulator
);
},
forEach(target) {
return callback =>
Object.keys(target).forEach(key => callback(target[key], key, target));
},
filter(target) {
return callback =>
Object.keys(target).reduce((acc, key) => {if (callback(target[key], key, target)) acc[key] = target[key];
return acc;
}, {});
},
slice(target) {return (start, end) => Object.values(target).slice(start, end);
},
find(target) {
return callback => {return (Object.entries(target).find(([key, value]) =>
callback(value, key, target)
) || [])[0];
};
},
findKey(target) {
return callback =>
Object.keys(target).find(key => callback(target[key], key, target));
},
includes(target) {return val => Object.values(target).includes(val);
},
keyOf(target) {
return value =>
Object.keys(target).find(key => target[key] === value) || null;
},
lastKeyOf(target) {
return value =>
Object.keys(target)
.reverse()
.find(key => target[key] === value) || null;
}
}
const methodKeys = Object.keys(methods)
const handler = {get(target, prop) {if (methodKeys.includes(prop)) {return methods[prop](target)
}
const [keys, values] = [Object.keys(target), Object.values(target)];
if (prop === 'length') return keys.length;
if (prop === 'keys') return keys;
if (prop === 'values') return values;
if (prop === Symbol.iterator)
return function* () {for (value of values) yield value;
return;
};
return Reflect.get(...arguments)
}
}
return new Proxy(obj, handler)
}
const x = toKeyedArray({a: 'A', b: 'B'});
x.a; // 'A'
x.keys; // ['a', 'b']
x.values; // ['A', 'B']
[...x]; // ['A', 'B']
x.length; // 2
// Inserting values
x.c = 'c'; // x = {a: 'A', b: 'B', c: 'c'}
x.length; // 3
// Array methods
x.forEach((v, i) => console.log(`${i}: ${v}`)); // LOGS: 'a: A', 'b: B', 'c: c'
x.map((v, i) => i + v); // ['aA', 'bB,'cc]
x.filter((v, i) => v !== 'B'); // {a: 'A', c: 'c'}
x.reduce((a, v, i) => ({...a, [v]: i }), {}); // {A: 'a', B: 'b', c: 'c'}
x.slice(0, 2); // ['A', 'B']
x.slice(-1); // ['c']
x.find((v, i) => v === i); // 'c'
x.findKey((v, i) => v === 'B'); // 'b'
x.includes('c'); // true
x.includes('d'); // false
x.keyOf('B'); // 'b'
x.keyOf('a'); // null
x.lastKeyOf('c'); // 'c'
本文由 mdnice 多平台公布