Подтвердить что ты не робот

Getter/setter в конструкторе

Недавно я прочитал о том, что есть возможность определять геттеры/сеттеры в 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 есть undefined:

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

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

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

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

4b9b3361

Ответ 1

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

Однако вы можете установить 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();

Ответ 2

Обновление для 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

Ответ 3

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

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

Я тестировал это в хроме, сафари, мобильном сафари, firefox, и все они работают (последние версии, конечно)

Ответ 4

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

Ответ 5

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

Ниже показано определение класса, в котором геттер и сеттер являются частью класса. Это определение очень похоже на ответ @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

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

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

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

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

Определите getter на уровне экземпляра, когда memoized value зависит от содержимого экземпляра. Определение движется внутри конструктора, а объектом внимания является '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

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

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 года, представил основные getter и setter, отметил, что он работал над объектом, но не на 'this', и спросил, как определить геттеры и сеттеры внутри конструктор. В дополнение ко всем приведенным выше примерам также "дешевый" способ сделать это: создать новый объект внутри конструктор, как и 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

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

Удачи.

IAM_AL_X

Ответ 6

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

Я уверен, что версия setter такая же, но просто изменила ее на 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

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