了不起的Nodejs学习笔记(前五章)

摘要:
它可以避免错误发布发布模块npmpublish2.3和由Node编写的命令行工具,该工具由二进制工具包的一些项目分发。发布时,在包中添加“bin”:“./path/to/script”项。json文件,并将其值指向可执行脚本或二进制文件。在JavaScript中,此属性名为lengthv
了不起的Nodejs学习笔记(前五章)

五大部分组成

  • Node核心设计理念
  • Node核心模块API
  • Web开发
  • 数据库
  • 测试

一、安装与概念

1、执行文件

  • Node.js通过node命令来执行Node脚本

  • 创建server.js

    var http = require('http');
    var server = http.createServer(function (req,res){
        res.writeHead(200);
        res.end('Hello World');
    });
    server.listen(3000);
    
  • 执行命令:node server.js

  • 浏览器输入:https://localhost:3000

2、NPM

2.1、安装模块

  • 执行以下命令

    mkdir my-project/
    cd my-project/
    npm install colors
    
  • 验证安装成功可以查看 my-project/node_modules/colors目录

  • 创建index.js

    require('colors');
    console.log('smashing node'.rainbow)
    
  • 执行命令

    node index.js
    # 输出:smashing node  (彩色字体)
    

2.2、自定义模块

  • 创建package.json文件

    • 好处
      • 不需要将整个node_modules目录发给别人
      • 方便记录所依赖模块的版本号
      • 分享更简单(npm publish发布到NPM库)
  • 创建项目

    mkdir my-project/
    cd my-project/
    touch package.json
    
  • 编辑package.json

    {
        "name":"my-colors-project",
        "version":"0.0.1",
        "dependencies":{
            "colors":"1.4.0"
        }
    }
    

    文件为json文件,遵循JSON格式。

  • 创建index.js

    require('colors');
    console.log('smashing node'.rainbow)
    
  • 执行命令

    npm install
    node index # 注意:这里文件名不需要加'.js'后缀
    
  • 发布

    • 编辑package.json

      {
          "name":"my-colors-project",
          "version":"0.0.1",
          "main":"./index",
          "dependencies":{
              "colors":"1.4.0"
          }
      }
      
      • 当别人使用require('my-project')时,为了能够让Node知道该载入哪个文件,我们可以使用main属性来指定
      • 查看package.json文件所有的属性文档,可以使用命令:npm help json
      • 如果不想发布模块,在package.json文件加入"private":"true"。可以避免误发布
    • 发布模块

      npm publish
      

2.3、安装二进制工具包

有的项目分发的Node编写的命令行工具。安装时要增加-g标志

举例来说,Web框架express就包含一个用于创建项目的可执行工具

npm install -g express

安装后,执行以下命令

# 创建目录
mkdir my-site
# 进入目录
cd my-site
express

如果想要发布此类脚本。发布时,在package.json文件添加"bin":"./path/to/script"项,并将其值指向可执行的脚本或者二进制文件。

2.4、浏览NPM仓库

  • npm search 模块名

    • 该命令会在已发布模块的name、tags以及description字段中搜索此关键字,并返回匹配的模块。
  • npm view 模块名

    • search命令找到感兴趣模块后,通过该命令查看package.json文件及与NPM相关的属性
  • npm help

    • 可以查看某个NPM命令的帮助文档
    • npm help publish就会叫你如何发布模块

二、JavaScript概览

1、介绍

  • 基于原型、面向对象、弱类型的动态脚本语言
  • 根据ECMAScript语言标准来实现。

2、基础

2.1、类型

  • 基本类型:访问基本类型,访问的是值
    • number
    • boolean
    • string
    • null
    • undefined
  • 复杂类型:访问复杂类型,访问的是对值的引用
    • array
    • function
    • object

2.2、类型的困惑

