Определение Setter / Getter для непаренной локальной переменной: невозможно? - PullRequest
37 голосов
/ 13 сентября 2011

Есть несколько предыдущих вопросов о StackOverflow, в которых спрашивалось, как можно получить доступ к локальным переменным через цепочку областей действия, например, если вы хотите ссылаться на локальные переменные, используя скобочные обозначения и строку, вам нужно что-то вроде __local__["varName"].До сих пор я не нашел даже самого хакерского метода для достижения этой цели и не придумал метод после нескольких часов использования каждого трюка, который я знаю.

Цель этого - реализовать геттеры / сеттеры напроизвольные непаренные переменные.Object.defineProperties или __defineGet/Setter__ требуют вызова контекста.Для свойств в глобальном или оконном контекстах вы можете достичь цели установки / получения для прямых ссылок на объект.

Object.defineProperty(this, "glob", {get: function(){return "direct access"})
console.log(glob); //"direct access"

Даже в моих тестах с пользовательским расширением я скомпилировал в модифицированный Chromium, которыйзапускается до создания любого окна, где контекст является фактическим глобальным контекстом, и даже пытаясь вызвать this непосредственно в глобальном контексте вылетает моей программы, я могу выполнить это без заминки:

Object.defineProperty(Object.prototype, "define", {
    value: function(name, descriptor){
        Object.defineProperty(this, name, descriptor);
    }
};
define("REALLYglobal", {get: function(){ return "above window context"; }});

И тогда он доступен во всех кадрах, созданных позже, как глобальный, маршрутизируемый через указанный метод получения / установки.Старый __defineGet/Setter__ также работает в этом контексте без указания того, на что его вызывать (хотя в Firefox не работает, как и вышеописанный метод).

Таким образом, в принципе, можно определить защиту get / set для любогопеременная объекта, в том числе окно / глобальный контекст с прямым вызовом объекта (вам не нужно window.propname, просто propname).Это проблема, связанная с невозможностью ссылаться на непаренные переменные области действия, поскольку это единственный тип, который может находиться в доступной области, но не иметь адресуемого контейнера.Конечно, они также наиболее часто используются, так что это не крайний случай.Эта проблема также выходит за рамки текущей реализации Proxies в ES6 / Harmony, поскольку она связана именно с неспособностью адресовать контейнер локального объекта с помощью синтаксиса языка.

Причина, по которой я хочу это сделать, заключается в том, чтоэто единственный барьер, позволяющий перегрузить большинство математических операторов для использования в сложных объектах, таких как массивы и хэши, и для получения комплексного результирующего значения.Мне нужно иметь возможность подключиться к сеттеру в тех случаях, когда значение устанавливается для типа объекта, который я настроил для перегрузки.Нет проблем, если объект может быть глобальным или может содержаться в родительском объекте, что, вероятно, я просто выберу.Это все еще полезно с a.myObject, но цель состоит в том, чтобы сделать его максимально прозрачным для использования.

Мало того, но было бы очень полезно иметь возможность выполнить что-то вроде этого:

var point3d = function(){
    var x, y, z;
    return {
        get: function(){ return [x, y, z]; },
        set: function(vals){ x=vals[0]; y=vals[1]; z=vals[2]; }
    };
};

(Это похоже на деструктуризацию ES6, но имеет более общие приложения для реализации функциональности, связанной с получением / настройкой, а не только с переносом сложных значений).Даже этот базовый код полностью потерпит неудачу:

var x = {myname: "intercept valueOf and :set: to overload math ops!", index: 5};
x++; //x is now NaN if you don't implement a setter somehow

Мне все равно, насколько хакерское решение, на данный момент мне просто интересно, может ли оно быть выполнено, даже если оно требуетломая каждую лучшую практику, которая существует.До сих пор я несколько сотен раз ломал Firefox и Chrome, пытаясь переопределить / перехватить / изменить Object.prototype.valueOf/toString, Function.prototype Function.prototype.constructor, Function.prototype.call/apply, arguments.callee.caller и т. Д. С бесконечной рекурсией.ошибки и тому подобное в попытках жюри задавать контексты задним числом.Единственное, что мне удалось сделать, - это обернуть практически все это в eval и динамически создавать куски кода, что является мостом, который я слишком далеко не могу использовать.Единственный другой удаленно успешный маршрут заключался в использовании with в сочетании с предварительным определением всех локальных переменных в контейнере, но это, очевидно, очень навязчиво из-за проблем с использованием with.

Ответы [ 3 ]

6 голосов
/ 03 февраля 2012

В настоящее время это возможно в средах с прокси. Это будет узел> 0,6, выполняемый как node --harmony_proxies или> 0,7 с node --harmony. Chromium Canary (пока не уверен, что это не так) в около: флаги внизу, экспериментальный javascript. Firefox уже некоторое время без флагов.

Так что, вероятно, это не сработает, когда ES6 станет более официальной, но теперь работает до определенной степени.

  var target = (function(){
    var handler = Proxy.create(Proxy.create({
      get: function(r, trap){
        return function(name,val,c,d){
          if (trap === 'get' || trap === 'set') {
            name = val;
            val = c;
          }
          console.log('"'+trap + '" invoked on property "'+name+'" ' + (val?' with value "'+val+'"':''));
          switch (trap) {
            case 'get': return target[name];
            case 'set': return target[name] = val;
            case 'has': return name in target;
            case 'delete': return delete target;
            case 'keys': return Object.keys(target);
            case 'hasOwn': return Object.hasOwnProperty.call(target, name);
            case 'getPropertyDescriptor':
            case 'getOwnPropertyDescriptor': return Object.getOwnPropertyDescriptor(target, name);
            case 'getPropertyNames':
            case 'getOwnPropertyNames': return Object.getOwnPropertyNames(target);
            case 'defineProperty': return Object.defineProperty(target, name, val);
          }
        }
      }
    }))

    var target = {
      x: 'stuff',
      f: { works: 'sure did' },
      z: ['overwritten?']
    };


    with (handler){
      var z = 'yes/no';
      if (x) {
        //x
      } else {
        x = true;
      }
      console.log(f.works);
      if (f.works) {
        f.works = true;
        delete f;
      }

    }
    return target
  })()
   // "getPropertyDescriptor" invoked on property "z" 
   // "getPropertyDescriptor" invoked on property "z" 
   // "getPropertyDescriptor" invoked on property "x" 
   // "get" invoked on property "x" 
   // "getPropertyDescriptor" invoked on property "console" 
   // "getPropertyDescriptor" invoked on property "f" 
   // "get" invoked on property "f" 
   // sure did
   // "getPropertyDescriptor" invoked on property "f" 
   // "get" invoked on property "f" 
   // "getPropertyDescriptor" invoked on property "f" 
   // "get" invoked on property "f" 
   // "getPropertyDescriptor" invoked on property "f" 

   target: { x: 'Stuff', f: { works: true },  z: ['overwritten?'] }

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

6 голосов
/ 14 сентября 2011

Похоже, что ответ Нет . Я долго искал подобное поведение. Я не смог придумать какое-либо приемлемое решение. Этот ТАК вопрос кажется похожим. Python имеет хорошее ключевое слово locals.

1 голос
/ 08 января 2019

Поскольку вы заявляете, что хотите, чтобы поведение было похоже на window/global, я предположил, что вы хотите это в данном контексте, отличном от window/global. Самый простой способ сделать это - использовать оператор with в сочетании с объектом local и функцией define, которые реализуют Object.defineProperty с local в качестве цели. Вы просто помещаете свой собственный код в блок with.

ВАЖНО: with перегружает собственные локальные переменные (var, let, const). Из-за этого очень важно сохранять четкий код и предотвращать дублирование имен в контексте и родительском / дочернем контекстах.

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

// This closure represents any function, class or other scoped block.
(function (){

}());

Затем мы добавляем контейнер для хранения и функцию define. Это в основном то, с чего вам всегда следует начинать, если вы хотите получить доступ к локальным свойствам из любого места вашего кода (в этой области).

// This is where we store the local property. (except: var, let, const)
const local = {};

// The define function is used to declare and define the local properties.
function define(name, descriptor){ Object.defineProperty(local, name, descriptor); }

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

// This with statement extends the current scope with local.
with(local){

    // This is where your code goes.

}

Теперь внешняя структура оператора with готова, и мы можем начать добавлять код внутри оператора with.

Весь код, помещенный в блок оператора with, имеет доступ к свойствам local, как если бы они были определены, например, с помощью var, включая свойства, определенные в операторе with.

Существует несколько способов работы со свойствами local. Самый простой способ определить свойство - установить его непосредственно в 'local'. Это нужно сделать только один раз, после чего свойство доступно только по его имени.

local.setDirectly = "directly set value";

console.log(setDirectly);    // logs "directly set value"

Другим способом определения свойства, кроме поддержки get/setters, а также опций перечисления и доступа для записи, является использование функции define. Ожидайте того же поведения, что и от Object.defineProperty.

Например, вы можете добавить свойство time, которое возвращает текущее время.

define("time", {
    get: function(){
        var date = new Date();
        return date.getHours() + ":" + ("0" + date.getMinutes()).substr(-2);
    }
})

console.log(time);

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

(function (){
    var counterValue = 0;
    define("count", {get: function(){ return counterValue++ }});
}());

console.log(count);          // logs 0
console.log(count);          // logs 1

Когда вы объедините все это, вы получите нечто похожее на следующий код

// This closure represeents any function, class or other scoped block.
(function(){
    // This is where we store the local property. (except: var, let, const)
    const local = {};

    // The define function is used to declare and define the local properties.
    function define(name, descriptor){ Object.defineProperty(local, name, descriptor); }

    // This with statement extends the current scope with local.
    with(local){
        // This is where your code goes.

        // Defining a variable directly into local.
        local.setDirectly = "directly set value";
        console.log(setDirectly);    // logs "directly set value"
        // Defining local properties with the define function
        // For instance a time variable that return the current time (Hours:Minutes)
        define("time", {
            get: function(){
                var date = new Date();
                return date.getHours() + ":" + ("0" + date.getMinutes()).substr(-2);
            }
        })
        console.log(time);           // logs HH:MM

        // Or a counter property that increments each time it's been accessed.
        (function (){
            var counterValue = 0;
            define("count", {get: function(){ return counterValue++ }});
        }());
        console.log(count);          // logs 0
        console.log(count);          // logs 1
        console.log(count);          // logs 2
        console.log(count);          // logs 3
    }
}());

Как я уже упоминал ранее, важно понимать последствия использования оператора with. Более подробную информацию о with можно найти на MDN - с . Как говорится в вопросе, это поиск того, как вы могли бы, а не как вы должны. Используйте информацию на MDN, чтобы увидеть, соответствует ли она вашей ситуации.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...