Javascript 没有这个的工厂函数,新的或原型

Javascript factory functions without this, new or prototype

我越来越频繁地使用这种奇怪的(至少对我而言)方法,我开始喜欢它,但与此同时,我不确定是否有什么可怕的错误大局背景。

简化示例:

function createCat(initName) {
  let name = initName;
  const getName = () => name;
  function setName(newName) {
    name = newName;
  }
  function meow() {
    console.log("MEOW");
  }
  return {
    meow,
    setName,
    getName
  };
}

function createSuperCat(name) {
  const proto = createCat(name);
  const getName = () =>
    `Super secret! But hey - they used to call himself ${name}`;
  const getSecretName = () => proto.getName();
  function bark() {
    console.log("WHOOF");
  }
  return { ...proto, bark, getName, getSecretName };
}

const someCat = createCat("Hugo");
const someSuperCat = createSuperCat("Fluffy");

// someCat
console.log(someCat.getName()); // Hugo
someCat.setName("Tom");
console.log(someCat.getName()); // Tom
someCat.meow(); // MEOW

// someSuperCat
console.log(someSuperCat.getName()); // Super secret! But hey - they used to call him Fluffy
console.log(someSuperCat.getSecretName()); // Fluffy
someSuperCat.setName("Mittens");
console.log(someSuperCat.getSecretName()); // Mittens
console.log(someSuperCat.getName()); // Super secret! But hey - they used to call him Fluffy
someSuperCat.meow(); // MEOW
someSuperCat.bark(); // WHOOF

我知道没有唯一正确的方法,如果可行,这就是有效的解决方案。

但是 - 我只是想知道...您是否看到这种方法有任何注意事项?

到目前为止,它似乎可以做所有奇特的事情,比如继承、扩展或覆盖继承的方法,你可以省略旧式 OOP 的 this,你不需要不必使用 new 关键字实例化标准工厂函数,没有 prototype 模式(好吧,实际上有点 - 因为它是 Javascript,总会有),没有任何 classes 或其他语法糖。

我可能是错的。我不是真正的程序员之类的,所以我很感激有经验的人的意见,因为我可能遗漏了一些可能会在以后引起不适的东西。

因此,这种方法最大的警告之一是,首先,给您的代码增加了不必要的复杂性,因为 JS 已经为您正在做的事情提供了解决方案,因此您通过这种方式并没有真正实现任何目标。但是,随着对象数量的增加,这种方法的效率也会大大降低,并且会浪费内存。原型继承和 this 关键字的一个好处是所有函数都可以存储在单个对象引用中

您的方法重新定义了每个对象的所有函数。因此,每次您创建一个新的 cat 对象时,您都在重新定义所有通用函数。每个对象都存储在内存中。

即使您想避免 类 而使用 new,最好利用原型继承和 this 关键字。

我重写了你的代码:

const catProto = {
  getName: function () {
    return this.name;
  },
  setName: function (newName) {
    this.name = newName;
  },
  meow: function () {
    console.log("MEOW");
  },
};

function createCat(name) {
  const cat = Object.create(catProto);
  cat.name = name;
  return cat;
}

function createSuperCat(name) {
  const cat = createCat(name);
  cat.getName = () => `Super secret...`;
  cat.getSecretName = catProto.getName;
  cat.bark = () => console.log("WHOOF");
  return cat;
}

const someCat = createCat("Hugo");
const someSuperCat = createSuperCat("Fluffy");

// someCat
console.log(someCat.getName()); // Hugo
someCat.setName("Tom");
console.log(someCat.getName()); // Tom
someCat.meow(); // MEOW

// someSuperCat
console.log(someSuperCat.getName()); // Super secret! But hey - they used to call him Fluffy
console.log(someSuperCat.getSecretName()); // Fluffy
someSuperCat.setName("Mittens");
console.log(someSuperCat.getSecretName()); // Mittens
console.log(someSuperCat.getName()); // Super secret! But hey - they used to call him Fluffy
someSuperCat.meow(); // MEOW
someSuperCat.bark(); // WHOOF

模式很好,只要你意识到像这样的特征丢失:

  • instanceof运算符,
  • Object.getPrototypeOf,
  • prototypeconstructor 属性
  • 没有原型上​​的函数副本

似乎对性能也有影响。