要在JavaScript中准确无误的判断变量值的类型并非易事。

  • 字符串

    // 创建字符串的两种形式
    var a = 'woot';
    var b = new String('woot');
    a + b; // 'woot woot'
    
    // 用 typeof 和 instanceof 操作符判断
    typeof a; // 'string'
    typeof b; // 'object'
    a instanceof String; // false
    b instanceof String; // true
    
    // == 与 ===
    a == b; // true
    a === b; // false
    
    // 事实上,两个变量都是字符串
    a.substr == b.substr; // true
    

    考虑到以上差异,建议始终通过直观方式进行定义,避免使用new。

  • 条件表达式中的一些特定值会被判定为false

    // null、undefined、' '、0
    var a = 0;
    if(a){
        // 这里始终不会被执行
    }
    a == false; // true
    a === false; // false
    
    // typeof不会把null识别为类型为null
    typeof null = 'object'; // true
    // 数组也不例外,就算是通过[]定义数组也是如此
    typeof [] = 'object'; // true
    
    // 判断数组
    Object.prototype.toString.call([]) == '[object Array]';
    
    // instanceof Array这种方式只适用于与数组初始化在相同上下文中才有效
    

2.3、函数

在JavaScript中,函数最为重要。

/** 以下属于一级函数:可以作为引用存储在变量中,随后可以像其他对象一样,进行传递 **/
var a = function(){}
console.log(a); // 将函数作为参数传递

/** JavaScript中所有函数都可以进行命名。有一点很重要,就是要能区分出函数名和变量名 **/
var a = function a (){
    console.log('function' == typeof a); // true
}
a();

/** THIS、FUNCTION#CALL、FUNCTION#APPLY **/
// 下述代码中函数被调用时,this的值是全局对象。在浏览器中,就是windows对象
function a (){
    console.log(window == this); // true
}
a();

// 调用以下函数,使用.call和.apply方法可以改变this的值
function a () {
    console.log(this.a == 'b'); // true
}
a.call({a:'b'});

// call和apply的区别在于,call接受参数列表,而apply接受一个参数数组
function a (b,c){
    console.log(b == 'first'); // true
    console.log(c == 'second'); // true
}
a.call({a:'b'},'first','second');
a.apply({a:'b'},['first','second']);

2.4、函数的参数数量

该属性指明函数声明时可接受的参数数量。在JavaScript中,该属性名为length

var a = function (a,b,c);
a.length == 3; // true

// 尽管这在浏览器端很少用,但,在流行的Node.js框架就是通过此属性来根据不同参数个数提供不同的功能。

2.5、闭包

在JavaScript中,每次函数调用时,新的作用域就会产生。

2.5.1、作用域
// 在某个作用域中定义变量只能在该作用域或其内部作用域(该作用域中定义的作用域)中才能访问到
var a = 5;
function woot () {
    console.log(a == 5); // false
    var a = 6;
    function test(){
        console.log(a == 6); // true
    }
    test();
};
woot();
2.5.2、自执行函数
// 自执行函数是一种机制,通过这种机制声明和调用一个匿名函数,能够达到仅定义一个新作用域的作用
var a = 3;
(function () {
	var a = 5;    
})();
console.log(a == 3); // true

// 自执行函数对声明私有变量是有用的,这样可以让私有变量不被其他代码访问。

2.6、类

// JavaScript中没有`class`关键字。类只能通过函数定义
function Animal(){}
// 要给所有Animal的实例定义函数,可以通过prototype属性来完成
Animal.prototype.eat = function (food) {
    // eat method
}
// 值得一提的是,在prototype的函数内部,this并非像普通函数那样指向global对象
// 而是指向通过该类创建的实例对象
function Animal(name) {
    this.name = name;
}
Animal.prototype.getName = function(){
    return this.name;
}
var animal = new Animal('tobi');
console.log(animal.getName() == 'tobi'); //true

2.7、继承

JavaScript有基于原型的继承的特点。通常,你可以通过以下方式来模拟类继承。

