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


132

다음과 같은 방법으로 이름이 이미 알려진 속성에 대해 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가 실제로 있습니까?

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


나는 그것을 관리했다 . 어떻게 내 대답을 참조하십시오 .

답변:


216

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

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


1
에 대한 대안이 있습니다 Proxy: Object.defineProperty(). 나는 새로운 대답에 세부 사항을 넣었다 .
앤드류

@Andrew-질문을 잘못 읽은 것 같습니다. 답변에 대한 내 의견을 참조하십시오.
TJ Crowder

4

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

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

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


작동하지 않습니다. 속성 이름을 지정하지 않으면 게터를 정의 할 수 없습니다.
John Kurlak

@JohnKurlak이 jsFiddle을 확인하십시오 : jsfiddle.net/oga7ne4x 작동합니다. 속성 이름 만 문자열로 전달하면됩니다.
clami219

3
아, 설명해 주셔서 감사합니다. get () / set () 언어 기능을 사용하려고했지만 자신의 get () / set ()을 작성하지 않았다고 생각했습니다. 그래도 원래 솔루션을 실제로 해결하지 않기 때문에이 솔루션을 좋아하지 않습니다.
John Kurlak

@ JohnKurlak 글쎄, 나는 그것이 원래의 접근법이라고 썼다. 더 전통적인 접근 방식을 사용하는 기존 코드가있는 경우 문제를 해결하지 않아도 문제를 해결하는 다른 방법을 제공합니다. 그러나 처음부터 시작하는 것이 좋습니다. 확실히
공감할

@JohnKurlak 이제 더 나아 보이는지보십시오! :)
clami219

0

머리말:

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입니다.


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


2
질문을 잘못 읽은 것 같습니다. OP는 구체적으로 PHP 같이 모두 잡을 것을 요청했습니다 . 그 사건을 처리하지 않습니다. 질문에서 : "즉, 아직 정의 되지 않은 속성 이름에 대한 getter 및 setter를 만듭니다 ." (그들의 강조). 속성을 미리 정의합니다. OP가 요청한 것을 수행하는 유일한 방법은 프록시입니다. __get__setdefinePropertydefineProperty
TJ Crowder

@TJCrowder 알겠습니다. 질문은 명확하지 않지만 기술적으로 정확합니다. 이에 따라 답변을 조정했습니다. 또한 일부 사람들은 실제로 우리의 답변을 개인적으로 원할 수도 있습니다.
앤드류

@Andrew 2011 년 에이 질문을했을 때 내가 생각했던 유스 케이스는 라이브러리가 obj.whateverProperty일반 getter로 인터셉트하고 속성 이름을 부여 할 수 있도록 사용자가 호출 할 수있는 객체를 반환 할 수있는 라이브러리였습니다 사용자가 액세스를 시도했습니다. 따라서 'catch-all getter 및 setter'에 대한 요구 사항입니다.
daiscog

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

이것은 나를 위해 작동


13
Function()그렇게 사용 하는 것은을 사용하는 것과 같습니다 eval. 함수를의 매개 변수로 직접 입력하면됩니다 defineProperty. 또는 어떤 이유로 동적으로 getset을 생성해야하는 경우 함수를 생성하고 반환하는 고차 함수를 사용하십시오.var get = (function(propName) { return function() { return this[propName]; };})('value');
chris-l
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.