简体   繁体   中英

How are properties shared across instances using javascript prototypes

My understanding of prototypical inheritance is every object has a prototype property. If a property doesn't exist on an object then it's prototype object is checked, so on and so on up the chain.

In this example my prototype object is a simple object with a counter property.

I'm expecting every instance to share the same prototype but they appear to get new instances. The output of this code is 00, I was expecting 01.

Am I missing something obvious?

"use strict";

var ConstructorFunction = function () {};

ConstructorFunction.prototype = {
    counter: 0,
    count: function () {
        return this.counter++;
    }
};

var a = new ConstructorFunction();
var b = new ConstructorFunction();

$("#output").append(a.count());
$("#output").append(b.count());

Here is the jsfiddle: http://jsfiddle.net/hdA6G/5/

It is true that the prototype properties are shares across all instances. The problem is that you never change the prototype property. Take a look at your fiddle with some extra logs:

"use strict";

var ConstructorFunction = function () {};

ConstructorFunction.prototype = {
    counter: 0,
    count: function () {
        return ++this.counter;
    }
};

var a = new ConstructorFunction();
var b = new ConstructorFunction();

$("#output").append(a.hasOwnProperty("counter") + " "); //false
a.count();
$("#output").append(a.hasOwnProperty("counter") + " "); //true

As you see, as soon as you call ++this.counter , a local property will be created which will be used from that on.

I assume that this is what happens:

++this.counter is interpreted as this.counter = this.counter + 1 . First, the part right of the equal sign is evaluated and since your instance doesn't have a counter property, the counter property of the prototype is used. The value of this property will be added to 1 and then assigned to this.counter , which now creates a local property, in the same way that it does, when you assign a property that hasn't been there at all, like a.xy = 1 . xy will be a local property of the instance in that case.

EDIT

There are two workarounds that still would let you use the prototype property:

1) explicitly set the prototype property inside of the count method:

ConstructorFunction.prototype.count = function() {
    return ++this.constructor.prototype.counter;
};

2) call the count method with apply and use the prototype as context:

a.count.apply(a.constructor.prototype);

BUT , if you set the prototype property the way you did, you will get problems with both of these methods.

 ConstructorFunction.prototype = {
     //...
 };

This overrides the complete prototype object and therefore also the constructor property of it. The constructor property will now point to the next higher object in the prototype chain, the Object object. To fix that, you could either set the constructor manually after you assigned the prototype object:

ConstructorFunction.prototype.constructor = ConstructorFunction;

or assign every property of the prototype object seperately:

ConstructorFunction.prototype.counter = 0;
ConstructorFunction.prototype.count = function () {
    return ++this.counter;
};

Consider using a closure so you can keep that variable private and shared across instances:

var Class = (function ClassModule() {

  var counter = 0;

  function Class() {}

  Class.prototype = {

    count: function() {
      return counter++;
    }
  };

  return Class;
}());

var class1 = new Class();
var class2 = new Class();

console.log(class1.count()); //=> 0
console.log(class2.count()); //=> 1

The counter variable is kept in context with the closure (module pattern), and won't be duplicated with each new instance.

In prototype based OOD, sharing means to share the same definition and use it for different contexts. If you need a static property to be shared between contexts you can do as given below

var ConstructorFunction = function (context) {
    this.count = function () {
        return context + "," + (++this.counter) + "," + (++ConstructorFunction.staticCounter);
    };
};

ConstructorFunction.prototype.counter = 0;

ConstructorFunction.staticCounter = 0;

var context1 = new ConstructorFunction("context1");
var context2 = new ConstructorFunction("context2");

$("#output").append(context1.count());
$("#output").append(" ");
$("#output").append(context2.count());

http://jsfiddle.net/hdA6G/1/

and a better way to define it

var ConstructorFunction = function (context) {
    this.context = context;
    this.counter = 0;
};
ConstructorFunction.staticCounter = 0;

ConstructorFunction.prototype.count = function () {
    return this.context + "," + (++this.counter) + "," + (++ConstructorFunction.staticCounter);
};

http://jsfiddle.net/hdA6G/3/

You have your inheritance backwards, the prototype inherits from the base object. I think this is more similar to what you are trying to accomplish.

function MyObject () {
    this.count = 0;
};

ConstructorFunction.prototype = {
    counter: function () {
        return this.count++;
    },
    print: function () {
        return this.count;
    }
};


Var Foo = new MyObject();

console.log(Foo.counter()); // 1
console.log(Foo.counter()); // 2
console.log(Foo.print()); // 2

I hope this helps.

edit

If you want your counter shared across all instances then it should go in you base object.

function MyObject () {
    this.count = 0;
    this.counter = function () {
        return this.count++; //may not need the this
    }
};

I know this is an old post, but I am new to Javascrpt and while experimenting I came across something similar, so wanted to put in my 2 cents.

It seems every object gets its own copy of variables declared in prototype once it is instantiated using new . From then on, accessing using this will do the usual look up and find the private copy which is operated upon.

However, if you create a prototype variable after creation of all objects, that variable will be shared and behaves like static. I think it is fairly simple to reason why this happens, but, nonetheless, I found this to be an interesting hack. I am not sure if this is a bug in technical specs that might be resolved in future versions, or side effect of standard behavior, so don't know if this is reliable. I am not even sure if this is a newly introduced 'feature' in later versions of the language. Actually, I started googling about this fact and came across this post.

Try this code.

var Worker = function (name) {
    this.name = name;
}
Worker.prototype.jobs = 0;
Worker.prototype.Work = function () {
    console.log("jobs finished", this.name, ++this.jobs);
}
Worker.prototype.WorkCount = function () {
    console.log("work count", this.name, this.workCount);
}
var ca = new Worker("ca");
var cb = new Worker("cb");
ca.Work();// shows 1
cb.Work();// shows 1
ca.WorkCount();// shows undefined
cb.WorkCount();// shows undefined
Worker.prototype.workCount = 2;
ca.WorkCount();// shows 2
cb.WorkCount();// shows 2

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM