Получатель / установщик в конструкторе - PullRequest
30 голосов
/ 07 марта 2011

Я недавно читал о том, что существует возможность определения геттеров / сеттеров в JavaScript.Это кажется чрезвычайно полезным - сеттер является своего рода «помощником», который может анализировать значение, которое должно быть установлено в первую очередь, перед его фактической установкой.

Например, у меня в настоящее время есть такой код:

var obj = function(value) {
    var test = !!value; // 'test' has to be a boolean
    return {
        get test() { return test },
        set test(value) { test = !!value }
    };
};

var instance = new obj(true);

Этот код всегда конвертирует value в логическое значение.Так что если вы кодируете instance.test = 0, то instance.test === false.

Однако, чтобы это работало, вы должны фактически вернуть объект , что означает, что новый экземпляр не имеет типа obj но это простой объект.Это означает, что изменение прототипа obj не влияет на экземпляры.Например, это не работает - instance.func не определено:

obj.prototype.func = function() { console.log(this.value); };

, поскольку instance не относится к типу obj.Чтобы заставить работать функции прототипа, я думаю, что я не должен возвращать простой объект, а скорее ничего не возвращать, чтобы instance был просто типа obj, как работает обычный конструктор.

Проблематогда как реализовать геттеры / сеттеры?Я могу только найти статьи, описывающие, как добавить их к объекту, а не как часть конструктора пользовательского типа.

Итак, как мне реализовать getters / setters в конструкторе, чтобы иметь возможностьиспользовать геттеры / сеттеры и расширять прототип?

Ответы [ 6 ]

45 голосов
/ 07 марта 2011

Вы не можете этого сделать.

Вы можете установить setter / getters для свойств объектов.Я советую вам использовать ES5 Object.defineProperties.конечно, это работает только в современных браузерах.

var obj = function() {
    ...
    Object.defineProperties(this, {
        "test": {
             "get": function() { ... },
             "set": function() { ... }
        }
    });
}

obj.prototype.func = function() { ... }

var o = new obj;
o.test;
o.func();
9 голосов
/ 20 ноября 2016

Обычно вам нужны класс методы.Ответ @Raynos от 7 мая 2011 года завершает работу, но он определяет метод instance , а не метод класса.

Следующее иллюстрирует определение класса, где getter и setter являются частью класса.Это определение во многом похоже на ответ @Raynos, но с двумя отличиями в коде: (1) Действие defineProperties () было удалено из конструктора.(2) Аргумент "defineProperties ()", который был изменен с объекта экземпляра "this", на объект-прототип конструктора.

function TheConstructor(side) {
  this.side = side;
}

Object.defineProperties(TheConstructor.prototype, {
        area: {
             get: function()    { return this.side * this.side; }
            ,set: function(val) { this.side = Math.sqrt(val);   }
        }
});

// Test code:

var anInstance = new TheConstructor(2);
console.log("initial  Area:"+anInstance.area);
anInstance.area = 9;
console.log("modified Area:"+anInstance.area);

, который приводит к следующим результатам:

initial  Area:4
modified Area:9

Хотя обычно различие между классом и определением экземпляра - это просто вопрос стиля, есть смысл в хорошем стиле, и есть случай, когда различие имеет значение: запоминаемый получатель .Цель для записанного получателя описана здесь: Умные / перезаписываемые / ленивые получатели

Определите получатель на уровне класса, когда записанное значение должно относиться ко всему классу.Например, файл конфигурации должен быть прочитан только один раз;результирующие значения должны применяться в течение всей программы.В следующем примере кода определяется запомненный геттер на уровне класса.

function configureMe() {
  return 42;
}

Object.defineProperties(TheConstructor.prototype, {
    memoizedConfigParam: {
        get: function() {
            delete TheConstructor.prototype.memoizedConfigParam;
            return TheConstructor.prototype.memoizedConfigParam = configureMe();
        }
        ,configurable:  true
    }
});

// Test code:

console.log("memoizedConfigParam:"+anInstance.memoizedConfigParam);

Производит:

memoizedConfigParam:42

Как видно из примера, запомненные геттеры имеют характеристику функции геттера.удаляет себя, а затем заменяет себя простым значением, которое (предположительно) никогда не изменится.Обратите внимание, что для параметра «настраиваемый» должно быть установлено значение «истина».

Определите метод получения на уровне экземпляра, когда запомненное значение зависит от содержимого экземпляра.Определение перемещается внутри конструктора, и объектом внимания является «this».

function TheConstructorI(side) {

  this.side = side;

  Object.defineProperties(this, {
    memoizedCalculation: {
        get: function() {
            delete this.memoizedCalculation;
            return this.memoizedCalculation = this.expensiveOperation();
        }
        ,configurable:  true
    }
  });
}

TheConstructorI.prototype.expensiveOperation = function() {
  return this.side * this.side * this.side;
}

//Test code:

var instance2 = new TheConstructorI(2);
var instance3 = new TheConstructorI(3);

console.log("memoizedCalculation 2:"+instance2.memoizedCalculation);
console.log("memoizedCalculation 3:"+instance3.memoizedCalculation);

Производит:

memoizedCalculation 2:8
memoizedCalculation 3:27

Если вы хотите гарантировать (а не предполагать), что запомненныйзначение никогда не изменится, атрибут «доступный для записи» необходимо изменить.Это делает код немного более сложным.

function TheConstructorJ(side) {

  this.side = side;

  Object.defineProperties(this, {
    memoizedCalculation: {
        get: function() {
            delete this.memoizedCalculation;
            Object.defineProperty( this, 'memoizedCalculation'
              ,{  value    : this.expensiveOperation()
                 ,writable : false
              });
            return this.memoizedCalculation;
        }
        ,configurable:  true
    }
  });
}

TheConstructorJ.prototype.expensiveOperation = function() {
  return this.side * this.side * this.side;
}

//Test code:

var instanceJ = new TheConstructorJ(2);

console.log("memoizedCalculation:"+instanceJ.memoizedCalculation);
instanceJ.memoizedCalculation = 42;  // results in error

Производит:

memoizedCalculation:8
>Uncaught TypeError: Cannot assign to read only property 'memoizedCalculation' of object '#<TheConstructorJ>'

В оригинальном вопросе OP от 7 марта 2011 г. представлен базовый синтаксис получения и установки, отметив, что онработал над объектом, но не над этим, и спрашивал, как определить методы получения и установки в конструкторе.В дополнение ко всем приведенным выше примерам существует также «дешевый» способ сделать это: создать новый объект в конструкторе, как это сделал OP, но затем назначить объект в качестве члена внутри «this».Итак, оригинальный код будет выглядеть так:

var MyClass = function(value) {
    var test = !!value; // 'test' has to be a boolean
    this.data = {
        get test() { return test },
        set test(value) { test = !!value }
    };
};

var instance = new MyClass(true);

// But now 'data' is part of the access path
instance.data.test = 0;
console.log(instance.data.test);

Производит:

false

Верьте или нет, я фактически столкнулся с ситуациями, когда этот "дешевый выстрел" являетсялучшее решение.В частности, я использовал эту технику, когда у меня были записи из нескольких таблиц, инкапсулированных в одном классе, и я хотел представить унифицированное представление, как если бы они были единой записью, называемой «data».

Fun.

IAM_AL_X

7 голосов
/ 20 октября 2015

Обновление для ES6 - взгляните на раздел 19.3.1 книги Алекса Раушмайера Изучение ES6 http://exploringjs.com/es6/ch_maps-sets.html#sec_weakmaps-private-data, в котором показано, как использовать WeakMaps с геттерами и сеттерами для хранения личных данных. Объединение с разделом 16.2.2.3 http://exploringjs.com/es6/ch_classes.html#leanpub-auto-getters-and-setters приведет к чему-то вроде

# module test_WeakMap_getter.js
var _MyClassProp = new WeakMap();
class MyClass {
    get prop() {
        return _MyClassProp.get( this ); 
    }
    set prop(value) {
        _MyClassProp.set( this, value );
    }
}
var mc = new MyClass();
mc.prop = 5 ;
console.log( 'My value is', mc.prop );

$ node --use_strict test_WeakMap_getter.js 
My value is 5
4 голосов
/ 10 апреля 2016
function Obj(value){
    this.value = !!value;
}

Obj.prototype = {
    get test () {
        return this.value;``
    },
    set test (value) {
        this.value = !!this.value;
    }
};
var obj = new Obj(true);
1 голос
/ 11 июня 2014

@ Алекс. Я вижу в этом больше возможностей и возможностей, программирование - это искусство, @Nat делится с нами своими находками, и за это я ему благодарен. Может быть, кто-то хочет сделать это таким образом.

Я уверен, что версия установщика такая же, но я просто изменяю g на s.

i.g:

function Constructor(input){
     this.input = input;
}

Object.__defineGetter__.call(Constructor.prototype, "value", function(){
    return this.input * 2;
});

Object.__defineSetter__.call(Constructor.prototype, "bar", function(foo){
    return this.input *= foo;
});

var test = new Constructor(5);
console.log(test.value); // 10
test.bar = 5;
console.log(test.input); //25

С учетом вышесказанного, эта функция устарела и не рекомендуется использовать в производственном кодировании.

1 голос
/ 09 августа 2011

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

function Constructor(input){
     this.input = input;
}
Object.__defineGetter__.call(Constructor.prototype, "value", function(){
    return this.input * 2;
});

var test = new Constructor(5);
alert(test.value) // 10

Я проверял это в Chrome, Safari, Mobile Safari, Firefox, и все они работают (конечно, в последних версиях)

...