Nodejs 基础知识 浅析

1. 模块化

常用模块化规范

CommonJS + nodejs

AMD(Asynchronous Module Definition) + RequireJS

CMD(Common Module Definition) + SeaJS

UMD

ECMAScript 2015 Module

CMD规范seajs

1seajs的基础语法:

引入sea.js插件包

创建一个test.js文件,在其中写如下代码:

define(function( require , exports , module ) {

        module.exports.name = ” Tom”

module模块对象exports对外接口对象(向外暴露数据的方法),name是添加在module模型对象上的属性

        console.log(module.exports === exports) 此处打印结果为true

也就是exports可以相当于module.exports来使用,但值得注意的是,exports并不是module.exports本身,只是给exports这个变量赋值为module.exports,实现机制类似于:var exports = module.exports

})

创建一个main.js文件,在其中写如下代码:

define(function( require , exports , module ) {

        console.log("hello world")

        console.log(require(‘./test’))

通过require( )方法,将需要获取数据的文件路径传递进去,如果该文件向外暴露了数据,此处即可获取该数据 { name: “Tom” },如果没有暴露数据,此处打印结果为空对象{ }require无视流程控制语句,只要写了就会加载

})

再在页面中创建一个<script>标签,写上seajs.use("./js/main");即上述main.js文件的路径,该文件的.js后缀名写不写都可以,便可执行上述文件中的代码

2)使用seajs加载第三方插件的兼容写法:(以Zepto为例)

在Zepto的js文件中,找到定义Zepto的位置:window.Zepto = Zepto

在这行代码下面添加define函数的兼容写法:

if( typeof define === ‘function’ &&define.cmd ){

此处全局判断,文件中是否加载了seajs,因为seajsdefine函数默认添加了cmd属性,如果加载了seajs才引入define函数暴露Zepto,这样可以避免在没有引入seajs时,直接引入Zepto会找不到define方法

                 define(function( require , exports , module ) {

                         module.exports = Zepto;

}

}

2. nodejs 浅析(node:节点)

nodejs基本介绍(在服务器端使用JavaScript

(1)JavaScript运行时,构建在chrome V8引擎之上

(2)是一个JavaScript运行环境

(3)event-driven 事件驱动

(4)non-blocking I/O model 非阻塞I/O模型

(5)包括npm开源库生态系统

REPL(Read-eval-print-loop):交互式解析器环境

(1)REPL的常用命令:

       进入node,即进入了REPL环境,在命令窗口输入node

       退出:输入.exit或者连续按ctrl+c两次

       点击tab可以打印出Node.js中的所有对象

       点击向上/向下可以查看历史命令

       .save filename保存输入的命令

       .load filename加载文件

在REPL环境下,可以用_代替上一次表达式的结果

(2)REPL中执行js代码文件

在命令窗口输入node ./nodetest.js  (需要打开的文件路径)

(3)终端/控制台(命令窗口)基础通用命令

dir 查看目录中的所有文件

cd 切换目录   切盘符直接输d:即可

cls clear screen 清理控制台显示内容

模块作用域

nodejs本身是模块化的,具备类似seajs中的moduleexportsrequire使用方法也基本相同,只是不需要写在define中,直接写在js文件内即可,再在需要加载输出内容的文件中使用require请求数据即可

例如:

在test.js文件中写:

module.exports.name = { name: ”Tom” }

global.obj = { name: ”Jack” }      globalnode提供的类似window的全局对象

在nodetest.js文件中写:

console.log( require(‘./test’).name );   require中传入的内容是模块标识,如果是引用同级文件,必须加上./否则会认为是引用的核心模块

console.log(global.obj);

在控制台输出:node nodetest.js  输出结果为{ name: ”Tom” }  { name: ”Jack” }

次案例中,moduleexportsrequire的意义与seajs中基本一致,称为伪全局对象,即在自身模块中有全局效果,每个模块都有这几个对象,用法与seajs中相同,需要注意的是global是node提供的一个全局对象,可以在全局中获取数据,但是存在冲突的可能性,不推荐使用

node模块中还包含__filename(双下划线)__dirname(双下划线)两个变量,

__filename当前文件模块的绝对路径,包含当前文件的文件名和后缀

__dirname当前文件所属的绝对路径,不包含当前文件的文件名和后缀

全局函数(异步执行函数)

setTimeout( )、clearTimeout( )、setInterval( )、clearInterval( )

setImmediate( )、clearImmediate( )

例如:setTimeout( function(){

                    console.log(“异步执行”);      

})   即使不传入时间参数,里面的内容也会最后执行,起到异步的作用

commonJS规范(为JavaScript在后台功能实现定义的API规范nodejs遵循这个规范

规范特征:

(1)每个单独的文件都是一个模块

(2)所有的代码都在自身的模块作用域中运行,不会污染全局作用域

(3)所有模块的对外接口都是module.exports对象

(4)require方法用于加载需要调用数据的模块文件

(5)模块可以多吃加载,但只会在首次加载时运行,并将运行结果进行缓存,以后再加载时,就直接读取缓存结果

(6)模块加载的顺序,按照其在代码中出现的顺序进行,边加载,边执行

模块加载

(1) require查找规则

1 ) 首先判断标识符是纯字符串还是相对路径

2 ) 如果是纯字符串:表示核心模块 或者 第三方模块(包)

3 ) 如果是第三方包:package.json中指定的优先级大于index.js

