Làm cách nào để đặt nguyên mẫu của một đối tượng JavaScript đã được khởi tạo?


106

Giả sử tôi có một đối tượng footrong mã JavaScript của mình. foolà một đối tượng phức tạp và nó được tạo ra ở một nơi khác. Làm cách nào để thay đổi nguyên mẫu của foođối tượng?

Động lực của tôi là thiết lập các nguyên mẫu thích hợp cho các đối tượng được tuần tự hóa từ các ký tự .NET sang JavaScript.

Giả sử rằng tôi đã viết mã JavaScript sau trong trang ASP.NET.

var foo = <%=MyData %>;

Giả sử đó MyDatalà kết quả của việc gọi .NET JavaScriptSerializertrên một Dictionary<string,string>đối tượng.

Tại thời điểm chạy, điều này trở thành như sau:

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

Như bạn có thể thấy, footrở thành một mảng các đối tượng. Tôi muốn có thể khởi tạo foovới một nguyên mẫu thích hợp. Tôi không muốn sửa đổi Object.prototypecũng không Array.prototype. Tôi có thể làm cái này như thế nào?


Bạn muốn thêm vào nguyên mẫu hiện có của nó hay chuyển nó sang một nguyên mẫu mới?
SLaks

Ý của bạn là thực hiện các thay đổi đối với nguyên mẫu - hoặc thực sự thay đổi nguyên mẫu khi chuyển đổi một nguyên mẫu và thay thế nó bằng một nguyên mẫu khác. Tôi thậm chí không chắc trường hợp sau này là có thể.
James Gaunt

2
Ý của bạn là thuộc tính nguyên mẫu rõ ràng hay liên kết nguyên mẫu ngầm ? (Hai là hai việc rất khác nhau)
Šime Vidas

Tôi ban đầu liên quan với điều này: stackoverflow.com/questions/7013545/...
Vivian sông

1
Bạn có quen thuộc với Backbone của extendhoặc của Google goog.inherit? Nhiều nhà phát triển cung cấp các cách xây dựng kế thừa trước khi gọi hàm newtạo cũ — đó là trước khi chúng tôi được cung cấp Object.createvà không phải lo lắng về việc ghi đè Object.prototype.
Ryan

Câu trả lời:


114

CHỈNH SỬA tháng 2 năm 2012: câu trả lời dưới đây không còn chính xác nữa. __proto__ đang được thêm vào ECMAScript 6 dưới dạng "tùy chọn quy chuẩn" có nghĩa là nó không bắt buộc phải được triển khai nhưng nếu có, nó phải tuân theo bộ quy tắc đã cho. Điều này hiện vẫn chưa được giải quyết nhưng ít nhất nó sẽ chính thức là một phần trong đặc tả của JavaScript.

Câu hỏi này phức tạp hơn rất nhiều so với bề ngoài, và vượt quá mức lương của hầu hết mọi người liên quan đến kiến ​​thức về nội bộ Javascript.

Các prototypetài sản của một đối tượng được sử dụng khi tạo các đối tượng con mới của đối tượng đó. Việc thay đổi nó không phản ánh trong bản thân đối tượng, đúng hơn được phản ánh khi đối tượng đó được sử dụng làm phương thức khởi tạo cho các đối tượng khác và không có tác dụng gì trong việc thay đổi nguyên mẫu của một đối tượng hiện có.

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

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

Các đối tượng có thuộc tính [[nguyên mẫu]] bên trong trỏ đến nguyên mẫu hiện tại. Cách hoạt động của nó là bất cứ khi nào một thuộc tính trên một đối tượng được gọi, nó sẽ bắt đầu từ đối tượng và sau đó đi lên qua chuỗi [[nguyên mẫu]] cho đến khi nó tìm thấy một kết quả phù hợp hoặc không thành công sau nguyên mẫu Đối tượng gốc. Đây là cách Javascript cho phép xây dựng thời gian chạy và sửa đổi các đối tượng; nó có một kế hoạch để tìm kiếm những gì nó cần.

