이미 인스턴스화 된 JavaScript 객체의 프로토 타입을 설정하는 방법은 무엇입니까?


106

foo내 JavaScript 코드에 개체가 있다고 가정 합니다. foo복잡한 개체이며 다른 곳에서 생성됩니다. foo개체 의 프로토 타입을 어떻게 변경할 수 있습니까?

내 동기는 .NET에서 JavaScript 리터럴로 직렬화 된 객체에 적절한 프로토 타입을 설정하는 것입니다.

ASP.NET 페이지에서 다음 JavaScript 코드를 작성했다고 가정합니다.

var foo = <%=MyData %>;

객체 MyData에서 .NET JavaScriptSerializer을 호출 한 결과 라고 가정 Dictionary<string,string>합니다.

런타임시 다음과 같이됩니다.

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

보시다시피 foo는 객체의 배열이됩니다. foo적절한 프로토 타입 으로 초기화 하고 싶습니다 . 나는 할 수 없습니다 수정하려는 Object.prototypeArray.prototype. 어떻게 할 수 있습니까?


기존 프로토 타입에 추가 하시겠습니까, 아니면 새 프로토 타입으로 전환 하시겠습니까?
SLaks

프로토 타입을 변경하거나 실제로 하나의 프로토 타입을 전환하고 다른 프로토 타입으로 교체하는 것처럼 프로토 타입을 변경한다는 의미입니까? 나중 사건이 가능할지조차 모르겠습니다.
James Gaunt

2
당신은 의미합니까 명시 적 프로토 타입 속성 또는 암시 적 프로토 타입 링크를? (이 두 가지는 매우 다른 두 가지입니다)
Šime Vidas 2011-08-10

나는 원래 이것에 관심이 있었다 : stackoverflow.com/questions/7013545/…
Vivian River

1
백본 extend이나 구글에 대해 잘 알고 계십니까 goog.inherit? 많은 개발자 new가 이전 생성자 를 호출하기 전에 상속을 빌드하는 방법을 제공합니다. 이는 우리에게 주어지기 Object.create전이고 재정의에 대해 걱정할 필요가 없습니다 Object.prototype.
라이언

답변:


114

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]] 체인을 통해 올라갑니다. 이것이 자바 스크립트가 객체의 런타임 빌드 및 수정을 허용하는 방법입니다. 필요한 것을 검색 할 계획이 있습니다.

__proto__속성은 (지금은 많이) 일부 구현에 존재 : 모질라 구현, 내가 아는 모든 웹킷 사람, 어떤 다른 사람을. 이 속성은 내부 [[prototype]] 속성을 가리키며 객체 생성 후 수정을 허용합니다. 이 연결 조회로 인해 모든 속성 및 기능이 프로토 타입과 일치하도록 즉시 전환됩니다.

이 기능은 현재 표준화되었지만 여전히 JavaScript의 필수 부분은 아니며이를 지원하는 언어에서는 코드가 "최적화되지 않은"범주로 떨어질 가능성이 높습니다. JS 엔진은 코드, 특히 자주 액세스되는 "핫"코드를 분류하기 위해 최선을 다해야하며 수정과 같은 멋진 작업을 수행하는 경우 __proto__코드를 전혀 최적화하지 않습니다.

이 게시물은 https://bugzilla.mozilla.org/show_bug.cgi?id=607863의 현재 구현 __proto__과 그 차이점에 대해 구체적으로 설명 합니다. 모든 구현은 어렵고 해결되지 않은 문제이기 때문에 다르게 수행합니다. a.) 구문 b.) 호스트 객체 (DOM은 기술적으로 Javascript 외부에 있음) 및 c.)를 제외하고 Javascript의 모든 것은 변경 가능합니다 __proto__. 나머지는 전적으로 귀하와 다른 모든 개발자의 손에 있으므로 __proto__엄지 손가락처럼 튀어 나온 이유를 알 수 있습니다 .

다른 방법으로는 __proto__불가능한 일이 있습니다. 런타임시 생성자와 별개로 객체 프로토 타입을 지정하는 것입니다. 이것은 중요한 사용 사례이며 __proto__아직 죽지 않은 주된 이유 중 하나입니다 . Harmony 공식화에서 진지한 논의가되거나 곧 ECMAScript 6으로 알려질만큼 중요합니다. 생성 중에 객체의 프로토 타입을 지정하는 기능은 Javascript의 다음 버전의 일부가 될 것입니다. __proto__의 날을 나타내는 종 은 공식적으로 번호가 매겨져 있습니다.

