Возможно ли добиться динамического определения объема в 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
Хотя JavaScript не поддерживает динамическую область видимости, мы можем реализовать его, используя eval
следующее:
var print = x => console.log(x);
var x = 1;
function g() {
print(x);
x = 2;
}
function f() {
// create a new local copy of `g` bound to the current scope
// explicitly assign it to a variable since functions can be unnamed
// place this code in the beginning of the function - manual hoisting
var g_ = eval("(" + String(g) + ")");
var x = 3;
g_();
}
f(); // prints 3
print(x); // prints 1
Я хотел бы знать, существует ли другой возможный способ достижения того же результата, не прибегая к eval
,
Изменить: это то, что я пытаюсь реализовать без использования eval
:
var print = x => console.log(x);
function Class(clazz) {
return function () {
var constructor;
var Constructor = eval("(" + String(clazz) + ")");
Constructor.apply(this, arguments);
constructor.apply(this, arguments);
};
}
var Rectangle = new Class(function () {
var width, height;
constructor = function (w, h) {
width = w;
height = h;
};
this.area = function () {
return width * height;
};
});
var rectangle = new Rectangle(2, 3);
print(rectangle.area());
Я знаю, что это не очень хороший пример, но общая идея - использовать динамическую область видимости для создания замыканий. Я думаю, что эта модель имеет большой потенциал.
7 ответов
Поиск атрибутов проходит через цепочку прототипов, которая вполне соответствует динамическим областям. Просто передайте свою собственную среду динамически изменяемых переменных для использования вместо использования лексической области видимости 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's long so I'll leave it out.
f(env.cow());
// f():
// env -> {__proto__: {x: 1}} // Called with env = caller's 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's 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
Чтобы добавить заметку на эту тему:
В JavaScript, когда вы используете:
оператор объявления функции или выражение определения функции, тогда локальные переменные будут иметь лексическую область видимости.
Конструктор функции, тогда локальные переменные будут ссылаться на глобальную область видимости (код верхнего уровня)
this
является единственным встроенным объектом в JavaScript, который имеет динамическую область видимости и устанавливается через контекст выполнения (или вызова).
Итак, чтобы ответить на ваш вопрос, в JS this
это уже динамически ограниченная функция языка, и вам даже не нужно подражать другой.
Почему никто не сказал 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
нулевой). В нестрогом режиме глобальный объект используется по умолчанию this
значение для вызываемой функции.
Я так не думаю.
Это не так, как работает язык. Вы должны использовать что-то кроме переменных, чтобы обратиться к этой информации о состоянии. Наиболее "естественным" способом является использование свойств this
, Похоже.
В вашем случае, вместо того, чтобы пытаться использовать динамическую область видимости для установки конструктора, что если вы использовали возвращаемое значение?
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());
Я знаю, что это не совсем отвечает на вопрос, но это слишком много кода, чтобы поместить в комментарий.
В качестве альтернативного подхода, вы можете посмотреть в 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;
}
});
Вы можете смоделировать динамическую область видимости, используя глобальные переменные, если у вас есть способ сделать синтаксический сахар (например, макросы с gensyms), и если у вас есть защита от перемотки.
Макрос может появиться для повторного связывания динамической переменной, сохранив ее значение в скрытой лексической структуре, а затем присвоив новое значение. Код unwind-protect гарантирует, что независимо от того, как завершится этот блок, будет восстановлено исходное значение global.
Псевдокод 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 для защиты от перемотки и макрос-препроцессор для генерации некоторого синтаксического сахара (то есть вы не открываете вручную все свои сохранения и восстановления, защищенные от перемотки), это может быть выполнимо.