2010-03-15 2 views
23

Есть ли способ получить поведение get/set в массиве? Я полагаю, что-то вроде этого:Getter/setter в массиве javascript?

var arr = ['one', 'two', 'three']; 
var _arr = new Array(); 

for (var i = 0; i < arr.length; i++) { 
    arr[i].__defineGetter__('value', 
     function (index) { 
      //Do something 
      return _arr[index]; 
     }); 
    arr[i].__defineSetter__('value', 
     function (index, val) { 
      //Do something 
      _arr[index] = val; 
     }); 
} 
+19

@Spudley Пожалуйста, не увековечивайте миф о том, что JS не является OO. Это определенно. Утверждение об обратном обнаруживает плохое понимание OO, JS или обоих. – KaptajnKold

ответ

15

Доступ к массиву ничем не отличается от обычного доступа к объекту. array[0] означает array['0'], поэтому вы можете определить свойство с именем '0' и через него перехватить доступ к первому элементу массива.

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

+10

Интересная стратегия.Я все еще жду JavaScript, чтобы познакомить с механизмом getter-get get/setter, и я буду счастливой уткой! – devios1

+0

Стоит упомянуть, что ключ Array определен в спецификации как способный преобразовывать их ключи в неподписанное 32-битное целое число. Хороший ответ. –

+0

