Получатель / установщик на массиве JavaScript? - PullRequest
30 голосов
/ 15 марта 2010

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

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

Ответы [ 10 ]

37 голосов
/ 11 марта 2015

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

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] }'

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

Прокси являются функцией ES6 и не поддерживаются в IE11 или ниже. См. список совместимых браузеров.

17 голосов
/ 15 марта 2010

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

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

5 голосов
/ 25 октября 2010

Я посмотрел в статье Джона Ресига 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.

1 голос
/ 17 декабря 2015

Можно определить геттеры и сеттеры для массивов JavaScript. Но вы не можете иметь аксессоры и значения одновременно. См. Документацию Mozilla :

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

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

//
// 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 возвращает весь запрос, содержащий закодированные значения.

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

1 голос
/ 12 августа 2010

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

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 голосов
/ 01 августа 2018

Этот ответ является лишь расширением решения на основе прокси. Посмотрите решение с прокси, в котором упоминается только get, но мы также можем использовать установить, как я показываю здесь.

Примечание: третий аргумент в наборе может нести значение ...

Код не требует пояснений.

<code>var _arr = ['one', 'two', 'three'];

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

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

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

print(accessCount);      // 0
print(arr[0]);           // 'one'
print(accessCount);      // 1
arr[1] = 10;
print(accessCount);      // 2
print(arr[1]);           // 10

<pre>
0 голосов
/ 10 марта 2015

я так делаю. Вам нужно будет настроить создание прототипа (я немного удалил из своей версии). Но это даст вам стандартное поведение 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]

Принятие критики от dmvaldman: Письмо теперь должно быть невозможно. Я переписал код так: 1) не использовать нецензурные элементы (__ defineGetter __) и 2) не принимать никаких записей (то есть неконтролируемых записей) к элементу уровней. Пример сеттера включен. (Мне пришлось добавить интервал к __ 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 голосов
/ 28 сентября 2013

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

0 голосов
/ 15 марта 2010

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

var a = new Car();

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

А потом,

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

Я думаю, вы поняли.

0 голосов
/ 15 марта 2010

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

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

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