开发者

How to set the prototype of a JavaScript object that has already been instantiated?

Suppose I have an object foo in my JavaScript code. foo is a complex object and it is generated somewhere else. How can I change the prototype of the foo object?

My motivation is setting appropriate prototypes to objects serialized from .NET to JavaScript literals.

Suppose that I've written the following JavaScript code within an ASP.NET page.

var foo = <%=MyData %>;

Suppose that MyData is the result of invoking the .NET JavaScriptSerializer on a Dictionary<string,string> object.

At run-time, this becomes the following:

var foo = [{"A":"1","B":"2"},{"X":"7","Y":"8"}];

As开发者_运维知识库 you can see, foo becomes an array of objects. I would like to be able to initialize foo with an appropriate prototype. I do not want to modify the Object.prototype nor Array.prototype. How can I do this?


Update: ES6 now specifies Object.setPrototypeOf(object, prototype)

.

EDIT Feb. 2012: the answer below is no longer accurate. proto is being added to ECMAScript 6 as "normative optional" which means it isn't required to be implemented but if it is, it must follow the given set of rules. This is currently unresolved but at least it will be officially part of JavaScript's specification.

This question is a lot more complicated than it seems on the surface, and beyond most peoples' pay grade in regards to knowledge of Javascript internals.

The prototype property of an object is used when creating new child objects of that object. Changing it does not reflect in the object itself, rather is reflected when that object is used as a constructor for other objects, and has no use in changing the prototype of an existing object.

function myFactory(){};
myFactory.prototype = someOtherObject;

var newChild = new myFactory;
newChild.__proto__ === myFactory.prototype === someOtherObject; //true

Objects have an internal [[prototype]] property which points to the current prototype. The way it works is whenever a property on an object is called it will start at the object and then go up through the [[prototype]] chain until it finds a match, or fail, after the root Object prototype. This is how Javascript allows for runtime building and modification of objects; it has a plan for searching for what it needs.

The __proto__ property exists in some implementations (a lot now): any Mozilla implementation, all the webkit ones I know of, some others. This property points to the internal [[prototype]] property and allows modification post-creation on objects. Any properties and functions will instantly switch to match the prototype due to this chained lookup.

This feature, while being standardized now, still is not a required part of JavaScript, and in languages supporting it has a high likelihood of knocking your code down into the "unoptimized" category. JS engines have to do their best to classify code, especially "hot" code which is accessed very often, and if you're doing something fancy like modifying __proto__, they won't optimize your code at all.

This posts https://bugzilla.mozilla.org/show_bug.cgi?id=607863 specifically discusses current implementations of __proto__ and the differences between them. Every implementation does it differently, because it's a hard and unsolved problem. Everything in Javascript is mutable, except a.) the syntax b.) host objects (the DOM exists outside Javascript technically) and c.) __proto__. The rest is completely in the hands of you and every other developer, so you can see why __proto__ sticks out like a sore thumb.

There is one thing that __proto__ allows for that is otherwise impossible to do: the designation of an objects prototype at runtime separate from its constructor. This is an important use case and is one of the primary reasons __proto__ isn't already dead. It's important enough that it's been a serious discussion point in the formulation of Harmony, or soon to be known as ECMAScript 6. The ability to specify the prototype of an object during creation will be a part of the next version of Javascript and this will be the bell indicating __proto__'s days are formally numbered.

In the short term, you can use __proto__ if you're targeting browsers that support it (not IE, and no IE ever will). It's likely it'll work in webkit and moz for the next 10 years as ES6 won't be finalized until 2013.

Brendan Eich - re:Approach of new Object methods in ES5:

