学习Node.js.

介绍

LTS的意思是Long Time Support.

Javascript由三部分组成:

  • ECMAScript
  • DOM
  • BOM

Node.js由两部分组成:

  • ECMAScript
  • Node模块API

在浏览器中全局对象是windows,而node中全局对象是global.

Node.js模块化开发

Javascript存在两个问题,文件依赖和命名冲突.

Node.js中一个文件就是一个模块,默认中内部定义的变量和函数默认情况下在外部得到.

模块内部可使用exports对象进行成员导出,require方法导入其他模块.

// a.js
let version=1.0

const sayHi=name=>`您好,${name}`;

// exports.version=version
// exports.sayHi=sayHi;
// module.exports.version=version
// module.exports.sayHi=sayHi

module.exports={
    version:version,
    sayHi:sayHi
}


// index.js
console.log("hello world");

//这里不能为a.js,必须为相对路径
let a=require('./a.js')
// let a=require('./a') 这样也行

console.log(a.version)

另一种模块化导出方法,exports是module.exports的别名,当module.exports和exports指向的对象不同时,导出对象以module.exports为准.


module.exports={
    version:version,
    sayHi:sayHi
}

exports={
    name:123
}

Node.js系统模块

相对路径和绝对路径

// 文件夹路径
console.log(__dirname)
// 绝对路径
console.log(__filename)

fs模块

file system模块,文件操作

const fs=require('fs')

// 读取当前目录
fs.readdir('.',(err,files)=>{
    if(err==null)
    {
        console.log(files)
    }
})

// 读取文件
fs.readFile('./a.js',(err,data)=>{
    console.log(err);
    console.log(data.toString());
})


//写入文件
fs.writeFile('./a.txt',"hello world",err => {
    if(err==null){
        console.log("文件写入成功");
    }
})

path模块

不同操作系统不同路径分隔符不统一.

const path=require('path')
// 路径拼接
const testpath=path.join(".",'a',"b",'c.html')
console.log(testpath)

第三方模块

第三方模块通常由多个文件组成,也成为包.

第三方模块的两种存在形式:

  • 命令行工具
  • js文件,提供api接口

npm install默认情况下安装在当前目录下.

npm uninstall即卸载.

npm install -g 下载在全局安装目录.

nodemon模块

用来辅助项目开发.

每次文件修改,都会执行特定文件.

直接在命令行执行nodemon index.js即可.

nrm模块

方便npm库下载地址的选择和切换.

C:\Users\real>nrm ls

* npm -------- https://registry.npmjs.org/
  yarn ------- https://registry.yarnpkg.com/
  cnpm ------- http://r.cnpmjs.org/
  taobao ----- https://registry.npm.taobao.org/
  nj --------- https://registry.nodejitsu.com/
  npmMirror -- https://skimdb.npmjs.com/registry/
  edunpm ----- http://registry.enpmjs.org/
# nrm use 下载地址名称即可切换地址.

Gulp模块

介绍

基于Node平台开发的前端构建工具.

将机械化操作编写成任务.

Gulp能做的事:

  • 项目上线,文件的压缩
  • 语法转换
  • 公共文件抽离
  • 浏览器的自动刷新

使用方法

安装

不要加-g参数

npm install gulp
npm install gulp-cli -g //命令行工具
建立gulpfile.js文件

在项目根目录下建立gulpfile.js文件

重构项目

src目录放置源代码文件,dist放置构建后文件.

编写任务

在gulpfile.js文件中编写任务.

执行

在命令行工具中执行gulp任务.

gulp任务编写

基本使用
// gulpfile.js
const gulp=require('gulp')

//通过gulp.task()方法建立任务,'first'为任务名称
gulp.task('first',()=>{
    console.log("第一个gulp任务运行啦!")
//    获取要获取的文件,将处理后的文件输出到dist目录
    gulp.src('./src/index.html').pipe(gulp.dest('./dist'))

})

命令行下执行任务(需安装gulp-cli)

gulp first

会报错,控制台打出如下提示:
The following tasks did not complete: testGulp
Did you forget to signal async completion?

解决方法

// 方法1
const gulp = require('gulp');
gulp.task('testGulp', async() => {
   await console.log('Hello World!');
});

// 方法2
gulp.task('testGulp', done => {
  console.log('Hello World!');
  done();
});

gulp创建一个default任务,可简化任务

gulp.task('default',['task1','task2'])

cmd下执行gulp即可执行所有任务

Gulp插件

Gulp自身功能并不多,其功能可由插件拓展.

