JavaScript'te dinamik alıcılar / ayarlayıcılar uygulamak mümkün mü?


132

Böyle bir şey yaparak, ismini zaten bilen mülkler için alıcı ve ayarlayıcıların nasıl yaratılacağının farkındayım:

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

Şimdi, sorum şu, bunlar gibi her şeyi yakalayan alıcıları ve ayarlayıcıları tanımlamak mümkün mü? Yani, herhangi bir özellik adı için Alıcılar ve ayarlayıcılar oluşturmak değildir zaten tanımlanmış.

Bu kavram PHP'de __get()ve __set()sihirli yöntemlerini kullanarak mümkündür ( bunlar hakkında bilgi için PHP belgelerine bakın ), bu yüzden gerçekten bunlara eşdeğer bir JavaScript var mı diye soruyorum.

Söylemeye gerek yok, ideal olarak tarayıcılar arası uyumlu bir çözümü tercih ederim.


Bunu yapmayı başardım, cevabımı nasıl yapacağımı burada görün .

Yanıtlar:


216

2013 ve 2015 Güncellemesi (2011'deki orijinal yanıt için aşağıya bakın) :

Bu, ES2015 ("ES6" olarak da bilinir) spesifikasyonundan itibaren değişti: JavaScript artık proxy'lere sahip . Proxy'ler, diğer nesneler için (cepheler üzerinde) gerçek proxy'ler olan nesneler oluşturmanıza izin verir. Aşağıda, alma sırasında dizeler olan tüm özellik değerlerini tamamen büyük harflere çeviren basit bir örnek verilmiştir:

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

Geçersiz kılmadığınız işlemlerin varsayılan davranışları vardır. Yukarıda, geçersiz kıldığımız tek şey get, ancak bağlanabileceğiniz işlemlerin tam bir listesi var.

In getişleyici işlevin argümanlar listesinin:

  • targetvekalet edilen nesnedir ( originalbizim durumumuzda).
  • name (elbette) alınan özelliğin adıdır, bu genellikle bir dizedir ancak aynı zamanda bir Sembol de olabilir.
  • receiverthisözellik, veri özelliği yerine erişimci ise, alıcı işlevinde olduğu gibi kullanılması gereken nesnedir . Normal durumda bu ondan devralır, ancak bu proxy veya bir şeydir olabilir tuzak tarafından tetiklenebilir beri herhangi bir şey olabilir Reflect.get.

Bu, istediğiniz tümünü yakalama alıcı ve ayarlayıcı özelliğiyle bir nesne oluşturmanıza olanak tanır:

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

Yukarıdakilerin çıktısı:

Varolmayan 'foo' özelliğini alma
[önce] obj.foo = tanımsız
Var olmayan 'foo' özelliğini ayarlama, başlangıç ​​değeri: bar
[after] obj.foo = bar

fooHenüz varolmadığında geri almaya çalıştığımızda ve tekrar oluşturduğumuzda, ancak ondan sonra değilken "var olmayan" mesajını nasıl aldığımıza dikkat edin.


2011'in yanıtı (2013 ve 2015 güncellemeleri için yukarıya bakın) :

Hayır, JavaScript'in tümünü yakalama özelliği yoktur. Kullandığınız erişimci sözdizimi , spesifikasyonun 11.1.5 Bölümünde ele alınmıştır ve herhangi bir joker karakter veya buna benzer bir şey sunmaz.

Elbette, bunu yapmak için bir işlevi uygulamak olabilir, ama muhtemelen kullanmak istemiyorum tahmin ediyorum f = obj.prop("foo");ziyade f = obj.foo;ve obj.prop("foo", value);yerine obj.foo = value;(işlev bilinmeyen özelliklerini işlemek için gerekli olurdu).

FWIW, alıcı işlevi (ayarlayıcı mantığıyla uğraşmadım) şuna benzer:

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

Ama yine de, nesneyi kullanma şeklinizi nasıl değiştirdiği için bunu gerçekten yapmak isteyeceğinizi hayal edemiyorum.


1
Alternatif için var Proxy: Object.defineProperty(). Ayrıntıları yeni cevabıma koydum .
Andrew

@Andrew - Korkarım soruyu yanlış okumuşsunuz, cevabınız hakkındaki yorumuma bakın.
TJ Crowder

4

Aşağıdakiler, bu soruna orijinal bir yaklaşım olabilir:

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

Kullanabilmek için özellikler dizge olarak aktarılmalıdır. İşte nasıl çalıştığına dair bir örnek:

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

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

Düzenleme: Önerdiğim şeye dayalı geliştirilmiş, daha nesne odaklı bir yaklaşım şu şekildedir:

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

Burada çalıştığını görebilirsiniz .


Bu çalışmıyor. Özelliğin adını belirtmeden alıcı tanımlayamazsınız.
John Kurlak

@JohnKurlak Bunu kontrol et jsFiddle: jsfiddle.net/oga7ne4x Çalışıyor. Yalnızca özellik adlarını dizeler olarak iletmeniz gerekir.
clami219

3
Ah, açıkladığınız için teşekkürler. Kendi get () / set () 'inizi yazmak yerine get () / set () dil özelliğini kullanmaya çalıştığınızı sanıyordum. Yine de bu çözümü sevmiyorum çünkü orijinal sorunu gerçekten çözmüyor.
John Kurlak

@JohnKurlak Evet, orijinal bir yaklaşım olduğunu yazdım. Daha geleneksel bir yaklaşım kullanan mevcut bir koda sahip olduğunuzda sorunu çözmese de, sorunu çözmek için farklı bir yol sağlar. Ama sıfırdan başlıyorsanız iyi olur. Kesinlikle olumsuz oylamaya değmez ...
clami219

@JohnKurlak Bakalım şimdi daha iyi görünüyor! :)
clami219

