如何设置已经实例化的JavaScript对象的原型?

时间:2011-08-10 18:15:16

标签: javascript function-prototypes

假设我的JavaScript代码中有一个对象foofoo是一个复杂的对象,它是在其他地方生成的。如何更改foo对象的原型?

我的动机是将适当的原型设置为从.NET序列化为JavaScript文字的对象。

假设我在ASP.NET页面中编写了以下JavaScript代码。

var foo = <%=MyData %>;

假设MyData是在JavaScriptSerializer对象上调用.NET Dictionary<string,string>的结果。

在运行时,这将成为以下内容:

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

如您所见,foo成为一个对象数组。我希望能够使用适当的原型初始化foo。我想要修改Object.prototypeArray.prototype。我怎么能这样做?

11 个答案:

答案 0 :(得分:111)

2012年2月编辑:以下答案不再准确。 __proto__作为“规范可选”添加到ECMAScript 6中,这意味着它不需要实现,但如果是,则必须遵循给定的规则集。目前尚未解决,但至少它将成为JavaScript规范的正式部分。

这个问题比表面看起来要复杂得多,超出了大多数人对Javascript内部知识的薪酬等级。

在创建该对象的新子对象时,将使用对象的prototype属性。更改它不会反映在对象本身中,而是反映在该对象用作其他对象的构造函数时,并且无法用于更改现有对象的原型。

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

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

对象具有指向当前原型的内部[[prototype]]属性。它的工作方式是每当调用一个对象上的属性时,它将从该对象开始,然后通过[[prototype]]链向上,直到它找到匹配,或者在根对象原型之后失败。这就是Javascript允许运行时构建和修改对象的方式;它有一个搜索它需要的计划。

__proto__属性存在于某些实现中(现在很多):任何Mozilla实现,我所知道的所有webkit,以及其他一些。此属性指向内部[[prototype]]属性,并允许在对象上创建后创建。由于这种链式查找,任何属性和函数都会立即切换到匹配原型。

此功能虽然现在已经标准化,但仍然不是JavaScript的必需部分,并且支持它的语言极有可能将您的代码压缩到“未优化”类别。 JS引擎必须尽最大努力对代码进行分类,特别是经常访问的“热门”代码,如果你正在做一些像修改__proto__这样的想法,它们根本不会优化代码。

这篇文章https://bugzilla.mozilla.org/show_bug.cgi?id=607863专门讨论了__proto__的当前实现以及它们之间的差异。每个实现都以不同的方式执行,因为它是一个困难且未解决的问题。 Javascript中的所有内容都是可变的,除了a。)语法b。)宿主对象(DOM在技术上存在于Javascript之外)和c。)__proto__。其余部分完全掌握在您和其他所有开发人员手中,因此您可以看到为什么__proto__像拇指一样伸出来。

__proto__允许的一件事是不可能做到的:在运行时指定对象原型与其构造函数分开。这是一个重要的用例,是__proto__尚未死亡的主要原因之一。重要的是它在Harmony的制定中是一个严肃的讨论点,或者很快被称为ECMAScript 6.在创建过程中指定对象原型的能力将成为下一版Javascript的一部分,这将是表示__proto__天的铃声正式编号。

在短期内,如果你的目标是支持它的浏览器(不是IE浏览器,也不会有IE浏览器),你可以使用__proto__。它可能会在未来10年内在webkit和moz中运行,因为ES6将在2013年之前完成。

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

  

抱歉,...但是可设置__proto__,除了对象初始化用例(即,在一个尚未到达的新对象上,类似于ES5的Object.create),这是一个糟糕的主意。我在12年前设计并实现了可设置的__proto__

     

......缺乏分层是一个问题(考虑使用密钥"__proto__"的JSON数据)。更糟糕的是,可变性意味着实现必须检查循环原型链以避免ilooping。 [需要不断检查无限递归]

     

最后,对现有对象进行变异__proto__可能会破坏新原型对象中的非泛型方法,这些方法无法对正在设置__proto__的接收方(直接)对象起作用。这通常是一种不好的做法,一种有意的类型混淆形式。

答案 1 :(得分:30)

ES6最终指定已在Chrome和Firefox中实施的Object.setPrototypeOf(object, prototype)

答案 2 :(得分:14)

您可以在对象的实例上使用constructor来就地更改对象的原型。我相信这就是你要做的事情。

这意味着如果foo是[{1}}的实例:

Foo

您可以通过执行以下操作向function Foo() {} var foo = new Foo(); 的所有实例添加属性bar

Foo

