-
Notifications
You must be signed in to change notification settings - Fork 0
nvim dev lua for ts devs
ES edited this page Jul 16, 2026
·
1 revision
对标 TypeScript。只讲不同和坑,基础编程概念不展开 Lua 版本:5.1(LuaJIT,Neovim 用的是这个)
| 维度 | TypeScript / JS | Lua (5.1 / LuaJIT) |
|---|---|---|
| 类型系统 | 结构化强类型(编译时) | 动态弱类型,运行时 type()
|
| 基本类型 | number / string / bigint / boolean / null / undefined / symbol |
number / string / boolean / nil / function / table / userdata / thread |
| 复合类型 |
Array / Object / Map / Set / class
|
只有 table(数组 + map 合体) |
| 索引 | 0-based |
1-based(除 nvim_* 部分 API,必须看文档) |
| 语句结束 |
; 可选 |
无分号 |
| 字符串拼接 |
+ 或模板字符串 |
..(双点) |
| 取长度 | .length |
#str / #tbl
|
| 相等 |
=== / ==
|
== / ~=(意思是 !=) |
| 空值 |
null + undefined 两种 |
只有 nil
|
| 真值 | 除 0/''/null/undefined/NaN/false 外都 truthy |
只有 nil 和 false 是 falsy;0 和 '' 都 truthy |
| 默认值 |
a ?? b / a || b
|
a or b(注意 0 和 '' 也会短路到 a) |
| 三元 | cond ? a : b |
cond and a or b(坑见下方) |
| self-op |
+= -= &&=
|
无,只能写全:x = x + 1
|
| 块作用域 |
{} / let / const
|
do...end / local(无 const) |
| 变量声明 | let/const/var |
local(不写就是全局,坑) |
| 作用域 | 词法 + 块级 | 词法 + 块级 |
| 箭头函数 | (x) => x + 1 |
function(x) return x + 1 end |
| 解构 | const {a, b} = o |
无原生语法,靠多返回值 local a, b = f()
|
| 扩展 |
...arr / {...o}
|
table.unpack(t) / {table.unpack(t)}
|
| 可变参 | (...args) |
function(...) + select('#', ...)
|
| 多返回值 | 返回 tuple / object |
一等公民:return a, b, c
|
| continue | continue |
无 —— 用 goto continue + ::continue::
|
| try/catch | try/catch/finally |
pcall(fn, args) / xpcall
|
| 类 |
class 语法糖 |
用 table + metatable 手写 |
| 继承 | extends |
setmetatable(child, {__index = parent}) |
| 模块 | import/export |
require('x') + return M
|
| async | async/await |
协程 coroutine.* 或 callback |
| 正则 | 完整 PCRE | Lua pattern(弱)或 vim.regex / vim.re / vim.lpeg
|
| 字符串方法 | str.split('.') |
vim.split(str, '.', {plain=true})(没内置 split) |
local x = 1 -- 块作用域(等价 TS 的 let)
local y, z = 2, 3 -- 多重赋值
x = 10 -- 无分号、无类型标注
g_bad = 'OOPS' -- 不写 local 就是全局,污染 _G(尽量别)
-- Lua 没有 const。约定大写常量:
local MAX = 100
-- 块作用域
do
local inner = 1
end
-- inner 在这里未定义唯一地道的坑:忘写 local → 变量进 _G。lua-language-server 会标红
-- number(Neovim 使用 LuaJIT,通常按双精度浮点数理解)
local n = 42
local f = 3.14
local h = 0xFF -- 十六进制
-- 整除 5.3+:10 // 3 == 3
-- 位运算 5.3+:10 & 3,Lua 5.1 用 bit.band(LuaJIT)
-- string
local s1 = 'single'
local s2 = "double"
local s3 = [[
long string
多行,原样保留
]]
local s4 = [==[ contains ]] inside ]==] -- 可带 = 当分隔符
-- boolean
local t, f = true, false
-- nil(= JS undefined;没有 null)
local x = nil
x = nil -- 给 table 字段赋 nil = 删除字段(重要)
-- function(一等公民)
local fn = function(x) return x * 2 end
-- table(唯一复合类型)
local arr = { 1, 2, 3 }
local obj = { name = 'foo', age = 1 }
-- thread(协程)、userdata(C 对象)少用| 类别 | 运算符 | 说明 |
|---|---|---|
| 算术 | + - * / % |
除法总是浮点;Lua 5.3+ 有 // 整除 |
| 字符串 | .. |
拼接 |
| 比较 | == ~= < > <= >= |
不等号是 ~= 不是 !=
|
| 逻辑 | and or not |
短路求值;返回操作数而非 true/false |
| 长度 | # |
#str 字节数,#arr 数组部分长度 |
| 位运算 | & | ~ << >> |
Lua 5.3+;LuaJIT 用 require'bit' 模块 |
和 TS 最大的差异:and/or 返回操作数
local x = nil or 'default' -- 'default'
local y = 0 or 'default' -- 0(!不是 'default')
local z = '' or 'default' -- ''(!)
-- TS 的 ?? 默认值只对 null/undefined 生效
-- Lua 的 or 对所有 falsy 生效,但只有 nil/false 是 falsy,所以对 number/string 安全
-- 陷阱:boolean 默认值用 or 会错
local flag = opts.flag or true -- 如果用户传 false 也会变 true
-- 正确写法:
local flag = opts.flag
if flag == nil then flag = true end三元:
local msg = ok and 'yes' or 'no' -- cond ? 'yes' : 'no'
-- 坑:如果 'yes' 本身是 nil/false,就会走到 'no'
-- TS 的 ? : 是语法,Lua 的 and/or 是值计算-- if / elseif / else / end
if x > 0 then
...
elseif x == 0 then
...
else
...
end
-- while
while cond do
...
end
-- for 数值
for i = 1, 10 do ... end -- 1 到 10 含两端
for i = 1, 10, 2 do ... end -- 步长 2
for i = 10, 1, -1 do ... end -- 倒序
-- for 泛型
for k, v in pairs(tbl) do ... end -- 遍历所有 key(顺序不定)
for i, v in ipairs(arr) do ... end -- 遍历数组部分(1..n,遇 nil 停)
-- repeat(= do while)
repeat
...
until cond
-- 没有 continue
for i = 1, 10 do
if i % 2 == 0 then goto continue end
print(i)
::continue::
end
-- break 和 TS 一样-- 函数是一等公民
local function add(a, b) return a + b end
local add = function(a, b) return a + b end -- 等价,但推荐前者(递归友好)
-- 可变参
local function sum(...)
local args = { ... } -- 收成 table
local n = select('#', ...) -- 精确参数个数(含 nil 占位)
local total = 0
for _, v in ipairs(args) do total = total + v end
return total
end
-- 多返回值
local function pair() return 'a', 1 end
local k, v = pair()
local t = { pair() } -- { 'a', 1 }
local only = (pair()) -- 'a'(加括号截断)
-- 方法调用糖::(自动传 self)
local obj = {
name = 'foo',
greet = function(self) print('hi', self.name) end,
}
obj:greet() -- 等价 obj.greet(obj)
-- 定义方法
function obj:bark() print(self.name) end -- 等价 function obj.bark(self)
-- 闭包
local function make_counter()
local n = 0
return function() n = n + 1; return n end
end-- 构造
local empty = {}
local arr = { 10, 20, 30 } -- 数组
local obj = { name = 'foo', age = 1 } -- map
local mix = { 1, 2, name = 'foo' } -- 两种混合
local dyn = { ['key with space'] = 1, [1 + 1] = 'two' } -- 动态 key
-- 访问
arr[1] -- 10(注意 1-based)
obj.name -- 'foo'
obj['name'] -- 同上
obj.missing -- nil,不报错
-- 修改
obj.age = 2
obj.new = 'x'
obj.age = nil -- 删除字段
-- 数组操作
table.insert(arr, 4) -- push
table.insert(arr, 1, 0) -- unshift(第 2 参是位置)
table.remove(arr) -- pop
table.remove(arr, 1) -- shift
#arr -- 长度(只对"紧凑"数组可靠,含 nil 洞不可靠)
table.concat(arr, ', ') -- join
-- 遍历
for i, v in ipairs(arr) do ... end -- 数组部分,顺序,遇 nil 停
for k, v in pairs(obj) do ... end -- 所有 key,顺序不定
-- 浅克隆 / 合并
local copy = vim.deepcopy(t) -- Neovim 扩展
local merged = vim.tbl_extend('force', t1, t2)
local deep = vim.tbl_deep_extend('force', t1, t2)重点差异(和 TS Array / Object):
-
数组和对象是同一个东西 —— Lua 里
{1,2,3}实际是{[1]=1, [2]=2, [3]=3} -
#不可靠 —— 中间有 nil 就会返回任意"边界"。尽量用ipairs或自己维护 length -
顺序 ——
ipairs数值部分保证顺序;pairs对 map 部分不保证 - 键可以是任意非 nil 值 —— 字符串、数字、table、function 都能当 key
Lua 面向对象的基础。类比 TS 的 Proxy + Symbol.*
local mt = {}
-- __index:字段找不到时的 fallback(继承的核心)
mt.__index = function(t, key) return 'default' end
-- 或
mt.__index = parent_table -- 直接指向父 table
-- __newindex:赋值时触发(用于只读 / 代理)
mt.__newindex = function(t, k, v) error('readonly') end
-- __call:表当函数用
mt.__call = function(self, ...) return self:method(...) end
-- __tostring:print(t) 时用
mt.__tostring = function(t) return 'MyObj:' .. t.name end
-- __eq / __lt / __le:比较重载
-- __add / __sub / __mul:算术重载
local t = setmetatable({}, mt)TS 对照:
| Lua | TS 近似 |
|---|---|
__index |
Proxy.get / 原型链 |
__newindex |
Proxy.set |
__call |
把对象当函数调(TS 需要 interface { (args): ret } 表达) |
__tostring |
toString() |
手写类:
local Animal = {}
Animal.__index = Animal -- 让实例找不到字段时回 Animal
function Animal.new(name)
local self = setmetatable({}, Animal)
self.name = name
return self
end
function Animal:speak() print(self.name, 'speaks') end
-- 继承
local Dog = setmetatable({}, { __index = Animal })
Dog.__index = Dog
function Dog.new(name)
local self = Animal.new(name) -- super()
return setmetatable(self, Dog)
end
function Dog:speak() print(self.name, 'barks') end
local d = Dog.new('Rex')
d:speak() -- Rex barks-- 一个模块 = 一个返回 table 的文件
-- lua/myplug/init.lua
local M = {}
M.foo = function() return 1 end
function M.bar() return 2 end
return M
-- 调用方
local myplug = require('myplug') -- lua/myplug/init.lua
local util = require('myplug.util') -- lua/myplug/util.lua规则:
-
.当目录分隔符 - 自动加
lua/前缀和.lua后缀 -
require会缓存:第二次调用返回同一个 table - 热更:
package.loaded['myplug'] = nil; require('myplug')(常见调试技巧)
-- 抛错
error('oops') -- 带栈信息
error({ code = 1, msg = 'oops' }) -- 也能抛任意对象
-- 捕获(TS 的 try/catch)
local ok, err = pcall(function()
error('boom')
end)
if not ok then print('caught:', err) end
-- 带上下文
local ok, err = pcall(dangerous_fn, arg1, arg2)
-- xpcall:加自定义 handler(如 traceback)
local ok, err = xpcall(fn, debug.traceback)
-- 主动 assert(失败抛错)
assert(x > 0, 'x must be positive')约定:失败 return nil, err_msg(Go 风格),而不是 throw
local function parse(s)
if s == '' then return nil, 'empty input' end
return tonumber(s)
end
local n, err = parse('')
if not n then return nil, err end类比 TS 的 generator + async 混合
local co = coroutine.create(function(x)
coroutine.yield(x + 1)
coroutine.yield(x + 2)
return x + 3
end)
local ok, v = coroutine.resume(co, 10) -- ok=true, v=11
ok, v = coroutine.resume(co) -- v=12
ok, v = coroutine.resume(co) -- v=13(return)
coroutine.status(co) -- 'dead'Neovim 里少用(因为有 vim.schedule 和 callback-style);plenary.async 基于这个封装
| Lua | TS 对照 |
|---|---|
#s |
s.length(字节数,非字符数) |
s:sub(i, j) |
s.slice(i-1, j) |
s:upper() / s:lower()
|
s.toUpperCase() |
s:rep(n) |
s.repeat(n) |
s:find(pat) |
s.search(re) + match
|
s:match(pat) |
s.match(re) |
s:gmatch(pat) |
迭代器版 |
s:gsub(pat, repl) |
s.replace(re, repl) |
s:byte(i) |
s.charCodeAt(i-1) |
s:format(...) / string.format
|
printf 风 / 模板字符串 |
table.concat({'a','b'}, ',') |
['a','b'].join(',') |
| 无内置 split | 用 vim.split(s, sep)
|
Lua pattern 很弱(不是正则)。要正则用 vim.regex(pat) 或 vim.re(LPeg)
-- Lua pattern 常用
'%a' -- 字母 ≈ [a-zA-Z]
'%d' -- 数字 ≈ [0-9]
'%s' -- 空白 ≈ \s
'%w' -- 字母数字
'%p' -- 标点
'.' -- 任意
'*' -- 贪婪 0+
'+' -- 贪婪 1+
'-' -- 非贪婪 0+(注意不是 ?)
'^' -- 字符串开头
'$' -- 字符串结尾
'%x' -- 转义特殊字符:%. %% %( %)| Lua | TS 对照 |
|---|---|
table.insert(t, v) |
arr.push(v) |
table.insert(t, 1, v) |
arr.unshift(v) |
table.remove(t) |
arr.pop() |
table.remove(t, 1) |
arr.shift() |
table.sort(t, cmp) |
arr.sort(cmp) |
table.concat(t, sep) |
arr.join(sep) |
table.unpack(t) / unpack(t)
|
...arr(spread) |
#t |
arr.length(不可靠,见 §7) |
Neovim 扩展:vim.iter(t):filter():map():totable() 更接近 TS 的链式
math.pi math.huge math.maxinteger math.mininteger
math.floor(x) math.ceil(x) math.abs(x) math.sign -- 无,自己写
math.min(a, b, ...) math.max(a, b, ...)
math.sqrt(x) math.pow(x, y) -- 5.1 有;5.3+ 用 x^y
math.random() math.random(n) math.random(m, n)
math.randomseed(s)
math.log(x) math.log(x, base) math.exp(x)
math.sin / cos / tan / asin / acos / atan
math.fmod(x, y) math.modf(x) -- 返回整数部分, 小数部分
math.tointeger(x) math.type(x) -- 'integer' / 'float' / nil-- 文件 IO
local f = io.open('/tmp/x', 'r') -- Node 的 fs.openSync
local content = f:read('*a') -- 全读
f:close()
-- 写入
io.open('/tmp/x', 'w'):write('hi'):close()
-- 一次性读取所有行
for line in io.lines('/tmp/x') do print(line) end
-- OS
os.time() -- epoch 秒
os.date('%Y-%m-%d') -- strftime 风
os.clock() -- CPU 时间
os.getenv('HOME')
os.execute('ls') -- 阻塞,尽量用 vim.system
os.exit(0)
os.tmpname() -- 临时文件名Neovim 里:别用 io.* 和 os.execute 做 IO。用 vim.uv 或 vim.system
-
忘写
local→ 变成全局变量。开 lua-language-server 会警告 -
1-based 索引 →
s:sub(1, 3)是前 3 个字符,不是s.slice(1, 3) -
0和''是 truthy →if x then不能代替if x != null -
#t不可靠 → 遇 nil 就断。数组含空洞时自己维护长度 -
pairs顺序不保证 → 需要顺序用ipairs -
nil赋给 table 字段 = 删除 →t.x = nil不是"置空",是"删 x" -
无 continue → 用
goto continue+::continue:: -
多返回值被吃掉 →
f()在中间位置只取第一个;最后一个位置才展开 -
and/or是值运算 →flag or true在 flag=false 时变 true -
字符串是 byte → 处理中文用
vim.fn.strdisplaywidth/vim.str_utfindex/vim.fn.strcharlen
// TS
async function main() {
try {
const content = await fs.readFile('/tmp/x', 'utf8')
const lines = content.split('\n').filter(l => l.length > 0)
console.log(lines)
} catch (e) {
console.error(e)
}
}-- Lua (用 vim.uv 回调)
vim.uv.fs_open('/tmp/x', 'r', 438, function(err, fd)
if err then return vim.schedule(function() vim.notify(err, vim.log.levels.ERROR) end) end
vim.uv.fs_fstat(fd, function(_, stat)
vim.uv.fs_read(fd, stat.size, 0, function(_, data)
vim.uv.fs_close(fd)
vim.schedule(function()
local lines = vim.split(data, '\n', { plain = true })
lines = vim.tbl_filter(function(l) return #l > 0 end, lines)
print(vim.inspect(lines))
end)
end)
end)
end)
-- 或者同步(Neovim 里够用)
local content = table.concat(vim.fn.readfile('/tmp/x'), '\n')
local lines = vim.split(content, '\n', { plain = true })
lines = vim.tbl_filter(function(l) return #l > 0 end, lines)// TS
class Shape {
constructor(public name: string) {}
area() { return 0 }
describe() { return `${this.name}: ${this.area()}` }
}
class Circle extends Shape {
constructor(public r: number) { super('circle') }
area() { return Math.PI * this.r ** 2 }
}-- Lua
local Shape = {}
Shape.__index = Shape
function Shape.new(name)
return setmetatable({ name = name }, Shape)
end
function Shape:area() return 0 end
function Shape:describe()
return ('%s: %s'):format(self.name, self:area())
end
local Circle = setmetatable({}, { __index = Shape })
Circle.__index = Circle
function Circle.new(r)
local self = Shape.new('circle')
self.r = r
return setmetatable(self, Circle)
end
function Circle:area() return math.pi * self.r ^ 2 end
local c = Circle.new(5)
print(c:describe()) -- circle: 78.539816339745- Lua 5.1 Reference Manual(Neovim 用这个)
-
LuaJIT 扩展(
bit、ffi、jit.*) - Programming in Lua, 4e(入门经典,前 15 章够用)
-
:h lua-guide(Neovim 的 Lua 速成) -
:h luaref(把 Lua 手册也包进来了)