단기적 __proto__으로는이를 지원하는 브라우저를 대상으로 하는 경우 사용할 수 있습니다 (IE가 아니고 IE도 지원하지 않음). ES6는 2013 년까지 완성되지 않기 때문에 향후 10 년 동안 웹킷과 moz에서 작동 할 것입니다.

Brendan Eich -re : ES5의 새로운 Object 메서드 접근 방식 :

죄송합니다, ...하지만 settable __proto__은 객체 이니셜 라이저 사용 사례 (예 : 아직 도달 할 수없는 새 객체에 대해 ES5의 Object.create와 유사 함)를 제외하고 끔찍한 아이디어입니다. 저는 __proto__12 년 전에 settable을 설계하고 구현 한이 글을 씁니다 .

... 계층화의 부족이 문제입니다 (키가있는 JSON 데이터 고려 "__proto__"). 그리고 더 나쁜 것은 가변성이 있다는 것은 구현이 ilooping을 피하기 위해 순환 프로토 타입 체인을 확인해야 함을 의미합니다. [무한 재귀에 대한 지속적인 검사 필요]

마지막으로 __proto__기존 객체를 변경 하면 새 프로토 타입 객체에서 제네릭이 아닌 메서드가 손상 될 수 있으며, 이는 __proto__설정중인 수신자 (직접) 객체에서 작동 할 수 없습니다 . 이것은 일반적으로 의도적 인 유형 혼란의 한 형태 인 단순히 나쁜 습관입니다.


훌륭한 고장! 변경 가능한 프로토 타입과 완전히 같은 것은 아니지만 ECMA Harmony는 아마도 프록시를 구현할 것입니다. 그러면 캐치 올 패턴을 사용하여 특정 객체에 추가 기능을 shim 할 수 있습니다.
Nick Husher 2011-08-23

2
Brendan Eich의 문제는 전체적으로 프로토 타이핑 언어에 고유합니다. __proto__를 만들면 non-writable, configurable사용자가 명시 적으로 속성을 다시 구성하도록하여 이러한 문제를 해결할 수 있습니다. 결국, 나쁜 관행은 능력 자체가 아니라 언어 능력의 남용과 관련이 있습니다. 쓰기 가능한 __proto__는 전례가 없습니다. 열거 할 수없는 쓰기 가능한 다른 속성이 많이 있으며 위험이 있지만 모범 사례도 있습니다. 망치의 머리는 누군가에게 상처를주기 위해 부적절하게 사용될 수 있다는 이유만으로 제거해서는 안됩니다.
Swivel

__proto__Object.create는 예를 들어 함수 나 Symbols, Regexes, DOM 요소 또는 기타 호스트 개체가 아닌 개체 만 생성하기 때문에 변경 이 필요합니다. 당신이 당신의 객체가 다른 방법으로 호출, 또는 특수,하지만 여전히 프로토 타입 체인을 변경하려는 경우, 당신은 설정할 수없이 붙어 __proto__또는 프록시
user2451227

2
마법의 속성이 아니라 Object.setPrototype을 사용하여 " __proto__in JSON"문제를 제거하면 더 좋을 것 입니다. Brendan Eich의 다른 우려는 웃을 수 있습니다. 재귀 적 프로토 타입 체인 또는 주어진 객체에 대해 부적절한 메서드를 사용하는 프로토 타입을 사용하는 것은 언어 오류가 아니라 프로그래머 오류이며 요인이되어서는 안됩니다 __proto__.
user2451227

1
IE 10은 __proto__.
kzh


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 : 이것은 유익하고 흥미롭지 만 내가 원하는 것을 얻는 데 실제로 도움이되지는 않습니다. 더 구체적으로 질문을 업데이트하고 있습니다.
Vivian River

당신은 또한 사용할 수 있습니다foo.__proto__.bar = 'bar';
잼 Risser

9