// 定义Animal
function Animal(name) {
    this.name = name;
}
// Animal类方法eat
Animal.prototype.eat = function (food) {
    console.log("吃:"+ food)
}
// 定义Ferret类:一个要继承自Animal的构造器
function Ferret () {};
// 要定义继承链,首先创建一个Animal对象,然后将其赋值给Ferret.prototype
Ferret.prototype = new Animal();
// 随后,可以为子类定义属性和方法。
Ferret.prototype.type = 'domestic';
// 通过prototype重写和调用父类函数
Ferret.prototype.eat = function (food) {
    // 第一种调用
    Animal.prototype.eat(food);
    // 第二种调用
    Animal.prototype.eat.call(this,food)
    // ferret特有的逻辑写在这里
}
// 构建ferret
var ferret = new Ferret();
// 打印type属性值
console.log(ferret.type);
// 调用父类方法
ferret.eat('冰淇凌');

// 这项技术很赞,它是同类方案中最好的(相比其他函数式技巧),而且它不会破坏instanceof操作符的结果
var animal = new Animal();
console.log(animal instanceof Animal); // true
console.log(animal instanceof Ferret); // false

var ferret1 = new Ferret();
console.log(ferret1 instanceof Animal); // true
console.log(ferret1 instanceof Ferret); // true

// 它最大的不足就是声明继承的时候创建的对象总要进行初始化`Ferret.prototype = new Animal()`
// 一种解决该问题的方法就是在构造器中添加判断条件
function Animal (a) {
    if (false !== a) return;
    // 初始化
}
Ferret.prototype = new Animal(false);
// 另外一种方法就是再定义一个新的空构造器,并重写它的原型
function Animal () {
    // constructor struff
}
function f () {};
f.prototype = Animal.prototype;
Ferret.prototype = new f;
// V8提供了更简洁的解决方案,见后文

2.8、异常捕获

/** try/catch允许进行异常捕获。**/
// 以下代码会抛出异常
var a = 5;
a() // TypeError: a is not a function

// 当函数抛出错误时,代码就停止执行了
function () {
    throw new Error('hi');
    console.log('hi') // 这里永远不会被执行到
}

// 若使用try/catch则可以进行错误处理,并让代码继续执行
function () {
    var a = 5;
    try{
        a();
    }catch (e) {
        e instanceof Error; // true
    }
    console.log('you get here')
}

3、V8中的JavaScript

3.1、OBJECT#KEYS

// 想要获取下述对象的键(a和c)
var a = {a:'b',c:'d'}
// 通常会使用如下迭代的方式
for(var i in a){
    console.log(i); // a // c
}
// 通过对键进行迭代,可以将它们收集到一个数组中。
// 不过如果采用如下方式对Object.prototype进行过扩展
Object.prototype.c = 'd'
// 为了避免再迭代过程中把c也获取到,就需要使用hasOwnProperty来进行检查
// hasOwnProperty() 方法会返回一个布尔值,指示对象自身属性中是否具有指定的属性(也就是,是否有指定的键)
for (var i in a){
    if (a.hasOwnProperty(i)){}
}
// 在V8中,要获取对象上的所有的自有键,还有更简单的方法
var a = {a:'b',c:'d'}
Object.keys(a); // ['a','c']

3.2、ARRAY#ISARRAY

// 对数组使用typeof操作符会返回object
// 下面介绍如何判断数组
console.log(Array.isArray(new Array())); // ture
console.log(Array.isArray([])); // ture
console.log(Array.isArray(null)); // false
console.log(Array.isArray(arguments)); // false

3.3、数组方法

// 1、遍历数组:forEach
[1,2,3].forEach(function (v) {
    console.log(v);
})
// 打印:
// 1
// 2
// 3

// 2、过滤数组元素:filter
let filterRes = [1,2,3].filter(function (v) {
    return v < 3;
});
console.log(filterRes) // [ 1, 2 ]

