[javascript] JavaScript에서 동적 getter / setter를 구현할 수 있습니까?

다음과 같은 방법으로 이름이 이미 알려진 속성에 대해 getter 및 setter를 만드는 방법을 알고 있습니다.

// A trivial example:
function MyObject(val){
    this.count = 0;
    this.value = val;
}
MyObject.prototype = {
    get value(){
        return this.count < 2 ? "Go away" : this._value;
    },
    set value(val){
        this._value = val + (++this.count);
    }
};
var a = new MyObject('foo');

alert(a.value); // --> "Go away"
a.value = 'bar';
alert(a.value); // --> "bar2"

이제 제 질문은 이런 종류의 잡동사니와 세터를 정의 할 수 있습니까? 즉, 아직 정의 되지 않은 속성 이름에 대한 getter 및 setter를 만듭니다 .

개념은 PHP에서 __get()and __set()magic 메소드를 사용하여 가능 합니다 (이에 대한 정보 는 PHP 문서 를 참조하십시오 ). 이것과 동등한 JavaScript가 실제로 있습니까?

말할 필요도없이, 크로스 브라우저와 호환되는 솔루션이 이상적입니다.



답변

2013 및 2015 업데이트 (2011 년의 원래 답변은 아래 참조) :

이것은 ES2015 (일명 “ES6”) 사양으로 변경되었습니다 . 이제 JavaScript에 프록시가 있습니다. 프록시를 사용하면 다른 객체에 대한 실제 프록시 인 객체를 만들 수 있습니다. 다음은 검색시 모든 대문자로 문자열 인 속성 값을 바꾸는 간단한 예입니다.

"use strict";
if (typeof Proxy == "undefined") {
    throw new Error("This browser doesn't support Proxy");
}
let original = {
    "foo": "bar"
};
let proxy = new Proxy(original, {
    get(target, name, receiver) {
        let rv = Reflect.get(target, name, receiver);
        if (typeof rv === "string") {
            rv = rv.toUpperCase();
        }
        return rv;
      }
});
console.log(`original.foo = ${original.foo}`); // "original.foo = bar"
console.log(`proxy.foo = ${proxy.foo}`);       // "proxy.foo = BAR"

재정의하지 않은 작업에는 기본 동작이 있습니다. 위에서 우리가 재정의하는 것은 모두 get이지만 연결할 수있는 전체 작업 목록이 있습니다.

에서 get핸들러 함수의 인수 목록 :

  • target프록시되는 객체입니다 (이 original경우에는).
  • name (물론) 검색되는 속성의 이름으로, 일반적으로 문자열이지만 기호 일 수도 있습니다.
  • receiverthis속성이 데이터 속성이 아닌 접근자인 경우 getter 함수에서와 같이 사용해야하는 객체입니다 . 일반적인 경우 프록시 또는 프록시에서 상속되는 것이지만 트랩이에 의해 트리거 될 있으므로 무엇이든 될 수 있습니다 Reflect.get.

이를 통해 원하는 범용 게터 및 세터 기능을 사용하여 객체를 만들 수 있습니다.

"use strict";
if (typeof Proxy == "undefined") {
    throw new Error("This browser doesn't support Proxy");
}
let obj = new Proxy({}, {
    get(target, name, receiver) {
        if (!Reflect.has(target, name)) {
            console.log("Getting non-existent property '" + name + "'");
            return undefined;
        }
        return Reflect.get(target, name, receiver);
    },
    set(target, name, value, receiver) {
        if (!Reflect.has(target, name)) {
            console.log(`Setting non-existent property '${name}', initial value: ${value}`);
        }
        return Reflect.set(target, name, value, receiver);
    }
});

console.log(`[before] obj.foo = ${obj.foo}`);
obj.foo = "bar";
console.log(`[after] obj.foo = ${obj.foo}`);

위의 결과는 다음과 같습니다.

존재하지 않는 속성 'foo'가져 오기
[이전] obj.foo = 정의되지 않음
존재하지 않는 속성 'foo'설정, 초기 값 : bar
[후] obj.foo = 바

foo아직 존재하지 않을 때 검색을 시도 할 때 그리고 존재하지 않을 때 다시 검색 할 때 “존재하지 않는”메시지를받는 방법에 주목 하십시오.


2011 년 답변 (2013 및 2015 업데이트는 위 참조) :

아니요, JavaScript에는 포괄적 인 속성 기능이 없습니다. 사용하는 접근 자 구문 은 사양의 11.1.5 절에서 다루며 와일드 카드 또는 이와 유사한 것을 제공하지 않습니다.

당신은 물론, 그것을 할 수있는 기능을 구현할 수 있습니다,하지만 난 당신이 아마 사용하지 않을 같은데요 f = obj.prop("foo");보다는 f = obj.foo;obj.prop("foo", value);보다는 obj.foo = value;(함수가 알 수없는 속성을 처리 할 필요가있을 것이다).

FWIW, getter 함수 (세터 로직을 신경 쓰지 않았습니다)는 다음과 같습니다.

