WebKong的网络日志

分享工作中的所用、所想、所感。

#CSS渲染

css是从右向左解析的

一开始我看到这个从右向左解析渲染的时候,也是呆了一下,但是想想这确实是目前效率最高的方式了。

OK,开始。

浏览器架构

![浏览器的一般结构](/images/2016/struct of browser.png)

1.用户界面 - 用户使用的界面:地址栏,书签,前进后退等

2.浏览器引擎 - 在用户界面和渲染引擎之间传递指令

3.渲染引擎 - 负责显示请求的内容,解析一些HTML、CSS并显示到屏幕

4.网络层 - 网络调用,比如:http、websocket等

5.JavaScript解释器 - 用于解释执行JavaScript代码

6.用户界面后端 -

7.数据存储 - 浏览器需要将持久化的数据存储在硬盘上。不如Cookie、localstroge等

我主要讨论一下渲染引擎

渲染引擎

渲染引擎从网络层获取到请求问道的内容之后,就会执行如下基本流程:

1.渲染引擎开始解析HTML文档,并将各个标记逐渐转化成DOM树上的DOM节点。同时,解析外部CSS文件以及样式元素中的样式数据。等所有CSS文件获取成功后,结合内部style和行内样式,生成render树,render树包含了每个DOM的节点的样式信息。

2.结合DOM树和render树来绘制页面。

CSS是从右向左解析的

构建render树的过程是遍历dom树,每次拿到一个dom节点,然后遍历所有的样式规则,查找与当前节点匹配的规则,最后将所有匹配的规则中定义的样式写入到一个render对象,再将render对象挂到render树上(建立对象和render树的联系)。如图:

![](/images/2016/render dom.png)

也就是说,每一个dom,已知的是class,id和tabName <a id="aa" class="bb" href=""> ,但是匹配的规则可能有成千上万条,渲染引擎需要从中找到符合条件的1条或者多条(数量不会很多)。

由于每条规则都有很多嵌套例如: #content .box p{} .title a{}

假如采用从左向右的方式读取CSS规则,那么大多数的规则读取到后面发现是不匹配的,会浪费很多时间在无用的匹配上。

所以,采用从右向左匹配就会在发现最后边或者次右边的选择器不匹配,整个规则就可以跳过了。

在css模块化了之后,模块打包到一个css文件中了。 比如:

1
2
3
4
5
.model_a a{...}
.model_b a{...}
.model_c a{...}
.model_d a{...}

浏览器会遍历所有的a,所以加大了匹配时间,所以,提倡不用tag标签来定义样式,一律用类 .class

参考

How browsers work

中文 How browsers work

Why do browsers match CSS selectors from right to left?

环境依赖

基础依赖

  1. Python任意版本,3.x没有mysql引擎

  2. pip 8.x 及以上

  3. virtualenv Python虚拟环境,隔离本地项目

使用virtualenv

1
2
3
4
5
6
7
8
9

mkdir mysite; cd mysite

virtualenv --no-site-package venv //创建一个虚拟环境

source venv/bin/activate //进入虚拟环境

deactivate //退出

接下来就可以在环境中配置Django的各种依赖

Django依赖

  1. pip install Django

  2. pip install MySQL-python

  3. pip install Pillow

还有其他依赖

django-suit

fast-rest-fromwork

验证

1
2
3
4
5

import django

print(django.get_version())

创建和使用Django

创建项目

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

django-admin startproject site



//生成的目录结构

mysite/

manage.py //django的命令行工具

mysite/

__init__.py

settings.py //django的配置文件

urls.py //url调度

wsgi.py

将应用配置进settings,以激活应用

数据库建立

配置数据库

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

//settings.py

DATABASES = {

'default': {

'ENGINE': 'django.db.backends.mysql',

'NAME': 'django-test',

'USER': 'root',

'PASSWORD': '1234567890',

'HOST': '127.0.0.1',

'PORT': '3306',

}

}

数据迁移(migrate)

1
2
3

python manage.py migrate

启动服务

1
2
3

python manage.py runserver

创建应用(模型)

1
2
3

python manage.py startapp ad

新建Models

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93

class AdInfo(models.Model):

id = models.CharField(primary_key=True, max_length=100)

name = models.CharField(unique=True, max_length=100)

description = models.CharField(max_length=1000, blank=True, null=True)

priority = models.IntegerField()

status = models.IntegerField()

start_time = models.DateTimeField(blank=True, null=True)

end_time = models.DateTimeField()

max_show_count = models.BigIntegerField()

max_day_show_count = models.BigIntegerField()

create_time = models.DateTimeField()

update_time = models.DateTimeField()



class Meta:

managed = True

db_table = 'ad_info'





class AdInfoPlacement(models.Model):

ad_placement = models.ForeignKey('AdPlacement', models.DO_NOTHING)

ad_info = models.ForeignKey(AdInfo, models.DO_NOTHING)

create_time = models.DateTimeField()



class Meta:

managed = True

db_table = 'ad_info_placement'

unique_together = (('ad_info', 'ad_placement'),)





class AdPlacement(models.Model):

id = models.CharField(primary_key=True, max_length=100)

name = models.CharField(max_length=100)

app_id = models.CharField(max_length=100)

status = models.IntegerField()

create_time = models.DateTimeField()



class Meta:

managed = True

db_table = 'ad_placement'

unique_together = (('app_id', 'name'),)





class AdProperties(models.Model):

ad_info_id = models.CharField(max_length=100)

property_key = models.CharField(max_length=100, blank=True, null=True)

property_value = models.CharField(max_length=2000, blank=True, null=True)

django不支持双主键或者多主键

执行命令通知django修改models

1
2
3
4
5

python manage.py makemigrations //创建迁移文件

python manage.py migrate //更新到数据库

python manage.py inspectdb > models.py //反向生成model

创建管理员,admin管理

1
2
3
4
5

python manage.py createsuperuser

根据提示一次输入用户名,邮箱和密码就OK了

启动服务看一下。

这样就成功启动了,根据自己定义的Model的Django。在host/admin/目录下可以登陆并看到相应的管理条目

自定义管理

未完待续…

其他链接

官方文档options

官方文档actions

配置详情
url调度器

总是忘了数组的方法,所以备忘一下,记性真是不好
一天之计在于晨

concat()

连接两个或者更多的数组,并返回结果。 不会改变现有数组,会返回被连接数组的副本。

arrayObject.concat(arr1,arr2, ... arrx)

返回值:返回一个新的数组。

1
2
3
var a = [1,2,3]
console.log(a.concat([4,5]))
// 1,2,3,4,5

join()

把数组的所有元素放入一个字符串。元素通过制定的分隔符进行分隔。

arrayObject.join(separator) 默认分隔符为’,’

返回值:返回一个字符串。

1
2
3
var arr = [1,2,3,4,5,6,'haha']
console.log(arr.join('.'))
// 1.2.3.4.5.6.haha

pop()

删除并返回数组的最后一个元素

arrayObject.pop()

返回值:arrayObject的最后一个元素
说明:原地操作。 pop()方法将删除arrayObject的最后一个元素,把数组长度减1,并返回删除的元素的值。如果数组为空,则不改变数组,并返回undefined值。

1
2
3
4
5
6
7
var a = [1,2,3]
console.log(a)
console.log(a.pop())
console.log(a)
// [1,2,3]
// 3
// [1,2]

push()

向数组的末尾添加一个或多个元素,并返回新的长度。

arrayObject.push(newelement1,newelement2,....,newelementX)

返回值:元素添加到数组之后的新长度
说明:原地操作。方法可把它的参数顺序添加到 arrayObject 的尾部。它直接修改 arrayObject,而不是创建一个新的数组。push() 方法和 pop() 方法使用数组提供的先进后出栈的功能。unshift()可以在头部添加一个或者多个元素

1
2
3
4
5
6
7
var a = [1,2,3,4,5]
console.log(a)
console.log(a.push(7))
console.log(a)
// [1,2,3,4,5]
// 6
// [1,2,3,4,5,7]

