Fork me on GitHub
行锋

低头走路,抬头思考


  • 首页

  • 分类

  • 归档

  • 标签

  • 关于

  • 搜索

异步编程及Async模块的使用

发表于 2018-10-26 | 分类于 前端

[toc]

1
汇智网-异步编程:http://cw.hubwiz.com/card/c/543e1a4f032c7816c0d5dfa1/1/3/6/

简介

github网址:https://github.com/caolan/async

  • 异步编程是指由于异步I/O等因素,无法同步获得执行结果时,在回调函数中进行下一步操作的代码编写风格,常见的如setTimeout函数、ajax请求等等
  • 使用Asycn模块需要安装,它不是node自带的
1
2
安装:npm install async 
引用:var async = require('async');
  • 异常捕获:异步I/O的实现主要有两个阶段,①提交请求;②处理结果; 这两个阶段彼此不关联,而异常并不一定发生在请求提交(即调用函数)时,平常的try/catch并不能有效的捕捉到程序的异常

函数式编程

高阶函数

  • 高阶函数与普通函数不同的地方是高阶函数可以把函数作为参数,或者是将函数作为返回值
  • 函数作为参数;函数作为返回值;
1
2
3
4
5
6
//高阶函数test的返回值是一个匿名函数
function test(v){
return function(){
return v;
}
}

偏函数

  • 一个创建函数的工厂函数;通过指定部分参数,定制新的函数
  • 假设有一个参数或变量已经预置的函数A,我们通过调用A来产生一个新的函数B,函数B就是我们说的偏函数
1
2
3
4
5
6
7
8
//isType函数中预置了判断类型的方法,只指定部分参数来产生的新的定制的函数isString和isFunction就是偏函数
var isType = function(type){
return function(obj){
return toString.call(obj)=='[object '+type+']';
}
};
var isString = isType('String');
var isFunction = isType('Function');

编写偏函数

1
2
3
4
5
6
7
8
var say =function(name){
return function(text){
console.log(name+' say '+text);
}
};
var tomSay = say('tom');

tomSay ('hello');

方法说明

series

它是控制异步函数按照串行顺序执行,只有前一个执行完毕,才能执行下一个异步调用

1
2
3
4
5
6
7
8
9
10
11
12
async.series([function(cb){
setTimeout(function(){
cb(null,1);
},1000)
},function(cb){
setTimeout(cb,1000,null,2);
}],function(err,result){ //result是每个回调函数传进来的data参数,result=[1,2]
if (err)
console.error(err);
else
console.log(result);
})

parallel

parallel的用法和series类似。只是数组中的函数是并行执行,parallel的总时间取决于运行时间最长的函数。而最终的回调函数里result的值是按照数组中函数的顺序排列的

waterfall

和series函数有很多相似之处,都是按照顺序执行。
不同之处是waterfall每个函数产生的值,都将传给下一个函数,而series则没有这个功能

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
async.waterfall([function(cb){
setTimeout(function(){
cb(null,1);
},1000)
},function(data,cb){
setTimeout(cb,1000,null,data+"+"+"2");
}],function(err,result){ //result = "1+2"
if (err)
console.error(err);
else
console.log(result);
})
~~~

## parallelLimit(tasks, limit, [callback])
parallelLimit函数和parallel类似,但是它多了一个参数limit。 limit参数限制任务只能同时并发一定数量,而不是无限制并发

## whilst(test, fn, callback)
相当于while,但其中的异步调用将在完成后才会进行下一次循环;test参数是一个返回布尔值结果的函数,通过返回值来决定循环是否继续,作用等同于while循环停止的条件

var count = 0;
async.whilst(
function () { return count < 5; },
function (callback) {
count++;
setTimeout(callback, 1000);
},
function (err) {

}

);

1
2
3
4
5
6

## doWhilst(fn, test, callback)
相当于do…while,较whilst而言,doWhilst交换了fn,test的参数位置,先执行一次循环,再做test判断

## until(test, fn, callback)
until与whilst正好相反,当test条件函数返回值为false时继续循环,与true时跳出。其它特性一致

var count = 5;
async.until(
function () { return count < 0; },
function (callback) {
count–;
setTimeout(callback, 1000);
},
function (err) {

}

);

1
2
3
4
5
6
7
8
9
10
11

## doUntil(fn, test, callback)
doUntil与doWhilst正好相反,当test为false时循环,与true时跳出。其它特性一致

## forever(fn, errback)
forever函数比较特殊,它的功能是无论条件如何,函数都一直循环执行,只有出现程序执行的过程中出现错误时循环才会停止,callback才会被调用

## compose(fn1, fn2...)
使用compose可以创建一个异步函数的集合函数,将传入的多个异步函数包含在其中,当我们执行这个集合函数时,会依次执行每一个异步函数,每个函数会消费上一次函数的返回值

==注意==:从内层到外层的执行的顺序;从右往左执行

var async = require(‘async’);
function fn1(n, callback) {
setTimeout(function () {
callback(null, n + 1);
}, 1000);
}
function fn2(n, callback) {
setTimeout(function () {
callback(null, n * 3);
}, 1000);
}
var demo = async.compose(fn2, fn1);
demo(4, function (err, result) {
console.log(result); //结果15
});
demo = async.compose(fn1, fn2);
demo(4, function (err, result) {
console.log(result); //结果13
});

1
2
3
4

## auto(tasks, [callback])
* 用来处理有依赖关系的多个任务的执行
* async.auto的强大是在于,你定义好相互之间的dependencies,他来帮你决定用parallel还是waterfull

async.auto({
getData: function(callback){
callback(null, ‘data’, ‘converted to array’);
},
makeFolder: function(callback){
callback(null, ‘folder’);
},
writeFile: [‘getData’, ‘makeFolder’, function(callback, results){
callback(null, ‘filename’);
}],
emailLink: [‘writeFile’, function(callback, results){
callback(null, {‘file’:results.writeFile, ‘email’:‘user@example.com’});
}]
}, function(err, results) {
console.log('err = ', err);
console.log('results = ', results);
});

1
2
3
4
5
6

## queue(worker, concurrency)
queue相当于一个加强版的parallel,主要是限制了worker数量,不再一次性全部执行。当worker数量不够用时,新加入的任务将会排队等候,直到有新的worker可用

## apply(function, arguments..)
apply是一个非常好用的函数,可以让我们给一个函数预绑定多个参数并生成一个可直接调用的新函数,简化代码

function(callback) {
test(3, callback);
};
用apply改写:
async.apply(test, 3);

1
2
3
4
5

## iterator(tasks)
* 将一组函数包装成为一个iterator,可通过next()得到以下一个函数为起点的新的iterator。该函数通常由async在内部使用,但如果需要时,也可在我们的代码中使用它
* 直接调用(),会执行当前函数,并返回一个由下个函数为起点的新的iterator。调用next(),不会执行当前函数,直接返回由下个函数为起点的新iterator
* 对于同一个iterator,多次调用next(),不会影响自己。如果只剩下一个元素,调用next()会返回null

var iter = async.iterator([
function() { console.log(‘111’) },
function() { console.log(‘222’) },
function() { console.log(‘333’) }
]);
iter();

NodeJS快速入门

发表于 2018-10-26 | 分类于 前端

http://cw.hubwiz.com/card/c/5359f6f6ec7452081a7873d8/1/1/2/

[toc]

Node中标准回调函数

1
2
3
4
5
function(err,data){

}
第一个参数为err是错误信息
第二个参数为data是返回的数据

进程管理

process是一个全局内置对象,可以在代码中的任何位置访问此对象,这个对象代表我们的node.js代码宿主的操作系统进程对象。

使用process对象可以截获进程的异常、退出等事件,也可以获取进程的当前目录、环境变量、内存占用等信息,还可以执行进程退出、工作目录切换等操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
* process.cwd();            //查看应用程序当前目录
* process.chdir("目录"); //改变应用程序目录
* stdout是标准输出流,作用就是将内容打印到输出设备上
console.log = function(d){
process.stdout.write(d+'\n');
}
* stderr是标准错误流,用来打印错误信息,可以通过它来捕获错误信息 //process.stderr.write(输入内容);
* stdin是进程的输入流,我们可以通过注册事件的方式来获取输入的内容
process.stdin.on('readable', function() {
var chunk = process.stdin.read();
if (chunk !== null) {
process.stdout.write('data: ' + chunk);
}
});
* process.exit(code); //需要在程序内杀死进程,退出程序时使用,参数code为退出后返回的代码,如果省略则默认返回0;
* process.on(); //此方法可监听进程事件
exit事件://当进程要退出之前,会触发exit事件。通过监听exit事件,我们可就以在进程退出前进行一些清理工作:
process.on("exit",function(code){//参数code表示退出码
console.log("I am tired...")//进行一些清理工作
});
uncaughtException事件: //如果进程发生了未捕捉的异常,会触发uncaughtException事件。通过监听这个事件,可以 让进程优雅的退出
process.on("uncaughtException",function(err){
console.log(err);
});
throw new Error("我故意的..."); //故意抛出一个异常
* 设置编码
process.stdin.setEncoding(编码);
process.stdout.setEncoding(编码);
process.stderr.setEncoding(编码);

子进程

==node.js是基于单线程模型架构==,这样的设计可以带来高效的CPU利用率,但是无法却利用多个核心的CPU,为了解决这个问题,node.js提供了==child_process模块,通过多进程来实现对多核CPU的利用==

child_process模块提供了四个创建子进程的函数,分别是spawn,exec,execFile和fork。

  1. spawn函数用给定的命令发布一个子进程,只能运行指定的程序,参数需要在列表中给出
1
2
3
4
5
6
var child_process = require('child_process');
var child = child_process.spawn( command );
child.stdout.on('data', function(data) {
console.log(data);
});
通过执行命令得到返回结果
  1. exec也是一个创建子进程的函数,与spawn函数不同它可以直接接受一个回调函数作为参数
1
2
3
4
var child_process = require('child_process');
child_process.exec( command , function(err, stdout , stderr ) {
console.log( stdout );
});
  1. execFile函数与exec函数类似,但execFile函数更显得精简,因为它可以直接执行所指定的文件
1
2
3
4
var child_process = require('child_process');
child_process.execFile( file , function(err, stdout , stderr ) {
console.log( stdout );
});
  1. ==fork函数可直接运行Node.js模块==,所以我们可以直接通过指定模块路径而直接进行操作.==该方法是spawn()的特殊情景,用于派生Node进程==
1
2
var child_process = require('child_process');
child_process.fork( modulePath );

文件I/O

node.js中提供一个名为fs的模块来支持I/O操作,fs模块的文件I/O是对标准POSIX函数的简单封装。

fs模块不但提供异步的文件操作,还提供相应的同步操作方法,需要指出的是,nodejs采用异步I/O正是为了避免I/O时的等待时间,提高CPU的利用率,所以在选择使用异步或同步方法的时候需要权衡取舍。


  • fs.writeFile(filename, data, callback)

异步的将数据写入一个文件
如果文件已经存在则会被替换;数据参数可以是string或者是Buffer,编码格式参数可选,默认为"utf8"

1
2
3
4
5
var fs= require("fs"); 
fs.writeFile('test.txt', 'Hello Node', function (err) {
if (err) throw err;
console.log('Saved successfully'); //文件被保存
});
  • fs.appendFile(文件名,数据,编码,回调函数(err));

将新的内容追加到已有的文件中,如果文件不存在,则会创建一个新的文件;编码格式默认为"utf8"

