Lua
- 动态
- 协程
- 单线程
- 函数是一等公民
- 闭包
- 尾调用优化: https://www.lua.org/pil/6.3.html
- 扩展性
- 极简主义, 核心足够小, 可用于嵌入式硬件
lua prog.lua # 运行lua脚本lua -i prog.lua # 运行完脚本内容后进入交互模式lua -o prog.lc prog.lua # 将lua代码预编译为中间格式lua prog.lc # 运行预编译的lua脚本
https://luarocks.org/
Lua的全局变量实际上被被存在一个名为
_G
的变量里, 它相当于JavaScript的 =window=/=global=/=globalThis=, 因此 _G._G == _G
.通常不建议修改
_G
, 因为会引入魔法._G
支持通过元表来改变它的行为, 例如通过设置 __index
和 __newindex
防止自定义全局变量, 但是不建议手动这么做:Lua提供了一个
strict.lua
模块, 相当于启用严格模式, 其中已经包含了阻止使用全局变量的实现, 只需要在文件头用 require strict
导入它就可以了._ENV
是 _G
的局部版本, 类似于Python的 locals()
._ENV
和 _G
一样是可写的, 可以被用来阻止访问 _G
.load和loadfile函数会将当前环境的
_ENV
作为被加载的代码的全局环境.通常不建议修改
_ENV
, 因为会引入魔法.require的行为类似于Node.js, 同一个文件只会加载一次.
在只有一个参数时, require调用的括号可以省略.
-- 在Lua里, 需要用"."表示文件系统的"/", 模块不需要包含扩展名require('path/to/module')-- 在模块里使用return向外导出--! file: example.luareturn 100-- 接收模块的返回值--! file: main.lualocal mod = require('example')print(mod)-- 100
加载和执行文件.
如果执行两遍, 则代码会执行两遍.
如果执行两遍, 则代码会执行两遍.
dofile('file.lua')
加载文件, 返回一个执行文件代码的函数.
函数本身不会抛出错误, 当出现错误时, 只会返回nil和错误信息.
-- file: lib.luafunction echo(x) print(x)end-- file: main.lualocal f = loadfile('lib.lua')print(echo) -- nilf()echo('ok') -- ok
加载字符串, 返回一个执行字符串代码的函数.
函数本身不会抛出错误, 当出现错误时, 只会返回nil和错误信息.
error('invalid') -- 抛出错误assert(condition, 'invalid') -- 断言-- 捕捉错误local result, err = pcall(function () ...end)
Lua不支持多线程.
Lua使用非抢占式多线程, 或协作式多线程来形容其协程特性, 这制造了很多迷惑性, 好像它是一种类似goroutine的多线程机制似的.
实际上Lua的协程几乎等价于Node.js基于libuv的异步I/O, 以及它基于generator或CPS变换的语法糖async/await, 只不过有一套专门的API.
Lua使用非抢占式多线程, 或协作式多线程来形容其协程特性, 这制造了很多迷惑性, 好像它是一种类似goroutine的多线程机制似的.
实际上Lua的协程几乎等价于Node.js基于libuv的异步I/O, 以及它基于generator或CPS变换的语法糖async/await, 只不过有一套专门的API.
Lua的协程有四种状态, 可以用
coroutine.status
检查其状态:- 挂起 suspended, 这是协程的起始状态, 中断在函数入口处.
- 运行 running, 当前协程正在运行中
- 正常 normal, 当前协程既没有挂起, 也不处于正在运行中, 也没有死亡: 在协程内切换到了另一个协程
- 死亡 dead, 协程运行完毕
co = coroutine.create(function () print('hi')end)type(co) -- threadcoroutine.status(co) -- suspendedcoroutine.resume(co) -- truecoroutine.status(co) -- deadcoroutine.resume(co) -- false, cannot resume dead coroutine 不会抛出异常
在协程中调用
coroutine.yield()
可以让出计算资源和返回数据(带参数的情况下), 直到再次用 coroutine.resume
恢复它的运行.coroutine.resume
也可以通过参数向协程的中断处传参.与create类似, 区别在于wrap返回的是一个函数, 调用此函数就会唤醒线程, 从而免去使用API.
co = coroutine.wrap(function () print('hi')end)type(co) -- functionco()co() -- 抛出异常, 协程已经 dead, 不能反复重用.
https://github.com/TypeScriptToLua/TypeScriptToLua
将TypeScript代码转换为Lua.
这个项目提供Lua标准库, Love2D, Defold等常见Lua使用环境的类型定义.
这个项目提供Lua标准库, Love2D, Defold等常见Lua使用环境的类型定义.
- 转换过程中有一些地方会与JavaScript的行为不一致.
- 大量使用declare定义类型.
- 大量使用JSDoc以解决一些语法层面的问题.
在该项目成为真正的主流选择之前, 使用它显然是有风险的.
https://github.com/teal-language/tl
具有类型的Lua方言.
https://github.com/edubart/nelua-lang
受Lua启发的系统编程语言.
https://github.com/rxi/classic
OOP库.
https://github.com/kikito/middleclass
OOP库.
https://github.com/kikito/bump.lua
https://vrld.github.io/HardonCollider/index.html
https://github.com/a327ex/windfield
除非使用
local
声明局部作用域, 否则Lua里的变量默认使用全局作用域.局部作用域的变量可以仅声明而不赋值(未赋值的变量值总是为
nil
).Lua有8种基本类型:
- nil
- boolean
- number: Lua 5.3之前的版本数值都是双精度浮点数, 5.3版本开始分为integer(64位整型)和float(双精度浮点数).
- string: Lua 5.3开始引入支持UTF-8编码的标准库
- function
- thread
- table
- userdata: 保存任意C语言数据类型, 内置的一些API就属于这个类型.
type(100) -- numbertype('100') -- string
Lua带有类似Perl和JavaScript的隐式类型转换行为, 建议采用显式类型转换.
tostring(100) -- '100'tonumber('100', 2) -- 4
Lua的用
""
和 ''
表示字符串, 二者没有语义上的区别.-- 多行字符串str = [[content]]-- 多行字符串的符号之间可以插入等号, 只有等号数量匹配时会被当作字符串str = [===[content]===]
Table是Lua里唯一的复合数据结构, 被用来表示其他语言里的多种数据结构.
它本质上是和JavaScript的数组类似的稀疏数组.
它本质上是和JavaScript的数组类似的稀疏数组.
尽管Table构造器会从1开始按整数递增的方式生成元素, 但Lua的索引值类型是不受限制的,
因此负数, 浮点数, 甚至其他Table也可以作为索引值, 这一点很像JavaScript的Map类型.
因此负数, 浮点数, 甚至其他Table也可以作为索引值, 这一点很像JavaScript的Map类型.
-- Table构造器语法local fruits = {'apple', 'banana'}-- orlocal fruits = { [1] = 'apple', [2] = 'banana'}-- Lua是从1开始计数的fruits[1] -- 'apple'fruits[2] -- 'banana'-- 获取Table的长度#fruits -- 2-- 如果给不连续的索引位置赋值, 则Table的长度会扩大到最后一个元素的索引值-- 如果从Table中不存在的索引取值, 会得到niltable.insert(fruits, 'pear') -- 在尾部追加元素table.remove(fruits, 2) -- 移除元素(其他元素会向前移动一位补上空缺)-- Lua会将用作列表的Table尾部的nil忽略.
local rect = {}rect['width'] = 100rect.height = 50-- or-- 记录式表构造(由于解释器提前知晓数组元素的数量, 创建更快)local rect = { width = 100, height = 50}
元表是JavaScript的原型(prototype)和Python对象的魔法方法的结合体, 通过在元表上设置元方法, 可以重载Lua的运算符.
t = {}print(getmetatable(t)) -- nil, table默认没有元表metatable = {}setmetatable(t, metatable) -- 为t设置元表
常见的元方法字段:
__add
加法__sub
减法__mul
乘法__div
除法__idiv
floor除法__unm
取负值__mod
取模__pow
幂__eq
等于__lt
小于__le
小于等于__tostring
字符串化__pairs
遍历__index
重载getter, 可以是一个方法, 也可以是一个table, 当它是table时就相当于JavaScript的prototype.__newindex
重载setter- =__mode== 引用模式
__gc
析构器, 必须在调用setmetatable时就带有此字段: 先设置metatable, 后追加的__gc
字段不会启用析构行为
Lua的table类型的键和值都是强引用, 因此直到table类型失去引用之前, 它里面的(非primitive)元素都不会被垃圾回收.
有时这会成为问题, 所以需要弱引用版本的table, 就像JavaScript里的WeakMap那样.
通过元表可以将一个table类型的对象设置为弱引用表, 一个弱引用表的元素引用都是弱引用, 这些元素从而允许被垃圾回收.
有时这会成为问题, 所以需要弱引用版本的table, 就像JavaScript里的WeakMap那样.
通过元表可以将一个table类型的对象设置为弱引用表, 一个弱引用表的元素引用都是弱引用, 这些元素从而允许被垃圾回收.
通过设置元表
通过设置元表
通过设置元表
{ __mode = 'k' }
, 可以将对象的键变成弱引用.通过设置元表
{ __mode = 'v' }
, 可以将对象的值变成弱引用.通过设置元表
{ __mode = 'kv' }
, 可以将对象的键和值变成弱引用.result = { value = 0 }function result.add(self, value) self.value = self.value + valueendresult.add(result, 1)-- 语法糖, 省略selffunction result:add(value) self.value = self.value + valueendresult:add(1)
Account = { balance = 0 } -- 默认值metatable = { __index = Account }function Account.new(obj) -- 如果没有设置balance, 则实例在访问时会通过__index取Account.balance的值, 并且在赋值后会遮蔽掉Account.balance. obj = obj or {} setmetatable(obj, metatable) return objendfunction Account:deposit(value) self.balance = self.balance + valueenda = Account.new{ balance = 0 } -- 可以省略括号a:deposit(100)
Account = { balance = 0 } -- 默认值function Account:new(obj) obj = obj or {} -- 把Account自己同时当作元表和原型, 这样就不需要额外准备一张元表. -- 但更重要的是, 其他类继承此类时, self会变成子类, 从而对子类的扩展将不会影响到父类. self.__index = self setmetatable(obj, self) return objendfunction Account:deposit(value) self.balance = self.balance + valueend-- 继承Account的构造函数(new)的返回值SpecialAccount = Account:new()-- 添加子类特有的属性SpecialAccount.limit = 100-- 由于new只是一个普通方法, 因此它在Account的实例中仍然存在,-- 此时再调用new方法, 就会因为self指向的是SpecialAccount的值而实现继承.SpecialAccount:new{balance = 50} -- { limit = 100, balance = 50 }
- 继承的子类如果覆盖父类的同名方法, 则无法在不具名的情况下调用父类的同名方法.
-- 语法糖function echo(x) return xendlocal function echo(x) return xendfunction mod.draw(x) return xend-- orecho = function (x) return xendlocal echo = function (x) return xend mod.draw = function (x) return xend-- Lua函数支持多返回值(根本上是因为Lua支持一次给多个变量赋值的语法)function echo(a, b) return a, bendlocal a, b = echo(1, 2) -- a = 1, b = 2local c = echo(1, 2) -- c = 1local d, e, f = echo(1, 2) -- d = 1, e = 2, f = nil-- 变长参数, `...`必须是最后一个参数function sum(...) local result = 0 for _, v in ipairs{...} do result = result + v end return resultend-- Lua会省略列表尾部的nil,-- 如果想要处理尾部带有nil的传参, 就需要`table.pack(...)`或`select('#', ...)`-- 面向对象风格的函数调用obj:method(args)
--[[comment--]]-- 跟字符串一样, 可以在符号之间加入等号, 只有等号数量匹配时会被当作多行注释--[===[comment--]===]
if condition then -- codeelseif condition then -- codeelse -- codeend
nil
false
允许用
break
跳出循环.for i = 1, 3 do print(i)end-- 1-- 2-- 3-- 带有步进for i = 1, 3, 3 do print(i)end-- 1-- 由于第二轮循环开始前检查发现1+3大于3, 故结束循环-- 遍历Tablefruits = {'apple', 'banana'}for i = 1, #fruits do -- codeend
-- ipairs: 为index优化的遍历, 保证顺序fruits = {'apple', 'banana'}for i, value in ipairs(fruits) do print(i, value)end-- pairs: 更通用的遍历, 不保证顺序dict = { a = 1, b = 2}for key, value in pairs(dict) do print(key, value)end
Lua的迭代器只是一个返回下一个迭代值的函数.
该函数由迭代器生成器, 即"返回一个用于迭代的函数"的函数生成.
当迭代器的返回值为nil时意味着迭代结束.
该函数由迭代器生成器, 即"返回一个用于迭代的函数"的函数生成.
当迭代器的返回值为nil时意味着迭代结束.
迭代器生成器最多可以返回3个返回值:
- 1.迭代函数
- 2.不可变状态, 该值在循环中不变
- 3.控制变量的初始值, 该值会在循环中递增1
后两个返回值在使用for-in循环时会变成迭代器的参数, 即:
function iter(t, i) i = i + 1 local v = t[i] if v then return i, v endendfunction ipairs(t) return iter, t, 0 -- t和0会被for-in循环用作iter函数的参数end
while condition do -- codeend
相当于其他语言的do-until, 在repeat后生成的局部变量对until是可见的.
local linerepeat line = io.read()until line ~= ''print(line)
-- 算术运算符// -- Lua 5.3引入的floor除法, 即"向下取整的除法"^ -- 幂% -- 取模# -- 取字符串的字节数.. -- 连接字符串-- 关系运算符== -- 等于~= -- 不等于-- 逻辑运算符-- Lua的逻辑运算符支持短路.and -- 逻辑与or -- 逻辑或not -- 逻辑非