Можно ли достичь динамического охвата в JavaScript без использования eval?
JavaScript имеет лексическое охват, что означает, что нелокальные переменные, получаемые из функции, разрешаются переменными, присутствующими в области действия этой функции, когда она была определена. Это противоречит динамическому охвату, при котором нелокальные переменные, получаемые из функции, разрешаются переменными, присутствующими в области вызова этой функции при ее вызове.
x=1
function g () { echo $x ; x=2 ; }
function f () { local x=3 ; g ; }
f # does this print 1, or 3?
echo $x # does this print 1, or 2?
Вышеупомянутая программа печатает 1, а затем 2 на лексическом языке, и печатает 3, а затем 1 на языке с динамическим охватом. Поскольку JavaScript лексически охвачен, он будет печатать 1, а затем 2, как показано ниже:
var print = x => console.log(x);
var x = 1;
function g() {
print(x);
x = 2;
}
function f() {
var x = 3;
g();
}
f(); // prints 1
print(x); // prints 2
Ответы
Ответ 1
Поиск атрибутов проходит через цепочку прототипов, что вполне соответствует динамическим областям. Просто передайте собственную среду переменных с динамическим диапазоном, чтобы использовать их вместо использования лексического охвата Javascript.
// Polyfill for older browsers. Newer ones already have Object.create.
if (!Object.create) {
// You don't need to understand this, but
Object.create = function(proto) {
// this constructor does nothing,
function cons() {}
// and we assign it a prototype,
cons.prototype = proto;
// so that the new object has the given proto without any side-effects.
return new cons();
};
}
// Define a new class
function dyn() {}
// with a method which returns a copy-on-write clone of the object.
dyn.prototype.cow = function() {
// An empty object is created with this object as its prototype. Javascript
// will follow the prototype chain to read an attribute, but set new values
// on the new object.
return Object.create(this);
}
// Given an environment, read x then write to it.
function g(env) {
console.log(env.x);
env.x = 2;
}
// Given an environment, write x then call f with a clone.
function f(env) {
env.x = 3;
g(env.cow());
}
// Create a new environment.
var env = new dyn();
// env -> {__proto__: dyn.prototype}
// Set a value in it.
env.x = 1;
// env -> {x: 1} // Still has dyn.prototype, but it long so I'll leave it out.
f(env.cow());
// f():
// env -> {__proto__: {x: 1}} // Called with env = caller env.cow()
// > env.x = 3
// env -> {x: 3, __proto__: {x: 1}} // New value is set in current object
// g():
// env -> {__proto__: {x: 3, __proto__: {x: 1}}} // caller env.cow()
// env.x -> 3 // attribute lookup follows chain of prototypes
// > env.x = 2
// env -> {x: 2, __proto__: {x: 3, __proto__: {x: 1}}}
console.log(env.x);
// env -> {x: 1} // still unchanged!
// env.x -> 1
Ответ 2
Чтобы добавить заметку к этой теме:
В JavaScript, когда вы используете:
-
выражение объявления функции или выражение определения функции, тогда локальные переменные будут иметь лексическое масштабирование.
-
Функция, тогда локальные переменные будут ссылаться на глобальную область (код верхнего уровня)
-
this
- единственный встроенный объект в JavaScript, который имеет динамический
и определяется через контекст выполнения (или вызова).
Итак, чтобы ответить на ваш вопрос, в JS this
уже есть динамически ограниченная функция языка, и вам даже не нужно эмулировать другую.
Ответ 3
Я так не думаю.
Это не так, как работает язык. Вы должны использовать что-то другое, кроме переменных, чтобы ссылаться на эту информацию состояния. Пожалуй, самый "естественный" способ использования свойств this
.
Ответ 4
В вашем случае вместо того, чтобы пытаться использовать динамическое масштабирование для установки конструктора, что, если вы использовали возвращаемое значение?
function Class(clazz) {
return function () {
clazz.apply(this, arguments).apply(this, arguments);
};
}
var Rectangle = new Class(function () {
var width, height;
this.area = function () {
return width * height;
};
// Constructor
return function (w, h) {
width = w;
height = h;
};
});
var rectangle = new Rectangle(2, 3);
console.log(rectangle.area());
Ответ 5
Почему никто не сказал this
?
Вы можете передавать переменные из области вызова в вызываемую функцию, связывая ее с контекстом.
function called_function () {
console.log(`My env ${this} my args ${arguments}`, this, arguments);
console.log(`JS Dynamic ? ${this.jsDynamic}`);
}
function calling_function () {
const env = Object.create(null);
env.jsDynamic = 'really?';
...
// no environment
called_function( 'hey', 50 );
// passed in environment
called_function.bind( env )( 'hey', 50 );
Возможно, стоит упомянуть, что в строгом режиме все функции не имеют по умолчанию "среды" (this
равно null). В нестандартном режиме глобальный объект является значением по умолчанию для this
для вызываемой функции.
Ответ 6
Вы можете моделировать динамическое масштабирование с использованием глобальных переменных, если у вас есть способ сделать синтаксический сахар (например, макросы с gensyms), и если у вас есть защита от разговора.
Макрос может появиться, чтобы восстановить динамическую переменную, сохранив ее значение в скрытой лексике, а затем присвоив новое значение. Защищенный код гарантирует, что независимо от того, как завершится этот блок, будет восстановлено исходное значение глобального.
Lisp псевдокод:
(let ((#:hidden-local dynamic-var))
(unwind-protect
(progn (setf dynamic-var new-value)
body of code ...)
(set dynamic-var #:hidden-local)))
Конечно, это не потокобезопасный способ выполнения динамической области, но если вы не выполняете потоки, это будет сделано! Мы бы спрятали его за макросом вроде:
(dlet ((dynamic-var new-value))
body of code ...)
Итак, если вы имеете возможность отключить защиту в Javascript и препроцессор макросов для создания некоторого синтаксического сахара (поэтому вы не вручную открываете кодирование всех ваших сохранений и восстанавливаемых восстановления), это может быть выполнимо.
Ответ 7
Я знаю, что это точно не отвечает на вопрос, но слишком много кода для добавления комментария.
В качестве альтернативного подхода вы можете посмотреть функцию ExtJS extend
. Вот как это работает:
var Rectangle = Ext.extend(Object, {
constructor: function (w, h) {
var width = w, height = h;
this.area = function () {
return width * height;
};
}
});
С общедоступными свойствами вместо частных переменных:
var Rectangle = Ext.extend(Object, {
width: 0,
height: 0,
constructor: function (w, h) {
this.width = w;
this.height = h;
},
area: function () {
return this.width * this.height;
}
});