// 3、改变数组中每个元素的值:map
let mapRes = [5,10,15].map(function (v) {
    return v * 2;
})
console.log(mapRes) // [ 10, 20, 30 ]

// 4、V8还提供了不常用的方法,如reduce、reduceRight以及lastIndexOf

3.4、字符串方法

// 移除字符首末的空格
console.log(' hello '.trim()); // 'hello'

3.5、JSON

// V8提供了JSON.stringify和JSON.parse方法来对JSON数据进行解码和编码
// JSON是一种编码标准和JavaScript对象字面量很相近,它用于大部分的Web服务和API服务:
var obj = JSON.parse('{"a":"b"}')
console.log(obj.a == 'b');  // true

3.6、FUNCTION#BIND

// .bind 允许改变对this的引用
function a () {
    console.log(this.hello == 'world'); // true
}

var b = a.bind({hello:'world'})
b()

3.7、FUNCTION#NAME

// V8还支持非标准的函数属性名
var a = function woot () {};
console.log(a.name == 'woot'); // true

// 该属性用于V8内部的堆栈追踪。
// 当有错误抛出时,V8会显示一个堆栈追踪的信息,会告诉你是哪个函数调用导致了错误的发生
var woot = function () {throw new Error();};
woot()	// 此时错误信息没有 函数名

var woot = function buggy () {throw new Error();};
woot()	// 此时错误信息含有 函数名

// 为函数命名有助于调试,因此,推荐始终对函数进行命名

3.8、继承

// _proto_ 使得定义继承链变得更加容易
function Animal(){}
function Ferret(){}
Ferret.prototype._proto_ = Animal.prototype
  • 借助中间构造器
  • 借助OOP的工具类库。无须再引入第三方模块来进行基于原型继承的声明

3.9、存取器

你可以通过调用方法来定义属性,访问属性就使用__defineGetter__、设置属性就使用__defineSetter__

// 以下例子,为所有的Date实例都添加了ago获取器,它会返回以自然语言描述的日期距离现在的时间间隔。
// 简单地访问该属性就会调用事先定义好的函数,无须显式调用
// 双下划线
Date.prototype.__defineGetter__('ago',function(){
    var diff = (
        ( (new Date()).getTime() - this.getTime() ) / 1000
    ), 
        day_diff = Math.floor(diff / 86400);
    return (
        diff < 60 && "just now" ||
        diff < 120 && "1 minute ago" ||
        diff < 3600 && Math.floor(diff / 60 ) + " minute ago" ||
        diff < 7200 && "1 hour ago" ||
        diff < 86400 && Math.floor(diff / 3600 ) + " hour ago" ||
        day_diff == 1 && "Yesterday" ||
        day_diff < 7 && day_diff + " days ago" ||
        Math.ceil( day_diff / 7 ) + " weeks ago"
    )
});

var a = new Date('12/12/1990')
console.log(a.ago);

三、阻塞与非阻塞IO

1、共享状态并发

  • 在Node中,需要对回调函数如何修改当前内存中的变量(状态)特别小心。

  • 除此之外,你要需要特别注意对错误的处理是否会潜在地修改这些状态,从而导致整个进程不可用。

nodejs代码

var books = ['笑傲江湖','天龙八部']
function serveBooks () {
    // 给客户端的html
    var html = '<b>' + books.join('</b><br/><b>') + '</b>';
    // 修改状态
    books = [];
    return html;
}

PHP代码

$books = array('笑傲江湖','天龙八部')
function serveBooks () {
    $html = '<b>'.join($book,'</b><br/><b>').</b>;
    $books = array();
    return html;
}
  • 以上两段代码,books是存放图书的数组,假设books就是状态,该数组用来将图书列表以HTML的形式返回给客户端
  • 以上两端代码,都在serveBooks函数中将books数组重置
  • 分别对两个服务发起各两次请求
  • Node会将完整的图书列表返回给第一个请求,而第二个请求则返回一个空的图书列表
  • PHP都能将完整的图书列表返回给两个请求
  • 两者区别在于架构
    • Node采用一个长期运行的进程。serveBooks再次被调用,此时books数组为空
    • Apache会产出多个线程(每个请求一个线程),每次都会刷新状态。在PHP中当解释器再次执行时,变量$books会被重新赋值。