Các __proto__bất động sản tồn tại trong một số hiện thực (rất nhiều bây giờ): bất kỳ thực hiện Mozilla, tất cả những cái webkit tôi biết, một số người khác. Thuộc tính này trỏ đến thuộc tính [[nguyên mẫu]] bên trong và cho phép tạo sau sửa đổi trên các đối tượng. Bất kỳ thuộc tính và chức năng nào sẽ ngay lập tức chuyển đổi để khớp với nguyên mẫu do tra cứu theo chuỗi này.

Tính năng này, mặc dù hiện đã được tiêu chuẩn hóa, vẫn không phải là một phần bắt buộc của JavaScript và trong các ngôn ngữ hỗ trợ nó có khả năng cao khiến mã của bạn rơi vào danh mục "chưa được tối ưu hóa". Các công cụ JS phải cố gắng hết sức để phân loại mã, đặc biệt là mã "nóng" được truy cập rất thường xuyên và nếu bạn đang làm điều gì đó lạ mắt như sửa đổi __proto__, chúng sẽ không tối ưu hóa mã của bạn.

Bài đăng này https://bugzilla.mozilla.org/show_bug.cgi?id=607863 thảo luận cụ thể về các triển khai hiện tại __proto__và sự khác biệt giữa chúng. Mọi triển khai đều khác nhau, bởi vì đó là một vấn đề khó và chưa được giải quyết. Mọi thứ trong Javascript đều có thể thay đổi, ngoại trừ a.) Cú pháp b.) Các đối tượng máy chủ lưu trữ (DOM tồn tại bên ngoài Javascript về mặt kỹ thuật) và c.) __proto__. Phần còn lại hoàn toàn nằm trong tay bạn và mọi nhà phát triển khác, vì vậy bạn có thể hiểu tại sao lại __proto__nhô ra như ngón tay cái đau.

Có một điều __proto__cho phép điều đó không thể làm được: việc chỉ định một nguyên mẫu đối tượng trong thời gian chạy tách biệt với phương thức khởi tạo của nó. Đây là một trường hợp sử dụng quan trọng và là một trong những lý do chính khiến __proto__nó chưa chết. Nó đủ quan trọng để nó là một điểm thảo luận nghiêm túc trong việc xây dựng Harmony, hay sắp được gọi là ECMAScript 6. Khả năng chỉ định nguyên mẫu của một đối tượng trong quá trình tạo sẽ là một phần của phiên bản tiếp theo của Javascript và điều này sẽ chuông chỉ __proto__ngày của được đánh số chính thức.

Trong ngắn hạn, bạn có thể sử dụng __proto__nếu bạn đang nhắm mục tiêu các trình duyệt hỗ trợ nó (không phải IE và không IE bao giờ). Có khả năng nó sẽ hoạt động trong webkit và moz trong 10 năm tới vì ES6 sẽ không được hoàn thiện cho đến năm 2013.

Brendan Eich - re: Cách tiếp cận các phương thức Đối tượng mới trong ES5 :

Xin lỗi, ... nhưng có thể thiết lập __proto__, ngoài trường hợp sử dụng trình khởi tạo đối tượng (tức là trên một đối tượng mới chưa thể truy cập, tương tự như Object.create của ES5), là một ý tưởng tồi. Tôi viết điều này có thể thiết kế và thực hiện được __proto__hơn 12 năm trước.

... việc thiếu phân tầng là một vấn đề (xem xét dữ liệu JSON với một khóa "__proto__"). Và tệ hơn, khả năng thay đổi có nghĩa là việc triển khai phải kiểm tra các chuỗi mẫu thử nghiệm theo chu kỳ để tránh bỏ lọt. [yêu cầu kiểm tra liên tục cho đệ quy vô hạn]

Cuối cùng, việc đột biến __proto__trên một đối tượng hiện có có thể phá vỡ các phương thức không chung chung trong đối tượng nguyên mẫu mới, không thể hoạt động trên đối tượng nhận (trực tiếp) __proto__đang được thiết lập. Nói chung, đây chỉ đơn giản là thực hành xấu, một dạng nhầm lẫn có chủ ý.


