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


106

假设我的fooJavaScript代码中有一个对象。 foo是一个复杂的对象,它在其他地方生成。如何更改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.prototype,也没有Array.prototype。我怎样才能做到这一点?


您要添加到其现有原型还是将其切换到新原型?
SLaks 2011年

您是说要更改原型-还是实际上要更改原型,就像关闭一个原型并替换为另一个原型一样。我什至不确定后面的情况是否可能。
James Gaunt

2
您是指显式原型属性还是隐式原型链接?(这两个是两个完全不同的东西)
森那维达斯


1
您是否熟悉Backbone extend或Google的goog.inherit?许多开发人员提供了在调用年长的new构造函数之前建立继承的方法,这是在获得经验之前Object.create,而不必担心重写Object.prototype
瑞安

Answers:


114

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

这个问题比表面上看起来要复杂得多,而且就Java组件内部知识而言,这超出了大多数人的薪水等级。

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

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

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

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

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

此功能虽然现在已标准化,但仍不是JavaScript的必需部分,并且在支持该功能的语言中,很有可能会将您的代码归为“未优化”类别。JS引擎必须尽全力对代码进行分类,尤其是经常访问的“热”代码,如果您正在做诸如修改之类的事情__proto__,它们将根本无法优化您的代码。

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

有一件事情是__proto__可以做到的,而这在其他方面是不可能做到的:在运行时指定对象原型与其构造函数分开。这是一个重要的用例,也是__proto__尚未死亡的主要原因之一。非常重要的一点是,这已经成为和声制定中的一个重要讨论点,或者即将被称为ECMAScript6。在创建过程中指定对象原型的能力将成为下一版Javascript的一部分,而这将是指示__proto__日期的铃铛已正式编号。

在短期内,__proto__如果您要针对支持它的浏览器(不支持IE,也永远不会支持IE)使用,则可以使用。由于ES6直到2013年才能最终定稿,因此它可能会在接下来的10年中在webkit和moz中使用。

Brendan Eich -re:ES5中新Object方法的方法

抱歉,但是可设置__proto__,除了对象初始化程序的用例之外(例如,在尚未到达的新对象上,类似于ES5的Object.create),这是一个可怕的想法。我写这是__proto__在12年前设计并实现可设置的。

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

最后,__proto__在现有对象上进行更改可能会破坏新原型对象中的非泛型方法,而该方法可能无法在__proto__正在设置其的接收器(直接)对象上运行。通常,这只是一种不良做法,是一种故意类型混淆的形式。


出色的故障!尽管它与可变原型并不完全相同,但ECMA Harmony可能会实现代理,使您可以使用包罗万象的模式将附加功能填充到特定对象上。
Nick Husher 2011年

2
布伦丹·艾希(Brendan Eich)的问题是对整个语言进行原型设计。non-writable, configurable通过强制用户明确地重新配置属性,可能使__proto__ 可以解决这些问题。最后,不良做法与语言能力的滥用有关,而不是与能力本身有关。可写的__proto__并非闻所未闻。还有许多其他不可数的可写属性,尽管存在危险,但也有最佳实践。锤子的头部不能仅仅因为会不适当地用来伤人而被取下。
2013年

突变__proto__是需要的,因为的Object.create只会产生对象,而不是函数例如,也没有符号,的正则表达式,DOM元素或其他宿主对象。如果您希望对象是可调用的或以其他方式特殊的,但仍更改其原型链,则您将无法设置__proto__或代理而陷入困境
user2451227 2014年

2
最好不要使用神奇的属性,而应使用Object.setPrototype来消除“ __proto__JSON中的”问题。布伦丹·艾希(Brendan Eich)的其他担忧令人可笑。递归原型链或对给定对象使用方法不足的原型是程序员错误,不是语言错误,也不应该是一个因素,此外,它们可能与Object.create以及可自由设置的对象一起发生__proto__
user2451227 2014年

1
IE 10支持__proto__
kzh 2014年


14

您可以constructor在对象的实例上使用,以就地更改对象的原型。我相信这是您要执行的操作。