4 ) 如果是相对路径,且有扩展名(.js.html等):直接去找对应的文件即可

5 ) 如果是相对路径,没有扩展名,例如require(‘./test’):,有优先级:test.js > test.json > test.node

6 ) 如果在当前node_modules找不到:返回上一级目录找

7 ) 最好使用require(‘path’)模块控制路径,如果直接加载路径,正/、反有时会产生分歧,产生无法加载文件的现象

(2) 核心模块(node提供的模块)具体功能查看文档

a ) fs模块(文件操作)

require(‘fs’).readFile(‘./readme.md’ , function(err , data)){

         console.log(data);  读取传入的文件,以二进制的十六进制形式输出

         console.log(data.toString())   以可视文件形式输出

}

b ) os模块(系统操作)

…….等等

(2) 加载第三方模块

a ) 库加载示例:underscore库加载

使用npm安装underscore库,然后再加载

加载:require(‘./node_module/underscore/underscore.js’)

或者不用写路径直接写模块名:require(‘underscore’)

实现原理是,node在node_module目录下找到underscore文件夹,查看里面的package.json文件,加载这个json文件里面的main属性(指定该包的入口)下的文件路径,这个路径是相对于该package.json文件而言的,也可根据自己需求修改路径

b ) 自定义库加载

同理可以按照underscore的思路,将自定义库放在node_module目录下,再在自定义库中创建一个package.json文件,指定main属性的路径到需要加载的文件,最后再require(自定义name’),即可实现加载

(如果package.json文件中没有main字段,require方法会查找包目录下的index.js , index.json , index.node作为默认入口)

c ) node中只需在根目录创建唯一node_module目录

如果当前目录没有node_module目录,node会自动查找上级目录,如果上级也没有,会查找上上级目录,类似JS中函数作用域链,这样带来的好处是,只需要在根目录下创建唯一node_module目录即可,不需要在创建的每个子目录下都创建node_module目录,提高库管理的效率。

引入库只需执行require(name’)即可

文件操作("fs”

(1)编码(字符串与二进制0101…)转换

a ) node中支持的编码类型(不包含gbk:支持中文的编码规则)

  ascii     早期的编码规则,只支持英文

  utf8     支持不同国家语言的编码规则

  utf16le

  base64

  binary

  hex

b ) Buffer对象

Buffer是内存中的一个缓冲区,或者说是内存中的一个数据块

var buf = new Buffer(2)  创建2个字节的数据块

buf.write(“a”在上述2字节空间中,用第一个字节写a

buf.toString()  将16进制显示的Buffer对象转换为字符串显示

Buffer.concat( [buf1 , buf2 , buf3 , ……] ) Buffer拼接,传入一个Buffer对象的数组

……等等 (参考官方文档)

c ) 解决node中不支持的编码类型

引入插件包:iconv – lite  具体使用方法查看官方文档