Sự cố xuất sắc! Mặc dù nó không hoàn toàn giống với một nguyên mẫu có thể thay đổi, nhưng ECMA Harmony có thể sẽ triển khai proxy , cho phép bạn bổ sung chức năng vào các đối tượng cụ thể bằng cách sử dụng mẫu catchall.
Nick Husher

2
Vấn đề của Brendan Eich bắt nguồn từ việc tạo mẫu các ngôn ngữ nói chung. Có thể tạo __proto__ non-writable, configurablesẽ khắc phục những lo ngại này bằng cách buộc người dùng phải định cấu hình lại thuộc tính một cách rõ ràng. Cuối cùng, các thực hành xấu có liên quan đến việc lạm dụng các khả năng của một ngôn ngữ, chứ không phải bản thân các khả năng đó. Viết __proto__ không phải là chưa từng được nghe. Có nhiều thuộc tính có thể ghi không phải liệt kê khác và trong khi có những nguy hiểm, cũng có những phương pháp hay nhất. Đầu búa không nên được tháo ra đơn giản vì nó có thể được sử dụng không đúng cách để làm bị thương ai đó.
Swivel

Việc thay đổi __proto__là cần thiết, bởi vì Object.create sẽ chỉ tạo ra các Đối tượng, không phải các chức năng, chẳng hạn như Biểu tượng, Regexes, phần tử DOM hoặc các đối tượng máy chủ lưu trữ khác. Nếu bạn muốn đối tượng của bạn để có thể được gọi, hoặc đặc biệt theo nhiều cách khác, nhưng vẫn thay đổi chuỗi nguyên mẫu của họ, bạn đang bị mắc kẹt mà không settable __proto__Proxy hay
user2451227

2
Sẽ tốt hơn nếu nó không được thực hiện với thuộc tính ma thuật mà thay vào đó là Object.setPrototype, loại bỏ __proto__mối quan tâm " trong JSON". Những mối quan tâm khác của Brendan Eich thật đáng buồn cười. Chuỗi nguyên mẫu đệ quy hoặc sử dụng nguyên mẫu với các phương thức không phù hợp cho đối tượng đã cho là lỗi của lập trình viên, không phải lỗi ngôn ngữ và không phải là một yếu tố, và bên cạnh đó, chúng có thể xảy ra với Object.create cũng như với thiết lập tự do __proto__.
user2451227

1
IE 10 hỗ trợ __proto__.
kzh


14

Bạn có thể sử dụng constructortrên một phiên bản của một đối tượng để thay đổi nguyên mẫu của một đối tượng tại chỗ. Tôi tin rằng đây là những gì bạn đang yêu cầu làm.

Điều này có nghĩa là nếu bạn có foođó là một ví dụ của Foo:

function Foo() {}

var foo = new Foo();

Bạn có thể thêm một thuộc tính barvào tất cả các trường hợp của Foobằng cách làm như sau:

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

Dưới đây là một trò chơi cho thấy bằng chứng của khái niệm: http://jsfiddle.net/C2cpw/ . Không chắc chắn lắm về việc các trình duyệt cũ sẽ hoạt động như thế nào khi sử dụng phương pháp này, nhưng tôi khá chắc chắn rằng điều này sẽ thực hiện công việc khá tốt.

Nếu ý định của bạn là kết hợp chức năng vào các đối tượng, đoạn mã này sẽ thực hiện công việc:

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: Đây là thông tin và thú vị, nhưng không thực sự giúp tôi đạt được những gì tôi muốn. Tôi đang cập nhật câu hỏi của mình để cụ thể hơn.
Vivian River

Bạn cũng có thể sử dụngfoo.__proto__.bar = 'bar';
Jam Risser

9

Bạn có thể làm foo.__proto__ = FooClass.prototype, AFAIK được hỗ trợ bởi Firefox, Chrome và Safari. Hãy nhớ rằng thuộc __proto__tính không phải là tiêu chuẩn và có thể biến mất vào một lúc nào đó.

