如何使用对象作为形式参数?
我们知道JS中对象和字段的操作方法是不同的,但是我们可以通过封装的方式给对象添加一个包装器,使其可以具有和字段相同的使用方法。 我们主要依赖Object.keys()、Object.values()、Object.entries()、Proxy。
对象.keys
看看MDN上的解释:
Object.keys() 方法返回一个由给定对象自己的可枚举属性组成的字段,其顺序与正常循环对象时返回数组中的属性名称的顺序相同。
即Object.keys可以获取对象的所有属性名并生成一个字段。
var obj = { a: 0, b: 1, c: 2 };
console.log(Object.keys(obj));
// console: ['a', 'b', 'c']
目的。 价值观
看看MDN上的解释:
Object.values() 方法返回给定对象本身的所有可枚举属性值的字段,其顺序与使用 for...in 循环相同(不同之处在于 for-in 循环枚举了原型链)。
Object.values() 返回一个字段,其元素是在对象上找到的可枚举属性值。
var obj = { foo: 'bar', baz: 42 };
console.log(Object.values(obj));
// ['bar', 42]
对象.条目
看看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是JS最新的对象代理形式,用于创建对象的代理,从而实现基本操作(如属性查找、赋值、枚举、函数调用等)的拦截和定制。
代理可以用来封装对象的原始操作。 当执行对象操作时,会交由Proxy处理,这样我们就可以实现链表操作命令。
基本获取示例
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
通过代理封装:
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函数。 第一个参数是原始对象,第一个参数是本机对象。 第二个参数是属性名称-forEachjavascript 函数 数组,这里我们将更改 forEach 函数。 首先,p.forEach的参数是一个函数,所以我们的代理对象的返回值需要接收一个函数作为参数javascript 函数 数组,所以改动如下:
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'
JS 系列 1 - 布尔陷阱以及如何预防它们
发表评论