JavaScript:将自定义 properties/methods 添加到内置对象(例如数组)的最佳方式

JavaScript: Best way to add custom properties/methods to built-in objects (e.g. arrays)

众所周知,您不应自定义 JavaScript 中的内置原型。但是,将自定义 properties/methods 添加到论文 classes 的特定实例的最佳方法是什么?需要考虑的是性能、干净的代码、易于使用、drawbacks/limitations 等...

我想到了以下4种方法,你可以随意展示你自己的。在示例中,我只有 1 个数组和 2 个要添加的方法,但问题是关于许多对象有很多附加 properties/methods.

第一种方式:直接向对象添加方法

let myArray1 = [1,2,3,2,1];
myArray1.unique = () => [...new Set(myArray1)];   
myArray1.last = () => myArray1.at(-1);

第二种方法,创建一个新的(子)class

class superArray extends Array{
    constructor() {
        super();
    } 
    unique(){return [...new Set(this)]}
    last(){return this.at(-1)}
}
let myArray2 = superArray.from([1,2,3,2,1]);

(更新了上面的例子,回应@Bergi的评论)

第三种方式,Object.assign

let superArrayMethods = {
    unique: function(){return [...new Set(this)]},
    last: function(){return this.at(-1)}
}
let myArray3 = [1,2,3,2,1];
myArray3 = Object.assign(myArray3, superArrayPrototype);

第4种方法,设置数组原型en增强原型的原型

let superArrayPrototype = {
    unique: function(){return [...new Set(this)]},
    last: function(){return this.at(-1)}
}
Object.setPrototypeOf(superArrayPrototype,Array.prototype);

let myArray4 = [1,2,3,2,1];
Object.setPrototypeOf(myArray4,superArrayPrototype);

在 Chrome 控制台中,我们看到四个结果:

问题!

向内置对象的特定实例添加自定义 properties/methods 的最佳方法是什么?为什么?

请记住,问题是关于创建具有(相同)许多功能的许多 objects/arrays(当然,这将是 constructed/enhanced 一起/以有组织的方式,而不是一个接一个就像在这些简化的例子中一样!)

同样的答案是否适用于其他内置对象,例如 HTML 元素?将自定义 properties/methods 添加到 HTML 元素的最佳方法是什么? (也许换个话题?)

方法一和方法三作用​​相同。您只是针对实例化数组定义函数。如果您追求速度,只需坚持使用方法 1 或 3。事实证明 类 从数组扩展和从数组原型(这在引擎盖下是同一件事)在涉及大量数据时非常慢.小案例应该没问题。我自己似乎也找不到关于这个主题的太多信息。但希望这是进一步测试的良好起点。完成测试可能需要 10 多秒,所以请耐心等待...

//Method 1
let myArray1 = [];
myArray1.unique = () => [...new Set(myArray1)];   
myArray1.last = () => myArray1.at(-1);
//Method 2
class superArray extends Array {
    constructor(...items) { super(...items) } 
    unique(){return [...new Set(this)]}
    last(){ return this.at(-1)}
}
let myArray2 = new superArray();
//Method 3
let superArrayMethods = {
    unique: function(){return [...new Set(this)]},
    last: function(){return this.at(-1)}
}
let myArray3 = [];
myArray3 = Object.assign(myArray3, superArrayMethods);
//Method 4
let superArrayPrototype = {
    unique: function(){return [...new Set(this)]},
    last: function(){return this.at(-1)}
}
Object.setPrototypeOf(superArrayPrototype,Array.prototype);
let myArray4 = [];
Object.setPrototypeOf(myArray4,superArrayPrototype);

//Timers
console.time("myArray1")
for(i=0; i<10000000; i++) { myArray1.push(i); }
console.timeEnd("myArray1")

console.time("myArray2")
for(i=0; i<10000000; i++) { myArray2.push(i); }
console.timeEnd("myArray2")

console.time("myArray3")
for(i=0; i<10000000; i++) { myArray3.push(i); }
console.timeEnd("myArray3")

console.time("myArray4")
for(i=0; i<10000000; i++) { myArray4.push(i); }
console.timeEnd("myArray4")


console.time("unique myArray1")
myArray1.unique();
console.timeEnd("unique myArray1")

console.time("unique myArray2")
myArray2.unique();
console.timeEnd("unique myArray2")

console.time("unique myArray3")
myArray3.unique();
console.timeEnd("unique myArray3")

console.time("unique myArray4")
myArray4.unique();
console.timeEnd("unique myArray4")

数组多,项少

根据要求,这是一个类似的测试,但是我们现在正在测试具有较少项目的多个数组是否对速度有任何影响。

下面的代码似乎展示了类似的结果; (打开开发人员工具 (F12) 以查看更易于阅读的 table 结果。

像任何测试脚本一样,总是运行它们几次来验证;

//Method 1
let method1 = () => {
  let myArray1 = []
  myArray1.unique = () => [...new Set(myArray1)];   
  myArray1.last = () => myArray1.at(-1);
  return myArray1;
}

//Method 2
class superArray extends Array {
    constructor(...items) { super(...items) } 
    unique(){return [...new Set(this)]}
    last(){ return this.at(-1)}
}
let method2 = () => {
    return new superArray();
}

//Method 3
let superArrayMethods = {
    unique: function(){return [...new Set(this)]},
    last: function(){return this.at(-1)}
}
let method3 = () => {
    let myArray3 = [];
    myArray3 = Object.assign(myArray3, superArrayMethods);
  return myArray3;
}

//Method 4
let superArrayPrototype = {
    unique: function(){return [...new Set(this)]},
    last: function(){return this.at(-1)}
}
Object.setPrototypeOf(superArrayPrototype,Array.prototype);
let method4 = () => {
    let myArray4 = [];
    Object.setPrototypeOf(myArray4,superArrayPrototype);
  return myArray4;
}

let results = {};
let arrayBuilder = (method) => { 
    let preDefinedObjectsArray = Array.from({ length: itemsPerArray }, () => ({ test: "string" }));
    results[method.name] = { Creation: 0, Population: 0, CallingFunction:0 }
  //Test method array creation speed;
    let t0 = performance.now();
  for(let i = 0; i < numberOfArrays; i++) {
    let x = method();
  }
  let t1 = performance.now();
  results[method.name].Creation = (t1 - t0).toFixed(4);
  
  //Create an array of all arrays to test adding items and calling it's functions;
  let tmpArray = Array.from({ length: numberOfArrays }, () => (method()));
  
  //Test array population speed;
  t0 = performance.now();
  tmpArray.forEach(a => preDefinedObjectsArray.forEach(o => a.push(o)));
  t1 = performance.now();   
  results[method.name].Population = (t1 - t0).toFixed(4);
   
  //Test function calling on array;
  t0 = performance.now();
  tmpArray.forEach(a => a.unique());
  t1 = performance.now();   
  results[method.name].CallingFunction = (t1 - t0).toFixed(4);
  
  tmpArray = null;
  
}

const itemsPerArray = 100;
const numberOfArrays = 100000;
console.log(`Running test - Creating ${numberOfArrays} arrays with ${itemsPerArray} items per array per method. Be patient...`);
setTimeout(_ => { 
    [method1, method2, method3, method4].forEach(m => arrayBuilder(m));
    console.table(results);
  console.log(results);
  console.log('Open Console for clearer view of results (F12)')
},100);
.as-console-wrapper { max-height: 100% !important; }