nodejs基础知识查缺补漏

1. 单线程、异步I/O、对比phpphp

  nodejs是单线程的,可是是异步I/O,对于高并发时,它也可以快速的处理请求,100万个请求也能够承担,可是缺点是很是的耗内存,可是咱们能够加大内存, 因此能用钱解决的事就不是事。 而PHP语言是使用Apache服务器的,它的方法和node是彻底不同的,虽然php也是单线程的,可是apache是支持多线程的,它的方法是派出150个线程放在线程池中,而后对于须要的程序来从线程池中取得线程,用完了以后再放回去。 显然,php作的后台在速度上是不如node的(通常状况下是这样),由于node是支持高并发的。html

  不只如此, nodejs的相较于php开发效率很是高。 这并非说它代码写的少,而是他在优化方面作得少一些就能够达到很好的效率,而php就要作大量的优化。node

  nodejs应用场景普遍, 可是目前在xp系统的某些应用还不能支持。mysql

  缺点是nodejs是很是新的, 因此可能在人才方面不够充足,不少公司不会轻易使用node, 而php有不少成熟的中间件,大量的从业人员,因此就目前来讲,php更加稳定一些。且node的IDE并吧完善。react

 

2. 框架es6

  目前用的比较多的就是express,更稳定一些。sql

  而koa也是一个node框架,可是这个框架比较新,还不够稳定,而且它是彻底基于es6的。express

  Hapi框架比较适合大型网站,入门较难。apache

  sails框架也不错,是基于express的。npm

 

3. 咱们使用下面的代码建立服务器时,会发现每次访问了一次,却console.log两次,以下所示:

var http = require("http");
http.createServer(function (req, res) {
            res.writeHead(200, {"Content-Type": "text/html"});
            console.log("访问");
            res.write("Hello world!");
            res.end();
}).listen(8081);

console.log("Server running at http:127.0.0.1:8081");

注意1: 其中的end()表示response结束,若是不添加,就会发现浏览器页面上会一直转圈,这其实是由于没有end因此服务器一直在等待接收资源,正如咱们打开一个资源较多的网站,若是资源没有加载完成,那么就一直会转圈。。。另外,在res.end()中能够输出一些内容到页面上,也能够不输出。

注意2: 咱们能够console.log("访问")应该在dos只有一次,可是每当咱们刷新的时候就会发现同时出现了两次,这实际上能够认为是nodejs的bug,咱们须要经过添加一句话来解决这个问题(在express框架中的底层中已经解决了这个问题,可是咱们使用原生nodejs的时候仍是须要注意的)。

var http = require("http");
http.createServer(function (req, res) {
            res.writeHead(200, {"Content-Type": "text/html"});
            if (req.url !== "/favicon.ico") { // 清除第二次访问
                console.log("访问");
                res.write("Hello world!");
          res.end(); } }).listen(
8081); console.log("Server running at http:127.0.0.1:8081");

如此,就能够解决问题了。(注意:必定是req.url !== "/favicon.ico")

 

 

4. 模块调用(类的继承)

  下面咱们解决这样一个问题,在当前目录创建一个server.js做为node后台,而后在当前目录下再建立一个modules目录,在modules目录下面建立三个模块: User、Teacher、Student。其中后二者继承前者的。前者包含最基本的id、name和age,以及一个enter方法,表示进入,在后台中打印出来。 Teacher在继承了User的基础上,又有本身的方法teach。 一样,Student在继承了User的基础上,也有本身的方法teach。

  因为ji是弱类型的,因此继承相对宽松一些,不像C++同样,有公有的、私有的、受保护的等等。 总体的架构以下:

 

 其中User.js内容以下:

function User(id, name, age) {
    this.id = id;
    this.name = name;
    this.age = age;
    this.enter = function () {
        console.log(this.name + "进入系统");
    }
}
module.exports = User;

即这是一个构造函数, 而后由于就一个,因此直接使用module.exports = User;便可。

 

其中Teacher.js代码以下所示:

var User = require("./User.js");

function Teacher(id, name, age) {
    User.apply(this, [id, name, age]);
    this.teach = function (res) {
        res.write(this.name + "正在上课");
    }
}
module.exports = Teacher;

