Геттеры \ сеттеры для чайников

133

Я пытаюсь получить мою голову вокруг геттеры и сеттеры и его не тонет. Я читал JavaScript геттеры и сеттеры и Defining геттеры и сеттеры и просто не получить его.

Может кто-то четко заявить:

  1. Для чего предназначены геттер и сеттер, и
  2. Приведите ОЧЕНЬ простые примеры?
Александр Абакумов
источник
12
Лично я не знаю, как вы можете получить более четкое объяснение, чем у Джона ...
Джейсон Бантинг
В ОО-мире методы получения и установки помогают написать класс, который правильно инкапсулирован
overexchange
По сути, я вижу это так: вы определяете перегрузки для получения и установки свойства, и эти перегрузки являются функциями; но вам не нужно звонить им. Таким образом, вы можете заменить 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 правильно, и они не запускают свой silverlight везде, поэтому мне приходится программировать все дважды, один для SL и один для остального мира :)
Akash Kava
2
@ Мартин: Вы можете сделать их приватными, используя ту же технику, что и в ответе Джона . Если вы хотите использовать реальные методы получения / установки, вам придется использовать 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'

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

В приведенных ниже примерах показан базовый синтаксис, хотя они просто получают и устанавливают значение внутреннего объекта, не делая ничего особенного. В реальных случаях вы должны изменить входное и / или выходное значение в соответствии с вашими потребностями, как указано выше.

получить / установить ключевые слова

ECMAScript 5 поддерживает getи setключевые слова для определения вычисляемых свойств. Они работают со всеми современными браузерами, кроме 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' )и получать «сырое» значение. Вы можете использовать условный код для различных действий в зависимости от имени свойства, к которому осуществляется доступ (через 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 get , set , Object.defineProperty () , __defineGetter __ () , __defineSetter __ ()
MSDN IE8 Getter Поддержка

Beejor
источник
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 [as bar] (<anonymous>: 4: 32) на панели Object.set [as bar] (<anonymous>: 4: 32) на панели Object.set [as bar] (<anonymous> : 4: 32) на панели Object.set [as bar] (<аноним>: 4: 32)
nevf
1
Имя set / get должно отличаться от имени свойства. Так что вместо bar: 123и this.bar = valueт. Д. Измените их, _bar например. Смотрите: hongkiat.com/blog/getters-setters-javascript
невф
@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)

millimoose
источник
Хорошо, я думаю, что начинаю понимать. Я пытаюсь присвоить метод получения свойству length объекта массива, но получаю сообщение об ошибке: «Переопределение длины var» И код выглядит так: obj = []; obj .__ defineGetter __ ('length', function () {return this.length;});
1
Это потому, что объекты Array уже имеют встроенное свойство length. Если переопределение было разрешено, вызов новой длины будет повторяться бесконечно. Попробуйте вызвать свойство "my_length" или что-то подобное.
Миллимы
Чтобы определить оба получателя в одном утверждении, используйте Object.defineProperties.
r0estir0bbe
Разве вы не можете просто сделать {"area": ​​function () {return ...}}? просто назначьте его как свойство объекта
RegarBoy
@developer Это не средство получения 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 по сравнению с созданием getMethod или setMethod. Единственное преимущество, которое вы можете назвать без ()? Должна быть еще одна причина, по которой он добавлен в javascript.
Андреас
@ Andreas Получатели и установщики ведут себя как свойства при вызове, что может помочь сформулировать их предполагаемое назначение. Они не открывают отсутствующие способности, но их использование может помочь вам организовать ваши мысли. Это реальная выгода. В качестве практического примера я использовал геттер для расширения объекта Google Maps. Мне нужно было рассчитать угол поворота камеры, чтобы я мог поворачивать плитки карты горизонтально к горизонту. Google делает это автоматически на сервере; но в то время мне было полезно получить его maps.rollкак свойство, а не maps.roll()как возвращаемое значение val. Это просто предпочтение.
Рохо
так что это просто синтаксический сахар, чтобы код выглядел чище без (). Я не могу понять, почему вы не могли ваш пример сmaps.roll()
Андреас
@ Андреас Кто сказал, что я не могу? Как я уже сказал, это просто способ помочь мне сохранить мои мысли организованными. Кодирование - это искусство. Вы не спрашиваете Боба Росса, почему он должен был использовать сожженную охру, когда он мог бы использовать апельсин. Возможно, вы не видите необходимости сейчас, но однажды, когда вы решите, что вашей картине нужна немного прожженная охра, она будет на вашей палитре.
rojo
:) Одна вещь, которую я вижу, что синтаксис get и set делает, это автозапуск, если он используется как свойство свойства.
Андреас
11

Методы получения и установки действительно имеют смысл, только когда у вас есть частные свойства классов. Поскольку Javascript на самом деле не имеет свойств закрытого класса, как вы обычно думаете из Object Oriented Languages, это может быть трудно понять. Вот один пример частного счетчика объекта. Хорошая вещь об этом объекте - то, что внутренняя переменная "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

Что в этом запутанного ... геттеры - это функции, которые вызываются, когда вы получаете свойство, сеттеры, когда вы его устанавливаете. Например, если вы делаете

obj.prop = "abc";

Вы устанавливаете свойство prop, если вы используете getter / 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.

shawkinaw
источник
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();

против

var a = { get value(){ return 5; }; }
for (var i = 0; i < 100000; i++)
    a.value;
olliej
источник
-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"

Если вы действительно хотите оживить вещи ... вы можете вставить проверку typeof:

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: type.of () код в codingforums.com

artsy.ca
источник
Суть в том, чтобы иметь возможность изменить атрибут на что-то более изощренное без необходимости изменения публичного интерфейса. Добавление тега call () меняет его.
Майкл Скотт Катберт