Nginx 基本配置介绍与静态网站上线流程

了解Nginx

Nginx是一个轻量级、高性能的HTTP和反向代理服务器,也是一个IMAP/POP3/SMTP邮件服务器。其特点是占有内存少,并发能力强,事实上nginx的并发能力确实在同类型的网页服务器中表现较好,中国大陆使用nginx网站用户有:百度、京东、新浪、网易、腾讯、淘宝等。
Nginx属于一种中间件架构,中间件有代理、分发任务的作用。在中间件的使用中,HTTP中间件使用的最多,在企业中应用广泛;常见的HTTP中间件服务有:HTTPD–Apache基金会、IIS–微软、GWS–Google、Nginx–BSD等。
Nginx支持epoll、select、kqueue等不同操作系统下的各种IO多路复用方式,下面就一个网上的小例子来通俗的介绍多路复用的含义。一个epoll场景:一个酒吧服务员(一个线程),前面趴了一群醉汉,突然一个吼一声“倒酒”(事件),你小跑过去给他倒一杯,然后随他去吧,突然又一个要倒酒,你又过去倒上,就这样一个服务员服务好多人,有时没人喝酒,服务员处于空闲状态,可以干点别的玩玩手机。至于epoll与select,poll的区别在于后两者的场景中醉汉不说话,你要挨个问要不要酒,没时间玩手机了。io多路复用大概就是指这几个醉汉共用一个服务员。
epoll 模型
如上图所示,在同一个线程里面, 通过拨开关的方式,来同时传输多个I/O流,仔细理解上面这个图就知道了epoll类型的IO多路复用含义。

Nginx的快速安装

Nginx兼容Linux系统,Windows NT系统,部署静态的网站需要使用一台有外网地址的电脑,或者租一台自己的云服务器,其实也不贵,建议直接在阿里云或者腾讯云上租下自己的云服务器,服务器的操作系统版本为CentOS 7.2,确定服务器的四项功能:

  • 网络可用
  • yum可用
  • 关闭iptables规则
  • 停用selinux

JavaScript设计模式:工厂模式及构造函数模式

创建对象

通过 Object 构造函数或者对象字面量都可以用来创建单个对象,但这些方法有着明显的缺点:使用同一个接口创建很多个对象,会产生大量的重复代码。为解决这样的问题,工厂模式和构造函数模式应运而生。

工厂模式

工厂模式是一种在软件工程领域内被广泛使用的一种设计模式,这种模式抽象了创建具体对象的过程。JS在 ECMAScript 中无法创建类,开发人员就发明了一种函数,用函数来封装以特定的接口创建对象的细节,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function createPerson(name, age, job) {
const obj = new Object();
obj.name = name;
obj.age = age;
obj.job = job;
obj.sayPerson = function(){
console.log("Name:"+this.name+" Age:"+this.age+" Job:"+this.job);
};
return obj;
}
const person1 = createPerson("Arrow", 35, "hero");
const person2 = createPerson("Flash", 25, "superHero");

person1.sayName(); // Name:Arrow Age:35 Job:hero
person2.sayName(); // Name:Flash Age:25 Job:superHero

函数 createPerson() 能够根据接受的参数来构建一个包含所有必要信息的 Person 对象。可以无数次的调用这个函数,而每次它都会返回一个包含三个属性和一个方法的对象。工厂模式解决了创建多个相似对象的问题,但却没有解决对象识别的问题(怎样知道一个对象的类型)。

构造函数模式

可以创建自定义的构造函数,从而定义自定义对象类型的属性和方法,将前面的例子重写成构造函的模式如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
const Person = function (name, age, job) {
this.name = name;
this.age = age;
this.job = job;
this.sayPerson = function () {
console.log("Name:"+this.name+" Age:"+this.age+" Job:"+this.job);
};
};
const person1 = new Person("Arrow", 35, "Hero");
const person2 = new Person("Flash", 25, "SuperHero");

person1.sayPerson(); // Name:Arrow Age:35 Job:hero
person2.sayPerson(); // Name:Flash Age:25 Job:superHero

上述的例子中,Person() 函数取代了 createPerson() 函数。Person() 中的代码与 createPerson() 中的相同的部分外,还存在一下的不同之处:

  • 没有显示的创建对象;
  • 直接将属性和方法赋给了 this 对象;
  • 没有 return 语句。