1
2
3
4
5
var fs= require("fs"); 
fs.appendFile('test.txt', 'data to append', function (err) {
if (err) throw err;
console.log('The "data to append" was appended to file!');
});
  • fs.exists(文件,回调函数(exists)); //exists的回调函数只有一个参数,类型为布尔型,通过它来表示文件是否存在
  • fs.rename(旧文件,新文件,回调函数(err)); ==//修改文件名称==
  • fs.rename(oldPath,newPath,function (err); ==//移动文件==
  • fs.readFile(文件,[编码],回调函数); //读取文件内容
  • fs.unlink(文件,回调函数(err)); ==//删除文件==
  • fs.mkdir(路径,权限,回调函数(err)); //创建目录;
1
权限:默认为0777,表示文件所有者、文件所有者所在的组的用户、所有用户,都有权限进行读、写、执行的操作
  • fs.rmdir(路径,回调函数(err)); //删除目录
  • fs.readdir(目录,回调函数(err,files));//读取目录下所有的文件

url处理

node.js为互联网而生,和url打交道是无法避免的了,url模块提供一些基础的url处理。

  • url.parse(‘http://www.baidu.com’); //解析url,返回一个json格式的数组
  • url.parse(‘http://www.baidu.com?page=1’,true);//当==第二个==参数为true时,会将查询条件也解析成json格式的对象。
  • url.parse(‘http://www.baidu.com/news’,false,true);当==第三个==参数为true,解析时会将url的"//“和第一个”/"之间的部分解析为主机名
  • url.format({
    protocol: ‘http:’,
    hostname:‘www.baidu.com’,
    port:‘80’,
    pathname :’/news’,
    query:{page:1}
    }
    );
    ==作用与parse相反,它的参数是一个JSON对象,返回一个组装好的url地址==
  • url.resolve(‘http://example.com/two’, ‘/one’);//组装路径,第一个路径是开始的路径或者说当前路径,第二个则是想要去往的路径。==结果:==‘http://example.com/one’

path优化

本模块包含一套用于处理和转换文件路径的工具集,用于处理目录的对象,提高用户开发效率

  • path.normalize(’/path///normalize/hi/…’);//将不符合规范的路径经过格式化转换为标准路径,解析路径中的.与…外,还能去掉多余的斜杠
  • path.join(’///you’, ‘/are’, ‘//beautiful’);//结果:’/you/are/beautiful’。join函数将传入的多个路径拼接为标准路径并将其格式化,返回规范后的路径,避免手工拼接路径字符串的繁琐
  • path.dirname(’/foo/strong/cool/nice’); //用来返回路径中的目录名
  • basename函数可返回路径中的最后一部分,并且可以对其进行条件排除.
  1. path.basename(‘路径字符串’);
  2. path.basename(‘路径字符串’, ‘[ext]’)<排除[ext]后缀字符串>;
  • path.extname(‘index.html’); //返回路径中文件的扩展名

字符串转换

Query String模块用于==实现URL参数字符串与参数对象之间的互相转换==,提供了"stringify"、"parse"等一些实用函数来针对字符串进行处理,通过序列化和反序列化,来更好的应对实际开发中的条件需求,对于逻辑的处理也提供了很好的帮助

序列化


  • querystring.stringify({foo:‘bar’,cool:[‘xux’, ‘yys’]}); //结果:foo=bar&cool=xux&cool=yys。
1
作用就是序列化对象,也就是说将对象类型转换成一个字符串类型(默认的分割符("&")和分配符("="))
  • querystring.stringify(“对象”,“分隔符”,“分配符”)
1
2
querystring.stringify({foo:'bar',cool:['xux', 'yys']},'*','$');
结果:'foo$bar*cool$xux*cool$yys'

反序列化


  • querystring.parse(‘foo=bar&cool=xux&cool=yys’);
1
2
运行结果:{ foo: 'bar', cool: ['xux', 'yys']}
parse函数的作用就是反序列化字符串(默认是由"="、"&"拼接而成),转换得到一个对象类型
  • querystring.parse(‘foo@barcool@xuxcool@xuxcool@xuxcool@yys’,’@’,’$’);
1
运行结果:{ foo: '', bar: 'cool', xux: 'cool', yys: '' }

实用工具

util模块。util模块呢,是一个Node.js核心模块,提供常用函数的集合,用于弥补核心JavaScript的一些功能过于精简的不足。并且还提供了一系列常用工具,用来对数据的输出和验证

  • util.inspect(object,[showHidden],[depth],[colors]); //将任意对象转换为字符串的函数,通常用于调试和错误输出
  • format函数
1
2
3
1. 如果占位符没有相对应的参数,占位符将不会被替换
2. 如果有多个参数占位符,额外的参数将会调用util.inspect()转换为字符串。这些字符串被连接在一起,并且以空格分隔
3. 如果第一个参数是一个非格式化字符串,则会把所有的参数转成字符串并以空格隔开拼接在一块,而且返回该字符串
  • util.isArray(object); //判断对象是否为数组类型,是则返回ture,否则为fals
  • util.isDate(object); //判断对象是否为日期类型,是则返回ture,否则返回false
  • util.isRegExp(object); //判断对象是否为正则类型,是则返回ture,否则返回false

Node API学习

发表于 2018-10-26 | 分类于 前端

Node.js 是一个基于 Chrome V8 引擎的 JavaScript 运行环境。
Node.js 使用了一个事件驱动、非阻塞式 I/O 的模型,使其轻量又高效。
Node.js 的包管理器 npm,是全球最大的开源库生态系统。

1
2
3
4
5
6
中文网址:http://nodejs.cn/
英文网址:https://nodejs.org/en/

其他相关网址:
https://www.npmjs.com/
https://github.com/

http://nodejs.cn/api/


[toc]

node命令用法

1
node [options] [v8 options] [script.js | -e "script"] [arguments]

assert (断言)

assert 模块提供了一组简单的断言测试集合,可被用于测试不变量。 该模块在代码中可通过 require(‘assert’) 使用。 assert 不是一个测试框架,也无意成为通用的断言库。

Buffer

在 ECMAScript 2015 (ES6) 引入 TypedArray 之前,JavaScript 语言没有读取或操作二进制数据流的机制。 Buffer 类被引入作为 Node.js API 的一部分,使其可以在 TCP 流和文件系统操作等场景中处理二进制数据流。

现在 TypedArray 已经被添加进 ES6 中,Buffer 类以一种更优与更适合 Node.js 用例的方式实现了 Uint8Array API。

Buffer 类的实例类似于整数数组,除了其是大小固定的、且在 V8 堆外分配物理内存。 ==Buffer 的大小在其创建时就已确定,且不能调整大小==。

Buffer 类在 Node.js 中是一个全局变量,因此无需 require(‘buffer’).Buffer。

C/C++ 插件

Node.js 插件是用 C 或 C++ 编写的动态链接共享对象,可以使用 require() 函数加载到 Node.js 中,且像普通的 Node.js 模块一样被使用。 它们主要用于为运行于 Node.js 的 JavaScript 和 C/C++ 库之间提供接口。

  • V8:Node.js 当前用于提供 JavaScript 实现的 C++ 库。 V8 提供了用于创建对象、调用函数等机制。 V8 的 API 文档主要在 v8.h 头文件中(Node.js 源代码中的 deps/v8/include/v8.h)==(V8在线文档) https://v8docs.nodesource.com/==
  • libuv:实现了 Node.js 的事件循环、工作线程、与平台所有的的异步操作的 C 库。 它也是一个跨平台的抽象库,使所有主流操作系统中可以像 POSIX 一样访问常用的系统任务,比如与文件系统、socket、定时器和系统事件的交互。 libuv 还提供了一个类似 POSIX 多线程的线程抽象,可被用于强化更复杂的需要超越标准事件循环的异步插件。 鼓励插件开发者多思考如何通过在 libuv 的非阻塞系统操作、工作线程、或自定义的 libuv 线程中降低工作负载来避免在 I/O 或其他时间密集型任务中阻塞事件循环。
  • 内置的 Node.js 库。Node.js 自身开放了一些插件可以使用的 C/C++ API。 其中最重要的是 node::ObjectWrap 类。
  • Node.js 包含一些其他的静态链接库,如 OpenSSL。 这些库位于 Node.js 源代码中的 deps/ 目录。 只有 V8 和 OpenSSL 符号是被 Node.js 有目的地再导出,并且通过插件被用于不同的场景

child_process (子进程)

child_process 模块提供了衍生子进程的能力

Cluster

CLI(命令行选项)

Node.js 自带了各种命令行选项。 这些选项对外暴露了内置调试、多种执行脚本的方式、以及其他有用的运行时选项。

要在终端中查看本文档作为操作手册,运行 man node

console (控制台)

console 模块提供了一个简单的调试控制台,它与 Web 浏览器提供的 JavaScript 控制台的机制类似。

1
2
3
console.time(label) //启动一个定时器,用以计算一个操作的持续时间。 定时器由一个唯一的 label 标识。 当调用 console.timeEnd() 时,可以使用相同的 label 来停止定时器,并以毫秒为单位将持续时间输出到 stdout。 定时器持续时间精确到亚毫秒。
console.timeEnd(label) //停止之前通过调用 console.time() 启动的定时器,并打印结果到 stdout
console.trace(message[, ...args]) //打印字符串 'Trace :' 到 stderr ,并通过 util.format() 格式化消息与堆栈跟踪在代码中的当前位置。

Crypto (加密)

debugger (调试器)

Node.js 包含一个进程外的调试工具,可以通过基于 TCP 的协议和内置调试客户端访问。 要使用它,可以带上 debug 参数启动 Node.js,并带上需要调试的脚本的路径;然后会显示一个提示,表明成功启动调试器

DNS (域名服务器)

Error 错误

Node.js 中运行的应用程序一般会遇到以下四类错误:

  • 标准的 JavaScript 错误:
    1. : 当调用 eval() 失败时抛出。
    2. : 当 JavaScript 语法错误时抛出。
    3. : 当一个值不在预期范围内时抛出。
    4. : 当使用未定义的变量时抛出。
    5. : 当传入错误类型的参数时抛出。
    6. : 当一个全局的 URI 处理函数被误用时抛出。
  • 由底层操作系的触发的系统错误,例如试图打开一个不存在的文件、试图向一个已关闭的 socket 发送数据等
  • 由应用程序代码触发的用户自定义的错误。
  • 断言错误是错误的一个特殊的类,每当 Node.js 检测到一个不应该发生的异常逻辑时会触发。 这类错误通常由 assert 模块触发。

JavaScript 的 throw 机制的任何使用都会引起异常,异常必须使用 try / catch 处理,否则 Node.js 进程会立即退出。

==开发者必须查阅各个方法的文档以明确在错误发生时这些方法是如何冒泡的。==

events (事件)

大多数 Node.js 核心 API 都是采用惯用的异步事件驱动架构,其中某些类型的对象(称为触发器)会周期性地触发命名事件来调用函数对象(监听器)。

==所有能触发事件的对象都是 EventEmitter 类的实例==。 这些对象开放了一个 eventEmitter.on() 函数,允许将一个或多个函数附加到会被对象触发的命名事件上。==当 EventEmitter 对象触发一个事件时,所有附加在特定事件上的函数都被同步地调用。==

当新的监听器被添加时,所有的 EventEmitter 会触发 ‘newListener’ 事件;当移除已存在的监听器时,则触发 ‘removeListener’。

每个事件默认可以注册最多 10 个监听器。可以通过一定方法设置。

1
2
3
4
5
6
7
8
//此方法可用来自定义事件
const EventEmitter = require('events');
class MyEmitter extends EventEmitter {}
const myEmitter = new MyEmitter();
myEmitter.on('event', () => {
console.log('发生了一个事件!');
});
myEmitter.emit('event');

异步与同步

EventListener 会按照监听器注册的顺序同步地调用所有监听器。 所以需要确保事件的正确排序且避免竞争条件或逻辑错误。监听器函数可以使用 setImmediate() 或 process.nextTick() 方法切换到异步操作模式.

  • eventEmitter.on() //监听器会在==每次==触发命名事件时被调用
  • eventEmitter.once() //注册一个对于特定事件被调用==最多一次==的监听器

错误事件

当 EventEmitter 实例中发生错误时,会触发一个 ‘error’ 事件.为了防止 Node.js 进程崩溃,可以在 process 对象的 uncaughtException 事件上注册监听器

fs (文件系统)

文件 I/O 是由简单封装的标准 POSIX 函数提供的。 通过 require(‘fs’) 使用该模块。 ==所有的方法都有异步和同步的形式==

异步形式始终以完成==回调作为它最后一个参数==。 传给完成回调的参数取决于具体方法,但==第一个参数总是留给异常==。 如果操作成功完成,则第一个参数会是 null 或 undefined。

当使用同步形式时,任何异常都会被立即抛出。 可以使用 try/catch 来处理异常,或让它们往上冒泡。

同步和异步的比较

1
2
3
4
5
6
7
8
9
10
11
//同步的方式
const fs = require('fs');
fs.unlinkSync('/tmp/hello');
console.log('successfully deleted /tmp/hello');

//异步方式
const fs = require('fs');
fs.unlink('/tmp/hello', (err) => {
if (err) throw err;
console.log('successfully deleted /tmp/hello');
});
  • 异步方法不保证执行顺序
  • 异步方法之间如果有执行顺序,则正确的方法是把回调链起来
  • 强烈推荐开发者使用这些函数的异步版本。 同步版本会阻塞整个进程,直到它们完成

全局变量

1
2
3
4
5
* __dirname     //当前模块的目录名。 等同于 __filename 的 path.dirname()
* __filename //当前模块的文件名。 这是当前模块文件的解析后的绝对路径
* exports //module.exports 的一个简短的引用
* module //当前模块的引用。 具体地说,module.exports 用于定义一个模块导出什么,且通过 require() 引入
* require.resolve() //使用内部的 require() 机制来查找模块的位置,但不会加载模块,只返回解析后的文件名

http

Node.js 中的 HTTP 接口被设计为支持以往较难使用的协议的许多特性。 比如,大块编码的消息。 该接口从不缓存整个请求或响应,所以用户能够流化数据。

https

HTTPS 是 HTTP 基于 TLS/SSL 的版本。在 Node.js 中,它被实现为一个独立的模块。

module (模块)

Node.js 有一个简单的模块加载系统。 在 Node.js 中,文件和模块是一一对应的(每个文件被视为一个独立的模块)

Express入门

发表于 2018-10-26 | 分类于 前端
1
中文网站:http://www.expressjs.com.cn/

安装

  • 方法1:命令行模式
1
2
3
4
5
1. 确定已安装nodejs
2. mkdir 项目名称;cd 项目名称
3. npm init //为应用创建一个 package.json 文件,然后根据提示操作或者一路回车
4. npm install express --save //安装 Express 并将其保存到依赖列表中
5. 新建index.js文件进行编写
  • 方法2:webstorm
1
2
1. npm install express -g //全局安装express
2. webstorm中选择Node.js Expresss App,然后创建
  • 方法3:Express 应用生成器
1
2
3
4
1. 安装生成器:npm install express-generator -g
2. 查看express 命令的用法:express -h
3. 创建项目:express 项目名称
4. 安装依赖:npm install
  • 运行:node index.js
  • 访问: http://localhost:3000/

静态文件

  • 通过 Express 内置的 express.static 可以方便地托管静态文件,例如图片、CSS、JavaScript 文件等
  • 将静态资源文件所在的目录作为参数传递给 express.static 中间件就可以提供静态资源文件的访问了,如:app.use(express.static(‘public’)),这样public下面的文件就可以直接访问了,如:http://localhost:3000/images/kitten.jpg
  • 通过为静态资源目录指定一个挂载路径的方式来实现访问的文件都存放在一个“虚拟(virtual)”目录(即目录根本不存在),如:
1
2
* app.use('/static', express.static('public'));
* 访问:http://localhost:3000/static/images/kitten.jpg

常见问题

  1. 如何处理 404
1
Express 执行了所有中间件、路由之后还是没有获取到任何输出。你所需要做的就是在其所有他中间件的后面添加一个处理 404 的中间件
  1. 如何设置一个错误处理器
1
错误处理器中间件的定义和其他中间件一样,唯一的区别是 4 个而不是 3 个参数,即 (err, req, res, next)
  1. 如何渲染纯 HTML 文件
1
无需通过 res.render() 渲染 HTML。你可以通过 res.sendFile() 直接对外输出 HTML 文件。如果你需要对外提供的资源文件很多,可以使用 express.static() 中间件

路由

学习网址:http://www.expressjs.com.cn/guide/routing.html

  • 路由(Routing)是由一个 URI(或者叫路径)和一个特定的 HTTP 方法(GET、POST 等)组成的,涉及到应用如何响应客户端对某个网站节点的访问
  • 每一个路由都可以有一个或者多个处理器函数,当匹配到路由时,这个/些函数将被执行
  • 路由的定义由如下结构组成:
1
2
3
4
5
6
app.METHOD(PATH, HANDLER)
其中,
app 是一个 express 实例;
METHOD 是某个 HTTP 请求方式中的一个;
PATH 是服务器端的路径;
HANDLER 是当路由匹配到时需要执行的函数
  • app.all() 是一个特殊的路由方法,没有任何 HTTP 方法与其对应,它的作用是对于一个路径上的所有请求加载中间件
1
2
3
4
5
//来自 “/secret” 的请求,不管使用 GET、POST、PUT等方法请求,句柄都会得到执行
app.all('/secret', function (req, res, next) {
console.log('Accessing the secret section ...');
next(); // pass control to the next handler
});
  1. 路由路径:路由路径和请求方法一起定义了请求的端点,它可以是字符串、字符串模式或者正则表达式
  2. 路由句柄:可以为请求处理提供多个回调函数,其行为类似 中间件;路由句柄有多种形式,可以是一个函数、一个函数数组,或者是两者混合
1
2
3
4
5
6
7
8
9
10
11
12
13
app.get('/example/b', function (req, res, next) {
console.log('response will be sent by the next function ...');
next();
}, function (req, res) {
res.send('Hello from B!');
});
//混合方式
app.get('/example/d', [cb0, cb1], function (req, res, next) {
console.log('response will be sent by the next function ...');
next();
}, function (req, res) {
res.send('Hello from D!');
});
  1. 响应方法:下表中响应对象(res)的方法向客户端返回响应,终结请求响应的循环。如果在路由句柄中一个方法也不调用,来自客户端的请求会一直挂起
方法 描述
res.download() 提示下载文件。
res.end() 终结响应处理流程。
res.json() 发送一个 JSON 格式的响应。
res.jsonp() 发送一个支持 JSONP 的 JSON 格式的响应。
res.redirect() 重定向请求。
res.render() 渲染视图模板。
res.send() 发送各种类型的响应。
res.sendFile 以八位字节流的形式发送文件。
res.sendStatus() 设置响应状态代码,并将其以字符串形式作为响应体的一部分发送。
  • app.route():可使用 app.route() 创建路由路径的链式路由句柄
1
2
3
4
5
6
7
8
9
10
app.route('/book')
.get(function(req, res) {
res.send('Get a random book');
})
.post(function(req, res) {
res.send('Add a book');
})
.put(function(req, res) {
res.send('Update the book');
});
  • express.Router:可使用 express.Router 类创建模块化、可挂载的路由句柄。推荐。定于格式如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//brid.js
var express = require('express');
var router = express.Router();

// 该路由使用的中间件 可选
router.use(function timeLog(req, res, next) {
console.log('Time: ', Date.now());
next();
});

// 定义 about 页面的路由
router.get('/about', function(req, res) {
res.send('About birds');
});

module.exports = router;

//加载使用
var birds = require('./birds');
...
app.use('/birds', birds);

中间件

Express 是一个自身功能极简,完全是由路由和中间件构成一个的 web 开发框架:从本质上来说,一个 Express 应用就是在调用各种中间件

中间件(Middleware) 是一个函数,它可以访问请求对象(request object (req)), 响应对象(response object (res)), 和 web 应用中处于请求-响应循环流程中的中间件,一般被命名为 next 的变量;如果当前中间件没有终结请求-响应循环,则必须调用 next() 方法将控制权交给下一个中间件,否则请求就会挂起

中间件的功能:执行任何代码;修改请求和响应对象;终结请求-响应循环;调用堆栈中的下一个中间件

Express 应用中间件种类:

  1. 应用级中间件
  2. 路由级中间件
  3. 错误处理中间件
  4. 内置中间件
  5. 第三方中间件

应用级中间件

应用级中间件绑定到 app 对象 使用 app.use() 和 app.METHOD(), 其中, METHOD 是需要处理的 HTTP 请求的方法,例如 GET, PUT, POST 等等,全部小写

  • 中间件系统的路由句柄可以为路径定义多个路由
  • 各路由之间通过next()逐次调用
  • 调用过程中如果某个路由句柄已经终止了请求-响应循环,则后面的路由及其句柄不会执行,也不会报错
  • 如果需要在中间件栈中跳过剩余中间件,调用 next(‘route’) 方法将控制权交给下一个路由; next(‘route’) 只对使用 app.VERB() 或 router.VERB() 加载的中间件有效
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 一个中间件栈,处理指向 /user/:id 的 GET 请求
app.get('/user/:id', function (req, res, next) {
// 如果 user id 为 0, 跳到下一个路由
if (req.params.id == 0) next('route');
// 否则将控制权交给栈中下一个中间件
else next(); //
}, function (req, res, next) {
// 渲染常规页面
res.render('regular');
});

// 处理 /user/:id, 渲染一个特殊页面
app.get('/user/:id', function (req, res, next) {
res.render('special');
});

路由级中间件

  • 路由级中间件和应用级中间件一样,只是它绑定的对象为 express.Router(),使用:var router = express.Router();
  • 路由级使用 router.use() 或 router.VERB() 加载
  • 需要将路由挂载至应用,通过 app.use挂载

错误处理中间件

错误处理中间件有 4 个参数,定义错误处理中间件时必须使用这 4 个参数。即使不需要 next 对象,也必须在签名中声明它,否则中间件会被识别为一个常规中间件,不能处理错误

内置中间件

从 4.x 版本开始,, Express 已经不再依赖 Connect 了。除了 express.static, Express 以前内置的中间件现在已经全部单独作为模块安装使用了

express.static(root, [options])

  • 参数 root 指提供静态资源的根目录
  • 可选的 options 参数拥有如下属性
属性 描述 类型 缺省值
dotfiles 是否对外输出文件名以点(.)开头的文件。可选值为 “allow”、“deny” 和 “ignore” String “ignore”
etag 是否启用 etag 生成 Boolean true
extensions 设置文件扩展名备份选项 Array []
index 发送目录索引文件,设置为 false 禁用目录索引 Mixed “index.html”
lastModified 设置 Last-Modified 头为文件在操作系统上的最后修改日期。可能值为 true 或 false Boolean true
maxAge 以毫秒或者其字符串格式设置 Cache-Control 头的 max-age 属性 Number 0
redirect 当路径为目录时,重定向至 “/” Boolean true
setHeaders 设置 HTTP 头以提供文件的函数 Function

第三方中间件

  • 通过使用第三方中间件从而为 Express 应用增加更多功能
  • 安装所需功能的 node 模块,并在应用中加载,可以在应用级加载,也可以在路由级加载

在 Express 中使用模板引擎

需要在应用中进行如下设置才能让 Express 渲染模板文件:

  • views, 放模板文件的目录,比如: app.set(‘views’, ‘./views’)
  • view engine, 模板引擎,比如: app.set(‘view engine’, ‘jade’)
  • 安装相应的模板引擎 npm 软件包:npm install jade --save

错误处理

  • 在其他 app.use() 和路由调用后,最后定义错误处理中间件
  • next() 和 next(err) 类似于 Promise.resolve() 和 Promise.reject()。它们让您可以向 Express 发信号,告诉它当前句柄执行结束并且处于什么状态。next(err) 会跳过后续句柄,除了那些用来处理错误的句柄

调试 Express

  • debug 有点像改装过的 console.log,不同的是,您不需要在生产代码中注释掉 debug。它会默认关闭,而且使用一个名为 DEBUG 的环境变量还可以打开
  • 在启动应用时,设置 DEBUG 环境变量为 express:*,可以查看 Express 中用到的所有内部日志。webstorm中默认已设置
  • 设置 DEBUG 的值为 express:router,只查看路由部分的日志;设置 DEBUG 的值为 express:application,只查看应用部分的日志

集成数据库

为 Express 应用添加连接数据库的能力,只需要加载相应数据库的 Node.js 驱动即可

MySQL

npm install mysql --save

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var mysql      = require('mysql');
var connection = mysql.createConnection({
host : 'localhost',
user : 'dbuser',
password : 's3kreee7'
});

connection.connect();

connection.query('SELECT 1 + 1 AS solution', function(err, rows, fields) {
if (err) throw err;
console.log('The solution is: ', rows[0].solution);
});

connection.end();

MongoDB

npm install mongoskin

1
2
3
4
5
6
var db = require('mongoskin').db('localhost:27017/animals');

db.collection('mamals').find().toArray(function(err, result) {
if (err) throw err;
console.log(result);
});

SQLite

npm install sqlite3

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
var sqlite3 = require('sqlite3').verbose();
var db = new sqlite3.Database(':memory:');

db.serialize(function() {

db.run('CREATE TABLE lorem (info TEXT)');
var stmt = db.prepare('INSERT INTO lorem VALUES (?)');

for (var i = 0; i < 10; i++) {
stmt.run('Ipsum ' + i);
}

stmt.finalize();

db.each('SELECT rowid AS id, info FROM lorem', function(err, row) {
console.log(row.id + ': ' + row.info);
});
});

db.close();

中文API

http://www.expressjs.com.cn/4x/api.html

gulp入门

发表于 2018-10-26 | 分类于 前端

[toc]

简介

gulpjs是一个前端构建工具,与gruntjs相比,gulpjs无需写一大堆繁杂的配置参数,API也非常简单,学习起来很容易,而且gulpjs使用的是nodejs中stream来读取和操作数据,其速度更快

1
2
3
4
5
6
Gulp官网 http://gulpjs.com/
Gulp中文网 http://www.gulpjs.com.cn/
Gulp中文文档 https://github.com/lisposter/gulp-docs-zh-cn
Gulp插件网 http://gulpjs.com/plugins/
Awesome Gulp https://github.com/alferov/awesome-gulp
StuQ-Gulp实战和原理解析 http://i5ting.github.io/stuq-gulp/

工作流程

gulp的使用流程一般是这样子的:

  1. 通过gulp.src()方法获取到我们想要处理的文件流,22. 把文件流通过pipe方法导入到gulp的插件中
  2. 把经过插件处理后的流再通过pipe方法导入到gulp.dest()中
  3. gulp.dest()方法则把流中的内容写入到文件中

==注意:== 给gulp.dest()传入的路径参数,只能用来指定要生成的文件的目录,而不能指定生成文件的文件名,它生成文件的文件名使用的是导入到它的文件流自身的文件名。==生成的文件名是由导入到它的文件流决定的==

安装

gulp基于node.js,要通过nodejs的npm安装gulp,所以先要安装nodejs环境

  • 全局方式:npm install -g gulp
  • gulp的项目中单独安装一次:npm install gulp
  • 安装的时候把gulp写进项目package.json文件的依赖中:npm install --save-dev gulp

在全局安装gulp后,还需要在项目中本地安装一次,是为了版本的灵活性,仅供参考

开始使用gulp

  1. 建立gulpfile.js文件
    此时我们的目录结构是这样子的:
1
2
3
4
5
├── gulpfile.js
├── node_modules
│ └── gulp
└── package.json
~

最简gulpfile.js:

1
2
3
4
var gulp = require('gulp');
gulp.task('default',function(){
console.log('hello world');
});
  1. 运行gulp任务
    切换到存放gulpfile.js文件的目录
  • 执行gulp命令:会执行任务名为default的默认任务
  • gulp task1:执行task1任务

gulpfile.js文件

全局配置config:当gulpfile.js太大时就不好维护了,此时可以将需要在gulpfile中引用的参数,放到这里,包括一些路径,功能的开关等,如:

1
2
3
4
5
6
7
8
9
10
11
12
13
module.exports = {
name : '.....',
devPath : '.....', //项目根路径,根路径下可以包含多个项目
prodPath : '....', //生产路径根路径
sassPath : '.....', //SASS包含文件路径
rmHtmlWhitespace : false,//html中是否去除空格
webpackEntry : {
index : 'index.js'//js合并
},
server : {
port : 8088
}
};

意下这里使用了module.exports,这是nodejs的语法。在gulpfile中将会用require引用config。

1
var config = require('./config');//加载项目配置

使用举例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
//引入gulp,项目文件中安装的gulp的引入方式
var gulp =require('gulp');

//引入组件
var jshint = require("gulp-jshint");
var gutil = require("gulp-util");
var sass= require("gulp-sass");
var concat = require("gulp-concat");
var uglify = require("gulp-uglify");
var rename = require("gulp-rename");

var path = require("path");
var del = require("del");

//你也许会想要在编译文件之前删除一些文件
gulp.task('clean', function(cb) {
return del(['build/**/*'], cb);
});

//检查脚本
gulp.task('lint',function () {
gulp.src('./src/javascript/**/*.js')
.pipe(jshint())
.pipe(jshint.reporter('default'));
});

//编译sass
//sass 任务会编译scss/目录下的scss文件,并把编译完成的css文件保存到/css目录中
gulp.task('sass',function () {
gulp.src("./src/scss/**/*.scss")
.pipe(sass({outputStyle: 'compact'}))
.pipe(gulp.dest("./build/css"));
});

//合并,压缩文件
//scipts 任务会合并js 目录下的所有js文件并输出到dist目录中,然后gulp会重命名。压缩合并的文件,也输出到dist/目录
gulp.task('scripts',function () {
gulp.src('./src/javascript/**/*.js')
.pipe(concat('all.js'))
.pipe(gulp.dest('./dest'))
.pipe(rename("all.min.js"))
.pipe(uglify())
.pipe(gulp.dest("./build"))
});

//这时,我们创建了一个基于其他任务的default任务。
//使用.run()方法关联和运行我们上面定义的任务,使用.watch() 方法去坚挺制定目录的文件变化,当有文件变化时,会运行回调定义的其他任务。
gulp.task('default',function(){
//将你的默认的任务代码放在这里
gulp.run('lint','sass','scripts');
//监听文件变化
gulp.watch("",function () {
gulp.run('lint','sass','scripts');
});
});

gulp的API介绍

更多API介绍: http://www.gulpjs.com.cn/docs/api/

gulp.src()

gulp.src()方法正是用来获取流的,但要注意这个流里的内容不是原始的文件流,而是一个虚拟文件对象流(Vinyl files),这个虚拟文件对象中存储着原始文件的路径、文件名、内容等信息

1
2
3
4
gulp.src(globs[, options])

* globs参数是文件匹配模式(类似正则表达式),用来匹配文件路径(包括文件名),当然这里也可以直接指定某个具体的文件路径。当有多个匹配模式时,该参数可以为一个数组
* options为可选参数
  • 当我们没有在gulp.src()方法中配置base属性时,base的默认值为通配符开始出现之前那部分路径,例如:
1
gulp.src('app/src/**/*.css') //此时base的值为 app/src

Gulp内部使用了node-glob模块来实现其文件匹配功能。我们可以使用下面这些特殊的字符来匹配我们想要的文件:

1
2
3
4
5
6
7
8
9
* 匹配文件路径中的0个或多个字符,但不会匹配路径分隔符,除非路径分隔符出现在末尾
** 匹配路径中的0个或多个目录及其子目录,需要单独出现,即它左右不能有其他东西了。如果出现在末尾,也能匹配文件。
? 匹配文件路径中的一个字符(不会匹配路径分隔符)
[...] 匹配方括号中出现的字符中的任意一个,当方括号中第一个字符为^或!时,则表示不匹配方括号中出现的其他字符中的任意一个,类似js正则表达式中的用法
!(pattern|pattern|pattern) 匹配任何与括号中给定的任一模式都不匹配的
?(pattern|pattern|pattern) 匹配括号中给定的任一模式0次或1次,类似于js正则中的(pattern|pattern|pattern)?
+(pattern|pattern|pattern) 匹配括号中给定的任一模式至少1次,类似于js正则中的(pattern|pattern|pattern)+
*(pattern|pattern|pattern) 匹配括号中给定的任一模式0次或多次,类似于js正则中的(pattern|pattern|pattern)*
@(pattern|pattern|pattern) 匹配括号中给定的任一模式1次,类似于js正则中的(pattern|pattern|pattern)

==注意:== 不能在数组中的第一个元素中使用排除模式

使用举例:

1
2
3
4
5
//使用数组的方式来匹配多种文件
gulp.src(['js/*.js','css/*.css','*.html'])

gulp.src([*.js,'!b*.js']) //匹配所有js文件,但排除掉以b开头的js文件
gulp.src(['!b*.js',*.js]) //不会排除任何文件,因为排除模式不能出现在数组的第一个元素中

gulp.dest()

gulp.dest()方法是用来写文件的,其语法为:

1
2
3
4
gulp.dest(path[,options])

* path为写入文件的路径
* options为一个可选的参数对象,通常我们不需要用到
  • 生成的文件路径是我们传入的path参数后面再加上gulp.src()中有通配符开始出现的那部分路径

gulp.task()

gulp.task方法用来定义任务,内部使用的是Orchestrator,其语法为:

1
2
3
4
gulp.task(name[, deps], fn)
* name 为任务名
* deps 是当前定义的任务需要依赖的其他任务,为一个数组。当前定义的任务会在所有依赖的任务执行完毕后才开始执行。如果没有依赖,则可省略这个参数
* fn 为任务函数,我们把任务要执行的代码都写在里面。该参数也是可选的。

使用举例:

1
2
3
gulp.task('mytask', ['array', 'of', 'task', 'names'], function() { //定义一个有依赖的任务
// Do something
});
  1. 如果任务相互之间没有依赖,任务会按你书写的顺序来执行
  2. 如果有依赖的话则会先执行依赖的任务
  3. 如果某个任务所依赖的任务是异步的,就要注意了,gulp并不会等待那个所依赖的异步任务完成,而是会接着执行后续的任务

gulp.watch()

gulp.watch()用来监视文件的变化,当文件发生变化后,我们可以利用它来执行相应的任务,例如文件压缩等。其语法为

1
2
3
4
gulp.watch(glob[, opts], tasks)
* glob 为要监视的文件匹配模式,规则和用法与gulp.src()方法中的glob相同。
* opts 为一个可选的配置对象,通常不需要用到
* tasks 为文件变化后要执行的任务,为一个数组
1
2
3
4
5
6
7
gulp.task('uglify',function(){
//do something
});
gulp.task('reload',function(){
//do something
});
gulp.watch('js/**/*.js', ['uglify','reload']);

一些常用的gulp插件

自动加载插件pulp-load-plugins

  • 这个插件能自动帮你加载package.json文件里的gulp插件
  • gulp-load-plugins是通过package.json文件来加载插件
  • gulp-load-plugins并不会一开始就加载所有package.json里的gulp插件,而是在我们需要用到某个插件的时候,才去加载那个插件
  • 定义及启用
1
2
3
var gulp = require('gulp');
//加载gulp-load-plugins插件,并马上运行它
var plugins = require('gulp-load-plugins')();
  • 使用举例:
1
plugins.rename          //gulp-rename插件的使用

重命名插件gulp-rename

1
2
3
4
5
6
gulp.task('rename', function () {
gulp.src('js/jquery.js')
.pipe(uglify()) //压缩
.pipe(rename('jquery.min.js')) //会将jquery.js重命名为jquery.min.js
.pipe(gulp.dest('js'));
});

js文件压缩插件gulp-uglify

1
2
3
4
5
6
7
8
var gulp = require('gulp'),
uglify = require("gulp-uglify");

gulp.task('minify-js', function () {
gulp.src('js/*.js') // 要压缩的js文件
.pipe(uglify()) //使用uglify进行压缩,更多配置请参考:
.pipe(gulp.dest('dist/js')); //压缩后的路径
});

css文件压缩插件gulp-minify-css

1
2
3
4
5
6
7
8
var gulp = require('gulp'),
minifyCss = require("gulp-minify-css");

gulp.task('minify-css', function () {
gulp.src('css/*.css') // 要压缩的css文件
.pipe(minifyCss()) //压缩css
.pipe(gulp.dest('dist/css'));
});

html文件压缩插件gulp-minify-html

1
2
3
4
5
6
7
8
var gulp = require('gulp'),
minifyHtml = require("gulp-minify-html");

gulp.task('minify-html', function () {
gulp.src('html/*.html') // 要压缩的html文件
.pipe(minifyHtml()) //压缩
.pipe(gulp.dest('dist/html'));
});

js代码检查插件

1
2
3
4
5
6
7
8
var gulp = require('gulp'),
jshint = require("gulp-jshint");

gulp.task('jsLint', function () {
gulp.src('js/*.js')
.pipe(jshint())
.pipe(jshint.reporter()); // 输出检查结果
});

文件合并插件gulp-concat

1
2
3
4
5
6
7
8
9
10
11
var gulp = require('gulp'),
concat = require("gulp-concat");

gulp.task('concat', function () {
gulp.src('js/*.js') //要合并的文件
.pipe(concat('all.js')) // 合并匹配到的js文件并命名为 "all.js"
.pipe(gulp.dest('dist/js'));
});
~~~

## 图片压缩插件

var gulp = require(‘gulp’);
var imagemin = require(‘gulp-imagemin’);
var pngquant = require(‘imagemin-pngquant’); //png图片压缩插件

gulp.task(‘default’, function () {
return gulp.src(‘src/images/*’)
.pipe(imagemin({
progressive: true,
use: [pngquant()] //使用pngquant来压缩png图片
}))
.pipe(gulp.dest(‘dist’));
});

grunt入门

发表于 2018-10-26 | 分类于 前端

[toc]

Grunt中文网:http://www.gruntjs.net/

简介

Grunt和 Grunt 插件是通过 npm 安装并管理的,npm是 Node.js 的包管理器。Grunt配合Node.js有相应的版本要求,如:Grunt 0.4.x 必须配合Node.js >= 0.8.0版本使用

奇数版本号的 Node.js 被认为是不稳定的开发版

Grunt-CLI

简介

安装:npm install -g grunt-cli

注意:

  1. 安装grunt-cli并不等于安装了 Grunt!
  2. Grunt CLI的任务很简单:调用与Gruntfile在同一目录中 Grunt。这样带来的好处是,允许你在同一个系统上同时安装多个版本的 Grunt。

运行原理:

  1. 每次运行grunt 时,他就利用node提供的require()系统查找本地安装的 Grunt。正是由于这一机制,你可以在项目的任意子目录中运行grunt 。
  2. 如果找到一份本地安装的 Grunt,CLI就将其加载,并传递Gruntfile中的配置信息,然后执行你所指定的任务。

一份新的 Grunt项目一般需要在你的项目中添加两份文件:package.json 和 Gruntfile

package.json:

package.json字段全解:http://blog.csdn.net/woxueliuyun/article/details/39294375

  1. 被npm用于存储项目的元数据便将此项目发布为npm模块。你可以在此文件中列出项目依赖的grunt和Grunt插件,放置于devDependencies配置段内
  2. package.json应当放置于项目的根目录中,与Gruntfile在同一目录中,并且应该与项目的源代码一起被提交
  3. 在目录(package.json所在目录)中运行npm install将依据package.json文件中所列出的每个依赖来自动安装适当版本的依赖
  4. 为项目添加package.json文件的方式:
  • 大部分 grunt-init 模版都会自动创建特定于项目的package.json文件
  • npm init命令会创建一个基本的package.json文件
  • 复制下面的案例,并根据需要做扩充,参考https://npmjs.org/doc/json.html
1
2
3
4
5
6
7
8
9
10
{
"name": "my-project-name",
"version": "0.1.0",
"devDependencies": {
"grunt": "~0.4.1",
"grunt-contrib-jshint": "~0.6.0",
"grunt-contrib-nodeunit": "~0.2.0",
"grunt-contrib-uglify": "~0.2.2"
}
}

Gruntfile:

  1. 此文件被命名为 Gruntfile.js 或 Gruntfile.coffee,用来配置或定义任务(task)并加载Grunt插件的
  2. Gruntfile.js 或 Gruntfile.coffee 文件是有效的 JavaScript 或 CoffeeScript 文件,应当放在你的项目根目录中,和package.json文件在同一目录层级
  3. Gruntfile由以下几部分构成:“wrapper” 函数;项目与任务配置;加载grunt插件和任务;自定义任务
  • "wrapper"函数:
    每一份 Gruntfile(和grunt插件)都遵循同样的格式,你所书写的Grunt代码必须放在此函数内:
1
2
3
module.exports = function(grunt) {
// Do grunt-related things in here
};
  • 项目与任务配置:大部分的Grunt任务都依赖某些配置数据,这些数据被定义在一个object内,并传递给grunt.initConfig方法,如package.json文件
  • 加载grunt插件和任务:像 concatenation、[minification]、grunt-contrib-uglify 和 linting这些常用的任务(task)都已经以grunt插件的形式被开发出来了。只要在 package.json 文件中被列为dependency(依赖)的包,并通过npm install安装之后,都可以在Gruntfile中以简单命令的形式使用:
1
2
// 加载能够提供"uglify"任务的插件。
grunt.loadNpmTasks('grunt-contrib-uglify');

注意: grunt --help 命令将列出所有可用的任务。

  • 自定义任务:通过定义 default 任务,可以让Grunt默认执行一个或多个任务

Gruntfile.js文件示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
module.exports = function(grunt) {
// Project configuration.
grunt.initConfig({
pkg: grunt.file.readJSON('package.json'), //package.json文件中的项目元数据(metadata)被导入到 Grunt 配置中
uglify: {
options: {
banner: '/*! <%= pkg.name %> <%= grunt.template.today("yyyy-mm-dd") %> */\n'
},
build: {
src: 'src/<%= pkg.name %>.js',
dest: 'build/<%= pkg.name %>.min.js'
}
}
});
// 加载包含 "uglify" 任务的插件。
grunt.loadNpmTasks('grunt-contrib-uglify');
// 默认被执行的任务列表。
grunt.registerTask('default', ['uglify']);
};

Gurnt CLI参数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
--help, -h ==Display help text
--base, -b ==Specify an alternate base path. By default, all file paths are relative to the Gruntfile.
==Alternative to grunt.file.setBase(...)
--no-color ==Disable colored output.
--gruntfile ==Specify an alternate Gruntfile.
==By default,grunt looks in the current or parent directories for the nearest Gruntfile.(js/coffee) file.
--debug, -d ==Enable debugging mode for tasks that support it.
--stack ==Print a stack trace when exiting with a warning or fatal error.
--force, -f ==A way to force your way past warnings.Want a suggestion? Don't use this option, fix your code.
--tasks ==Additional directory paths to scan for task and "extra" files.Alternative to grunt.loadTasks(...)
--npm ==Npm-installed grunt plugins to scan for task and "extra" files.Alternative to grunt.loadNpmTasks(...)
--no-write ==Disable writing files (dry run).
--verbose, -v ==Verbose mode. A lot more information output.
--version, -V ==Print the grunt version. Combine with --verbose for more info.
--completion ==Output shell auto-completion rules. See the grunt-cli documentation for more information.

安装Gurnt和Gurnt插件

安装命令

  • npm install grunt --save-dev
  • npm install grunt ==[@VERSION]== --save-dev

Gurnt插件

http://gruntjs.com/plugins

Grunt官方插件列表,其中带星号的为官方维护的插件

创建插件:

  1. 通过 npm install -g grunt-init 命令安装 grunt-init 。
  2. 通过 git clone git://github.com/gruntjs/grunt-init-gruntplugin.git ~/.grunt-init/gruntplugin 命令安装grunt插件模版。
  3. 在一个空的目录中执行 grunt-init gruntplugin 。
  4. 执行 npm install 命令以准备开发环境。
  5. 为你的插件书写代码。
  6. 执行 npm publish 命令将你创建的 Grunt 插件提发布npm

注意:

  1. grunt-contrib" 命名空间保留给 Grunt 团队维护的task使用,请给你自己的task起一个合适名字,并且避免使用被保留的命名空间
  2. Grunt默认隐藏error stack traces,但可–stack启用方便调试自己的task;在bash中可通过alias grunt='grunt --stack’创建别名默认记录下stack trace
  3. 存储任务文件:建议使用几个常用npm模块(例如 temporary、tmp)来调用操作系统级别的临时目录功能
  4. 避免改变当前工作目录:process.cwd()
  • 默认包含gruntfile文件的目录被设置为当前工作目录。用户可在自己的gruntfile中通过grunt.file.setBase()改变改变当前工作目录,但是插件不应该改变它
  • path.resolve(‘foo’) 可以被用来获取’foo’ 相对于 Gruntfile 所在目录的绝对路径
  1. Grunt常用插件
  • grunt-contrib-uglify:压缩js代码
  • grunt-contrib-concat:合并js文件
  • grunt-contrib-qunit:单元测试
  • grunt-contrib-jshint:js代码检查
  • grunt-contrib-watch:监控文件修改并重新执行注册的任务

Task

Grunt就只支持两种任务:基本的Task以及MultiTasks

区别是基本的Task的任务配置只有一个,而MultiTasks则有多个。大多数的grunt插件任务都是MultiTasks

Task的创建

  • grunt注册任务的格式:
1
grunt.registerTask(taskName, [description, ] taskList)
  • grunt默认任务:
1
2
//如果运行Grunt时没有指定任何任务,它将自动执行'jshint'、'qunit'、'concat' 和 'uglify' 任务
grunt.registerTask('default', ['jshint', 'qunit', 'concat', 'uglify']);
  • grunt任务带参数的格式:
1
grunt.registerTask('dist', ['concat:distArg', 'uglify:distArg']);
  1. 当一个基本任务执行时,Grunt并不会检查配置和环境 – 它仅仅执行指定的任务函数,并传递任何使用冒号分割的参数作为函数的参数
  2. 如果你的任务并没有遵循 “多任务” 结构,那就使用自定义任务,在一个任务内部,执行其他的任务,使用grunt.task.run(‘bar’, ‘baz’);
  3. 任务还可以依赖于其他任务的成功执行。注意 grunt.task.requires 并不会真正的运行其他任务,它仅仅检查其它任务是否已经执行,并且没有失败

Task的配置

Grunt的task配置都是在 Gruntfile 中的grunt.initConfig方法中指定的。此配置主要是以任务名称命名的属性,也可以包含其他任意数据。一旦这些代表任意数据的属性与任务所需要的属性相冲突,就将被忽略。

在一个任务配置中:

  • options属性可以用来指定覆盖内置属性的默认。
  • 每一个目标(target)中还可以拥有一个专门针对此目标(target)的options属性
  • 目标(target)级的options将会覆盖任务级的options
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
grunt.initConfig({
concat: { =================Task
options: {
// 这里是任务级的Options,覆盖默认值
},
foo: { =================Target,并非子任务
options: {
// "foo" target options may go here, overriding task-level options.
},
},
bar: {
// No options specified; this target will use task-level options.
},
},
});

文件

由于大多的任务都是执行文件操作,Grunt有一个强大的抽象层用于声明任务应该操作哪些文件。这里有好几种定义src-dest(源文件-目标文件)文件映射的方式,均提供了不同程度的描述和控制操作方式。任何一种多任务(multi-task)都能理解下面的格式,所以你只需要选择满足你需求的格式就行。

详见:http://www.gruntjs.net/configuring-tasks

项目实战

Nodejs和CLI安装好之后,参考:http://www.bluesdream.com/blog/windows-installs-the-grunt-and-instructions.html

  1. mkdir testProject -> cd testProject
  2. 创建package.json文件

package.json官方文档:https://docs.npmjs.com/json

  • A: npm init ==自动创建pachage.json文件
  • B: 手动创建package.json文件,添加项目/模块的描述信息
  1. 安装Grunt和Grunt插件:
  • 手动添加,修改package.json文件,然后执行npm install
    {
    “name”: “my-project”,
    “version”: “0.1.0”,
    “devDependencies”: {
    “grunt”: “~0.4.1”,
    “grunt-contrib-cssmin”: “~0.7.0” //其中"~0.7.0"代表安装该插件的某个特定版本,如果只需安装最新版本,可以改成"*"
    }
    }
  • 自动安装: 其中–save-dev,表示将它作为你的项目依赖添加到package.json文件中devDependencies内
1
2
npm install grunt --save-dev //安装最新版的Grunt
npm install grunt-contrib-cssmin --save-dev //安装我们所需要的插件
  1. 创建Gruntfile.js文件:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
module.exports = function(grunt) {
// 配置任务参数
grunt.initConfig({
pkg: grunt.file.readJSON('package.json'),
cssmin: {
combine: {
files: {
'css/release/compress.css': ['css/*.css'] // 指定合并的CSS文件 ['css/base.css', 'css/global.css']
}
},
minify: {
options: {
keepSpecialComments: 0, /* 删除所有注释 */
banner: '/* minified css file */'
},
files: {
'css/release/master.min.css': ['css/master.css']
}
}
}
});
// 插件加载(加载 "cssmin" 模块)
grunt.loadNpmTasks('grunt-contrib-cssmin');
// 自定义任务:通过定义 default 任务,可以让Grunt默认执行一个或多个任务。
grunt.registerTask('default', ['cssmin']);
};
  1. 执行:
  • grunt //执行配置中所有的任务
  • grunt cssmin //执行特定的任务
  1. 测试:
  • 在项目文件夹中创建个子文件夹,命名为:CSS
  • 在里面创建base.css和master.css,2个CSS文件,你可以随便写点内容在里面。
  • 在命令行中执行grunt,看到如下提示说明执行成功:
1
2
3
4
5
Running "cssmin:combine" (cssmin) task
File css/release/compress.css created.
Running "cssmin:minify" (cssmin) task
File css/release/master.min.css created.
Done, without errors.

JSDoc&Grunt

grunt-jsdoc是一个Grunt的插件。这个插件集成了JsDoc Toolkit 3,并且你能够通过配置Grunt任务来生成API文档

补充:grunt-jsdoc-plugin是同一个开发者,但是区别是grunt-jsdoc是基于JsDoc Toolkit 3而grunt-jsdoc-plugin是基于JsDoc Toolkit 2的

安装:

  1. 已安装好JAVA且配置好了Java环境变量
  2. npm install grunt-jsdoc --save-dev //安装jsdoc插件

grunt-jsdoc的grunt任务配置

1
2
3
4
5
6
7
8
9
10
grunt.initConfig({
jsdoc : {
dist : {
src: ['src/*.js', 'test/*.js'],
options: {
destination: 'doc'
}
}
}
});

参数说明:

1
2
3
4
5
6
7
src: 要自动生成API文档的源文件路径数组
jsdoc: jsdoc的bin文件夹目录
options: jsdoc单独使用的配置项
destination: 必填,指定文档输出路径
configure: jsdoc配置文件路径
template: 文档模板路径
private: 是否在文档中输出private成员,默认为true

更多参数:参考官方文档:Command-line arguments to JSDoc: http://usejsdoc.org/about-commandline.html

Grunt.js和Gulp.js工作方式的区别

  • Grunt主要是以文件为媒介来运行它的工作流的,比如在Grunt中执行完一项任务后,会把结果写入到一个临时文件中,然后可以在这个临时文件内容的基础上执行其它任务,执行完成后又把结果写入到临时文件中,然后又以这个为基础继续执行其它任务…就这样反复下去。
  • 在Gulp中,使用的是Nodejs中的stream(流),首先获取到需要的stream,然后可以通过stream的pipe()方法把流导入到你想要的地方,比如Gulp的插件中,经过插件处理后的流又可以继续导入到其他插件中,当然也可以把流写入到文件中。所以Gulp是以stream为媒介的,它不需要频繁的生成临时文件,这也是Gulp的速度比Grunt快的一个原因

webpack入门

发表于 2018-10-26 | 分类于 前端

[toc]

1
2
3
http://webpack.org/
https://github.com/webpack-china/webpack.js.org
http://www.css88.com/doc/webpack2/

简介

image
Webpack 是当下最热门的前端资源模块化管理和打包工具。它可以将许多松散的模块按照依赖和规则打包成符合生产环境部署的前端资源。还可以将按需加载的模块进行代码分隔,等到实际需要的时候再异步加载。通过 loader 的转换,任何形式的资源都可以视作模块,比如 CommonJs 模块、 AMD 模块、 ES6 模块、CSS、图片、 JSON、Coffeescript、 LESS 等。

前身叫browserify,缺点为只能转换js

概念

入口(Entry)

  • webpack 将创建所有应用程序的依赖关系图表(dependency graph)。图表的起点被称之为入口起点(entry point)
  • 入口起点告诉 webpack 从哪里开始,并遵循着依赖关系图表知道要打包什么
  • 可以将应用程序的入口起点认为是根上下文(contextual root)或 app 第一个启动文件

出口(Output)

  • 将所有的资源(assets)归拢在一起后,我们还需要告诉 webpack 在哪里打包我们的应用程序。webpack 的 output 属性描述了如何处理归拢在一起的代码(bundled code)
  • 即使可以存在多个入口起点,但只指定一个输出配置
  • 更多配置:http://www.css88.com/doc/webpack2/concepts/output/

加载器(Loader)

  • webpack 的目标是,让 webpack 聚焦于项目中的所有资源(asset),而浏览器不需要关注考虑这些(这并不意味着资源(asset)都必须打包在一起)。webpack 把每个文件(.css, .html, .scss, .jpg, etc.) 都作为模块处理。而且 webpack 只理解 JavaScript
  • webpack loader 会将这些文件转换为模块,而转换后的文件会被添加到依赖图表中
  • 在 webpack 配置中定义 loader 时,要定义在 module.rules 中,而不是 rules
  • webparck默认加载的是js,如果要加载如css,需要额外loader
  1. npm install style-loader css-loader -D
  2. 在webpack中,多个loader加载通过!连接,后面的“-loader可以省略”,如:require(“style!css!./mystyle.css”)

插件(Plugins)

想要使用一个插件,

  1. 需要 require() 它,
  2. 它添加到 plugins 数组中
  3. 多数插件可以通过选项(option)自定义
  4. 由于需要在一个配置中,多次使用一个插件,来针对不同的目的,因此你需要使用 new 来创建插件的实例,并且通过实例来调用插件

webpack 插件是一个具有 apply 属性的 JavaScript 对象。 apply 属性会被 webpack compiler 调用,并且 compiler 对象可在整个 compilation 生命周期访问

webpack.config.js示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
const HtmlWebpackPlugin = require('html-webpack-plugin'); //installed via npm
const webpack = require('webpack'); //to access built-in plugins
const path = require('path');

const config = {
entry: './path/to/my/entry/file.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'my-first-webpack.bundle.js'
},
module: {
rules: [
{test: /\.(js|jsx)$/, use: 'babel-loader'}
]
},
plugins: [
new webpack.optimize.UglifyJsPlugin(),
new HtmlWebpackPlugin({template: './src/index.html'})
]
};

module.exports = config;

webpack安装及起步

安装

  • cnpm install webpack-cli -g//webpack的cli环境
  • cnpm install webpack-dev-server //webpack的自带服务器

运行:

  1. 开发环境:webpack
  2. 生产环境:webpack -p //会压缩
  3. 监听模式:webpack -w //自动编译
  4. 开启sourcemaps:webpack -d //方便调试

起步

1
2
3
mkdir webpack-demo && cd webpack-demo
npm init -y
npm install --save-dev webpack

创建并编辑app/index.js

1
2
3
4
5
6
7
8
9
10
11
12
import _ from 'lodash';

function component () {
var element = document.createElement('div');

/* 需要引入 lodash,下一行才能正常工作 */
element.innerHTML = _.join(['Hello','webpack'], ' ');

return element;
}

document.body.appendChild(component());
1
npm install --save lodash
1
2
3
4
5
6
7
8
<html>
<head>
<title>webpack 2 demo</title>
</head>
<body>
<script src="dist/bundle.js"></script>
</body>
</html>
1
2
webpack
查看浏览器index.html页面内容:Hello webpack

#代码拆分
分离资源,实现缓存资源和并行加载资源:

  • 一个典型的应用程序,会依赖于许多提供框架/功能需求的第三方库代码。不同于应用程序代码,这些第三方库代码不会频繁修改
  • 如果我们将这些库(library)中的代码,保留到与应用程序代码相独立的 bundle 上,我们就可以利用浏览器缓存机制,把这些文件长时间的缓存到用户的机器上

CSS分割

要通过webpack打包CSS,像任何其他模块一样将CSS导入JavaScript代码,并使用css-loader(它输出CSS作为JS模块),并可选地应用ExtractTextWebpackPlugin(它提取打包的CSS并输出CSS文件

  1. 导入 CSS
  • import ‘bootstrap/dist/css/bootstrap.css’;
  1. 使用 css-loader:webpack.config.js中配置 css-loader
1
2
3
4
5
6
7
8
module.exports = {
module: {
rules: [{
test: /\.css$/,
use: 'css-loader'
}]
}
}
  1. 使用 ExtractTextWebpackPlugin
  • npm install --save-dev extract-text-webpack-plugin
  1. webpack.config.js中添加插件配置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
module.exports = {
module: {
rules: [{
test: /\.css$/,
- use: 'css-loader'
+ use: ExtractTextPlugin.extract({
+ use: 'css-loader'
+ })
}]
},
+ plugins: [
+ new ExtractTextPlugin('styles.css'),
+ ]
}

Libraries分割

默认会将库文件打包,可通过为库,如moment 添加一个单独的入口点并将其命名为 vendor 来缓解这一情况

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var path = require('path');

module.exports = function(env) {
return {
entry: {
main: './index.js',
vendor: 'moment'
},
output: {
filename: '[chunkhash].[name].js',
path: path.resolve(__dirname, 'dist')
}
}
}

运行webpakc生成了两个 bundle,都包含lodash,所以还需要插件

  • CommonsChunkPlugin:它从根本上允许我们从不同的 bundle 中提取所有的公共模块,并且将他们加入公共 bundle 中。如果公共 bundle 不存在,那么它将会创建一个出来
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
var webpack = require('webpack');
var path = require('path');

module.exports = function(env) {
return {
entry: {
main: './index.js',
vendor: 'moment'
},
output: {
filename: '[chunkhash].[name].js',
path: path.resolve(__dirname, 'dist')
},
plugins: [
new webpack.optimize.CommonsChunkPlugin({
name: 'vendor' // 指定公共 bundle 的名字。
})
]
}
}

以上完成之后,每次运行的vendor文件的hash码会改变,需在plugins配置如下:

1
2
3
4
5
plugins: [
new webpack.optimize.CommonsChunkPlugin({
names: ['vendor', 'manifest'] // 指定公共 bundle 的名字
})
]

将运行时代码提取到一个单独的 manifest 文件中就解决了

生产环境构建

  1. 自动方式
  • 运行webpack -p (也可以运行 webpack --optimize-minimize --define process.env.NODE_ENV="‘production’", 他们是等效的). 它会执行如下步骤:
    • 使用UglifyJsPlugin进行 JS文件压缩
    • 运行LoaderOptionsPlugin
    • 设置Node环境变量
  1. 手动方式: 为多环境配置Webpack
    编写一个基本配置文件,把所有公用的功能放在里面。再编写特定环境的文件,使用’webpack-merge’来合并他们

base.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
module.exports = function() {
return {
entry: {
'vendor': './src/vendor.ts',
'main': './src/main.ts'

},
output: {
path: path.join(__dirname, '/../dist/assets'),
filename: '[name].bundle.js',
publicPath: publicPath,
sourceMapFilename: '[name].map'
},
resolve: {
extensions: ['', '.js', '.json'],
modules: [path.join(__dirname, 'src'), 'node_modules']

},
module: {
loaders: [{
test: /\.css$/,
loaders: ['to-string-loader', 'css-loader']
}, {
test: /\.(jpg|png|gif)$/,
loader: 'file-loader'
}, {
test: /\.(woff|woff2|eot|ttf|svg)$/,
loader: 'url-loader?limit=100000'
}],
},
plugins: [
new ForkCheckerPlugin(),

new webpack.optimize.CommonsChunkPlugin({
name: ['polyfills', 'vendor'].reverse()
}),
new HtmlWebpackPlugin({
template: 'src/index.html',
chunksSortMode: 'dependency'
})
],
};
}

使用’webpack-merge’合并这个基础配置和针对环境的特定的配置

prod.js (updated)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
const webpackMerge = require('webpack-merge');

const commonConfig = require('./base.js');

module.exports = function(env) {
return webpackMerge(commonConfig(), {
plugins: [
new webpack.LoaderOptionsPlugin({
minimize: true,
debug: false
}),
new webpack.DefinePlugin({
'process.env': {
'NODE_ENV': JSON.stringify('prod')
}
}),
new webpack.optimize.UglifyJsPlugin({
beautify: false,
mangle: {
screw_ie8: true,
keep_fnames: true
},
compress: {
screw_ie8: true
},
comments: false
})
]
})
}

缓存

这一块没有理解,需重新看
为了能够长期缓存webpack生成的静态资源:

  1. 使用[chunkhash]向每个文件添加一个依赖于内容的缓存杀手(cache-buster)
  2. 将webpack mainfest提取到一个单独的文件中去
  3. 对于一组依赖关系相同的资源,确保包含引导代码的入口起点模块(entrychunk)不会随时间改变它的哈希值
  4. 当需要在HTML中加载资源时,使用编译器统计信息(compiler stats)来获取文件名
  5. 生成模块清单(chunk manifest)的JSON内容,并在页面资源加载之前内联进HTML中去
  • 将开发和生产模式的配置分开,并在开发模式中使用[name].js的文件名, 在生产模式中使用[name].[chunkhash].js文件名
  • 为了在HTML中引用正确的文件,因为有hash生存文件名的一部分,可以使用下面这个插件,从webpack编译统计中提取:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// webpack.config.js
const path = require("path");

module.exports = {
plugins: [
function() {
this.plugin("done", function(stats) {
require("fs").writeFileSync(
path.join(__dirname, "build", "stats.json"),
JSON.stringify(stats.toJson()));
});
}
]
};

或者使用插件:https://www.npmjs.com/package/webpack-manifest-plugin

开发

调整你的文本编辑器

  • 一些文本编辑器有“safe write”(安全写入)功能,并且默认启用。因此,保存文件后并不总是会导致 webpack 重新编译
  • WebStorm - 在 Preferences > Appearance & Behavior > System Settings 中取消选中 Use “safe write”

Source Maps

更多配置:http://www.css88.com/doc/webpack2/configuration/devtool/

1
devtool:'source-map'

选择一个工具

  • webpack 可以在 watch mode(监视模式)下使用。在这种模式下,webpack 将监视您的文件,并在更改时重新编译
  • webpack-dev-server 提供了一个易于部署的开发服务器,具有快速的实时重载(live reloading)功能
  • 如果你已经有一个开发服务器并且需要完全的灵活性,可以使用 webpack-dev-middleware 作为中间件

webpack-dev-server

  1. npm install webpack-dev-server --save-dev
  2. webpack-dev-server --open

webpack-dev-middleware

webpack-dev-middleware 适用于基于链接的中间件环境(connect-based middleware stacks)。如果你已经有一个 Node.js 服务器或者你想要完全控制服务器,这将很实用

  1. npm install express webpack-dev-middleware --save-dev
  2. 使用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var express = require("express");
var webpackDevMiddleware = require("webpack-dev-middleware");
var webpack = require("webpack");
var webpackConfig = require("./webpack.config");

var app = express();
var compiler = webpack(webpackConfig);

app.use(webpackDevMiddleware(compiler, {
publicPath: "/" // 大部分情况下和 `output.publicPath`相同
}));

app.listen(3000, function () {
console.log("Listening on port 3000!");
});

根据你在 output.publicPath 和 output.filename 中设置的内容,你的 bundle 现在应该在 http://localhost:3000/bundle.js 中可以看到了
3. 默认情况下会使用watch mode。也可以使用 lazy mode,这使得 webpack 只在对入口点进行请求时再进行重新编译

1
2
3
4
app.use(webpackDevMiddleware(compiler, {
lazy: true,
filename: "bundle.js" // Same as `output.filename` in most cases.
}));
  1. 命令说明
1
2
3
4
* webpack-dev-server  //默认8080
* webpack-dev-server --port 8088
* webpack-dev-server --inline //改变代码之后,自动刷新浏览器
* webpack-dev-server --hot //热重载(局部更改)
  1. 此功能设置在webpack.config.js配置文件中如下:
1
2
3
4
devServer:{
port:8088,
inline:true
}
  1. 也可以配置在package.json文件中,如:
1
2
3
4
5
"scripts":{
"dev":"webpack-dev-server --port 8088 --inline --hot"
}

$ run npm dev
  1. resolve配置
  • 配置扩展名,即代码中引用的时候可以省略后缀
1
2
3
resolve:{
"extensions":['','.js','.css','.json']
}

配合babel的使用

以下为react配合webpack的各种依赖库:

  • cnpm install babel-core -D
  • cnpm install babel-preset-es2015 --save-dev
  • cnpm install babel-loader -D

设置js的转换

  1. 通过weback.config.js设置
1
2
3
4
5
6
7
8
9
10
11
12
moudule:{
loaders:[
{
test:/\.js$/,
loader:'babel',
exclude:/node_moudules/
}
]
},
babel:{
"presets":['es2015']
}
  1. 通过.babelrc文件,文件内容为:
1
2
3
{
"presets":["es2015"]
}

配合react使用

前提:配合babel的配置已经安装

  • cnpm install babel-preset-react -D //babel的react预设,babel可以给其他用,react是支持的一种
  • cnpm install react-hot-loader
  • 设置预设.babelrc
1
2
3
4
5
6
{
"presets":[
["es2015"],
["react"]
]
}
  • 预设webpack.config

Vue入门

发表于 2018-10-26 | 分类于 前端
1
2
3
https://cn.vuejs.org/
https://vuejs.org/
https://cn.vuejs.org/v2/api/

简介

Vue.js是当下很火的一个JavaScript MVVM库,它是以数据驱动和组件化的思想构建的。相比于Angular.js,Vue.js提供了更加简洁、更易于理解的。是个人维护项目

Vue.js是数据驱动的,你无需手动操作DOM

通过一些特殊的HTML语法,将DOM和数据绑定起来。一旦你创建了绑定,DOM将和数据保持同步,每当变更了数据,DOM也会相应地更新

使用Vue.js时,也可以结合其他库一起使用,比如jQuery

使用Vue的过程就是定义MVVM各个组成部分的过程的过程

  1. 定义View
1
2
3
<div id="app">
{{ message }}
</div>
  1. 定义Model,如:
1
2
3
var exampleData = {
message: 'Hello World!'
}
  1. 创建一个Vue实例或"ViewModel",它用于连接View和Model
1
2
3
4
new Vue({
el: '#app',
data: exampleData
})

Vue实例

构造器

每个 Vue.js 应用都是通过构造函数 Vue 创建一个 Vue 的根实例 启动的:

1
2
3
var vm = new Vue({
// 选项
})

实例化 Vue 时,需要传入一个选项对象,它可以包含数据、模板、挂载元素、方法、生命周期钩子等选项,具体API查看:https://cn.vuejs.org/v2/api/

属性和方法

  • 每个 Vue 实例都会代理其 data 对象里所有的属性
  • ==只有这些被代理的属性是响应的==
  • 如果在实例创建之后添加新的属性到实例上,它不会触发视图更新
  • Vue 实例暴露了一些有用的实例属性与方法。这些属性与方法都有前缀 $,以便与代理的 data 属性区分

实例生命周期

每个 Vue 实例在被创建之前都要经过一系列的初始化过程

image

Vue.js的常用指令

Vue.js的指令是以v-开头的,它们作用于HTML元素,指令提供了一些特殊的特性,将指令绑定在元素上时,指令会为绑定的目标元素添加一些特殊的行为,我们可以将指令看作特殊的HTML特性(attribute)

  • 用 key 管理可复用的元素:添加一个具有唯一值的 key 属性,来声明“这两个元素是完全独立的——不要复用它们”

Vue.js提供了一些常用的内置指令,接下来我们将介绍以下几个内置指令:

v-if指令

条件渲染指令,它根据表达式的真假来删除和插入元素

1
<h1 v-if="age >= 25">Age: {{ age }}</h1>

v-show指令

控制显示/隐藏,true/false

  • 和v-if指令不同的是,使用v-show指令的元素始终会被渲染到HTML,它只是简单地为元素设置CSS的style属性
1
<h1 v-show="age >= 25">Age: {{ age }}</h1>

v-else指令

  • 可以用v-else指令为v-if或v-show添加一个“else块”
  • v-else元素必须立即跟在v-if或v-show元素的后面——否则它不能被识别
1
2
<h1 v-show="name.indexOf('keep') >= 0">Name: {{ name }}</h1>
<h1 v-else>Sex: {{ sex }}</h1>

v-for指令

1
2
3
4
5
6
7
8
<ul>
<li v-for="value in json">
{{value}} {{$index}} {{$key}}
</li>
<li v-for="(k,v) in json">
{{k}} {{v}} {{$index}} {{$key}}
</li>
</ul>

v-bind指令

  • 如果属性中要绑定Vue数据,最好用绑定的方式
  • v-bind指令可以在其名称后面带一个参数,中间放一个冒号隔开,这个参数通常是HTML元素的特性(attribute)
1
<img v-bind:src="{{url}} alt=""/>"//后台不会报错误,不绑定后台会报错,界面不影响
  • class

用法一:其值为数组形式,数组中的值为Vue的data中定义的属性,而vue中属性对应的值为真正的css样式

1
2
3
4
5
6
7
8
9
10
11
12
13
.astyle{ color:red}
.bstyle{ background-color:bule}
<script>
new Vue(){
data:{
a:"astyle",
b:"bstyle"
}
}
</script>
<div id="box">
<strong :class="[a,b]">测试文字</strong>
</div>

方式二:其值为json格式,json的key为真正的css样式名称,value为true/false/data中的属性

1
2
3
<div id="box">
<strong :class="{astyle:true,bstyle:a}">测试文字</strong>
</div>

方式三:class的值直接是data的一个json数据

1
2
3
4
5
6
7
8
9
10
11
new Vue(){
data:{
jsonData:{
astyle:true,
bstyle:false
}
}
}
<div id="box">
<strong :class="jsonData">测试文字</strong>
</div>
  • style:复合样式采用的是驼峰命名法

方法一:

1
<strong :style="color:red">文字</strong>

方式二:

1
2
3
4
5
6
7
8
9
new Vue(){
data:{
astyle:{color:'red'},
bstyle:{backgroudColor:'blue'}
}
}
<div id="box">
<strong :style="[astyle,bstyle]]">测试文字</strong>
</div>

方式三:官方推荐

1
2
3
4
5
6
7
8
9
10
11
new Vue(){
data:{
jsonData:{
color:'red',
backgroudColor:'blue'
}
}
}
<div id="box">
<strong :style="jsonData">测试文字</strong>
</div>

v-on指令

用于监听DOM事件

1
2
3
4
5
6
7
8
9
<script>
new Vue(){
methods:{
show:function(){alert(124);}
}
}
</script>

<input type="button" value="弹框" v-on:click="show()"

==知识点==:v-bind指令可以缩写为一个冒号,v-on指令可以缩写为@符号

v-model

表单元素的双向绑定,它会根据控件类型自动选取正确的方法来更新元素

1
2
<input v-model="message.trim" placeholder="edit me">
<p>Message is: {{ message }}</p>
  • .number:将用户的输入值转为 Number 类型(如果原值的转换结果为 NaN 则返回原值),如:
  • .trim:自动过滤用户输入的首尾空格
  • .lazy:在默认情况下, v-model 在 input 事件中同步输入框的值与数据 ,但可以添加一个修饰符 lazy ,从而转变为在 change 事件中同步

事件

事件修饰符

1
2
3
4
5
6
7
8
9
10
11
12
<!-- 阻止单击事件冒泡 -->
<a v-on:click.stop="doThis"></a>
<!-- 提交事件不再重载页面 -->
<form v-on:submit.prevent="onSubmit"></form>
<!-- 修饰符可以串联 -->
<a v-on:click.stop.prevent="doThat"></a>
<!-- 只有修饰符 -->
<form v-on:submit.prevent></form>
<!-- 添加事件侦听器时使用事件捕获模式 -->
<div v-on:click.capture="doThis">...</div>
<!-- 只当事件在该元素本身(而不是子元素)触发时触发回调 -->
<div v-on:click.self="doThat">...</div>

事件冒泡阻止

  1. 传递事件$event,ev.cancleBubble=true;
  2. @click.stop

默认行为

如网页中添加了右键事件后,系统还有默认右键事件

  1. 传递事件$event,ev.preventDefault();
  2. @contextmenu.prevent

键盘事件

  1. 传递事件$event,ev.keyCode,判断后进行操作
  2. @keyup.键值,如:@keyup.13
  3. @keyup.键盘键面值,如:@keyup.enter

按键修饰符

1
<input v-on:keyup.13="submit">

全部的按键别名:

1
2
3
4
5
6
7
8
9
.enter
.tab
.delete (捕获 “删除” 和 “退格” 键)
.esc
.space
.up
.down
.left
.right

模版语法

msg类似为js变量,Mustache中可以进行JS编程,如申明变量,条件判断等

1
2
3
* 数据绑定最常见的形式就是使用 “Mustache” 语法(双大括号)的文本插值:{{msg}} 数据更新模版变化
* {{*msg}} 只绑定一次
* {{{msg}}} HTML转义,html语法会翻译

计算属性

  • 在模板中放入太多的逻辑会让模板过重且难以维护,应当考虑使用计算属性
  • 可以像绑定普通属性一样在模板中绑定计算属性
  • 计算属性是基于它们的依赖进行缓存的。计算属性只有在它的相关依赖发生改变时才会重新求值,==这也是计算属性和methods的区别,需依据具体情况使用==
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<div id="example">
<p>Original message: "{{ message }}"</p>
<p>Computed reversed message: "{{ reversedMessage }}"</p>
</div>

var vm = new Vue({
el: '#example',
data: {
message: 'Hello'
},
computed: {
// a computed getter
reversedMessage: function () {
// `this` points to the vm instance
return this.message.split('').reverse().join('')
}
}
});
  • 计算属性默认只有 getter ,不过在需要时你也可以提供一个 setter
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
computed: {
fullName: {
// getter
get: function () {
return this.firstName + ' ' + this.lastName
},
// setter
set: function (newValue) {
var names = newValue.split(' ')
this.firstName = names[0]
this.lastName = names[names.length - 1]
}
}
}

在运行 vm.fullName = 'John Doe' 时, setter 会被调用

过滤器

过滤模版数据

1
{{msg|filterA 参数|filterB 参数|...}}

系统默认提供过滤器,如:

1
2
3
4
5
6
* {{'welcome'|uppercase}}
* {{'welcome'|lowercase}}
* {{'welcome'|capitalize}}
* {{'welcome'|currency}}
* {{'welcome'|currency "$"}} //传参
* {{ message | filterA('arg1', arg2) }} //穿参

交互

Vue本身不支持Ajax框架,需引入官方库vue-resource,支持get、post、jsonp

  • get
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
methods:{
getFun:function(){
this.$http.get("a.txt").then(function(res){
console.log(res.data);
},function(res){
console.log(res.status);
});
},
//传递参数
get2Fun:function(){
this.$http.get("a.php",{a:1,b:2}}).then(function(res){
console.log(res.data);
},function(res){
console.log(res.status);
});
}
}
  • post
1
2
3
4
5
6
7
8
9
methods:{
postFun:function(){
this.$http.post("a.php",{a:1,b:2},{emulateJSON:true}).then(function(res){
console.log(res.data);
},function(res){
console.log(res.status);
});
}
}
  • jsonp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
methods:{
postFun:function(){
this.$http.jsonp(
"https://www.baidu....",
{a:1},
{jsonp:''cb''}//callback 名字
)
.then(function(res){
console.log(res.data);
},function(res){
console.log(res.status);
}
);
}
}

组件

使用组件

  • 要注册一个全局组件,你可以使用 Vue.component(tagName, options)。 例如:
1
2
3
Vue.component('my-component', {
// 选项
})
  • Vue.js建议自定义标签名:==小写,并且包含一个短杠==
  • 要确保在初始化根实例 之前 注册了组件
  • 局部注册
1
2
3
4
5
6
7
8
9
10
var Child = {
template: '<div>A custom component!</div>'
}
new Vue({
// ...
components: {
// <my-component> 将只在父模板可用
'my-component': Child
}
})

组件通信

在 Vue.js 中,父子组件的关系可以总结为 props down, events up 。父组件通过 props 向下传递数据给子组件,子组件通过 events 给父组件发送消息

Prop

1
2
3
4
5
6
7
8
9
Vue.component('child', {
// 声明 props
props: ['myMessage'],
// 就像 data 一样,prop 可以用在模板内
// 同样也可以在 vm 实例中像 “this.myMessage” 这样使用
template: '<span>{{ myMessage }}</span>'
})

<child my-message="hello!"></child> //传入属性值
  • HTML 特性是不区分大小写的。所以,当使用的不是字符串模版,camelCased (驼峰式) 命名的 prop 需要转换为相对应的 kebab-case (短横线隔开式) 命名
  • 每次父组件更新时,子组件的所有 prop 都会更新为最新值,不应该在子组件内部改变 prop,如果有改变的需要,可通过A.定义一个局部变量;B.定义一个计算属性

Prop验证

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
Vue.component('example', {
props: {
// 基础类型检测 (`null` 意思是任何类型都可以)
propA: Number,
// 多种类型
propB: [String, Number],
// 必传且是字符串
propC: {
type: String,
required: true
},
// 数字,有默认值
propD: {
type: Number,
default: 100
},
// 数组/对象的默认值应当由一个工厂函数返回
propE: {
type: Object,
default: function () {
return { message: 'hello' }
}
},
// 自定义验证函数
propF: {
validator: function (value) {
return value > 10
}
}
}
})

type 可以:String/Number/Boolean/Function/Object/Array

自定义事件

每个 Vue 实例都实现了事件接口(Events interface),即:

  • 使用 $on(eventName) 监听事件
  • 使用 $emit(eventName) 触发事件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<div id="counter-event-example">
<p>{{ total }}</p>
<button-counter v-on:increment="incrementTotal"></button-counter>
</div>

Vue.component('button-counter', {
template: '<button v-on:click="increment">{{ counter }}</button>',
data: function () {
return {
counter: 0
}
},
methods: {
increment: function () {
this.counter += 1
this.$emit('increment')
}
},
})
new Vue({
el: '#counter-event-example',
data: {
total: 0
},
methods: {
incrementTotal: function () {
this.total += 1
}
}
})

非父子组件通信

1
2
3
4
5
6
7
var bus = new Vue()
// 触发组件 A 中的事件
bus.$emit('id-selected', 1)
// 在组件 B 创建的钩子中监听事件
bus.$on('id-selected', function (id) {
// ...
})

使用Slot分发内容

  • 为了让组件可以组合,我们需要一种方式来混合父组件的内容与子组件自己的模板,这个过程被称为 内容分发
  • 使用特殊的 元素作为原始内容的插槽
  • 父组件模板的内容在父组件作用域内编译;子组件模板的内容在子组件作用域内编译
1
2
<!-- 无效,试图在父组件模板内将一个指令绑定到子组件的属性/方法 -->
<child-component v-show="someChildProperty"></child-component>

如果要绑定作用域内的指令到一个组件的根节点,你应当在组件自己的模板上做:

1
2
3
4
5
6
7
8
9
Vue.component('child-component', {
// 有效,因为是在正确的作用域内
template: '<div v-show="someChildProperty">Child</div>',
data: function () {
return {
someChildProperty: true
}
}
})

CommonJS规范&AMD&CMD

发表于 2018-10-26 | 分类于 前端

浏览器端的js和服务器端js都主要做了哪些事

服务器端JS 浏览器端JS
相同的代码需要多次执行 代码需要从一个服务器端分发到多个客户端执行
CPU和内存资源是瓶颈 带宽是瓶颈
加载时从磁盘中加载 加载时需要通过网络加载

CommonJS是主要为了JS在==后端的表现制定==的,他是不适合前端的;AMD(异步模块定义)出现了,它就主要==为前端JS的表现制定规范==

CommonJS规范加载模块是同步的,也就是说,只有加载完成,才能执行后面的操作。

AMD规范则是非同步加载模块,允许指定回调函数。

由于Node.js主要用于服务器编程,模块文件一般都已经存在于本地硬盘,所以加载起来比较快,不用考虑非同步加载的方式,所以CommonJS规范比较适用。但是,如果是浏览器环境,要从服务器端加载模块,这时就必须采用非同步模式,因此浏览器端一般采用AMD规范

[toc]

CommonJS

CommonJS规范: http://javascript.ruanyifeng.com/nodejs/module.html

CommonJS模块的特点如下:

1
2
3
所有代码都运行在模块作用域,不会污染全局作用域。
模块可以多次加载,但是只会在第一次加载时运行一次,然后运行结果就被缓存了,以后再加载,就直接读取缓存结果。要想让模块再次运行,必须清除缓存。
模块加载的顺序,按照其在代码中出现的顺序

CommonJS是一种规范,NodeJS是这种规范的实现

JavaScript是一个强大面向对象语言,它有很多快速高效的解释器。官方JavaScript标准定义的API是为了构建基于浏览器的应用程序。然而,并没有定于一个用于更广泛的应用程序的标准库。

CommonJS API定义很多普通应用程序(主要指非浏览器的应用)使用的API,从而填补了这个空白。它的终极目标是提供一个类似Python,Ruby和Java标准库。这样的话,开发者可以使用CommonJS API编写==应用程序==,然后这些应用可以运行在不同的JavaScript解释器和不同的主机环境中。

CommonJS定义的模块分为:模块引用(require);模块定义(exports);模块标识(module)

require

require命令用于加载文件,后缀名默认为.js

每个模块中有一个自由变量require,它是一个方法,这个方法接受一个参数,即模块的唯一ID。

CommonJS模块的加载机制是,输入的是被输出的值的拷贝。也就是说,一旦输出一个值,模块内部的变化就影响不到这个值

require根据外部模块ID,返回该模块输出的API。如果外部模块被required的时候还没有执行完,require至少应改返回该模块的exports(另一个自由变量)。如果必需的模块不存在,require方法应该抛出一个异常。

require可以有一个main属性,属性值要么为undefined,要么等于module(另一个自由变量);可以用来判断模块是直接执行,还是被调用执行。直接执行的时候(node module.js),require.main属性指向模块本身;调用执行的时候(通过require加载该脚本执行),==require.main === module== 返回false

require可以有一个paths属性,属性值为由路径字符串组成的数组,路径按优先级从高到低的顺序排列

根据参数的不同格式,require命令去不同路径寻找模块文件

1
2
3
4
5
6
* 如果参数字符串以“/”开头,则表示加载的是一个位于绝对路径的模块文
* 如果参数字符串以“./”开头,则表示加载的是一个位于相对路径的模块文件
* 如果参数字符串不以“./“或”/“开头,则表示加载的是一个默认提供的核心模块或者一个位于各级node_modules目录的已安装模块
* 如果参数字符串不以“./“或”/“开头,而且是一个路径如果参数字符串不以“./“或”/“开头,而且是一个路径,比如require('example-module/path/to/file'),则将先找到example-module的位置,然后再以它为参数,找到后续路径。
* 如果指定的模块文件没有发现,Node会尝试为文件名添加.js、.json、.node后,再去搜索
* 如果想得到require命令加载的确切文件名,使用require.resolve()方法

exports

每个模块中还有一个自由变量exports,它是一个对象,==模块对外输出的API就绑定在这个对象上==。而且==exports是模块对外输出API的唯一途径==。Node为每个模块提供一个exports变量,指向module.exports

不能直接将exports变量指向一个值,因为这样等于切断了exports与module.exports的联系

module

CommonJS规范规定,每个模块内部,module变量代表当前模块。这个变量是一个对象,它的exports属性(即module.exports)是对外的接口。==加载某个模块,其实是加载该模块的module.exports属性==

每个模块中必须有一个自由变量module,它是对象。这个对象有一个id属性,表示该模块的id,同时应该是只读属性。

module对象可以有一个uri属性,表示这个模块被加载的来源。

每个模块内部,都有一个module对象,代表当前模块。它有以下属性:

1
2
3
4
5
6
module.id 模块的识别符,通常是带有绝对路径的模块文件名。
module.filename 模块的文件名,带有绝对路径。
module.loaded 返回一个布尔值,表示模块是否已经完成加载。
module.parent 返回一个对象,表示调用该模块的模块。
module.children 返回一个数组,表示该模块要用到的其他模块。
module.exports 表示模块对外输出的值

目录的加载规则

通常,我们会把相关的文件会放在一个目录里面,便于组织。这时,最好为该目录设置一个入口文件,让require方法可以通过这个入口文件,加载整个目录

在目录中放置一个package.json文件,并且将入口文件写入main字段

如果package.json文件没有main字段,或者根本就没有package.json文件,则会加载该目录下的index.js文件或index.node文件

模块的缓存

第一次加载某个模块时,Node会缓存该模块。以后再加载该模块,就直接从缓存取出该模块的module.exports属性

所有缓存的模块保存在require.cache之中,如果想删除模块的缓存,可以像下面这样写

1
2
3
4
5
6
7
// 删除指定模块的缓存
delete require.cache[moduleName];

// 删除所有模块的缓存
Object.keys(require.cache).forEach(function(key) {
delete require.cache[key];
})

AMD

AMD就只有一个接口:define(id?,dependencies?,factory);

RequireJS就是实现了AMD规范

CMD

大名远扬的玉伯写了seajs,就是遵循他提出的CMD规范,与AMD蛮相近的,不过用起来感觉更加方便些,最重要的是中文版

middleware

发表于 2018-10-26 | 分类于 React

https://zhuanlan.zhihu.com/p/20597452

简介

  • middleware 提供了一个分类处理 action 的机会,在 middleware 中你可以检阅每一个流过的 action,挑选出特定类型的 action 进行相应操作,给你一次改变 action 的机会
  • redux 的 middleware 是为了增强 dispatch 而出现的
  • redux 提供了 applyMiddleware 这个 api 来加载 middleware

image
image

四步理解 middleware 机制

image

1
2
3
4
5
var middlewareAPI = {
getState: store.getState,
dispatch: (action) => dispatch(action)
};
chain = middlewares.map(middleware => middleware(middlewareAPI));

函数式编程思想设计 middleware

middleware 的设计有点特殊,是一个层层包裹的匿名函数,这其实是函数式编程中的柯里化 curry,一种使用匿名单参数函数来实现多参数函数的方法。applyMiddleware 会对 logger 这个 middleware 进行层层调用,动态地对 store 和 next 参数赋值

柯里化的 middleware 结构好处在于:

  1. 易串联,柯里化函数具有延迟执行的特性,通过不断柯里化形成的 middleware 可以累积参数,配合组合( compose,函数式编程的概念,Step. 2 中会介绍)的方式,很容易形成 pipeline 来处理数据流
  2. 共享store,在 applyMiddleware 执行过程中,store 还是旧的,但是因为闭包的存在,applyMiddleware 完成后,所有的 middlewares 内部拿到的 store 是最新且相同的

给 middleware 分发 store

创建一个普通的 store 通过如下方式:

1
2
3
4
5
//applyMiddleware 函数陆续获得了三个参数
//第一个是 middlewares 数组,[mid1, mid2, mid3, ...]
//第二个 next 是 Redux 原生的 createStore
//最后一个是 reducer
let newStore = applyMiddleware(mid1, mid2, mid3, ...)(createStore)(reducer, null);

组合串联 middlewares

1
dispatch = compose(...chain)(store.dispatch);

compose 是函数式编程中的组合,compose 将 chain 中的所有匿名函数,[f1, f2, … , fx, …, fn],组装成一个新的函数,即新的 dispatch,当新 dispatch 执行时,[f1, f2, … , fx, …, fn],从右到左依次执行( 所以顺序很重要)

在 middleware 中调用 dispatch

在middleware 中调用 store.dispatch() 和在其他任何地方调用效果是一样的,而在 middleware 中调用 next(),效果是进入下一个 middleware

1…101112…50
行锋

行锋

496 日志
15 分类
74 标签
GitHub E-Mail
自古写字楼如青楼,不许楼里见白头
© 2015 — 2019 行锋
博客全站共229.9k字