Tài liệu: https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Object/proto . Ngoài ra, hãy xem http://www.mail-archive.com/jsmentors@googlegroups.com/msg00392.html để biết giải thích tại sao không có Object.setPrototypeOf()và tại sao __proto__không được dùng nữa.


3

Bạn có thể xác định hàm tạo proxy của mình và sau đó tạo một phiên bản mới và sao chép tất cả các thuộc tính từ đối tượng ban đầu sang nó.

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

Bản demo trực tiếp: http://jsfiddle.net/6Xq3P/

Hàm Customtạo đại diện cho nguyên mẫu mới, ergo, Custom.prototypeđối tượng của nó chứa tất cả các thuộc tính mới mà bạn muốn sử dụng với đối tượng ban đầu của mình.

Bên trong hàm Customtạo, bạn chỉ cần sao chép tất cả các thuộc tính từ đối tượng ban đầu sang đối tượng thể hiện mới.

Đối tượng thể hiện mới này chứa tất cả các thuộc tính từ đối tượng ban đầu (chúng đã được sao chép vào nó bên trong phương thức khởi tạo), và tất cả các thuộc tính mới được định nghĩa bên trong Custom.prototype(vì đối tượng mới là một Customthể hiện).


3

Bạn không thể thay đổi nguyên mẫu của một đối tượng JavaScript đã được khởi tạo theo cách trình duyệt chéo. Như những người khác đã đề cập, các tùy chọn của bạn bao gồm:

  1. thay đổi các tiêu chuẩn phi / trình duyệt chéo __proto__bất động sản
  2. Sao chép thuộc tính Đối tượng sang một đối tượng mới

Cả hai đều không đặc biệt tuyệt vời, đặc biệt nếu bạn phải lặp đệ quy qua một đối tượng vào các đối tượng bên trong để thay đổi một cách hiệu quả toàn bộ nguyên mẫu phần tử.

Giải pháp thay thế cho câu hỏi

Tôi sẽ xem xét một cách trừu tượng hơn về chức năng mà bạn mong muốn.

Về cơ bản nguyên mẫu / phương thức chỉ cho phép một cách để nhóm các chức năng dựa trên một đối tượng.
Thay vì viết

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

bạn viết

'   test  '.trim();

