这种方式在原型上定义函数有什么缺点? [英] What are the downsides of defining functions on prototype this way?

查看:105
本文介绍了这种方式在原型上定义函数有什么缺点?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

通常人们会这样写代码:

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

Shape.prototype.move = function(x,y){
this.x + = x;
this.y + = y;
}

然而,我试图想出一种方法来定义一个函数原型没有将构造函数与函数定义分开。这是我得到的:

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


这允许你做类似的事情:

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

this.method('move',function(x,y){
this.x + = x;
this.y + = y;
} )
}

无论您创建一个形状多少次,函数都会只能定义一次。



我知道增加 Object.prototype 不被认为是一种好的做法。但除此之外,这种方法有什么缺点?



编辑:



Johan提出了一个很好的观点;我应该让方法不可枚举。这里是修改后的版本:


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


解决方案

让我们实际比较两种方法,看看哪一种更快:



下图显示了我们:


  1. 每个构造函数都有一个名为 prototype 的属性,它指向构造函数的原型对象。

  2. 每个原型都有一个名为构造函数的属性,它指向原型对象的构造函数。
  3. 从构造函数中创建一个实例。然而这个实例实际上继承自原型,而不是构造函数。

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



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

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

Shape.prototype.move = function(x,y){
this.x + = x;
this.y + = y;
};

然而,使用我们新发现的知识,我们可能会写下如下内容:

  var shape = {
构造函数:function(){
this.x = 0;
this.y = 0;
},
move:function(x,y){
this.x + = x;
this.y + = y;
}
};

这两个示例中包含的信息都是相同的。然而,我们需要一些额外的脚手架来使第二个例子工作。特别是我们需要:

  var Shape = shape.constructor; 
Shape.prototype = shape;

这不是一个大问题。我们只是创建一个函数来为我们做这件事:

 函数defclass(prototype){
var constructor = prototype。构造;
constructor.prototype = prototype;
返回构造函数;

$ / code>

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

$ $ p $ code $ var Shape = defclass({
构造函数:function(){
this .x = 0;
this.y = 0;
},
move:function(x,y){
this.x + = x;
this .y + = y;
}
});

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



希望这有助于。


Usually people write code like this:

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

Shape.prototype.move = function(x, y) {
  this.x += x;
  this.y += y;
}

However I was trying to come up with a way to define a function on a prototype without separating the function definition with the constructor. Here is what I got:

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

This allows you to do something like:

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

  this.method('move', function(x, y) {
    this.x += x;
    this.y += y;
  })
}

And also no matter how many times you create a shape, the function will only be defined once.

I'm aware that augmenting Object.prototype isn't considered a good practice. But other than that are there any downsides with this approach?

EDIT:

Johan brought up a good point; I should have made method not enumerable. Here is the revised version:

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

解决方案

Let's actually compare both the ways and see which one is faster: http://jsperf.com/traditional-oop-vs-derek-s-oop-variant

As you can see your method is much slower than the traditional method. The reasons are:

  1. Your constructor is doing more stuff than required. Hence if you create multiple instances then the extra cost of creating an instance adds up.
  2. As @Alxandr mentioned you're creating a new anonymous function for method every time you create a new instance for no good reason. It'll only be useful once after which it'll be a waste of processing power.
  3. You're calling a function to check whether the prototype of the constructor has a given method or not, and to add the method to the prototype if it doesn't. This seems unnecessary. You don't need to create a function to do this for you. IMHO the function call is just additional overhead.

Since you asked for criticism:

I'm aware that augmenting Object.prototype isn't considered a good practice. But other than that are there any downsides with this approach?

Beside being terribly slow your approach also suffers from:

  1. Being difficult to understand. You may find this approach intuitive. However a person reading your code would surely wonder what the function this.method does. They would need to read the definition of Object.prototype.method to fully comprehend your code.
  2. Being unintuitive. As I mentioned before it makes no sense to be defining prototype properties inside the constructor. It'll only be needed once after which it'll just become additional baggage. It's best to keep the constructor logic and the prototype properties separate.
  3. It may lead to unexpected behavior. As @basilikum pointed out if you never call the constructor then the prototype properties will never be set. This may lead to problems when you attempt to access a property on the prototype. For example, when inheriting properties from the prototype no properties will be inherited until the base constructor is called.

I believe your goal is to encapsulate both the constructor and the prototype properties into a single entity:

However I was trying to come up with a way to define a function on a prototype without separating the function definition with the constructor.

Is there an easy way to do this? Let's see, JavaScript is a prototypal object oriented language. Hence we should focus more on the prototype instead of the constructor.

The above diagram was taken from the following answer: https://stackoverflow.com/a/8096017/783743

This diagram shows us:

  1. Every constructor has a property called prototype which points to the prototype object of the constructor function.
  2. Every prototype has a property called constructor which points to the constructor function of the prototype object.
  3. We create an instance from a constructor function. However the instance actually inherits from the prototype, not the constructor.

This is very useful information. Traditionally we've always created a constructor function first and then we've set its prototype properties. However this information shows us that we may create a prototype object first and then define the constructor property on it instead.

For example, traditionally we may write:

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

Shape.prototype.move = function(x, y) {
    this.x += x;
    this.y += y;
};

However using our newfound knowledge we may write the same thing as:

var shape = {
    constructor: function () {
        this.x = 0;
        this.y = 0;
    },
    move: function (x, y) {
        this.x += x;
        this.y += y;
    }
};

The information contained in both these examples is the same. However we need a little additional scaffolding to make the second example work. In particular we need to do:

var Shape = shape.constructor;
Shape.prototype = shape;

This is not a big issue. We simply create a function to do this for us:

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

Now we can define Shape as follows:

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

As you can see encapsulation is easy to achieve in JavaScript. All you need to do is think sideways. Inheritance however is a different issue. You need to do a little more work to achieve inheritance.

Hope this helped.

这篇关于这种方式在原型上定义函数有什么缺点?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