You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
423 lines
20 KiB
423 lines
20 KiB
2 years ago
|
# 第五章 VimL 函数进阶
|
||
|
|
||
|
## 5.3 字典函数
|
||
|
|
||
|
函数引用能保存在字典,这不意外,上节就提到过,脚本内定义的 `s:` 前缀变量(包括
|
||
|
函数引用),就自动保存在 `s:` 这个特殊字典中。关键是如何主动利用这个特性,为编
|
||
|
程需求带来便利。在本节中,将保存在字典中的函数引用简称为字典函数。
|
||
|
|
||
|
### 将已有函数保存在字典中
|
||
|
|
||
|
沿用上节的示例,将函数引用保存在字典中,相关代码改写如下:
|
||
|
|
||
|
```vim
|
||
|
" >>File: ~/.vim/vimllearn/funcref.vim
|
||
|
|
||
|
let s:dOperator = {'desc': 'some function on varargins'}
|
||
|
let s:dOperator['+'] = function('s:sum')
|
||
|
let s:dOperator['*'] = function('s:prod')
|
||
|
|
||
|
function! CalculateD(operator, ...) abort
|
||
|
let l:Fnr = s:dOperator[a:operator]
|
||
|
let l:result = call(l:Fnr, a:000)
|
||
|
return l:result
|
||
|
endfunction
|
||
|
```
|
||
|
|
||
|
这里先定义了一个字典变量 `s:dOperator`,并用键 `+` 保存函数 `s:sum()` 的引用
|
||
|
,用键 `*` 保存函数 `s:prod()` 的引用。然后改写 `CalculateD()` 函数就很简洁了
|
||
|
,根据传入的第一参数索引字典,获得相应的函数引用,再调用之。因为直接用键索引字
|
||
|
典,且认为没有遍历全部键的需求,所以还可以在 `s:dOperator` 字典加入非函数引用
|
||
|
的键,比如 `desc` 保存了一条描述,字符串类型。
|
||
|
|
||
|
可以在命令行中输入 `:echo CalculateD('*', 1, 2, 3, 4)` 测验一下。注意到该函数
|
||
|
没有检查传入参数是否有效的键,如 `:echo CalculateD('**', 1, 2, 3, 4)` 会报错。
|
||
|
可以先用 `has_key()` 内置函数检查参数 `a:operator` 是否存在的键,更进一步,可
|
||
|
再用 `type()` 函数与该键相关联的值是否函数引用。如果参数是非法的,则提前返回,
|
||
|
至于返回什么值表示错误,那就与具体需求有关了。也许在某些情况下,不检查参数,直
|
||
|
接让它在出错时终止脚本运行也是可接受的处理方式。
|
||
|
|
||
|
### 按成员的方式引用函数
|
||
|
|
||
|
我们知道,字典元素有两种索引方式,一是用方括号(类似列表索引),一种是用点号(
|
||
|
类似成员索引)。不过后者只是前者的语法糖,要求键名是简单字符串(有效标志符)。
|
||
|
因此可以用一个较有意义单词键名来代替 `+` `*` 符号键名,例如:
|
||
|
|
||
|
```vim
|
||
|
" >>File: ~/.vim/vimllearn/funcref.vim
|
||
|
|
||
|
let s:dOperator.sumFnr = s:dOperator['+']
|
||
|
let s:dOperator.prodFnr = s:dOperator['*']
|
||
|
echo s:dOperator.sumFnr(1, 2, 3, 4)
|
||
|
echo s:dOperator.prodFnr(1, 2, 3, 4)
|
||
|
```
|
||
|
|
||
|
如果之前没有在字典中定义 `+` 键,也可以直接用 `let s:dOperator.sumFnr =
|
||
|
function('s:sum')` 获得函数引用。这里以小写字母开头的键名也可以保存函数引用。
|
||
|
然后调用函数的写法就是 `s:dOperator.sumFnr()`。由于使用的是脚本局部的字典变量
|
||
|
,须用 `:source` 命令重新加载脚本文件执行上例,观察这种调用方法的结果。
|
||
|
|
||
|
### 直接定义字典函数
|
||
|
|
||
|
为了在字典键中保存一个函数引用,之前其实分了三步工作:
|
||
|
|
||
|
1. 用 `:function` 命令定义一个函数;
|
||
|
2. 用 `function()` 函数获取这个函数的引用;
|
||
|
3. 用 `:let` 命令将这个函数引用赋值给字典的某个键。
|
||
|
|
||
|
但这三步曲(实际是两条语句)可以合起来,直接在定义函数时就将其引用保存在字典中
|
||
|
,其语法示例如下:
|
||
|
|
||
|
```vim
|
||
|
" >>File: ~/.vim/vimllearn/funcref.vim
|
||
|
|
||
|
function s:dOperator.sum(...)
|
||
|
let l:sum = 0
|
||
|
for l:arg in a:000
|
||
|
let l:sum += l:arg
|
||
|
endfor
|
||
|
return l:sum
|
||
|
endfunction
|
||
|
|
||
|
function! s:dOperator.prod(...)
|
||
|
let l:prod = 1
|
||
|
for l:arg in a:000
|
||
|
let l:prod = l:prod * l:arg
|
||
|
endfor
|
||
|
return l:prod
|
||
|
endfunction
|
||
|
|
||
|
echo s:dOperator.sum(1, 2, 3, 4)
|
||
|
echo s:dOperator.prod(1, 2, 3, 4)
|
||
|
```
|
||
|
|
||
|
其实就相当于将之前的函数头 `:function s:sum(...)` 改为 `:function
|
||
|
s:dOperator.sum(...)`,函数体功能实现完全一样。要注意的是在执行这一行之前,
|
||
|
`s:dOprator` 字典必须是已定义的。然后调用该函数的用法完全一样。
|
||
|
|
||
|
请注意区分一下,`s:dOperator.sumFnr` 显然是一个函数引用,它引用事先已定义的
|
||
|
`s:sum()` 函数。`s:dOperator.sum` 也是一个函数引用,它引用的又是哪个函数呢?
|
||
|
它引用的是即时定义的函数,它没有名字(没机会也没必要给个名字),也叫做匿名
|
||
|
函数。在 Vim 内部,它将给这样定义的匿名函数一个编号,所以也叫编号函数。
|
||
|
|
||
|
如果在脚本文件末尾写上 `echo s:` 这条语句,根据其输出结果,就能更清楚地分辨这
|
||
|
些函数引用变量的异同。例如,执行结果大概相当于如下定义:
|
||
|
```vim
|
||
|
s:fnrSum = function('<SNR>77_sum')
|
||
|
s:fnrProd = function('<SNR>77_prod')
|
||
|
|
||
|
s:dOperator['+'] = function('<SNR>77_sum')
|
||
|
s:dOperator['*'] = function('<SNR>77_prod')
|
||
|
|
||
|
s:dOperator.sumFnr = function('<SNR>77_sum')
|
||
|
s:dOperator.prodFnr = function('<SNR>77_prod')
|
||
|
|
||
|
s:dOperator.sum = function('172')
|
||
|
s:dOperator.prod = function('173')
|
||
|
```
|
||
|
|
||
|
因此,`s:fnrSum` `s:dOperator['+']` 与 `s:dOperator.sumFnr` 都是引用同一个函数
|
||
|
,那就是 `s:sum()` 局部函数,不过 vim 自动将其修正为 `<SNR>77_sum()` 全局函数
|
||
|
。而 `s:dOperator.sum` 则完全引用另一个函数,是编号为 `172` 的匿名函数。当然,
|
||
|
你的输出中,脚本编号与函数编号极可能是不一样的。
|
||
|
|
||
|
我们知道,退化的 `:function` 命令可以查看打印函数定义。所以可以用 `:function
|
||
|
<SNR>77_sum` 在命令行直接执行,其输出应该与脚本中定义的 `s:sum()` 函数一致。但
|
||
|
是在命令行使用 `:function s:sum` 是错误。那匿名函数怎么查看呢,直接用编号作为
|
||
|
参数是不行的,需用一个大括号括起来,如:
|
||
|
|
||
|
```vim
|
||
|
: function <SNR>77_sum
|
||
|
: function {173}
|
||
|
```
|
||
|
|
||
|
但是,用于获取一个函数引用的 `function()` 却无有效方法仅从匿名函数的编号获得其
|
||
|
引用。如 `function('173')` 或 `function('{173}')` 都不能正常工作。匿名函数一般
|
||
|
必须在创建时赋值给某个函数引用变量,然后只能通过该函数引用调用之。当然了,该函
|
||
|
数引用可以再赋值给其他变量就是。
|
||
|
|
||
|
### 字典函数的特殊属性
|
||
|
|
||
|
如果仔细观察上述 `:function {173}` 命令输出,可以发现它在函数头定义行尾,自己
|
||
|
添加了一个关键字 `dict`,表示将要定义的函数具有 `dict` 属性。这个属性指出该函
|
||
|
数必须通过字典来激活调用,也就是说必须将其引用保存在字典的某个键中。然后在函数
|
||
|
体中,可以使用 `self` 这个关键字,它表示调用该函数时所用到的字典变量。
|
||
|
|
||
|
例如,假设我们要在上述 `s:dOperator` 字典中另外加一个计算圆面积的函数。从数学
|
||
|
上讲,圆面积只是其半径的函数,应该只要传入半径参数。但在程序中实现计算时,还要
|
||
|
涉及一个圆周率常量。这个常量不适合放在函数内定义,当然可以定义为 `s:` 脚本变量
|
||
|
,不过最好还是保存在同一个字典中。
|
||
|
|
||
|
```vim
|
||
|
" >>File: ~/.vim/vimllearn/funcref.vim
|
||
|
|
||
|
let s:dOperator.PI = 3.14
|
||
|
function! s:dOperator.area(r)
|
||
|
return self.PI * a:r * a:r
|
||
|
endfunction
|
||
|
|
||
|
echo s:dOperator.area(2)
|
||
|
```
|
||
|
|
||
|
我们先定义了 `s:dOperator.area` 函数(引用),然后调用 `s:dOperator.area(2)` 来
|
||
|
计算半径为 `2` 的圆面积。在函数定义体内用到了 `self.PI`,这个 `self` 就是调用
|
||
|
该函数时所用到的字典变量,也即 `s:dOperator`。
|
||
|
|
||
|
这里,我们调用时与定义时用到的字典变量是同一个,但这不是必须的。比如,我们可以
|
||
|
创建另一个字典 `s:Math`,它保存了一个 `PI` 键,为示区别,这个 `PI` 保存的圆周
|
||
|
率精度大一些:
|
||
|
|
||
|
```vim
|
||
|
" >>File: ~/.vim/vimllearn/funcref.vim
|
||
|
|
||
|
let s:Math = {}
|
||
|
let s:Math.PI = 3.14159
|
||
|
let s:Math.Area = s:dOperator.area
|
||
|
echo s:Math.Area(2)
|
||
|
```
|
||
|
|
||
|
请观察 `s:dOperator.area(2)` 与 `s:Math.Area(2)` 计算结果的不同,表明后者调用
|
||
|
时 `self.PI` 确实用到了 `s:Math.PI` 的值,而不是 `s:dOperator.PI` 的值。而且,
|
||
|
在 `s:Math` 中的函数名 `Area` 不一定要与最初定义时所用的 `area` 相同。但是函数
|
||
|
体内用到的 `PI` 键名,必须相同。
|
||
|
|
||
|
如果把 `s:dOperator.area` 这个函数(引用)赋值给普通变量(非字典键),会发生什
|
||
|
么情况呢?尝试在脚本末尾继续添加如下代码并加载运行:
|
||
|
|
||
|
```vim
|
||
|
let g:Fnr = s:dOperator.area
|
||
|
echo g:Fnr(2)
|
||
|
```
|
||
|
|
||
|
结果它会报 `E725` 错误,提出不能在没有字典的情况下调用具有 `dict` 属性的函数。
|
||
|
这似乎很好理解,因为在 `area()` 函数体内,用到了 `self.PI`,没有字典的话,这个
|
||
|
`self` 就无所引用了。实际上,即使在函数体内没有到用 `self` ,也不能绕过字典去
|
||
|
调用字典函数。比如原来的 `s:dOperator.sum()` 就没用到 `self`,但如下代码也时非
|
||
|
法的:
|
||
|
|
||
|
```vim
|
||
|
let g:Fnr = s:dOperator.sum
|
||
|
echo g:Fnr(1,2,3,4)
|
||
|
```
|
||
|
|
||
|
在为 `g:Fnr` 赋值时不会出错,在调用 `g:Fnr()` 时才出错。所以 vim 是通过 `dict`
|
||
|
这个函数属性来检测调用合法性的,因为这种函数体内有可能用到 `self`,提前终止潜
|
||
|
在的错误,总是更安全的设计。而且,既然用到 `dict` ,就意味着大概率会用到 `self`,
|
||
|
否则将一个非 `dict` 属性的函数保存在字典中,是很无趣的(虽然合法)。以下语句却
|
||
|
不会出错:
|
||
|
|
||
|
```vim
|
||
|
let g:Fnr = s:dOperator.sumFnr
|
||
|
echo g:Fnr(1,2,3,4)
|
||
|
```
|
||
|
|
||
|
因为 `s:dOperator.sumFnr` 所引用的函数其实是 `s:sum()`,它在定义时未指定 `dict`
|
||
|
属性。所以 `s:dOperator.sunFnr` 只起到一个传递变量值的中介作用,`g:Fnr` 也是
|
||
|
对 `s:sum()` 的函数引用,当然也就可以直接调用了。
|
||
|
|
||
|
### 普通函数的字典属性
|
||
|
|
||
|
上面在定义 `s:dOperator.sum` 与 `s:dOperator.area` (对匿名函数的引用)时,并
|
||
|
未显式写出 `dict` 属性。这只是 `:function` 定义字典函数时的语法糖,vim 会自动
|
||
|
添加 `dict` 属性。
|
||
|
|
||
|
定义普通函数时也可以指定 `dict` 属性,例如我们另外写个计算矩形面积的函数:
|
||
|
|
||
|
```vim
|
||
|
function! s:area(width, height) dict
|
||
|
return a:width * a:height
|
||
|
endfunction
|
||
|
|
||
|
" echo s:area(3, 4) |" 出错
|
||
|
|
||
|
let s:Rect = {}
|
||
|
let s:Rect.area = function('s:area')
|
||
|
echo s:Rect.area(3, 4) |" 正确
|
||
|
```
|
||
|
|
||
|
但是,由于 `s:area()` 函数是 `dict` 属性的,所以直接调用 `s:area()` 会出误。必
|
||
|
须把它(的引用)放在一个字典中,如上为此专门建了个空字典变量 `s:Rect`,将函数
|
||
|
引用保存在其 `area` 键名中,才能调用 `s:Rect.area()`。
|
||
|
|
||
|
因此,当一个普通函数用了 `dict` 属性,却没用到 `self` 特性,好像用处不是很大,
|
||
|
反而限制了其正常使用。为此,将 `s:area()` 函数重新定义如下:
|
||
|
|
||
|
```vim
|
||
|
function! s:area() dict
|
||
|
return self.width * self.height
|
||
|
endfunction
|
||
|
|
||
|
let s:Rect.width = 3
|
||
|
let s:Rect.height = 4
|
||
|
echo s:Rect.area()
|
||
|
```
|
||
|
|
||
|
取消 `s:area()` 的函数参数,而将 `width` 与 `height` 参数保存在 `s:Rect` 字典
|
||
|
中,然后就可以无参调用 `s:Rect.area()` 了。这样,长、宽就相当于矩形(`s:Rect`)
|
||
|
的属性,而求面积的 `area()` 就相当于它的方法。这就初具面向对象的特征了(这将在
|
||
|
后续章节中再详细讨论)。
|
||
|
|
||
|
注意这里的 `s:area()` 函数体内用到了 `self`,则在函数头一定要指定 `dict` 属性
|
||
|
。反之则不强制要求。
|
||
|
|
||
|
具有 `dict` 属性的函数,除了对用字典键引用来调用外,也可以用 `call()` 函数间接
|
||
|
调用。之前已经介绍过 `call()` 函数,其实它还可接收第三个可选参数,按 `:help
|
||
|
call()` 介绍其用法是 `call({func}, {arglist} [, {dict}])`。如果第一个参数(函
|
||
|
数名或函数引用)所指代的函数具有 `dict` 属性,第三个参数就应该提供一个字典传递
|
||
|
给这个函数体实现中的 `self` 变量。
|
||
|
|
||
|
因此,第二个版本(无参数)的 `s:area()` 可以这么调用:
|
||
|
```vim
|
||
|
echo call('s:area', [], s:Rect)
|
||
|
echo call(function('s:area'), [], s:Rect)
|
||
|
```
|
||
|
这两条语句都合法,不过由于使用了 `s:area` 字符串,必须在脚本中才能运行。当
|
||
|
`call()` 在调用 `s:area()` 时,`s:area()` 函数内的 `self` 也就是 `s:Rect` 了。
|
||
|
|
||
|
至于第一个版本带两个参数的 `s:area()` 则可以这么调用:
|
||
|
```vim
|
||
|
echo call('s:area', [5, 6], {})
|
||
|
echo call('s:area', [5, 6]) |" 出错
|
||
|
```
|
||
|
将参数收集在一个列表变量中,作为第二参数传入。由于函数体内未用到 `self` ,在第
|
||
|
三参数随便提供一个字典变量就行,即使是个空字典 `{}`。但若不提供这个字典参数,
|
||
|
则会发生运行时错误。
|
||
|
|
||
|
### 直接定义字典函数与间接定义的比较
|
||
|
|
||
|
综上再小结一下,定义字典函数(引用)有两种方式。一是直接用一条语句搞定,字典键
|
||
|
引用了一个匿名函数;二是先定义函数,再将该有名函数的引用赋值给字典键。不妨分别
|
||
|
称之为直接定义与间接定义。
|
||
|
|
||
|
* 直接定义:`function dict.method()`
|
||
|
* 间接定义:`function Method()` 与 `let dict.method = function('Method')`
|
||
|
|
||
|
显然,直接定义的语法更简洁方便,请尽量使用这种语法。那么间接定义的写法还有没有
|
||
|
什么存在的意义呢?
|
||
|
|
||
|
首先,这可能是历史原因。VimL 也是随 Vim 逐步发展完善起来的,很有可能函数引用的
|
||
|
概念先于 `dict` 属性与 `self` 变量的引入。因而也就先有分步写的字典函数引用,然
|
||
|
后才有一步到位的语法糖写法。
|
||
|
|
||
|
其次,间接定义的函数引用有更灵活的控制权。直接定义的字典函数必定是匿名函数的引
|
||
|
用,且隐含具有 `dict` 的属性,不论是否显式写出该关键词。这也就意味着不能将直接
|
||
|
定义的字典函数引用赋值给普通函数引用变量,那是不能工作的。但在间接定义字典函数
|
||
|
时有更多的选择,在定义函数时可根据需要是否指定 `dict` 属性。没有 `dict` 属性的
|
||
|
函数引用可以赋值给普通变量。因此,从编码实践上建议:
|
||
|
|
||
|
* 直接定义的字典函数,也始终显式加上 `dict` 关键词,不要太依赖语言的隐式作用。
|
||
|
* 普通函数,如果实现体中需要用到 `self` 才加 `dict` 属性关键词。
|
||
|
|
||
|
最后,字典键名引用有名或匿名函数,会影响调试与错误信息。通过示例详细说明,将以
|
||
|
下代码片断添加到本节的演示脚本末尾,并用 `:source` 重新加载。
|
||
|
|
||
|
```vim
|
||
|
" >>File: ~/.vim/vimllearn/funcref.vim
|
||
|
|
||
|
function! s:Rect.debug1() dict abort
|
||
|
echo expand('<sfile>')
|
||
|
Hello Vim, 我在这里就是个错误
|
||
|
endfunction
|
||
|
|
||
|
function! s:debug2() abort
|
||
|
echo expand('<sfile>')
|
||
|
Hello Vim, 我来这里也是个错误
|
||
|
endfunction
|
||
|
let s:Rect.debug2 = function('s:debug2')
|
||
|
|
||
|
function! s:Rect.test() dict " abort
|
||
|
echo expand('<sfile>')
|
||
|
call self.debug1()
|
||
|
call self.debug2()
|
||
|
endfunction
|
||
|
|
||
|
function! s:test() abort
|
||
|
echo expand('<sfile>')
|
||
|
call s:Rect.test()
|
||
|
endfunction
|
||
|
|
||
|
function! Test() abort
|
||
|
echo expand('<sfile>')
|
||
|
call s:test()
|
||
|
endfunction
|
||
|
|
||
|
echo expand('<sfile>')
|
||
|
```
|
||
|
|
||
|
复用原来的字典 `s:Rect`,增加了两个函数引用键,其中 `debug1` 是直接定义的,
|
||
|
`debug2` 是间接引用 `s:debug2()` 的。这两个函数内随意加了一行错误语句。这在加
|
||
|
载脚本时并不会出错误,只有实际调用了相应函数才有机会出错。然后再定义了一个统一
|
||
|
的 `s:Rect.test()` 函数,在其内调用这两个 `debug` 函数。最后还定义了 `s:test()`
|
||
|
与 `Test()` 函数。只有 `Test()` 是全局的,可以在命令行中执行 `:call Test()` 查
|
||
|
看结果。在执行前先人工分析下这将发生的函数调用链:
|
||
|
|
||
|
```
|
||
|
全局函数 Test() --> 脚本函数 s:text() --> 字典函数 s:Rect.test()
|
||
|
[1] --> 字典函数 s:Rect.debug1() | 引用匿名函数
|
||
|
[2] --> 字典函数 s:Rect.debug2() | 引用 s:debug2() 函数
|
||
|
```
|
||
|
|
||
|
我这里执行 `:call Test()` 后输出如下,脚本编号与函数编号肯定是依环境不同的:
|
||
|
```vim
|
||
|
function Test
|
||
|
function Test[2]..<SNR>77_test
|
||
|
function Test[2]..<SNR>77_test[2]..181
|
||
|
function Test[2]..<SNR>77_test[2]..181[2]..180
|
||
|
Error detected while processing function Test[2]..<SNR>77_test[2]..181[2]..180:
|
||
|
line 2:
|
||
|
E492: Not an editor command: Hello Vim, 我在这里就是个错误
|
||
|
function Test[2]..<SNR>77_test[2]..181[3]..<SNR>77_debug2
|
||
|
Error detected while processing function Test[2]..<SNR>77_test[2]..181[3]..<SNR>77_debug2:
|
||
|
line 2:
|
||
|
E492: Not an editor command: Hello Vim, 我来这里也是个错误
|
||
|
```
|
||
|
|
||
|
其中,常规字体是各函数内 `echo expand('<sfile>')` 的正常输出,红字部分是错误语
|
||
|
句触发的输出,即 vim 自动给出的错误提示信息。主要是触发 `E492` 这个错误,它说
|
||
|
`Helle Vim` 不是编辑器的有效命令。并在之前先打印出错时所在的函数名与行号。重点
|
||
|
关注一下函数名的表示方法,例如在 `s:Rect.debug1()` 出错时的位置信息:
|
||
|
```
|
||
|
function Test[2]..<SNR>77_test[2]..181[2]..180:
|
||
|
```
|
||
|
|
||
|
对比之前的分析,第一层调用是全局函数 `Test`,中括号 `[2]` 表示在第二行调用下一
|
||
|
层函数,即 `s:test()`,它被转化成 `<SNR>77_test` 函数名,然后第二行再调用
|
||
|
`s:Rect.test()` ,这是匿名函数,所以只能打印出编号 `181`,然后继续调用
|
||
|
`s:Rect.debug1()` ,它也是匿名函数,也只打印出编号 `180`。到这个函数就出错了,
|
||
|
没能再调用其他函数,出错行号另起一行打印出来。
|
||
|
|
||
|
在 `s:Rect.debug2()` 出错时的位置信息类似:
|
||
|
```
|
||
|
function Test[2]..<SNR>77_test[2]..181[3]..<SNR>77_debug2:
|
||
|
```
|
||
|
只不过在倒数第二层的行号从第二行改为了第三行,最后一个函数名打印出了实际所引用
|
||
|
的函数名 `<SNR>77_debug2` ,也就是脚本中的 `s:debug()`。
|
||
|
|
||
|
这有什么差别呢?试想我们若用 VimL 开发实用功能(主要是插件时),调用链经常也会
|
||
|
这么长或者更长。当 vim 报错时,给出一长串错误提示,我们第一反应是想知道哪里出
|
||
|
错了,最终出错在哪个函数中。这反映在出错信息的最后一个调用函数,但是像
|
||
|
`s:Rect.debug1()` 这样的直接定义的字典函数,vim 只打印个 `180` 编号,可能完全
|
||
|
不知所云。而像 `s:Rect.debug2()` 这个间接定义的字典函数,它会打印出函数名。即
|
||
|
使你也不知脚本编号,那也是有迹可循,比如用 `:scriptnames` 检查。而且在实践中,
|
||
|
你也不可能在很多不同脚本中都定义了相当的函数,那么不用检查脚本编号也基本能定位
|
||
|
错误了。
|
||
|
|
||
|
还有重要一点,在开发 VimL 脚本过程中,如果修改 Bug 后重新加载脚本,那直接定义
|
||
|
的字典函数所引用的匿名函数编号是会变化的。因为它相当于重新定义了另一个匿名函数
|
||
|
并为字典键赋值,而原来那个匿名函数再无引用无可访问就会自动释放(垃圾回收机制)
|
||
|
。但是,脚本编号并不会改变,除非大重构把文件名也改了。这种编号的变化性对查 Bug
|
||
|
也多少会有影响的。
|
||
|
|
||
|
顺便提一下,也许你也注意到了,vim 自动打印的出错位置信息,其实就是 `<sfile>`
|
||
|
的值。如果用在函数中,那就是运行到该处时完整的调用链字符串;在不同时刻从不同入
|
||
|
口调用时还可能给出不同的值。但如果用在函数外,那就只能是在脚本文件中,`<sfile>`
|
||
|
就表示脚本文件名(故不能直接用在命令行中)。这也是 `sfile` 这个单词意义的来源
|
||
|
。不过你也可以将脚本整体理解为一个函数(也是一个执行单元),其“函数名”显然就是
|
||
|
脚本名了。
|
||
|
|
||
|
还有一点得注意,在定义 `s:Rect.test()` 函数时,没有加 `abort` 属性。按之前的建
|
||
|
议,定义函数时始终加 `abort` 是良好的习惯,因为它会在出错时立即终止运行,避免
|
||
|
更多的错乱。不过在这里,如果有 `abort` 属性,它在调用 `self.debug1()` 出错后就
|
||
|
立即终止,`self.debug2()` 也就没机会调用了。由于我们想对比出错信息,要求触发所
|
||
|
有错误,因而特意取消 `abort` 属性。
|