Геттери / сетери для манекенів


133

Я намагався обернутись гетерами та сетерами, і це не занурюється. Я читав JavaScript Getters і Setters і визначав Getters і Setters і просто не отримував цього.

Хтось може чітко заявити:

  1. Що призначено робити геттером і сетером, і
  2. Наведіть ДУЖЕ прості приклади?

12
Особисто я не знаю, як можна отримати більш чітке пояснення, ніж Джон ...
Джейсон Бантінг

В OO світі, геттер і сетер допомагають написати клас, який правильно інкапсульований
overrexchange

В основному я бачу це так: ви визначаєте перевантаження для отримання та встановлення властивості, і ці перевантаження є функціями; але вам не потрібно їх телефонувати. Таким чином , ви можете замінити a = setValue(5);з a = 5;і setValue()тим самим буде називатися під капотом , щоб зробити все , що вам подобається.
Андрій

Відповіді:


102

Окрім відповіді @ millimoose, сеттери також можуть використовуватися для оновлення інших значень.

function Name(first, last) {
    this.first = first;
    this.last = last;
}

Name.prototype = {
    get fullName() {
        return this.first + " " + this.last;
    },

    set fullName(name) {
        var names = name.split(" ");
        this.first = names[0];
        this.last = names[1];
    }
};

Тепер ви можете встановити fullName, firstі lastбуде оновлено, і навпаки.

n = new Name('Claude', 'Monet')
n.first # "Claude"
n.last # "Monet"
n.fullName # "Claude Monet"
n.fullName = "Gustav Klimt"
n.first # "Gustav"
n.last # "Klimt"

2
@Akash: Ні, хоча Internet Explorer 9 підтримує новішу Object.definePropertyфункцію, яка може визначати геттери та сетери.
Меттью Крамлі

9
Хіба це не боляче, що MS не підтримують JS належним чином, і вони не змушують сріблястого світла працювати всюди, тому я повинен програмувати все двічі, один для SL і один для решти світу :)
Акаш Кава

2
@Martin: Ви можете зробити їх приватними, використовуючи ту саму техніку, що і у відповіді Джона . Якщо ви хочете використовувати реальні геттери / сетери, вам доведеться використовувати this.__defineGetter__або новішу Object.definePropertyфункцію.
Меттью Крамлі

1
Лише одна проблема з переліченим вище підходом, якщо ви хочете додати геттери та сеттери для вже існуючого класу, це замінить прототипи, і оригінальні методи не будуть доступні.
xchg.ca

1
Чи не підходить цей підхід Name.prototype.constructor? Здається, погана альтернатива відповіді Мілімуза .
r0estir0bbe

62

Геттери та сетери в JavaScript

Огляд

Геттери і сетери в JavaScript використовуються для визначення обчислених властивостей або аксесуарів . Обчислена властивість - це те, що використовує функцію для отримання або встановлення значення об'єкта. Основна теорія робить щось подібне:

var user = { /* ... object with getters and setters ... */ };
user.phone = '+1 (123) 456-7890'; // updates a database
console.log( user.areaCode ); // displays '123'
console.log( user.area ); // displays 'Anytown, USA'

Це корисно для того, щоб автоматично робити речі за кадром, коли доступ до ресурсу, наприклад, зберігання чисел у діапазоні, переформатування рядків, запуск подій, що змінюються значенням, оновлення реляційних даних, надання доступу до приватних ресурсів тощо.

Наведені нижче приклади показують основний синтаксис, хоча вони просто отримують і встановлюють внутрішнє значення об'єкта, не роблячи нічого особливого. У реальних випадках ви можете змінити вхідні та / або вихідні значення відповідно до ваших потреб, як зазначено вище.

get / set Ключові слова

Підтримки getта setключові слова ECMAScript 5 для визначення обчислюваних властивостей. Вони працюють з усіма сучасними браузерами, крім IE 8 і нижче.

var foo = {
    bar : 123,
    get bar(){ return bar; },
    set bar( value ){ this.bar = value; }
};
foo.bar = 456;
var gaz = foo.bar;

Спеціальні користувачі та сетери