要创建 Person 的新实例,必须要使用 new 操作符。以这种方式调用构造函数实际上会经历以下4个步骤:
1、创建一个新对象;
2、将构造函数的作用域赋给新对象(因此 this 指向这个新对象);
3、执行构造函数中的代码(为这个新对象添加属性);
4、返回新对象。
在上述的例子中,person1和 person2分别保存着 Person的一个不同的实例。在这两个实例对象中都有一个 constructor(构造函数)属性,该属性指向 Person,如下代码所示:

1
2
console.log(person1.constructor == Person); //true
console.log(person2.constructor == Person); //true

对象的constructor属性一开始是为了标识对象类型的。但是,提到类型检测时发现,通过 instanceof来检测更为可靠一点。本例子中创建的所有对象即是 Object的实例,同时也是 Person的实例,这一点可以通过 instanceof操作符来验证。

1
2
3
4
console.log(person1 instanceof Object); //true
console.log(person1 instanceof Person); //true
console.log(person2 instanceof Object); //true
console.log(person2 instanceof Person); //true

创造自定义的构造函数意味着可以将他的实例标识为一种特定的类型,这也正是构造函数模式胜过工厂模式的地方。

将构造函数当作普通的函数

构造函数与普通函数的唯一区别就是其调用的方式不同。不过,构造函数也是函数,不存在定义构造函数的特殊语法。任何函数,只要其通过 new操作符来调用,那他就可以作为构造函数;同样,任何函数如果不通过 new操作符来调用,那么他和普通函数就没什么区别。例如,前面的例子可以通过以下的任一方式来调用。

1
2
3
4
5
6
7
8
9
10
//当作构造函数来调用
const person1 = new Person("Arrow", 35, "Hero");
person1.sayPerson(); //Name:Arrow Age:35 Job:Hero
//作为普通函数来调用
Person("SuperGirl", 22, "femaleHero");
window.sayPerson(); //Name:SuperGirl Age:22 Job:femaleHero
//在另一个对象的作用域中调用
const o = new Object();
Person.call(o, "superMan", 28, "manHero");
o.sayPerson(); //Name:superMan Age:28 Job:manHero

构造函数存在的问题

使用构造函数的主要问题,就是每个方法都需要在每个实例上重新创建一遍。在前面的例子中,person1和 person2都用一个名为 sayPerson()的方法,但两个方法并不是同一个 Function的实例。然而,创建两个完成同样任务的 Function实例的确没有必要;何况有 this对象在,根本不用在执行代码前就把函数绑定到特定对象上面。因此,可以通过把函数定义转移到构造函数外部来解决这个问题。

1
2
3
4
5
6
7
8
9
10
11
function Person(name, age, job){
this.name = name;
this.age = age;
this.job = job;
this.sayPerson = sayPerson;
}
function sayPerson(){
console.log("Name:"+this.name+" Age:"+this.age+" Job:"+this.job);
}
const person1 = new Person("Arrow", 35, "Hero");
const person2 = new Person("Flash", 25, "SuperHero");

在上述的实例中,把sayPerson()函数的定义转移到了构造函数的外部。在构造函数的内部,将sayPerson的属性设置成了全局的 sayPerson()函数。由于 sayPerson包含的是一个指向函数的指针,因此 person1和 person2对象就共享了在全局作用域中定义的同一个sayPerson()函数。这样做的确解决了函数共享的问题,但出现了新的问题:如果对象需要定义许多方法,那么就要定义很多个全局函数,于是自定义的引用类型就丝毫没有封装性可言了。好在,这些问题可以通过原型模式来解决。

JavaScript设计模式:单例模式

前言

总是想绕却始终绕不开的设计模式,决定花点时间弄明白,先从单例模式开始。在开发工程师眼里,单例就是保证一个类只有一个实例,实现的方法一般是先判断实例存在与否,如果存在直接返回,如果不存在就创建了再返回,这就确保了一个类只有一个实例对象。在JavaScript里,单例作为一个命名空间提供者,从全局命名空间里提供一个唯一的访问点来访问该对象。

定义

单例模式之所以这么叫,是因为它限制一个类只能有一个实例化对象。经典的实现方式是,创建一个类,这个类包含一个方法,这个方法在没有对象存在的情况下,将会创建一个新的实例对象。如果对象存在,这个方法只是返回这个对象的引用。如果创建多个对象,一定记住,除了第一个对象之外,其他的对象均是此对象的引用,这样来理解单例模式就容易的多。

