Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
258 views
in Technique[技术] by (71.8m points)

javascript - What are the downsides of defining functions on prototype this way?

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
})
See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

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.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...