reverse()

颠倒数组中元素的顺序。

arrayObject.reverse()

返回值:颠倒顺序的数组本身
说明:原地操作。直接改变原来的数组,不会创建新的数组。

1
2
3
4
var a = [1,2,3]
console.log(a)
console.log(a.reverse()=== a)
console.log(a)

slice()

从已有的数组中返回选定的元素。

arrayObject.slice(start,end)
start 必须,规定从何处开始选取。如果是负值,那么它规定从数组的为不开始算起的位置。也就是 -1 是最后一个元素,-2 是倒数第二个,以此类推
end 可选,规定从何处结束选取。该参数是数组片段结束处的数组下标。如果没有指定,那么切分的数组包含从start到数组结束的所有元素。如果这个参数是负数,那么它规定的是从数组尾部开始算起的元素。

返回值:返回一个新的数组。包含start到end(不含)的元素

说明:非原地操作,返回子数组。start,end都是元素的下标

1
2
3
4
5
6
7
var a = [1,2,3,4,5,6,7]
console.log(a)
console.log(a.slice(1)) //从下标是1开始选取到最后 [2,3,4,5,6,7]
console.log(a.slice(1,3))//从下标1开始到下标3 [2,3]
console.log(a.slice(1,-2)) //从下标1开始到倒数第二个[2,3,4,5]
console.log(a.slice(-2)) //从倒数第二个开始到最后 [6,7]
console.log(a.(-3,-1)) //从倒数第三个到倒数第一个[5,6]

splice()

方法向/从数组中添加/删除项目,然后返回被删除的项目。

arrayObject.splice(index,howmany,item1,.....,itemX)

index 必须,整数, 规定添加/删除的位置,如果是负数,可从数组结尾处规定位置
howmany 必须,要删除的项目的数量。如果设置为0 ,则不会删除项目。就是删除几个
item,…,itemx 可选,向数组添加的新的项目

说明:原地操作。 splice() 方法可删除从 index 处开始的零个或多个元素,并且用参数列表中声明的一个或多个值来替换那些被删除的元素。
如果从 arrayObject 中删除了元素,则返回的是含有被删除的元素的数组。

1
2
3
4
5
6
7
8
var a = [1,2,3,4,5];
var b = [];
b = a.slice(0);
console.log(a.splice(1,3,9,8,7,6)) //执行后的返回值 [ 2, 3, 4 ]
console.log(a) //[ 1, 9, 8, 7, 6, 5 ]
console.log(b.splice(1,2))//[ 2, 3 ]
console.log(b)//[ 1, 4, 5 ]

sort()

用于对数组的元素进行排序。

arrayObject.sort(sortby) sortby可选。规定排序顺序。必须是函数。

说明:原地操作。 数组在原数组上进行排序,不生成副本。
如果调用该方法时没有使用参数,将按字母顺序对数组中的元素进行排序,说得更精确点,是按照字符编码的顺序进行排序。要实现这一点,首先应把数组的元素都转换成字符串(如有必要),以便进行比较。
如果想按照其他标准进行排序,就需要提供比较函数,该函数要比较两个值,然后返回一个用于说明这两个值的相对顺序的数字。比较函数应该具有两个参数 a 和 b,其返回值如下:
若 a 小于 b,在排序后的数组中 a 应该出现在 b 之前,则返回一个小于 0 的值。
若 a 等于 b,则返回 0。
若 a 大于 b,则返回一个大于 0 的值。

shift()

把数组的第一个元素从其中删除,并返回第一个元素的值。
arrayObject.shift()

说明:原地操作。 如果数组是空的,那么 shift() 方法将不进行任何操作,返回 undefined 值。和pop()方法使用相同

unshift()

向数组的开头添加一个或更多元素,并返回新的长度。
arrayObject.unshift(newelement1,newelement2,....,newelementX)

说明:原地操作。类似push()方法。unshift() 方法将把它的参数插入 arrayObject 的头部,并将已经存在的元素顺次地移到较高的下标处,以便留出空间。该方法的第一个参数将成为数组的新元素 0,如果还有第二个参数,它将成为新的元素 1,以此类推。

toString()

把数组转换为字符串,并返回结果。数组中的元素之间用逗号分隔。

arrayObject.toString()

说明:arrayObject 的字符串表示。返回值与没有参数的 join() 方法返回的字符串相同。

toLocaleString()

把数组转换为本地字符串。
首先调用每个数组元素的 toLocaleString() 方法,然后使用地区特定的分隔符把生成的字符串连接起来,形成一个字符串。

valueOf()

返回 Array 对象的原始值。

arrayObject.valueOf()

说明:方法通常由 JavaScript 在后台自动调用,并不显式地出现在代码中。

indexOf()

indexOf()方法返回给定元素能找在数组中找到的第一个索引值,否则返回-1。

arr.indexOf(searchElement[, fromIndex = 0])

searchElement:要查找的元素
fromIndex:开始查找的位置,如果该索引值大于或等于数组长度,意味着不会在数组里查找,返回-1。如果参数中提供的索引值是一个负值,则将其作为数组末尾的一个抵消,即-1表示从最后一个元素开始查找,-2表示从倒数第二个元素开始查找 ,以此类推。 注意:如果参数中提供的索引值是一个负值,仍然从前向后查询数组。如果抵消后的索引值仍小于0,则整个数组都将会被查询。其默认值为0.

indexOf 在ECMA-262 标准 的第5版中被加入,但并非所有的浏览器都支持该方法。可以如下使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
// Production steps of ECMA-262, Edition 5, 15.4.4.14
// Reference: http://es5.github.io/#x15.4.4.14
if (!Array.prototype.indexOf) {
Array.prototype.indexOf = function(searchElement, fromIndex) {

var k;

// 1. Let O be the result of calling ToObject passing
// the this value as the argument.
if (this == null) {
throw new TypeError('"this" is null or not defined');
}

var O = Object(this);

// 2. Let lenValue be the result of calling the Get
// internal method of O with the argument "length".
// 3. Let len be ToUint32(lenValue).
var len = O.length >>> 0;

// 4. If len is 0, return -1.
if (len === 0) {
return -1;
}

// 5. If argument fromIndex was passed let n be
// ToInteger(fromIndex); else let n be 0.
var n = +fromIndex || 0;

if (Math.abs(n) === Infinity) {
n = 0;
}

// 6. If n >= len, return -1.
if (n >= len) {
return -1;
}

// 7. If n >= 0, then Let k be n.
// 8. Else, n<0, Let k be len - abs(n).
// If k is less than 0, then let k be 0.
k = Math.max(n >= 0 ? n : len - Math.abs(n), 0);

// 9. Repeat, while k < len
while (k < len) {
// a. Let Pk be ToString(k).
// This is implicit for LHS operands of the in operator
// b. Let kPresent be the result of calling the
// HasProperty internal method of O with argument Pk.
// This step can be combined with c
// c. If kPresent is true, then
// i. Let elementK be the result of calling the Get
// internal method of O with the argument ToString(k).
// ii. Let same be the result of applying the
// Strict Equality Comparison Algorithm to
// searchElement and elementK.
// iii. If same is true, return k.
if (k in O && O[k] === searchElement) {
return k;
}
k++;
}
return -1;
};
}

lastIndexOf()

lastIndexOf() 方法返回指定元素(也即有效的 JavaScript 值或变量)在数组中的最后一个的索引,如果不存在则返回 -1。从数组的后面向前查找,从 fromIndex 处开始。

语法

1
arr.lastIndexOf(searchElement[, fromIndex = arr.length - 1])

参数
searchElement
被查找的元素。
fromIndex
从此位置开始逆向查找。默认为数组的长度减 1,即整个数组都被查找。如果该值大于或等于数组的长度,则整个数组会被查找。如果为负值,将其视为从数组末尾向前的偏移。即使该值为负,数组仍然会被从后向前查找。如果该值为负时,其绝对值大于数组长度,则方法返回 -1,即数组不会被查找。
返回值
数组中最后一个元素的索引,如未找到返回-1