这是一个显示概念验证的小提琴:http://jsfiddle.net/C2cpw/。不是很确定老浏览器将如何使用这种方法,但我很确定这应该可以很好地完成这项工作。

如果您打算将功能混合到对象中,则此代码段应该可以完成以下任务:

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

答案 3 :(得分:9)

您可以执行foo.__proto__ = FooClass.prototype,受Firefox,Chrome和Safari支持的AFAIK。请记住,__proto__属性是非标准属性,可能会在某些时候消失。

文档:https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Object/proto。另请参阅http://www.mail-archive.com/jsmentors@googlegroups.com/msg00392.html,了解为什么没有Object.setPrototypeOf()以及为什么__proto__被弃用的解释。

答案 4 :(得分:3)

您可以定义代理构造函数,然后创建一个新实例并将原始对象的所有属性复制到该实例。

// 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

现场演示: http://jsfiddle.net/6Xq3P/

Custom构造函数表示新原型ergo,其Custom.prototype对象包含您要与原始对象一起使用的所有新属性。

Custom构造函数中,您只需将原始对象中的所有属性复制到新的实例对象。

这个新的实例对象包含原始对象的所有属性(它们在构造函数中被复制到它),以及Custom.prototype中定义的所有新属性(因为新对象是{{1} }实例)。

答案 5 :(得分:3)

您无法更改已以跨浏览器方式实例化的JavaScript对象的原型。正如其他人所提到的,您的选择包括:

  1. 更改非标准/跨浏览器__proto__属性
  2. 将Objects属性复制到新对象
  3. 两者都不是特别好,特别是如果你必须递归地将对象循环到内部对象中以有效地改变整个原型的元素。

    问题的替代解决方案

    我将更加抽象地看一下你想要的功能。

    基本上,原型/方法只允许基于对象对函数进行分组的方法 而不是写

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

    '   test  '.trim();
    

    由于object.method()语法,上面的语法被称为术语OOP。 与传统函数式编程相比,OOP的一些主要优势包括:

    1. 简短的方法名称和更少的变量obj.replace('needle','replaced')与必须记住str_replace ( 'foo' , 'bar' , 'subject')之类的名称以及不同变量的位置
    2. 方法链接(string.trim().split().join())可能更容易修改和编写嵌套函数join(split(trim(string))
    3. 不幸的是,在JavaScript中(如上所示),您无法修改已存在的原型。理想情况下,您可以仅针对上面给定的Object修改Object.prototype,但遗憾的是修改Object.prototype可能会破坏脚本(导致属性冲突和覆盖)。

      这两种编程风格之间没有常用的中间层,也没有OOP方式来组织自定义函数。

      UnlimitJS提供了一个允许您定义自定义方法的中间地带。它避免了:

      1. 属性冲突,因为它不会扩展对象的原型
      2. 仍允许使用OOP链接语法
      3. 这是450字节的跨浏览器(IE6 +,Firefox 3.0 +,Chrome,Opera,Safari 3.0+)脚本,Unlimit的大部分JavaScript的原型属性冲突问题
      4. 使用上面的代码,我只需创建一个您打算针对该对象调用的函数的命名空间。

        以下是一个例子:

        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]();
        

        您可以在此处阅读更多示例UnlimitJS。基本上,当您在函数上调用[Unlimit]()时,它允许将函数作为Object上的方法调用。它就像是OOP和功能性道路之间的中间地带。

答案 6 :(得分:2)

据我所知,您无法更改已构建对象的[[prototype]]引用。您可以更改原始构造函数的prototype属性,但是,正如您已经评论过的那样,该构造函数是Object,并且更改核心JS构造是一件坏事。

您可以创建构造对象的代理对象,以实现您需要的其他功能。您还可以通过直接分配给相关对象来对其他方法和行为进行monkeypatch。

如果你愿意从另一个角度接近,也许你可以通过其他方式获得你想要的东西:你需要做什么,包括弄乱原型?

答案 7 :(得分:1)

如果您知道原型,为什么不在代码中注入它?

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

因此,一旦数据被序列化,你就会得到

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

现在你只需要一个以数组作为参数的构造函数。

答案 8 :(得分:0)

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

答案 9 :(得分:0)

没有办法真正从Array或“子类”继承它。

你能做的就是这个(警告:前面的代码):

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

这样可行,但是对于任何穿过它的路径的人来说都会造成一定的麻烦(它看起来像一个数组,但如果你尝试操作它就会出错)。

为什么不进入理智的路径并将方法/属性直接添加到foo,或者使用构造函数并将数组保存为属性?

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"}]

答案 10 :(得分:0)

如果你想动态创建原型,这就是

之一
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);