使用"Object.create"代替"new"

391

Javascript 1.9.3 / ECMAScript 5 引入了 Object.create,Douglas Crockford 等人长期以来一直在 倡导。如何使用 Object.create 替换下面代码中的 new

var UserA = function(nameParam) {
    this.id = MY_GLOBAL.nextId();
    this.name = nameParam;
}
UserA.prototype.sayHello = function() {
    console.log('Hello '+ this.name);
}
var bob = new UserA('bob');
bob.sayHello();

(假设存在MY_GLOBAL.nextId)。

我能想到的最好解决办法是:

var userB = {
    init: function(nameParam) {
        this.id = MY_GLOBAL.nextId();
        this.name = nameParam;
    },
    sayHello: function() {
        console.log('Hello '+ this.name);
    }
};
var bob = Object.create(userB);
bob.init('Bob');
bob.sayHello();

看起来没有任何优势,所以我认为我没有理解它。我可能过于新古典主义了。如何使用 Object.create 来创建用户“bob”?


当被接受的答案的投票数少于问题本身时,也许这个被接受的答案并不可接受?@CMS会让你编写一个工厂函数,该函数在内部使用Object.create来获得与new UserA('bob')相同的单步功能。 - Rick Jolly
1
或许这是可接受的答案,因为它获得了6个答案中最多的投票。 - Cory Gross
1
Crockford曾是JS世界中的早期指导智者,但他的全盛时期已经过去。尽管Object.create在其他方面很有用,但他建议完全停止使用new从未得到广泛采纳。 - Andy
@Andy 这取决于你使用的编程范式。如果你在 JS 中使用函数式编程,你更喜欢使用 Object.create 而不是 new 关键字。 - Anastasis
15个回答

3

new操作符

  • 用于从构造函数中创建对象
  • new关键字也会执行构造函数
function Car() {
  console.log(this) // this points to myCar
  this.name = "Honda";
}

var myCar = new Car()
console.log(myCar) // Car {name: "Honda", constructor: Object}
console.log(myCar.name) // Honda
console.log(myCar instanceof Car) // true
console.log(myCar.constructor) // function Car() {}
console.log(myCar.constructor === Car) // true
console.log(typeof myCar) // object

Object.create

  • 你也可以使用 Object.create 来创建一个新的对象
  • 但它不会执行构造函数
  • Object.create 用于从另一个对象创建一个对象
const Car = {
  name: "Honda"
}

var myCar = Object.create(Car)
console.log(myCar) // Object {}
console.log(myCar.name) // Honda
console.log(myCar instanceof Car) // ERROR
console.log(myCar.constructor) // Anonymous function object
console.log(myCar.constructor === Car) // false
console.log(typeof myCar) // object


1
好的,发现了一个错别字。现在已经修复了。 - Shardul

3
你需要制作一个自定义的Object.create()函数。这个函数需要解决Crockford的担忧并且调用你的初始化函数。
以下是可行的代码:
var userBPrototype = {
    init: function(nameParam) {
        this.name = nameParam;
    },
    sayHello: function() {
        console.log('Hello '+ this.name);
    }
};


function UserB(name) {
    function F() {};
    F.prototype = userBPrototype;
    var f = new F;
    f.init(name);
    return f;
}

var bob = UserB('bob');
bob.sayHello();

UserB类似于Object.create,但是可以根据我们的需求进行调整。

如果需要,您也可以调用:

var bob = new UserB('bob');

1
为什么不让UserB直接说var f = Object.create(userPrototype); f.init(name); return f;呢? - Daniel Earwicker
init 可能会被多次调用。没有任何阻止这种情况的措施。 - oligofren

3
newObject.create有不同的用途。new旨在创建对象类型的新实例。Object.create旨在仅创建一个新对象并设置其原型。为什么这很有用?为了实现继承而不访问__proto__属性。对象实例的原型被称为[[Prototype]],是虚拟机的内部属性,不应直接访问。实际上可以直接访问[[Prototype]]作为__proto__属性的唯一原因是它一直是每个主要虚拟机对ECMAScript的实现的事实标准,并且此时删除它将破坏大量现有代码。
针对7ochem的上面的答案,对象的原型绝不能设置为new语句的结果,不仅因为没有调用多次相同的原型构造函数,而且因为如果在创建后修改了其中一个实例的原型,则两个相同类的实例可能会有不同的行为。由于误解并打破原型继承链的预期行为,两个示例都只是坏代码。
在不访问__proto__的情况下,应在使用Object.create或之后使用Object.setPrototypeOf创建实例的原型,并使用Object.getPrototypeOfObject.isPrototypeOf读取。
此外,正如Mozilla对Object.setPrototypeOf的文档指出的那样,修改对象的原型在创建后是一个坏主意,除了性能问题之外,还因为如果可以在修改原型之前或之后执行访问它的给定代码,则修改对象的原型可能会导致未定义的行为,除非该代码非常小心地检查当前原型或不访问任何在两者之间不同的属性。
给定以下代码:

