javascript构造函数和object.create可以组合使用吗

Can javascript constructor function and object.create be combined?

本文关键字:组合 构造函数 object create javascript      更新时间:2023-09-26

更新

如果无法做到这一点,请随时提供解释原因的答案。我很乐意标记它已被接受。


我想稍微简化以下代码(对于对象"声明",我希望有两个步骤):

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不是函数"。

尽管我意识到我可以用语法糖(一个辅助库)来实现这一点,但我在这里的目标是尽可能简单,并对JS对象、原型和构造函数内部有一些了解。我试着尽可能多地阅读:与SO相关的问题,Crockford,Ben Nadel,Joost Diepenmaat。

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

当然,我可以接受两步流程(Masher)。把所有东西都包装在一个镜头里,感觉很好(巴舍尔)。

有办法做到这一点吗?谢谢

如果您想使用基于类的方法,可以用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的哲学,即只使用对象并从中派生其他对象(阅读维基百科上关于它的文章,并确保从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'});

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

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

我在这里输入了一个答案,但我不确定这是否完全回答了我的问题,所以我不会将其标记为答案,因为我不相信我完全理解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解释器/编译器中有一些深层次的关系,并且该语言不能提供我想要做的事情。