实现

在JavaScript语言中, 单例服务作为一个从全局空间的代码实现中隔离出来共享的资源空间是为了提供一个单独的函数访问指针。

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
const mySingleton = (function (){
let instance = null;
function init(){
// 私有的成员函数和私有变量
function privateMethod(){
console.log("I am a private Method.");
}
const private_var = "I am a private data.";
const private_num = Math.random();
return {
// return出去的函数是属于公有的方法
publicMethod: function (){
console.log("Public can see me.");
privateMethod();
},
getRandomNumber:function(){
return private_num;
},
public_var: "I am a public data.",
}
}

return {
getInstance: function(){
if(!instance){
instance = init();
}
return instance;
}
}
})();
// 调用
const A_obj = mySingleton.getInstance();
const B_obj = mySingleton.getInstance();
console.log(A_obj.getRandomNumber() === B_obj.getRandomNumber()); //控制台打印true,说明B_obj只是对A_obj的一个引用

console.log(A_obj.public_var); //访问return对象中的公有对象,可访问;若直接访问initial()中的私有变量,则报错。
A_obj.publicMethod(); //访问公有的成员函数,成员函数中的私有变量或者私有函数只可以通过共由的方法来访问,保证了封装性。

注释

在上述代码的调用阶段,A_obj 和B_obj 均是通过mySingleton调用了getInstance()方法获取的,在该方法中我只知道是通过判断是否存在一个实例对象再去确定是否创建新的对象,有人可能会误以为这是导致其余对象是实例对象的引用,其实不然,若将函数的代码段修改为始终初始化对象,会发现结果返回的仍然是true,这也证实了单例模式只有一个实例对象的原理。

拓展

在四人帮(GoF)的书里面,单例模式的应用描述如下:

  • 每个类只有一个实例,这个实例必须通过一个广为人知的接口,来被客户访问。
  • 子类如果要扩展这个唯一的实例,客户可以不用修改代码就能使用这个扩展后的实例。

关于第二点,可以参考如下的实例,我们需要这样编码:

1
2
3
4
5
6
7
8
9
10
mySingleton.getInstance = function(){
if(!this.instance){
if(Math.random()>0.5){
this.instance = this.init();
}else{
this.instance = this.init_2();
}
return this.instance;
}
};

总结

nodeJS 中的stream对象

前言

如果你是一名当今世界的前端开发者,那么流一定是一个你需要掌握的概念。如果你想成为一个前端开发高手,那么流一定是武功秘籍中不可缺少的一个部分。本文中的部分内容来自GitHub上的开源社区。

为什么要使用流,流到底是什么

“流(stream)在 Node.js 中是处理流数据的抽象接口(abstract interface)。 stream 模块提供了基础的 API 。使用这些 API 可以很容易地来构建实现流接口的对象。Node.js 提供了多种流对象。 例如, HTTP 请求 和 process.stdout 就都是流的实例。流可以是可读的、可写的,或是可读写的。所有的流都是 EventEmitter 的实例。” 这是 nodeJS 官网上对流的一段解释,估计很多新人看到这个定义是一脸懵逼,下面我们通过一个简单的例子来慢慢解释。
定义中我们知道http请求是流的实例,是一个可读的、可写的流,我们以http服务器的例子来说明,先看下面的例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const http = require('http');
const fs = require('fs');

const server = http.createServer((req,res)=>{
fs.readFile(__dirname+"/data.txt",(err,data)=>{
if(err) throw err;
res.end(data);
});
});
server.on("connection",()=>{
console.log("Someone connected.");
});

server.listen(5566);

上述例子创建了一个简单的服务器,当接收到请求是,响应一个data.txt文件,文件的内容有一百万行,在没有使用流的情况下,需要将文件中的所有内容读取完成之后才可以进行发送数据,这大大的损耗了内存资源,在客户端的体验也是非常糟糕的,需要等待服务器端完成所有data.txt文件的读取之后才能响应。下面引入流的实例

1
2
3
4
5
6
7
8
9
10
11
12
const http = require('http');
const fs = require('fs');

const server = http.createServer((req,res)=>{
const reader = fs.createReadStream(__dirname+"/data.txt");
reader.pipe(res);
});
server.on("connection",()=>{
console.log("Someone connected.");
});