https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array/lastIndexOf

isArray()

方法用来判断某个值是否为Array。如果是,则返回 true,否则返回 false。

Array.isArray(value)
value 要检测的值

every()

every() 方法测试数组的所有元素是否都通过了指定函数的测试。

arr.every(callback[, thisArg])

callback:用来测试每个元素的函数。
thisArg:执行 callback 时使用的 this 值。

说明:非原地操作。
every 方法为数组中的每个元素执行一次 callback 函数,直到它找到一个使 callback 返回 false(表示可转换为布尔值 false 的值)的元素。如果发现了一个这样的元素,every 方法将会立即返回 false。否则,callback 为每一个元素返回 true,every 就会返回 true。callback 只会为那些已经被赋值的索引调用。不会为那些被删除或从来没被赋值的索引调用。

callback 被调用时传入三个参数:元素值,元素的索引,原数组。

如果为 every 提供一个 thisArg 参数,在该参数为调用 callback 时的 this 值。如果省略该参数,则 callback 被调用时的 this 值,在非严格模式下为全局对象,在严格模式下传入 undefined。
every 不会改变原数组。

same()

some() 方法测试数组中的某些元素是否通过由提供的函数实现的测试。
对于放在空数组上的任何条件,此方法返回false。

1
arr.some(callback[, thisArg])

参数
callback
用来测试每个元素的函数,接受三个参数:
currentValue
数组中正在处理的元素。
index 可选
数组中正在处理的元素的索引值。
array可选
some()被调用的数组。
thisArg可选
执行 callback 时使用的 this 值。
返回值
如果回调函数返回任何数组元素的truthy值,则返回true;否则为false。

1
2
3
4
5
6
7
8
9
10
  var array = [1, 2, 3, 4, 5];

var even = function(element) {
// checks whether an element is even
return element % 2 === 0;
};

console.log(array.some(even));
// expected output: true

filter()

filter() 方法使用指定的函数测试所有元素,并创建一个包含所有通过测试的元素的新数组。

var new_arrary = arr.filter(callback[, thisArg])

callback:用来测试数组的每个元素的函数。调用时使用参数 (element, index, array)。
返回true表示保留该元素(通过测试),false则不保留。
thisArg:可选。执行 callback 时的用于 this 的值。

说明:非原地操作。 返回通过filter的元素数组

map()

善用map,能有效避免js里面for循环的坑

map() 方法返回一个由原数组中的每个元素调用一个指定方法后的返回值组成的新数组。

array.map(callback[, thisArg])
参数说明:

callback, 原数组中的元素经过该方法后返回一个新的元素。

  1. currentValue ,callback 的第一个参数,数组中当前被传递的元素。
  2. index, callback 的第二个参数,数组中当前被传递的元素的索引。
  3. array, callback 的第三个参数,调用 map 方法的数组。
    thisArg, 执行 callback 函数时 this 指向的对象。

reduce()

reduce() 方法接收一个函数作为累加器(accumulator),数组中的每个值(从左到右)开始合并,最终为一个值。

arr.reduce(callback,[initialValue])
参数说明:
callback, 执行数组中每个值的函数,包含四个参数

  1. previousValue, 上一次调用回调返回的值,或者是提供的初始值(initialValue)
  2. currentValue, 数组中当前被处理的元素
  3. index, 当前元素在数组中的索引
  4. array, 调用 reduce 的数组
    initialValue, 作为第一次调用 callback 的第一个参数。

说明:回调函数第一次执行时,previousValue 和 currentValue 的取值有两种情况,如果 initialValue 在调用 reduce 时被提供,那么第一个 previousValue 等于 initialValue ,并且currentValue 等于数组中的第一个值;如果initialValue 未被提供,那么previousValue 等于数组中的第一个值,currentValue等于数组中的第二个值。

1
2
3
[0,1,2,3,4].reduce(function(previousValue, currentValue, index, array){
return previousValue + currentValue;
});
previousValue currentValue index array return value
first call 0 1 1 [0,1,2,3,4] 1
second call 1 2 2 [0,1,2,3,4] 3
third call 3 3 3 [0,1,2,3,4] 6
fourth call 6 4 4 [0,1,2,3,4] 10

ES6中的方法

from()

可以将一个类数组对象或可遍历对象转换成真正的数组。

copyWithin()

会浅拷贝数组的部分元素到同一数组的不同位置,且不改变数组的大小,返回该数组。

Array.of()

Array.of() 方法会将它的任意类型的多个参数放在一个数组里并返回。

Array.of() 和 Array 构造函数不同的是:在处理数值类型的参数时,Array.of(42) 创建的数组只有一个元素,即 42, 但 Array(42) 创建了42个元素,每个元素都是undefined。

Array.of(element0[, element1[, ...[, elementN]]])

fill()

使用 fill() 方法,可以将一个数组中指定区间的所有元素的值, 都替换成或者说填充成为某个固定的值。

arr.fill(value[, start = 0[, end = this.length]])
value:用来填充数组元素的值。
start:可选,开始索引。
end:可选,结束索引。
说明:原地操作。具体要填充的元素区间是 [start, end) , 一个半开半闭区间.
如果 start 是个负数, 则开始索引会被自动计算成为 length+start, 其中 length 是 this 对象的 length 属性值. 如果 end 是个负数, 则结束索引会被自动计算成为 length+end.

fill 方法故意被设计成通用方法, 也就是说它不需要 this 值必须是个数组对象, 类数组对象也是可以调用该方法的 .

fill 方法是个可变方法, 它会改变调用它的 this 对象本身, 然后返回它, 而并不是返回一个副本.

find()

如果数组中某个元素满足测试条件,find() 方法就会返回那个元素的第一个值,如果没有满足条件的元素,则返回 undefined。
区别:findIndex()方法返回的是满足条件的元素的索引,而非它的值。

arr.find(callback[, thisArg])

参数说明:
callback
在数组每一项上执行的函数,接收 3 个参数:

  1. element, 当前遍历到的元素。
  2. index, 当前遍历到的索引。
  3. array, 数组本身。
    thisArg
    可选,指定 callback 的 this 参数。

includes()

1
2
arr.includes(searchElement)
arr.includes(searchElement, fromIndex)

参数说明:
searchElement : 需要查找的元素值。

fromIndex 可选
从该索引处开始查找 searchElement。如果为负值,则按升序从 array.length - fromIndex 的索引开始搜索。默认为 0。
返回值节
返回值
一个 Boolean。

key()

keys() 方法返回一个包含数组中每个索引键的Array Iterator对象。

语法

1
arr.keys()

返回值
一个新的 Array 迭代器对象。

使用技巧

  • slice()方法复制数组,看前面的例子

  • push()和pop()可以实现先进后出栈的原理

  • push()和shift()可以实现先进先出队列的原理

后续继续添加

参考

MDN-JavaScript标准库
w3school JavaScript Array 对象

当忘记了Django的管理员密码可以通过一下两个办法来解决

直接修改

1
2
3
4
5
6
7
# 在项目目录下
python manage.py changepassword admin

Changing password for user 'admin'
Password:
Password (again):
Password changed successfully for user 'admin'

就修改成功了。

如果修改不成功可以通过新建来达到目的

1
python manage.py createsuperuser --username admin

可以删掉之前的,重新再添加一个。

转载自:前端跨域整理

什么是跨域?

