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编译为字节码

TIP

v8会把js代码转换为高效的机器码,而不在是依赖于解释器去执行

v8引入了JIT在运行时把js代码进行转换为机器码

参考资料:浏览器组成和工作原理

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
1
2
3
4
5
6
7
8

单线程

Nodejs是基于JavaScript写的,JavaScript是单线程,Nodejs也没用突破这个极限,也是单线程的

TIP

单线程和多线程是操作系统部分的知识点,大家可以去学习一下

题外话:如果你不是计算机专业出身的程序员,强烈建议你去学习一下操作系统这门课

参考资料:计算机操作系统

npm

  • 拥有世界最大的开源生态系,Nodejs内置了npm,npm-v 即可查看

  • 官方网站

  • NPM中文网

  • npm可以做些什么:

    • 允许用户从NPM服务器下载别人编写的第三方包到本地使用
    • 允许用户从NPM服务器下载并安装别人编写的命令行程序到本地使用
    • 允许用户将自己编写的包或命令行程序上传到NPM服务器供别人使用

TIP

只要是我们想到的功能,都已经有人帮我们已经写好了,就在 npm 这个代码仓库里

Nodejs可以做什么

  • Nodejs可以像JavaScript一样,编写web网站应用程序
  • Nodejs可以编写桌面应用程序,想写了解:ElectronNW.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是别人开源的代码,不可控,也可能存在很大的安全隐患
  • 对于企业来说,安全是第一要素

TIP

恶意攻击者发布和修改包含恶意的代码,上传到npm上面的一些大批量被使用的依赖包,进行攻击

月下载量千万的 npm 包被黑客篡改,Vue 开发者可能正在遭受攻击

ESLint的NPM账户遭黑客攻击,可能窃取用户NPM访问令牌

采用的技术

  • 花钱购买,主要你肯花钱,一切技术解决方案都可以有的
  • sinopia
  • Cnpmjs.org

TIP

自建npm参考教程

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>
1
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
1
2

安装指定版本

只需要在其后加 ‘@版本号’
npm install <Module Name>@版本号
1
2

安装选项

  • -S, --save 安装包信息将加入到dependencies(生产阶段的依赖)
  • -D, --save-dev 安装包信息将加入到devDependencies(开发阶段的依赖),所以开发阶段一般使用它
  • -O, --save-optional 安装包信息将加入到optionalDependencies(可选阶段的依赖)
  • -E, --save-exact 精确安装指定模块版本

查看安装的模块

# 查看安装包
npm ls   
# 查看全局安装包
npm ls -g 
1
2
3
4

检查模块是否已经过时

npm outdated
1
  • current:当前安装的包的版本
  • wanted:package.json里面写的版本
  • latest:最新版本

卸载模块

npm uninstall <Module Name>
1

更新模块

//更新当前项目中安装的某个包
npm update <包名>

//更新当前项目中安装的所有包
npm update

//更新全局安装的包
npm update <包名> -g
1
2
3
4
5
6
7
8

清除npm缓存

npm cache clean -f
1

npm run

  • package.json 文件中的scripts字段可以自定义一些命令行语句
  • 可以通过npm run 来运行这些命令行语句
{
    "script":{
    	"dev":"node ./dev.js",
        "build":"node ./app.js"
    }
}

// 运行
npm run dev
npm run build
1
2
3
4
5
6
7
8
9
10

启动模块

npm start
1

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
1
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]
1
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'
1
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
1

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包
1
2
3

安装我们发布的包

npm install 自己的包名,即可安装成功
1

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
1
  • 持久使用
npm config set registry https://registry.npm.taobao.org
1
  • 配置后可通过下面方式来验证是否成功
npm config get registry
1

找到taobao关键字即可

  • 通过 cnpm 使用
npm install -g cnpm --registry=https://registry.npm.taobao.org
1

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

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

修改配置文件

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}
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
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
# 将原有默认用户和密码清除掉,添加我们生成的用户名和密码
1
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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

使用

nrm ls # 查看我们当前使用的是否使我们的私有镜像源
# 下载即可(注意配置的权限,如果配置了权限,可能需要先登录才可以下载)
npm install 我们的包名
1
2
3

TIP

如果我们下载的包名在我们的私有服务器上面没有找到,会自动代理到我们 verdaccio 配置文件中配置的代理proxy服务器去下载对应的包 也就是说:我们私有包,发布到自己的仓库,下载使用就行,不是私有的包,完全不会出现影响

nrm

nrm 是一个 npm 源管理器,允许你快速地在 npm 源间切换

安装

sudo npm install -g nrm
1

使用

# 列出可用的源
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 别名
1
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 安装
1

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' }
1
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);  
}; 
1
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();
};
1
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;
};
1
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();
}
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

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;
1
2
3

VERSION

EJS版本

// 源码实现
// 最终导出的版本是在package.json中定义的version字段
var _VERSION_STRING = require('../package.json').version字段;
exports.VERSION = _VERSION_STRING;
1
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 = {};
  }
};
1
2
3
4
5
6
7
8
9
10
11
12

fileLoader

  • 自定义文件加载程序

  • 用于模板预处理或限制对文件系统的特定部分的访问

var fs = require('fs');
exports.fileLoader = fs.readFileSync
1
2

localsName

  • 包含本地变量的对象的名称
  • 如果不是“undefined”,则该变量由 Options.localsName 覆盖
var _DEFAULT_LOCALS_NAME = 'locals';
exports.localsName = _DEFAULT_LOCALS_NAME;
1
2

promiseImpl

封装Promise

exports.promiseImpl = (new Function('return this;'))().Promise;
1

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;
};
1
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;

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
56
57
58
59
60
61
62
63

clearCache

清空模板编译函数

// 清空模板编译函数
exports.clearCache = function () {
  // 清除缓存
  exports.cache.reset();
};
1
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;  
};
1
2
3
4
5
6
7
8
9
10
11
12

####__express

通过模板路径和模板数据获取编译结果,并执行回调

// 和renderFile相关
// 通过模板路径和模板数据获取编译结果,并执行回调
exports.__express = exports.renderFile;
1
2
3

include

可以一段ejs模板作为公共模板(需要提供 'filename' 参数)

// 参数一:模板文件路径
// 参数二:需要传递的参数
<%- include('user/show', {user: user}); %>
1
2
3

标签

标签:<%

'脚本' 标签,用于流程控制,无输出

// 声明变量
<% title="EJS Template engine" %>

<ul>
    // 普通流程语句
    <% for(var i in users){ %>
    	// 这里是不输出东西的,因为就是一个<%%>
     	<span><% name1 %></span>
 	<% } %>
</ul>
1
2
3
4
5
6
7
8
9
10

标签:<%=和<%-

<%=:输出数据到模板(输出是转义 HTML 标签)

<%-:输出非转义的数据到模板

ejs.render({name:'<h1>小峰哥ejs</h1>})

<%= name %> // 结果:<h1>小峰哥ejs</h1>
<%- name %> // 结果:小峰哥ejs:一个h1标题
1
2
3
4

标签:%>和-%>

%>:一般结束标签

-%>:删除紧随其后的换行符

标签:<%_和_%>

<%_ 删除其前面的空格符

_%> 将结束标签后面的空格符删除

标签:<%#

注释标签:不执行、不输出内容

####标签:<%%

输出字符串:<%

Express + EJS

安装

npm install ejs  # ejs 安装
npm install express # express 安装
1
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')
})

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 对象

  • 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
1
2
3
4
5
6
7
8
9
10
11
12