zoukankan      html  css  js  c++  java
  • nodejs基础知识查缺补漏

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

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

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

      nodejs应用场景广泛, 但是目前在xp系统的某些应用还不能支持。

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

    2. 框架

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

      而koa也是一个node框架,但是这个框架比较新,还不够稳定,并且它是完全基于es6的。

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

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

    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

  • 相关阅读:
    Ionic 2.0 相关资料
    Tkinter的Menubutton组件
    Tkinter的Menubutton组件之OptionMenu
    Tkinter的Menu组件
    Tkinter的Scale组件
    Tkinter的Scrollbar组件
    Tkinter的Listbox组件
    Tkinter的Text组件
    Tkinter的Spinbox组件
    Tkinter的Label组件
  • 原文地址:https://www.cnblogs.com/zhuzhenwei918/p/6710871.html
Copyright © 2011-2022 走看看