2、阻塞

尝试区分下面PHP代码和Node代码有什么不同?

PHP

print('Hello');
sleep(5);
print('World')

Node

console.log('Hello')
setTimeout(function () {
    console.log('World')
},5000)
  • 区别

    • 语义区别(Node.js使用回调函数)

    • 阻塞和非阻塞

      • PHP:sleep()阻塞了线程的执行。当程序进入休眠,就什么事情也不做

      • Node.js使用了事件轮询,因此setTimeout时非阻塞的。参看以下代码:

        console.log('Hello')
        setTimeout(function () {
            console.log('World')
        },5000)
        console.log('Bye')
        // 输出:
        // Hello
        // Bye
        // World
        
  • 事件轮询意味着什么?

    本质上讲,Node会先注册事件,随后不停地询问内核这些事件是否已经分发。

    当事件分发时,对应的回调函数就会被触发。然后继续执行下去。

    如果没有事件触发,则继续执行其他代码,直到有新事件时,再去执行对应的回调函数。

  • Node并发实现也采用了事件轮询

    所有像http、net这样的原生模块中的IO部分也都采用了事件轮询技术

    tomeout机制中Node内部会不停地等待,并当超时完成时,触发一个和文件描述符相关的通知

  • 文件描述符

    文件描述符是抽象的句柄,存有对打开的文件、socket、管道等的引用

    本质上,Node接受到从浏览器发来的HTTP请求时,底层的TCP连接会分配一个文件描述符

    随后,如果客户端向服务器发送数据,Node就会收到该文件描述符,然后触发JavaScript回调函数

3、单线程的世界

有一点很重要,Node是单线程的。在没有第三方模块的帮助下是无法改变这一事实的。

var start = Date.now();
setTimeout(function () {
    console.log(Date.now() - start);
    for (var i=0;i<1000000000;i++){}
},1000)
setTimeout(function () {
    console.log(Date.now() - start);
},2000)
// 打印 
// 1000
// 3738
  • 以上程序显示了每个setTimeout执行的时间间隔,其结果和代码中设定的值并不相同
  • 原因:
    • 事件轮询被JavaScript代码阻塞。
    • 当第一个事件分发时,会执行JavaScript回调函数。
    • 由于回调函数需要执行很长时间(循环次数很多),所以下一个事件轮询执行时间远超2秒
    • 因此,JavaScript的timeout并不能严格遵守时钟设置

Node如何做到高并发?

  • 所有同步任务都在主线程上执行,形成一个执行栈
  • 主线程之外,还存在一个"任务队列"(task queue)。只要异步任务有了运行结果,就在"任务队列"之中放置一个事件。
  • 一旦"执行栈"中的所有同步任务执行完毕,系统就会读取"任务队列",看看里面有哪些事件。那些对应的异步任务,于是结束等待状态,进入执行栈,开始执行。
  • 主线程不断重复上面的第三步。

4、错误处理

首先,很重要的一点。Node应用依托在一个拥有大量共享状态的大进程中。

举例来说,如果某个回调函数发生错误,整个进程都会遭殃。

var http = require('http')
http.createServer(function () {
    throw new Error('错误不会被捕捉')
}).listen(3000)

