Як визначити сеттер / геттер на прототипі


78

EDIT Жовтень 2016 : Зверніть увагу, що це питання було задано у 2012 році. Щомісяця хтось додає нову відповідь або коментар, який спростовує відповідь, але насправді не має сенсу робити це, оскільки питання, ймовірно, застаріло (пам’ятайте, це було для Gnome Javascript для написання розширень гном-оболонка, а НЕ браузер матеріалу, який є вельми специфічний).

Після мого попереднього запитання про те, як зробити підклас у Javascript, я роблю підклас суперкласу таким чином:

function inherits(Child,Parent) {
    var Tmp = function {};
    Tmp.prototype = Parent.prototype;
    Child.prototype = new Tmp();
    Child.prototype.constructor = Child;
}
/* Define subclass */
function Subclass() {
    Superclass.apply(this,arguments);
    /* other initialisation */
}
/* Set up inheritance */
inherits(Subclass,Superclass);
/* Add other methods */
Subclass.prototype.method1 = function ... // and so on.

Моє питання: як мені визначити сеттер / геттер на прототипі за допомогою цього синтаксису?

Раніше я робив:

Subclass.prototype = {
    __proto__: Superclass.prototype,
    /* other methods here ... */

    get myProperty() {
        // code.
    }
}

Але очевидно, що наступне не буде працювати:

Subclass.prototype.get myProperty() { /* code */ }

Я використовую GJS (GNOME Javascript), і двигун повинен бути більш-менш таким, як у Mozilla Spidermonkey. Мій код не призначений для браузера, тому, поки він підтримується GJS (мабуть, це означає Spidermonkey?), Я не проти, якщо він не сумісний.


У документах Mozilla згадується __defineGetter__та __defineSetter(але я насправді ніколи їх не використовував ...). developer.mozilla.org/en/Core_JavaScript_1.5_Guide/…
bfavaretto

Фантастично, це схоже на те, що я шукаю. Якщо ви опублікуєте це як відповідь, я прийму його. ура! :)
математична

Зробив це і додав приклади з MDN.
bfavaretto

1
Будь ласка, не роби цього. Спадщина в JS складається з трьох рядків: викликаємо супер клас, встановлюємо прототип на суперклас і скидаємо конструктор назад до дочірнього класу. Кінець. Такі методи написання - повна втрата часу.
Hal50000

Відповіді:


74

Використання об’єктного буквального оголошення (найпростіший спосіб):

var o = {
    a: 7,
    get b() {
        return this.a + 1;
    },
    set c(x) {
        this.a = x / 2
    }
};

Використання Object.defineProperty(у сучасних браузерах, що підтримують ES5):

Object.defineProperty(o, "myProperty", {
    get: function myProperty() {
        // code
    }
});

Або використовуючи __defineGetter__та __defineSetter__( ЗАСТОСОВАНО ):

var d = Date.prototype;
d.__defineGetter__("year", function() { return this.getFullYear(); });
d.__defineSetter__("year", function(y) { this.setFullYear(y); });

Вибачте, моя помилка. Я неправильно прочитав, що для нової позначення функції, введеної в es6:let foo = { bar () { return baz }}
royhowie

@royhowie Я бачу, синтаксис ES5 get/, setсхоже, надихнув синтаксис нового методу :)
bfavaretto

Я не думаю, що потрібно знову писати ім'я функції для геттера. У прикладах документа MDN використовуються анонімні функції.
StanE

Також будьте обережні з лямбдами в геттерах / сетерах. thisу лямбдах відноситься до глобального об'єкта.
grabantot

102

Використовуйте Object.defineProperty()на Subclass.prototype. Є __defineGetter__ і __defineSetter__доступні в деяких браузерах, але вони застаріли. Для вашого прикладу це буде:

Object.defineProperty(Subclass.prototype, "myProperty", {
    get: function myProperty() {
        // code
    }
});

1
"вони застаріли (так само __proto__)" - зауважте, __proto__що стандартизовано станом на проект ES6.
Fabrício Matté

@ FabrícioMatté: Я знаю (але мені це не подобається). Однак суть тут полягала у використанні Object.createзамість цього.
Бергі,

Так, я просто додав примітку, що він не буде вважатись застарілим, як тільки ES6 стане стандартом (навіть витік для IE11, мабуть, уже реалізований). Хоча, звичайно, є багато часу, поки ми не зможемо подумати про використання його в реальному середовищі, для тих небагатьох випадків використання, які можуть бути.
Fabrício Matté

1
Дух! "Як визначити сеттер / геттер [властивість] на прототипі?" "Ви визначаєте це як властивість прототипу."
Йоганн,

2
@bob Не впевнений, що повинен означати ваш коментар. Звичайно, якщо ви хочете динамічно отримувати тривалість, вам знадобиться геттер, але він може працювати з іншими властивостями так само, як і всі інші методи:Object.defineProperty(…, "duration", { get: function() { return this.end - this.begin; }});
Бергі,

41

Я думаю, ви хотіли зробити так:

function Unit() {
   	this._data; // just temp value
}
Unit.prototype = {
 	get accreation() {
   		return this._data;
   	},
   	set accreation(value) {
   		this._data = value
   	},
}
Unit.prototype.edit = function(data) {
   	this.accreation = data; // setting
   	this.out();
};

Unit.prototype.out = function() {
    alert(this.accreation); // getting
};

var unit = new Unit();
unit.edit('setting and getting');

function Field() {
    // children
}

Field.prototype = Object.create(Unit.prototype);

Field.prototype.add = function(data) {
  this.accreation = data; // setting
   	this.out();
}

var field1 = new Field();
field1.add('new value for getter&setter');