简单示例:

        var fs = require('fs');

        var iconv = require('iconv-lite');

        var data = iconv.encode('这是一段中文', 'gbk');

        fs.writeFile('./test.txt', data, function(err) {

            if (err) throw err;

        });

        fs.readFile('./test.txt', function(err, data) {

            if (err) throw err;

            console.log(data);  ------  <Buffer d5 e2 ca c7 d2 bb b6 ce d6 d0 ce c4>

                 二进制数据(Buffer类型的对象)

            var str = iconv.decode(data, 'gbk');

            console.log(str);  ------  “这是一段中文”

        })

(2)同步和异步文件调用

a ) 同步操作:阻塞I/O操作(input/output)输入/输出端口

1 ) 会立即执行,阻塞I/O

2 ) 以同步编码的方式接受返回值

3 ) 针对同步I/O必须使用try – catch 捕获异常

4 ) 编程思路简单,易于操作

5 ) node中的文件操作(fs)API,带sync的都表示同步

 

b ) 异步操作:非阻塞I/O操作

1 ) 不会立即执行,会先把任务添加到事件队列,再依次执行

2 ) 必须通过回调函数的方式接收异步操作的返回值

3 ) 通过回调函数中的参数err对象,判断是否有错误

4 ) 相对于同步操作而言,编程思路复杂,不容易操作

(3)fs文件操作相关API简单示例(查看官方文档)

读文件  readFilereadFileSync

写文件  writeFilewriteFileSync

监视文件  watchFile

查看文件相关信息  stat

……等等

(4)可读流、可写流

a ) 可读流

创建可读流对象:var readStream = require(‘fs’).createReadStream(‘srcRead’)

通过可读流对象,可以监听文件读取过程,读取过程是通过chunk对象,一点一点进行读取,chunk即是每次读取的文件的大小,读取示例如下:

读取文件的总字节数:

var totalSize = require(‘fs’).statSync(‘srcRead’).size

开始监听文件过程读取:

readStream.on(‘data’ , function(chunk){

chunk是一个Buffer对象,具备length属性,length是字节的长度

由于每次只能读取chunk.length长度的字节,记录总共读取到的文件的大小:

curSize += chunk.length

通过curSize/ totalSize,可以获得已经读取的文件的百分比

})

读取结束时触发end事件:

readStream.on(‘end’, function(){

        console.log(“读取结束”)

}

b ) 可写流

创建可读流对象:var writeStream = require(‘fs’).createWriteStream(‘srcWrite’)

写入数据:writeStream.write(datadata可以是字符串也可以是变量,如果将可读流中的chunk对象,传入可写流方法,可实现大文件的复制

关闭可写流:writeStream.end( )

c ) pipe(管道方法)

创建可读流、可写流对象:

var readStream = require(‘fs’).createReadStream(‘srcRead’)

var writeStream = require(‘fs’).createWriteStream(‘srcWrite’)

可读流具有pipe方法,可直接写入可写流中:

readStream.pipe(writeStream)

网络编程

(1)OSI(Open System Interconnection)开放系统互联模型,将网络通信的工作分为7

应用层:HTTP、SMTP、IMAP等

表示层:加密、解密等

会话层:通信连接、维持会话

传输层:TCP、UDP

网络层:IP

链路层:网络特有的链路接口

物理层:网络物理硬件