因为错误未被捕获,若访问Web服务器,进程就会崩溃。

  • uncatchException处理器

    // 添加了uncaughtException处理器,进程不会退出,并且之后的事情你都能掌控
    process.on('uncaughtException',function (err) {
        console.error(err);
        process.exit(1); // 手动退出
    })
    // 以上例子中,行为方式和分发error事件的API行为方式一致
    
  • error事件

    var net = require('net')
    net.createServer(function (connection) {
        connection.on('error',function (err){
            // err是一个错误对象
        })
    }).listen(400);
    // Node中许多像http、net这样的原生模块都会分发error事件。如果该事件未处理,就会抛出未捕获的异常
    

除了uncaughtException和error事件外,绝大部分Node异步API接受的回调函数,第一个参数都是错误对象和NULL。

var fs = require('fs')
fs.readFile('/etc/passwd',function (err,data){
    if (err) return console.error(err);
    console.log(data);
})

错误处理中,每一步都很重要,因此它能让你书写更安全的程序,并且不丢失出发错误的上下文信息。

5、堆栈追踪

在JavaScript中,当错误发生时,在错误信息中,可以看到一系列的函数调用,这称为堆栈追踪。

function c () {
    b();
}
function b () {
    a();
}
function a () {
    throw new Error('here');
}
c();

// 运行以上代码,可以看到堆栈追踪信息。
// 可以清晰看见导致错误发生的函数调用路径

Error: here
    at a (e:demo	est	est.js:8:11)
    at b (e:demo	est	est.js:5:5)
    at c (e:demo	est	est.js:2:5)
    at Object.<anonymous> (e:demo	est	est.js:10:1)
    at Module._compile (internal/modules/cjs/loader.js:959:30)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:995:10)
    at Module.load (internal/modules/cjs/loader.js:815:32)
    at Function.Module._load (internal/modules/cjs/loader.js:727:14)
    at Function.Module.runMain (internal/modules/cjs/loader.js:1047:10)
    at internal/main/run_main_module.js:17:11
// 引入事件轮询
function c () {
    b();
}
function b () {
    a();
}
function a () {
    setTimeout(function () {
        throw new Error('here');
    },10)
}
c();

// 可以发现,有价值的堆栈信息丢失了
// 堆栈信息显示的是从事件轮询开始的

Error: here
    at Timeout._onTimeout (e:demo	est	est.js:10:15)
    at listOnTimeout (internal/timers.js:531:17)
    at processTimers (internal/timers.js:475:7)

同理,要捕获一个未来才会执行到的函数所抛出的错误是不可能的。

这会直接抛出未捕获的异常,并且catch代码块永远都不会被执行。

try{
    setTimeout(function () {
        throw new Error('here');
    },10)
} catch(e) {}

这就是为什么Nodejs中,每步都要正确进行错误处理的原因。

一旦遗漏,就会发现错误后很难追踪,因为上下文信息都丢失了。

四、Node中的JavaScript

1、global对象

  • 在浏览器中,全局对象指window对象。在window对象上定义的任何内容都可以被全局访问到。

    • setTimeout:其实就是window.setTimeout
    • document:其实就是window.document
  • Node中有两个类似但却各自代表不同含义的对象

    • global:和window一样,任何global对象上的属性都可以被全局访问到。
    • process:所有全局执行上下文中的内容都在process对象中
      • 在浏览器中,只有一个window对象。
      • 在Node中,也只有一个process对象。

2、实用的全局对象

  • process.nextTick:将一个函数的执行时间规划到下一个事件循环中
  • console:最早由Firefox中辅助开发的插件-Firebug实现。
    • console.log
    • console.error

3、模块系统

  • JavaScript语言标准中并未为模块依赖以及模块独立定义专门的API
  • Node摒弃了采用定义一堆全局变量的方式,转而引入模块系统.
    • 模块系统三个核心的全局对象
      • require
      • module
      • exports

4、绝对和相对模块

  • 绝对模块:指Node通过在其内部node_modules查找到的模块,或者Node内置的如fs这样的模块。
    • colors:修改了String.prorotype,因此无须暴露API。require('colors')
    • fs:暴露了一系列函数。var fs = require('fs');fs.readFile(...)
  • 相对模块:将require指向一个相对工作目录中的JavaScript文件。require(./module)