常见插件
  • gulp-file-include: 抽取公共文件
  • browser-sync: 省时的浏览器同步测试工具,多设备、多屏幕自动刷新页面
  • http-proxy-middleware: 解决本地开发代理跨域请求插件
  • autoprefixer: 根据定制的兼容规则给css添加浏览器前缀插件
  • wiredep: wiredep解决了bower前端库引入进html中的问题
  • del: 删除文件及文件夹
  • yargs: Node中处理命令行参数的通用解决方案,只要一句代码 var args = require(‘yargs’).argv;就可以让命令行的参数都放在变量args上,可以根据参数判断是测试环境还是正式环境。
  • gulp-load-plugins: 批量引入package.json文件中的依赖项
  • gulp-plumber: 防止因gulp插件的错误而导致管道中断,plumber可以阻止 gulp 插件发生错误导致进程退出并输出错误日志。
  • gulp-notify:gulp通知插件
  • gulp-sourcemaps: 用来生成映射文件的一个插件,SourceMap 文件记录了一个存储源代码与编译代码对应位置映射的信息文件。我们在调试时都是没办法像调试源码般轻松,这就需要 SourceMap 帮助我们在控制台中转换成源码,从而进行 debug。
  • gulp-useref: 可以将HTML引用的多个CSS和JS合并起来,减小依赖的文件个数,从而减少浏览器发起的请求次数。gulp-useref根据注释将HTML中需要合并压缩的区块找出来,对区块内的所有文件进行合并。注意:它只负责合并,不负责压缩!
  • gulp-rev:为静态文件随机添加一串hash值, 解决cdn缓存问题, a.css --> a-d2f3f35d3.css。根据静态资源内容,生成md5签名,打包出来的文件名会加上md5签名,同时生成一个json用来保存文件名路径对应关系。
  • gulp-rev-collector:根据gulp-rev生成的manifest.json文件中的映射, 去替换文件名称, 也可以替换路径。
  • gulp-rev-rewrite:重写对由gulp-rev修订的资产的引用
  • gulp-rev-css-url:用于在gulp-rev之后,用修订后的URL覆盖CSS文件中的URL
  • gulp-rev-outdated:旧的静态资产修订文件过滤器
  • gulp-rev-delete-original:删除由gulp-rev或 gulp-rev-all重写的原始文件 。
  • rev-del:这是一款从模块(如gulp-rev)生成的修订清单中删除旧的、未使用的指纹文件。
  • gulp-rev-format:提供静态资产的哈希格式选项(前缀,后缀,最后扩展名)
  • gulp-imagemin: 缩小PNG,JPEG,GIF和SVG图像的插件
  • gulp-cache:这是一款基于临时文件的gulp缓存代理任务。
  • gulp-filter: 可以把stream里的文件根据一定的规则进行筛选过滤。比如gulp.src中传入匹配符匹配了很多文件,可以把这些文件pipe给gulp-filter作二次筛选
  • gulp-inject:这个插件的作用与wiredep类似,不同的是可以自己任意指定需要插入文件的列表。它同样是利用注释来寻找插入的位置。
  • gulp-replace:gulp3的字符串替换插件
  • gulp-htmlmin:这是一款HTML文件压缩插件
  • gulp-if:这是一款条件判断插件
  • gulp-size:显示项目的大小插件
  • gulp-uglify:使用UglifyJS缩小js文件
  • gulp-beautify:这是一款使用js-beautify进行资产美化插件
  • pump:这是一款小型节点模块,可将流连接在一起并在其中一个关闭时将其全部销毁
  • gulp-postcss:通过多个插件通过管道传递CSS,但是仅解析一次CSS。
  • cssnano:这是一款将你的 CSS 文件做 多方面的的优化,以确保最终生成的文件 对生产环境来说体积是最小的插件
  • gulp-babel:Babel的Gulp插件
  • gulp-eslint:一个用于识别和报告在ECMAScript/JavaScript代码中找到的模式的Gulp插件。
插件使用方法

一个简单的例子.

const gulp=require('gulp')
const htmlmin=require('gulp-htmlmin')

//html任务
//1.压缩html任务
//2.抽取html中的公共代码

// 前提要执行命令 npm install gulp-htmlmin
// 具体命令看readme
gulp.task('htmlmin',()=>{
    return gulp.src('src/*.html')
        .pipe(htmlmin({ collapseWhitespace: true }))
        .pipe(gulp.dest('dist'));
})

package.json文件

node_modules文件夹的问题

  1. 文件夹过多过碎
  2. 复杂的模块依赖关系

package.json文件的作用

项目描述文件,使用npm init -y生成.y的话就是默认值.

npm install 即可安装所有依赖.

解决了第一个问题

在项目的开发阶段需要,而运营阶段不需要的依赖,称为开发依赖.如gulp.安装方法如下

npm install gulp --save-dev

对应package.json的devDependencies.

  • npm install 安装所有依赖
  • npm install --production 只安装运营依赖

package-lock.json

记录了模块与模块的依赖关系.优点:

  • 锁定包的版本
  • 加快包的下载速度