getі setне є зарезервованими словами, тому їх можна перевантажувати, щоб створити власні власні функції, обчислені між веб-браузерами. Це буде працювати в будь-якому браузері.

var foo = {
    _bar : 123,
    get : function( name ){ return this[ '_' + name ]; },
    set : function( name, value ){ this[ '_' + name ] = value; }
};
foo.set( 'bar', 456 );
var gaz = foo.get( 'bar' );

Або для більш компактного підходу може використовуватися одна функція.

var foo = {
    _bar : 123,
    value : function( name /*, value */ ){
        if( arguments.length < 2 ){ return this[ '_' + name ]; }
        this[ '_' + name ] = value;
    }
};
foo.value( 'bar', 456 );
var gaz = foo.value( 'bar' );

Уникайте подібних дій, що може призвести до роздуття коду.

var foo = {
    _a : 123, _b : 456, _c : 789,
    getA : function(){ return this._a; },
    getB : ..., getC : ..., setA : ..., setB : ..., setC : ...
};

У наведених вище прикладах внутрішні назви властивостей абстрагуються підкресленням, щоб відмовити користувачам просто робити в foo.barпорівнянні з foo.get( 'bar' )та отримувати значення "uncooked". Ви можете використовувати умовний код, щоб робити різні речі залежно від назви ресурсу, до якого доступ (через nameпараметр).

Object.defineProperty ()

Використання Object.defineProperty()- це ще один спосіб додавання геттерів та сеттерів, і їх можна використовувати на об'єктах після їх визначення. Він також може бути використаний для встановлення настроюваних та перелічених форм поведінки. Цей синтаксис також працює з IE 8, але, на жаль, лише на об'єктах DOM.

var foo = { _bar : 123 };
Object.defineProperty( foo, 'bar', {
    get : function(){ return this._bar; },
    set : function( value ){ this._bar = value; }
} );
foo.bar = 456;
var gaz = foo.bar;

__defineGetter __ ()

Нарешті, __defineGetter__()ще один варіант. Він застарілий, але все ще широко використовується в Інтернеті, і тому навряд чи зникне незабаром. Він працює у всіх браузерах, крім IE 10 і нижче. Хоча інші варіанти також добре працюють на не-IE, тому цей не такий корисний.

var foo = { _bar : 123; }
foo.__defineGetter__( 'bar', function(){ return this._bar; } );
foo.__defineSetter__( 'bar', function( value ){ this._bar = value; } );

Також варто зазначити, що в останніх прикладах внутрішні імена повинні відрізнятися від імен доступу, щоб уникнути рекурсії (тобто foo.barвиклику foo.get(bar)виклику foo.barвиклику foo.get(bar)...).

Дивитися також

MDN отримати , встановити , Object.defineProperty () , __defineGetter __ () , __defineSetter __ ()
MSDN IE8 Getter Support


1
У більш компактному підході , this[ '_' + name ] = value;може бути this[ '_' + name ] = arguments[1];і не було б ніякої необхідності вказувати valueаргумент.
Редхарт

1
Приклад: var foo = { bar : 123, get bar(){ return bar; }, set bar( value ){ this.bar = value; } }; foo.bar = 456; підвищує виняток: Uncaught RangeError: Максимальний розмір стека викликів перевищено на панелі Object.set [як бар] (<анонімний>: 4: 32) на панелі Object.set [як бар] (<анонімний>: 4: 32) ) на панелі Object.set [як бар] (<анонімний>: 4: 32) на панелі Object.set [як бар] (<анонімний>: 4: 32) на панелі Object.set [як бар] (<барний> : 4: 32) на барі Object.set [як бар] (<анонімний>: 4: 32)
nevf

1
Ім'я набору / отримання має відрізнятися від імені властивості. Тож замість bar: 123і this.bar = valueт. Д. Змініть їх, _bar наприклад. Див: hongkiat.com/blog/getters-setters-javascript
nevf

@nevf Дякую за виправлення! Так, зазвичай з обчислюваними властивостями "справжній" внутрішній називається як _fooабо mFoo. Якщо це те саме, що і геттер / сеттер, він спричинить нескінченний цикл через рекурсію, а потім переповнення стека ™ ;-), тому що коли ви говорите a = b, він викликає a.get (b), який сам називає a = b , який закликає a.get (b), ...
Beejor