5、暴露API

要让模块暴露一个API称为require调用的返回值,就要依靠module和exports这两个全局变量

  • 默认情况下,每个模块都会暴露出一个空对象

    // module_a.js
    exports.name = 'zhangsan'
    exports.data = 'data'
    
    var pv = 5;
    exports.getPv = function () {
        return pv;
    }
    
    // index.js
    var a = require('./module_a')
    console.log(a.name)
    console.log(a.data)
    console.log(a.getPv())
    
    • 以上例子,exports其实就是对module.exports的引用,其在默认情况下是一个对象。

要是在该对象上逐个添加属性无法满足你都需求,你还可以彻底重写module.exports。

  • 常见的将模块中构造器暴露出来的例子

    // person.js
    module.exports = Person;
    
    function person (name) {
        this.name = name;
    }
    
    Person.prototype.talk = function () {
        console.log('我的名字:',this.name)
    }
    
    // index.js
    var Person = require('./person')
    var john = new Person('john')
    john.talk();
    
    • 以上是一个具备JavaScript OOP风格的Node.js模块的例子
    • 在index.js文件中,不再接受一个对象作为返回值,而是函数,归功于module.exports的重写

6、事件

Node.js中的基础API之一就是EventEmitter。

无论是在Node中还是浏览器中,大量代码都依赖于所监听或者分发的事件。

  • 浏览器中负责处理事件相关的DOM API包括

    • addEventListener
    • removeEventListener
    • dispatchEvent
    • 它们还用在一系列从window到XMLHTTPRequest等的其他对象上
  • Node暴露了Event EmitterAPI

    • on

    • emit

    • removeListener

    • 以process.EventEmitter形式暴露

      var EventEmitter = require('events').EventEmitter,a = new EventEmitter;
      a.on('event',function () {
          console.log('event called')
      })
      a.emit('event')
      
    • 添加到自己的类

      var EventEmitter = process.EventEmitter,MyClass = function (){}
      MyClass.prototype.__proto__ = EventEmitter.prototype;
      // 这样,所有MyClass的实例都具备了事件功能
      var a = new MyClass();
      a.on('某一事件',function () {
          // 做些什么
      })
      

​ 事件是Node非阻塞设计的重要体现。Node通常不会直接返回数据(因为这样可能会在等待某个资源的时候发生线程阻塞),而是采用分发事件来传递数据的方式。

​ 以HTTP服务为例。请求到达时,Node会调用一个回调函数,这个时候数据可能不会一下子都到达。

// 当用户提交表单时,通常会监听请求的data和end事件
http.Server(function (req,res) {
    var buf = '';
    req.on('data',function (data) {
        buf += 'data';
    });
    req.on('end',function () {
        console.log('数据接收完毕!')
    })
})
// 将请求数据内容进行缓冲(data)事件
// 等到所有数据都接收完毕(end事件)再对数据进行处理

7、buffer

除了模块之外,Node还弥补了语言另外一个不足之处,对二进制数据的处理。

  • buffer

    • 是一个表示固定内存分配的全局对象(也就是说,要放到缓冲区中的字节数需要提前定下)

    • 它好比一个由八位字节元素组成的数组,可以有效地在JavaScript中表示二进制数据

    • 该功能的一部分作用就是可以对数据进行编码转换

      // 创建一副用base64表示的图片,将其作为二进制PNG图片的形式写入到文件中
      // buffers/index.js
      var mybuffer = new Buffer('.....','base64')
      console.log(mybuffer)
      require('fs').writeFile('logo.png',mybuffer)
      
      // 运行
      node index
      open logo.png
      

五、命令行工具(CLI)以及FS API

