Nginx/OpenResty详解,Nginx Lua编程,Lua开发基础
Lua开发基础
Lua是一个可扩展的轻量级脚本语言,Lua的设计目是为了嵌入应用程序中,从而为应用程序提供灵活的扩展和定制功能。Lua的代码简洁优美,几乎在所有操作系统和平台上都可以编译和运行。
Lua脚本需要通过Lua解释器来解释执行,除了Lua官方的默认解释器外,目前使用广泛的Lua解释器叫作LuaJIT。
LuaJIT是采用C语言编写的Lua脚本解释器。LuaJIT被设计成全兼容标准Lua 5.1,因此LuaJIT代码的语法和标准Lua的语法没多大区别。LuaJIT和Lua的一个区别是,LuaJIT的运行速度比标准Lua快数十倍,可以说是一个Lua的高效率版本。
Lua模块的定义和使用
与Java类似,实际开发的Lua代码需要进行分模块开发。Lua中的一个模块对应一个Lua脚本文件。使用require指令导入Lua模块,第一次导入模块后,所有Nginx进程全局共享模块的数据和代码,每个Worker进程需要时会得到此模块的一个副本,不需要重复导入,从而提高Lua应用的性能。接下来,演示开发一个简单的Lua模块,用来存放公有的基础对象和基础函数。
//代码清单:src/luaScript/module/common/basic.lua
--定义一个应用程序公有的Lua对象app_info
local app_info = { version = "0.10" }
--增加一个path属性,保存Nginx进程所保存的Lua模块路径,包括conf文件配置的部分路径
app_info.path = package.path;
--局部函数,取得最大值
local function max(num1, num2)
if (num1 > num2) then
result = num1;
else
result = num2;
end
return result;
end
--统一的模块对象
local _Module = {
app_info = app_info;
max = max;
}
return _Module
模块内的所有对象、数据、函数都定义成局部变量或者局部函数。然后,对于需要暴露给外部的对象或者函数,作为成员属性保存到一个统一的Lua局部对象(如_Module)中,通过返回这个统一的局部对象将内部的成员对象或者方法暴露出去,从而实现Lua的模块化封装。
Lua模块的使用
接下来,创建一个Lua脚本
src/luaScript/module/demo/helloworld.lua来调用前面定义的这个基础模块src/luaScript/module/common/basic.lua文件。
helloworld.lua的代码如下:
//代码清单:src/luaScript/module/demo/helloworld.lua
---启动调试
local mobdebug = require("luaScript.initial.mobdebug");
mobdebug.start();
--导入自定义的模块
local basic = require("luaScript.module.common.basic ");
--使用模块的成员属性
ngx.say("Lua path is: " .. basic.app_info.path);
ngx.say("<br>" );
--使用模块的成员方法
ngx.say("max 1 and 11 is: ".. basic.max(1,11) );
在使用require内置函数导入Lua模块时,对于多级目录下的模块,使用require("目录1.目录2.模块名")的形式进行加载,源目录之间的“/”斜杠分隔符改成“.”点号分隔符。这一点和Java的包名的分隔符类似。
--导入自定义的模块
local basic = require("luaScript.module.common.basic");
Lua文件查找时,首先会在Nginx的当前工作目录查找,如果没有
找到,就会在Nginx的Lua包路径lua_package_path和lua_package_cpath声明的位置查找。整个Lua文件的查找过程和Java的.class文件查找的过程很类似。需要注意的是,Lua包路径需要在nginx.conf配置文件中进行配置:
lua_package_path " E:/tool/ZeroBraneStudio-1.80/lualibs/?/?.lua;;";
lua_package_cpath " E:/tool/ZeroBraneStudio-1.80/bin/clibs/?.dll;;";
这里有两个包路径配置项:lua_package_path用于配置Lua文件的包路径;lua_package_cpath用于配置C语言模块文件的包路径。在Linux系统上,C语言模块文件的类型是“.so”;在Windows平台上,C语言模块文件的类型是“.dll”。
Lua包路径如果需要配置多个路径,那么路径之间使用分号“;”分隔。末尾的两个分号“;;”表示加上Nginx默认的Lua包搜索路径,其中包含Nginx的安装目录下的lua目录。
//路径清单:一个默认的Lua文件搜索路径输出案例
./site/lualib/?.ljbc;
./site/lualib/?/init.ljbc;
./lualib/?.ljbc;
./lualib/?/init.ljbc;
./site/lualib/?.lua;
./site/lualib/?/init.lua;
./lualib/?.lua;
./lualib/?/init.lua;
.\?.lua;
E:\tool\openresty-1.13.6.2-win32\lualib\?.lua;
E:\tool\openresty-1.13.6.2-win32\lua\?.lua;
E:\tool\openresty-1.13.6.2-win32\lua\?\init.lua;;
在OpenResty的lualib下已经提供了大量第三方开发库,如CJSON、Redis客户端、MySQL客户端等,并且这些Lua模块已经包含到默认的搜索路径中。OpenResty的lualib下的模块可以直接在Lua文件中通过require方式导入:
导入
操作模块--导入redis操作模块
local redis = require("resty.redis")
--导入cjson操作模块
local cjson = require("cjson")
Lua的数据类型
Lua中大致有8种数据类型,具体如表8-1所示。
表8-1 8种数据类型
Lua是弱类型语言,和JavaScript等脚本语言类似,变量没有固定的数据类型,每个变量可以包含任意类型的值。使用内置的type(…)方法可以获取该变量的数据类型。下面是一段简单的类型输出演示程序。
--输出数据类型
local function showDataType()
local i;
basic.log("字符串的类型", type("hello world"))
basic.log("方法的类型", type(showDataType))
basic.log("true的类型", type(true))
basic.log("整数数字的类型", type(360))
basic.log("浮点数字的类型", type(360.0))
basic.log("nil值的类型", type(nil))
basic.log("未赋值变量i的类型", type(i))
end上面的方法定义在
luaScript.module.demo.dataType模块中。然后定义一个专门的调试模块runDemo,来调用上面定义的showDataType方法。runDemo.lua的代码清单如下:
---启动调试
local mobdebug = require("luaScript.initial.mobdebug");
mobdebug.start();
--导入自定义的基础模块
local basic = require("luaScript.module.common.basic");
--导入自定义的dataType模块
local dataType = require("luaScript.module.demo.dataType");
ngx.say("下面是数据类型演示的结果输出:<br>" );
dataType.showDataType();
在nginx-debug.conf配置好runDemo.lua之后,就可以通过浏览器执行了,输出的结果如图8-3所示。
图8-3 Lua数据类型输出
关于Lua的数据类型,有以下几点需要注意:
(1)nil是一种类型,在Lua中表示“无效值”。nil也是一个值,表示变量是否被赋值,如果变量没有被赋值,那么值为nil,类型也为nil。
与Nginx略微有一点不同,OpenResty还提供了一种特殊的空值,即ngx.null,用来表示空值,但是不同于nil。
(2)boolean(布尔类型)的可选值为true和false。在Lua中,只有nil与false为“假”,其他所有值均为“真”,比如数字0和空字符串都是“真”。这一点和Java语言的boolean类型还是有一点区别的。
(3)number类型用于表示实数,与Java中的double类型类似。但是又有区别,Lua的整数类型也是number。一般来说,Lua中的number类型是用双精度浮点数来实现的。可以使用数学函数math.lua来操作number类型的变量。在math.lua模块中定义了大量数字操作方法,比如定义了floor(向下取整)和ceil(向上取整)等操作。下面是一个演示方法。
--演示取整操作
local function intPart(number)
basic.log("演示的整数",number)
basic.log("向下取整是", math.floor(number));
basic.log("向上取整是", math.ceil(number))
end
上面的方法定义在
luaScript.module.demo.dataType模块中,然后在runDemo.lua模块中调用上面定义的intPart方法。调用的代码清单如下:
---启动调试
local mobdebug = require("luaScript.initial.mobdebug");
mobdebug.start();
--导入自定义的dataType模块
local dataType = require("luaScript.module.demo.dataType");
ngx.say("<hr>下面是数字取整的输出:<br>" );dataType.intPart(0.01);
dataType.intPart(3.14);
运行之后,输出的结果如图8-4所示。
图8-4 floor(向下取整)和ceil(向上取整)操作的结果
(4)table类型实现了一种抽象的“关联数组”,相当于Java中的Map。“关联数组”是一种具有特殊索引方式的数组,索引(也就是Map中的key)通常是number类型或者string类型,也可以是除nil以外的任意类型的值。默认情况下,table中的key是number类型的,并且key的值为递增的数字。
(5)和JavaScript脚本语言类似,在Lua中的函数也是一种数据类型,类型为function。函数可以存储在变量中,可以作为参数传递给其他函数,还可以作为其他函数的返回值。定义一个有名字的函数本质上是定义一个函数对象,然后赋值给变量名称(函数名)。例如,前面定义在basic.lua中的max函数可以变成如下形式:
--局部函数,取得最大值
local max= function (num1, num2)
local result = nil;
if (num1 > num2) then
result = num1;
else
result = num2;
end
return result;
end
在上面的代码中,首先定义了一个变量max,然后使用function定义了一个匿名函数,并且将函数赋给max变量。这种定义方法等价于直接定义一个带名字的函数的方式:
--局部函数,取得最大值
local max= function (num1, num2)
//省略函数体
end
Lua的字符串
Lua中有3种方式表示字符串:
(1)使用一对匹配的半角英文单引号,例如'hello';
(2)使用一对匹配的半角英文双引号,例如"hello";
(3)使用一种双方括号“[[]]”括起来的方式定义,例如[["add\name",'hello']]。需要说明的是,双方括号内的任何转义字符不被处理,比如"\n"就不会被转义。
Lua的字符串的值是不可改变的,和Java一样,string类型是不可变类型。如果需要改变,就需要根据修改要求来创建一个新的字符串并返回。另外,Lua不支持通过下标来访问字符串的某个字符。
Lua定义了一个负责字符串操作的string模块,包含很多强大的字符操作函数。主要的字符串操作介绍如下:
(1)“..”:字符串拼接符号。
在Lua中,如果需要进行字符串的连接,使用两点符号“..”,例如:
--演示字符串操作
local function stringOperator(s)
local here="这里是:" .. "高性能研习社群" .. "疯狂创客圈";
print(here);
basic.log("字符串拼接演示",here);
end
(2)string.len(s):获取字符串的长度。
此函数接收一个字符串作为参数,返回它的长度。此函数的功能和#运算符类似,后者也是取字符串的长度。在实际开发过程中,建议尽量使用#运算符来获取Lua字符串的长度。
--演示字符串的长度获取
local function stringOperator(s)
local here = "这里是:" .. "高性能研习社群" .. "疯狂创客圈";
basic.log("获取字符串的长度", string.len(here));
basic.log("获取字符串的长度方式二", #here);end
(3)string.format(formatString,...):格式化字符串。
第一个参数formatString表示需要进行格式化的字符串规则,通常由常规文本和格式指令组成,比如:
--简单的圆周率格式化规则
string.format(" 保留两位小数的圆周率 %.4f", 3.1415926);
--格式化日期
string.format("%s %02d-%02d-%02d", "今天is:", 2020, 1, 1));
在formatString参数中,除了常规文本之外,还有格式指令。格式指令由%加上一个类型字母组成,比如%s(字符串格式化)、%d(整数格式化)、%f(浮点数格式化)等,在%和类型符号的中间可以选择性地加上一些格式控制数据,比如%02d,表示进行两位的整数格式输出。总体来说,formatString参数中的格式化指令规则与标准C语言中printf函数的格式化规则基本相同。
format函数后面的参数是一个可变长参数,表示一系列需要进行格式化的值。一般来说,前面的formatString参数中有多少格式化指令,后面就可以放置对应数量的参数值,并且后面的参数类型需要与formatString参数中对应位置的格式化指令中的类型符号相匹配。
(4)string.find(s,pattern[,init[,plain]]):字符串匹配。在s字符串中查找第一个匹配正则表达式pattern的子字符串,返回第一次在s中出现的满足条件的子串的开始位置和结束位置,若匹配失败,则返回nil。第三个参数init默认为1,表示从起始位置1开始找起。第四个参数的值默认为false,表示第二个参数pattern为正则表达式,默认进行表达式匹配,当第四个参数为true时,只会把pattern看成一个普通字符串。
--演示字符串查找操作
local function stringOperator(s)
local here="这里是:" .. "高性能研习社群" .. "疯狂创客圈";
local find = string.find;
basic.log("字符串查找",find(here,"疯狂创客圈"));
end
(5)string.upper(s):字符串转成大写接收一个字符串s,返回一个把所有小写字母变成大写字母的字符串。
--演示字符串操作
local function stringOperator(s)
local src = "Hello world!";
basic.log("字符串转成大写",string.upper(src));
basic.log("字符串转成小写",string.lower(src));
end
与string.upper(s)方法类似,string.lower(s)方法的作用是接收一个字符串s,返回一个全部字母变成小写的字符串。
Lua的数组容器
Lua数组的类型定义的关键词为table,通过名字进行翻译的话,可以直接翻译为二维表。和Java的数组对比起来,Lua数组有以下几个要点:
要点一:Lua数组内部实际采用哈希表保存键-值对,这一点和Java的容器HashMap类似。不同的是,Lua在初始化一个普通数组时,如果不显式地指定元素的key,就会默认用数字索引作为key。
要点二:定义一个数组使用花括号,中间加上初始化的元素序列,元素之间以逗号隔开即可。
--定义一个数组
local array1 = { "这里是:" , "高性能研习社群" ,"疯狂创客圈" }
--定义一个元素类型为键-值对的数组,相当于Java的HashMap
local array2 = { k1="这里是:" , k2= "高性能研习社群" , k3="疯狂创客圈" }
要点三:普通Lua数组的数字索引对应于Java的元素下标,是从1开始计数的。
要点四:普通Lua数组的长度的计算方式和C语言有些类似。从第一个元素开始,计算到最后一个非nil的元素为止,中间的元素数量就是长度。
要点五:取得数组元素值使用[]符号,形式为array[key],其中array代表数组变量名称,key代表元素的索引,这一点和Java语言类似。对于普通的数组,key为元素的索引值;对于键-值对(Key-ValuePair)类型的数组容器,key就是键-值对中的key。
迭代上面定义的普通数组 --迭代上面定义的普通数组
for i = 1, 3 do
ngx.say(i .. "=" .. array1[i] .. ",");
end
ngx.say("<br>");
--迭代上面定义的键-值对的容器数组
for k, v in pairs(array2) do
ngx.say(k .. "=" .. array2[k] .. ",");
end
ngx.say("<br><br>");
Lua定义了一个负责数组和容器操作的table模块,主要的字符串操作大致如下:
(1)table.getn(t):获取长度。
对于普通的数组,键从1到n放着一些非空值时,它的长度就精确为n。如果数组有一个元素为“空值”(nil值被夹在中间,相当于有一个空洞),那么数组长度为“空值”前面部分的数组长度,“空值”后面的数组元素不会计算在内。
获取数组长度,Lua中还有一个更为简单的操作符,即一元操作符#。并且,在Lua 5.1之后的版本去掉了table.getn(t)方法,直接使用#获取长度。
--定义一个数组
local array1 = { "这里是:", "高性能研习社群", "疯狂创客圈" }
--定义一个K-V元素类型的数组
local array2 = { k1 = "这里是:", k2 = "高性能研习社群", k3 = "疯狂创客圈" }
--取得数组长度
basic.log("使用table.getn (t)获取长度", table.getn (array1));
basic.log("使用 一元操作符#获取长度", #array1 );
(2)table.concat(array,[,sep,[,i,[,j]]]):连接数组元素。
按照array[i]..sep..array[i+1]..sep..array[j]的方式将普通数组中所有的元素连接成一个字符串并返回。分隔字符串sep默认为空白字符串。起始位置i默认为1,结束位置j默认是array的长度。如果i大于j,就返回一个空字符串。
local testTab = { 1, 2, 3, 4, 5, 6, 7 }
basic.log("连接元素",table.concat(testTab)) --输出: 1234567
basic.log("带分隔符连接元素",table.concat(testTab, "*", 1, 3)) --输出: 1*2*3(3)table.insert(array,[pos,],value):插入元素。
在array的位置pos处插入元素value,后面的元素向后顺移。pos的默认值为#list+1,因此调用table.insert(array,x)会将x插在普通数组array的末尾。
local testTab = { 1, 2, 3, 4 }
--插入一个元素到末尾
table.insert(testTab, 5)
basic.printTable(testTab) --输出: 1 2 3 4 5
--插入一个元素到位置索引2
table.insert(testTab, 2, 10)
basic.printTable(testTab) --输出: 1 10 2 3 4 5
上面用了一个新的成员basic.printTable(testTab),是为了输出数组元素。在basic模块专门定义了一个新输出方法_printTable(tab),然后暴露为printTable,代码如下:
--在屏幕上输出table元素
function _printTable(tab)
local output = ""
for i, v in ipairs(tab) do
ngx.say(v .. " ");
end
ngx.say("<br>");
end
(4)table.remove(array[,pos]):删除元素。删除array中pos位置上的元素,并返回这个被删除的值。当pos是1到#list之间的整数时,将后面的所有元素前移一位,并删除最后一个元素。
testTab = { 1, 2, 3, 4, 5, 6, 7 }
--删除最后一个元素
table.remove(testTab)
basic.printTable(testTab) --输出: 1 2 3 4 5 6
--删除第二个元素
table.remove(testTab, 2) --输出: 1 3 4 5 6
basic.printTable(testTab)
Lua的控制结构
首先介绍分支控制结构if-else。if-else是Java工程师熟知的一种控制结构,分成3类进行介绍:单分支结构、两分支结构和多分支结构。
1.单分支结构:if
以关键词if开头,以关键词end结束。这一点和Java不同,Java中
使用右花括号作为分支结构体的结束符号。
--单分支结构
Local x = '疯狂创客圈'
if x == '疯狂创客圈' then
basic.log("单分支演示:", "这个是一个高性能研习社群")
end
输出的结果是:
单分支演示:这个是一个高性能研习社群
2.两分支结构:if-else
与Java类似,两分支结构的控制语句在单分支的基础上加入了else子句。
--两分支
local x = '疯狂创客圈'
if x == '这个是一个高性能研习社群' then
basic.log("两分支演示:", "这儿是疯狂创客圈")
else
两分支演示
这儿还是疯狂创客圈 basic.log("两分支演示:", "这儿还是疯狂创客圈")
end
输出的结果是:
两分支演示:这儿还是疯狂创客圈
3.多分支结构:if-elseif-else
多分支结构就是添加elseif条件子句,可以添加多个elseif条件子句。与Java语言不同的是,else与if不是分开的,是连在一起的。
--多分支
local x = '疯狂创客圈' if x == '这个是一个高性能研习社群' then
basic.log("多分支演示:", "这儿是疯狂创客圈")
elseif x == '疯狂创客圈' then
basic.log("多分支演示:", "这个是一个高性能研习社群")
else
basic.log("多分支演示:", "这儿不是疯狂创客圈")
end
输出的结果是:
多分支演示:这个是一个高性能研习社群然后介绍for循环控制结构,分成两类进行介绍:基础for循环和增强版的foreach循环。
(1)基础for循环,语法如下:
for var = begin, finish, step do
--body
end
基础for循环的语法中,var表示迭代变量,begin、finish、step表示控制的变量。迭代变量var从begin开始,一直变化到finish循环结束,每次变化都以step作为步长递增。begin、finish、step可以是表达式,但是3个表达式只会在循环开始时执行一次。其中,步长表达式step是可选的,如果没有设置,默认值就为1。迭代变量var的作用域仅在for循环内,并且在循环过程中不要改变迭代变量var的值,否则会带来不可预知的影响。
--for循环,步长为2
for i = 1, 5, 2 do
ngx.say(i .. " ")
end
--for循环,步长为1
ngx.say("<br>");
for i = 1, 5 do
ngx.say(i .. " ")
end
输出的结果分别为:
1 3 5
1 2 3 4 5
(2)增强版的foreach循环,语法如下:
for key, value in pairs(table) do
--body
end
前面讲到,在Lua的table内部保存有一个键-值对的列表,foreach循环就是对这个列表中的键-值对进行迭代,pairs(table)函数的作用就是取得table内部的键-值对列表。
--foreach循环,打印table t中所有的键(key)和值(value)
local days = {
"Sunday", "Monday", "Tuesday", "Wednesday",
"Thursday", "Friday", "Saturday"
}
for key, value in pairs(days) do
ngx.say(key .. ":" .. value .. "; ")
end
local days2 = {
Sunday = 1, Monday = 2, Tuesday = 3, Wednesday = 4,
Thursday = 5, Friday = 6, Saturday = 7
}
for key, value in pairs(days2) do
ngx.say(key .. ":" .. value .. "; ")
end
输出的结果如下:
1:Sunday; 2:Monday; 3:Tuesday; 4:Wednesday; 5:Thursday; 6:Friday; 7:Saturday;
Tuesday:3; Monday:2; Sunday:1; Thursday:5; Friday:6; Wednesday:4; Saturday:7;
Lua的函数定义
Lua函数使用关键词function来定义,使用函数的好处如下:
(1)降低程序的复杂性:模块化编程的好处是将复杂问题变成一个个小问题,然后分而治之。把函数作为一个独立的模块或者当作一个黑盒,而不需要考虑函数里面的细节。
(2)增强代码的复用度:当程序中有相同的代码部分时,可以把这部分写成一个函数,通过调用函数来实现这部分代码的功能,可以节约空间、减少代码长度。
(3)隐含局部变量:在函数中使用局部变量,变量的作用范围不会超出函数,这样就不会给外界带来干扰。
首先来看Lua的函数定义,格式如下:
optional_function_scope function function_name( argument1, argument2, argument3..., argumentn)
function_body
return result_params_comma_separated
end
对上面定义的参数说明如下:
(1)optional_function_scope:该参数表示所定义的函数是全局函数还是局部函数,该参数是可选参数,默认为全局函数,如果定义为局部函数,那么设置为关键字local。
(2)function_name:该参数用于指定函数名称。
(3)argument1,argument2,argument3,…,argumentn:函数参数,多个参数以逗号隔开,也可以不带参数。
(4)function_body:函数体,函数中需要执行的代码语句块。
(5)
result_params_comma_separated:函数返回值,Lua语言中的函数可以返回多个值,每个值以逗号隔开。
下面定义一个局部函数max(),参数为num1和num2,用于比较两个值的大小,并返回最大值:
--局部函数,取得最大值
local function max(num1, num2)
local result = nil;
if (num1 > num2) then
result = num1;
else
result = num2;
end
return result;
end
怎么使用Lua的可变参数呢?和Java语言类似,Lua语言中的函数可以接收可变数目的参数,在函数参数列表中使用三点“...”表示函数有可变的参数。
在函数的内部可以通过一个数组访问可变参数的实参列表,简称可变实参数组。只不过访问可变实参数组前需要将其赋值给一个变量。
--在屏幕上打印日志,可以输入多个打印的数据
local function log(...)
local args = { ... } --这里的...和{}符号中间需要有空格号,否则会出错
for i, v in pairs(args) do
print("index:", i, " value:", v)
ngx.say(v .. ",");
end
ngx.say("<br>");
end
这里不得不提函数参数值的传递方式。大家知道,主要有两种方式:一种是值传递;另一种是引用传递。Lua中的函数的参数大部分是按值传递的。值传递就是调用函数时,把实参的值通过赋值传递给形参,然后形参的改变和实参就没有关系了。在这个过程中,实参和形参是通过在参数表中的位置匹配起来的。但是有一种数据类型除外,就是table数组类型,table类型的传递方式是引用传递。当函数参数是table类型时,传递进来的是实际参数的引用(内存地址),此时在函数内部对该table所做的修改会直接对实际参数生效,而无须自己返回结果和让调用者进行赋值。怎么使得函数可以有多个返回值呢?Lua语言具有一项与众不同的特性,允许函数返回多个值。比如,Lua的内置函数string.find,在源字符串中查找目标字符串,若查找成功,则返回两个值:一个起始位置和一个结束位置。
local s, e = string.find("hello world", "lo") -->返回值为:4 5
print(s, e) -->输出:4 5
如果一个函数需要在return后面返回多个值,那么值与值之间用“,”隔开。
最后总结一下定义一个Lua的函数要注意的几点:
(1)利用名字来解释函数、变量的目的是使人通过名字就能看出来函数的作用。让代码自己说话,不需要注释最好。
(2)由于全局变量一般会占用全局名字空间,同时也有性能损耗(查询全局环境表的开销),因此我们应当尽量使用“局部函数”,在开头加上local修饰符。
(3)由于函数定义本质上就是变量赋值,而变量的定义总是要放置在变量使用之前,因此函数的定义也需要放置在函数调用之前。
Lua的面向对象编程
大家知道,在Lua中使用表(table)实现面向对象,一个表就是一个对象。由于Lua的函数(function)也是一种数据类型,表可以拥有前面介绍的8大数据类型的成员属性。
下面在DataType.lua模块中定义带有一个成员的_Square类,代码如下:
--正方形类
_Square = { side = 0 }
_Square.__index = _Square
--类的方法getArea
function _Square.getArea(self)
return self.side *self.side;
end
--类的方法new
function _Square.new(self, side)
local cls = {}
setmetatable(cls, self)
cls.side = side or 0
return cls
end
--一个统一的模块对象
local _Module = {
...
Square = _Square;
}
在调用Square类的方法时,建议将点号改为冒号。使用冒号进行成员方法调用时,Lua会隐性传递一个self参数,它将调用者对象本身作为第一个参数传递进来。
ngx.say("<br><hr>下面是面向对象操作的演示:<br>");
local Square = dataType.Square;
local square = Square:new(20);
ngx.say("正方形的面积为", square:getArea());
输出的结果如下:
下面是面向对象操作的演示:
正方形的面积为400
Lua的面向对象用到了两个重要的概念:
(1)metatable元表:简单来说,如果一个表(也叫对象)的属性找不到,就去它的元表中查找。通过setmetatable(table,metatable)方法设置一个表的元表。
(2)第一点不完全对。为什么呢?准确来说,不是直接查找元表属性,是去元表中的一个特定的属性,名为__index的表(对象)中查找属性。__index也是一个table类型,Lua会在__index中查找相应的属性。
所以,在上面的代码中,_Square表设置了__index属性的值为自身,当为新创建的new对象查找getArea方法时,需要在原表_Square表的__index属性中查找,找到的就是getArea方法的定义。这个调用的链条如果断了,新创建的new对象的getArea方法就会导航失败。