如何设置已经实例化的JavaScript对象的原型?
假设我在JavaScript代码中有一个对象foo
。 foo
是一个复杂的对象,它是在其他地方生成的。 我如何改变foo
对象的原型?
我的动机是将适当的原型设置为从.NET到JavaScript文字序列化的对象。
假设我在ASP.NET页面中编写了以下JavaScript代码。
var foo = <%=MyData %>;
假设MyData
是在Dictionary<string,string>
对象上调用.NET JavaScriptSerializer
的结果。
在运行时,这成为以下内容:
var foo = [{"A":"1","B":"2"},{"X":"7","Y":"8"}];
正如你所看到的, foo
变成了一个对象数组。 我希望能够用适当的原型来初始化foo
。 我不想修改Object.prototype
或Array.prototype
。 我怎样才能做到这一点?
编辑2012年2月:下面的答案不再准确。 __proto__被添加到ECMAScript 6中作为“规范可选”,这意味着它不需要实现,但是如果是,它必须遵循给定的规则集。 目前这个问题还没有解决,但至less它是JavaScript规范的正式组成部分。
这个问题比表面上看起来要复杂得多,在JavaScript内部知识方面超出了大多数人的薪酬等级。
创build对象的新子对象时使用对象的prototype
属性。 改变它并不反映在对象本身中,而是反映在对象被用作其他对象的构造函数时,而不能用于改变现有对象的原型。
function myFactory(){}; myFactory.prototype = someOtherObject; var newChild = new myFactory; newChild.__proto__ === myFactory.prototype === someOtherObject; //true
对象具有指向当前原型的内部[[prototype]]属性。 它的工作方式是每当一个对象的属性被调用时,它将从该对象开始,然后遍历[[prototype]]链直到find匹配,或者在根对象原型之后失败。 这是Javascript如何允许运行时build立和修改对象; 它有一个计划去寻找它需要的东西。
__proto__
属性存在于一些实现中(现在很多):任何Mozilla实现,我知道的所有webkit,还有一些。 该属性指向内部[[prototype]]属性,并允许在对象上进行修改后创build。 由于这种链接查找,任何属性和函数都会立即切换到匹配原型。
这个function虽然现在已经标准化了,但仍然不是JavaScript的必要组成部分,在支持它的语言中,很有可能把你的代码敲入“未优化”的类别。 JS引擎必须尽最大努力对代码进行分类,特别是经常访问的“热门”代码,如果你想做一些像修改__proto__
这样的东西,他们根本不会优化你的代码。
这篇文章https://bugzilla.mozilla.org/show_bug.cgi?id=607863特别讨论;__proto__
当前实现及其差异。 每个实现都不一样,因为这是一个难以解决的问题。 JavaScript中的所有东西都是可变的,除了a。)语法b。)宿主对象(DOM在技术上存在于Javascript之外)和c。) __proto__
。 其余的完全掌握在你和其他开发者手中,所以你可以看到为什么__proto__
像拇指一样伸出手。
有一件事__proto__
允许这是不可能的:在运行时指定一个对象的原型与其构造函数分开。 这是一个重要的用例,也是__proto__
还没有死的主要原因之一。 重要的是它已经成为和谐的一个严肃的讨论点,或者很快被称为ECMAScript 6.在创build过程中指定对象的原型的能力将成为下一个版本的Javascript的一部分,这将是指示__proto__
日子的钟声正式编号。
在短期内,如果你的浏览器支持它(不是IE,IE也不会),你可以使用__proto__
。 未来十年,它可能会在webkit和moz中工作,因为ES6将在2013年才能最终完成。
Brendan Eich – re: ES5中新对象方法的方法 :
对不起,…但可设置的
__proto__
,除了对象初始化用例(即,对于一个新对象不可达,类似于ES5的Object.create),是一个可怕的想法。 我写这个已经在12年前devise和实现了可设置的__proto__
。…缺乏分层是一个问题(考虑使用键
"__proto__"
JSON数据)。 更糟的是,可变性意味着实现必须检查循环原型链,以避免il </s>。 [不断检查无限recursion是必需的]最后,在现有对象上对
__proto__
进行变异可能会破坏新原型对象中的非generics方法,这对于设置了__proto__
的receiver(direct)对象来说是不可能的。 一般来说,这只是不好的做法,一种故意types混淆的forms。
ES6最后指定了已经在Chrome和Firefox中实现的Object.setPrototypeOf(object,prototype) 。
您可以在对象的实例上使用constructor
函数来就地更改对象的原型。 我相信这是你要做的。
这意味着如果你有foo
这是Foo
一个实例:
function Foo() {} var foo = new Foo();
您可以通过执行以下操作来将属性bar
添加到Foo
的所有实例:
foo.constructor.prototype.bar = "bar";
这是一个小提琴展示的概念validation: http : //jsfiddle.net/C2cpw/ 。 不是很确定使用这种方法的旧版浏览器会如何,但是我非常肯定这应该能够很好地完成这项工作。
如果你的目的是把function混入对象中,这个片段应该做这个工作:
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; } }; };
你可以做foo.__proto__ = FooClass.prototype
,AFAIK,它支持Firefox,Chrome和Safari。 请记住, __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__
不推荐使用。
您无法更改已经以跨浏览器方式实例化的JavaScript对象的原型。 正如其他人所说,你的select包括:
- 更改非标准/跨浏览器
__proto__
属性 - 将对象属性复制到一个新的对象
两者都不是特别好,特别是如果你必须recursion地循环一个对象到内部对象中来有效地改变一个元素的整个原型。
问题的替代解决scheme
我将更抽象地看看你想要的function。
基本上,原型/方法只是允许基于对象对function进行分组的方式。
而不是写作
function trim(x){ /* implementation */ } trim(' test ');
你写
' test '.trim();
由于object.method()语法,上面的语法已经被称为术语OOP。 OOP的一些主要优势比传统的函数式编程包括:
- 简短的方法名称和更less的variables
obj.replace('needle','replaced')
vs不得不记住名字str_replace ( 'foo' , 'bar' , 'subject')
和不同variables的位置 - 方法chaining(
string.trim().split().join()
)是一个可能更容易修改和编写嵌套函数join(split(trim(string))
不幸的是,在JavaScript中(如上所示),你不能修改已经存在的原型。 理想情况下,你可以修改Object.prototype
只有上面给定的对象,但不幸的是修改Object.prototype
可能会破坏脚本(导致属性冲突和重写)。
在这两种编程风格之间没有常用的中间地带,也没有用于组织自定义函数的OOP方法。
UnlimitJS提供了一个中间的基础,允许您定义自定义方法。 它避免:
- 属性冲突,因为它不扩展对象的原型
- 仍然允许使用OOP链接语法
- 它是450字节的跨浏览器(IE6 +,Firefox 3.0 +,Chrome,Opera,Safari 3.0+)脚本,Unlimit的大部分JavaScript的原型属性碰撞问题
使用上面的代码我只是简单地创build一个函数的名称空间,你打算调用对象。
这里是一个例子:
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]()
时,它允许函数作为一个对象的方法被调用。 这就像面向对象和function性道路之间的中间地带。
您可以定义您的代理构造函数,然后创build一个新的实例,并将原始对象的所有属性复制到它。
// 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
实例)。
就我所知,你不能改变已经构build的对象的[[prototype]]
引用。 你可以改变原始构造函数的原型属性,但是,正如你已经评论过的那样,构造函数是Object
,改变核心JS结构是一件坏事。
不过,您可以创build构build对象的代理对象,以实现所需的附加function。 您也可以通过直接分配给所讨论的对象来修改附加的方法和行为。
也许你可以用其他方式得到你想要的东西,如果你愿意从一个不同的angular度来看:你需要做什么涉及到原型的混乱?
如果你知道原型,为什么不把它注入代码?
var foo = new MyPrototype(<%= MyData %>);
所以,一旦数据被序列化,你会得到
var foo = new MyPrototype([{"A":"1","B":"2"},{"X":"7","Y":"8"}]);
现在只需要一个以数组为参数的构造函数。
foo.prototype.myFunction = function(){alert("me");}
没有办法真正从Array
或“sub-class”中inheritance它。
你可以做的是这个( 警告: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
这样做会起作用,但是对于任何跨越path的人来说都会造成一定的麻烦(看起来像一个数组,但是如果你尝试操作,会出错)。
你为什么不走理性的路线,直接添加方法/属性到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"}]
如果你想在飞行中创build原型,这是其中一个方法
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);