Pergunta

Existe uma maneira de obter um comportamento Get/Set em uma matriz? Eu imagino algo assim:

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;
        });
}
Foi útil?

Solução

O acesso à matriz não é diferente do acesso normal à propriedade. array[0] significa array['0'], para que você possa definir uma propriedade com nome '0' e intercepte o acesso ao primeiro item da matriz por meio disso.

No entanto, isso o torna impraticável para todas as matrizes curtas, mais ou sem fixo. Você não pode definir uma propriedade para "todos os nomes que são inteiros", tudo de uma só vez.

Outras dicas

Usando Proxies, você pode obter o comportamento desejado:

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>

O construtor proxy criará um objeto que envolve nossa matriz e usará funções chamadas armadilhas para substituir comportamentos básicos. o get a função será chamada para algum pesquisa de propriedades e doSomething() antes de retornar o valor.

Os proxies são um recurso ES6 e não são suportados no IE11 ou inferior. Ver Lista de compatibilidade do navegador.

Eu olhei no artigo de John Resig JavaScript Getters e Setters, mas seu exemplo de protótipo não funcionou para mim. Depois de experimentar algumas alternativas, encontrei uma que parecia funcionar. Você pode usar Array.prototype.__defineGetter__ Da seguinte maneira:

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

Trabalhou para mim no Chrome e Firefox.

Espero que ajude.

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(', ') + ']';
        }
    }
);

É possível definir getters e setters para matrizes JavaScript. Mas você não pode ter acessadores e valores ao mesmo tempo. Veja a Mozilla documentação:

Não é possível ter um getter vinculado a uma propriedade e ter essa propriedade realmente possui um valor

Portanto, se você definir acessadores para uma matriz, precisará ter uma segunda matriz para o valor real. A seguir exemplo ilustra isso.

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

O código usa três matrizes:

  1. um para os valores reais,
  2. um para os valores codificados JSON
  3. e um para os acessores.

A matriz com os acessores é devolvida ao chamador. Quando um set é chamado atribuindo um valor ao elemento da matriz, as matrizes que contêm os valores simples e codificadas são atualizadas. Quando get é chamado, ele retorna apenas o valor simples. E toString Retorna toda a consulta que contém os valores codificados.

Mas como outros já declararam: isso faz sentido apenas quando o tamanho da matriz é constante. Você pode modificar os elementos existentes da matriz, mas não pode adicionar elementos adicionais.

Por que não criar uma nova classe para os objetos internos?

var a = new Car();

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

E depois,

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

Eu acho que você entendeu.

É possível criar setters para cada elemento de uma matriz, mas há uma limitação: você não seria capaz de definir diretamente elementos de matriz para índices que estão fora da região inicializada (por exemplo, myArray[2] = ... // wouldn't work if myArray.length < 2) Usando as funções Array.Prototype funcionará. (por exemplo, push, pop, emenda, mudança, desvio.) Dou um exemplo de como realizar isso aqui.

Você pode adicionar quaisquer métodos que você goste a um Array, adicionando -os a Array.prototype. Aqui está um exemplo que adiciona um getter e um setter

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

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

É assim que eu faço as coisas. Você terá que ajustar a criação do protótipo (removi um pouco da minha versão). Mas isso fornecerá o comportamento padrão do Getter / Setter que estou acostumado em outros idiomas baseados em classes. Definir um getter e nenhum setter significa que escrever para o elemento será ignorado ...

Espero que isto ajude.

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

Isso me dá o comportamento esperado de:

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

Tomar a crítica de Dmvaldman: a escrita agora deve ser impossível. Reescrevi o código para 1) não uso elementos depreciados (__ definegetter __) e 2) não aceitam nenhuma escrita (ou seja: escrita não controlada) para o elemento de níveis. Um exemplo de setter está incluído. (Eu tive que adicionar espaçamento a __ Definegetter por causa do Markdown)

De DMValdmans Pedido:

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]

Esta resposta é apenas uma extensão da solução com base no proxy. Veja a solução com proxy, pois só é mencionado, mas também podemos usar o conjunto como estou mostrando aqui.

Aviso: o terceiro argumento no conjunto pode carregar o valor ...

O código é auto -explicativo.

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
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top