0

Önsöz:

TJ Crowder'ın cevabıProxy , OP'nin istediği gibi, var olmayan mülkler için her şeyi kapsayan alıcı / ayarlayıcı için gerekli olacak olan a'dan bahsediyor . Dinamik alıcılar / ayarlayıcılar ile gerçekte hangi davranışın istendiğine bağlı olarak, aslında Proxygerekli olmayabilir; veya potansiyel olarak, Proxyaşağıda size göstereceğim şey ile a'nın bir kombinasyonunu kullanmak isteyebilirsiniz .

(Not: ProxySon zamanlarda Linux üzerinde Firefox ile kapsamlı bir şekilde deneyler yaptım ve çok yetenekli olduğunu buldum, ama aynı zamanda biraz kafa karıştırıcı / çalışmayı ve doğru yapmayı zor buldum. Daha da önemlisi, oldukça yavaş buldum (en azından JavaScript'in bugünlerde nasıl optimize edildiğiyle ilgili) - ondalık katlar dünyasında daha yavaş konuşuyorum.)


Dinamik olarak oluşturulmuş alıcıları ve ayarlayıcıları özel olarak uygulamak için Object.defineProperty()veya kullanabilirsiniz Object.defineProperties(). Bu da oldukça hızlı.

İşin özü, aşağıdaki gibi bir nesne üzerinde alıcı ve / veya ayarlayıcı tanımlayabilmenizdir:

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.

Burada dikkat edilmesi gereken birkaç nokta:

  • Sen kullanamaz value(özellik tanımlayıcısı özelliği olmayan eş zamanlı olarak yukarıda gösterilen) getve / veya set; dokümanlardan:

    Nesnelerde bulunan özellik tanımlayıcıları iki ana çeşide sahiptir: veri tanımlayıcıları ve erişimci tanımlayıcıları. Bir veri tanımlayıcı veya yazılabilir olabilir de olmayabilir de bir değere sahip olan bir özelliktir. Erişimci tanımlayıcı , alıcı-ayarlayıcı işlev çifti tarafından tanımlanan bir özelliktir. Bir tanımlayıcı bu iki çeşniden biri olmalıdır; ikisi birden olamaz.

  • Böylece, ben oluşturulan dikkat edeceğiz valmülkiyet dışında bir Object.defineProperty()çağrı / mülk tanımlayıcısı. Bu standart bir davranıştır.
  • Hata gereğince burada , set yok writableetmek truekullanırsanız özellik tanımlayıcısı getveya set.
  • Ayarlamayı düşünmek isteyebilirsiniz configurableve enumerablebununla birlikte, neyin peşinde olduğunuza bağlı olarak; dokümanlardan:

    yapılandırılabilir

    • true ancak ve ancak bu özellik tanımlayıcısının tipi değiştirilebilirse ve özellik ilgili nesneden silinebilirse.

    • Varsayılan olarak yanlıştır.


    sayılabilir

    • true Yalnızca ve yalnızca bu özellik, ilgili nesnedeki özelliklerin numaralandırılması sırasında görünürse.

    • Varsayılan olarak yanlıştır.


Bu notta, bunlar da ilgi çekici olabilir:

  • Object.getOwnPropertyNames(obj): Numaralandırılamayanlar dahil olmak üzere bir nesnenin tüm özelliklerini alır (AFAIK bunu yapmanın tek yoludur!).
  • Object.getOwnPropertyDescriptor(obj, prop): bir nesnenin, Object.defineProperty()yukarıya iletilen nesnenin özellik tanımlayıcısını alır .
  • obj.propertyIsEnumerable(prop);: belirli bir nesne örneğindeki bağımsız bir özellik için, belirli özelliğin numaralandırılabilir olup olmadığını belirlemek için nesne örneğinde bu işlevi çağırın.

2
Korkarım soruyu yanlış anladınız. OP özellikle istediği tüm catch gibi PHP'nin __getve__set . definePropertybu davayla ilgilenmiyor. Sorusuna Gönderen: "Ie, herhangi bir mülkiyet adı için Alıcılar ve ayarlayıcılar oluşturmak değildir zaten tanımlanmış." (vurguları). definePropertyözellikleri önceden tanımlar. OP'nin istediğini yapmanın tek yolu bir vekildir.
TJ Crowder

@TJCrowder Görüyorum. Soru çok açık olmasa da teknik olarak haklısınız. Cevabımı buna göre ayarladım. Ayrıca, bazıları cevaplarımızın bir kombinasyonunu gerçekten isteyebilir (ben şahsen istiyorum).
Andrew

@Andrew 2011'de bu soruyu tekrar sorduğumda, aklımdaki kullanım durumu, kullanıcının arayabileceği bir nesneyi döndürebilen bir obj.whateverPropertykitaplıktı, böylece kitaplık bunu genel bir alıcıyla kesebilir ve özellik adı verilebilir. kullanıcı erişmeye çalıştı. Bu nedenle 'her şeyi yakalayan alıcılar ve ayarlayıcılar' gereksinimi.
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)

bu benim için çalışıyor


13
Function()Böyle kullanmak kullanmak gibidir eval. Sadece doğrudan işlevleri parametreleri olarak koyun defineProperty. Veya, herhangi bir nedenle dinamik olarak oluşturmakta ısrar ediyorsanız getve setdaha sonra işlevi oluşturan ve geri döndüren yüksek dereceli bir işlevi kullanıyorsanız, örneğinvar get = (function(propName) { return function() { return this[propName]; };})('value');
chris-l
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.