script

npm run 别名即可运行命令.

Node.js中模块加载机制

拥有路径但无后缀

查找规则:

  1. 找同名js文件
  2. 找同名文件夹,如果由同名文件夹找index.js,如果无index.js去package.json中找main选项的入口文件.

没有路径且没有后缀

查找规则:

  1. 假设为系统模块
  2. 去node_modules文件夹中,看是否由同名js,如果没有则去同名文件夹查找

Node.js服务器端

创建Web服务器

//引用系统模块
const http = require('http')
//创建web服务器
const app=http.createServer()
//当客户端发送请求时
app.on('request',(req,res)=>{
    res.end('hello world');
})

app.listen(80)

区分不同请求方式

//引用系统模块
const http = require('http')
//创建web服务器
const app=http.createServer()
//当客户端发送请求时
app.on('request',(req,res)=>{
    // res.end('hello world');
    if(req.method=='POST') res.end('post');
    if(req.method=='GET') res.end('get');
})
app.listen(80)

请求

req.headers返回的是一个对象

解析请求的代码

//引用系统模块
const url =require('url')
let params=url.parse(req.url,true)).query;
<form method="post" action="http://localhost">
    <input type="text" name="userid">
    <input type="password" name="passwd">
    <input type="submit" name="">
</form>

点击上面的按钮,就会发送post数据至localhost,例如:
userid: 13
passwd: 23

//引用系统模块
const http = require('http')
const querystring = require('querystring')
//创建web服务器
const app=http.createServer()

//当客户端发送请求时
app.on('request',(req,res)=>{
    let postparams='';
    req.on('data',params=>{
        postparams+=params;
    });
    req.on('end',()=>{

        console.log(querystring.parse(postparams));

    });
    res.end('ok');
})
app.listen(80)

Node.js异步编程

同步和异步API

  • 同步就是阻塞的
  • 异步就是不会阻塞的

同步API可以从返回值中拿到API执行的结果,而异步API则是不可以.可以通过回调函数得到异步API返回的值.

自己创建回调函数的一个例子.

function getData(callback) {
    callback('2341');
}
var x='14';
console.log(x);
getData(function (n) {
    console.log('回调函数已经调用');
    x=n;
});
console.log(x);

Promise

用来解决回调地狱.


let promise=new Promise((resolve, reject) => {
    setTimeout(()=>{
        if (true){
            resolve({name:'张三'})
        }else{
            reject('失败了');
        }
    },2000);
})

promise.then(result=>console.log(result))
        .catch(error=>console.log(error));

输出一个对象 {name:‘张三’}.

new了一个promise,其内部代码会立刻执行.

要保证顺序,需要用函数包装promise.

最后解决回调地狱的代码.


//封装一个异步读取文件的内容的函数
//此函数返回对应异步任务的promise对象
function getFileByPath(path) {
  return new Promise(function (resolve,reject) {
    fs.readFile(path, 'utf8', function (err, data) {
      if(err){
        reject(err);   //失败
      }else{
        resolve(data);    //成功
      }
    })
  });
}

//由于then通过getFileByPath返回的是一个promise对象,所以可以继续.then串联调用(链式调用)
getFileByPath('./files/1.txt')
.then(function(data){
  console.log("成功:"+data);
  return getFileByPath('./files/2.txt');
},function(err){
  console.log("失败:"+err);
  return getFileByPath('./files/2.txt');
}) 
.then(function(data){ 
  console.log("成功:"+data);
  return getFileByPath('./files/3.txt');
},function(err){
  console.log("失败:"+err);
  return getFileByPath('./files/3.txt');
})
.then(function(data){
  console.log("成功:"+data);
},function(err){
  console.log("失败:"+err);
});

解决promise的繁琐问题

//在普通函数前面加上async关键字,普通函数变成了异步函数
//异步函数默认的返回值是promis对象
async function fn() {
    return 123;
    throw "ewr";
}
// console.log(fn)
fn().then(function (data) {
    console.log(data);
}).catch(err=>{
    console.log(err);
})

async function p1() {
    return 'p1';
}

async function p2() {
    return 'p2';
}
async function p3() {
    return 'p3';
}
async function run() {
    let r1=await p1();
    let r2=await p2();
    let r3=await p3();
    console.log(r1);
    console.log(r2);
    console.log(r3);
}
run()
  • await只能在异步函数中使用
  • await后面只能是promise对象
  • await可暂停异步函数向下执行,知道promise返回执行结果.

将一般的异步函数转为promise对象的方法.

const fs=require('fs')
const promisify=require('util').promisify

// 转为了promise对象
const readFile=promisify(fs.readFile)
async function run() {
    let s=await readFile('./form.html')
    console.log("hello world");
    console.log(s.toString());
}
run()

我很好奇