跨域一词从字面意思看,就是跨域名嘛,但实际上跨域的范围绝对不止那么狭隘。具体概念如下:只要协议、域名、端口有任何一个不同,都被当作是不同的域。之所以会产生跨域这个问题呢,其实也很容易想明白,要是随便引用外部文件,不同标签下的页面引用类似的彼此的文件,浏览器很容易懵逼的,安全也得不到保障了就。什么事,都是安全第一嘛。但在安全限制的同时也给注入iframe或是ajax应用上带来了不少麻烦。所以我们要通过一些方法使本域的js能够操作其他域的页面对象或者使其他域的js能操作本域的页面对象(iframe之间)。下面是具体的跨域情况详解:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
URL                      说明       是否允许通信
http://www.a.com/a.js
http://www.a.com/b.js 同一域名下 允许
http://www.a.com/lab/a.js
http://www.a.com/script/b.js 同一域名下不同文件夹 允许
http://www.a.com:8000/a.js
http://www.a.com/b.js 同一域名,不同端口 不允许
http://www.a.com/a.js
https://www.a.com/b.js 同一域名,不同协议 不允许
http://www.a.com/a.js
http://70.32.92.74/b.js 域名和域名对应ip 不允许
http://www.a.com/a.js
http://script.a.com/b.js 主域相同,子域不同 不允许(cookie这种情况下也不允许访问)
http://www.a.com/a.js
http://a.com/b.js 同一域名,不同二级域名(同上) 不允许(cookie这种情况下也不允许访问)
http://www.cnblogs.com/a.js
http://www.a.com/b.js 不同域名 不允许

这里我们需要注意两点:
如果是协议和端口造成的跨域问题“前台”是无能为力的;
在跨域问题上,域仅仅是通过“URL的首部”来识别而不会去尝试判断相同的ip地址对应着两个域或两个域是否在同一个ip上。
(“URL的首部”指window.location.protocol +window.location.host,也可以理解为“Domains, protocols and ports must match”。)

通过document.domain跨域

前面说过了,浏览器有一个同源策略,其限制之一是不能通过ajax的方法去请求不同源中的文档。 第二个限制是浏览器中不同域的框架之间是不能进行js的交互操作的。不同的框架之间是可以获取window对象的,但却无法获取相应的属性和方法。比如,有一个页面,它的地址是http://www.damonare.cn/a.html , 在这个页面里面有一个iframe,它的src是http://damonare.cn/b.html, 很显然,这个页面与它里面的iframe框架是不同域的,所以我们是无法通过在页面中书写js代码来获取iframe中的东西的:

1
2
3
4
5
6
7
8
9
<script type="text/javascript">
function test(){
var iframe = document.getElementById('ifame');
var win = iframe.contentWindow;//可以获取到iframe里的window对象,但该window对象的属性和方法几乎是不可用的
var doc = win.document;//这里获取不到iframe里的document对象
var name = win.name;//这里同样获取不到window对象的name属性
}
</script>
<iframe id = "iframe" src="http://damonare.cn/b.html" onload = "test()"></iframe>

这个时候,document.domain就可以派上用场了,我们只要把http://www.damonare.cn/a.html和http://damonare.cn/b.html这两个页面的document.domain都设成相同的域名就可以了。但要注意的是,document.domain的设置是有限制的,我们只能把document.domain设置成自身或更高一级的父域,且主域必须相同。
在页面http://www.damonare.cn/a.html 中设置document.domain:

1
2
3
4
5
6
7
<iframe id = "iframe" src="http://damonare.cn/b.html" onload = "test()"></iframe>
<script type="text/javascript">
document.domain = 'damonare.cn';//设置成主域
function test(){
alert(document.getElementById('iframe').contentWindow);//contentWindow 可取得子窗口的 window 对象
}
</script>

在页面http://damonare.cn/b.html 中也设置document.domain:

1
2
3
<script type="text/javascript">
document.domain = 'damonare.cn';//在iframe载入这个页面也设置document.domain,使之与主页面的document.domain相同
</script>

修改document.domain的方法只适用于不同子域的框架间的交互。

通过location.hash跨域

因为父窗口可以对iframe进行URL读写,iframe也可以读写父窗口的URL,URL有一部分被称为hash,就是#号及其后面的字符,它一般用于浏览器锚点定位,Server端并不关心这部分,应该说HTTP请求过程中不会携带hash,所以这部分的修改不会产生HTTP请求,但是会产生浏览器历史记录。此方法的原理就是改变URL的hash部分来进行双向通信。每个window通过改变其他 window的location来发送消息(由于两个页面不在同一个域下IE、Chrome不允许修改parent.location.hash的值,所以要借助于父窗口域名下的一个代理iframe),并通过监听自己的URL的变化来接收消息。这个方式的通信会造成一些不必要的浏览器历史记录,而且有些浏览器不支持onhashchange事件,需要轮询来获知URL的改变,最后,这样做也存在缺点,诸如数据直接暴露在了url中,数据容量和类型都有限等。下面举例说明:
假如父页面是baidu.com/a.html,iframe嵌入的页面为google.com/b.html(此处省略了域名等url属性),要实现此两个页面间的通信可以通过以下方法。
a.html传送数据到b.html
a.html下修改iframe的src为google.com/b.html#paco
b.html监听到url发生变化,触发相应操作
b.html传送数据到a.html,由于两个页面不在同一个域下IE、Chrome不允许修改parent.location.hash的值,所以要借助于父窗口域名下的一个代理iframe
b.html下创建一个隐藏的iframe,此iframe的src是baidu.com域下的,并挂上要传送的hash数据,如src=”http://www.baidu.com/proxy.html#data“
proxy.html监听到url发生变化,修改a.html的url(因为a.html和proxy.html同域,所以proxy.html可修改a.html的url hash)
a.html监听到url发生变化,触发相应操作
b.html页面的关键代码如下:

1
2
3
4
5
6
7
8
9
try {  
parent.location.hash = 'data';
} catch (e) {
// ie、chrome的安全机制无法修改parent.location.hash,
var ifrproxy = document.createElement('iframe');
ifrproxy.style.display = 'none';
ifrproxy.src = "http://www.baidu.com/proxy.html#data";
document.body.appendChild(ifrproxy);
}

proxy.html页面的关键代码如下 :

1
2
//因为parent.parent(即baidu.com/a.html)和baidu.com/proxy.html属于同一个域,所以可以改变其location.hash的值  
parent.parent.location.hash = self.location.hash.substring(1);

通过HTML5的postMessage方法跨域

高级浏览器Internet Explorer 8+, chrome,Firefox , Opera 和 Safari 都将支持这个功能。这个功能主要包括接受信息的”message”事件和发送消息的”postMessage”方法。比如damonare.cn域的A页面通过iframe嵌入了一个google.com域的B页面,可以通过以下方法实现A和B的通信
A页面通过postMessage方法发送消息:

1
2
3
4
5
window.onload = function() {  
var ifr = document.getElementById('ifr');
var targetOrigin = "http://www.google.com";
ifr.contentWindow.postMessage('hello world!', targetOrigin);
};

postMessage的使用方法:
otherWindow.postMessage(message, targetOrigin);
otherWindow:指目标窗口,也就是给哪个window发消息,是 window.frames 属性的成员或者由 window.open 方法创建的窗口
message: 是要发送的消息,类型为 String、Object (IE8、9 不支持)
targetOrigin: 是限定消息接收范围,不限制请使用 ‘*
B页面通过message事件监听并接受消息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var onmessage = function (event) {  
var data = event.data;//消息
var origin = event.origin;//消息来源地址
var source = event.source;//源Window对象
if(origin=="http://www.baidu.com"){
console.log(data);//hello world!
}
};
if (typeof window.addEventListener != 'undefined') {
window.addEventListener('message', onmessage, false);
} else if (typeof window.attachEvent != 'undefined') {
//for ie
window.attachEvent('onmessage', onmessage);
}

同理,也可以B页面发送消息,然后A页面监听并接受消息。

通过jsonp跨域

刚才说的这几种都是双向通信的,即两个iframe,页面与iframe或是页面与页面之间的,下面说几种单项跨域的(一般用来获取数据),因为通过script标签引入的js是不受同源策略的限制的。所以我们可以通过script标签引入一个js或者是一个其他后缀形式(如php,jsp等)的文件,此文件返回一个js函数的调用。
比如,有个a.html页面,它里面的代码需要利用ajax获取一个不同域上的json数据,假设这个json数据地址是http://damonare.cn/data.php,那么a.html中的代码就可以这样:

1
2
3
4
5
6
<script type="text/javascript">
function dosomething(jsondata){
//处理获得的json数据
}
</script>
<script src="http://example.com/data.php?callback=dosomething"></script>

我们看到获取数据的地址后面还有一个callback参数,按惯例是用这个参数名,但是你用其他的也一样。当然如果获取数据的jsonp地址页面不是你自己能控制的,就得按照提供数据的那一方的规定格式来操作了。
因为是当做一个js文件来引入的,所以http://damonare.cn/data.php返回的必须是一个能执行的js文件,所以这个页面的php代码可能是这样的(一定要和后端约定好哦):

1
2
3
4
5
<?php
$callback = $_GET['callback'];//得到回调函数名
$data = array('a','b','c');//要返回的数据
echo $callback.'('.json_encode($data).')';//输出
?>

最终,输出结果为:dosomething([‘a’,’b’,’c’]);
如果你的页面使用jquery,那么通过它封装的方法就能很方便的来进行jsonp操作了。

1
2
3
4
5
<script type="text/javascript">
$.getJSON('http://example.com/data.php?callback=?,function(jsondata)'){
//处理获得的json数据
});
</script>

jquery会自动生成一个全局函数来替换callback=?中的问号,之后获取到数据后又会自动销毁,实际上就是起一个临时代理函数的作用。$.getJSON方法会自动判断是否跨域,不跨域的话,就调用普通的ajax方法;跨域的话,则会以异步加载js文件的形式来调用jsonp的回调函数。
JSONP的优缺点
JSONP的优点是:它不像XMLHttpRequest对象实现的Ajax请求那样受到同源策略的限制;它的兼容性更好,在更加古老的浏览器中都可以运行,不需要XMLHttpRequest或ActiveX的支持;并且在请求完毕后可以通过调用callback的方式回传结果。
JSONP的缺点则是:它只支持GET请求而不支持POST等其它类型的HTTP请求;它只支持跨域HTTP请求这种情况,不能解决不同域的两个页面之间如何进行JavaScript调用的问题。

通过CORS跨域

CORS(Cross-Origin Resource Sharing)跨域资源共享,定义了必须在访问跨域资源时,浏览器与服务器应该如何沟通。CORS背后的基本思想就是使用自定义的HTTP头部让浏览器与服务器进行沟通,从而决定请求或响应是应该成功还是失败。目前,所有浏览器都支持该功能,IE浏览器不能低于IE10。整个CORS通信过程,都是浏览器自动完成,不需要用户参与。对于开发者来说,CORS通信与同源的AJAX通信没有差别,代码完全一样。浏览器一旦发现AJAX请求跨源,就会自动添加一些附加的头信息,有时还会多出一次附加的请求,但用户不会有感觉。
因此,实现CORS通信的关键是服务器。只要服务器实现了CORS接口,就可以跨源通信。
平时的ajax请求可能是这样的:

1
2
3
4
5
<script type="text/javascript">
var xhr = new XMLHttpRequest();
xhr.open("POST", "/damonare",true);
xhr.send();
</script>

以上damonare部分是相对路径,如果我们要使用CORS,相关Ajax代码可能如下所示:

1
2
3
4
5
<script type="text/javascript">
var xhr = new XMLHttpRequest();
xhr.open("GET", "http://segmentfault.com/u/trigkit4/",true);
xhr.send();
</script>

代码与之前的区别就在于相对路径换成了其他域的绝对路径,也就是你要跨域访问的接口地址。
服务器端对于CORS的支持,主要就是通过设置Access-Control-Allow-Origin来进行的。如果浏览器检测到相应的设置,就可以允许Ajax进行跨域的访问。关于CORS更多了解可以看下阮一峰老师的这一篇文章:跨域资源共享 CORS 详解
CORS和JSONP对比
JSONP只能实现GET请求,而CORS支持所有类型的HTTP请求。
使用CORS,开发者可以使用普通的XMLHttpRequest发起请求和获得数据,比起JSONP有更好的错误处理。
JSONP主要被老的浏览器支持,它们往往不支持CORS,而绝大多数现代浏览器都已经支持了CORS)。
CORS与JSONP相比,无疑更为先进、方便和可靠。

通过window.name跨域

window对象有个name属性,该属性有个特征:即在一个窗口(window)的生命周期内,窗口载入的所有的页面都是共享一个window.name的,每个页面对window.name都有读写的权限,window.name是持久存在一个窗口载入过的所有页面中的,并不会因新页面的载入而进行重置。
比如:我们在任意一个页面输入

1
2
3
4
window.name = "My window's name";
setTimeout(function(){
window.location.href = "http://damonare.cn/";
},1000)

进入damonare.cn页面后我们再检测再检测 window.name :

1
window.name; // My window's name