foo.__proto__ = FooClass.prototypeFirefox, 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__와 더 이상 사용되지 않는 이유에 대한 설명 도 참조 하십시오 .


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생성자는 새로운 프로토 타입, 에고, 그 나타내는 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. 메소드 체이닝 ( string.trim().split().join())은 중첩 함수를 수정하고 작성하기가 더 쉬울 수 있습니다.join(split(trim(string))

불행히도 JavaScript에서는 (위에 표시된대로) 이미 존재하는 프로토 타입을 수정할 수 없습니다. 이상적으로는 위 Object.prototype의 주어진 Object에 대해서만 수정할 수 있지만 불행히도 수정 Object.prototype하면 잠재적으로 스크립트가 손상 될 수 있습니다 (속성 충돌 및 재정의 결과).

이 두 가지 프로그래밍 스타일 사이에는 일반적으로 사용되는 중간 지점이 없으며 사용자 지정 함수를 구성하는 OOP 방식도 없습니다.

UnlimitJS 는 사용자 지정 메서드를 정의 할 수있는 중간 지점을 제공합니다. 다음을 방지합니다.

  1. 객체의 프로토 타입을 확장하지 않기 때문에 속성 충돌
  2. 여전히 OOP 체인 구문을 허용합니다.
  3. Unlimit의 JavaScript 프로토 타입 속성 충돌 문제가 많은 450 바이트 크로스 브라우저 (IE6 +, Firefox 3.0 +, Chrome, Opera, Safari 3.0+) 스크립트입니다.

위의 코드를 사용하여 개체에 대해 호출하려는 함수의 네임 스페이스를 간단히 만들 것입니다.

다음은 예입니다.

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와 기능 도로 사이의 중간 지점과 같습니다.


2

[[prototype]]내가 아는 한 이미 구성된 객체 의 참조를 변경할 수 없습니다 . 원래 생성자 함수의 프로토 타입 속성을 변경할 수 있지만 이미 주석을 달았 듯이 해당 생성자는 Object이고 핵심 JS 구성을 변경하는 것은 나쁜 일입니다.

하지만 필요한 추가 기능을 구현하는 생성 된 개체의 프록시 개체를 만들 수 있습니다. 또한 문제의 객체에 직접 할당하여 추가 메서드와 동작을 monkeypatch 할 수도 있습니다.

다른 각도에서 기꺼이 접근하려는 경우 다른 방법으로 원하는 것을 얻을 수 있습니다. 프로토 타입을 엉망으로 만드는 것과 관련하여 무엇을해야합니까?


다른 사람이 이것의 정확성에 대해 언급 할 수 있습니까?
Vivian River

이 끔찍한 jsfiddle 을 기반으로 기존 객체의 __proto__속성을 변경하여 프로토 타입을 변경할 수있는 것으로 보입니다 . 이는 __proto__Chrome 및 Firefox와 같은 표기법 을 지원하는 브라우저에서만 작동하며 더 이상 사용되지 않습니다 . 즉, [[prototype]]객체 의 를 변경할 수 있지만 그렇게해서는 안됩니다.
Nick Husher 2011-08-22

1

프로토 타입을 알고 있다면 코드에 삽입하지 않는 이유는 무엇입니까?

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

따라서 데이터가 직렬화되면

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

이제 배열을 인수로 사용하는 생성자 만 필요합니다.


0

실제로 상속 Array하거나 "하위 클래스"를 만들 수있는 방법은 없습니다 .

당신이 할 수있는 일은이 (입니다 : 늪지대 코드 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

이것은 작동하지만 경로를 가로 지르는 모든 사람에게 특정 문제를 일으킬 것입니다 (배열처럼 보이지만 조작하려고하면 문제가 발생합니다).

정상적인 경로로 이동하여 메서드 / 속성을에 직접 추가 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

@SLaks prototype는 정적입니까? 무슨 말인지 잘 모르겠습니다.
Šime Vidas

1
prototype객체가 아니라 함수의 속성입니다. Object.prototype존재한다; {}.prototype하지 않습니다.
SLaks

브라우저 호환성에 관심이 없다면를 사용 Object.getPrototypeOf(foo)하여 생성자 프로토 타입 객체를 가져올 수 있습니다 . 해당 속성을 수정하여 객체의 프로토 타입을 수정할 수 있습니다. 최근 브라우저에서만 작동하지만 어떤 브라우저인지는 알 수 없습니다.
Nick Husher 2011-08-10

@TeslaNick : 반환 Object.prototype될 가능성이 가장 높기 때문에 직접 가서 수정할 수 있습니다 Object.getPrototypeOf(foo). 별로 유용하지 않습니다.
Wladimir Palant 2011 년
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.