server.listen(5588);

通过fs模块创建一个可读的流,用来读取data.txt中的一百万行数据,在将读取到的数据流 pipe 到 响应结果的 res 中,这样客户端发起请求的时候就大大减少了等待的时间,服务器端的数据源源不断的 pipe 到响应中去。在这里,.pipe()方法会自动帮助我们监听data和end事件。上面的这段代码不仅简洁,而且data.txt文件中每一小段数据都将源源不断的发送到客户端。除此之外,使用.pipe()方法还有别的好处,比如说它可以自动控制后端压力,以便在客户端连接缓慢的时候node可以将尽可能少的缓存放到内存中。

.pipe()的使用

无论哪一种流,都会使用.pipe()方法来实现输入和输出。.pipe()函数很简单,它仅仅是接受一个源头src并将数据输出到一个可写的流dst中:

1
src.pipe(dst)

.pipe(dst) 将会返回 dst ,因此你可以链式调用多个流:

1
a.pipe(b).pipe(c).pipe(d)

上述的代码等同于以下:

1
2
3
a.pipe(b)
b.pipe(c)
c.pipe(d)

介绍了 src.pipe(dst) 的用法之后,我们需要知道不是所有的属性的流都可以有 .pipe() 方法的,只有可读的流才可以调用该方法,举个例子就如上述的链式调用可以实现就必须要求 b,c,d 本身即是一个可读的流也是一个可写的流,一句话就是,可读的流可以产生数据,再 pipe 到一个可写的流中去。就相当于源源不断的从可读的流文件中读取数据,再源源不断的写入一个可写的流文件中去,实现流最强大的功能。

创建并使用流

说完了流的用法之后,如何创建流是接下来讨论的问题。nodeJS中的 stream 模块可以通过以下方式引入:

1
const stream = require("stream")

尽管所有的 Node.js 用户都应该理解流的工作方式,这点很重要, 但是 stream 模块本身只对于那些需要创建新的流的实例的开发者最有用处。 对于主要是 消费流 的开发者来说,他们很少(如果有的话)需要直接使用 stream 模块。在nodeJS中的fs模块中就封装了很好用的模块,下面的例子就是用来创建一个的可写的流和一个可读的流,并 pipe 到可写的流中去:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const fs = require('fs');
const path = require('path');

const file_r = path.join(__dirname,"/test-data.txt");
const file_w = path.join(__dirname,"/test.txt");

const reader = fs.createReadStream(file_r,(err)=>{if(err) throw err;});
const writer = fs.createWriteStream(file_w,(err)=>{if(err) throw err;});

//直接向reader的buffer中写入数据可以采用,当buffer压入null是触发 end 事件
reader.push("Arrow")
reader.push("Flash")
reader.push("Superman")
reader.push(null)

//将reader缓存中的数据和实际文件中的数据 pipe 到可写的流中去
reader.pipe(writer)

流的执行原理

Node.js 中有四种基本的流类型:
1、Readable - 可读的流 (例如fs.createReadStream())
2、Writable - 可写的流 (例如fs.createWriteStream())
3、Duplex - 可读写的流 (例如net.Socket)
4、Transform - 在读写过程中可以修改和变换数据的 Duplex 流 (例如zlib.createDeflate()).

  • Readable 和 Writable流都会将数据存储到内部的缓存中。这些缓存可以通过相应writable._writableState.getBuffer()readable._readableState.buffer来获取缓存中的内容。
  • 当可读流的实现调用 stream.push(chunk) 方法时,数据被放到缓存中。如果流的 消费者 没有调用 stream.read() 方法, 这些数据会始终存在于内部队列中,直到被消费。
  • 当内部可读缓存的大小达到 highWaterMark 指定的阈值时,流会暂停从底层资源读取数据,直到当前 缓存的数据被消费 (也就是说, 流会在内部停止调用 readable._read() 来填充可读缓存)。
  • 可写流通过反复调用 writable.write(chunk) 方法将数据放到缓存。当内部可写缓存的总大小小于 highWaterMark 指定的阈值时, 调用 writable.write() 将返回true。 一旦内部缓存的大小达到或超过 highWaterMark ,调用 writable.write() 将返回 false 。
  • stream API 的关键目标, 尤其对于 stream.pipe() 方法, 就是限制缓存数据大小,以达到可接受的程度。这样,对于读写速度不匹配的源头和目标,就不会超出可用的内存大小。