这里是与现代语法中的 OOP 实现的比较。请注意,它使用私有字段,在撰写本文时,并非所有引擎都支持它,尤其是 FireFox。但是例如在 Chrome 它运行良好:

// Parasitic implementation
function createCat(initName) {
  let name = initName;
  const getName = () => name;
  function setName(newName) {
    name = newName;
  }
  function meow() {
    return "MEOW";
  }
  return {
    meow,
    setName,
    getName
  };
}

function createSuperCat(name) {
  const proto = createCat(name);
  const getName = () =>
    `Super secret! But hey - they used to call himself ${name}`;
  const getSecretName = () => proto.getName();
  function bark() {
    return "WHOOF";
  }
  return { ...proto, bark, getName, getSecretName };
}

// OOP implementation
class Cat {
    #name
    constructor(initName) {
        this.#name = initName;
    }
    get name() { 
        return this.#name; 
    }
    set name(newName) {
        this.#name = newName;
    }
    meow() {
        return "MEOW";
    }
}

class SuperCat extends Cat {
    #name
    constructor(initName) {
        super(initName);
        this.#name = initName;
    }
    get name() {
        return `Super secret! But hey - they used to call himself ${super.name}`;
    }
    set name(newName) {
        this.#name = newName;
    }
    get secretName() {
        return this.#name;
    }
    bark() {
        return "WHOOF";
    }
}

function testParasitic() {
    // First produce the output as by OP
    let output = [];
    const someCat = createCat("Hugo");
    const someSuperCat = createSuperCat("Fluffy");

    // someCat
    output.push(someCat.getName()); // Hugo
    someCat.setName("Tom");
    output.push(someCat.getName()); // Tom
    output.push(someCat.meow()); // MEOW

    // someSuperCat
    output.push(someSuperCat.getName()); // Super secret! But hey - they used to call him Fluffy
    output.push(someSuperCat.getSecretName()); // Fluffy
    someSuperCat.setName("Mittens");
    output.push(someSuperCat.getSecretName()); // Mittens
    output.push(someSuperCat.getName()); // Super secret! But hey - they used to call him Fluffy
    output.push(someSuperCat.meow()); // MEOW
    output.push(someSuperCat.bark()); // WHOOF
    console.log(output.join(", "));

    // Then run a performance test:
    let start = performance.now();
    for (let i = 0; i < 1000; i++) {
        const someCat = createCat("Hugo");
        someCat.setName("Tom");
        someCat.getName(); // Tom
        someCat.meow(); // MEOW
        for (let j = 0; j < 100; j++) {
            const someSuperCat = createSuperCat("Fluffy");
            someSuperCat.setName("Mittens");
            someSuperCat.getSecretName();
            someSuperCat.getName();
            someSuperCat.meow();
            someSuperCat.bark();
        }
    }
    return performance.now() - start;
}

function testOOP() {
    // First produce the output as by OP
    let output = [];
    const someCat = new Cat("Hugo");
    const someSuperCat = new SuperCat("Fluffy");

    // someCat
    output.push(someCat.name); // Hugo
    someCat.name = "Tom";
    output.push(someCat.name); // Tom
    output.push(someCat.meow()); // MEOW

    // someSuperCat
    output.push(someSuperCat.name); // Super secret! But hey - they used to call him Fluffy
    output.push(someSuperCat.secretName); // Fluffy
    someSuperCat.name = "Mittens";
    output.push(someSuperCat.secretName); // Mittens
    output.push(someSuperCat.name); // Super secret! But hey - they used to call him Fluffy
    output.push(someSuperCat.meow()); // MEOW
    output.push(someSuperCat.bark()); // WHOOF
    console.log(output.join(", "));

    // Then run a performance test:
    let start = performance.now();
    for (let i = 0; i < 1000; i++) {
        const someCat = new Cat("Hugo");
        someCat.name = "Tom";
        someCat.name;
        someCat.meow();
        for (let j = 0; j < 100; j++) {
            const someSuperCat = new SuperCat("Fluffy");
            someSuperCat.name = "Mittens";
            someSuperCat.secretName;
            someSuperCat.name;
            someSuperCat.meow();
            someSuperCat.bark();
        }
    }
    return performance.now() - start;
}

let dur1 = testParasitic();
let dur2 = testOOP();

console.log("duration parasitic test", Math.round(dur1));
console.log("duration OOP test", Math.round(dur2));