tutorial - ¿Es posible lograr un alcance dinámico en JavaScript sin recurrir a eval?
javascript eval function with parameters (7)
JavaScript tiene un alcance léxico que significa que las variables no locales a las que se accede desde dentro de una función se resuelven a variables presentes en el alcance de esa función cuando se definió. Esto está en contraste con el alcance dinámico en el que las variables no locales a las que se accede desde dentro de una función se resuelven a las variables presentes en el ámbito de llamada de esa función cuando se llama.
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?
El programa anterior imprime 1 y luego 2 en un lenguaje de ámbito léxico, e imprime 3 y luego 1 en un lenguaje de ámbito dinámico. Como JavaScript tiene un alcance léxico, imprimirá 1 y luego 2 como se muestra a continuación:
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
Aunque JavaScript no admite el alcance dinámico, podemos implementarlo utilizando eval
siguiente manera:
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
Me gustaría saber si existe otra forma posible de lograr el mismo resultado sin recurrir a la eval
.
Editar: Esto es lo que intento implementar sin usar 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());
Sé que no es un buen ejemplo, pero la idea general es utilizar el alcance dinámico para crear cierres. Creo que este patrón tiene mucho potencial.
¿Por qué nadie dijo this
?
Puede pasar variables del ámbito de llamada a la función llamada al vincularlo a un contexto.
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 );
Quizás vale la pena mencionar que en modo estricto, todas las funciones no tienen ningún "entorno" enviado por defecto ( this
es nulo). En modo no estricto, el objeto global es el valor predeterminado de this
valor para una función llamada.
En su caso, en lugar de tratar de usar el alcance dinámico para establecer el constructor, ¿qué pasaría si utilizara el valor de retorno?
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());
La búsqueda de atributos cae a través de la cadena de prototipos, que coincide bastante bien con los ámbitos dinámicos. Simplemente pase su propio entorno de variables de ámbito dinámico para usar en lugar de utilizar el alcance léxico de 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
No lo creo.
Así no es como funciona el lenguaje. Debe usar algo que no sean variables para referirse a esta información de estado. La forma más "natural" de usar propiedades de this
, supongo.
Para agregar una nota sobre este tema:
En JavaScript cada vez que hace uso de:
declaración de declaración de función o expresión de definición de función, entonces las variables locales tendrán Alcance léxico .
Constructor de funciones , las variables locales se referirán al alcance global (código de nivel superior)
this
es el único objeto incorporado en JavaScript que tiene un alcance dinámico y se establece a través del contexto de ejecución (o invocación).
Entonces, para responder a su pregunta, en JS this
ya es una característica de alcance dinámico del lenguaje e incluso no necesita emular a otro.
Puede simular el alcance dinámico utilizando variables globales, si tiene una forma de hacer azúcar sintáctica (por ejemplo, macros con gensyms) y si tiene protección contra el desenredo.
La macro puede aparecer para volver a enlazar la variable dinámica guardando su valor en un léxico oculto y luego asignando un nuevo valor. El código de desenrollar-proteger asegura que no importa cómo termine ese bloque, el valor original del global será restaurado.
Pseudocódigo Lisp:
(let ((#:hidden-local dynamic-var))
(unwind-protect
(progn (setf dynamic-var new-value)
body of code ...)
(set dynamic-var #:hidden-local)))
Por supuesto, esta no es una forma segura de hacer subprocesos de alcance dinámico, pero si no está haciendo threading, ¡funcionará! Lo esconderíamos detrás de una macro como:
(dlet ((dynamic-var new-value))
body of code ...)
Así que si tiene unwind-protect en Javascript y un preprocesador de macros para generar algo de azúcar sintáctico (para que no esté manualmente codificando abiertamente todas sus copias guardadas y restauraciones protegidas), podría ser factible.
Sé que esto no responde exactamente a la pregunta, pero es demasiado código para poner en un comentario.
Como enfoque alternativo, es posible que desee examinar la función de extend
de ExtJS. Así es como funciona:
var Rectangle = Ext.extend(Object, {
constructor: function (w, h) {
var width = w, height = h;
this.area = function () {
return width * height;
};
}
});
Con propiedades públicas en lugar de variables privadas:
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;
}
});