var field2 = new Field();
field2.out();// because field2 object has no setting


2
Дивовижний. Єдина правильна відповідь не набирає голосів. Існує НУЛЬКА причина використовувати, Object.definePropertyякщо вам не доведеться зазнати образи старого IE.
Hal50000

1
@ Hal50000 Ви ігноруєте, що ця відповідь була опублікована майже через 4 роки після постановки запитання.
faintsignal

1
@faintsignal Насправді, щоб виправити себе, є кілька причин використовувати Object.defineProperty()замість наведеного вище прикладу, коли ви хочете встановити перелічувані, налаштовувані, доступні для запису тощо. Але ви маєте рацію, погляд на дату публікації є хорошим ідея.
Hal50000

6
Unit.prototype.constructorстирається при Unit.prototypeтакому перезаписі . Мені це рішення дуже подобається. Може, ти міг би це зробити Object.assign(Unit.prototype, { get accreation() { ... } });.
jonS90

1
як згадує @ jonS90, є кілька досить вагомих причин, чому цей метод може бути поганою ідеєю. Ви втрачаєте успадкування класу тощо. Вам було б краще використовувати альтернативні методи, якщо ви використовуєте ієрархію класів. наприклад, ви не можете застосувати властивість за Fieldдопомогою цього методу.
cmroanirgo

5

Щоб визначити сеттери та геттери "всередині прототипу об'єкта", потрібно зробити щось подібне:

Object.defineProperties(obj.__proto__, {"property_name": {get: getfn, set: setfn}})

Ви можете скоротити це за допомогою функції утиліти:

//creates get/set properties inside an object's proto
function prop (propname, getfn, setfn) {
    var obj = {};
    obj[propname] = { get: getfn, set: setfn };
    Object.defineProperties(this, obj);        
}

function Product () {
     this.name =  "Product";
     this.amount =  10;
     this.price =  1;
     this.discount =  0;
}

//how to use prop function
prop.apply(Product.prototype, ["total", function(){ return this.amount * this.price}]);

pr = new Product();
console.log(pr.total);

Тут ми використовуємо prop.apply для встановлення контексту Product.prototype як "це", коли ми його називаємо.

За допомогою цього коду ви закінчуєте властивістю get / set усередині прототипу об’єкта, а не екземпляром, як задано питання.

(Перевірено Firefox 42, Chrome 45)


Дуже мило. Хоча, ви не застосували сеттер у вашому прикладі, тому спроба встановити pr.totalмогла б невдало. На практиці, мабуть, найкраще передавати функцію сеттера, щоб видавати помилку, коли потрібно лише створити геттер.
Faintsignal

4

Вкажіть геттер або сеттер у конструкторах методом Object.defineProperty (). Цей метод приймає три аргументи: перший аргумент - це об’єкт, до якого слід додати властивість, другий - ім’я властивості, третій - дескриптор властивості. Наприклад, ми можемо визначити конструктор для нашого об'єкта person таким чином:

var Employee = (function() {
    function EmployeeConstructor() {
        this.first = "";
        this.last = "";
        Object.defineProperty(
            this,
            "fullName", {
                get: function() {
                    return this.first + " " +
                        this.last;
                },
                set: function(value) {
                    var parts = value.toString().split(" ");
                    this.name = parts[0] || "";
                    this.last = parts[1] || "";
                }
            });
    }
    return
    EmployeeConstructor;
}());

Використання Object.defineProperty () дає більше контролю над нашим визначенням властивостей. Наприклад, ми можемо вказати, чи властивість, яку ми описуємо, може бути динамічно видалена або перевизначена, чи можна змінити її значення тощо.

Ми можемо застосувати такі обмеження, встановивши такі властивості об’єкта дескриптора:

  • доступний для запису: це логічне значення, яке говорить про те, чи можна змінювати значення властивості; значення за замовчуванням - false
  • конфігурується: це логічна формула, яка говорить про те, чи можна змінити дескриптор властивості або видалити саму властивість; значення за замовчуванням - false
  • enumerable: Це логічне значення, яке вказує, чи можна отримати доступ до властивості в циклі над властивостями об'єкта; значення за замовчуванням - false
  • значення: Це представляє вартість, пов’язану з властивістю; значення за замовчуванням не визначено

0

Ось простий приклад Animal → Dogуспадкування з Animalнаявністю геттера та сеттера :

//////////////////////////////////////////
// General Animal constructor
function Animal({age, name}) {
  // if-statements prevent triggering the setter on initialization
  if(name) this.name = name
  if(age) this.age = age
}

// an alias "age" must be used, so the setter & getter can use an
// alternative variable, to avoid using "this.age", which will cause
// a stack overflow of "infinite" call stack when setting the value.
Object.defineProperty(Animal.prototype, "age", {
  get(){
    console.log("Get age:", this.name, this._age) // getting
    return this._age
  },
  set(value){
    this._age = value
    console.log("Set age:", this.name, this._age) // setting
  }
})




//////////////////////////////////////////
// Specific Animal (Dog) constructor
function Dog({age = 0, name = 'dog'}) {
  this.name = name
  this.age = age
}

// first, defined inheritance
Dog.prototype = new Animal({});

// add whatever additional methods to the prototype of Dog
Object.assign(Dog.prototype, {
  bark(woff){
    console.log(woff)
  }
})


//////////////////////////////////////////
// Instanciating
var koko = new Animal({age:300, name:'koko'})
var dog1 = new Dog({age:1, name:'blacky'})
var dog2 = new Dog({age:5, name:'shorty'})

console.log(dog1)
koko.age
dog1.age = 3;
dog1.age
dog2.age

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.