NodeJs
TIP
最后更新时间:2019年10月07日
字数:22229
那些所谓的牛人,只不过是普通人把自己的想法付诸了行动而已,你的行动力直接决定了你的人生高度
学习资料
Nodejs
Nodejs定义
标准官方说法:Node.js 是一个基于 Chrome V8 引擎的 JavaScript 运行环境
简单的说 Node.js 就是运行在服务端的 JavaScript
V8引擎
- V8引擎是一个JavaScript引擎实现,后被谷歌收购,随后进行了开源。
- V8使用C++开发,在运行JavaScript之前,相比其它的JavaScript的引擎转换成字节码或解释执行,V8将其编译成原生机器码(IA-32, x86-64, ARM, or MIPS CPUs),并且使用了如内联缓存(inline caching)等方法来提高性能。
- 有了这些功能,JavaScript程序在V8引擎下的运行速度媲美二进制程序。V8支持众多操作系统,如windows、linux、android等,也支持其他硬件架构,如IA32,X64,ARM等,具有很好的可移植和跨平台特性
JavaScript引擎
- JavaScript引擎是执行JavaScript代码的程序或解释器。
- javaScript引擎可以实现为标准解释器或即时编译器,它以某种形式将JavaScript编译为字节码
Nodejs特点
Node.js是服务端JavaScript环境,使用了一个事件驱动、非阻塞式 I/O 的模型,使其轻量又高效
事件驱动程序
- Node.js 使用事件驱动模型,当web server接收到请求,就把它关闭然后进行处理,然后去服务下一个web请求
- 当这个请求完成,它被放回处理队列,当到达队列开头,这个结果被返回给用户
- 这个模型非常高效可扩展性非常强,因为web server一直接受请求而不等待任何读写操作
- 相当于观察者模式,在事件驱动模型中,会生成一个主循环来监听事件,当检测到事件时触发回调函数
非阻塞 I/O
- 事件驱动中,一直接受请求,而不等待任何读写操作,可以看做是非阻塞式IO或者事件驱动IO
- Node.js 异步编程的直接体现就是回调,非阻塞I/O依赖也是回调函数
- 例如
- 我们可以一边读取文件,一边执行其他命令
- 在文件读取完成后,我们将文件内容作为回调函数的参数返回
- 这样在执行代码时就没有阻塞或等待文件 I/O 操作
- 这就大大提高了 Node.js 的性能,可以处理大量的并发请求
console.log('开始执行读取文件函数------1');
fs.readFile(function(data){
console.log(data,'去读文件函数结束,获取数据-------2');
})
console.log('执行读取文件函数后--------3');
// 结果:1,3,2
2
3
4
5
6
7
8
单线程
Nodejs是基于JavaScript写的,JavaScript是单线程,Nodejs也没用突破这个极限,也是单线程的
npm
拥有世界最大的开源生态系,Nodejs内置了npm,npm-v 即可查看
npm可以做些什么:
- 允许用户从NPM服务器下载别人编写的第三方包到本地使用
- 允许用户从NPM服务器下载并安装别人编写的命令行程序到本地使用
- 允许用户将自己编写的包或命令行程序上传到NPM服务器供别人使用
TIP
只要是我们想到的功能,都已经有人帮我们已经写好了,就在 npm 这个代码仓库里
Nodejs可以做什么
- Nodejs可以像JavaScript一样,编写web网站应用程序
- Nodejs可以编写桌面应用程序,想写了解:Electron和NW.js,原来叫做nodeWebkit,这两个都是基于
Node.js
运行,都是可以利用html+css+js就可以开发桌面应用 - Nodejs 可以开发控制台程序(命令行程序)
Nodejs下载安装
由于Nodejs支持多个平台,不同平台安装是不一样的,大家可以安装的时候要注意!
下载地址
运行程序
node 程序目录 运行即可
TIP
初期学习可以使用node命令运行,后期可以在package.json中配置script启动命令
项目开发中可以使用pm2来配合nodejs做开发,非常方便和自动化
参考资料:pm2学习使用教程
npm
npm其实分为三种:
- 个人npm(很少)
- 企业npm(大公司很多都会采用)
- 全世界共用的npm
TIP
我们平时用的npm全世界共用的大型npm,不过很多企业都是有自己企业内部的npm的
企业npm
企业内部代码复用的npm,方便共享通用的业务代码
企业自建npm原因
- 我们平时用的npm都是需要互联网的,并且是全世界共用的
- 这就要求我们能够连接到互联网,但是某些情况下,是不允许联网的
- 或者有些时候,因为网络稳定性问题,导致npm安装依赖很慢,消耗时间
- 项目间的常用业务代码需要共享,但是npm是全世界都可以看到的,对于保密性来说,是不可取的,这个时候我们自建企业内部的npm就是很有必要的
- 因为使用npm是别人开源的代码,不可控,也可能存在很大的安全隐患
- 对于企业来说,安全是第一要素
采用的技术
- 花钱购买,主要你肯花钱,一切技术解决方案都可以有的
- sinopia
- Cnpmjs.org
package.json
- Node管理本地安装 npm 包的最好方式就是创建 package.json 文件
package.json作用
- 作为一个描述文件,描述了你的项目依赖哪些包
- 允许我们使用 “语义化版本规则”指明你项目依赖包的版本
- 让你的构建更好地与其他开发者分享,便于重复使用
TIP
- package.json文件很重要,如果出错,项目可能就废掉了!
- package.json文件文件中不允许出现注释!不允许注释!不允许注释!
创建package.json
- npm init
- 会问你一系列问题,其实就是package的一些配置(项目的一些配置)
- 输入内容或者选择默认即可
- npm init -y (yes也行)
- 自动创建,问题全都是默认的选项
- 注意:项目文件目录不允许中文,否则报错
本地安装(local)
npm install <Module Name>
npm i <Module Name>
2
TIP
npm install 和 npm i 在不同的操作系统上面是有区别的,下面的区别,在有些系统上会发生
- 用npm i安装的模块无法用npm uninstall删除,用npm uninstall i才卸载掉
- npm i会帮助检测与当前node版本最匹配的npm包版本号,并匹配出来相互依赖的npm包应该提升的版本号
- 部分npm包在当前node版本下无法使用,必须使用建议版本
- 部分npm包在当前node版本下无法使用,必须使用建议版本
全局安装(global)
npm install <Module Name> -g
npm install <Module Name> --global
2
安装指定版本
只需要在其后加 ‘@版本号’
npm install <Module Name>@版本号
2
安装选项
- -S, --save 安装包信息将加入到dependencies(生产阶段的依赖)
- -D, --save-dev 安装包信息将加入到devDependencies(开发阶段的依赖),所以开发阶段一般使用它
- -O, --save-optional 安装包信息将加入到optionalDependencies(可选阶段的依赖)
- -E, --save-exact 精确安装指定模块版本
查看安装的模块
# 查看安装包
npm ls
# 查看全局安装包
npm ls -g
2
3
4
检查模块是否已经过时
npm outdated
- current:当前安装的包的版本
- wanted:package.json里面写的版本
- latest:最新版本
卸载模块
npm uninstall <Module Name>
更新模块
//更新当前项目中安装的某个包
npm update <包名>
//更新当前项目中安装的所有包
npm update
//更新全局安装的包
npm update <包名> -g
2
3
4
5
6
7
8
清除npm缓存
npm cache clean -f
npm run
- package.json 文件中的scripts字段可以自定义一些命令行语句
- 可以通过npm run 来运行这些命令行语句
{
"script":{
"dev":"node ./dev.js",
"build":"node ./app.js"
}
}
// 运行
npm run dev
npm run build
2
3
4
5
6
7
8
9
10
启动模块
npm start
npm可以简写的脚步命令
npm start === npm run start
npm stop === npm run stop
npm test === npm run test
npm restart === npm run stop && npm run restart && npm run start
2
3
4
查看配置
npm config 结果
npm config set <key> <value> [-g|--global]
npm config get <key>
npm config delete <key>
npm config list
npm config edit
npm get <key>
npm set <key> <value> [-g|--global]
2
3
4
5
6
7
8
9
TIP
对应的是npm配置文件:npmrc
npm 设置默认配置
- 使用
npm set
命令用来设置环境变量 - 也可以用它来为
npm init
设置默认值,这些值会保存在~/.npmrc
文件中
npm set init-author-name 'xiaofengge'
npm set init-author-email 'Your email'
npm set init-author-url 'http://www.xuefeng666.com.com'
npm set init-license 'MIT'
2
3
4
npm发布自己编写的包
注册账号
- 注册网址
- 注册需要填写的参数:
- Full Name:全名
- Public Email:邮箱
- Username:用户名
- Password:密码
创建自己的项目
npm init
- 全部默认即可
- 注意你的包名,也就是name字段,npm规定包名不允许重复
TIP
在命令行里面输入npm install 你要取的名字
,如果报错,表示npm上没有跟你同名的包,我们可以使用
如果成功下载下来了,说明这个包名被人使用了,那就只能改名了
添加README.md文件
- 这个是项目说明文件,用来描述我们的项目
- README.md文件编写教材 强烈推荐看一下
- 如果正式版本的,最好推荐英文说明
创建index.js
- 其实相当于编写我们的代码
- 入口文件
切换到官方镜像源
npm config set registry http://www.npmjs.org
TIP
国内开发者大部分都会把npm镜像源切换到淘宝的镜像源,原因你们都懂得!
这里要注意:我们发布npm,需要切换到npm官方镜像源发布的
命令行登录
切换到要发布的目录中
npm login
- 输入用户名,密码就可登陆成功。
npm publish 发布
- 每一次发布新的一版,version版本要改变,不然npm会给我报错。
- 一般情况下,一旦你要修改你已经发布后的代码,然后又要执行发布操作,务必到package.json里面,把version改一下
- 比如从1.0.0改为1.0.1,然后在执行
npm publish
,这样就可以成功发布了
cd 进入项目根目录
npm longin 登录账户
npm publish 包名 发布我们的npm包
2
3
安装我们发布的包
npm install 自己的包名,即可安装成功
TIP
刚发布的包可能是无法下载的,能看到,但是点不进去,也无法安装,小峰哥是等到第二天才能下载安装的
发布过程中的一些注意事项
- 镜像源的问题,如果切换回淘宝镜像源,要注意自己的包是否被淘宝镜像源同步的问题
- 全局切换到淘宝镜像源: npm config set registry http://www.npmjs.org
- 检测是否切换到了淘宝镜像源:npm info underscore
删除自己发布的包
- npm基本上不允许删除自己发布的包
- 导火索:
- 有一个大牛删除了一个非常知名的库
- 导致很多依赖它的著名的npm包构建失败
- 甚至影响到了不少公司的生产环境
- 结果:
- 版本更新少于24小时的包允许下架
- 超过24小时的包的下架需要联系npm维护者
- 如果有npm维护者参与,npm将检查是否有其他包依赖该包,如果有则不允下架
- 如果某个包的所有版本都被移除,npm会上传一个空的占位包,以防后来的使用者不小心引用怀有恶意的替代者
切换淘宝镜像源
- 临时使用
npm --registry https://registry.npm.taobao.org
- 持久使用
npm config set registry https://registry.npm.taobao.org
- 配置后可通过下面方式来验证是否成功
npm config get registry
找到taobao
关键字即可
- 通过 cnpm 使用
npm install -g cnpm --registry=https://registry.npm.taobao.org
TIP
npm和cnpm混用之后,再用npm升级模块导致报错
解决办法:
删除node_modules文件下的文件后,重新执行npm install即可
自建公司私有npm
框架选择
一开始选择的是sinopia,网上找到的教程大部分也都是这个,但是很不幸的是sinopia 已经在3年前就停止维护了,所幸的是有一群人又出了一个sinopia的fork,也就是sinopia的分支,叫做:verdaccio ,并且在一直维护,所以小峰哥选择的是 verdaccio 来构建我们自己的私有npm
安装形式 docker
# 拉取镜像
docker pull verdaccio/verdaccio
# 在根目录下创建docker文件
mkdir -p ~/docker/data
# 进入data文件
cd ~/docker/data
# 拉取实例demo到data目录下(注:服务器可能没有安装git命令,请自行安装,我的服务器centos:yum install git)
git clone https://github.com/verdaccio/docker-examples
# 进入目录
cd ~/docker/data/docker-examples
# 推荐先创建文件,再拷贝mv文件(小峰哥遇到过问题,拷贝文件后运行提示说不是一个安装区,很尴尬)
mkdir -p ~/docker/verdaccio
# 移动配置文件
mv docker-local-storage-volume ~/docker/verdaccio
# 设置文件权限
chown -R 100:101 ~/docker/verdaccio
# 启动镜像
docker run --name verdaccio -itd -v ~/docker/verdaccio:/verdaccio -p 8082:4873 verdaccio/verdaccio
# 启动提示:portainer.io 后台看到的
warn --- config file - /verdaccio/conf/config.yaml # 告诉我们配置文件的地址
warn --- Plugin successfully loaded: verdaccio-htpasswd
warn --- Plugin successfully loaded: verdaccio-audit
warn --- http address - http://0.0.0.0:4873/ - verdaccio/4.0.4 # 告诉我们访问的地址,默认是端口号:4873
# 访问
http://ip:8082 # 因为我们对外开放的端口是8082
# 因为我们用docker映射的是8082地址,所以我们需要进行修改
# 进入配置文件目录
cd ~/docker/verdaccio/conf/
# 备份一份配置文件
cp ./config.yaml ./config.yaml.back
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
修改配置文件
vim ./conf.yaml
#
# Do not configure host and port under `listen` in this file
# as it will be ignored when using docker.
# see https://github.com/verdaccio/verdaccio/blob/master/wiki/docker.md#docker-and-custom-port-configuration
#
# Look here for more config file examples:
# https://github.com/verdaccio/verdaccio/tree/master/conf
#
# path to a directory with all packages
storage: /verdaccio/storage # 所有包的缓存目录
#验证信息
auth:
htpasswd:
# 用户信息存储目录
file: /verdaccio/conf/htpasswd
# Maximum amount of users allowed to register, defaults to "+inf".
# You can set this to -1 to disable registration.
#max_users: 1000
security:
api:
jwt:
sign:
expiresIn: 60d
notBefore: 1
web:
sign:
expiresIn: 7d
# a list of other known repositories we can talk to
# #公有仓库配置
uplinks:
npmjs:
url: https://registry.npmjs.org/
packages:
'@jota/*':
access: $all
publish: $all
'@*/*':
# scoped packages
access: $all
publish: $all
# 代理:表示没有的仓库会去这个npmjs 里面去找
# npmjs 又指向 https://registry.npmjs.org/ ,就是上面的 uplinks 配置
proxy: npmjs
'**':
# 三种身份,所有人,匿名用户,认证(登陆)用户
# allow all users (including non-authenticated users) to read and
# publish all packages
#
# you can specify usernames/groupnames (depending on your auth plugin)
# and three keywords: "$all", "$anonymous", "$authenticated"
# 是否可访问所需要的权限
access: $all
# allow all known users to publish packages
# (anyone can register by default, remember?)
# 发布package 的权限
publish: $authenticated
# if package is not available locally, proxy requests to 'npmjs' registry
# 如果package 不存在,就向代理的上游服务发起请求
proxy: npmjs
# To use `npm audit` uncomment the following section
middlewares:
audit:
enabled: true
# log settings
logs:
- {type: stdout, format: pretty, level: trace}
#- {type: file, path: verdaccio.log, level: info}
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
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
TIP
docker 启动,我们没添加listen: 0.0.0.0:4873,这一项配置,也成功可以访问了。 如果不是docker 启动,亲添加配置:listen: 0.0.0.0:4873
添加用户
因为这里是内部使用,所以是没有npm注册权限的,大家可以配置专属的账号来发布(publish)包即可 这里的用户名和密码要通过专门的网站生成包装(http://www.htaccesstools.com/htpasswd-generator/) 输入用户名和密码,然后将得到的结果复制到
htpasswd
文件中即可
# 进入数据文件目录
cd ~/docker/verdaccio/conf
# htpasswd:里面存放的就是用户名称和密码
vim htpasswd
# 将原有默认用户和密码清除掉,添加我们生成的用户名和密码
2
3
4
5
发布demo包
我们使用的是nrm管理npm镜像源,可以下面一个章节介绍的就是nrm,可以提前看一下
# nrm添加镜像源
nrm add xuefeng_npm http://xx.xx.xx.xx:8082/
# 使用私有仓库
nrm use xuefeng_npm
# 创建本地npm
npm init
# 添加文件,代码等从操作
# 登陆(因为我们设置了必须登陆才允许发布代码)
npm login
# 发布
npm publish
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
使用
nrm ls # 查看我们当前使用的是否使我们的私有镜像源
# 下载即可(注意配置的权限,如果配置了权限,可能需要先登录才可以下载)
npm install 我们的包名
2
3
TIP
如果我们下载的包名在我们的私有服务器上面没有找到,会自动代理到我们 verdaccio 配置文件中配置的代理proxy服务器去下载对应的包 也就是说:我们私有包,发布到自己的仓库,下载使用就行,不是私有的包,完全不会出现影响
nrm
nrm 是一个 npm 源管理器,允许你快速地在 npm 源间切换
安装
sudo npm install -g nrm
使用
# 列出可用的源
nrm ls
# 结果
npm ---- https://registry.npmjs.org/
cnpm --- http://r.cnpmjs.org/
* taobao - https://registry.npm.taobao.org/
nj ----- https://registry.nodejitsu.com/
npmMirror https://skimdb.npmjs.com/registry/
edunpm - http://registry.enpmjs.org/
# 通过 nrm use指令来切换不同的源
nrm use taobao
# 添加源
nrm add 别名 源地址
# 删除源
nrm del 别名
# 测试源的响应时间
nrm test 别名
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
模板引擎(EJS)
EJS介绍
- EJS(Embedded JavaScript templates)是一个简单高效的模板语言,通过数据和模板,可以生成HTML标记文本。
- 可以说EJS是一个JavaScript库,EJS可以同时运行在客户端和服务器端,客户端安装直接引入文件即可,服务器端用npm包安装
为什么使用EJS
- 传统JavaScript字符串拼接方式有很多痛点
- 代码丑陋
- 破坏原有的HTML结构
- 代码难以阅读
- 无法解决很多复杂情况下的问题
- 不能很好的实现视图与数据的分离
- 使用EJS可以解决的问题:
- 使用EJS来找回你的明确、维护性良好的HTML代码结构
- 可以解决视图与数据分离的问题
- 简单,容易入门,功能完善
下载和安装
npm install ejs #ejs 安装
EJS特点
- 快速编译与绘制输出
- 简洁的模板标签:<% %>
- 自定义分割符(例如:用 <? ?> 替换 <% %>)
- 引入模板片段
- 同时支持服务器端和浏览器 JS 环境
- JavaScript 中间结果静态缓存
- 模板静态缓存
- 兼容 Express 视图系统
EJS对象解读
const ejs = require('ejs')
console.log(ejs)
{ cache:
{ _data: {},
set: [Function: set],
get: [Function: get],
reset: [Function: reset] },
fileLoader: [Function: readFileSync],
localsName: 'locals',
promiseImpl: [Function: Promise],
resolveInclude: [Function],
compile: [Function: compile],
render: [Function],
renderFile: [Function],
clearCache: [Function],
escapeXML: { [Function] toString: [Function] },
__express: [Function],
VERSION: '2.6.1',
name: 'ejs' }
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
render(重要)
通过模板路径和模板数据、编译配置获取编译结果,得到html字符串
exports.render = function (template, d, o) {
var data = d || {};
var opts = o || {};
// No options object -- if there are optiony names
// in the data, copy them to options
if (arguments.length == 2) {
utils.shallowCopyFromList(opts, data, _OPTS);
}
// handleCache 缓存中有编译函数,获取编译函数;若无,通过文件名读取文件后,获得编译函数,并存入缓存
return handleCache(opts, template)(data);
};
2
3
4
5
6
7
8
9
10
11
12
13
compile(重要)
编译模板字符串template,返回编译函数,编译配置为opts
// template:ejs模板
// opts:参数配置对象
// 返回值:返回值可以是任何类型,并且返回值取决于opts.client和opts.async
exports.compile = function compile(template, opts) {
var templ;
// 旧版本的scope替换为contex
if (opts && opts.scope) {
if (!scopeOptionWarned){
console.warn('`scope` option is deprecated and will be removed in EJS 3');
scopeOptionWarned = true;
}
if (!opts.context) {
opts.context = opts.scope;
}
delete opts.scope;
}
templ = new Template(template, opts);
return templ.compile();
};
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
render和compile配置对象
var _OPTS_PASSABLE_WITH_DATA = ['delimiter', 'scope', 'context', 'debug', 'compileDebug',
'client', '_with', 'rmWhitespace', 'strict', 'filename', 'async'];
// 因为要兼容Express2,3,4各个版本,所以这里有2个,并且有render和renderFile2个方法
var _OPTS_PASSABLE_WITH_DATA_EXPRESS = _OPTS_PASSABLE_WITH_DATA.concat('cache');
// 调用
// render方法
utils.shallowCopyFromList(opts, data, _OPTS_PASSABLE_WITH_DATA);
// renderFile方法
utils.shallowCopyFromList(opts, data, _OPTS_PASSABLE_WITH_DATA_EXPRESS);
utils.js
// list以数组形式约定从from浅拷贝给to的属性名或方法名
exports.shallowCopyFromList = function (to, from, list) {
for (var i = 0; i < list.length; i++) {
var p = list[i];
if (typeof from[p] != 'undefined') {
to[p] = from[p];
}
}
return to;
};
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
var _DEFAULT_DELIMITER = '%';
var _DEFAULT_LOCALS_NAME = 'locals';
// 配置项
function Template(text, opts) {
opts = opts || {};
var options = {};
options.client = opts.client || false;
options.escapeFunction = opts.escape || utils.escapeXML;
options.compileDebug = opts.compileDebug !== false;
options.debug = !!opts.debug;
options.filename = opts.filename;
options.delimiter = opts.delimiter || exports.delimiter || _DEFAULT_DELIMITER;
options.strict = opts.strict || false;
options.context = opts.context;
options.cache = opts.cache || false;
options.rmWhitespace = opts.rmWhitespace;
options.root = opts.root;
options.outputFunctionName = opts.outputFunctionName;
options.localsName = opts.localsName || exports.localsName || _DEFAULT_LOCALS_NAME;
options.views = opts.views;
options.async = opts.async;
if (options.strict) {
options._with = false;
}
else {
options._with = typeof opts._with != 'undefined' ? opts._with : true;
}
this.opts = options;
this.regex = this.createRegex();
}
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
cache
- 是否缓存解析后的模版,需要filename作为key
- 默认false
delimiter
- 指定标签的开闭符号,默认为 % ,可以指定例如 ? 或$
scope
- complile后的Function执行所在的上下文环境
- 旧版本的scope替换为contex
context
- complile后的Function执行所在的上下文环境
debug
- 标识是否是debeg状态
- debug为true则会输出生成的Function内容
compileDebug
- 标识是否是编译debug
- 为true则会生成解析过程中的跟踪信息,用于调试
client
- 标识是否用于浏览器客户端运行
- 为true则返回解析后的可以单独运行的Function函数
- 默认:false
_with
- 指定模板是否使用with(){}函数结构
rmWhitespace
- 删除所有可删除的安全空格,包括前导和尾随空格。
- 它还为所有scriptlet标记启用了-%>行提高了更安全版本(它不会在行的中间删除新的标记行)
strict
- 设置为时
true
,生成的函数处于严格模式
filename
- 模版文件名
- 用于引入文件或指定cache的键名
async
- 设置为
true
,EJS将使用异步函数进行渲染 - 取决于JavaScript运行时中的异步/等待支持
其余对象属性解读
name
EJS检测名称
// 源码实现
var _NAME = 'ejs';
exports.name = _NAME;
2
3
VERSION
EJS版本
// 源码实现
// 最终导出的版本是在package.json中定义的version字段
var _VERSION_STRING = require('../package.json').version字段;
exports.VERSION = _VERSION_STRING;
2
3
4
cache源码对象解读
EJS模板函数缓存
- 这可以是来自lru-cache NPM模块的LRU对象
- 默认情况下,它是一个不执行任何类型的限制的持续增长的简单进程内缓存
- 组成部分:
- _data:内置数据对象
- set:在_data设置数据(key,value)
- get:根据key在_data中获取数据
- reset:重置_data = {} 空对象
exports.cache = {
_data: {},
set: function (key, val) {
this._data[key] = val;
},
get: function (key) {
return this._data[key];
},
reset: function () {
this._data = {};
}
};
2
3
4
5
6
7
8
9
10
11
12
fileLoader
自定义文件加载程序
用于模板预处理或限制对文件系统的特定部分的访问
var fs = require('fs');
exports.fileLoader = fs.readFileSync
2
localsName
- 包含本地变量的对象的名称
- 如果不是“undefined”,则该变量由 Options.localsName 覆盖
var _DEFAULT_LOCALS_NAME = 'locals';
exports.localsName = _DEFAULT_LOCALS_NAME;
2
promiseImpl
封装Promise
exports.promiseImpl = (new Function('return this;'))().Promise;
resolveInclude
// 获取子模板的绝对路径
// 当前模板设置filename文件或目录路径,name传参为相对filename目录的文件路径
exports.resolveInclude = function(name, filename, isDir) {
var dirname = path.dirname;
var extname = path.extname;
var resolve = path.resolve;
var includePath = resolve(isDir ? filename : dirname(filename), name);
var ext = extname(name);
if (!ext) {
includePath += '.ejs';
}
return includePath;
};
2
3
4
5
6
7
8
9
10
11
12
13
renderFile
**通过模板路径和模板数据获取编译结果,并执行回调 **
/**
* 通过模板路径和模板数据获取编译结果,并执行回调
* @param {String} filename 模板路径
* @param {Object} [data={}] 模板数据
* @param {Options} [opts={}] 编译配置
* @param {RenderFileCallback} cb 回调函数
*/
exports.renderFile = function () {
var args = Array.prototype.slice.call(arguments);
var filename = args.shift();
var cb;
var opts = {filename: filename};
var data;
var viewOpts;
// Do we have a callback?
if (typeof arguments[arguments.length - 1] == 'function') {
cb = args.pop();
}
// Do we have data/opts?
if (args.length) {
// Should always have data obj
data = args.shift();
// Normal passed opts (data obj + opts obj)
if (args.length) {
// Use shallowCopy so we don't pollute passed in opts obj with new vals
utils.shallowCopy(opts, args.pop());
}
// Special casing for Express (settings + opts-in-data)
else {
// Express 3 and 4
if (data.settings) {
// Pull a few things from known locations
if (data.settings.views) {
opts.views = data.settings.views;
}
if (data.settings['view cache']) {
opts.cache = true;
}
// Undocumented after Express 2, but still usable, esp. for
// items that are unsafe to be passed along with data, like `root`
viewOpts = data.settings['view options'];
if (viewOpts) {
utils.shallowCopy(opts, viewOpts);
}
}
// Express 2 and lower, values set in app.locals, or people who just
// want to pass options in their data. NOTE: These values will override
// anything previously set in settings or settings['view options']
utils.shallowCopyFromList(opts, data, _OPTS_PASSABLE_WITH_DATA_EXPRESS);
}
opts.filename = filename;
}
else {
data = {};
}
return tryHandleCache(opts, data, cb);
};
// 通过模板路径和模板数据获取编译结果,并执行回调
exports.__express = exports.renderFile;
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
56
57
58
59
60
61
62
63
clearCache
清空模板编译函数
// 清空模板编译函数
exports.clearCache = function () {
// 清除缓存
exports.cache.reset();
};
2
3
4
5
escapeXML
html转义
// ejs.js
exports.escapeXML = utils.escapeXML;
// utils.js
// html转义
exports.escapeXML = function (markup) {
return markup == undefined ? '' : String(markup).replace(_MATCH_HTML, encode_char);
};
// 函数转化成字符串时,同时将参数变量全部转化为字符串输入
exports.escapeXML.toString = function () {
return Function.prototype.toString.call(this) + ';\n' + escapeFuncStr;
};
2
3
4
5
6
7
8
9
10
11
12
####__express
通过模板路径和模板数据获取编译结果,并执行回调
// 和renderFile相关
// 通过模板路径和模板数据获取编译结果,并执行回调
exports.__express = exports.renderFile;
2
3
include
可以一段ejs模板作为公共模板(需要提供 'filename' 参数)
// 参数一:模板文件路径
// 参数二:需要传递的参数
<%- include('user/show', {user: user}); %>
2
3
标签
标签:<%
'脚本' 标签,用于流程控制,无输出
// 声明变量
<% title="EJS Template engine" %>
<ul>
// 普通流程语句
<% for(var i in users){ %>
// 这里是不输出东西的,因为就是一个<%%>
<span><% name1 %></span>
<% } %>
</ul>
2
3
4
5
6
7
8
9
10
标签:<%=和<%-
<%=:输出数据到模板(输出是转义 HTML 标签)
<%-:输出非转义的数据到模板
ejs.render({name:'<h1>小峰哥ejs</h1>})
<%= name %> // 结果:<h1>小峰哥ejs</h1>
<%- name %> // 结果:小峰哥ejs:一个h1标题
2
3
4
标签:%>和-%>
%>:一般结束标签
-%>:删除紧随其后的换行符
标签:<%_和_%>
<%_ 删除其前面的空格符
_%> 将结束标签后面的空格符删除
标签:<%#
注释标签:不执行、不输出内容
####标签:<%%
输出字符串:<%
Express + EJS
安装
npm install ejs # ejs 安装
npm install express # express 安装
2
示例代码
const express = require('express')
const path = require('path')
const app = express()
// 指定模板文件存放位置
app.set('views', path.join(__dirname,'views'))
app.engine('html', require('ejs').renderFile)
// 设置默认的模板引擎
// 第二个参数如果是html,render的时候,html文件就不用添加扩展名
// 第二个参数如果是ejs,render的时候,ejs文件就不用添加扩展名
// 注意:如果render的时候,文件添加了后缀名,都是可以渲染的
// app.set('view engine', 'html')
app.set('view engine', 'ejs')
// 路由
app.get('/ejs',function(req,res){
// 返回res.render渲染的模板文件
res.render('index.ejs',{name:'小峰哥ejs'})
})
app.get('/html',function(req,res){
res.render('admin.html',{name:'小峰哥html'})
})
app.listen('8080',function(){
console.log('服务器已启动,端口号:8080')
})
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
事件循环
事件循环
在浏览器或者nodejs环境中,运行时对js脚本的调度方式叫事件循环
// setTimeout 异步
setTimeout(() => {
console.log('timeout')
}, 0)
// Promise:异步
Promise.resolve().then(res =>{
console.log('promise')
})
// 主线程
console.log('main')
// 结果:main promise timeout
2
3
4
5
6
7
8
9
10
11
12
浏览器事件循环
为了协调时间(event)、用户交互(user interaction)脚本(script)、渲染(rendering)、网络(networking)等,用户代理(user agent)必须使用事件循环(event loops)
- 事件:PostMessage,MutationObserver等
- 用户交互:click、onScroll
- 渲染:解析DOM、CSS等
- 脚本:js脚本执行
JavaScript单线程原因
JavaScript是操作DOM的,这就决定了它必须是单线程的!!
因为如果多线程同时操作同一个DOM,那么问题就会非常复杂了,到底以哪一个线程为准呢!!
任务队列
- 单线程就意味着需要任务队列
- 因为I/O操作的时候CPU是空闲的,那么这个时候就会出现大量的性能浪费
- 所以js设计一门异步语言,就无须做等待了,
任务分类
同步任务:
- synchronous
异步任务:
- asynchronous
执行规则
- 所有的同步任务都在主线程上面执行,形参一个执行栈(execution context stack)
- 主线程之外,还存在一个“任务队列(task queue)”,只要异步任务有了运行结果,就在“任务队列”之中放置一个事件
- 一旦“执行栈”中所有的同步任务执行完毕,系统就会读取“任务队列”,看看里面有那些事件,那些对应的异步任务,于是结束等待状态,进入执行栈,开始执行
- 主线程不断重复上面的第三步
主线程从“任务队列”中读取事件,这个过程是不断循环的,所有整个的运行机制又称为 事件循环(Event Loop)
宏任务与微任务
JavaScript单线程中的任务可以分为宏任务和微任务
// setTimeout 异步
setTimeout(() => {
console.log('timeout')
}, 0)
// Promise:异步
Promise.resolve().then(res =>{
console.log('promise')
})
// 主线程
console.log('main')
// 结果:main promise timeout
2
3
4
5
6
7
8
9
10
11
12
- 碰到timeout,宏任务,放到IO中,继续执行
- 遇到Promise,微任务,遇到微任务,会执行所有微任务队列中的微任务,然后进行下一轮循环
- 新的循环一开始执行宏任务,也就是timeout的打印
- 注意:一开始的console是主线程,所以肯定是一开始就执行了
nodejs事件循环
事件循环允许nodejs执行非阻塞I/O操作,尽管JavaScript是单线程的,通过尽可能的将操作写在到内核。
由于大部分现代内核都是多线程的,因此他们可以处理在后台执行的多个操作,当其中一个操作完成时,内核会告诉nodejs,以便可以将对应的回调添加到轮训队列中以最终执行
Nodejs
是基于chrome
浏览器的V8
引擎构建的,也就说明他的模型与浏览器类似,我们的js会运行在单个进程的单个线程上。但是从严格意义上来讲。nodejs不是单线程架构,因为他有
I/O
线程,定时器线程等等,只不过这些都是由更底层的libuv
处理,libuv
将执行结果放入到队列中等待执行,这就涉及到了nodejs
的事件循环了
- 事件:EventEmitter
- 非阻塞I/O:网络请求,文件读写
- 脚本:js脚本执行
多进程
process 对象
- process 对象是一个全局变量,它提供当前 Node.js 进程的有关信息,以及控制当前 Node.js 进程
- 因为是全局变量,所以无需使用 require()
组成
- process.argv 属性返回一个数组,这个数组包含了启动Node.js进程时的命令行参数
- 第一个元素为process.execPath。如果需要获取argv[0]的值请参见node文档的 process.argv0
- 第二个元素为当前执行的JavaScript文件路径
- 剩余的元素为其他命令行参数
// 遍历打印 process.argv
process.argv.forEach((val, index) => {
console.log(`${index}: ${val}`);
});
node process-args.js one two=three four
0: /usr/local/bin/node
1: /Users/mjr/work/node/process-args.js
2: one
3: two=three
4: four
2
3
4
5
6
7
8
9
10
11
12