const X = function(v) { this.v = v }; X.prototype.whatAmI = 'X'; X.prototype.getWhatIAm = () => this.whatAmI; X.prototype.getV = () => this.v;

以下VM伪代码等同于语句const x0 = new X(1);:

const x0 = {}; x0.[[Prototype]] = X.prototype; X.prototype.constructor.call(x0, 1);

请注意,尽管构造函数可以返回任何值,但new语句总是忽略其返回值并返回对新创建的对象的引用。

以下伪代码等同于语句const x1 = Object.create(X.prototype);:

const x0 = {}; x0.[[Prototype]] = X.prototype;

如您所见,两者之间唯一的区别在于Object.create不执行构造函数,如果未另行指定,则只返回新对象引用this
现在,如果我们想要创建一个定义如下的子类Y:

const Y = function(u) { this.u = u; } Y.prototype.whatAmI = 'Y'; Y.prototype.getU = () => this.u;

那么我们可以这样从X继承:

Y.prototype.__proto__ = X.prototype;

虽然可以在不写入__proto__的情况下完成相同的操作:

Y.prototype = Object.create(X.prototype); Y.prototype.constructor = Y;

在后一种情况下,需要设置原型的构造函数属性,以便通过new Y语句调用正确的构造函数,否则new Y将调用函数X。如果程序员确实想要new Y调用X,则最好在Y的构造函数中使用X.call(this, u)

3

虽然 Douglas Crockford 曾经是 Object.create() 的狂热支持者,实际上他是 JavaScript 中这个构造函数存在的原因,但他不再持有这种观点。

他停止使用 Object.create 是因为完全停止使用 this 关键字会带来太多麻烦。例如,如果你不小心,它很容易指向全局对象,这可能会导致非常严重的后果。而且他声称,如果不使用 this,Object.create 就没有意义了。

您可以查看他在 Nordic.js 上发表的 2014 年视频:

https://www.youtube.com/watch?v=PSGEjv3Tqo0

enter image description here


2

我更喜欢闭包的方法。

我仍然使用new,因为我喜欢它的声明性质。我不使用Object.createthis

对于简单的继承,可以考虑使用这种方法。

window.Quad = (function() {

    function Quad() {

        const wheels = 4;
        const drivingWheels = 2;

        let motorSize = 0;

        function setMotorSize(_) {
            motorSize = _;
        }

        function getMotorSize() {
            return motorSize;
        }

        function getWheelCount() {
            return wheels;
        }

        function getDrivingWheelCount() {
            return drivingWheels;
        }
        return Object.freeze({
            getWheelCount,
            getDrivingWheelCount,
            getMotorSize,
            setMotorSize
        });
    }

    return Object.freeze(Quad);
})();

window.Car4wd = (function() {

    function Car4wd() {
        const quad = new Quad();

        const spareWheels = 1;
        const extraDrivingWheels = 2;

        function getSpareWheelCount() {
            return spareWheels;
        }

        function getDrivingWheelCount() {
            return quad.getDrivingWheelCount() + extraDrivingWheels;
        }

        return Object.freeze(Object.assign({}, quad, {
            getSpareWheelCount,
            getDrivingWheelCount
        }));
    }

    return Object.freeze(Car4wd);
})();

let myQuad = new Quad();
let myCar = new Car4wd();
console.log(myQuad.getWheelCount()); // 4
console.log(myQuad.getDrivingWheelCount()); // 2
console.log(myCar.getWheelCount()); // 4
console.log(myCar.getDrivingWheelCount()); // 4 - The overridden method is called
console.log(myCar.getSpareWheelCount()); // 1

欢迎提出反馈意见。


1
这很有趣。你能否扩展一下例子,展示如何实现setter方法?还是Object.freeze()会阻止它? - jk7
1
冻结只是停止“类”的运行时修改。我已经扩展了父级示例,使用闭包中的局部变量、getter和setter来展示如何管理闭包中的“私有”变量。 - p0wdr.com
关于反馈,能否提供一个创建实例的代码示例?我感觉这是“服务器”端的代码,没有给出“客户端”代码。 - S Meaden
实例的创建发生在以下代码示例的末尾:let myQuad = new Quad();let myCar = new Car4wd();这是客户端代码,它使用window对象来存储类型/类。 - p0wdr.com

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接