58

Ви використовуєте їх, наприклад, для реалізації обчислених властивостей.

Наприклад:

function Circle(radius) {
    this.radius = radius;
}

Object.defineProperty(Circle.prototype, 'circumference', {
    get: function() { return 2*Math.PI*this.radius; }
});

Object.defineProperty(Circle.prototype, 'area', {
    get: function() { return Math.PI*this.radius*this.radius; }
});

c = new Circle(10);
console.log(c.area); // Should output 314.159
console.log(c.circumference); // Should output 62.832

(CodePen)


Гаразд, я думаю, я починаю це розуміти. Я намагаюся призначити getter властивістю довжини об’єкта масиву, але отримую помилку: "Перевизначення довжини var" І код виглядає приблизно так: obj = []; obj .__ defineGetter __ ('length', function () {return this.length;});

1
Це тому, що об’єкти Array вже мають властивість вбудованої довжини. Якщо передекларація була дозволена, виклик нової довжини повторювався б нескінченно. Спробуйте назвати властивість "my_length" або щось подібне.
мільйозу

Для того, щоб визначити обидва getters в одному операторі, використовуйте Object.defineProperties.
r0estir0bbe

Ви не можете просто зробити {"area": ​​function () {return ...}}? просто призначте його як об’єкт властивості
RegarBoy

@developer Це не Getter Javascript, як визначено мовою, це лише функція. Вам потрібно зателефонувати, щоб отримати значення, воно не перевантажує доступ до ресурсу. Також є спеціальне коло пекла, зарезервоване для людей, які вигадують власні розбиті об'єктні системи в JS, а не будують на тій, яку вона вже має.
мілімоз

16

Вибачте, що відновив старе запитання, але я подумав, що можу зробити декілька дуже основних прикладів та пояснень для манекенів. Жоден з інших відповідей, розміщених таким чином, не ілюструє синтаксис, як перший приклад керівництва MDN , який є настільки ж базовим, як можна отримати.

Getter:

var settings = {
    firstname: 'John',
    lastname: 'Smith',
    get fullname() { return this.firstname + ' ' + this.lastname; }
};

console.log(settings.fullname);

... Зробить журнал John Smith, звичайно. А гетерні поводиться як властивість змінюваного об'єкта, але забезпечує гнучкість функції для розрахунку її значення, що повертається на льоту. Це в основному фантазійний спосіб створити функцію, яка не вимагає () під час дзвінка.

Сетер:

var address = {
    set raw(what) {
        var loc = what.split(/\s*;\s*/),
        area = loc[1].split(/,?\s+(\w{2})\s+(?=\d{5})/);

        this.street = loc[0];
        this.city = area[0];
        this.state = area[1];
        this.zip = area[2];
    }
};

address.raw = '123 Lexington Ave; New York NY  10001';
console.log(address.city);

... увійде New Yorkдо консолі. Як і геттери, сеттери називаються з тим же синтаксисом, що і встановлення значення властивості об'єкта, але це ще один фантазійний спосіб викликати функцію без ().

Дивіться цю jsfiddle для більш ретельного, можливо, більш практичного прикладу. Передача значень у сеттер об'єкта ініціює створення або сукупність інших об'єктів. Зокрема, у прикладі jsfiddle передача масиву чисел спонукає сетера обчислити середнє, медіану, режим та діапазон; потім встановлює властивості об'єкта для кожного результату.


Я все ще не розумію переваги використання get і set vs створення getMethod або setMethod. Єдина перевага, яку можна назвати без ()? Повинно бути ще одна причина, що вона додається до JavaScript.
Андреас

@Andreas Геттери та сетери поводяться як властивості, коли викликаються, що може допомогти сформулювати їх призначення. Вони не розблокують інакше відсутні здібності, але їх використання може допомогти вам організувати свої думки. Це реальна користь. Як практичний приклад, я використовував геттер для розширення об’єкта Google Maps. Мені потрібно було обчислити кут нахилу камери, щоб я міг обертати плитки карти плоскою до горизонту. Google робить це автоматично на задньому кінці; але в той час мені було корисно відшукати maps.rollяк властивість, а не maps.roll()повернення val. Це просто вподобання.
rojo