(2)网络操作(net

服务器端代码示例:

var net = require('net');

创建一个网络服务器:

var server = net.createServer();

监听服务器的连接请求,设置请求处理回调函数,只要客户端连接成功就会触发connect连接事件,将当前连接的客户端封装成一个socket对象(双向数据流)

server.on('connection', function(socket) {

console.log('客户端连接');

设置客户端连接:首先启动window的Talnet客户端功能,然后在命令窗口输入telnet 127.0.0.1 3000即可看到客户端的信息,此处为”hello”

    console.log('当前客户端IP地址:'+socket.remoteAddress)

    console.log('当前客户端port端口号:'+socket.remotePort)

    socket.write('hello client');

    socket.on('data', function(data) {

        console.log(data.toString())

    })

})

开启服务器,并分配一个端口号进行监听,此处分配端口号3000

server.listen(3000, function() {

    console.log("server is running")

    console.log("please visit 127.0.0.1:3000")

});

客户端代码示例:

var net = require('net');

var client = net.createConnection(3000, '127.0.0.1');

client.on('connect', function() {    创建客户端与服务器的连接

    console.log('connect success');

})

client.on('data', function(data) {   data是服务器中socket.write写入的数据

    var data = data.toString();

    console.log(data);

    if (data == "hello client") {

        client.write('hello server');   向服务器中写入数据

    }

})

(3)http协议基本概念

客户端浏览器、服务器连接的简单图示:(以百度为例)

利用curl查看请求和响应报文: >表示请求报文、<表示响应报文

下载curl后,配置好环境变量后即可在CMD终端中查看报文

具体命令:curl www.baidu.com –v

三次握手

* Rebuilt URL to: www.baidu.com/

* Trying 115.239.211.112...  

* TCP_NODELAY set

* Connected to www.baidu.com (115.239.211.112) port 80 (#0)

下述部分是请求报文

> GET / HTTP/1.1    请求首行:请求方法+请求路径+HTTP协议版本

> Host: www.baidu.com

> User-Agent: curl/7.53.1    键值对,包含当前客户端信息

> Accept: */*

> 回车换行

下述部分是响应报文

< HTTP/1.1 200 OK

< Server: bfe/1.0.8.18

< Date: Wed, 01 Mar 2017 02:01:40 GMT

< Content-Type: text/html

< Content-Length: 2381

< Last-Modified: Mon, 23 Jan 2017 13:28:28 GMT

< Connection: Keep-Alive

< ETag: "588604fc-94d"

< Cache-Control: private, no-cache, no-store, proxy-revalidate, no-transform

< Pragma: no-cache

< Set-Cookie: BDORZ=27315; max-age=86400; domain=.baidu.com; path=/

< Accept-Ranges: bytes

回车换行

<!DOCTYPE html>   响应体(index.html中的内容)

<!--STATUS OK--><html>

……

(4)node中的http模块

示例代码如下:

var http = require('http');   

通过npm安装moment插件,可以获取当前时间

var moment = require('moment'); 

var fs = require('fs');

var server = http.createServer();  创建http服务器

server.on('request', function(request, response) {

两个参数request表示请求报文解析成的对象,response表示客户端对象与net中的socket基本相同

    var url = request.url;   requesturl属性,如果没有值,默认为/

    if (url == "/") {

        fs.readFile(require('path').join(__dirname, 'test.html'), 'utf8', function(err, data) {

    response.writeHead(200, {  设置文件类型,可以写text/plain表示任意类型

        'Content-Type': 'text/html;charset=utf-8'

        })

    response.write('当前服务器最新时间是:' + moment().format('YYYY-MM-DD hh:mm:ss'))

    response.end(data);  设置响应结束后的内容,此处渲染test.html

        })

}

加载静态资源:

例如其他的cssjs文件,或者img图片,需要再次发送请求,以css为例,如果test.html页面中有<link rel="stylesheet" href="/css/test.css">则还需在node中加载该文件

else if (url == "/css/test.css") {

        fs.readFile(require('path').join(__dirname, 'test.html'), 'utf8', function(err, data) {

                      response.writeHead(200, {  设置文件类型为css

                     'Content-Type': 'text/css;charset=utf-8'

                })

                      response.end(data);  设置响应结束后的内容,此处渲染test.css

                })

}

如果有大量静态资源需要加载,这样重复写else if的方法显然不合适,此时可以通过npm安装mime插件,根据url路径判断Content-Type,方法如下:

var mime = require('mime'); 

var url = request.url;

var statciPath = require('path').join(__dirname, url)  获取静态资源的路径

fs.readFile(statciPath, 'utf8', function(err, data) {

        if(err){ return response.end(‘404 Not Found’) }   如果资源有错误,报错

(通过mime中的lookup方法获取资源的Content-Type

        var contentType = mime.lookup(statciPath)  

       response.writeHead(200, { 

                  'Content-Type': contentType

          })

       response.end(data);  

})

})

server.listen(3000, '127.0.0.1', function() {   设置端口号为3000

    console.log('请访问:127.0.0.1')

})

(5)安装formidable插件可以对文件上传等操作进行管理

查考文档:https://www.npmjs.com/package/formidable

原文地址:https://www.cnblogs.com/Tabb/p/6583232.html