MyObject.prototype.prop = function(propName) {
    if (propName in this) {
        // This object or its prototype already has this property,
        // return the existing value.
        return this[propName];
    }

    // ...Catch-all, deal with undefined property here...
};

그러나 다시 한 번 말하지만, 객체 사용 방식이 어떻게 바뀌는 지에 따라 실제로 그렇게하고 싶다고 상상할 수 없습니다.


답변

이 문제에 대한 독창적 인 접근 방식은 다음과 같습니다.

var obj = {
  emptyValue: null,
  get: function(prop){
    if(typeof this[prop] == "undefined")
        return this.emptyValue;
    else
        return this[prop];
  },
  set: function(prop,value){
    this[prop] = value;
  }
}

이를 사용하려면 속성을 문자열로 전달해야합니다. 작동 방식의 예는 다음과 같습니다.

//To set a property
obj.set('myProperty','myValue');

//To get a property
var myVar = obj.get('myProperty');

편집 :
내가 제안한 것을 기반으로 개선 된보다 객체 지향적 인 접근 방식은 다음과 같습니다.

function MyObject() {
    var emptyValue = null;
    var obj = {};
    this.get = function(prop){
        return (typeof obj[prop] == "undefined") ? emptyValue : obj[prop];
    };
    this.set = function(prop,value){
        obj[prop] = value;
    };
}

var newObj = new MyObject();
newObj.set('myProperty','MyValue');
alert(newObj.get('myProperty'));

여기서 작동하는 것을 볼 수 있습니다 .


답변

머리말:

TJ Crowder의 답변Proxy 은 OP가 요구 한대로 존재하지 않는 속성에 대한 포괄적 인 getter / setter에 필요한 a를 언급합니다 . 동적 게터 / 세터에서 실제로 원하는 동작에 따라 실제로는 Proxy필요하지 않을 수도 있습니다. 또는 Proxy아래에 표시 할 내용과 의 조합을 사용하고 싶을 수도 있습니다.

(PS 필자는 Proxy최근 Linux의 Firefox에서 철저히 실험 해 보았으며 매우 유능한 것으로 나타 났지만 다소 혼란스럽고 어려워서 작업하고 올바르게 사용할 수 있음을 발견했습니다. 더 중요한 것은 (최소한 요즘에는 최적화 된 JavaScript가 어떻게 사용되는지와 관련이 있습니다)-나는 데카-다중의 영역에서 느리게 이야기하고 있습니다.)


동적으로 생성 된 게터 및 세터를 구체적으로 구현하려면 Object.defineProperty()또는 을 사용할 수 있습니다 Object.defineProperties(). 이것은 또한 매우 빠릅니다.

요점은 다음과 같이 객체에 getter 및 / 또는 setter를 정의 할 수 있다는 것입니다.

let obj = {};
let val = 0;
Object.defineProperty(obj, 'prop', { //<- This object is called a "property descriptor".
  //Alternatively, use: `get() {}`
  get: function() {
    return val;
  },
  //Alternatively, use: `set(newValue) {}`
  set: function(newValue) {
    val = newValue;
  }
});

//Calls the getter function.
console.log(obj.prop);
let copy = obj.prop;
//Etc.

//Calls the setter function.
obj.prop = 10;
++obj.prop;
//Etc.

여기에 몇 가지주의 할 사항이 있습니다.

  • value속성 설명자 ( 위에 표시 되지 않음) 에서 속성 을 get및 / 또는 동시에 사용할 수 없습니다 set. 문서에서 :

    객체에 존재하는 속성 디스크립터는 데이터 디스크립터와 접근 자 디스크립터의 두 가지 주요 특징이 있습니다. 데이터 디스크립터 또는 기록 가능하지 않을 수있는 값을 갖는 속성이다. 접근 디스크립터 기능 게터 세터 쌍 설명 속성이다. 설명자는이 두 가지 특징 중 하나 여야합니다. 둘 다 될 수는 없습니다.

  • 따라서, 당신은 내가 만든 알게 될 것이다 val속성 외부Object.defineProperty()전화 / 속성 설명을. 이것은 표준 동작입니다.
  • 오류에 따라 여기에서 설정하지 writabletrue당신이 사용하는 경우 속성 기술자 get또는 set.
  • 당신은 설정을 고려하는 것이 좋습니다 configurable그리고 enumerable, 그러나, 당신이 계신에 따라; 문서에서 :

    구성 가능

    • true 이 프로퍼티 디스크립터의 타입이 변경 될 수 있고 프로퍼티가 대응하는 객체로부터 삭제 될 수있는 경우에만.

    • 기본값은 false입니다.


    열거 할 수있는

    • true 해당 객체의 속성을 열거하는 동안이 속성이 표시되는 경우에만

    • 기본값은 false입니다.


이 메모에서 다음 사항도 관심이있을 수 있습니다.


답변

var x={}
var propName = 'value'
var get = Function("return this['" + propName + "']")
var set = Function("newValue", "this['" + propName + "'] = newValue")
var handler = { 'get': get, 'set': set, enumerable: true, configurable: true }
Object.defineProperty(x, propName, handler)

이것은 나를 위해 작동


답변