在前面我们了解了hook机制以及如何定义和执行hook。利用hook可以很方便的对一些事件进行处理。但是它最大的问题在于,需要程序的设计者预先留下可供hook的槽点。有时候设计者没有预留对应的hook,但是我们又需要在它执行的前后做文章、或者修改它的行为。这个时候我们可以使用Advice机制
背景知识
它可以在不修改原函数的情况下,为其附加额外行为。你可以把它理解为对函数进行“建议”或“增强”。它是一种可以不需要预先埋设hook点位的hook。
现代Emacs更推荐使用 advice-add 和 advice-remove 进行添加和删除函数的advice。函数的签名如下:
(advice-add SYMBOL HOW FUNCTION &optional PROPS)第一个参数 SYMBOL 是函数的符号。而HOW 则是advice接管函数的方式,它有以下几种形式
:around, 这是最强大、最灵活的类型。我们传入的FUNCTION函数会完全接管原始函数,但原始函数会作为一个额外的参数 (orig-fun) 传递给你。此时在函数内部你可以自由的调整调用原函数的时机、参数等:before, 在原始函数之前执行 advice 函数, 两者接收相同参数,但返回值始终是原始函数的返回值。这类似于在函数入口处执行一个“前置动作”。:after, 在原始函数之后执行 advice 函数。两者接收相同参数,返回值仍是原始函数的返回值。这类似于在函数调用完成之后执行一个“清理动作”。:override, 完全替换原始函数, 它类似于使用fset替换函数符号绑定的函数,对于一些重要功能的函数,替换之后会出现大面积的问题,所以应该谨慎使用
除了这四个基本的以外,还提供了 :after-until、after-while 这种根据原函数返回值决定是否调用advice函数和 :before-until、:before-while 这类根据advice 函数返回值来决定是否执行原函数的控制方式。
advice 机制还能处理原函数的返回值和接收参数,可以通过 :filter-args 和 :filter-args。例如下面是一个控制原函数返回值的示例:
(defun my-double (x)
(* x 2))
(defun my-increase(x)
(1+ x))
(advice-add 'my-double :filter-return 'my-increase)
(my-double 3) ;; ==> 这里返回的值不再是6,而是7从这里,相比读者已经看出来它的弊端了。某些时候它会破坏原函数的行为,就像这个例子,如果函数的调用者不知道某个插件或者配置导致增加了函数的advice,他可能无论如何也无法理解为什么这里返回值跟预想中不一致。而且有时候advice使用不当会导致无限递归,或者其他致命的错误。
所以在存在hook的情况下,优先选择使用hook,应该将 advie 作为没办法的办法。作为最后的手段。
对于一些特别重要的函数,你不确定它是否存在 advice、可以使用 advice-member-p 进行判断。然后利用 addvice-remove 进行清理。
练习
本节我们来对某些重要的函数实现一个统计耗时的功能。例如我们以find-file-noselect 为例
(defun my-timed-find-file-noselect (origin-fun &rest args)
(let ((start (float-time))
(file (car args)))
(unwind-protect
(apply origin-fun args)
(message "find-file-noselect %S: %.3fs" file (- (float-time) start)))))
(advice-add 'find-file-noselect :around 'my-timed-find-file-noselect)这段代码不难理解,我们通过 :around 包裹,在函数调用之前记录一次时间,然后调用函数,在函数结束之后再调用一次。最终返回时间。上面代码调用原始函数的部分使用 unwind-protect,该函数是一种异常处理机制,你可以理解为一般编程语言中异常处理中的final。它保证BODY中的代码不论以何种形式退出,都会执行 CLEANUP 里面的代码
总结
我们今天了解了 addvice 机制。今天介绍的函数如下:
advice-addadvice-removeadvice-member-p
评论 (0)