1. 模块化
①常用模块化规范
CommonJS + nodejs
AMD(Asynchronous Module Definition) + RequireJS
CMD(Common Module Definition) + SeaJS
UMD
ECMAScript 2015 Module
② CMD规范seajs
(1)seajs的基础语法:
引入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,因为seajs中define函数默认添加了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中的module,exports,require,使用方法也基本相同,只是不需要写在define中,直接写在js文件内即可,再在需要加载输出内容的文件中使用require请求数据即可
例如:
在test.js文件中写:
module.exports.name = { name: ”Tom” }
global.obj = { name: ”Jack” } global是node提供的类似window的全局对象
在nodetest.js文件中写:
console.log( require(‘./test’).name ); require中传入的内容是模块标识,如果是引用同级文件,必须加上./否则会认为是引用的核心模块
console.log(global.obj);
在控制台输出:node nodetest.js 输出结果为{ name: ”Tom” } { name: ”Jack” }
次案例中,module,exports,require的意义与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简单示例(查看官方文档)
读文件 readFile、readFileSync
写文件 writeFile、writeFileSync
监视文件 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(data) data可以是字符串也可以是变量,如果将可读流中的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; request的url属性,如果没有值,默认为/
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
})
}
加载静态资源:
例如其他的css、js文件,或者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