如果您想订阅本博客内容,每天自动发到您的邮箱中, 请点这里
NodeJs基础
nodejs的本质:不是一门新的编程语言,nodejs是javascript运行在服务端的运行环境,编程语言还是javascript
global模块-全局变量
Node.js 中的全局对象是 global, 类似于浏览器中的window
常用的global属性
console: 用于打印日志
setTimeout/clearTimeout: 设置清除延时器
setInterval/clearInterval: 设置清除定时器
__dirname: 当前文件的路径,不包括文件名
__filename: 获取当前文件的路径,包括文件名
//与模块化相关的,模块化的时候会用到
require
exports
module
除了global模块中的内容可以直接使用,其他模块都是需要加载的。
其他模块不是全局的,不能直接使用。因此需要导入才能使用。
fs模块
fileSystem-- 文件系统,提供了一系列操作文件的API接口,可以方便我读写文件
读取文件
语法:fs.readFile(path[, options], callback)
方式一:不传编码参数
//参数1: 文件的名字
//参数2: 读取文件的回调函数
//参数1:错误对象,如果读取失败,err会包含错误信息,如果读取成功,err是null
//参数2:读取成功后的数据(是一个Buffer对象)
fs.readFile("data.txt", function(err, data){
console.log(err);
console.log(data);
// 可以通过data.toString() 把二进制数据转成文本,当然前提是读取的文件本来就是文本,如果是图片获取的二进制就无法转换成文本
});
方式二:传编码参数
//参数1: 文件的路径
//参数2: 编码,如果设置了,返回一个字符串,如果没有设置,会返回一个buffer对象
//参数3: 回调函数
fs.readFile("data.txt", "utf8",function(err, data){
console.log(err);
console.log(data);
});
关于Buffer对象
1. Buffer对象是Nodejs用于处理二进制数据的。
2. 其实任意的数据在计算机底层都是二进制数据,因为计算机只认识二进制。
3. 所以读取任意的文件,返回的结果都是二进制数据,即Buffer对象
4. Buffer对象可以调用toString()方法转换成字符串。
写文件
语法:fs.writeFile(file, data[, options], callback)
//参数1:写入的文件名(如果文件不存在,会自动创建)
//参数2:写入的文件内容(注意:写入的内容会覆盖以前的内容)
//参数3:写文件后的回调函数
fs.writeFile("2.txt", "hello world, 我是一个中国人", function(err){
if(err) {
return console.log("写入文件失败", err);
}
console.log("写入文件成功");
});
1
2
3
4
5
6
7
8
9
注意:
写文件的时候,会把原来的内容给覆盖掉
追加文件
语法:fs.appendFile(path, data[, options], callback)
//参数1:追加的文件名(如果文件不存在,会自动创建)
//参数2:追加的文件内容(注意:写入的内容会覆盖以前的内容)
//参数3:追加文件后的回调函数
fs.appendFile("2.txt", "我是追加的内容", function(err){
if(err) {
return console.log("追加文件内容失败");
}
console.log("追加文件内容成功");
})
思考:如果没有appendFile,通过readFile与writeFile应该怎么实现?
文件同步与异步的说明
fs中所有的文件操作,都提供了异步和同步两种方式
异步方式:不会阻塞代码的执行
同步方式:会阻塞代码的执行
//同步方式
console.log(111);
var result = fs.readFileSync("2.txt", "utf-8");
console.log(result);
console.log(222);
总结:同步操作使用虽然简单,但是会影响性能,因此尽量使用异步方法,尤其是在工作过程中。
stream
stream是Node.js提供的又一个仅在服务区端可用的模块,目的是支持“流”这种数据结构。
什么是流?流是一种抽象的数据结构。想象水流,当在水管中流动时,就可以从某个地方(例如自来水厂)源源不断地到达另一个地方(比如你家的洗手池)。我们也可以把数据看成是数据流,比如你敲键盘的时候,就可以把每个字符依次连起来,看成字符流。这个流是从键盘输入到应用程序,实际上它还对应着一个名字:标准输入流(stdin)。
如果应用程序把字符一个一个输出到显示器上,这也可以看成是一个流,这个流也有名字:标准输出流(stdout)。流的特点是数据是有序的,而且必须依次读取,或者依次写入,不能像Array那样随机定位。
有些流用来读取数据,比如从文件读取数据时,可以打开一个文件流,然后从文件流中不断地读取数据。有些流用来写入数据,比如向文件写入数据时,只需要把数据不断地往文件流中写进去就可以了。
在Node.js中,流也是一个对象,我们只需要响应流的事件就可以了:data事件表示流的数据已经可以读取了,end事件表示这个流已经到末尾了,没有数据可以读取了,error事件表示出错了。
下面是一个从文件流读取文本内容的示例:
'use strict';
var fs = require('fs');
// 打开一个流:
var rs = fs.createReadStream('sample.txt', 'utf-8');
rs.on('data', function (chunk) {
console.log('DATA:')
console.log(chunk);
});
rs.on('end', function () {
console.log('END');
});
rs.on('error', function (err) {
console.log('ERROR: ' + err);
});
要注意,data事件可能会有多次,每次传递的chunk是流的一部分数据。
要以流的形式写入文件,只需要不断调用write()方法,最后以end()结束:
'use strict';
var fs = require('fs');
var ws1 = fs.createWriteStream('output1.txt', 'utf-8');
ws1.write('使用Stream写入文本数据...\n');
ws1.write('END.');
ws1.end();
var ws2 = fs.createWriteStream('output2.txt');
ws2.write(new Buffer('使用Stream写入二进制数据...\n', 'utf-8'));
ws2.write(new Buffer('END.', 'utf-8'));
ws2.end();
所有可以读取数据的流都继承自stream.Readable,所有可以写入的流都继承自stream.Writable。
pipe
就像可以把两个水管串成一个更长的水管一样,两个流也可以串起来。一个Readable流和一个Writable流串起来后,所有的数据自动从Readable流进入Writable流,这种操作叫pipe。
在Node.js中,Readable流有一个pipe()方法,就是用来干这件事的。
让我们用pipe()把一个文件流和另一个文件流串起来,这样源文件的所有数据就自动写入到目标文件里了,所以,这实际上是一个复制文件的程序:
'use strict';
var fs = require('fs');
var rs = fs.createReadStream('sample.txt');
var ws = fs.createWriteStream('copied.txt');
rs.pipe(ws);
默认情况下,当Readable流的数据读取完毕,end事件触发后,将自动关闭Writable流。如果我们不希望自动关闭Writable流,需要传入参数:
readable.pipe(writable, { end: false });
1
path模块
路径操作的问题
具体的说明可以参考 NodeJs学习.md
在读写文件的时候,文件路径可以写相对路径或者绝对路径
//data.txt是相对路径,读取当前目录下的data.txt, 相对路径相对的是指向node命令的路径
//如果node命令不是在当前目录下执行就会报错, 在当前执行node命令的目录下查找data.txt,找不到
fs.readFile("data.txt", "utf8", function(err, data) {
if(err) {
console.log("读取文件失败", err);
}
console.log(data);
});
相对路径:相对于执行node命令的路径
绝对路径:__dirname: 当前文件的目录,__filename: 当前文件的目录,包含文件名
path模块的常用方法
关于路径,在linux系统中,路径分隔符使用的是/,但是在windows系统中,路径使用的\
在我们拼写路径的时候会带来很多的麻烦,经常会出现windows下写的代码,在linux操作系统下执行不了,path模块就是为了解决这个问题而存在的。
常用方法:
path.join();//拼接路径
//windows系统下
> path.join("abc","def","gg", "index.html")
"abc\def\gg\a.html"
//linux系统下
> path.join("abc","def","gg", "index.html")
'abc/def/gg/index.html'
http模块
创建服务器步骤
// 移入http模块
const http = require('http')
// 调用创建http 服务器的方法
const server = http.createServe()
// 给服务器注册request事件监听,每次浏览器像服务器发送请求的时候都会被监听到
server.on('request', function(request, response){
// request 浏览器请求的数据,包括请求方式method 请求的地址 url等
// response 浏览器的响应,可以设置响应头、响应体、响应状态码
const method = request.method
const url = request.url
// 设置响应的状态码
response.StatusCode = 404
// 设置响应的头
response.setHeader('Content-Type', 'text/html');
// 设置响应体内容,write可以调用多次
response.write('hello world!')
// 响应结束
response.end()
// 如果在end(content),这样的写法相当于是让write和end的合写
response.end('hello world!')
})
// 给服务器设置监听,相当于启动服务器
server.listen(8888,function(){
console.log('服务器启动成功')
})
// 简写方式
http.createServer((req,res) => {
....
}).listen(8888,() => {
....
})
详细说明
给服务器注册request事件,只要服务器接收到了客户端的请求,就会触发request事件
request事件有两个参数,request表示请求对象,可以获取所有与请求相关的信息,response是响应对象,可以获取所有与响应相关的信息。
服务器监听的端口范围为:1-65535之间,推荐使用3000以上的端口,因为3000以下的端口一般留给系统使用
response对象详解
常见的属性和方法:
res.write(data): 给浏览器发送请求体,可以调用多次,从而提供连续的请求体
res.end(); 通知服务器,所有响应头和响应主体都已被发送,即服务器将其视为已完成。
res.end(data); 结束请求,并且响应一段内容,相当于res.write(data) + res.end()
res.statusCode: 响应的的状态码 200 404 500
res.statusMessage: 响应的状态信息, OK Not Found ,会根据statusCode自动设置。
res.setHeader(name, value); 设置响应头信息, 比如content-type
res.writeHead(statusCode, statusMessage, options); 设置响应头,同时可以设置状态码和状态信息。
1
2
3
4
5
6
7
注意:必须先设置响应头,才能设置响应。
实现静态WEB服务器
服务器响应首页
注意:浏览器中输入的URL地址,仅仅是一个标识,不与服务器中的目录一致。也就是说:返回什么内容是由服务端的逻辑决定
server.on('request', function(req, res) {
var url = req.url
if(url === '/') {
fs.readFile('./index.html', function(err, data) {
if(err) {
return res.end('您访问的资源不存在~')
}
res.end(data)
})
}
})
根据根据不同url,响应不同文件
content-type设置-MIME类型
MIME(Multipurpose Internet Mail Extensions)多用途Internet邮件扩展类型 是一种表示文档性质和格式的标准化方式
浏览器通常使用MIME类型(而不是文件扩展名)来确定如何处理文档;因此服务器将正确的MIME类型附加到响应对象的头部是非常重要的
MIME类型的通用处理-mime模块
作用:获取文件的MIME类型
安装:npm i mime
var mime = require('mime')
// 获取路径对应的MIME类型
mime.getType('txt') // ⇨ 'text/plain'
// 根据MIME获取到文件后缀名
mime.getExtension('text/plain') // ⇨ 'txt'
1
2
3
4
5
6
有了这个模块我们就可以把设置响应头的代码改写成下面
// mime 不仅可以只写一个后缀名,还可以通过url来解析出后缀名来,因此这里可以直接写url
response.setHeader('content-type',mime.getType(request.url))
1
2
npm - Node包管理工具
初始化包
npm init; //这个命令用于初始化一个包,创建一个package.json文件,我们的项目都应该先执行npm init
npm init -y; //快速的初始化一个包, 不能是一个中文名
1
2
安装包
npm install 包名; //安装指定的包名的版本到项目中
npm install 包名@版本号; //安装指定包的指定版本
npm i 包名; //简写
卸载包
npm uninstall 包名; //卸载已经安装的包
1
清除缓存
npm cache clean -f // 如果npm安装失败了,可以用这个命令来清除缓存
1
package.json文件
package.json文件,包(项目)描述文件,用来管理组织一个包(项目),它是一个纯JSON格式的。
作用:描述当前项目(包)的信息,描述当前包(项目)的依赖项
如何生成:npm init或者npm init -y
作用
作为一个标准的包,必须要有package.json文件进行描述
一个项目的node_modules目录通常都会很大,不用拷贝node_modules目录,可以通过package.json文件配合npm install直接安装项目所有的依赖项
描述内容
{
"name": "03-npm", //描述了包的名字,不能有中文
"version": "1.0.0", //描述了包的的版本信息, x.y.z 如果只是修复bug,需要更新Z位。如果是新增了功能,但是向下兼容,需要更新Y位。如果有大变动,向下不兼容,需要更新X位。
"description": "", //包的描述信息
"main": "index.js", //入口文件(模块化加载规则的时候详细的讲)
"scripts": { //配置一些脚本,在vue的时候会用到,现在体会不到
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [], //关键字(方便搜索)
"author": "", //作者的信息
"license": "ISC", //许可证,开源协议
"dependencies": { //重要,项目的依赖, 方便代码的共享 通过 npm install可以直接安装所有的依赖项
"bootstrap": "^3.3.7",
"jquery": "^3.3.1"
}
}
注意:一个合法的package.json,必须要有name和version两个属性
本地安装和全局安装
有两种方式用来安装 npm 包:本地安装和全局安装。选用哪种方式来安装,取决于你如何使用这个包。
全局安装:如果你想将其作为一个命令行工具,那么你应该将其安装到全局。这种安装方式后可以让你在任何目录下使用这个命令。比如less命令,webpack命令,hcc-md命令 。
本地安装:如果你自己的模块依赖于某个包,并通过 Node.js 的 require 加载,那么你应该选择本地安装,这种方式也是 npm install 命令的默认行为。
// 全局安装,会把npm包安装到C:\Users\HUCC\AppData\Roaming\npm目录下,作为命令行工具使用
npm install -g 包名;
//本地安装,会把npm包安装到当前项目的node_modules文件中,作为项目的依赖
npm install 包名;
常见的命令行工具
nrm
nrm:npm registry manager(npm仓库地址管理工具)
安装:npm i -g nrm
# 带*表示当前正在使用的地址
# 查看仓库地址列表
nrm ls
# 切换仓库地址
nrm use taobao
nodemon 自动重启
作用:监视到js文件修改后,自动重启node程序
安装:npm i -g nodemon
使用:nodemon app.js 运行node程序
模块化(module)
基本概念
在nodejs中,应用由模块组成,nodejs中采用commonJS模块规范。
一个js文件就是一个模块
每个模块都是一个独立的作用域,在这个而文件中定义的变量、函数、对象都是私有的,对其他文件不可见。
node中模块分类
1 核心模块
由 node 本身提供,不需要单独安装(npm),可直接引入使用
2 第三方模块
由社区或个人提供,需要通过npm安装后使用
3 自定义模块
由我们自己创建,比如:tool.js 、 user.js
核心模块
fs:文件操作模块
http:网络操作模块
path:路径操作模块
url: 解析地址的模块
querystring: 解析参数字符串的模块
基本使用:1 先引入 2 再使用
// 引入模块
var fs = require('fs');
1
2
第三方模块
第三方模块是由 社区或个人 提供的
比如:mime模块/art-template/jquery…
基本使用:1 先通过npm下载 2 再引入 3 最后使用
用户自定义模块
由开发人员创建的模块(JS文件)
基本使用:1 创建模块 2 引入模块
注意:自定义模块的路径必须以./获取../开头
// 加载模块
require('./a') // 推荐使用,省略.js后缀!
require('./a.js')
1
2
3
4
模块导入
/*
nodejs中模块分为3大类
1. nodejs本身提供的核心模块 fs http path url querystring
核心模块不需要安装,直接导入即可。
核心模块的加载语法: const fs = require('fs')
2. 第三方模块 mime art-template
第三方模块: 必须先安装(npm install XXX) 才能导入
第三方模块的加载语法: npm install XXX const mime = require('mime')
3. 自定义的模块 一个js文件
不需要安装 只需要自己创建一个js文件
自定义模块的加载语法: require('模块的路径') 模块不能是名字,必须是路径 ./ ../ .js后缀是可以省略
require加载规则(以mime模块为例)
1. 判断是否是路径, 如果是 就是自定义模块
2. 如果是名字 判断是否是核心模块
3. 如果是第三方模块 在当前目录找node_modules
4. 在node_modules中查找mime文件夹
5. 查找是否有package.json, 查看是否main属性
6. 判断是否有main, 如果没有,默认查找index.js index.json index.node
7. 如果没有
8. 如果找不到,就去上一层目录,一直找到根目录
9, 如果还没有,就说明模块不存在
*/
模块导出
/*
1. 模块中定义的变量和函数都是私有的
2. 任意的一个模块中, 都有自带一个属性 module (全局属性) module代表的就是当前的这个模块。
3. module中有一个属性 exports ,这个exports属性是一个对象,代表的就是当前模块的导出 module.exports当前模块唯一能够被外界访问到的
*/
//通过module.exports对外导出一些值
module.exports = 值 只能导出一个值
module.exports = {} 可以把所有要导出的内容都放到一个新的对象中
module.export.xxx = 值
/*
在任意的模块中 module.exports表示该模块的导出
为了我们方便导出, 每个模块中还提供了 exports
exports 初始状态下,和module.exports指向了同一个对象。
注意点: 如果通过exports的方式来导出内容,只能给对象增加属性 不能替换这个对象
*/
// 我们真正到处的对象是module.exports指向的对象
exports = {} // 这样只是改了exports的指向,而module.exports的指向没有改变,所以这样是不对的
// 以下这种是允许的
exports.xxx = '值'
express与mysql
首先需要安装mysql模块
npm i mysql
1
基本使用
// 导入第三方包
const mysql = require('mysql')
// 创建连接
var connection = mysql.createConnection({
// 本地
host: 'localhost',
user: 'root',
password: 'root',
// 数据库名称
database: 'mydb',
port: 3306
})
// 连接数据库
connection.connect()
// 执行sql语句
connection.query('select * from user where id = 8', (err, result) => {
if (err) return console.log('查询失败', err)
// result返回的是数组, 数组中是一个对象
console.log(result)
})
// 关闭连接
connection.end()
查询语句
var name = 'zs'
// 使用?表示占位,可以防止sql注入
connect.query(`select * from user where name=?`, name, (err, result) => {
if (err) return console.log('错误了', err)
console.log(result)
})
1
2
3
4
5
6
插入语句
connect.query(
'insert into user (name, age, gender, content) values (?, ?, ?, ?)',
['zs', 18, '男', '哈哈哈哈'],
err => {
if (err) return console.log('错误', err)
console.log('添加成功了')
}
)
// 方式2
connect.query(
'insert into user set ?',
{
name: 'zs',
age: 30,
gender: '男',
content: '哈哈哈'
},
(err, result) => {
if (err) return console.log('错误', err)
console.log('添加成功了', result)
}
)
修改语句
connect.query(
'update user set ? where id = ?',
[
{
name: 'zs',
age: 30,
gender: '男',
content: '哈哈哈'
},
10
],
(err, result) => {
if (err) return console.log('错误', err)
console.log('添加成功了', result)
}
)
删除语句
connect.query('delete from user where id = ?', 10, (err, result) => {
if (err) return console.log('失败', err)
console.log(result)
})
1
2
3
4
登录状态保持
http是无状态的,但是随着技术的发展,我们需要记住某些东西,但是因为http是无状态的,无法让服务器记住东西,因此就引入了cookie和session这两个东西,cookie用于浏览器端,session用于服务器端。
以用户登录为例:
当用户登录时,浏览器会给服务器发送请求,这时候服务器就会开辟一个空间用于存放session数据,并且会把生成的sessionId返回给浏览器,存放在浏览器的cookie中,之后浏览器在请求服务器的时候,就会去比对是否存在这个session。这样你的登录状态就已经保持下来了
cookie的特点
cookie大小只有4k
cookie每次请求的时候,都会自动携带
cookie可以设置过期时间
为了方便使用,我们可以使用express-session这个包,可以很方便使用session
express-session步骤:
1. 下载 npm i express-session
2. 导入 const session = require("express-session")
3. 使用session中间件
app.use(session({
secret: 'itcast',
// 设置浏览器端cookie的名字, 默认connect.sid
name: 'itcast',
resave: false,
// 在浏览器和服务器连接的第一时间,分配session 给浏览器指定一个cookie
saveUninitialized: true
}))
可以通过req.session访问到session
4. 登录成功,把登录成功的用户信息存储到 req.session.xxx中
5. 提供一个中间件,这个中间件在路由的前面,判断 req.session.xxx是否有值,有值,放走,没值,去登录,细节: 如果是/login 直接放走
6. 退出功能: 把req.session.xxx 清空即可
浏览器登录和退出
1. 登录做什么 把用户名和密码给服务器
2. 退出做什么, 1. 告诉服务器,要退出 2.(清缓存也行)
yarn和npm的说明
官网:https://yarn.bootcss.com/
Yarn是由Facebook、Google、Exponent 和 Tilde 联合推出了一个新的 JS 包管理工具 ,Yarn 是为了弥补 npm 的一些缺陷而出现的。
Yarn 缓存了每个下载过的包,所以再次使用时无需重复下载。
同时利用并行下载以最大化资源利用率,因此安装速度更快。
yarn的用法和npm的用法差不多
yarn命令
初始化一个新项目
yarn init
1
添加依赖包
yarn add 包名
1
升级依赖包
yarn upgrade 包名
1
移除依赖包
yarn remove 包名
1
安装项目的全部依赖
yarn
1
全局安装
yarn global add 包名
1
使用gulp自动化构建
官网:https://gulpjs.com/
中文文档:https://www.gulpjs.com.cn/
用自动化构建工具增强你的工作流程!
在开发过程中,有很多重复性的工作需要执行。
less转成css
对css代码压缩混淆
对js代码压缩混淆
写完代码后需要刷新浏览器
无法共用模版
gulp是前端开发过程中对代码进行构建的工具,是自动化项目的构建利器;她不仅能对网站资源进行优化,而且在开发过程中很多重复的任务能够使用正确的工具自动完成;使用她,我们不仅可以很愉快的编写代码,而且大大提高我们的工作效率。
gulp -----> grunt ------>webpack
环境安装
初始化项目
npm init -y
1
全局安装gulp
npm install gulp -g
yarn global add gulp
1
2
作为项目的依赖进行安装
yarn add gulp --save-dev 或者
yarn add gulp --save-dev
--save-dev 等同于 -D
如果这个依赖包只是在开发阶段需要用到,需要加-D
1
2
3
4
新建gulpfile.js文件
// 参数1: 任务名
// 参数2: 任务需要执行的内容
gulp.task('aa', function() {
console.log('哈哈')
})
1
2
3
4
5
执行任务
gulp 任务名;
gulp; 如果不接任务名,那么会执行默认的 default任务
1
2
3
glup任务-文件拷贝-lib
文件拷贝使用到了gulp提供的几个核心方法
gulp.task: 定义任务
gulp.src() 读取文件
gulp.pipe() 把文件交给管道处理
gulp.dest() 输出文件到某个目录
gulp.task定义任务
gulp.src('./src/lib/**/*.*')把文件读取成一个文件流
gulp.pipe() 把文件流交给下一个流
gulp.dest('./dist/lib')输出文件
// 简单拷贝, 处理 lib文件夹, lib文件不需要做任何的处理,只需要拷贝到dist目录
// 任务需要加一个return, 表示任务完成
gulp.task('lib', function() {
// 读取文件
// gulp.src() 读取文件
// gulp.pipe() 管道
// gulp.dest() 放到哪儿
return gulp.src('./src/lib/**/*.*').pipe(gulp.dest('./dist/lib'))
})
1
2
3
4
5
6
7
8
9
gulp任务-js代码压缩与混淆
gulp-uglify-es: 给js代码进行压缩,处理ES6的代码
gulp-rename: 重命名
安装依赖
yarn add gulp-uglify-es --save-dev
1
配置任务
const uglify = require('gulp-uglify-es').default
gulp.task('js', function() {
return gulp
.src('./js/*.js')
.pipe(uglify())
.pipe(gulp.dest('./dist/js'))
})
1
2
3
4
5
6
7
8
安装重命名依赖
yarn add gulp-rename -D
1
重命名配置
task('js', function() {
return src('./js/*.js')
.pipe(dest('./dist/js'))
.pipe(uglify())
.pipe(
rename({
// 配置重命名的后缀名
suffix: '.min'
})
)
.pipe(dest('./dist/js'))
})
1
2
3
4
5
6
7
8
9
10
11
12
gulp任务-less处理
gulp-less: 把less变成css
gulp-rename: 重命名
gulp-minify-css: 压缩css代码
安装依赖包
yarn add gulp-less -D
1
less任务
// less任务
task('less', function() {
return src('./less/*.less')
.pipe(less())
.pipe(
rename({
extname: '.css'
})
)
.pipe(dest('./dist/css'))
})
1
2
3
4
5
6
7
8
9
10
11
安装css压缩处理包
yarn add gulp-minify-css -D
1
压缩css
// less任务
task('less', function() {
return src('./less/*.less')
.pipe(less())
.pipe(
rename({
extname: '.css'
})
)
.pipe(dest('./dist/css'))
.pipe(minifycss())
.pipe(
rename({
suffix: '.min'
})
)
.pipe(dest('./dist/css'))
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
gulp任务-图片压缩
gulp-imagemin: 可以对图片进行压缩
gulp-cache: 图片压缩比较耗时的一个任务, 如果每次都对所有的图片进行重新压缩,比较浪费时间, 会缓存下来所有已经压缩过的图片
安装依赖
yarn add gulp-imagemin -D
1
压缩图片的任务
task('image', function() {
return src('./img/*')
.pipe(imagemin())
.pipe(dest('./dist/img'))
})
1
2
3
4
5
安装gulp-cachae
yarn add gulp-cache -D
1
压缩图片是比较耗时的,我们可以使用gulp-cache来缓存已经压缩过的图片
task('image', function() {
return src('./img/*')
.pipe(cache(imagemin()))
.pipe(dest('./dist/img'))
})
1
2
3
4
5
参考资料:https://www.cnblogs.com/yuzhongwusan/p/5417090.html
gulp任务-处理html
gulp-minify-html: 压缩html文件
gulp-html-extend: 语句当前html去导入另一个html
压缩html
yarn add gulp-minify-html -D
1
使用
// 处理html
task('html', function() {
return src('./src/*.html')
.pipe(minifyHtml())
.pipe(dest('./dist'))
})
1
2
3
4
5
6
导入html
yarn add gulp-html-extend -D
1
// 处理html
task('html', function() {
return src('./src/*.html')
.pipe(extender())
.pipe(minifyHtml())
.pipe(dest('./dist'))
})
1
2
3
4
5
6
7
在页面中,如何导入html
<!-- @@include ./template/header.html -->
1
gulp任务-清空任务
安装
yarn add del -D
1
配置任务
task('clean', function() {
return del('./dist')
})
1
2
3
gulp-任务整合series
task('build', gulp.series('clean', 'html', 'less', 'js', 'image', 'lib'))
1
gulp任务-监听文件的变化
// 实现一个,修改代码,会自动执行任务
// 监听的任务,,,,,,做一件事件,当我们修改了对应的文件,需要执行对应的任务
// gulp.watch() 监视文件
task('watch', function() {
// 参数1:监视的文件
// 参数2: 对应的任务, 多个任务
watch('./src/**/*.html', series('html'))
watch('./src/less/*.less', series('less'))
watch('./src/js/*.js', series('js'))
watch('./src/lib/**/*.*', series('lib'))
watch('./src/img/*.*', series('img'))
})
gulp任务-自动刷新
安装
yarn add gulp-connect -D
蓝蓝设计( www.lanlanwork.com )是一家专注而深入的界面设计公司,为期望卓越的国内外企业提供卓越的UI界面设计、BS界面设计 、 cs界面设计 、 ipad界面设计 、 包装设计 、 图标定制 、 用户体验 、交互设计、 网站建设 、平面设计服务。
如果您想订阅本博客内容,每天自动发到您的邮箱中, 请点这里
vue路由加载页面时,数据返回慢的时候页面会有闪动的效果,数据加载前和加载后的区别。(特别是el-table表格数据)
路由前加载数据,等数据加载完再路由渲染页面
使用vue-router的 路由守卫 beforeRouteEnter,组件内直接定义以下路由导航守卫,和钩子函数的写法一样,下面列出三种路由守卫:
beforeRouteEnter(to,from,next)0{
// 在渲染该组件的对应路由被 confirm 前调用// 不!能!获取组件实例 `this`
// 因为当守卫执行前,组件实例还没被创建
} 复制代码
beforeRouteUpdate(to,from,next){
// 在当前路由改变,但是该组件被复用时调用
// 举例来说,对于一个带有动态参数的路径 /foo/:id,在 /foo/1 和 /foo/2 之间跳转的时候,
// 由于会渲染同样的 Foo 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。
// 可以访问组件实例 `this`
} 复制代码
beforeRouteLeave(to,from,next){
// 导航离开该组件的对应路由时调用// 可以访问组件实例 `this`
} 复制代码
蓝蓝设计( www.lanlanwork.com )是一家专注而深入的界面设计公司,为期望卓越的国内外企业提供卓越的UI界面设计、BS界面设计 、 cs界面设计 、 ipad界面设计 、 包装设计 、 图标定制 、 用户体验 、交互设计、 网站建设 、平面设计服务。
如果您想订阅本博客内容,每天自动发到您的邮箱中, 请点这里
实现的最终效果图(文末会贴出全部代码,方便参考)
1, 将此行代码加在红线所在的位置
,{fixed: 'right', width: 150, align:'center', toolbar: '#toolbarDemo'}
2,在合适的HTML位置加入
<script type="text/html" id="toolbarDemo">
<div class="layui-btn-container">
<button class="layui-btn layui-btn-sm" lay-event="update">编辑</button>
<button class="layui-btn layui-btn-danger layui-btn-sm" lay-event="delete">删除</button>
</div>
</script>
全部代码:
{include file="common/header"}
<div class="x-nav">
<span class="layui-breadcrumb">
<a href="">首页</a>
<a href="">演示</a>
<a>
<cite>导航元素</cite></a>
</span>
<a class="layui-btn layui-btn-small" style="line-height:1.6em;margin-top:3px;float:right" href="javascript:location.replace(location.href);" title="刷新">
<i class="layui-icon" style="line-height:30px">ဂ</i></a>
</div>
<table class="layui-hide" id="test" lay-size="sm" lay-filter="test"></table>
<script type="text/html" id="toolbarDemo">
<div class="layui-btn-container">
<button class="layui-btn layui-btn-sm" lay-event="update">编辑</button>
<button class="layui-btn layui-btn-danger layui-btn-sm" lay-event="delete">删除</button>
</div>
</script>
<script>
layui.use('table', function(){
var table = layui.table;
table.render({
elem: '#test'
,url:"{:url('admin/user/page')}"
,page: { //支持传入 laypage 组件的所有参数(某些参数除外,如:jump/elem) - 详见文档
layout: ['limit', 'count', 'prev', 'page', 'next', 'skip'] //自定义分页布局
//,curr: 5 //设定初始在第 5 页
,limit:10 //一页显示多少条
,limits:[5,10,15]//每页条数的选择项
,groups: 2 //只显示 2 个连续页码
,first: "首页" //不显示首页
,last: "尾页" //不显示尾页
}
,cols: [[
{field:'id', width:80, title: '代理ID', sort: true}
,{field:'username', width:100, title: '代理名称'}
,{field:'level', width:100, title: '代理级别'}
,{field:'email', width:80, title: '电子邮箱'}
,{field:'phone', width:280, title: '手机号'}
,{field:'agent_id', width:280, title: '上级代理ID'}
,{field:'status', width:280, title: '是否启用'}
,{field:'isAuth', width:280, title: '是否已经认证'}
,{field:'money', width:280, title: '金额'}
,{field:'created_time', width:280, title: '访问时间',sort: true}
,{fixed: 'right', width: 150, align:'center', toolbar: '#toolbarDemo'}
]]
});
//监听工具条
table.on('tool(test)', function(obj){ //注:tool是工具条事件名,test是table原始容器的属性 lay-filter="对应的值"
console.log(obj);
var data = obj.data; //获得当前行数据
var layEvent = obj.event; //获得 lay-event 对应的值(也可以是表头的 event 参数对应的值)
var tr = obj.tr; //获得当前行 tr 的DOM对象
if(layEvent === 'detail'){ //查看
//do somehing
} else if(layEvent === 'del'){ //删除
layer.confirm('真的删除行么', function(index){
obj.del(); //删除对应行(tr)的DOM结构,并更新缓存
layer.close(index);
//向服务端发送删除指令
});
} else if(layEvent === 'edit'){ //编辑
//do something
//同步更新缓存对应的值
obj.update({
username: '123'
,title: 'xxx'
});
}
});
});
</script>
</body>
</html>
蓝蓝设计( www.lanlanwork.com )是一家专注而深入的界面设计公司,为期望卓越的国内外企业提供卓越的UI界面设计、BS界面设计 、 cs界面设计 、 ipad界面设计 、 包装设计 、 图标定制 、 用户体验 、交互设计、 网站建设 、平面设计服务。
如果您想订阅本博客内容,每天自动发到您的邮箱中, 请点这里
最近遇到一个作业,要求使用 Echarts 散点图,本来这个图是很容易的,官网上也有很多的教程。但是如果可以动态的更新 Echarts 散点图就更好了。我本身对 js 不感兴趣,经过不停的查找资料最终实现了这一功能。
我的项目是 Servlet + jsp + Echarts。先从 Servlet 入手,我们的项目需要传递的数值是 x 坐标和 y 坐标。我首先写了一个 JavaBean
julie.java
package JavaBean;
public class julei {
public julei(double x, double y) {
this.x = x;
this.y = y;
}
double x;
public double getX() {
return x;
}
public void setX(double x) {
this.x = x;
}
public double getY() {
return y;
}
public void setY(double y) {
this.y = y;
}
double y;
@Override
public String toString() {
return "[" + this.x + "," + this.y + "]";
}
}
Servlet中的代码,因为使用的是 json 来传递的数据,所以 json 相关的包还是少不了的。
BackServlet
package Servlet;
import JavaBean.Readtxt;
import JavaBean.julei;
import org.json.JSONArray;
import org.json.JSONObject;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@javax.servlet.annotation.WebServlet("/BackServlet")
public class BackServlet extends javax.servlet.http.HttpServlet {
protected void doPost(javax.servlet.http.HttpServletRequest request, javax.servlet.http.HttpServletResponse response) throws javax.servlet.ServletException, IOException {
List<julei> list = new ArrayList<>();
try {
list = Readtxt.out();//这是我们项目中的一个类,不重要。
}catch (Exception e)
{
System.out.println(e.toString());
}
JSONArray jsonArray = new JSONArray(list);
System.out.println(jsonArray.toString());
//最重要的就是这一句,将数据发送给谁来申请的位置
response.getWriter().write(jsonArray.toString());
}
protected void doGet(javax.servlet.http.HttpServletRequest request, javax.servlet.http.HttpServletResponse response) throws javax.servlet.ServletException, IOException {
}
}
好了,现在到了最重要的前端方面了。
index.jsp
正常声明散点图,只要把data声明为空就好。
var myChart = echarts.init(document.getElementById('man'));
var option = {
title : {
text: '死亡分布图',
},
xAxis: {axisTick: {//决定是否显示坐标刻度
alignWithLabel: true,
show:true
},},
yAxis: {},
series: [{
symbolSize: 20,
data: [],
type: 'scatter'
}]
};
myChart.setOption(option);
数据接收部分:
var num = [];
var gao = new Array();
$.ajax({
type : "post",
async : true, //异步请求(同步请求将会锁住浏览器,其他操作须等请求完成才可执行)
url : "BackServlet", //请求发送到TestServlet
data : {},
dataType : "json", //返回数据形式为json
//7.请求成功后接收数据name+num两组数据
success : function(result) {
//result为服务器返回的json对象
if (result) {
//8.取出数据存入数组
for (var i = 0; i < result.length; i++) {
gao.push([result[i].x,result[i].y]);//这一句很重要,它将数据转化为了正确的格式。
}
myChart.hideLoading(); //隐藏加载动画
//9.覆盖操作-根据数据加载数据图表
myChart.setOption({
series : [ {
// 根据名字对应到相应的数据
data : gao//在这里对data进行赋值。
} ]
});
}
},
error : function(errorMsg) {
//请求失败时执行该函数
alert("图表请求数据失败!");
myChart.hideLoading();
}
})
完毕
我把整个jsp都放上来了,但是里面的 css 还有 js 就不放了,重点是传数据的那一部分。
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>LOL数据分析</title>
<script src="https://cdn.bootcss.com/jquery/1.10.2/jquery.min.js"></script>
<script type="text/javascript" src='js/echarts.js'></script>
<link rel="stylesheet" href="css/jquery.fullPage.css">
<link rel="stylesheet" href="css/style.css">
<link rel="icon" href="https://jscdn.com.cn/highcharts/images/favicon.ico">
<meta name="viewport" content="width=device-width, initial-scale=1">
<script src="https://code.highcharts.com.cn/highcharts/highcharts.js"></script>
<script src="https://code.highcharts.com.cn/highcharts/modules/exporting.js"></script>
<script src="https://img.hcharts.cn/highcharts-plugins/highcharts-zh_CN.js"></script>
</head>
<body>
<div class="bgcolor">
<div style="z-index:100;" id="dowebok">
<!--第一屏-->
<div class="section">
<div class="ly-box01">
<img class="ly-img01" src="img/logol.png" style="width:100%;height:100%">
</div>
</div>
<!--第三屏-->
<div class="section">
<div class="timeline"></div>
<div class="timepoint21"></div>
<div class="ly-box11">
<div id="man" style=" width: 600px;height: 500px;"></div>
<script type="text/javascript">
// 基于准备好的dom,初始化echarts实例
var value=[];
$.ajaxSettings.async=false;
var myChart = echarts.init(document.getElementById('man'));
var option = {
title : {
text: '死亡分布图',
},
xAxis: {axisTick: {//决定是否显示坐标刻度
alignWithLabel: true,
show:true
},},
yAxis: {},
series: [{
symbolSize: 20,
data: [],
type: 'scatter'
}]
};
myChart.setOption(option);
var num = [];
var gao = new Array(4);
$.ajax({
type : "post",
async : true, //异步请求(同步请求将会锁住浏览器,其他操作须等请求完成才可执行)
url : "BackServlet", //请求发送到TestServlet
data : {},
dataType : "json", //返回数据形式为json
//7.请求成功后接收数据name+num两组数据
success : function(result) {
//result为服务器返回的json对象
if (result) {
//8.取出数据存入数组
for (var i = 0; i < result.length; i++) {
gao.push([result[i].x,result[i].y]);
}
// document.write(gao);
myChart.hideLoading(); //隐藏加载动画
//9.覆盖操作-根据数据加载数据图表
myChart.setOption({
series : [ {
// 根据名字对应到相应的数据
data : gao
} ]
});
}
},
error : function(errorMsg) {
//请求失败时执行该函数
alert("图表请求数据失败!");
myChart.hideLoading();
}
})
</script>
</div>
<div class="ly-triangle21"></div>
</div>
<!--试验-->
<ul class="bg-bubbles">
<li><img src="img/logol.png" style="width:100%;height:100%"></li>
<li><img src="img/logol.png" style="width:100%;height:100%"></li>
<li><img src="img/logol.png" style="width:100%;height:100%"></li>
<li><img src="img/logol.png" style="width:100%;height:100%"></li>
<li><img src="img/logol.png" style="width:100%;height:100%"></li>
<li><img src="img/logol.png" style="width:100%;height:100%"></li>
<li><img src="img/logol.png" style="width:100%;height:100%"></li>
<li><img src="img/logol.png" style="width:100%;height:100%"></li>
<li><img src="img/logol.png" style="width:100%;height:100%"></li>
<li><img src="img/logol.png" style="width:100%;height:100%"></li>
</ul>
</div>
<audio src="music/1.mp3" autoplay="autoplay" loop="loop" />
<script src="js/jquery-1.8.3.min.js"></script>
<script src="js/jquery.fullPage.min.js"></script>
<script src="js/diy.js"></script>
</body>
</html>
蓝蓝设计( www.lanlanwork.com )是一家专注而深入的界面设计公司,为期望卓越的国内外企业提供卓越的UI界面设计、BS界面设计 、 cs界面设计 、 ipad界面设计 、 包装设计 、 图标定制 、 用户体验 、交互设计、 网站建设 、平面设计服务。
如果您想订阅本博客内容,每天自动发到您的邮箱中, 请点这里
更新阶段分为两部分 父组件执行 render 或者调用 this.setState。
componentWillReceiveProps
大部分网上教程为外部 props 发生改变才触发 componentWillReceiveProps,其实不是,当父组件进入 render 时,无论子组件的 props 发没发生改变,都会执行该生命周期函数。
函数参数有一个,为 nextProps,为将要新的 props。
值得注意的是,在整个更新阶段的生命周期函数,只有在此函数内可以调用 this.setState 方法,当然其他也可以调用,但是会造成死循环 。
shouldComponentUpdate
该函数需要返回值,如没定义则默认返回 true。当返回值为 true 时,进入 componentWillIpdate ,如为 false ,则什么都不发生。所以说这是一个可以进行 React 性能优化的地方。函数参数有两个 nextProps 和 nextState。我们需用做的就是在 this.props、this.state、nextState、nextProps之间进行对比,来解决重复渲染的目的。
componentWillUpdate
如果 shouldComponentUpdate 返回值为 true 的话,生命周期会进入该函数中。在这个函数中我们可以根据实际情况做一些事情,但是不能调用 this.setState。
render
在更新阶段的 render 来讲一讲 调和 过程。 render 返回的 JSX 标签会保存在内存中,react 会通过 diff 算法来计算出最小化改动完成差异的更新。diff 是逐层递归比较,首先比较类型是否一样。如果发现 <div>和 <span> 的差别的话,react 会选择直接放弃之前的 dom 元素, 重新渲染。所以说即使是更新阶段的调和过程,也会触发组件的挂载、卸载阶段。
componentDidUpdate
在这个时候已经更新完 dom 结构,可以重新使用 dom 操作。
总结
总体来说更新的生命周期要做的最重要的事情就是性能优化,减少重复渲染次数。
在这个方面已经有很多成熟的解决方法了,在我的博客中也会介绍如何定制更新阶段的生命周期函数。
在使用上,最最重要的一点就是不要在除了 componentWillReceiveProps 之外的其他更新阶段生命周期函数内调用 this.setState。
相关链接:
浅入 React 生命周期相关(一)挂载生命周期
---------------------
如果您想订阅本博客内容,每天自动发到您的邮箱中, 请点这里
前几天面试问道 react 的相关知识,对我打击比较大,感觉对 react 认识非常肤浅,所以在这里重新梳理一下,想想之前没有仔细思考过的东西。
另外有说的不对的地方还请帮我指正一下,先谢谢各位啦。
目录索引:
React 有一套合理的运行机制去控制程序在指定的时刻该做什么事,当一个生命周期钩子被触发后,紧接着会有下一个钩子,直到整个生命周期结束。
生命周期代表着每个执行阶段,比如组件初始化,更新完成,马上要卸载等等,React 会在指定的时机执行相关的生命周期钩子,使我们可以有机在程序运行中会插入自己的逻辑。
我们写代码的时候往往会有很多组件以及他们的子组件,各自调用不同的生命周期,这时就要解决谁先谁后的问题,在 react v16 之前是采用了递归调用的方式一个一个执行,而在现在 v16 的版本中则采用了与之完全不同的处理(调度)方式,名叫 Fiber,这个东西 facebook 做了有两年时间,实现非常复杂。
具体 Fiber 它是一个什么东西呢?不要着急,我们先从最基本的生命周期钩子看起。
首先看一下 React V16.4 后的生命周期概况(图片来源)
constructor()
- 类构造器初始化
static getDerivedStateFromProps()
- 组件初始化时主动触发
render()
- 递归生成虚拟 DOM
componentDidMount()
- 完成首次 DOM 渲染
static getDerivedStateFromProps()
- 每次 render() 之前执行
shouldComponentUpdate()
- 校验是否需要执行更新操作
render()
- 递归生成虚拟 DOM
getSnapshotBeforeUpdate()
- 在渲染真实 DOM 之前
componentDidUpdate()
- 完成 DOM 渲染
componentWillUnmount()
- 组件销毁之前被直接调用
static getDerivedStateFromProps()
这个钩子会在每个更新操作之前(即使props没有改变)执行一次,使用时应该保持谨慎。
componentDidMount()
和 componentDidUpdate()
执行的时机是差不多的,都在 render
之后,只不过前者只在首次渲染后执行,后者首次渲染不会执行
getSnapshotBeforeUpdate()
执行时可以获得只读的新 DOM 树,此函数的返回值为 componentDidUpdate(prevProps, prevState, snapshot)
的第三个参数
关于 Fiber,强烈建议听一下知乎上程墨Morgan的 live 《深入理解React v16 新功能》,这里潜水员的例子和图片也是引用于此 live。
我们知道 React 是通过递归的方式来渲染组件的,在 V16 版本之前的版本里,当一个状态发生变更时,react 会从当前组件开始,依次递归调用所有的子组件生命周期钩子,而且这个过程是同步执行的且无法中断的,一旦有很深很深的组件嵌套,就会造成严重的页面卡顿,影响用户体验。
React 在V16版本之前的版本里引入了 Fiber 这样一个东西,它的英文涵义为纤维,在计算机领域它排在在进程和线程的后面,虽然 React 的 Fiber 和计算机调度里的概念不一样,但是可以方便对比理解,我们大概可以想象到 Fiber 可能是一个比线程还短的时间片段。
Fiber 把当前需要执行的任务分成一个个微任务,安排优先级,然后依次处理,每过一段时间(非常短,毫秒级)就会暂停当前的任务,查看有没有优先级较高的任务,然后暂停(也可能会完全放弃)掉之前的执行结果,跳出到下一个微任务。同时 Fiber 还做了一些优化,可以保持住之前运行的结果以到达复用目的。
我们可以把调度当成一个潜水员在海底寻宝,v16 之前是通过组件递归的方式进行寻宝,从父组件开始一层一层深入到最里面的子组件,也就是如下图所示。
而替换成了 Fiber 后,海底变成的狭缝(简单理解为递归变成了遍历),潜水员会每隔一小段时间浮出水面,看看有没有其他寻宝任务。注意此时没有寻到宝藏的话,那么之前潜水的时间就浪费了。就这样潜水员会一直下潜和冒泡,具体如下图所示。
从生命周期那张图片纵向来看,Fiber 将整个生命周期分成了三个阶段:
componentWillMount()
,componentWillUpdate()
,componentWillReceiveProps()
的三个生命周期钩子被加上了 UNSAFE
标记
简而言之:以 render() 为界,之前执行的生命周期都有可能会打断并多次调用,之后的生命周期是不可被打断的且只会调用一次。所以尽量把副作用的代码放在只会执行一次的 commit 阶段。
除了上面常用的钩子,React 还提供了如下钩子:
static getDerivedStateFromError()
在 render 阶段执行,通过返回 state 更新组件状态
componentDidCatch()
在 commit 阶段执行,可以放一些有副作用的代码
理解了生命周期和三个执行阶段,就可以比较容易理解组件状态的更新机制了。
这个方法可以让我们更新组件的 state 状态。第一个参数可以是对象,也可以是 updater 函数,如果是函数,则会接受当前的 state 和 props 作为参数。第二个参数为函数,是在 commit 阶段后执行,准确的说是在 componentDidUpdate()
后执行。
setState() 的更新过程是异步的(除非绑定在 DOM 事件中或写在 setTimeout 里),而且会在最后合并所有的更新,如下:
Object.assign( previousState,
{quantity: state.quantity + 1},
{quantity: state.quantity + 1},
...
)
复制代码
之所以设计成这样,是为了避免在一次生命周期中出现多次的重渲染,影响页面性能。
如果我们想强制刷新一个组件,可以直接调用该方法,调用时会直接执行 render()
这个函数而跳过 shouldComponentUpdate()
。
function wait() { return new Promise(resolve => {
setTimeout(() => {
resolve(); console.log("wait");
}, 0);
});
} //......省略组件创建 async componentDidMount() { await wait(); this.setState({ name: "new name" }); console.log("componentDidMount");
}
componentDidUpdate() { console.log("componentDidUpdate");
}
render() { console.log(this.state); return null } //......省略组件创建 // 输出结果如下 // wait // {name: "new name"} // componentDidUpdate // componentDidMount // 注意 componentDidUpdate 的输出位置,一般情况下 // componentDidUpdate 都是在componentDidMount 后面 // 执行的,但是这里因为setState 写在了 await 后面 // 所以情况相反。 复制代码
了解 react 生命周期和更新机制确实有利于编写代码,特别是当代码量越来越大时,错用的 setState 或生命周期钩子都可能埋下越来越多的雷,直到有一天无法维护。。。
我的个人建议如下:
getDerivedStateFromProps()
当成是 UNSAFE_componentWillReceiveProps()
的替代品,因为 getDerivedStateFromProps()
会在每次 render() 之前执行,即使 props 没有改变
蓝蓝设计( www.lanlanwork.com )是一家专注而深入的界面设计公司,为期望卓越的国内外企业提供卓越的UI界面设计、BS界面设计 、 cs界面设计 、 ipad界面设计 、 包装设计 、 图标定制 、 用户体验 、交互设计、 网站建设 、平面设计服务。
如果您想订阅本博客内容,每天自动发到您的邮箱中, 请点这里
附Java/C/C++/机器学习/算法与数据结构/前端/安卓/Python/程序员必读书籍书单大全:
书单导航页(点击右侧 极客侠栈 即可打开个人博客):极客侠栈
①【Java】学习之路吐血整理技术书从入门到进阶最全50+本(珍藏版)
②【算法数据结构+acm】从入门到进阶吐血整理书单50+本(珍藏版)
③【数据库】从入门到进阶必读18本技术书籍网盘吐血整理网盘(珍藏版)
④【Web前端】从HTML到JS到AJAX到HTTP从框架到全栈帮你走更少弯路(珍藏版)
⑤【python】书最全已整理好(从入门到进阶)(珍藏版)
⑥【机器学习】+python整理技术书(从入门到进阶已经整理好)(珍藏版)
⑦【C语言】推荐书籍从入门到进阶带你走上大牛之路(珍藏版)
⑧【安卓】入门到进阶推荐书籍整理pdf书单整理(珍藏版)
⑨【架构师】之路史诗级必读书单吐血整理四个维度系列80+本书(珍藏版)
⑩【C++】吐血整理推荐书单从入门到进阶成神之路100+本(珍藏)
⑪【ios】IOS书单从入门到进阶吐血整理(珍藏版)
--------------------------------------------------------------------------------------------------------------------
如果您已经入门reactjs,请绕道~ 这篇博客只适合初学者,初学reactjs的时候,如果你不会webpack,相信很多人都会被官方的例子绕的晕头转向。 ES6的例子也会搞死一批入门者。之前一直用的gulp,突然换了webpack,我也非常不习惯。在这块也卡住了,对于想学reactjs的朋友,我的学习建议是这样的:
nodejs => webpack => ES6 => reactjs
官方的很多例子都是ES6语法+webpack打包的,所以在学习reactjs之前,最好是会ES6和webpack,这样能事半功倍!
1、首先来说说nodejs
先安装版本的nodejs,npm一般都是自带的。安装成全局的比较方便构建项目。
npm install -g grunt-cli # 全局安装
npm可安装的插件可以在这里去找找 www.npmjs.com/ 如果不能安装,可以使用淘宝的镜象资源
2、webpack
webpack是一款打包工具,可以做一些js压缩,sass,less转换,图片处理,js代码合成,ES6转ES5语法等很多功能,如果用过grunt,或者gulp的朋友,webpack也就不陌生了。都是需要写配置文件。
3、ES6
github上很多案例都是用到了ES6的语法,所以,这里我们可以通过webpack的工具 babel ,把ES6的语法转化为ES5的语法,这样我们就可以使用github上面的demo了。
比如:
import '../css/common.scss';
import React from 'react'
import { render } from 'react-dom'
import { Router, Route, Link, IndexRoute } from 'react-router'
import { browserHistory } from 'react-router'
这里的import 就是ES6的语法,在webpack里面使用babel工具将其转化为 ES5的语法。我这里用了JSX(reactjs 提供的一种简洁的语法)如果对JSX不了解的,可以去百度下。
4、快速开发
每次我们在修改JSX文件,或者SASS文件后,都要执行webpack命令进行打包,这样的开发效率很慢,官方提供了一个很牛X的工具,react-hot-loader + webpack-dev-server 可以帮助你快速开发,自动刷新页面。
5、DEMO小试牛刀
这里我把自己做的一个DEMO分享给大家,如果你已经安装了nodejs,并且npm也是全局的。下载后解压,打开 start.bat,输入 npm install 安装所需的插件,安装成功后执行 npm start ,等项目跑起来后,在浏览器输入 http://127.0.0.1:3000 就可以访问项目了。
这里是一个 react-router 的一个例子。
github 地址:https://github.com/mtsee/react-router-demo
蓝蓝设计( www.lanlanwork.com )是一家专注而深入的界面设计公司,为期望卓越的国内外企业提供卓越的UI界面设计、BS界面设计 、 cs界面设计 、 ipad界面设计 、 包装设计 、 图标定制 、 用户体验 、交互设计、 网站建设 、平面设计服务。
如果您想订阅本博客内容,每天自动发到您的邮箱中, 请点这里
按照这个来配置就好了,这里相当于是帮你把webpack需要的繁琐的配置给你都弄好了,然后你直接从那里全部下下来就可以
记得先下好nodejs,安装的代码记得全都c v,不要自己敲
安装完nodejs之后再安装淘宝npm镜像,安装完了之后后面的安装速度会快一点
npm install -g cnpm --registry=https://registry.npm.taobao.org
1
然后按照下面的五步流程一个一个来
1、全局安装vuecli
npm install -g vue-cli
1
2、进入目录–初始化项目
vue init webpack my-project //这个要进入项目目录再用,会创建一个my-project的文件夹
1
3、进入项目
cd my-project
1
4、安装依赖
npm install
1
5、启动项目
npm run dev
1
以上执行完后会出现一个 my-project 项目文件夹,用vscode打开后会看到以下目录
下面解释一下这些文件及目录分别是干什么的
目录结构的分析
1、bind
├── build // 项目构建(webpack)相关代码 记忆:(够贱) 9个
│ ├── build.js // 生产环境构建代码
│ ├── checkversions.js // 检查node&npm等版本
│ ├── devclient.js // 热加载相关
│ ├── devserver.js // 构建本地服务器
│ ├── utils.js // 构建配置公用工具
│ ├── vueloader.conf.js // vue加载器
│ ├── webpack.base.conf.js // webpack基础环境配置
│ ├── webpack.dev.conf.js // webpack开发环境配置
│ └── webpack.prod.conf.js // webpack生产环境配置
2、config
├── config// 项目开发环境配置相关代码 记忆: (环配) 3个
│ ├── dev.env.js // 开发环境变量(看词明意)
│ ├── index.js //项目一些配置变量
│ └── prod.env.js // 生产环境变量
3、node_modules
├──node_modules// 项目依赖的模块 记忆: (依赖) *个
4、src
├── src// 源码目录 5
1
│ ├── assets// 资源目录
│ │ └── logo.png
2
│ ├── components// vue公共组件
│ │ └── Hello.vue
3
│ ├──router// 前端路由
│ │ └── index.js// 路由配置文件
4
│ ├── App.vue// 页面入口文件(根组件)
5
│ └── main.js// 程序入口文件(入口js文件)
5、static
└── static// 静态文件,比如一些图片,json数据等
│ ├── .gitkeep
6、剩余的文件
├── .babelrc// ES6语法编译配置
├── .editorconfig// 定义代码格式
├── .gitignore// git上传需要忽略的文件格式
├── index.html// 入口页面
├── package.json// 项目基本信息
├── README.md// 项目说明
---------------------
蓝蓝设计( www.lanlanwork.com )是一家专注而深入的界面设计公司,为期望卓越的国内外企业提供卓越的UI界面设计、BS界面设计 、 cs界面设计 、 ipad界面设计 、 包装设计 、 图标定制 、 用户体验 、交互设计、 网站建设 、平面设计服务。
如果您想订阅本博客内容,每天自动发到您的邮箱中, 请点这里
加粗样式
***@TOC
欢迎使用Markdown编辑器
你好! 这是你第一次使用 Markdown编辑器 所展示的欢迎页。如果你想学习如何使用Markdown编辑器, 可以仔细阅读这篇文章,了解一下Markdown的基本语法知识。
新的改变
我们对Markdown编辑器进行了一些功能拓展与语法支持,除了标准的Markdown编辑器功能,我们增加了如下几点新功能,帮助你用它写博客:
全新的界面设计 ,将会带来全新的写作体验;
在创作中心设置你喜爱的代码高亮样式,Markdown 将代码片显示选择的高亮样式 进行展示;
增加了 图片拖拽 功能,你可以将本地的图片直接拖拽到编辑区域直接展示;
全新的 KaTeX数学公式 语法;
增加了支持甘特图的mermaid语法1 功能;
增加了 多屏幕编辑 Markdown文章功能;
增加了 焦点写作模式、预览模式、简洁写作模式、左右区域同步滚轮设置 等功能,功能按钮位于编辑区域与预览区域中间;
增加了 检查列表 功能。
功能快捷键
撤销:Ctrl/Command + Z
重做:Ctrl/Command + Y
加粗:Ctrl/Command + B
斜体:Ctrl/Command + I
标题:Ctrl/Command + Shift + H
无序列表:Ctrl/Command + Shift + U
有序列表:Ctrl/Command + Shift + O
检查列表:Ctrl/Command + Shift + C
插入代码:Ctrl/Command + Shift + K
插入链接:Ctrl/Command + Shift + L
插入图片:Ctrl/Command + Shift + G
合理的创建标题,有助于目录的生成
直接输入1次#,并按下space后,将生成1级标题。
输入2次#,并按下space后,将生成2级标题。
以此类推,我们支持6级标题。有助于使用TOC语法后生成一个完美的目录。
如何改变文本的样式
强调文本 强调文本
加粗文本 加粗文本
标记文本
删除文本
引用文本
H2O is是液体。
210 运算结果是 1024.
插入链接与图片
链接: link.
图片:
带尺寸的图片:
居中的图片:
居中并且带尺寸的图片:
当然,我们为了让用户更加便捷,我们增加了图片拖拽功能。
如何插入一段漂亮的代码片
去博客设置页面,选择一款你喜欢的代码片高亮样式,下面展示同样高亮的 代码片.
// An highlighted block
var foo = 'bar';
1
2
生成一个适合你的列表
项目
项目
项目
项目1
项目2
项目3
计划任务
完成任务
创建一个表格
一个简单的表格是这么创建的:
项目 Value
电脑 $1600
手机 $12
导管 $1
设定内容居中、居左、居右
使用:---------:居中
使用:----------居左
使用----------:居右
第一列 第二列 第三列
第一列文本居中 第二列文本居右 第三列文本居左
SmartyPants
SmartyPants将ASCII标点字符转换为“智能”印刷标点HTML实体。例如:
TYPE ASCII HTML
Single backticks 'Isn't this fun?' ‘Isn’t this fun?’
Quotes "Isn't this fun?" “Isn’t this fun?”
Dashes -- is en-dash, --- is em-dash – is en-dash, — is em-dash
创建一个自定义列表
Markdown
Text-to-HTML conversion tool
Authors
John
Luke
如何创建一个注脚
一个具有注脚的文本。2
注释也是必不可少的
Markdown将文本转换为 HTML。
KaTeX数学公式
您可以使用渲染LaTeX数学表达式 KaTeX:
Gamma公式展示 Γ(n)=(n−1)!∀n∈N \Gamma(n) = (n-1)!\quad\foralln\in\mathbb NΓ(n)=(n−1)!∀n∈N 是通过欧拉积分
Unexpected text node: ' 'Unexpected text node: ' '
Γ(z)=∫
0
∞
t
z−1
e
−t
dt.
你可以找到更多关于的信息 LaTeX 数学表达式here.
新的甘特图功能,丰富你的文章
Mon 06
Mon 13
Mon 20
已完成
进行中
计划一
计划二
现有任务
Adding GANTT diagram functionality to mermaid
关于 甘特图 语法,参考 这儿,
UML 图表
可以使用UML图表进行渲染。 Mermaid. 例如下面产生的一个序列图::
张三
李四
王五
你好!李四, 最近怎么样?
你最近怎么样,王五?
我很好,谢谢!
我很好,谢谢!
李四想了很长时间,文字太长了不适合放在一行.
打量着王五...
很好... 王五, 你怎么样?
张三
李四
王五
这将产生一个流程图。:
链接
长方形
圆
圆角长方形
菱形
关于 Mermaid 语法,参考 这儿,
FLowchart流程图
我们依旧会支持flowchart的流程图:
开始
我的操作
确认?
结束
yes
no
关于 Flowchart流程图 语法,参考 这儿.
导出与导入
导出
如果你想尝试使用此编辑器, 你可以在此篇文章任意编辑。当你完成了一篇文章的写作, 在上方工具栏找到 文章导出 ,生成一个.md文件或者.html文件进行本地保存。
导入
如果你想加载一篇你写过的.md文件或者.html文件,在上方工具栏可以选择导入功能进行对应扩展名的文件导入,
继续你的创作。
mermaid语法说明 ↩︎
注脚的解释 ↩︎
蓝蓝设计( www.lanlanwork.com )是一家专注而深入的界面设计公司,为期望卓越的国内外企业提供卓越的UI界面设计、BS界面设计 、 cs界面设计 、 ipad界面设计 、 包装设计 、 图标定制 、 用户体验 、交互设计、 网站建设 、平面设计服务。
如果您想订阅本博客内容,每天自动发到您的邮箱中, 请点这里
1. 如何实现一个响应式对象
最近在看 Vue 的源码,其中最核心基础的一块就是 Observer/Watcher/Dep, 简而言之就是,Vue 是如何拦截数据的读写, 如果实现对应的监听,并且特定的监听执行特定的回调或者渲染逻辑的。总的可以拆成三大块来说。这一块,主要说的是 Vue 是如何将一个 plain object 给处理成 reactive object 的,也就是,Vue 是如何拦截拦截对象的 get/set 的
我们知道,用 Object.defineProperty 拦截数据的 get/set 是 vue 的核心逻辑之一。这里我们先考虑一个最简单的情况 一个 plain obj 的数据,经过你的程序之后,使得这个 obj 变成 Reactive Obj (不考虑数组等因素,只考虑最简单的基础数据类型,和对象):
如果这个 obj 的某个 key 被 get, 则打印出 get ${key} - ${val} 的信息
如果这个 obj 的某个 key 被 set, 如果监测到这个 key 对应的 value 发生了变化,则打印出 set ${key} - ${val} - ${newVal} 的信息。
对应的简要代码如下:
Observer.js
export class Observer {
constructor(obj) {
this.obj = obj;
this.transform(obj);
}
// 将 obj 里的所有层级的 key 都用 defineProperty 重新定义一遍, 使之 reactive
transform(obj) {
const _this = this;
for (let key in obj) {
const value = obj[key];
makeItReactive(obj, key, value);
}
}
}
function makeItReactive(obj, key, val) {
// 如果某个 key 对应的 val 是 object, 则重新迭代该 val, 使之 reactive
if (isObject(val)) {
const childObj = val;
new Observer(childObj);
}
// 如果某个 key 对应的 val 不是 Object, 而是基础类型,我们则对这个 key 进行 defineProperty 定义
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: () => {
console.info(`get ${key}-${val}`)
return val;
},
set: (newVal) => {
// 如果 newVal 和 val 相等,则不做任何操作(不执行渲染逻辑)
if (newVal === val) {
return;
}
// 如果 newVal 和 val 不相等,且因为 newVal 为 Object, 所以先用 Observer迭代 newVal, 使之 reactive, 再用 newVal 替换掉 val, 再执行对应操作(渲染逻辑)
else if (isObject(newVal)) {
console.info(`set ${key} - ${val} - ${newVal} - newVal is Object`);
new Observer(newVal);
val = newVal;
}
// 如果 newVal 和 val 不相等,且因为 newVal 为基础类型, 所以用 newVal 替换掉 val, 再执行对应操作(渲染逻辑)
else if (!isObject(newVal)) {
console.info(`set ${key} - ${val} - ${newVal} - newVal is Basic Value`);
val = newVal;
}
}
})
}
function isObject(data) {
if (typeof data === 'object' && data != 'null') {
return true;
}
return false;
}
index.js
import { Observer } from './source/Observer.js';
// 声明一个 obj,为 plain Object
const obj = {
a: {
aa: 1
},
b: 2,
}
// 将 obj 整体 reactive 化
new Observer(obj);
// 无输出
obj.b = 2;
// set b - 2 - 3 - newVal is Basic Value
obj.b = 3;
// set b - 3 - [object Object] - newVal is Object
obj.b = {
bb: 4
}
// get b-[object Object]
obj.b;
// get a-[object Object]
obj.a;
// get aa-1
obj.a.aa
// set aa - 1 - 3 - newVal is Basic Value
obj.a.aa = 3
这样,我们就完成了 Vue 的第一个核心逻辑, 成功把一个任意层级的 plain object 转化成 reactive object
2. 如何实现一个 watcher
前面讲的是如何将 plain object 转换成 reactive object. 接下来讲一下,如何实现一个watcher.
实现的伪代码应如下:
伪代码
// 传入 data 参数新建新建一个 vue 对象
const v = new Vue({
data: {
a:1,
b:2,
}
});
// watch data 里面某个 a 节点的变动了,如果变动,则执行 cb
v.$watch('a',function(){
console.info('the value of a has been changed !');
});
// watch data 里面某个 b 节点的变动了,如果变动,则执行 cb
v.$watch('b',function(){
console.info('the value of b has been changed !');
})
Vue.js
// 引入将上面中实现的 Observer
import { Observer } from './Observer.js';
import { Watcher } from './Watcher.js';
export default class Vue {
constructor(options) {
// 在 this 上挂载一个公有变量 $options ,用来暂存所有参数
this.$options = options
// 声明一个私有变量 _data ,用来暂存 data
let data = this._data = this.$options.data
// 在 this 上挂载所有 data 里的 key 值, 这些 key 值对应的 get/set 都被代理到 this._data 上对应的同名 key 值
Object.keys(data).forEach(key => this._proxy(key));
// 将 this._data 进行 reactive 化
new Observer(data, this)
}
// 对外暴露 $watch 的公有方法,可以对某个 this._data 里的 key 值创建一个 watcher 实例
$watch(expOrFn, cb) {
// 注意,每一个 watcher 的实例化都依赖于 Vue 的实例化对象, 即 this
new Watcher(this, expOrFn, cb)
}
// 将 this.keyName 的某个 key 值的 get/set 代理到 this._data.keyName 的具体实现
_proxy(key) {
var self = this
Object.defineProperty(self, key, {
configurable: true,
enumerable: true,
get: function proxyGetter() {
return self._data[key]
},
set: function proxySetter(val) {
self._data[key] = val
}
})
}
}
Watch.js
// 引入Dep.js, 是什么我们待会再说
import { Dep } from './Dep.js';
export class Watcher {
constructor(vm, expOrFn, cb) {
this.cb = cb;
this.vm = vm;
this.expOrFn = expOrFn;
// 初始化 watcher 时, vm._data[this.expOrFn] 对应的 val
this.value = this.get();
}
// 用于获取当前 vm._data 对应的 key = expOrFn 对应的 val 值
get() {
Dep.target = this;
const value = this.vm._data[this.expOrFn];
Dep.target = null;
return value;
}
// 每次 vm._data 里对应的 expOrFn, 即 key 的 setter 被触发,都会调用 watcher 里对应的 update方法
update() {
this.run();
}
run() {
// 这个 value 是 key 被 setter 调用之后的 newVal, 然后比较 this.value 和 newVal, 如果不相等,则替换 this.value 为 newVal, 并执行传入的cb.
const value = this.get();
if (value !== this.value) {
this.value = value;
this.cb.call(this.vm);
}
}
}
对于什么是 Dep, 和 Watcher 里的 update() 方法到底是在哪个时候被谁调用的,后面会说
3. 如何收集 watcher 的依赖
前面我们讲了 watcher 的大致实现,以及 Vue 代理 data 到 this 上的原理。现在我们就来梳理一下,Observer/Watcher 之间的关系,来说明它们是如何调用的.
首先, 我们要来理解一下 watcher 实例的概念。实际上 Vue 的 v-model, v-bind , {{ mustache }}, computed, watcher 等等本质上是分别对 data 里的某个 key 节点声明了一个 watcher 实例.
<input v-model="abc">
<span>{{ abc }}</span>
<p :data-key="abc"></p>
...
const v = new Vue({
data:{
abc: 111,
}
computed:{
cbd:function(){
return `${this.abc} after computed`;
}
watch:{
abc:function(val){
console.info(`${val} after watch`)
}
}
}
})
这里,Vue 一共声明了 4 个 watcher 实例来监听abc, 1个 watcher 实例来监听 cbd. 如果 abc 的值被更改,那么 4 个 abc - watcher 的实例会执行自身对应的特定回调(比如重新渲染dom,或者是打印信息等等)
不过,Vue 是如何知道,某个 key 对应了多少个 watcher, 而 key 对应的 value 发生变化后,又是如何通知到这些 watcher 来执行对应的不同的回调的呢?
实际上更深层次的逻辑是:
在 Observer阶段,会为每个 key 都创建一个 dep 实例。并且,如果该 key 被某个 watcher 实例 get, 把该 watcher 实例加入 dep 实例的队列里。如果该 key 被 set, 则通知该 key 对应的 dep 实例, 然后 dep 实例会将依次通知队列里的 watcher 实例, 让它们去执行自身的回调方法
dep 实例是收集该 key 所有 watcher 实例的地方.
watcher 实例用来监听某个 key ,如果该 key 产生变化,便会执行 watcher 实例自身的回调
相关代码如下:
Dep.js
export class Dep {
constructor() {
this.subs = [];
}
// 将 watcher 实例置入队列
addSub(sub) {
this.subs.push(sub);
}
// 通知队列里的所有 watcher 实例,告知该 key 的 对应的 val 被改变
notify() {
this.subs.forEach((sub, index, arr) => sub.update());
}
}
// Dep 类的的某个静态属性,用于指向某个特定的 watcher 实例.
Dep.target = null
observer.js
import {Dep} from './dep'
function makeItReactive(obj, key, val) {
var dep = new Dep()
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: () => {
// 收集依赖! 如果该 key 被某个 watcher 实例依赖,则将该 watcher 实例置入该 key 对应的 dep 实例里
if(Dep.target){
dep.addSub(Dep.target)
}
return val
},
set: (newVal) => {
if (newVal === val) {
return;
}
else if (isObject(newVal)) {
new Observer(newVal);
val = newVal;
// 通知 dep 实例, 该 key 被 set,让 dep 实例向所有收集到的该 key 的 watcher 实例发送通知
dep.notify()
}
else if (!isObject(newVal)) {
val = newVal;
// 通知 dep 实例, 该 key 被 set,让 dep 实例向所有收集到的该 key 的 watcher 发送通知
dep.notify()
}
}
})
}
watcher.js
import { Dep } from './Dep.js';
export class Watcher {
constructor(vm, expOrFn, cb) {
this.cb = cb;
this.vm = vm;
this.expOrFn = expOrFn;
this.value = this.get();
}
get() {
// 在实例化某个 watcher 的时候,会将Dep类的静态属性 Dep.target 指向这个 watcher 实例
Dep.target = this;
// 在这一步 this.vm._data[this.expOrFn] 调用了 data 里某个 key 的 getter, 然后 getter 判断类的静态属性 Dep.target 不为null, 而为 watcher 的实例, 从而把这个 watcher 实例添加到 这个 key 对应的 dep 实例里。 巧妙!
const value = this.vm._data[this.expOrFn];
// 重置类属性 Dep.target
Dep.target = null;
return value;
}
// 如果 data 里的某个 key 的 setter 被调用,则 key 会通知到 该 key 对应的 dep 实例, 该Dep实例, 该 dep 实例会调用所有 依赖于该 key 的 watcher 实例的 update 方法。
update() {
this.run();
}
run() {
const value = this.get();
if (value !== this.value) {
this.value = value;
// 执行 cb 回调
this.cb.call(this.vm);
}
}
}
总结:
至此, Watcher, Observer , Dep 的关系全都梳理完成。而这些也是 Vue 实现的核心逻辑之一。再来简单总结一下三者的关系,其实是一个简单的 观察-订阅 的设计模式, 简单来说就是, 观察者观察数据状态变化, 一旦数据发生变化,则会通知对应的订阅者,让订阅者执行对应的业务逻辑 。我们熟知的事件机制,就是一种典型的观察-订阅的模式
Observer, 观察者,用来观察数据源变化.
Dep, 观察者和订阅者是典型的 一对多 的关系,所以这里设计了一个依赖中心,来管理某个观察者和所有这个观察者对应的订阅者的关系, 消息调度和依赖管理都靠它。
Watcher, 订阅者,当某个观察者观察到数据发生变化的时候,这个变化经过消息调度中心,最终会传递到所有该观察者对应的订阅者身上,然后这些订阅者分别执行自身的业务回调即可
参考
Vue源码解读-滴滴FED
代码参考
蓝蓝设计( www.lanlanwork.com )是一家专注而深入的界面设计公司,为期望卓越的国内外企业提供卓越的UI界面设计、BS界面设计 、 cs界面设计 、 ipad界面设计 、 包装设计 、 图标定制 、 用户体验 、交互设计、 网站建设 、平面设计服务。
蓝蓝设计的小编 http://www.lanlanwork.com