这意味着如果您拥有foo以下实例Foo

function Foo() {}

var foo = new Foo();

您可以通过执行以下操作将属性添加bar到的所有实例Foo

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

这是显示概念验证的小提琴:http : //jsfiddle.net/C2cpw/。不十分确定使用这种方法的旧版浏览器的性能如何,但是我很确定这应该能很好地完成工作。

如果您打算将功能混入对象中,则此代码段应该可以完成此工作:

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;
    }
  };
};

1
+1:这是有益且有趣的,但并不能真正帮助我获得想要的东西。我正在更新我的问题,使其更加具体。
维维安河

您还可以使用foo.__proto__.bar = 'bar';
Jam Risser


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(因为新对象是一个Custom实例)。


3

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

  1. 更改非标准/跨浏览器__proto__属性
  2. 将对象属性复制到新对象

两者都不是特别出色,特别是如果您必须将对象递归地循环到内部对象中以有效地更改整个原型的元素时。

问题的替代解决方案

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

基本上,原型/方法仅允许基于对象对功能进行分组的方法。
而不是写

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

你写

'   test  '.trim();

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

  1. 简短的方法名称和较少的变量obj.replace('needle','replaced')与必须记住名称(如str_replace ( 'foo' , 'bar' , 'subject')和不同变量的位置)相比
  2. 方法chaining(string.trim().split().join())可能更容易修改和编写然后嵌套函数join(split(trim(string))

不幸的是,在JavaScript中(如上所示),您无法修改已经存在的原型。理想情况下,您只能Object.prototype为上面的给定对象进行修改,但不幸的是,修改Object.prototype可能会破坏脚本(导致属性冲突和覆盖)。

这两种编程风格之间没有通用的中间立场,也没有用于组织自定义功能的OOP方法。

UnlimitJS提供了一个中间立场,可让您定义自定义方法。它避免了:

  1. 属性冲突,因为它不会扩展对象的原型
  2. 仍然允许使用OOP链接语法
  3. 这是450字节跨浏览器(IE6 +,Firefox 3.0 +,Chrome,Opera,Safari 3.0+)脚本,它可以解决很多JavaScript原型属性冲突问题

使用上面的代码,我将简单地创建要针对该对象调用的函数的名称空间。

这是一个例子:

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]()函数时,它允许将该函数作为对象上的方法进行调用。这就像OOP和功能性道路之间的中间地带。


2

[[prototype]]据我所知,您不能更改已构造对象的引用。您可以更改原始构造函数的prototype属性,但是,正如您已经提到的那样,该构造函数是Object,而更改核心JS构造是一件不好的事。

您可以创建构造对象的代理对象,该代理对象实现所需的其他功能。您还可以通过直接分配给所讨论的对象来猴子修补其他方法和行为。

如果您愿意从另一个角度出发,也许您可​​以以其他方式得到您想要的东西:您需要做哪些涉及弄乱原型的事情?


有人能对此发表评论吗?
维维安河

基于这种可怕的jsfiddle,您似乎可以通过更改其__proto__属性来更改现有对象的原型。这仅在支持该__proto__表示法的浏览器(Chrome和Firefox)中有效,并且已弃用。因此,简而言之,您可以更改[[prototype]]对象的,但您可能不应该更改。
Nick Husher 2011年

1

如果您知道原型,为什么不将其注入代码中呢?

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

因此,一旦数据序列化,您将获得

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

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


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

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

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

不,你不能。那是静态属性。
SLaks 2011年

@SLaks prototype是静态的吗?我不确定你是什么意思。
西米·维达斯

1
prototype是函数的属性,而不是对象。 Object.prototype存在 {}.prototype不。
SLaks 2011年

如果您不关心浏览器的兼容性,则可以使用Object.getPrototypeOf(foo)获取构造函数原型对象。您可以修改其属性以修改对象的原型。它仅在最近的浏览器中有效,但是无法告诉您哪些浏览器。
Nick Husher 2011年

@TeslaNick:您可以直接进行修改Object.prototype,因为这很可能Object.getPrototypeOf(foo)会返回。不是很有用。
2011年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.