1、需求

  • 需求
    • 程序需要在命令行运行。
      • 要么通过node命令来执行
      • 要么直接执行,然后通过终端提供交互给用户进行输入、输出。
    • 程序启动后,需要显示当前目录列表
    • 选择某个文件时,程序需要显示该文件内容
    • 选择一个目录时,程序需要显示改目录下的信息
    • 运行结束后程序退出
  • 项目步骤
    • 创建模块
    • 决定采用同步fs还是异步fs
    • 理解什么是流(Stream)
    • 实现输入输出
    • 重构
    • 使用fs进行文件交互
    • 完成

2、首个Node项目

2.1、创建模块

package.json

{
    "name":"file-explorer",
    "version":"0.0.1",
    "description":"A command-file file explorer!"
}
# 验证package.json有无问题
npm insall

2.2、同步还是异步

  • fs模块是唯一一个同时提供同步和异步API的模块
  • 为了学习单线程中创建能够处理高并发的高效程序,就得采用异步、事件驱动的程序。
// index.js
var fs = require('fs')
fs.readdir(__dirname,function (err,files){
    console.log(files);
})
// 输出:[ 'index.js', 'package-lock.json', 'package.json' ]

2.3、流(stream)

process全局对象包含了三个流对象,分别对应三个UNIX标准流

  • stdin:标准输入。可读流。
  • stdout:标准输出。可写流。
  • stderr:标准错误。可写流。

stream对象和EventEmitter很像,事实上,前者继承自后者。

2.4、输入和输出

var fs = require('fs')
fs.readdir(process.cwd(), function (err, files) {
    // 为了输出友好,首先输出一个空行
    console.log('');
    if (!files.length) {
        // 如果files数组为空,告知用户当前目录没有文件
        // 

免责声明:内容来源于网络,仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇百度开源的分布式 id 生成器用户权限管理基础原理的讲解-------玩转它!下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

百度编辑器不能插入html标签解决方法

在ueditor.all.js文件中找到此方法:   me.addInputRule(function (root) {   var allowDivTransToP = this.options.allowDivTransToP;   var val;   function tdParent(node){   while(node...

AESTest

usingGaea.MySql; usingSystem; usingSystem.Data; usingSystem.IO; usingSystem.Security.Cryptography; usingMicrosoft.Extensions.DependencyInjection; usingSystem.Text; usingSys...

Vue之项目搭建

  一、Vue自动化工具的安装 nvm:nodejs 版本管理工具。 也就是说:一个 nvm 可以管理很多 node 版本和 npm 版本。 nodejs:在项目开发时的所需要的代码库 npm:nodejs 包管理工具。 在安装的 nodejs 的时候,npm 也会跟着一起安装,它是包管理工具。 npm 管理 nodejs 中的第三方插件   1,安装nv...

vue开发环境搭建

大致分这么几个骤: 1. 安装node 、npm 、nvm 2. 安装git 、vscode 以及vscode上的一些插件 3. 安装vue-cli 以下详细来说每个步骤 1. 安装node 、 npm 、nvm    在官网下载的 node 安装包,在mac上运行会自动安装在全局目录,使用过程中经常会遇到一些权限问题,所以推荐按该网站 https://...

nodejs源码—初始化

概述 相信很多的人,每天在终端不止一遍的执行着node这条命令,对于很多人来说,它就像一个黑盒,并不知道背后到底发生了什么,本文将会为大家揭开这个神秘的面纱,由于本人水平有限,所以只是讲一个大概其,主要关注的过程就是node模块的初始化,event loop和v8的部分基本没有深入,这些部分可以关注一下我以后的文章。(提示本文非常的长,希望大家不要看烦~)...

es6 解构赋值

ES6允许按照一定模式,从数组和对象中提取值,对变量进行赋值,这被称为解构(Destructuring)。 关于给变量赋值,传统的变量赋值是这样的: var arr = [1,2,3];//把数组的值分别赋给下面的变量; var a = arr[0]; var b = arr[1]; var c = arr[2];...