以这种方式在原型上定义函数的缺点是什么?

Der*_*ang 4 javascript prototype function

通常人们会编写如下代码:

function Shape() {
  this.x = 0;
  this.y = 0;
}

Shape.prototype.move = function(x, y) {
  this.x += x;
  this.y += y;
}
Run Code Online (Sandbox Code Playgroud)

但是,我试图想出一种方法来定义原型上的函数,而不将函数定义与构造函数分开.这是我得到的:

Object.prototype.method = function(name, func) {
  if (typeof(this.constructor.prototype[name]) === 'undefined') {
    this.constructor.prototype[name] = func
  }
}
Run Code Online (Sandbox Code Playgroud)

这允许您执行以下操作:

function Shape() {
  this.x = 0;
  this.y = 0;

  this.method('move', function(x, y) {
    this.x += x;
    this.y += y;
  })
}
Run Code Online (Sandbox Code Playgroud)

而且无论您创建多少次形状,该功能都只会被定义一次.

我知道扩充Object.prototype不被认为是一种好习惯.但除此之外,这种方法有任何缺点吗?

编辑:

约翰提出了一个好点; 我应该做的method不是可枚举的.这是修订版:

Object.defineProperty(Object.prototype, 'method', {
    value: function(name, func) {
        if (typeof(this.constructor.prototype[name]) === 'undefined') {
            this.constructor.prototype[name] = func
        }
    },
    enumerable: false
})
Run Code Online (Sandbox Code Playgroud)

Aad*_*hah 7

让我们实际比较两种方式,看看哪一种更快:http://jsperf.com/traditional-oop-vs-derek-s-oop-variant

如您所见,您的方法比传统方法慢得多.原因是:

  1. 您的构造函数正在执行比所需更多的操作.因此,如果您创建多个实例,则创建实例的额外成本会增加.
  2. 正如@Alxandr提到的那样,method每次创建一个新实例时都会创建一个新的匿名函数.它只会在一次之后才有用,它会浪费处理能力.
  3. 您正在调用一个函数来检查prototype构造函数是否具有给定方法,并将该方法添加到prototypeif中.这似乎没必要.您不需要创建一个函数来为您执行此操作.恕我直言,函数调用只是额外的开销.

既然你要求批评:

我知道扩充Object.prototype不被认为是一种好习惯.但除此之外,这种方法有任何缺点吗?

除了速度非常慢之外,您的方法也会受到以下影响:

  1. 难以理解.您可能会发现这种方法很直观.但是,阅读代码的人肯定会想知道这个功能this.method是做什么的.他们需要阅读Object.prototype.method完全理解您的代码的定义.
  2. 不直观.正如我之前提到的,prototype在构造函数中定义属性是没有意义的.它只需要一次,之后它就会成为额外的行李.最好将构造函数逻辑和prototype属性分开.
  3. 它可能会导致意外行为.正如@basilikum所指出的那样,如果你从不调用构造函数,那么prototype将永远不会设置属性.当您尝试访问该属性时,这可能会导致问题prototype.例如,在prototype调用基本构造函数之前,将继承从no属性继承属性.

我相信您的目标是将构造函数和prototype属性封装到单个实体中:

但是,我试图想出一种方法来定义原型上的函数,而不将函数定义与构造函数分开.

是否有捷径可寻?让我们看看,JavaScript是一种原型面向对象的语言.因此,我们应该更多地关注prototype而不是构造函数.

上图取自以下答案:https://stackoverflow.com/a/8096017/783743

该图显示了我们:

  1. 每个构造函数都有一个名为property的属性prototype,该属性指向构造函数的原型对象.
  2. 每个原型都有一个属性constructor,该属性指向原型对象的构造函数.
  3. 我们从构造函数创建一个实例.但是实例实际上继承自prototype构造函数,而不是构造函数.

这是非常有用的信息.传统上我们总是首先创建一个构造函数,然后我们设置它的prototype属性.但是,这些信息告诉我们,我们可以先创建一个原型对象,然后constructor在其上定义属性.

例如,传统上我们可以写:

function Shape() {
    this.x = 0;
    this.y = 0;
}

Shape.prototype.move = function(x, y) {
    this.x += x;
    this.y += y;
};
Run Code Online (Sandbox Code Playgroud)

然而,使用我们新发现的知识,我们可能会写同样的东西:

var shape = {
    constructor: function () {
        this.x = 0;
        this.y = 0;
    },
    move: function (x, y) {
        this.x += x;
        this.y += y;
    }
};
Run Code Online (Sandbox Code Playgroud)

这两个例子中包含的信息是相同的.但是,我们需要一些额外的脚手架来使第二个例子起作用.特别是我们需要这样做:

var Shape = shape.constructor;
Shape.prototype = shape;
Run Code Online (Sandbox Code Playgroud)

这不是一个大问题.我们只是创建一个函数来为我们这样做:

function defclass(prototype) {
    var constructor = prototype.constructor;
    constructor.prototype = prototype;
    return constructor;
}
Run Code Online (Sandbox Code Playgroud)

现在我们可以定义Shape如下:

var Shape = defclass({
    constructor: function () {
        this.x = 0;
        this.y = 0;
    },
    move: function (x, y) {
        this.x += x;
        this.y += y;
    }
});
Run Code Online (Sandbox Code Playgroud)

正如您所看到的,在JavaScript中很容易实现封装.你需要做的就是侧身思考.然而,继承是一个不同的问题.你需要做更多的工作才能实现继承.

希望这有帮助.