能够看到,这里咱们使用借用构造函数的方法,这与原型继承是不一样的,另外,我给Teacher类又添加了一个本身的方法teach,并须要调用参数res。 

 

相似的,Student.js代码以下所示:

var User = require("./User");

function Student(id, name, age) {
    User.apply(this, [id, name, age]);
    this.learn = function (res) {
        res.write(this.name + "正在学习");
    }
}

module.exports = Student;

 

server.js以下所示:

var http = require("http");
var User = require("./modules/User");
var Teacher = require("./modules/Teacher");
var Student = require("./modules/Student");

http.createServer(function (req, res) {
    res.writeHead(200, {"Content-Type": "text/html; charset=utf8"});
    if (req.url !== "/favicon.ico") {
        var student = new Student(1, "John Zhu", 21);
        console.log(student.enter());
        student.learn(res);
        res.end();
    }
}).listen(8000);

console.log("Server is running at http://127.0.0.1:8000");

即将这几个模块都引入了进来,而后经过初始化构造函数建立实例, 最后调用了一些方法。 

这个例子就展现了类与继承、模块引入等方法。

 

5. nodejs路由

这是一个重要的概念,以前一直都没有很好的理解,因此这里着重整理。

其实很简单,就是输入 http://127.0.0.1:8000/login, 那么服务端就返回与login相关的respond。  方法就是经过在server.js中拿到url,在拿到login,而后找到对应的js文件,拿到对应的方法,再将相应的文件显示到页面上便可。

话很少说,举例以下:

router.js以下:

module.exports = {
    login: function (req, res) {
        res.write("进入login页面");
    },
    register: function (req, res) {
        res.write("进入register页面");
    }
};

server.js以下:

var http = require("http");
var url = require("url");
var router = require("./router");

