Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

简单实现一个数组 #12

Open
hacker0limbo opened this issue Oct 26, 2020 · 0 comments
Open

简单实现一个数组 #12

hacker0limbo opened this issue Oct 26, 2020 · 0 comments
Labels
javascript 原生 JavaScript 笔记整理

Comments

@hacker0limbo
Copy link
Owner

本文参考了 Creating your own implementation of Array, 加入了自己的补充和修改. 是一个精简版, 如果希望看原文可以点击原链接, 原文还有配套视频

先看一下最后实现的结果:

const friends = List("Jordyn", "Mikenzi");

friends.push("Joshy"); // 3
friends.push("Jake"); // 4

friends.pop(); // Jake

friends.filter(friend => friend.charAt(0) !== "J"); // ['Mikenzi']

friends.map(friend => `Hello ${friend}`); // ["Hello Jordyn", "Hello Mikenzi", "Hello Joshy"]

friends.reduce((result, friend) => result + `${friend}, `, "friends: "); // friends: Jordyn, Mikenzi, Joshy,
console.log(friends);
/*
  {
    0: 'Jordyn',
    1: 'Mikenzi',
    2: 'Joshy',
    length: 3,
    push: fn,
    pop: fn,
    filter: fn,
    map: fn,
    reduce: fn
  }
*/

首先要明确二点:

  • 为了防止命名冲突, 我将构造器命名为 List (列表), 实际上用任何名字都行...
  • 实现的过程中不应该使用任何 Array.prototype 上的任何方法, 但是可以使用基于对象上的方法

思路

首先思考一下, 在 JavaScript 中 Array 到底是啥. 我们可以用 typeof 来测试一下:

const arr = []
typeof arr // "object"

所以数组其实也是一个对象. 他可以看成是一个键为类似数字类型的(numerical), 且有一个 length 属性的对象. 同时这个对象还配有一些方法, 比如 .push(), .pop(), 具体类比如下:

const friendsArray = ['Jake', 'Jordyn', 'Mikenzi']
const friendsObj = { 0: 'Jake', 1: 'Jordyn', 2: 'Mikenzi' }

friendsArray[1] // Jordyn
friendsObj[1] // Jordyn

第一步

实现第一步目标:

  • 创建一个函数, 返回一个拥有 .length 属性的对象
  • 返回的对象其原型链委托在该函数上(可以想象成该函数就是一个类)

我们可以使用 Object.create() 来实现需求:

function List() {
  const array = Object.create(List.prototype)
  array.length = 0

  return array
}

第二步

List() 函数接收任意数量的参数作为 array 的元素, 我们可以用 ...args 来获取并实现, 但是现在假设的情况是没有数组及配套方法...所以用 arguments 以及 for...in 方法遍历传入的所有参数元素

function List() {
  let array = Object.create(List.prototype)
  array.length = 0

  for (key in arguments) {
    array[key] = arguments[key]
    array.length += 1
  }

  return array
}

const friends = List('Jake', 'Mikenzi', 'Jordyn')
friends[0] // Jake
friends[2] // Jordyn
friends.length // 3

第三步

还需要实现对应的方法, 包括 .push(), .pop(), .map(), filter().reduce(), 这些方法分别在 Listprototype 上实现:

List.prototype.push = function() {

}

List.prototype.pop = function() {

}

List.prototype.filter = function() {

}

// ...

.push()

先来实现 .push(), 注意两点:

  • 这里的 this 指向的是实例对象(也就是 List() 之后 return 出来的东西)
  • .push() 最后返回的是当前数组的长度
List.prototype.push = function(element) {
  this[this.length] = element;
  this.length++;
  return this.length;
};

.pop()

.push() 很像

List.prototype.pop = function() {
  this.length--;
  const elementToRemove = this[this.length];
  delete this[this.length];
  return elementToRemove;
};

.filter()

List.prototype.filter = function(callback) {
  const result = List();

  for (const index in this) {
    // 由于 for ...in 会遍历包括原型链上的键, 使用 .hasOwnProperty() 阻止遍历 prototype 上的方法
    if (this.hasOwnProperty(index)) {
      const element = this[index];
      if (callback(element, index, this)) {
        result.push(element);
      }
    }
  }
  return result;
};

实际上到目前为止, 上面的代码都是存在一点问题的, 只不过在实现 .filter() 的时候, 当使用 for...in 会暴露出这个问题

测试一下:

const friends = List('Jake', 'Jordyn', 'Mikenzi')

friends.filter((friend) => friend.charAt(0) !== 'J')

/* Breakdown of Iterations*/

1) friend is "Jake". The callback returns false
2) friend is "Jordyn". The callback returns false
3) friend is "Mikenzi". The callback returns true
4) friend is "length". The callback throws an error

问题在于, 使用 for...in 的时候, 由于 length 也为一个可遍历属性(enumerable), 所以也会被遍历到. (回顾之前的实现, 我们只是简单的设置了 array.length = 0). 因此我们需要加一些限制使得 .length 属性无法被遍历. 这里可以用 Reflect.defineProperty() 方法, 进行元编程修改属性的属性.

function List() {
  const array = Object.create(List.prototype);

  // 阻止遍历 length 属性
  Reflect.defineProperty(array, "length", {
    value: 0,
    enumerable: false,
    writable: true
  });

  for (const key in arguments) {
    array[key] = arguments[key];
    array.length += 1;
  }

  return array;
}

.map() 和 .reduce()

后续还有 .map().reduce(), 实现思路都是类似的. 这里我简单写了一下自己的实现方法, 仅供参考. 完整代码如下:

function List() {
  const array = Object.create(List.prototype);

  // 阻止遍历 length 属性
  Reflect.defineProperty(array, "length", {
    value: 0,
    enumerable: false,
    writable: true
  });

  for (const key in arguments) {
    array[key] = arguments[key];
    array.length += 1;
  }

  return array;
}

List.prototype.push = function(element) {
  this[this.length] = element;
  this.length++;
  return this.length;
};

List.prototype.pop = function() {
  this.length--;
  const elementToRemove = this[this.length];
  delete this[this.length];
  return elementToRemove;
};

List.prototype.filter = function(callback) {
  const result = List();

  for (const index in this) {
    // 阻止遍历 prototype 上的 方法
    if (this.hasOwnProperty(index)) {
      const element = this[index];
      if (callback(element, index, this)) {
        result.push(element);
      }
    }
  }
  return result;
};

List.prototype.map = function(callback) {
  const result = List();

  for (const index in this) {
    if (this.hasOwnProperty(index)) {
      const element = this[index];
      result.push(callback(element, index, this));
    }
  }
  return result;
};

List.prototype.reduce = function(callback, initialValue) {
  let startPoint = typeof initialValue !== "undefined" ? 0 : 1;
  let result = typeof initialValue !== "undefined" ? initialValue : this[0];
  for (const i in this) {
    const index = parseInt(i) + startPoint;
    if (this.hasOwnProperty(index) && index < this.length) {
      const element = this[index];
      result = callback(result, element, index, this);
    }
  }

  return result;
};

参考

@hacker0limbo hacker0limbo added the javascript 原生 JavaScript 笔记整理 label Oct 26, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
javascript 原生 JavaScript 笔记整理
Projects
None yet
Development

No branches or pull requests

1 participant