เป็นไปได้หรือไม่ที่จะใช้ตัวรับสัญญาณ / ตัวตั้งค่าแบบไดนามิกใน JavaScript


132

ฉันรู้วิธีสร้าง getters และ setters สำหรับคุณสมบัติที่มีชื่ออยู่แล้วโดยทำสิ่งนี้:

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

ทีนี้คำถามของฉันคือมันเป็นไปได้ไหมที่จะกำหนดตัวเรียงลำดับของตัวรับทั้งหมดที่จับได้และตัวติดตั้งแบบนี้ เช่นสร้าง getters และ setters สำหรับชื่อคุณสมบัติใด ๆ ที่ยังไม่ได้กำหนด

แนวคิดเป็นไปได้ใน PHP โดยใช้วิธีการ__get()และ__set()เวทมนตร์ (ดูเอกสาร PHPสำหรับข้อมูลเกี่ยวกับสิ่งเหล่านี้) ดังนั้นฉันถามจริง ๆ ว่ามี JavaScript เทียบเท่ากับสิ่งเหล่านี้หรือไม่

ไม่จำเป็นต้องบอกว่าฉันต้องการโซลูชันที่เข้ากันได้กับเบราว์เซอร์


ฉันจัดการเพื่อทำมันดูคำตอบของฉันที่นี่สำหรับวิธี

คำตอบ:


216

การอัปเดตปี 2556 และ 2558 (ดูคำตอบต้นฉบับจาก 2011) ด้านล่าง :

