javascript 函数 数组-JS系列2-如何使用对象作为形参

如何使用对象作为形式参数?

我们知道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 - 布尔陷阱以及如何预防它们