http.createServer(function (req, res) {
    res.writeHead(200, {"Content-Type": "text/html; charset=utf8"});
    if (req.url !== "/favicon.ico") {
        var path = url.parse(req.url).pathname;
        path = path.replace(/\//, "");
        router[path](req, res);
        res.end();
    }
}).listen(8888);

console.log("Server is running at http://127.0.0.1:8888");

 

即引入路由模块,而后使用url模块中的parser方法解析url获得用户输入的path,并使用正则替换没必要要的/,最后再利用js中的[]来调用方法,达到路由的目的。

 

 

 

6. nodejs文件读取

nodejs中文件读取有两种,一种是同步,一种是异步,同步是指读完以后再作下一件事情,异步是不等读完就作下一件事情,相似于又开了一个线程。

同步执行很是简单,就是执行完一个执行另外一个,下面主要看同步会出现的问题,以下:

server.js以下:

var http = require("http");
var url = require("url");
var optfile = require("./modules/optfile");

http.createServer(function (req, res) {
    res.writeHead(200, {"Content": "utf8; charset=utf8"});
    if (req.url !== "/favicon.ico") {
        function recall(data) {
            res.write(data);
            res.end();
        }
        optfile.readfile("./view/login.html", recall);
        console.log("主程序执行完毕");
    }
}).listen(8888);

console.log("server is running at 127.0.0.1:8888");

 

而后optfile.js以下:

var fs = require("fs");

module.exports = {
    readfile: function (path, recall) {
        fs.readFile(path, function (err, data) {
            if (err) {
                console.log(err);
            } else {
                recall(data);
            }
        });
        console.log("异步方法执行完毕!");
    },
    readfileSync: function (path) {
        var data = fs.readFileSync(path,"utf-8");
        console.log(data.toString());
        console.log("同步方法执行完毕");
    }
};

 

login.html以下:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>login</title>
</head>
<body>
    <p>login.html</p>
</body>
</html>

 

最终运行输出以下:

为何"server is running at 127.0.0.1:8888"老是最早打出来?这是由于在node中全部的api都是异步的,因此http.createServer也是异步的,因而不等执行完, 就跳过去执行console.log("server is running at 127.0.0.1:8888");了.

 

另外,为何异步方法执行完毕在主程序执行完毕以前呢? 

由于虽然readFile是异步的,可是咱们本身建立的readfileSync确是同步的,只有在console.log("异步。。")执行完毕,这个同步的才执行完毕,因此console.log('同步。。')才会以后输出。

 

问题: 为何必定要使用闭包呢? 若是我不传入recall,而只是将res.write()和res.end()下载optfile.js中else下面可行吗?

不可行!!!

由于进入readfile(这是同步的)以后,readFile是一个异步的,因此当即执行了console.log("异步。。")。而后栈退回到了http.createServer,这是这个函数也当即执行完毕,因此res做为局部变量就当即被释放了,而这是处在另外一个线程上读完文件后,可能再用res,就已经被销毁了,而不能找到。

可是使用了闭包以后就不同了,使用了闭包,就会对res有一个引用,即便是在函数执行完以后,也不会释放res。 

可我直接将res.write()和res.end()放在optfile.js中else下面时,也是在一个函数里啊,而后保存了对外层函数中的引用,这难道就不是闭包吗???  

这里咱们能够先粗暴的理解,闭包必须是同步的,而不能是异步的

 

 

7. 读取并显示图片

var http = require("http");
var fs = require("fs");

http.createServer(function (req, res) {
    res.writeHead(200, {"Content-Type": "image/jpeg"});
    if (req.url !== "/favicon.ico") {
        fs.readFile("./pig.jpg", "binary", function (err, data) {
            if (err) {
                console.log(err);
            } else {
                res.write(data, "binary");
                res.end();
            }
        });
    }
}).listen(8888);

console.log("Server is running at http://127.0.0.1:8888");

 

这里区别并不大,以前使用的是text/plain,而这里使用的是image/jpeg,虽然,最终咱们读取的是jpg图片,可是即便咱们读取png图片,最后也是能够的。

注意,在readFile中的第二个参数中,是接受编码的类型,普通的咱们也能够写成"utf-8", 当是读取图片时 ,就要写成"binary"。

通过尝试,咱们能够发现Content-Type的值也能够是image/png或者是image/jpg都是能够的。可是若是换成其余的就会出问题了。

另外,若是除了图片,还输出了文字,就会报错,下面就会讲述若是图文混排。

 

 

8. nodejs异常处理

即若是咱们的路由出现问题时,后台就会崩溃,这就是异常。 

处理异常的方式也很简单。

对于同步的方法就是使用try-catch语句,把有可能出错的代码放在try中,而后catch到错误后处理错误。也能够根据状况,在try失败时,throw出来,而后再catch。

而对于异步方法能够直接在自身的err下处理异常就能够了。 即对于可能使得后台崩溃的地方使用异常处理。

 

9. 异步流程控制

第一步:读文件, 第二步: 写入磁盘, 第三步: 入库。

在其余的后台语言中,这三步是一个执行完成另一个再执行,可是在node中,因为默认全部的api都是异步的,因此这三个步骤是一块儿并发运行的。可是咱们须要的是第一步完成后才能实用该结果运行第二步,第二步完成以后才能实用结果完成第三步。。。。

那么这个问题怎么解决呢?  在node中,每每最后一个参数是一个回调函数,因此能够在作完第一步以后再将第二步做为一个回调函数去执行,而后第二步执行完成以后,去完成做为第二步的回调函数的第三步。。。。这就是所谓了回调地狱了。

这只是三个步骤,若是去写嵌套,就会发现十分的不友好, 因此咱们须要的是有一种处理机制来解决这个问题。

 

首先,咱们分析一个多个函数运行可能会出现的状况:

  • 串行无关联 --- 即一个执行完成以后再执行另外一个,可是这几个之间的执行结果都不会被下一个利用。 
  • 并行无关联 --- 即全部的函数并行执行,谁先执行完是不知道的,关键是谁先执行完都对结果没有影响。
  • 串行有关联 --- 即咱们再上面的问题中所须要的一种执行模式。 一个执行完,才能执行下一个,而且这个执行完的结果是做为下一个函数执行所须要的。
  • 并行限制 --- 即不少函数并行执行,可是咱们能够限制每次并行执行的数量, 如限制每次只有2个函数能够并行执行。

前面三个是比较重要的,因此咱们先学习前面三个,就要用到nodejs中的async。 可是这个不是node核心所包含的。因此咱们须要在一个文件中引入async, 即:

npm install async --save-dev

 

 可是不知是什么问题。。。 不能安装成功,因此仍是使用淘宝的镜像吧。。。

cnpm install async --save-dev

 

安装完成以后,就能够发现package.json中已经包含了这个依赖项:

"devDependencies": {
    "async": "^2.3.0"
  }

 

 

咱们首先看一下两个函数并行执行的状况:

function oneFun() {
    ii = 0;
    setInterval(function () {
        ii++;
        if (ii == 3) {
            clearInterval(this);
        }
        console.log("aaa" + new Date());
    });
    console.log("oneFun");
}
function twoFun() {
    jj = 0;
    setInterval(function () {
        jj++;
        if (jj == 3) {
            clearInterval(this);
        }
        console.log("bbb" + new Date());
    });
    console.log("twoFun");
}
oneFun();
twoFun();
console.log("执行完毕");

 

输出结果以下:

oneFun
twoFun
执行完毕
aaaSat Apr 15 2017 11:21:40 GMT+0800 (中国标准时间)
bbbSat Apr 15 2017 11:21:40 GMT+0800 (中国标准时间)
aaaSat Apr 15 2017 11:21:40 GMT+0800 (中国标准时间)
bbbSat Apr 15 2017 11:21:40 GMT+0800 (中国标准时间)
aaaSat Apr 15 2017 11:21:40 GMT+0800 (中国标准时间)
bbbSat Apr 15 2017 11:21:40 GMT+0800 (中国标准时间)

 

因为setInterval()是异步的,因此oneFun和twoFun很快就执行结束,而后因为两个setInterval异步,因此他们并发执行,能够看到他们是接替执行的,aaa、bbb、aaa、bbb、aaa、bbb。。。

注意:这里使用clearInterval(this);就能够清楚当前的计时器。

 

可是,若是咱们但愿在oneFun执行完了以后再执行twoFun呢? 因为setTimeout()是异步的,通常方法咱们就只能用下面的方法来实现,以下:

function oneFun() {
    ii = 0;
    setInterval(function () {
        ii++;
        if (ii == 3) {
            clearInterval(this);
            twoFun();
        }
        console.log("aaa" + new Date());
    });
    console.log("oneFun");
}
function twoFun() {
    jj = 0;
    setInterval(function () {
        jj++;
        if (jj == 3) {
            clearInterval(this);
        }
        console.log("bbb" + new Date());
    });
    console.log("twoFun");
}
oneFun();
console.log("执行完毕");

 

 可是不难理解,这样的作法会很难管理,而且这还只是两个异步的控制,若是有多个,简直就会疯掉~

 

因而async就能够用获得了。。。

首先咱们使用async完成下面的串行无关联,利用async.series。 

var async = require("async");

function exec() {
    async.series(
        {
            one: function (done) {
                ii = 0;
                setInterval(function () {
                    ii++;
                    console.log("aaa" + new Date());
                    if (ii == 3) {
                        clearInterval(this);
                        done(null, "onefun执行完毕");
                    }
                });
            },
            two: function (done) {
                jj = 0;
                setInterval(function () {
                    jj++;
                    console.log("bbb" + new Date());
                    if (jj == 3) {
                        clearInterval(this);
                        done(null, "onefun执行完毕");
                    }
                });
            },
            three: function (done) {
                done(null, "全部程序执行完毕");
            }
        },function (err, rs) {
            console.log(err);
            console.log(rs);
        }
    );
}
exec();

 

即实现将async模块引入,而后利用async.series()方法,即串行无关联,这个方法接受两个参数,第一个参数是一个对象,对象中能够定义咱们但愿串行执行的方法,而后第二个参数是一个回调函数,这个回调函数要在第一个参数的方法中做为回调。 指的注意的是: 这里只有第一个参数的每个方法直到执行完了done();而且done()的第一个参数是null(即没有出错),才会执行下一个方法, 这样就能够实现串行无关联了。

结果以下所示:
 

能够看到aaa所有打印出来以后,才打印的bbb,而后最后输出了一个对象说明了执行状况。 

注意: 前面的one和two中的两个done是用来告知下一个方法:我执行完了,你能够执行了。 而最后一个done才是用来提示的,若是最后一个done没有回调,固然也能够串行执行,由于他是最后一个了,可是就不会返回这个对象了。

 

下面再介绍一下并行无关联,很简单,只须要将上面的async修改成parallel便可。最后的输出结果以下:

能够看到,a和b显然是并行执行的,从最后一句能够看到three是最早完成的,由于并行状况下执行时间最短的最早完成。  

注意: 这里因为是并行执行,因此done()的做用就不是控制后续进程的进行了,而只是说明是否执行了。

 

最后,咱们介绍一个 串行有关联 的实现, 即几个异步的函数,咱们让他们串行执行,而且在一个执行完了以后将结果传输给下一个要执行的函数利用,以下所示:

var async = require("async");

function exec() {
    async.waterfall(
        [
            function (done) {
                ii = 0;
                setInterval(function () {
                    ii++;
                    console.log("aaa" + new Date());
                    if (ii == 3) {
                        clearInterval(this);
                        done(null, "第一个函数向下传递值");
                    }
                });
            },
            function (preValue ,done) {
                jj = 0;
                setInterval(function () {
                    jj++;
                    console.log("bbb"+ "后面是接受的第一个函数的值 " + preValue);
                    if (jj == 3) {
                        clearInterval(this);
                        done(null, "第二个函数向下传递值");
                    }
                });
            },
            function (preValue,done) {
                console.log("后面是第二个函数给我传递的值:" + preValue);
                done(null, "全部程序执行完毕");
            }
        ],function (err, rs) {
            console.log(err);
            console.log(rs);
        }
    );
}
exec();

 

注意点1:使用async.waterfall方法实现串行有关联

注意点2: 该方法的第一个参数是一个数组, 第二个参数是一个回调函数,没有变化。 因为这是数组,因此就没有键的概念了,故每个元素都是一个函数。

注意点3: 因为这是串行有关联的,因此咱们能够经过function的第一个参数来接收上一个函数执行完以后返回的结果。 

 

 

 

10. 链接MySQL

链接MySQL的方法有两种,一种是直接链接,另外一种是使用链接池链接。  直接链接更简单一些,因此咱们先学习直接链接。

直接链接MySQL

首先使用node安装在当前文件夹下,以下(使用npm安装会出现问题,因此使用cnpm):

cnpm install mysql

 

 

11. Buffer

在网络层对于不一样的文件都是使用二进制交互的,而js自己不具备能力,因此在node中引入了Buffer用于引入。 对于Buffer,咱们能够在命令行中查看。

直接输入Buffer,回车,能够看到Buffer是一个对象, 它也是一个构造函数: 以下:

 

咱们能够new 一个Buffer,而且传入一个字符串,存入Buffer时,默认的编码格式是uft-8, 以下:

 

而后,咱们能够指定其具体的编码格式,如base64, 以下:

 

咱们还能够指定其长度,而后写入:

能够看到咱们已经定义了buf的长度为7, 即便写的超过了7, 最后也只会留下7个,其余的被忽略掉。

 

咱们可使用Buffer.isBuffer()来判断是不是Buffer,以下:

 

 

Buffer写入,若是咱们直接使用write()方法写入,即只接受一个字符串,那么会覆盖以前的, 以下所示:

 

 

可是write()方法还能够接受第二个参数,即偏移量,即从哪里开始写,以下所示:

 

从这里能够看出, 偏移量是指从0开始计算的。 而且一旦开始new的时候传入了字符串,就肯定了buf的长度,后面即便是再写入,也不能超过原来的长度。

 

copy()方法以下所示:

即第二个参数指定开始写的位置,第三个参数指定复制的起始位置,第四个参数指定赋值的结束位置。

 

 

 

重要

  每次咱们再修改了js内容时,都须要从新启动服务器,这是很是麻烦的,因此咱们可使用相似react中的热加载,即安装supervisor,以下:

npm install supervisor --save

  而后就能够全局使用了, 接着建立一个node文件,原来咱们是经过 node <文件名> 方式来执行的,如今,咱们使用 supervisor --harmony <文件名> 的方式就能执行,而且只要咱们改了js,而且保存,这时再刷新页面就会发现已经改变, 而不须要再次启动node服务器。

 

 

 

 

 

 

 

 

 

 

 

 

视频资源:http://study.163.com/course/courseLearn.htm?courseId=1003228034#/learn/video?lessonId=1003665724&courseId=1003228034

代码资源: http://www.yuankuwang.com

相关文章
相关标签/搜索