นี้มีการเปลี่ยนแปลงเป็นของ ES2015 (aka "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 คือ (แน่นอน) ชื่อของคุณสมบัติที่ถูกเรียกคืนซึ่งโดยปกติจะเป็นสตริง แต่อาจเป็นสัญลักษณ์ได้
  • receiverเป็นวัตถุที่ควรใช้เช่นเดียวกับthisในฟังก์ชั่น getter ถ้าคุณสมบัติเป็น accessor แทนที่จะเป็นคุณสมบัติของข้อมูล ในกรณีปกตินี้เป็นพร็อกซี่หรือสิ่งที่สืบทอดจากมัน แต่มันสามารถReflect.getเป็นอะไรก็ได้ตั้งแต่ดักอาจถูกเรียกโดย

สิ่งนี้ช่วยให้คุณสร้างวัตถุที่มีคุณสมบัติ getter และ setter แบบ catch-all ที่คุณต้องการ:

"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' ค่าเริ่มต้น: แถบ
[หลังจาก] obj.foo = bar

โปรดสังเกตว่าเราได้รับข้อความ "ไม่มีอยู่จริง" เมื่อเราพยายามดึงข้อมูลfooเมื่อยังไม่มีอยู่และอีกครั้งเมื่อเราสร้าง แต่ไม่ใช่หลังจากนั้น


คำตอบจาก 2011 (ดูด้านบนสำหรับการปรับปรุง 2013 และ 2015) :

ไม่จาวาสคริปต์ไม่มีคุณสมบัติที่ดึงดูดทั้งหมด ไวยากรณ์ของ accessor ที่คุณใช้นั้นอยู่ในหัวข้อที่ 11.1.5ของข้อมูลจำเพาะและไม่มีสัญลักษณ์ตัวแทนหรืออะไรทำนองนั้น

แน่นอนคุณสามารถใช้ฟังก์ชั่นที่จะทำ แต่ฉันเดาว่าคุณอาจไม่ต้องการใช้f = obj.prop("foo");มากกว่าf = obj.foo;และobj.prop("foo", value);มากกว่าobj.foo = value;(ซึ่งจำเป็นสำหรับฟังก์ชันในการจัดการคุณสมบัติที่ไม่รู้จัก)

FWIW, ฟังก์ชั่น getter (ฉันไม่ได้ยุ่งกับ setter logic) จะมีลักษณะเช่นนี้:

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

@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 กำลังขอ ทั้งนี้ขึ้นอยู่กับพฤติกรรมที่ต้องการด้วยตัวรับแบบไดนามิก / ตัวตั้งค่า a Proxyอาจไม่จำเป็นจริง ๆ หรืออาจเป็นไปได้ว่าคุณอาจต้องการใช้การรวมกันของ a Proxyกับสิ่งที่ฉันจะแสดงให้คุณเห็นด้านล่าง

(PS ฉันได้ทดลองใช้Proxyอย่างละเอียดใน Firefox บน Linux เมื่อเร็ว ๆ นี้และพบว่ามันมีความสามารถมาก แต่ก็ค่อนข้างสับสน / ยากที่จะทำงานด้วยและถูกต้องที่สำคัญฉันยังพบว่ามันค่อนข้างช้า (อย่างน้อยใน ความสัมพันธ์กับจาวาสคริปต์ที่ปรับให้เหมาะสมในปัจจุบัน) - ฉันกำลังพูดถึงขอบเขตของ deca-multiples ช้าลง)


ในการดำเนินการสร้างแบบไดนามิก getters และ setters เฉพาะคุณสามารถใช้หรือ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()อธิบายการโทร / คุณสมบัติ นี่คือพฤติกรรมมาตรฐาน
  • เป็นต่อข้อผิดพลาดที่นี่ไม่ได้ตั้งค่าwritableไปtrueในคำอธิบายถึงคุณสมบัติถ้าคุณใช้หรือgetset
  • คุณอาจต้องการพิจารณาการตั้งค่าconfigurableและenumerableอย่างไรก็ตามขึ้นอยู่กับสิ่งที่คุณหลังจาก; จากเอกสาร:

    ที่กำหนด

    • true ถ้าเพียงถ้าชนิดของตัวบอกคุณสมบัตินี้อาจมีการเปลี่ยนแปลงและถ้าคุณสมบัติอาจถูกลบออกจากวัตถุที่สอดคล้องกัน

    • ค่าเริ่มต้นเป็นเท็จ


    นับ

    • true ถ้าเพียง แต่ถ้าคุณสมบัตินี้จะปรากฏขึ้นในระหว่างการแจงนับคุณสมบัติในวัตถุที่สอดคล้องกัน

    • ค่าเริ่มต้นเป็นเท็จ


ในหมายเหตุนี้สิ่งเหล่านี้อาจเป็นที่สนใจ:

  • Object.getOwnPropertyNames(obj): รับคุณสมบัติทั้งหมดของวัตถุแม้กระทั่งวัตถุที่ไม่สามารถนับได้ (AFAIK นี่เป็นวิธีเดียวที่จะทำได้!)
  • Object.getOwnPropertyDescriptor(obj, prop): รับอธิบายคุณสมบัติของวัตถุวัตถุที่ถูกส่งไปObject.defineProperty()ด้านบน
  • obj.propertyIsEnumerable(prop);: สำหรับคุณสมบัติส่วนบุคคลบนอินสแตนซ์ออบเจกต์ที่เฉพาะเจาะจงให้เรียกใช้ฟังก์ชันนี้บนอินสแตนซ์ออบเจ็กต์เพื่อพิจารณาว่าคุณสมบัติเฉพาะนั้นนับได้หรือไม่

2
ฉันเกรงว่าคุณจะอ่านคำถามผิด สหกรณ์โดยเฉพาะขอให้จับทั้งหมดเช่นของ PHP__get__setและ definePropertyไม่ได้จัดการกับกรณีนั้น จากคำถาม: "คือสร้าง getters และ setters สำหรับชื่อคุณสมบัติที่ยังไม่ได้กำหนด" (เน้นของพวกเขา) definePropertyกำหนดคุณสมบัติล่วงหน้า วิธีเดียวที่จะทำสิ่งที่ OP ขอเป็นพร็อกซี
TJ Crowder

@TJCrowder ฉันเห็น คุณถูกต้องทางเทคนิค แต่คำถามยังไม่ชัดเจน ฉันปรับคำตอบแล้ว นอกจากนี้บางคนอาจต้องการคำตอบของเรา (ฉันทำเอง)
Andrew

@Andrew เมื่อฉันถามคำถามนี้ย้อนกลับไปในปี 2011 การใช้งานกรณีที่ฉันมีอยู่ในใจคือห้องสมุดที่สามารถส่งคืนวัตถุที่ผู้ใช้สามารถเรียกobj.whateverPropertyเช่นที่ห้องสมุดสามารถสกัดกั้นที่มีทะเยอทะยานทั่วไปและได้รับชื่อคุณสมบัติ ผู้ใช้พยายามเข้าถึง ดังนั้นความต้องการสำหรับ 'getters และ setters ทั้งหมด'
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เพียงแค่ใส่โดยตรงฟังก์ชั่นเป็นพารามิเตอร์ของ หรือถ้าด้วยเหตุผลบางอย่างคุณยืนยันที่จะสร้างแบบไดนามิกgetและsetจากนั้นใช้ฟังก์ชั่นลำดับสูงที่สร้างฟังก์ชั่นและส่งคืนมันเช่นvar get = (function(propName) { return function() { return this[propName]; };})('value');
chris-l
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.