JavaScript vars存储在哪里?

时间:2016-11-12 14:16:24

标签: javascript lambda ecmascript-6 lisp

我正在玩普通的lisp,并意识到与Lisp不同,所有局部变量都是参数(通过lambda)lambda或通过参数(通过let)。

换句话说,他们总是遵循IIFE惯用语:

((x, y, z) => {
   /* I has variables */
})(1, 2, 3);

例如

((lambda (x y z)
    ; I has variables
) 1 2 3)

(let ((x 1)(y 2)(z 3))
    ; I has variables
)

在JavaScript中,vars“感觉”像setq,但是如果局部变量没有被声明为影子,则setq变异全局范围,在JavaScript中,vars不会改变全局范围,无论如何。

假设我想在Lisp中执行此操作:

(function() {
    var x = 1;
    var y = 2;
    var z = 3;

    /* woo, I has 3 vars */
})();
/*note that the vars no longer exist */

如果我尝试这样做:

; progn is like lambda but never has arguments and automatically iifes itself.
; eg (progn (setq x 1)) is ((lambda () (setq x 1))
(progn
    (setq x 1)
    (setq y 2)
    (setq z 3)
    ; Woo I has 3 vars
)
; oops, I polluted global scope :(

为了获得这种类似JavaScript的感觉,我最终会做类似

的事情
; wait, we're writing smalltalk now? 
; [ 
;     | x y z | 
;     x := 1.
;     y := 2.
;     z := 3.
;     "I has three vars..."
; ] value.
(let
    ((x)(y)(z))

    (setq x 1)
    (setq x 2)
    (setq x 3)
)

奇怪的是,Lisp似乎没有与JavaScript var / let / const并行;就上面的例子而言(是吗?我对Lisp不是很熟悉......)。

我的问题是; vars实际存储在哪里?它们不作为参数传递,并且它们没有在参数中显式声明......但它们必须存储在某个地方,并且某处不是全局范围...

4 个答案:

答案 0 :(得分:10)

JavaScript变量是名为LexicalEnvironment对象的对象的“绑定”。当执行进入可以拥有自己的绑定的范围时(例如输入函数时),将创建一个新的 LexicalEnvironment ,并使用参数,本地和本地声明的函数进行填充。

这是一个规范构造; JavaScript引擎如何实际实现它取决于引擎,只要它忠实地复制规范的语义。规范中没有任何内容可以让我们直接访问该对象。 (特别是:如果没有关闭变量,它们可以很高兴地在堆栈上实现并通过在退出时重置堆栈指针来清理。)

请注意,这就是闭包在JavaScript中的工作方式,因此阅读闭包可以让您更深入地了解变量的存储位置。简而言之:当您创建函数时,该函数对活动 LexicalEnvironment 对象的创建时间(有点间接)引用。由于对象包含变量的绑定,因此函数可以通过对象访问它们。

一个具体的例子可能会有所帮助;见评论:

// A function that returns a function that closes over its
// local variable
function f() {
  // When this function is called, a *LexicalEnvironment* object
  // is created and populated with an `a` variable
  // (and a few other things)
  var a = Math.random();
  
  // If we create a function, it gets a reference to the object,
  // and so it can access that variable
  return function() {
    return a;
  };
}

// Create a lexical environment containing a variable, get back
// a function with access to it
var f1 = f();

// Do it again
var f2 = f();

// Now we have *two* separate lexical environment objects (well,
// more, but two related to `f`). They both continue to exist
// as long as there's something referring to them (like all other
// objects). Our `f1` and `f2` each refer to one of them, so they
// still exist and `f1` and `f2` can use the `a` on each of them:
console.log(f1());
console.log(f2());

// Now we release the functions, which release the lexical
// environment object they had references to
f1 = f2 = undefined;

答案 1 :(得分:5)

在Common Lisp中,为setq之类的变量赋值变量不会声明变量。没有这种块范围的机制,提到变量将在最内部的块范围内创建它。

您已经提到了letlambda

请注意,lambda包含&aux个变量:

(lambda (&aux (x 1) (y 2) (z 3))

  ; x and y and z are variables here...

  )

这些辅助变量需要在lambda参数列表中声明,但不是调用函数的参数的一部分。

示例:

CL-USER 61 > ((lambda (x &aux (y (* x x)) (z 12))
                (+ y x z))
              5)
42

你的例子

CL-USER 63 > (let (x y z)

               (setq x 1)
               (setq y 2)
               (setq z 3)

               (+ x y z))
6

是一种流行的选择,特别是因为几年前它通常写成:

CL-USER 72 > (prog (x y z)

               (setq x 1)
               (setq y 2)
               (setq z 3)

               (return (+ x y z)))
6

prog提供,局部变量和标记体。在标签体中,我们可以使用本地标记并通过跳转到构造。

摘要:这意味着您可以拥有块本地或函数局部变量,但您必须先在Common Lisp中声明它们。在某些其他语言中,您可以在某个范围内的任何地方执行此操作。

当您输入块时,这样,词汇变量列表就已知并且已修复。语言实现不需要扫描正文以获取新变量,也不需要提供某种方法来使用新变量扩展当前词法环境。

答案 2 :(得分:1)

这不是一个答案,但评论太长了:对不起。

创建似乎很常见的变量绑定有三种可能性:

  1. 变量显式绑定在范围的开头;
  2. 有一个构造可以在范围内的任何地方创建一个绑定,但它不仅仅是赋值;
  3. 变量在第一次分配时隐式绑定在周围范围内。
  4. (3)的一个例子是Python。执行此操作的语言最终需要明确的范围解析运算符(Python中的global和现在nonlocal)来处理他们已经完成的愚蠢,懒惰,双关语并且不值得进一步讨论:您需要绑定不同于任务的结构,或者你永远生活在痛苦之中。

    Common Lisp是(1)的一个例子,C曾经是:它们都是(C是)语言的例子,其中所有的绑定都需要在范围构造的开头创建。

    在(1)中,有些语言具有某种块构造(C {中的{ ... },可用于对范围进行分组,以及具有分组结构和一个或多个单独的作用域结构的那些:CL属于后一族。

    所以在(旧)C中你会说

    {
        int i = 3;
        ...
    }
    

    在CL中你说

    (let ((i 3)) ...)
    

    - 这里的范围构造是let(当然还有其他的)。

    当然,CL,作为一个Lisp,可以非常愉快地发明一个看起来像C的块的构造:

    (defmacro scope (&body vars/body)
      (loop for (var? . body?) on vars/body
            while (and (listp var?)
                       (<= 2 (length var?) 3)
                       (eql (first var?) 'var)
                       (symbolp (second var?)))
            collect (case (length var?)
                      (2 (second var?))
                      (3 (rest var?))) into bindings
            finally (return `(let* ,bindings ,var? ,@body?))))
    

    现在

    (scope
      (var a)
      (var b 2)
      ...)
    

    是该语言的结构。

    因此,(1)中的这两个变体实际上与它们的不同之处更为相似:它只是关于如何在语言中切割所需的基础结构的问题:C混合范围块和分组,CL没有按&#39;吨

    然而,

    (2)是不同的。在这里,您可以在范围构造中混合绑定构造和其他内容。 C现在是这样的,JavaScript也是如此。 CL本身就不像那样(显然,作为一个Lisp,它可以成为这样的一些宏观,虽然那个宏观会比我上面写的更多毛茸茸)

    在这种语言中,你真的需要一个单独的绑定创建结构,因为它不能成为范围构造的一部分。像C这样的语言已经将绑定创建结构视为独立的,因此对于它们而言,只需要放宽规则,即必须在范围构造的开头创建绑定。

    这样的语言有一个重要的问题要回答: 绑定的范围是什么?有类似

    的东西
    {
        ...
        int x = 3;
        ...
    }
    

    真的意味着

    {
        ...
        {
            int x = 3;
            ...
        }
    }
    

    或者是否意味着

    {
        int x;
        ...
        x = 3;
        ...
    }
    

    后一种情况更容易,但意味着有一个尴尬的区域,其中对x的引用是合法的,但其价值可能没有明确定义。 C采用我认为的前解释。

    这对于像C这样的语言来说非常好,其中分组结构与作用域结构相同。但是对于像JavaScript这样的语言并不好,其中分组构造不是作用域构造,更糟糕的是,除了函数之外没有作用域构造(或者不使用)。事实上,像这样的语言无法解决问题,因为没有有用的范围构造。当然,他们通过发展范围构造来解决这个问题。

    请注意,尽管CL是(1)语言,但有Lisp族语言是(2)种语言:特别是Racket。在Racket中(但不是,我认为,在Scheme中)你可以说

    (define (foo)
      (define bar 1)
      ;; is baz bound here?
      (display bar)
      (define baz 3)
      (list bar baz))
    

    答案是肯定的,baz绑定在那里,但是它绑定到一个未定义的对象,如果你有错误(在运行时,而不是编译时)试着用它。所以这是一个运行时错误:

    (define(foo)       (定义栏1)       (显示巴兹)       (定义巴兹3)       (列表吧巴兹))

    虽然这个

    (define (foo)
      (define bar 1)
      (display baz)
      (let ()
        (define baz 3)
        (list bar baz)))
    

    是编译时错误。 (我实际上对Racket创建范围中的哪些结构感到有些困惑。)

答案 3 :(得分:0)

因此,如果我正确理解答案,行为看起来像这样:

var env_get = function(env, varname) {
    var tenv = env;

    while (true) {    
        if (tenv.hasOwnProperty(varname)) {
            return tenv[varname];
        } else {
            if (tenv.superenv == null) {
                throw "no such variable" + tenv;
            } else {
                tenv = tenv.superenv;
            }
        }
    }
};

(function(superenv) {
    var env = {
        'superenv': superenv,
        'one': 1
    };

    console.log(env_get(env, "one"));

    (function(superenv) {
        var env = {
            'superenv': superenv,
            'two': 2
        };
        console.log(env_get(env, "one"));
        console.log(env_get(env, "two"));

        (function(superenv) {
            var env = {
                'superenv': superenv,
                'three': 3
            }                        
            console.log(env_get(env, "one"));
            console.log(env_get(env, "two"));                
            console.log(env_get(env, "three"));
        })(env);
    })(env);
})(null);

或在Lisp中:

(defun env_get (env varname) (let
    ((tenv))
    (setq tenv env)

    (loop (let
        ((val))

        (setq val (gethash varname tenv))
        (if val
            (return val)
            (let
                ((superenv))
                (setq superenv (gethash 'superenv tenv))

                (if superenv
                    (setq tenv superenv)
                    (return 'undefined)
                )
            )
        )
    ))
))

((lambda (superenv) (let 
    ((env))

    (setq env (make-hash-table))
    (setf (gethash 'superenv env) nil)
    (setf (gethash 'one env) 1)

    (print (env_get env 'one))
    (print (env_get env 'two))
    (print (env_get env 'three))     

    ((lambda (superenv) (let        
        ((env))

        (setq env (make-hash-table))
        (setf (gethash 'superenv env) superenv)
        (setf (gethash 'two env) 2)

        (print (env_get env 'one))
        (print (env_get env 'two))
        (print (env_get env 'three))  

        ((lambda (superenv) (let        
            ((env))

            (setq env (make-hash-table))
            (setf (gethash 'superenv env) superenv)
            (setf (gethash 'two env) 2)
            (setf (gethash 'three env) 3)

            (print (env_get env 'one))
            (print (env_get env 'two))
            (print (env_get env 'three))                    
        )) env)                    
    )) env)    
)) nil)