Я реализовал предложение @ bobince [здесь] (http://michaelgeekbrewer.blogspot.com/2013/09/capturing-array-element-edits.html). Он работает хорошо для всех, кроме случаев, когда массив доступен за пределами установленных пределов массива. – Constablebrew

2

Вы можете добавить любые методы, которые вы хотели бы в Array, добавив их в Array.prototype. Вот пример, который добавляет геттер и сеттер

Array.prototype.get = function(index) { 
    return this[index]; 
} 

Array.prototype.set = function(index, value) { 
    this[index] = value; 
} 
+0

Звучит очень функционально и совместимо, но если вы хотите настроить Array навсегда, чем больше не хотите массива, но класс, как @jpabluz сказал –

+3

Но я хочу, чтобы массив, казалось бы, работал, как и раньше, поэтому Я могу сделать arr [0] = «значение», а не arr.set() и т. Д. И все равно выполнить некоторый код, когда это будет сделано. Это то, как геттер/сеттеры функционируют для нормальных свойств. –

0

Почему бы не создать новый класс для внутренних объектов?

var a = new Car(); 

function Car() 
{ 
    // here create the setters or getters necessary 
} 

А потом,

arr = new Array[a, new Car()] 

Я думаю, вы получите идею.

+1

'new Array [...]' не является синтаксически правильным. Вместо этого вы должны использовать parens: 'new Array (...)'. Или просто оставьте «новый массив» и просто используйте литерал '...]' literal. –

1

Надеюсь, это поможет.

Object.extend(Array.prototype, { 
    _each: function(iterator) { 
        for (var i = 0; i < this.length; i++) 
        iterator(this[i]); 
       }, 
    clear: function() { 
        this.length = 0; 
        return this; 
       }, 
    first: function() { 
        return this[0]; 
       }, 
    last: function() { 
       return this[this.length - 1]; 
       }, 
    compact: function() { 
     return this.select(function(value) { 
               return value != undefined || value != null; 
               } 
              ); 
     }, 
    flatten: function() { 
      return this.inject([], function(array, value) { 
        return array.concat(value.constructor == Array ? 
         value.flatten() : [value]); 
        } 
      ); 
     }, 
    without: function() { 
     var values = $A(arguments); 
       return this.select(function(value) { 
         return !values.include(value); 
       } 
      ); 
    }, 
    indexOf: function(object) { 
     for (var i = 0; i < this.length; i++) 
     if (this[i] == object) return i; 
     return -1; 
    }, 
    reverse: function(inline) { 
      return (inline !== false ? this : this.toArray())._reverse(); 
     }, 
    shift: function() { 
     var result = this[0]; 
     for (var i = 0; i < this.length - 1; i++) 
     this[i] = this[i + 1]; 
     this.length--; 
     return result; 
    }, 
    inspect: function() { 
      return '[' + this.map(Object.inspect).join(', ') + ']'; 
     } 
    } 
); 
+0

Пожалуйста, объясните свой код. – hims056

+1

На самом деле это отличная идея, и ее следует правильно объяснить. Код создает подкласс Array. Главное - расширение 'Array.prototype'. Более подходящим ответом будет 'var SubArrayClass = {}; SubArrayClass.prototype = Object.extend (Array.prototype, {get: ..., set: ...}); ' – 00500005

+1

Ну, отличная идея, о которой я мечтал. В настоящее время нет способа перехватить 'Object [property]', что действительно необходимо для переопределения. Это просто предоставляет объект типа Array, который действительно не является тем, о чем просит плакат. – 00500005

4

Я посмотрел в статье Резиг в JavaScript Getters And Setters, но его пример прототип не работает для меня. Выбрав некоторые альтернативы, я нашел тот, который, казалось, работал. Вы можете использовать Array.prototype.__defineGetter__ следующим образом:

Array.prototype.__defineGetter__("sum", function sum(){ 
var r = 0, a = this, i = a.length - 1; 
do { 
    r += a[i]; 
    i -= 1; 
} while (i >= 0); 
return r; 
}); 
var asdf = [1, 2, 3, 4]; 
asdf.sum; //returns 10 

Работал для меня в Chrome и Firefox.

0

Можно создать сеттеры для каждого элемента массива, но есть одно ограничение: вы не сможете напрямую устанавливать элементы массива для индексов, которые находятся за пределами инициализированной области (например, myArray[2] = ... // wouldn't work if myArray.length < 2) Использование Array.prototype функции будут работать. (например, push, pop, splice, shift, unshift.) Я приведу пример того, как это сделать here.

0

так я делаю вещи. Вам нужно будет настроить Prototype Creation (я удалил бит из моей версии). Но это даст вам поведение по умолчанию getter/setter, к которому я привык, в других языках на основе классов. Определение Getter и no Setter означает, что запись в элемент будет проигнорирована ...

Надеюсь, это поможет.

function Game() { 
    var that = this; 
    this._levels = [[1,2,3],[2,3,4],[4,5,6]]; 

    var self = { 
    levels: [], 
    get levels() { 
     return that._levels; 
    }, 
    setLevels: function(what) { 
     that._levels = what; 
     // do stuff here with 
     // that._levels 
    } 
    }; 
    Object.freeze(self.levels); 
    return self; 
} 

Это дает мне ожидаемое поведение:

var g = new Game() 
g.levels 
/// --> [[1,2,3],[2,3,4],[4,5,6]] 
g.levels[0] 
/// --> [1,2,3] 

Подняв critizism из dmvaldman: Теперь написание должно быть невозможно. Я переписал код, чтобы 1) не использовать разрозненные элементы (__ defineGetter __) и 2) не принимать никакую запись (то есть: неконтролируемую запись) в элемент уровней. Включен пример setterter.(Я должен был добавить интервал до __ defineGetter из уценки)

Из запроса dmvaldmans:

g.levels[0] = [2,3,4]; 
g.levels; 
/// --> [[1,2,3],[2,3,4],[4,5,6]] 

//using setter 
g.setLevels([g.levels, g.levels, 1,2,3,[9]]); 
g.levels; 
/// --> [[[1,2,3],[2,3,4],[4,5,6]],[[1,2,3],[2,3,4],[4,5,6]], ....] 

//using setLevels 
g.setLevels([2,3,4]); 
g.levels; 
/// --> [2,3,4] 
+0

g.levels [0] = [2,3,4] не будет перехватываться установщиком. Это функциональность, о которой просит оригинальный плакат. – dmvaldman

+0

