用操作数组的方式来操作对象,该怎么做?

165 阅读4分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第 15 天,点击查看活动详情

大家好,爱吃鱼的桶哥Z。我们在日常的工作中,经常需要与我们打交道的数据包括数组、对象、字符串等。我们都知道数组的可以用formap之类的来进行遍历操作的,因为数组的有序的数据结构,而对象就不能直接遍历,因为它本身是无序的。如果我想要像操作数组那样来操作对象,我该如何做呢?让我们一起来学习一下吧!

起因

在开发中,我们经常需要将对象作为数组来进行处理,一般我们可以使用Object.keys()Object.values() 或者 Object。entries() 来进行对象的遍历,但是如果我们这样写,很快我们的代码就会变得很冗长。

我们是否可以创建一个方法,它可以接受一个对象,并给这个方法定义一些类似数组的操作,例如:数组中的 Array.prototype.map()Array.prototype.find()Array.prototype.includesArray.prototype.length 等相关方法。

上面说的这些方法都比较简单,我们可以通过对象来创建,唯一比较麻烦的就是让对象可以像数组一样进行可迭代操作,这就需要使用到 Symbol.iterator了。

当我们向一个对象中新增某些方法是比较简单的,但是这种直接新增方法的缺点是会让新增的方法变成这个实际对象的一部分,这其实不是我们想要的,那我们能怎么在一个对象中添加方法呢?

JavaScript 中,我们可以通过 Proxy 这个方法来创建一个对象的代码,从而实现基本的拦截和操作,例如:属性查找、赋值等。在上面的描述中,我们就可以通过 Proxy 将所需要的功能包装到一个函数中,以避免原对象被新增的方法污染。

下面我们一起来看一下该如何封装这样的一个方法,来帮我们实现对象的操作。

代码实现

通过上面的分析,我们接下来就一起实现一下这个方法吧,代码如下:

// 定义一个转换为数组操作的方法
const toKeyedArray = obj => {
    // 定义一个方法集合
    const methods = {
        // 实现数组中的 map 方法
        map(target) {
            // 返回一个函数,函数中的参数类似数组中 map 的参数
            return callback => Object.keys(target).map(key => callback(target[key], key, target));
        },
        // 实现数组中的 reduce 方法
        reduce(target) {
            return (callback, initData) => Object.keys(target).reduce((prev, cur) => callback(prev, target[cur], cur, target), initData);
        },
        // 实现数组中的 forEach 方法
        forEach(target) {
          return callback => Object.keys(target).forEach(key => callback(target[key], key, target));
        },
        // 实现数组中的 filter 方法
        filter(target) {
            return callback => Object.keys(target).reduce((acc, key) => {
                if (callback(target[key], key, target)) {
                    acc[key] = target[key];
                }
                return acc;
            }, {});
        },
    };
    
    // 获取所有的方法名称
    const methodKeys = Object.keys(methods);
    
    // 注册操作方法
    const handler = {
        get(target, prop, receiver) {
            // 判断方法是否在方法的列表中
            if (methodKeys.includes(prop)) {
                 return methods[prop](...arguments);
            }
            // 获取到对象中的所有方法名称和对应的方法
            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;
                };
            } else {
                return Reflect.get(...arguments);
            }
        }
    };
    
    return new Proxy(obj, handler);
};

在上述的代码中,我们通过 Proxy 将对象中的方法进行了拦截,这样我们就能够实现类似数组一样的操作了,我们一起来看一下代码的使用吧。

// 创建一个对象
const obj = toKeyedArray({ name: 'zhangsan', age: 18 });

// 我们可以直接访问这个对象中原有的属性
console.log(obj.name); // 'zhangsan'
console.log(obj.keys); // ['name', 'age']
console.log(obj.values); // ['zhangsan', 18]
console.log([...obj]); // ['name', 'age']
console.log(obj.length); // 2

执行效果如下图所示:

image.png

上面主要是访问对象的属性和值,下面我们在一起来看一下之前添加的相关方法的操作,如下:

obj.map((key, value) => key + ':' + value); // ['name:zhangsan', 'age:18']
obj.reduce((prev, cur, index) => ({...prev, [cur]: index}), {}); // {18: 'age', zhangsan: 'name'}
obj.filter((value) => value !== 18);  // { name: 'zhangsan' }

执行效果如下图所示:

image.png

最后

我们借助 Proxy 这个方法,将一些自定义的方法代理到对象中,这样让对象也拥有了数组相关的操作方法,当然上面的方法写的还不全,如果大家有兴趣,可以自己尝试去实现更多数组中的操作方法。

最后,如果这篇文章有帮助到你,❤️关注+点赞❤️鼓励一下作者,谢谢大家

参考文档

MDN Proxy