Sorry, ... but settable __proto__, apart from the object initialiser use case (i.e., on a new object not yet reachable, analogous to ES5's Object.create), is a terrible idea. I write this having designed and implemented settable __proto__ over 12 years ago.

... the lack of stratification is a problem (consider JSON data with a key "__proto__"). And worse, the mutability means implementations must check for cyclic prototype chains in order to avoid ilooping. [constant checks for infinite recursion are required]

Finally, mutating __proto__ on an existing object may break non-generic methods in the new prototype object, which cannot possibly work on the receiver (direct) object whose __proto__ is being set. This is simply bad practice, a form of intentional type confusion, in general.


ES6 finally specifies Object.setPrototypeOf(object, prototype) which is already implemented in Chrome and Firefox.


You can use constructor on an instance of an object to alter the prototype of an object in-place. I believe this is what you're asking to do.

This means if you have foo which is an instance of Foo:

function Foo() {}

var foo = new Foo();

You can add a property bar to all instances of Foo by doing the following:

foo.constructor.prototype.bar = "bar";

Here's a fiddle showing the proof-of-concept: http://jsfiddle.net/C2cpw/. Not terribly sure how older browsers will fare using this approach, but I'm pretty sure this should do the job pretty well.

If your intention is to mixin functionality into objects, this snippet should do the job:

function mix() {
  var mixins = arguments,
      i = 0, len = mixins.length;

  return {
    into: function (target) {
      var mixin, key;

      if (target == null) {
        throw new TypeError("Cannot mix into null or undefined values.");
      }

      for (; i < len; i += 1) {
        mixin = mixins[i];
        for (key in mixin) {
          target[key] = mixin[key];
        }

        // Take care of IE clobbering `toString` and `valueOf`
        if (mixin && mixin.toString !== Object.prototype.toString) {
          target.toString = mixin.toString;
        } else if (mixin && mixin.valueOf !== Object.prototype.valueOf) {
          target.valueOf = mixin.valueOf;
        }
      }
      return target;
    }
  };
};


You can do foo.__proto__ = FooClass.prototype, AFAIK that's supported by Firefox, Chrome and Safari. Keep in mind that the __proto__ property is non-standard and might go away at some point.

Documentation: https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Object/proto. Also see http://www.mail-archive.com/jsmentors@googlegroups.com/msg00392.html for an explanation why there is no Object.setPrototypeOf() and why __proto__ is deprecated.


You could define your proxy constructor function and then create a new instance and copy all the properties from the original object to it.

// your original object
var obj = { 'foo': true };

// your constructor - "the new prototype"
function Custom(obj) {
    for ( prop in obj ) {
        if ( obj.hasOwnProperty(prop) ) {
            this[prop] = obj[prop];
        }
    }
}

// the properties of the new prototype
Custom.prototype.bar = true;

// pass your original object into the constructor
var obj2 = new Custom(obj);

// the constructor instance contains all properties from the original 
// object and also all properties inherited by the new prototype
obj2.foo; // true
obj2.bar; // true

Live demo: http://jsfiddle.net/6Xq3P/

The Custom constructor represents the new prototype, ergo, its Custom.prototype object contains all the new properties which you'd like to use with your original object.

Inside the Custom constructor, you just copy all the properties from the original object to the new instance object.

This new instance object contains all the properties from the original object (they were copied to it inside the constructor), and also all the new properties defined inside Custom.prototype (because the new object is a Custom instance).


You can't change the prototype of a JavaScript object that has already been instantiated in a cross browser way. As others have mentioned, your options include:

  1. changing the non standard/cross browser __proto__ property
  2. Copy the Objects properties to a new object

Neither are particularly great, especially if you have to recursively loop through an object into inner objects to effectively change an elements entire prototype.

Alternative solution to question

I'm going to take a more abstract look at the functionality it appears you desire.

Basically prototype/methods just allow for a way to group functions based on an object.
Instead of writing

function trim(x){ /* implementation */ }
trim('   test   ');

you write

'   test  '.trim();

The above syntax has been coined the term OOP because of the object.method() syntax. Some of OOPs main advantage over traditional functional programming includes:

  1. Short methods names and fewer variables obj.replace('needle','replaced') vs having to remember names like str_replace ( 'foo' , 'bar' , 'subject') and the location of the different variables
  2. method chaining(string.trim().split().join()) is a potentially easier to modify and write then nested functions join(split(trim(string))

Unfortunately in JavaScript (as shown above) you can't modify an already existent prototype. Ideally above you could modify Object.prototype for only the given Object's above, but unfortunately modifying Object.prototype would potentially break scripts (resulting in property collision and overriding).

There is no commonly used middle ground between these 2 styles of programming, and no OOP way to organize custom functions.

UnlimitJS provides a middle ground that allows you to define custom methods. It avoids:

  1. Property collision, because it doesn't extend Objects' prototypes
  2. Still allows for an OOP chaining syntax
  3. It is 450 byte cross browser(IE6+,Firefox 3.0+,Chrome,Opera,Safari 3.0+) script that Unlimit's much of JavaScript's prototype property collision issues

Using your code above I would simply create a namespace of functions that you intend to call against the object.

Here is an example:

var foo = [{"A":"1","B":"2"},{"X":"7","Y":"8"}];

// define namespace with methods
var $ = {
  log:function(){
    console.log(this);
    return this;
  }[Unlimit](),
  alert:function(){
    alert(''+this);
  }[Unlimit]()
}


foo[$.log]()
   [$.log]()
   [$.alert]();

You can read more of the examples here UnlimitJS. Basically when you call [Unlimit]() on a function, it allows for the function to be called as a method on an Object. It's like a middle ground between the OOP and functional roads.


You cannot change the [[prototype]] reference of already-constructed objects, as far as I know. You could alter the the prototype property of the original constructor function but, as you've already commented, that constructor is Object, and altering core JS constructs is a Bad Thing.

You could create a proxy object of the constructed object that implements the additional functionality that you need, though. You could also monkeypatch the additional methods and behaviors by assigning directly to the object in question.

Perhaps you can get what you want some other way, if you're willing to approach from a different angle: What do you need to do that involves messing with the prototype?


If you know the prototype, why not injecting it in the code?

var foo = new MyPrototype(<%= MyData %>);

So, once the data is serialized, you get

var foo = new MyPrototype([{"A":"1","B":"2"},{"X":"7","Y":"8"}]);

now you only need a constructor that takes an array as argument.


There is no way to really inherit from Array or "sub-class" it.

What you can do is this (WARNING: FESTERING CODE AHEAD):

function Foo(arr){
  [].push.apply(this, arr)
}
Foo.prototype = []
Foo.prototype.something = 123

var foo = new Foo(<%=MyData %>)

foo.length // => 2
foo[0] // => {"A":"1","B":"2"}
foo.something // => 123

This works, but will cause certain trouble for anyone that crosses it's path (it looks like an array, but things will go wrong if you try manipulating it).

Why don't you go the sane route and add methods/properties directly to foo, or use a constructor and save your array as a property?

function Foo(arr){
  this.items = arr
}
Foo.prototype = {
  someMethod : function(){ ... }
  //...
}

var foo = new Foo(<%=MyData %>)
foo.items // => [{"A":"1","B":"2"},{"X":"7","Y":"8"}]


if you want to create prototype on the fly, this is one of the way

function OntheFlyProto (info){
    this.items = info;
    this.y =-1;
    for(var i = 0; i < this.items.length ; i++){
        OntheFlyProto.prototype["get"+this.items[i].name] = function (){
            this.y++;
            return this.items[this.y].value;
        }
    }
}

var foo = [{name:"one", value:1},{name:"two", value:2}];
v = new OntheFlyProto(foo);


foo.prototype.myFunction = function(){alert("me");}
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