так що це просто синтаксичний цукор, щоб зробити код більш чистим без (). Я не можу зрозуміти, чому ви не могли б зробити свій прикладmaps.roll()
Андреас

@Andreas Хто каже, що я не міг? Як я кажу, це просто спосіб допомогти мені організувати свої думки. Кодування - це мистецтво. Ви не питаєте Боба Росса, чому йому довелося використовувати спалену охру, коли він міг використовувати апельсин. Можливо, ви зараз не бачите потреби, але одного дня, коли ви вирішите, що для вашої картини потрібно трохи спалити охру, вона буде на вашій палітрі.
rojo

:) одне, що я бачу, що синтаксис отримання та встановлення робить, це автоматично запускається, якщо він використовується як властивість властивості.
Андреас

11

Геттери та сетери дійсно мають сенс лише тоді, коли у вас є приватні властивості класів. Оскільки Javascript насправді не має властивостей приватного класу, як це зазвичай можна думати з об'єктно-орієнтованих мов, це може бути важко зрозуміти. Ось один приклад приватного лічильника. Приємно в цьому об’єкті - те, що до внутрішньої змінної "count" не можна отримати доступ ззовні.

var counter = function() {
    var count = 0;

    this.inc = function() {
        count++;
    };

    this.getCount = function() {
        return count;
    };
};

var i = new Counter();
i.inc();
i.inc();
// writes "2" to the document
document.write( i.getCount());

Якщо ви все ще розгублені, подивіться статтю Крокфорда про приватних членів у Javascript .


39
Я не погоджуюсь. Геттери і сетери також дуже корисні для інкапсуляції інформації, визначення якої може бути не простою змінною. Це може бути зручно, якщо вам потрібно змінити поведінку системи, яка раніше використовувала прості властивості і від яких можуть залежати інші речі. Крім того, ваш приклад демонструє лише "псевдопобутові", які є лише функціями. Реальні користувачі JavaScript виглядають як прості значення (і до них звертаються без позначень функцій), отже, реальна сила їх.
devios1

Не впевнений, чи назвав би я цього потужного. Що - то з'являється в X , але це на самому справі Y не обов'язково ясно. Я б абсолютно НЕ очікував var baz = foo.barмати за собою повний роздутий набір прихованої поведінки. Однак я б цього очікував foo.getBar().
AgmLauncher

8

Я думаю, перша стаття, яку ви посилаєте, констатує це досить чітко:

Очевидною перевагою написання JavaScript таким чином є те, що ви можете використовувати його неясні значення, до яких ви не хочете, щоб користувач мав прямий доступ.

Мета полягає в тому, щоб інкапсулювати та абстрагувати поля, лише дозволяючи доступ до них через метод get()або set()метод. Таким чином, ви можете зберігати поле / дані всередині будь-якого способу, але зовнішні компоненти є лише опублікованим інтерфейсом. Це дозволяє вносити внутрішні зміни без зміни зовнішніх інтерфейсів, робити певну перевірку чи перевірку помилок у межах set()методу тощо.


6

Хоча часто ми звикли бачити об’єкти із загальнодоступними властивостями без будь-якого контролю доступу, JavaScript дозволяє нам точно описати властивості. Насправді ми можемо використовувати дескриптори, щоб контролювати, як можна отримати доступ до властивості та яку логіку ми можемо застосувати до неї. Розглянемо наступний приклад:

var employee = {
    first: "Boris",
    last: "Sergeev",
    get fullName() {
        return this.first + " " + this.last;
    },
    set fullName(value) {
        var parts = value.toString().split(" ");
        this.first = parts[0] || "";
        this.last = parts[1] || "";
    },
    email: "boris.sergeev@example.com"
};

Кінцевий результат:

console.log(employee.fullName); //Boris Sergeev
employee.fullName = "Alex Makarenko";

console.log(employee.first);//Alex
console.log(employee.last);//Makarenko
console.log(employee.fullName);//Alex Makarenko

2

