在现代 Lisp 方言中,特殊变量(见下文)的局部变量绑定是动态的;而在旧版 Lisp 方言中,所有变量的绑定都是动态的。(See 选择 Lisp 方言。) 动态变量绑定有其适用场景,但总体来说比词法绑定更容易出错、效率更低,并且编译器也更难发现使用动态绑定的代码中的错误。
当一个变量是动态绑定时,在 Lisp 程序执行的任意时刻,它的当前绑定就是该符号最近创建的动态局部绑定;如果不存在这样的局部绑定,则使用全局绑定。
动态绑定具有动态作用域和动态存在期,如下例所示:
(defvar x -99) ;xreceives an initial value of −99. (defun getx () x) ;xis used free in this function. (let ((x 1)) ;xis dynamically bound. (getx)) ⇒ 1 ;; After theletform finishes,xreverts to its ;; previous value, which is −99. (getx) ⇒ -99
函数 getx 引用了 x。这是一种自由引用(free reference)—— 也就是说,在 defun 这个绑定结构内部,并没有为 x 建立任何绑定。当我们在一个将 x(以动态方式)绑定的 let 表达式内部调用 getx 时,它会获取到这个局部值(即 1);但当我们在 let 表达式外部调用 getx 时,它会获取到全局值(即 −99)。
再看另一个示例,该示例展示了如何使用 setq 设置动态绑定的变量:
(defvar x -99) ;xreceives an initial value of −99. (defun addx () (setq x (1+ x))) ; Add 1 toxand return its new value. (let ((x 1)) (addx) (addx)) ⇒ 3 ; The twoaddxcalls add toxtwice. ;; After theletform finishes,xreverts to its ;; previous value, which is −99. (addx) ⇒ -98
即使启用了词法绑定,某些变量仍然会使用动态绑定。这些变量被称为 特殊变量(special variables)。所有通过 defvar、defcustom 或 defconst 定义的变量都是特殊变量(see 定义全局变量)。其余所有变量则采用词法绑定。
使用不带初始值的 defvar,可以只在单个文件内,或文件的某一部分代码中将变量设为动态绑定,而在其他地方仍然使用词法绑定。例如:
(let (_) (defvar x) ; Let-bindings ofxwill be dynamic within this let. (let ((x -99)) ; This is a dynamic binding ofx. (defun get-dynamic-x () x))) (let ((x 'lexical)) ; This is a lexical binding ofx. (defun get-lexical-x () x)) (let (_) (defvar x) (let ((x 'dynamic)) (list (get-lexical-x) (get-dynamic-x)))) ⇒ (lexical dynamic)
该函数会检查 symbol 是否为特殊变量:如果是(即该变量通过 defvar、defcustom 或 defconst 完成了变量定义),则返回非 nil 值;否则返回 nil。
需要注意的是,由于这是一个函数,它仅会对永久特殊的变量返回非 nil 值,而对于仅在当前词法作用域内特殊的变量,不会返回非 nil 值。
不支持将特殊变量用作函数的形式参数。
Emacs Lisp 以一种简单的方式实现动态绑定:每个符号都有一个值单元(value cell),用于存储该符号当前的动态值(或标记无值状态)(See 符号的组成)。当为一个符号创建动态局部绑定时,Emacs 会先将值单元中的原有内容(或无值状态)记录到栈中,再把新的局部值存入值单元;当绑定结构执行完毕后,Emacs 会将栈中保存的旧值弹出,并重新写入值单元。