Cú pháp trên được đặt thành thuật ngữ OOP do cú pháp object.method (). Một số lợi thế chính của OOP so với lập trình chức năng truyền thống bao gồm:

  1. Tên phương thức ngắn và ít biến hơn obj.replace('needle','replaced')so với việc phải nhớ các tên như str_replace ( 'foo' , 'bar' , 'subject')và vị trí của các biến khác nhau
  2. method chaining ( string.trim().split().join()) có khả năng dễ dàng hơn để sửa đổi và viết các hàm lồng nhaujoin(split(trim(string))

Thật không may trong JavaScript (như được hiển thị ở trên), bạn không thể sửa đổi một nguyên mẫu đã tồn tại. Ở trên lý tưởng nhất là bạn chỉ có thể sửa đổi Object.prototypeđối tượng đã cho ở trên, nhưng không may sửa đổi Object.prototypesẽ có khả năng phá vỡ các tập lệnh (dẫn đến xung đột thuộc tính và ghi đè).

Không có điểm trung gian thường được sử dụng giữa 2 kiểu lập trình này và không có cách OOP nào để tổ chức các chức năng tùy chỉnh.

UnlimitJS cung cấp nền tảng trung gian cho phép bạn xác định các phương thức tùy chỉnh. Nó tránh:

  1. Va chạm thuộc tính, vì nó không mở rộng nguyên mẫu của Đối tượng
  2. Vẫn cho phép cú pháp chuỗi OOP
  3. Nó là tập lệnh trình duyệt chéo 450 byte (IE6 +, Firefox 3.0 +, Chrome, Opera, Safari 3.0+) mà Unlimit của nhiều vấn đề xung đột thuộc tính nguyên mẫu của JavaScript

Sử dụng đoạn mã của bạn ở trên, tôi chỉ cần tạo một không gian tên của các hàm mà bạn định gọi đối tượng.

Đây là một ví dụ:

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

Bạn có thể đọc thêm các ví dụ ở đây UnlimitJS . Về cơ bản khi bạn gọi [Unlimit]()một hàm, nó cho phép hàm được gọi như một phương thức trên một Đối tượng. Nó giống như một trung gian giữa đường OOP và đường chức năng.


2

Bạn không thể thay đổi [[prototype]]tham chiếu của các đối tượng đã được xây dựng, theo như tôi biết. Bạn có thể thay đổi thuộc tính nguyên mẫu của hàm tạo ban đầu nhưng, như bạn đã nhận xét, hàm tạo đó chính là Object, và việc thay đổi các cấu trúc JS lõi là Điều tồi tệ.

Tuy nhiên, bạn có thể tạo một đối tượng proxy của đối tượng đã xây dựng để triển khai chức năng bổ sung mà bạn cần. Bạn cũng có thể bắt cặp các phương thức và hành vi bổ sung bằng cách gán trực tiếp cho đối tượng được đề cập.

Có lẽ bạn có thể đạt được những gì bạn muốn theo cách khác, nếu bạn sẵn sàng tiếp cận từ một góc độ khác: Bạn cần làm gì liên quan đến việc làm rối tung nguyên mẫu?


Bất cứ ai khác có thể nhận xét về tính chính xác của điều này?
Vivian River

Có vẻ như dựa trên jsfiddle khủng khiếp này mà bạn có thể thay đổi nguyên mẫu của một đối tượng hiện có bằng cách thay đổi thuộc tính của nó __proto__. Điều này sẽ chỉ hoạt động trong các trình duyệt hỗ trợ __proto__ký hiệu, đó là Chrome và Firefox và nó đã không được dùng nữa . Vì vậy, trong ngắn hạn, bạn có thể thay đổi [[prototype]]đối tượng, nhưng bạn có thể không nên.
Nick Husher

1

Nếu bạn biết nguyên mẫu, tại sao không đưa nó vào mã?

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

Vì vậy, khi dữ liệu được tuần tự hóa, bạn sẽ

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

bây giờ bạn chỉ cần một hàm tạo nhận một mảng làm đối số.


0

Không có cách nào để thực sự kế thừa từ Array hoặc "phân lớp" nó.

Những gì bạn có thể làm là sau ( CẢNH BÁO: MÃ LỄ HỘI 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

Điều này hoạt động, nhưng sẽ gây ra một số rắc rối nhất định cho bất kỳ ai đi qua đường dẫn của nó (nó trông giống như một mảng, nhưng mọi thứ sẽ trở nên sai nếu bạn cố gắng thao tác nó).

Tại sao bạn không đi theo tuyến an toàn và thêm các phương thức / thuộc tính trực tiếp vào foohoặc sử dụng một hàm tạo và lưu mảng của bạn dưới dạng thuộc tính?

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

nếu bạn muốn tạo nguyên mẫu một cách nhanh chóng, đây là một trong những cách

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

Không, bạn không thể. Đó là thuộc tính tĩnh.
SLaks

@SLaks prototypecó tĩnh không? Tôi không chắc bạn muốn nói gì.
Šime Vidas

1
prototypelà một thuộc tính của một hàm, không phải là một đối tượng. Object.prototypetồn tại; {}.prototypekhông.
SLaks

Nếu bạn không quan tâm đến khả năng tương thích của trình duyệt, bạn có thể sử dụng Object.getPrototypeOf(foo)để lấy đối tượng nguyên mẫu của phương thức khởi tạo. Bạn có thể sửa đổi các thuộc tính trên đó để sửa đổi nguyên mẫu của đối tượng. Tuy nhiên, nó chỉ hoạt động trong các trình duyệt gần đây, không thể cho bạn biết cái nào.
Nick Husher

@TeslaNick: Bạn chỉ có thể đi và sửa đổi Object.prototypetrực tiếp vì đó rất có thể là những gì Object.getPrototypeOf(foo)sẽ trở lại. Không hữu ích lắm.
Wladimir Palant
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.