做一个Javascript网页爬虫

前言

puppeteer的出现让其他的JavaScript脚本写的爬虫程序黯然失色,puppeteer的是Google团队开发的一款针对chrome浏览器的性能极强的爬虫程序,本文中的主要内容便是基于puppeteer实现一个简单的爬虫程序,实例代码主要是爬取百度的高清壁纸,并自动化的保存到本地磁盘。

系统环境要求

NodeJS、可以使用npm、puppeteer

1、安装NodeJS

2、安装puppeteer,使用npm安装时会出现错误,主要是因为国内暂不支持chormize的下载,需要翻墙才可以;所以采用第二种办法,先使用npm安装阿里云的cnpm,命令如下

1
$ npm install cnpm -g

然后使用cnpm安装puppeteer即可,代码如下

1
$ cnpm install puppeteer -g

代码编写

1、初始化项目,首先建立空文件夹,在该目录下的控制台键入命令

1
$ npm init -y

2、此时文件目录中应该包括了package.json文件,由于全局安装了puppeteer,所以此时无须再次安装;若不想全局安装,则执行以下命令即可(生产环境可用)

1
$ cnpm install puppeteer -S

3、准备工作完成了即可以编写核心的爬虫代码了,本实例代码只为了验证爬虫的执行,就不进行结构化书写代码了,绝大部分的代码只包含在一个文件中即可,方便大家理解;为了将爬取的内容保存下来,在根目录下新建一个download文件夹;然后在根目录下新建一个main.js文件,文件内容编写代码如下

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
const puppeteer = require('puppeteer');
const path = require('path');
const dir = path.resolve(__dirname,"../download");
const fs = require('fs');

(async ()=>{
const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.goto("http://www.baidu.com",{waitUntil:'networkidle2'});
console.log("loading...");

await page.setViewport({
width:1920,
height:5080
});
console.log("set Viewport done.");

const s_def = "武侠小说";
await page.type("#kw",s_def);
await page.click("#su");
await page.waitFor(2000); //必须等待,否则浏览器不能正确刷新页面。
console.log('Search input has done.');

const file_pdf = path.join(dir,`${Date.now()}.pdf`);
const file_png = path.join(dir,`${Date.now()}.png`);
const file_txt = path.join(dir,`${Date.now()}.txt`);
const file_md = path.join(dir,`${Date.now()}.md`);

await page.pdf({
path:file_pdf,
format:"letter"
});
await page.screenshot({path:file_png,fullscreen:true});
console.log("PDF and screenCapture has Process done.");

await page.waitFor(".c-container");

const selector = ".c-container h3 a";
const text_content = await page.$$eval(selector,anchors=>anchors.map(anchor=>{
const title = anchor.innerHTML.trim();
const href = anchor.getAttribute("href");
return `${title}---${href}`;
}));

text_content.map(text=>{fs.appendFile(file_txt, text+"\n", (err) => {
if (err) throw err;
console.log('The "data" was appended to file!');
})});

const writer = fs.createWriteStream(file_md,(err)=>{if(err) throw err;});
text_content.map(text=>writer.write(text+"\n\n",(err)=>{
if(err) throw err;
console.log("mdFile has download.");
}));

await browser.close();
})();

4、执行代码,在命令行键入

1
$ node main.js

5、此时打开download目录即可看到从百度搜索中爬取到的内容了。

结尾

简单的一个写爬虫的代码,很容易实现,如需了解更多有关puppeteer的详细内容,直接到GitHub上搜索puppeteer即可,本人主要是兴趣使然,刚开始研究这玩意,也欢迎大家有问题一起交流,项目代码地址Github

Hello World About Hexo

Welcome to Hexo! This is your very first post. Check documentation for more info. If you get any problems when using Hexo, you can find the answer in troubleshooting or you can ask me on GitHub.

Quick Start

Create a new post

1
$ hexo new "My New Post"

More info: Writing

Run server

1
$ hexo server

More info: Server

Generate static files

1
$ hexo generate

More info: Generating

Deploy to remote sites

1
$ hexo deploy

More info: Deployment

Attention:

在_config.yml文件中修改deploy选项时注意需要将远程仓库的地址写成与github用户名相同的xxx.github.io,例如我的github账户名称为betali,则远程仓库的名字只能为betali.github.io