【问题标题】:Can javascript constructor function and object.create be combined?javascript构造函数和object.create可以结合吗?
【发布时间】:2014-10-06 14:32:58
【问题描述】:

更新

如果这不可能,请随时提供解释原因的答案。我很乐意将其标记为已接受。


我想稍微简化一下下面的代码(一个对象“声明”的两个步骤,我想要一个):

var Masher = function(opts) {
    this._name  = opts.name;
};

Masher.prototype = Object.create(Object.prototype, {
    _name:  { writable: true },
    name:  { get: function() { return this._name;  }}
});

// Note: (new Masher({name: 'bar'})).name == 'bar'

我想一次性创建整个函数原型,构造函数出现在 Object.create 中的某处。或许,是这样的:

var Basher = Object.create(Function.prototype, {
    _name:  { writable: true },
    name:  { get: function() { return this._name;  }},
    constructor: { value: function(opts) { this._name = opts.name; }}
});

但是,当我调用 new Basher() 时,我得到:'TypeError: object is not a function'。

虽然我意识到我可以使用语法糖(一个辅助库)来做到这一点,但我的目标是让事情尽可能简单,并对 JS 对象、原型、构造函数内部结构有所了解。我已尝试尽可能多地阅读:SO related questionsCrockfordBen NadelJoost Diepenmaat

也许我没有找到正确的表述,或者我正在与 Object.create 的设计理念作斗争,或者语言不允许这样做。也许,这真的只是一种风格上的东西,因此是一种自负。

当然,我可以接受两步流程 (Masher)。一次将所有内容打包在一个镜头中感觉不错(巴舍尔)。

有没有办法做到这一点?谢谢。

【问题讨论】:

  • 不,你不能。您需要创建两个单独的对象:构造函数和原型对象。

标签: javascript constructor new-operator object-create


【解决方案1】:

如果您想使用基于类的方法,您可以使用new 调用构造函数,您将始终有两个部分:

  • 构造函数本身(用于初始化实例)
  • 原型对象(用于共享属性)

如果你不想完全放弃原型,没有 JavaScript 语法可以一次性完成函数创建和原型设置(当然,除了新的 ES6 class 语法)同时仍然保持从函数到原型对象的.prototype 链接。当然,一个简单的辅助函数(不需要是一个完整的库)就可以了:

function Class(p) {
    return (p.constructor.prototype = p).constructor;
}
var Casher = Class({
    constructor: function(opt) { this._name = opt.name },
    get name() { return this._name }
});
var foo = new Casher({name:'bar'});

这种模式与Object.create 根本没有太大关系(除非您希望您的原型继承自另一个)。

所以是的,也许您正在尝试与 Object.create 的哲学作斗争,即仅使用对象并从中派生其他对象(阅读上面的 Wikipedia article 并确保查看其他语言中的一些示例比 JS)。您不会有构造函数,也不会有 new 运算符 - 而是在对象上调用 create 方法:

var Proto = { // some helper methods (usually native in more prototype-focused languages)
    clone: function() {
        return Object.create(this);
    },
    create: function(opt) {
        var derived = this.clone();
        derived.init(opt);
        return derived;
    },
    init: function(opt) {
        Object.getOwnPropertyNames(opt).forEach(function(p) {
            Object.defineProperty(this, p, Object.getOwnPropertyDescriptor(opt, p));
        }, this);
    }
};

var Pasher = Proto.create({ // "subclass" Proto
    init: function(opt) {
        if ("name" in opt) this._name = opt.name;
    },
    _name: "",
    get name() { return this._name; }
});
var foo = Pasher.create({name:'bar'});

【讨论】:

  • 谢谢。 ES6 类语法指针有帮助。我喜欢 Class 建议,虽然它缺少属性描述符,所以我不确定我会使用它。我阅读了维基百科的文章。没有任何东西可以激发为什么在基于原型的语言中创建对象不需要构造函数,更不用说构造函数的格式良好的语法了。在查看了新的 ES6 类定义之后,对我来说,这似乎是一个语言演变的问题——从其他语言中借鉴了好的想法。我在 Self、Lisp、C++、Java 和 C# 方面的经验让我想要一个有序、明确的类定义。我想我等一下。
【解决方案2】:

Object.create() 返回一个对象,而不是函数,并定义了特定的原型继承链。

