定义原型属性的正确方法

3
我有一个关于定义原型属性的问题。为了不用在每个属性前面写"MyClass.prototype.",有些人会创建新对象来替换原本的对象。
就像这样:
var MyClass = function() {}

MyClass.prototype = {
    sayHi : function() {
        alert('hi');
    }
}

但是如果这样做,当您尝试从任何实例访问构造函数时可能会导致问题。

var o1 = new MyClass();

alert( o1 instanceof MyClass ); // true
alert( o1.constructor === MyClass ); // false !!!

o1.constructor通常会指向MyClass,但由于原型被更改,它不再这样。

我通过MyClass.prototype.constructor = MyClass;解决了这个问题,并且现在可以正常工作。

问题是,更改原型可能导致什么其他问题?

你如何定义你的原型属性?


“constructor” 应该是不可枚举的。 - SLaks
4个回答

2

我通常使用一个简单的 defclass 函数从内部开始创建 JavaScript 中的 "类":

function defclass(prototype) {
    var constructor = prototype.constructor;
    constructor.prototype = prototype;
    return constructor;
}

该函数允许我创建以下类:
var MyClass = defclass({
    constructor: function () {},
    sayHi: function () {
        alert("hi");
    }
});

这种方法具有以下优点:

  1. 所有原型属性都封装在一个对象字面量中。
  2. constructor函数本身只是另一个原型属性。
  3. 实例始终具有正确的constructor属性。

例如:

var o1 = new MyClass;
alert(o1 instanceof MyClass);      // true
alert(o1.constructor === MyClass); // true

您还可以轻松修改defclass以支持继承:

function defclass(uber, body) {
    var base = uber.prototype;
    var prototype = Object.create(base);
    var constructor = body.call(prototype, base), prototype.constructor;
    constructor.prototype = prototype;
    return constructor;
}

您可以按照以下方式使用它:
var Rectangle = defclass(Object, function () {
    this.constructor = function (width, height) {
        this.height = height;
        this.width = width;
    };

    this.area = function () {
        return this.width * this.height;
    };
});

继承同样也很简单:

var Square = defclass(Rectangle, function (base) {
    this.constructor = function (side) {
        base.constructor.call(this, side, side);
    };
});

一切都按预期运行:

var sq = new Square(5);

alert(sq.area());                 // 25
alert(sq instanceof Square);      // true
alert(sq instanceof Rectangle);   // true
alert(sq.constructor === Square); // true

这就是全部内容。


1
你可以使用合并/扩展函数(通常在 jQuery 或 Lodash 等库中找到):
$.extend(MyClass.prototype, { prop: ..., ... });

1
我可以想象jQuery.extend使用的是“for in循环”,这被认为是不良实践,除非绝对必要,否则应该避免。 - Filip Minx
@Python-:for in循环只有在迭代数组时才是不良实践。对于遍历对象成员,它们是正确、推荐和唯一明智的选择。(只需注意原型) - SLaks

1

constructor 属性仅是一个方便的引用,您可以简单地重新分配它:

MyClass.prototype = {
    constructor: MyClass,
    sayHi : function() {
        alert('hi');
    }
}

重新定义原型对象不应该存在任何其他“问题”,除非您正在扩展具有预定义原型属性的另一个对象。

如果您想在语法上更加大胆,请尝试使用with。我不建议这样做,但如果您只是想缩短语法,这仍然是一种选择:

var MyClass = function() {}

with (MyClass.prototype) {
    sayHi = function() {
        alert('hi');
    }
}

是的,我知道,我在我的问题中已经说过了,但我想知道它是否会引起其他任何问题。 - Filip Minx
“with”结构也被认为是一种不好的编程实践 :) 不过我猜你已经知道了,因为你写的方式表明了这一点 :D - Filip Minx
@Python- 更好的替代方案是意识到构造函数可以像任何其他原型属性一样对待。因此,最清晰的方法是创建一个带有“constructor”属性的原型对象文字,并使用原型来改装构造函数。这可以通过一个简单的三行函数轻松完成。有关更多详细信息,请参见我的答案:https://dev59.com/QXrZa4cB1Zd3GeqP1l96#20644888 - Aadit M Shah

1

我通常选择:

function MyClass() {}

MyClass.prototype = {
    constructor: MyClass,

    foo: function foo() {
        // do something
    }
};

我知道它会覆盖constructor属性,但是这样更容易维护,在实践中,我用这种方式编写JavaScript“类”已经有10年了,也没有发现任何问题。
原生的Object.create方法也可以使用。
一段时间以前,我创建了一个小型的JavaScript库来帮助编写类:Inherit.js 它允许你像这样创建类:
var Point = Object.extend({
    includes: [
        Mixin1,
        Mixin2
    ],
    self: {
        // class or "static" methods and properties go here
    },
    prototype: {
        // instance methods and properties go here
    }
});

var Point3D = Point.extend({ ... });

它还提供了对"mixins"的支持,如果声明如下:
var Mixin = {
    includes: [
        // Yup, mixins can include mixins too
    ],
    included: function(Klass) {
        // Klass is the constructor function that just included this mixin
    },
    self: {
        // class or "static" methods and properties go here
    },
    prototype: {
        // instance methods and properties go here
    }
};

有些人可能会反对将鸭子类型应用于原生类,但我完全支持。

我选择这种方式的原因是:

  • instanceof 操作符仍然有效
  • 我想继承实例和类级别的方法
  • 我希望它能很好地与大多数现有的JavaScript框架配合使用
  • 支持Mixins,这可以是一个方便的功能或者是一个救命稻草,具体取决于情况。

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