Що так заплутано в цьому ... getters - це функції, які викликаються, коли ви отримуєте властивість, setters, коли ви їх встановлюєте. Наприклад, якщо ви робите

obj.prop = "abc";

Ви встановлюєте опору властивості, якщо ви використовуєте getters / setters, тоді буде викликана функція setter з аргументом "abc". Визначення функції сеттера всередині об'єкта в ідеалі виглядатиме приблизно так:

set prop(var) {
   // do stuff with var...
}

Я не впевнений, наскільки добре це реалізовано в браузерах. Здається, Firefox також має альтернативний синтаксис із подвійним підкресленням спеціальних ("магічних") методів. Як звичайно, Internet Explorer нічого з цього не підтримує.


2

Ви можете визначити метод екземпляра для класу js через прототип конструктора.

Далі наведено зразок коду:

// BaseClass

var BaseClass = function(name) {
    // instance property
    this.name = name;
};

// instance method
BaseClass.prototype.getName = function() {
    return this.name;
};
BaseClass.prototype.setName = function(name) {
    return this.name = name;
};


// test - start
function test() {
    var b1 = new BaseClass("b1");
    var b2 = new BaseClass("b2");
    console.log(b1.getName());
    console.log(b2.getName());

    b1.setName("b1_new");
    console.log(b1.getName());
    console.log(b2.getName());
}

test();
// test - end

І це повинно працювати для будь-якого браузера, ви також можете просто використовувати nodejs для запуску цього коду.


1
Це просто створення нових методів getName та setName. Вони не пов'язані зі створенням власності!
uzay95

2

Мене також дещо збентежило прочитане пояснення , оскільки я намагався додати властивість до існуючого прототипу, який я не писав, тому заміна прототипу здавалася невірним підходом. Отже, для нащадків, ось як я додав lastвластивість до Array:

Object.defineProperty(Array.prototype, "last", {
    get: function() { return this[this.length - 1] }
});

Ніколи не так приємніше, ніж додавання функції IMHO.


1

Якщо ви маєте на увазі концепцію аксесуарів, то найпростішою метою є приховання базового сховища від довільних маніпуляцій. Найбільш крайній механізм для цього є

function Foo(someValue) {
    this.getValue = function() { return someValue; }
    return this;
}

var myFoo = new Foo(5);
/* We can read someValue through getValue(), but there is no mechanism
 * to modify it -- hurrah, we have achieved encapsulation!
 */
myFoo.getValue();

Якщо ви маєте на увазі фактичну функцію отримання / встановлення JS, наприклад. defineGetter/ defineSetter, або { get Foo() { /* code */ } }, тоді варто зазначити, що в більшості сучасних двигунів подальше використання цих властивостей буде набагато повільніше, ніж це було б інакше. напр. порівняти продуктивність

var a = { getValue: function(){ return 5; }; }
for (var i = 0; i < 100000; i++)
    a.getValue();

vs.

var a = { get value(){ return 5; }; }
for (var i = 0; i < 100000; i++)
    a.value;

-1

У мене є один для вас, хлопці, який може бути трохи некрасивим, але це робиться на всіх платформах

function myFunc () {

var _myAttribute = "default";

this.myAttribute = function() {
    if (arguments.length > 0) _myAttribute = arguments[0];
    return _myAttribute;
}
}

таким чином, коли ви телефонуєте

var test = new myFunc();
test.myAttribute(); //-> "default"
test.myAttribute("ok"); //-> "ok"
test.myAttribute(); //-> "ok"

Якщо ви дійсно хочете приправити речі. Ви можете вставити чек типу:

if (arguments.length > 0 && typeof arguments[0] == "boolean") _myAttribute = arguments[0];
if (arguments.length > 0 && typeof arguments[0] == "number") _myAttribute = arguments[0];
if (arguments.length > 0 && typeof arguments[0] == "string") _myAttribute = arguments[0];

або ще більше шаленійте з розширеним кодом typeof check: type.of () на сайті codingforums.com


справа полягала в тому, щоб мати можливість змінити атрибут на щось більш фантазійне, не потребуючи зміни публічного інтерфейсу. Додавання тегу call () змінює його.
Майкл Скотт Катберт
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.