var Basher = Object.create(Function.prototype, {
    _name:  { writable: true },
    name:  { get: function() { return this._name;  }},
    constructor: { value: function(opts) { this._name = opts.name; }}
});
> undefined
Basher
> Object {_name: undefined, name: (...)}_name: undefinedconstructor: function (opts) { this._name = opts.name; }name: (...)get name: function () { return this._name;  }__proto__: function Empty() {}
> typeof Basher
"object"

不过,您可以将 Object.create() 和构造函数结合起来,让您将对象字面量作为 API 重用,这使代码看起来更简洁:

var basherAPI = {
    name: function () {
        return this._name;
    }
};

function Basher(name) {
    var inst = Object.create(basherAPI);
    // assign instance *state* here; the API is
    // reusable but each object needs its own state
    inst._name = name;
    return inst;
}

var basher = new Basher('Tom');
basher.name() // Tom

编辑:在这种情况下,使用 new 关键字纯粹是约定;它与构造函数中发生的事情无关。也可以写:var basher = Basher('Tom');

【讨论】:

  • 让 Basher 以大写 B 开头可能会让其他程序员误以为他们可以继承它,但实际上不能。由于它是工厂功能,最好不要以大写开头
  • 是的,很好。尽管Object.create() 仍然可以实现继承:Object.create(basher)。也许将Bahser 重命名为basherFactory 会是更好的方法。
  • @NicholasCloud,我感谢您的努力。也就是说,我知道我可以通过其他功能对其进行微调,使其看起来像一条线。此外,我喜欢“new”运算符,因为它向阅读代码的人发出对象创建信号。将 Factory 添加到函数名称确实会提供类似的信号。但是,这里不适合工厂模式。
【解决方案3】:

我在这里给出了答案,但我不确定这是否完全回答了我的问题,所以我不会将其标记为 答案,因为我不完全相信我了解这部分JS语言。

我相信我能做到的最接近的情况如下:

var Lasher;

(Lasher = function (opts) {
    this._name = opts.name;
}).prototype = Object.create(Object.prototype, {
    _name:  { writable: true },
    name:  { get: function() { return this._name;  }},
});

这个提法暴露了正在发生的部分事情,@Bergi 暗示了一些事情,尽管是简短的。首先,请注意它没有回答我的问题,因为它仍然需要两个步骤(变量声明和值赋值)。它确实触及了我的风格问题的核心,即共同定位所有对象的声明代码。它通过捕获 Lasher 中的匿名函数定义(构造函数),然后引用 prototype 属性来为其分配原型对象(“类”声明的其余部分)。

虽然作为一个计算单元组合在一起,但坦率地说,在视觉上它更难解析并且相当丑陋。此外,匿名函数甚至可能在其范围内捕获 Lasher(我不确定)。

顺便说一句,我尝试了这种有缺陷的方法。这是一种说法,虽然还是有点难看:

(function Crasher(opts) {
    this._name = opts.name;
}).prototype = Object.create(Object.prototype, {
    _name:  { writable: true },
    name:  { get: function() { return this._name;  }},
});

太糟糕了,它不起作用。 Crasher 的作用域是第一个 (),并不存在于外部作用域中,这就是为什么早期的代码必须将 Crasher 声明为变量才能捕获匿名函数的原因。

问题是,从行数或可读性的角度来看,Lasher 并不比 Masher(上图)简单。因此,原始配方是最好的(到目前为止)。

另外,虽然我理解为什么 Object.create 在创建对象时不能使用,但我想我不明白内部 JS 解释器如何创建函数与对象。我的意思是,一个函数是一个对象,但我想没有办法创建一个可以直接从Object.create 返回的函数。这是主要问题。

此外,无论原型赋值和构造函数赋值内部发生了什么,在 Object.create 中添加构造函数属性都不起作用。我们可以通过这段代码看到:

var Masher = function(){ console.log("default");};

Masher.prototype = Object.create(Object.prototype, {
    _name:  { writable: true },
    name:  { get: function() { return this._name;  }},
    constructor: { value: function(opts) {
        console.log("ctor");
        this._name  = opts.name;
    return this;
    }}
});
// "new Masher()" outputs:
// default
// undefined

所以,如果我正确理解了其中的一些内容,那么在此之上的函数创建和原型创建在 JS 解释器/编译器中具有一些深厚的关系,并且该语言没有提供一种方法来做我想做的事情。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-12-30
    • 2023-03-06
    • 2011-04-21
    • 1970-01-01
    相关资源
    最近更新 更多