可以看到,如果在一个标签里面跳转网页的话,我们的 window.name 是不会改变的。
基于这个思想,我们可以在某个页面设置好 window.name 的值,然后跳转到另外一个页面。在这个页面中就可以获取到我们刚刚设置的 window.name 了。
由于安全原因,浏览器始终会保持 window.name 是string 类型。
同样这个方法也可以应用到和iframe的交互来:
比如:我的页面(http://damonare.cn/index.html)中内嵌了一个iframe:

<iframe id="iframe" src="http://www.google.com/iframe.html"></iframe>
在 iframe.html 中设置好了 window.name 为我们要传递的字符串。
我们在 index.html 中写了下面的代码:

1
2
3
4
5
var iframe = document.getElementById('iframe');
var data = '';
iframe.onload = function() {
data = iframe.contentWindow.name;
};

Boom!报错!肯定的,因为两个页面不同源嘛,想要解决这个问题可以这样干:

1
2
3
4
5
6
7
8
var iframe = document.getElementById('iframe');
var data = '';
iframe.onload = function() {
iframe.onload = function(){
data = iframe.contentWindow.name;
}
iframe.src = 'about:blank';
};

或者将里面的 about:blank 替换成某个同源页面(about:blank,javascript: 和 data: 中的内容,继承了载入他们的页面的源。)
这种方法与 document.domain 方法相比,放宽了域名后缀要相同的限制,可以从任意页面获取 string 类型的数据。

文件操作

Lua I/O 库提供两种不同的方式处理文件:隐式文件描述,显式文件描述。

这些文件 I/O 操作,在 OpenResty 的上下文中对事件循环是会产生阻塞效应。 OpenResty 比较擅长的是高并发网络处理,在这个环境中,任何文件的操作,都将阻塞其他并行执行的请求。实际中的应用,在 OpenResty 项目中应尽可能让网络处理部分、文件 I/0 操作部分相互独立,不要揉和在一起。

隐式文件描述

设置一个默认的输入或输出文件,然后在这个文件上进行所有的输入或输出操作。所有的操作函数由 io 表提供。

打开已经存在的 test1.txt 文件,并读取里面的内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
file = io.input("test1.txt")    -- 使用 io.input() 函数打开文件

repeat
line = io.read() -- 逐行读取内容,文件结束时返回nil
if nil == line then
break
end
print(line)
until (false)

io.close(file) -- 关闭文件

--> output
my test file
hello
lua

在 test1.txt 文件的最后添加一行 “hello world”

1
2
3
4
file = io.open("test1.txt", "a+")   -- 使用 io.open() 函数,以添加模式打开文件
io.output(file) -- 使用 io.output() 函数,设置默认输出文件
io.write("\nhello world") -- 使用 io.write() 函数,把内容写到文件
io.close(file)

在相应目录下打开test1.txt文件,查看文件内容发生的变化。

显式文件描述

使用 file:XXX() 函数方式进行操作,其中 file 为 io.open() 返回的文件句柄。

打开已经存在的 test2.txt 文件,并读取里面的内容

1
2
3
4
5
6
7
8
9
10
11
file = io.open("test2.txt", "r")    -- 使用 io.open() 函数,以只读模式打开文件

for line in file:lines() do -- 使用 file:lines() 函数逐行读取文件
print(line)
end

file:close()

-->output
my test2
hello lua

在 test2.txt 文件的最后添加一行 “hello world”

1
2
3
file = io.open("test2.txt", "a")  -- 使用 io.open() 函数,以添加模式打开文件
file:write("\nhello world") -- 使用 file:open() 函数,在文件末尾追加内容
file:close()

在相应目录下打开test2.txt文件,查看文件内容发生的变化。

文件操作函数

io.open (filename [, mode])

按指定的模式 mode,打开一个文件名为filename的文件,成功则返回文件句柄,失败则返回 nil 加错误信息。模式:

mode

模式字符串后面可以有一个 ‘b’,用于在某些系统中打开二进制文件。

注意 “w” 和 “wb” 的区别

  • “w” 表示文本文件。某些文件系统(如 Linux 的文件系统)认为 0x0A 为文本文件的换行符,Windows 的文件系统认为 0x0D0A 为文本文件的换行符。为了兼容其他文件系统(如从 Linux 拷贝来的文件),Windows 的文件系统在写文件时,会在文件中 0x0A 的前面加上 0x0D。使用 “w”,其属性要看所在的平台。

  • “wb” 表示二进制文件。文件系统会按纯粹的二进制格式进行写操作,因此也就不存在格式转换的问题。(Linux 文件系统下 “w” 和 “wb” 没有区别)

file:close ()

关闭文件。注意:当文件句柄被垃圾收集后,文件将自动关闭。句柄将变为一个不可预知的值。

io.close ([file])

关闭文件,和 file:close() 的作用相同。没有参数 file 时,关闭默认输出文件。

file:flush ()

把写入缓冲区的所有数据写入到文件 file 中。

io.flush ()

相当于 file:flush(),把写入缓冲区的所有数据写入到默认输出文件。

io.input ([file])

当使用一个文件名调用时,打开这个文件(以文本模式),并设置文件句柄为默认输入文件;
当使用一个文件句柄调用时,设置此文件句柄为默认输入文件;
当不使用参数调用时,返回默认输入文件句柄。

file:lines ()

返回一个迭代函数,每次调用将获得文件中的一行内容,当到文件尾时,将返回 nil,但不关闭文件。

io.lines ([filename])

打开指定的文件 filename 为读模式并返回一个迭代函数,每次调用将获得文件中的一行内容,当到文件尾时,将返回 nil,并自动关闭文件。若不带参数时 io.lines() 等价于 io.input():lines() 读取默认输入设备的内容,结束时不关闭文件。

io.output ([file])

类似于 io.input,但操作在默认输出文件上。

file:read (…)

按指定的格式读取一个文件。按每个格式将返回一个字符串或数字,如果不能正确读取将返回 nil,若没有指定格式将指默认按行方式进行读取。格式:

read

io.read (…)

相当于 io.input():read

io.type (obj)

检测 obj 是否一个可用的文件句柄。如果 obj 是一个打开的文件句柄,则返回 “file” 如果 obj 是一个已关闭的文件句柄,则返回 “closed file” 如果 obj 不是一个文件句柄,则返回 nil。

file:write (…)

把每一个参数的值写入文件。参数必须为字符串或数字,若要输出其它值,则需通过 tostring 或 string.format 进行转换。

io.write (…)

相当于 io.output():write。

file:seek ([whence] [, offset])

设置和获取当前文件位置,成功则返回最终的文件位置(按字节,相对于文件开头),失败则返回 nil 加错误信息。缺省时,whence 默认为 “cur”,offset 默认为 0。
参数 whence:

whence

file:setvbuf (mode [, size])

设置输出文件的缓冲模式。模式:

buf

最后两种模式,size 可以指定缓冲的大小(按字节),忽略 size 将自动调整为最佳的大小。

原文
https://moonbingbing.gitbooks.io/openresty-best-practices/content/lua/file.html

Lua 数学库由一组标准的数学函数构成。数学库的引入丰富了 Lua 编程语言的功能,同时也方便了程序的编写。常用数学函数见下表:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
print(math.pi)           -->output  3.1415926535898
print(math.rad(180)) -->output 3.1415926535898
print(math.deg(math.pi)) -->output 180

print(math.sin(1)) -->output 0.8414709848079
print(math.cos(math.pi)) -->output -1
print(math.tan(math.pi / 4)) -->output 1

print(math.atan(1)) -->output 0.78539816339745
print(math.asin(0)) -->output 0

print(math.max(-1, 2, 0, 3.6, 9.1)) -->output 9.1
print(math.min(-1, 2, 0, 3.6, 9.1)) -->output -1

print(math.fmod(10.1, 3)) -->output 1.1
print(math.sqrt(360)) -->output 18.97366596101

print(math.exp(1)) -->output 2.718281828459
print(math.log(10)) -->output 2.302585092994
print(math.log10(10)) -->output 1

print(math.floor(3.1415)) -->output 3
print(math.ceil(7.998)) -->output 8

另外使用 math.random() 函数获得伪随机数时,如果不使用 math.randomseed() 设置伪随机数生成种子或者设置相同的伪随机数生成种子,那么得得到的伪随机数序列是一样的。
示例代码:

1
2
3
4
math.randomseed (100) --把种子设置为100
print(math.random()) -->output 0.0012512588885159
print(math.random(100)) -->output 57
print(math.random(100, 360)) -->output 150

稍等片刻,再次运行上面的代码。

1
2
3
4
math.randomseed (100) --把种子设置为100
print(math.random()) -->output 0.0012512588885159
print(math.random(100)) -->output 57
print(math.random(100, 360)) -->output 150

两次运行的结果一样。为了避免每次程序启动时得到的都是相同的伪随机数序列,通常是使用当前时间作为种子。
修改上例中的代码:

1
2
3
4
math.randomseed (os.time())   --把100换成os.time()
print(math.random()) -->output 0.88369396038697
print(math.random(100)) -->output 66
print(math.random(100, 360)) -->output 228

稍等片刻,再次运行上面的代码。

1
2
3
4
math.randomseed (os.time())   --把100换成os.time()
print(math.random()) -->output 0.88946195867794
print(math.random(100)) -->output 68
print(math.random(100, 360)) -->output 129

原文

https://moonbingbing.gitbooks.io/openresty-best-practices/content/lua/math_library.html

table 库是由一些辅助函数构成的,这些函数将 table 作为数组来操作。

下标从 1 开始

在 Lua 中,数组下标从 1 开始计数。
官方解释:Lua lists have a base index of 1 because it was thought to be most friendly for non-programmers, as it makes indices correspond to ordinal element positions.
确实,对于我们数数来说,总是从 1 开始数的,而从 0 开始对于描述偏移量这样的东西有利。 而 Lua 最初设计是一种类似 XML 的数据描述语言,所以索引(index)反应的是数据在里面的位置,而不是偏移量。
在初始化一个数组的时候,若不显式地用键值对方式赋值,则会默认用数字作为下标,从 1 开始。由于在 Lua 内部实际采用哈希表和数组分别保存键值对、普通值,所以不推荐混合使用这两种赋值方式。

1
2
3
4
5
6
local color={first="red", "blue", third="green", "yellow"}
print(color["first"]) --> output: red
print(color[1]) --> output: blue
print(color["third"]) --> output: green
print(color[2]) --> output: yellow
print(color[3]) --> output: nil

从其他语言过来的开发者会觉得比较坑的一点是,当我们把 table 当作栈或者队列使用的时候,容易犯错,追加到 table 的末尾用的是 s[#s+1] = something,而不是 s[#s] = something,而且如果这个 something 是一个 nil 的话,会导致这一次压栈(或者入队列)没有存入任何东西,#s的值没有变。如果 s = { 1, 2, 3, 4, 5, 6 },你令s[4] = nil,#s 会令你“匪夷所思”地变成 3

table.getn 获取长度

取长度操作符写作一元操作 #。 字符串的长度是它的字节数(就是以一个字符一个字节计算的字符串长度)。
对于常规的数组,里面从 1 到 n 放着一些非空的值的时候, 它的长度就精确的为 n,即最后一个值的下标。 如果数组有一个“空洞” (就是说,nil 值被夹在非空值之间), 那么 #t 可能是指向任何一个是 nil 值的前一个位置的下标 (就是说,任何一个 nil 值都有可能被当成数组的结束)。这也就说明对于有“空洞”的情况,table 的长度存在一定的 不可确定性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
local tblTest1 = { 1, a = 2, 3 }
print("Test1 " .. table.getn(tblTest1))

local tblTest2 = { 1, nil }
print("Test2 " .. table.getn(tblTest2))

local tblTest3 = { 1, nil, 2 }
print("Test3 " .. table.getn(tblTest3))

local tblTest4 = { 1, nil, 2, nil }
print("Test4 " .. table.getn(tblTest4))

local tblTest5 = { 1, nil, 2, nil, 3, nil }
print("Test5 " .. table.getn(tblTest5))

local tblTest6 = { 1, nil, 2, nil, 3, nil, 4, nil }
print("Test6 " .. table.getn(tblTest6))

我们使用 Lua 5.1 和 LuaJIT 2.1 分别执行这个用例,结果如下:

$ lua test.lua
Test1 2
Test2 1
Test3 3
Test4 1
Test5 3
Test6 1
$ luajit test.lua
Test1 2
Test2 1
Test3 1
Test4 1
Test5 1
Test6 1

这一段的输出结果,就是这么 匪夷所思。请问,你以后还敢在 lua 的 table 中用 nil 值吗?如果你继续往后面加 nil,你可能会发现点什么。你可能认为你发现的是个规律。但是,你千万不要认为这是个规律,因为这是错误的。
不要在 lua 的 table 中使用 nil 值,如果一个元素要删除,直接 remove,不要用 nil 去代替。

table.concat (table [, sep [, i [, j ] ] ])

对于元素是 string 或者 number 类型的表 table,返回 table[i]..sep..table[i+1] ··· sep..table[j] 连接成的字符串。填充字符串 sep 默认为空白字符串。起始索引位置 i 默认为 1,结束索引位置 j 默认是 table 的长度。如果 i 大于 j,返回一个空字符串。

1
2
3
4
5
local a = {1, 3, 5, "hello" }
print(table.concat(a)) -- output: 135hello
print(table.concat(a, "|")) -- output: 1|3|5|hello
print(table.concat(a, " ", 4, 2)) -- output:
print(table.concat(a, " ", 2, 4)) -- output: 3 5 hello

table.insert (table, [pos ,] value)

在(数组型)表 table 的 pos 索引位置插入 value,其它元素向后移动到空的地方。pos 的默认值是表的长度加一,即默认是插在表的最后。

1
2
3
4
5
6
7
8
9
10
local a = {1, 8}             --a[1] = 1,a[2] = 8
table.insert(a, 1, 3) --在表索引为1处插入3
print(a[1], a[2], a[3])
table.insert(a, 10) --在表的最后插入10
print(a[1], a[2], a[3], a[4])

-->output
3 1 8
3 1 8 10

table.maxn (table)

返回(数组型)表 table 的最大索引编号;如果此表没有正的索引编号,返回 0。
当长度省略时,此函数通常需要 O(n) 的时间复杂度来计算 table 的末尾。因此用这个函数省略索引位置的调用形式来作 table 元素的末尾追加,是高代价操作。

1
2
3
4
5
6
7
8
9
local a = {}
a[-1] = 10
print(table.maxn(a))
a[5] = 10
print(table.maxn(a))

-->output
0
5

此函数的行为不同于 # 运算符,因为 # 可以返回数组中任意一个 nil 空洞或最后一个 nil 之前的元素索引。当然,该函数的开销相比 # 运算符也会更大一些。

table.remove (table [, pos])

在表 table 中删除索引为 pos(pos 只能是 number 型)的元素,并返回这个被删除的元素,它后面所有元素的索引值都会减一。pos 的默认值是表的长度,即默认是删除表的最后一个元素。

1
2
3
4
5
6
7
8
9
10
11
12
local a = { 1, 2, 3, 4}
print(table.remove(a, 1)) --删除速索引为1的元素
print(a[1], a[2], a[3], a[4])

print(table.remove(a)) --删除最后一个元素
print(a[1], a[2], a[3], a[4])

-->output
1
2 3 4 nil
4
2 3 nil nil

table.sort (table [, comp])

按照给定的比较函数 comp 给表 table 排序,也就是从 table[1] 到 table[n] ,这里 n 表示 table 的长度。 比较函数有两个参数,如果希望第一个参数排在第二个的前面,就应该返回 true,否则返回 false。 如果比较函数 comp 没有给出,默认从小到大排序。

1
2
3
4
5
6
7
8
9
10
11
12
13
local function compare(x, y) --从大到小排序
return x > y --如果第一个参数大于第二个就返回true,否则返回false
end

local a = { 1, 7, 3, 4, 25}
table.sort(a) --默认从小到大排序
print(a[1], a[2], a[3], a[4], a[5])
table.sort(a, compare) --使用比较函数进行排序
print(a[1], a[2], a[3], a[4], a[5])

-->output
1 3 4 7 25
25 7 4 3 1

table 其他非常有用的函数

LuaJIT 2.1 新增加的 table.new 和 table.clear 函数是非常有用的。前者主要用来预分配 lua table 空间,后者主要用来高效的释放 table 空间,并且它们都是可以被 JIT 编译的。具体可以参考一下 OpenResty 捆绑的 lua-resty-* 库,里面有些实例可以作为参考。

原文

https://moonbingbing.gitbooks.io/openresty-best-practices/content/lua/table_library.html

Lua 字符串库包含很多强大的字符操作函数。字符串库中的所有函数都导出在模块 string 中。在 Lua 5.1 中,它还将这些函数导出作为 string 类型的方法。这样假设要返回一个字符串转的大写形式,可以写成 ans = string.upper(s) ,也能写成 ans = s:upper()。为了避免与之前版本不兼容,此处使用前者。
Lua 字符串总是由字节构成的。Lua 核心并不尝试理解具体的字符集编码(比如 GBK 和 UTF-8 这样的多字节字符编码)。
需要特别注意的一点是,Lua 字符串内部用来标识各个组成字节的下标是从 1 开始的,这不同于像 C 和 Perl 这样的编程语言。这样数字符串位置的时候再也不用调整,对于非专业的开发者来说可能也是一个好事情,string.sub(str, 3, 7) 直接表示从第三个字符开始到第七个字符(含)为止的子串。

string.byte(s[,i[,j]])

返回字符 s[i]、s[i + 1]、s[i + 2]、······、s[j] 所对应的 ASCII 码。i 的默认值为 1,即第一个字节,j 的默认值为 i 。

1
2
3
4
5
6
7
8
print(string.byte("abc", 1, 3))
print(string.byte("abc", 3)) -- 缺少第三个参数,第三个参数默认与第二个相同,此时为 3
print(string.byte("abc")) -- 缺少第二个和第三个参数,此时这两个参数都默认为 1

-->output
97 98 99
99
97

由于 string.byte 只返回整数,而并不像 string.sub 等函数那样(尝试)创建新的 Lua 字符串, 因此使用 string.byte 来进行字符串相关的扫描和分析是最为高效的,尤其是在被 LuaJIT 2 所 JIT 编译之后。

string.char (…)

接收 0 个或更多的整数(整数范围 :0~255),返回这些整数所对应的 ASCII 码字符组成的字符串。当参数为空时,默认是一个 0。

1
2
3
4
5
6
7
8
9
print(string.char(96, 97, 98))
print(string.char()) -- 参数为空,默认是一个0,
-- 你可以用string.byte(string.char())测试一下
print(string.char(65, 66))

--> output
`ab

AB

此函数特别适合从具体的字节构造出二进制字符串。这经常比使用 table.concat 函数和 .. 连接运算符更加高效。

string.upper(s)

接收一个字符串 s,返回一个把所有小写字母变成大写字母的字符串。

string.lower(s)

接收一个字符串 s,返回一个把所有大写字母变成小写字母的字符串。

string.len(s)

接收一个字符串,返回它的长度。

使用此函数是不推荐的。应当总是使用` # 运算符来获取 Lua 字符串的长度
由于 Lua 字符串的长度是专门存放的,并不需要像 C 字符串那样即时计算,因此获取字符串长度的操作总是 O(1) 的时间复杂度。

string.find(s, p [, init [, plain]])

在 s 字符串中第一次匹配 p 字符串。若匹配成功,则返回 p 字符串在 s 字符串中出现的开始位置和结束位置;若匹配失败,则返回 nil。 第三个参数 init 默认为 1,并且可以为负整数,当 init 为负数时,表示从 s 字符串的 string.len(s) + init 索引处开始向后匹配字符串 p 。 第四个参数默认为 false,当其为 true 时,只会把 p 看成一个字符串对待。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
local find = string.find
print(find("abc cba", "ab"))
print(find("abc cba", "ab", 2)) -- 从索引为2的位置开始匹配字符串:ab
print(find("abc cba", "ba", -1)) -- 从索引为7的位置开始匹配字符串:ba
print(find("abc cba", "ba", -3)) -- 从索引为6的位置开始匹配字符串:ba
print(find("abc cba", "(%a+)", 1)) -- 从索引为1处匹配最长连续且只含字母的字符串
print(find("abc cba", "(%a+)", 1, true)) --从索引为1的位置开始匹配字符串:(%a+)

-->output
1 2
nil
nil
6 7
1 3 abc
nil

对于 LuaJIT 这里有个性能优化点,对于 string.find 方法,当只有字符串查找匹配时,是可以被 JIT 编译器优化的,有关 JIT 可以编译优化清单,大家可以参考 http://wiki.luajit.org/NYI ,性能提升是非常明显的,通常是 100 倍量级。 这里有个的例子,大家可以参考 https://groups.google.com/forum/m/#!topic/openresty-en/rwS88FGRsUI。

string.format(formatstring, …)

按照格式化参数 formatstring,返回后面 … 内容的格式化版本。编写格式化字符串的规则与标准 c 语言中 printf 函数的规则基本相同:它由常规文本和指示组成,这些指示控制了每个参数应放到格式化结果的什么位置,及如何放入它们。一个指示由字符 % 加上一个字母组成,这些字母指定了如何格式化参数,例如 d 用于十进制数、x 用于十六进制数、o 用于八进制数、f 用于浮点数、s 用于字符串等。在字符%和字母之间可以再指定一些其他选项,用于控制格式的细节。

1
2
3
4
5
6
7
8
9
print(string.format("%.4f", 3.1415926))     -- 保留4位小数
print(string.format("%d %x %o", 31, 31, 31))-- 十进制数31转换成不同进制
d = 29; m = 7; y = 2015 -- 一行包含几个语句,用;分开
print(string.format("%s %02d/%02d/%d", "today is:", d, m, y))

-->output
3.1416
31 1f 37
today is: 29/07/2015

string.match(s, p [, init])

在字符串 s 中匹配(模式)字符串 p,若匹配成功,则返回目标字符串中与模式匹配的子串;否则返回 nil。第三个参数 init 默认为 1,并且可以为负整数,当 init 为负数时,表示从 s 字符串的 string.len(s) + init 索引处开始向后匹配字符串 p。

1
2
3
4
5
6
7
8
9
10
11
print(string.match("hello lua", "lua"))
print(string.match("lua lua", "lua", 2)) --匹配后面那个lua
print(string.match("lua lua", "hello"))
print(string.match("today is 27/7/2015", "%d+/%d+/%d+"))

-->output
lua
lua
nil
27/7/2015

string.match 目前并不能被 JIT 编译,应 尽量 使用 ngx_lua 模块提供的 ngx.re.match 等接口。

string.gmatch(s, p)

返回一个迭代器函数,通过这个迭代器函数可以遍历到在字符串 s 中出现模式串 p 的所有地方。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
s = "hello world from Lua"
for w in string.gmatch(s, "%a+") do --匹配最长连续且只含字母的字符串
print(w)
end

-->output
hello
world
from
Lua


t = {}
s = "from=world, to=Lua"
for k, v in string.gmatch(s, "(%a+)=(%a+)") do --匹配两个最长连续且只含字母的
t[k] = v --字符串,它们之间用等号连接
end
for k, v in pairs(t) do
print (k,v)
end

-->output
to Lua
from world

此函数目前并不能被 LuaJIT 所 JIT 编译,而只能被解释执行。应 尽量 使用 ngx_lua 模块提供的 ngx.re.gmatch 等接口。

string.rep(s, n)

返回字符串 s 的 n 次拷贝。

string.sub(s, i [, j])

返回字符串 s 中,索引 i 到索引 j 之间的子字符串。当 j 缺省时,默认为 -1,也就是字符串 s 的最后位置。 i 可以为负数。当索引 i 在字符串 s 的位置在索引 j 的后面时,将返回一个空字符串。

1
2
3
4
5
6
7
8
9
10
print(string.sub("Hello Lua", 4, 7))
print(string.sub("Hello Lua", 2))
print(string.sub("Hello Lua", 2, 1)) --看到返回什么了吗
print(string.sub("Hello Lua", -3, -1))

-->output
lo L
ello Lua

Lua

如果你只是想对字符串中的单个字节进行检查,使用 string.char 函数通常会更为高效。

string.gsub(s, p, r [, n])

将目标字符串 s 中所有的子串 p 替换成字符串 r。可选参数 n,表示限制替换次数。返回值有两个,第一个是被替换后的字符串,第二个是替换了多少次。

1
2
3
4
5
6
print(string.gsub("Lua Lua Lua", "Lua", "hello"))
print(string.gsub("Lua Lua Lua", "Lua", "hello", 2)) --指明第四个参数

-->output
hello hello hello 3
hello hello Lua 2

此函数不能为 LuaJIT 所 JIT 编译,而只能被解释执行。一般我们推荐使用 ngx_lua 模块提供的 ngx.re.gsub 函数。

string.reverse (s)

接收一个字符串 s,返回这个字符串的反转。

1
print(string.reverse("Hello Lua"))  --> output: auL olleH

原文地址

https://moonbingbing.gitbooks.io/openresty-best-practices/content/lua/string_library.html

从 Lua 5.1 语言添加了对模块和包的支持。一个 Lua 模块的数据结构是用一个 Lua 值(通常是一个 Lua 表或者 Lua 函数)。一个 Lua 模块代码就是一个会返回这个 Lua 值的代码块。 可以使用内建函数 require() 来加载和缓存模块。简单的说,一个代码模块就是一个程序库,可以通过 require 来加载。模块加载后的结果通过是一个 Lua table,这个表就像是一个命名空间,其内容就是模块中导出的所有东西,比如函数和变量。require 函数会返回 Lua 模块加载后的结果,即用于表示该 Lua 模块的 Lua 值。

require 函数

Lua 提供了一个名为 require 的函数用来加载模块。要加载一个模块,只需要简单地调用 require “file”就可以了,file 指模块所在的文件名。这个调用会返回一个由模块函数组成的 table ,并且还会定义一个包含该 table 的全局变量。
在 Lua 中创建一个模块最简单的方法是:创建一个 table ,并将所有需要导出的函数放入其中,最后返回这个 table 就可以了。相当于将导出的函数作为 table 的一个字段,在 Lua 中函数是第一类值,提供了天然的优势。

1
2
3
4
5
6
7
8
9
10
11
12
local foo={}

local function getname()
return "Lucy"
end

function foo.greeting()
print("hello " .. getname())
end

return foo

调用

1
2
local fp = require("my")
fp.greeting() -->output: hello Lucy

注:对于需要导出给外部使用的公共模块,处于安全考虑,是要避免全局变量的出现。我们可以使用 lua-releng 工具完成全局变量的检测,具体参考 lua 的 局部变量 章节。

原文

https://moonbingbing.gitbooks.io/openresty-best-practices/content/lua/module.html