@ dmvaldman. Хорошо, вопрос задает вопрос о геттерах и сеттерах вообще. И ваша критика будет отвечать за большинство других ответов тоже, так что ... хорошо, неважно. Я переработал код, чтобы не согласиться писать на уровни. Надеюсь, вам понравится это лучше. Добро пожаловать в конструктивную критику. Thx .. – LeTigre

+0

Опять же, нет. Существует причина, по которой этот ответ ниспровергнут: это непонимание вопроса. После g.levels [0] = [2,3,4] плакат ожидает [[2,3,4], [2,3,4], [4,5,6]] – dmvaldman

23

Используя Proxies, вы можете получить желаемое поведение:

var _arr = ['one', 'two', 'three']; 
 

 
var accessCount = 0; 
 
function doSomething() { 
 
    accessCount++; 
 
} 
 

 
var arr = new Proxy(_arr, { 
 
    get: function(target, name) { 
 
    doSomething(); 
 
    return target[name]; 
 
    } 
 
}); 
 

 
function print(value) { 
 
    document.querySelector('pre').textContent += value + '\n'; 
 
} 
 

 
print(accessCount);  // 0 
 
print(arr[0]);   // 'one' 
 
print(arr[1]);   // 'two' 
 
print(accessCount);  // 2 
 
print(arr.length);  // 3 
 
print(accessCount);  // 3 
 
print(arr.constructor); // 'function Array() { [native code] }'
<pre></pre>

Конструктор Proxy создаст объект, который обертывает наш массив и использует функции, называемые ловушками для переопределения b азидное поведение. Функция get будет вызываться для любым способом поиска и doSomething() перед возвратом значения.

Прокси - это функция ES6 и не поддерживаются в IE11 или ниже. См. browser compatibility list.

+0

«Обратите внимание, что Proxy возвращает объект, а не массив». Попробуйте это: 'print (arr.constructor);' и вы увидите, что это все еще массив. Btw 'typeof (new Array())' возвращает ''object''. – xoxox

+0

@xoxox Спасибо. Я удалил эту часть – acbabis

1

Можно определить Getters и Setters для массивов JavaScript. Но вы не можете иметь аксессоров и значений одновременно. Смотрите Mozilla documentation:

Это не представляется возможным одновременно иметь геттер привязывается к собственности и имеют это свойство фактически содержать значение

Так что, если вы определяете аксессоров для массива вы должны иметь второй массив для фактического значения. Это иллюстрирует следующее: example.

// 
// Poor man's prepare for querySelector. 
// 
// Example: 
// var query = prepare ('#modeler table[data-id=?] tr[data-id=?]'); 
// query[0] = entity; 
// query[1] = attribute; 
// var src = document.querySelector(query); 
// 
var prepare; 
{ 
    let r = /^([^?]+)\?(.+)$/; // Regular expression to split the query 

    prepare = function (query, base) 
    { 
    if (!base) base = document; 
    var q = []; // List of query fragments 
    var qi = 0; // Query fragment index 
    var v = []; // List of values 
    var vi = 0; // Value index 
    var a = []; // Array containing setters and getters 
    var m;  // Regular expression match 
    while (query) { 
     m = r.exec (query); 
     if (m && m[2]) { 
     q[qi++] = m[1]; 
     query = m[2]; 
     (function (qi, vi) { 
      Object.defineProperty (a, vi, { 
      get: function() { return v[vi]; }, 
      set: function(val) { v[vi] = val; q[qi] = JSON.stringify(val); }}); 
     })(qi++, vi++); 
     } else { 
     q[qi++] = query; 
     query = null; 
     } 
    } 
    a.toString = function() { return q.join(''); } 
    return a; 
    } 
} 

Код использует три массива:

  1. один для действительных значений,
  2. один для JSON-кодированных значений
  3. и один для аксессоров.

Массив с аксессуарами возвращается вызывающему абоненту. Когда set вызывается путем назначения значения элементу массива, массивы, содержащие простые и закодированные значения, обновляются. Когда вызывается get, он возвращает просто равное значение. И toString возвращает весь запрос, содержащий закодированные значения.

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

Смежные вопросы