梦入琼楼寒有月,行过石树冻无烟

📖 earlier posts 📖

Vue Prop

在组建基础中,我们讲解了 Prop是作为一个DOM属性绑定,而不是属性绑定,如果有不了解或不知道的读者可以阅读下前面几章的内容最后来阅读本章。

大小写

通常情况下,HTML中的属性名称是不存在大小写敏感的,所以浏览器大多都会将大写字母解释为小写字母,这也就意味着驼峰命名的方式需要使用其段横线分割的命名方式。

类型

通常的情况下,我们的Props类型都会为字符串类型,对此,Vue为我们提供了一个多种类型的选择方式,如:

ID DA
String 字符串
Number 数字
Boolean 布尔
Array 数组
Object 对象
Function 功能
Promise 承诺

对此,我们的 code 可以将之前单纯的定义属性之后,在指定其类型,如:

1
props: ['title', 'age']

改为:

1
2
3
props: {
title: String,
age: Number,

如:

1
2
3
4
5
6
7
8
9
10
11
12
<script>
Vue.component("props", {
props: {
title: String,
age: Number,
},
template: '<h1>{{title}}</h1>'
})
new Vue (
{el: '#props'}
)
</script>

传递静态和动态

当一切执行完成之后你会发现,即使我使用的是 age,但vue并没有起到什么实质性的效果,甚至还可以输入Hello,world字符串类型的数据。在这个时候,由于我们传递给 prop中是静态 Prop即:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<div id="props">
<props age="10"></props>
</div>
<script>
Vue.component("props", {
props: {
title: String,
age: Number,
},
template: '<h1>{{age}}</h1>'
})
new Vue (
{el: '#props'}
)
</script>

如果想传递一个动态 Prop,需要使用v-bind:即v-bind的缩写来让其成为一个动态的 prop

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<div id="props">
<props :age="10"></props>
</div>
<script>
Vue.component("props", {
props: {
title: String,
age: Number,
text: [String, Number]
},
template: '<h1>{{age}}</h1>'
})
new Vue (
{el: '#props'}
)
</script>

当使用过v-bind来传递 Prop的时候,那么所有的数据都将是动态的 prop,所以 vue 也可以根据所定义的属性来进行判断 Prop数据是否符合所定义的Prop 类型

验证


除了指定 Prop 的类型之外,我们也可以使用 vue 为我们所提供的 Prop 验证,(虽然这一点有点像是 Laravel 所提供的表单验证,但在Vue中也被发挥的琳琳尽至)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<div id="app">
<props-component :title-data="titleData"></props-component>
</div>
<script>
var propsComponent = {
props: {
titleData: {
type: String,
required: true
}
},
template: '<h1>{{titleData}}'
};

var vm = new Vue({
components: {
'props-component':propsComponent
},
el: '#app',
data: {
}
})
</script>

在上图之中,主要来验证titleData属性是否存在数据,如果存在数据则进行显示,但如果没有数据则会在控制台中输出错误。

默认值


在通常的情况下,如果你不指定相关数据,那么将会被输出为null即空,且不可见,对于这种情况,我们可以使用 vue 所提供的default方法来设置一个组件的默认值,而默认值的数据则是其属性内的信息,如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<div id="props">
<props :age="101"></props>
<props></props>
</div>
<script>
Vue.component("props", {
props: {
age: {
type: Number,
default: 1
}
},
template: '<h1>{{age}}</h1>'
})
new Vue (
{el: '#props'}
)
</script>

Vue 组建注册

组建名

关于 Vue 的组建名特别是全局组件的注册格式:

1
2
3
Vue.component('components-name', {
// ……
})

其中在全局组件注册中第一个参数是全局组件的名称,而第二参数则为组件中的code

组件名的命名方式

组建名的定义主要命名方式为 kebab-case即“短横线分割命名方法”还有驼峰式命名,他们的命名方式主要体现在:

1
2
3
Vue.component("component-name", {
// ……
})

以及驼峰式命名方法:

1
2
Vue.component("componentName" , {
})

短横线命名以及驼峰式命名主要区分在component-namecomponentName,通常使用短横线明明更加的明显但在一些语言中并不支持,所以驼峰式命名方法受众更多。

在官方文档中,虽然短横线分割以及驼峰式命名方式都会被接受,但是直接在DOM中使用时只支持以短横线分割Kebab-case是有效的

全局注册与局部注册

全局注册

通过全局注册通常会使用Vue.component来创建一个全局组件即:

1
2
3
Vue.component('component-name', {
// ……
})

在他们注册之后可以用在任何新的Vue实例中,就比如:

1
2
3
4
5
6
7
8
9
10
Vue.component('componente-one', {
// ……
})
Vue.component('componente-two', {
// …… code
})

new Vue ({
el: '#app'
})

之后可以可以用过使用下放实例直接使用刚刚所注册的全局组件:

1
2
3
4
<div id="id">
<componente-one></componente-one>
<componente-two></componente-two>
</div>

局部注册


全局注册通常往往都不会是太于理想的,如果你有很多的组件,但是你用到的只有那一两个,则会造成很多的代码行数增加(经管全局注册的初始结构会比局部注册的少几行),通过局部注册,即有多少,注册多少,但全局注册则是尽管来,趁热,来者不惧的的方式进行组件的注册。通过上述的对比我们可以更快的理解全局注册与局部注册相互之间的差异以及区别,读者可根据在开发项目中时自行进行使用两种方法来构建一个项目。

Vue 基础

Vue 是由尤雨溪所开发的一种前端js框架,以vue为中心的vue 库最为出名,其中知名的有vue-data、vue-table……分别运用在数据大屏以及表格处理方面,其中vue主要用于构建用户界面的渐进式框架,核心库只支持前端显示。而渐进式框架主要是:“一开始不需要完全掌握全部功能,到后面会慢慢的提升”,就以vue-data为例,即使光通过官方文档也可以快速上手,这与vue.js的成功密切相关。

vue.js 的官方文档是写的最为详细的框架文档之一,这与其vue.js发展之出的Laravel 社区文档出现了对比,如果按照通俗的话来讲就是“教你造车但不教你开车”,这也是vue.js如此成功的关键因素之一,其二就是由于vue.js是由国人所开发的,这也增加了vue.js在国内的受欢迎程度不同类框架第一影响更好。

安装

Vue的安装非常的简单,本文主要使用vue所提供的cdn来引入项目内进行演示,vue为开发者提供了两种方式,分别为开发环境版以及生产环境版本,两者的区别就是一个含有了帮助以及命令行警告,而另一个主要优化了尺寸和处理速度,我们分别可以使用以下 CDN进行引入 :

ID DA
开发环境版 <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
生产环境版 <script src="https://cdn.jsdelivr.net/npm/vue"></script>

输出

编辑 app.message 值
Vue.js允许使用较为简洁的模板语法以声明式的将数据渲染到HTML DOM(HTML Document Object Model,文档对象模型)内,使得数据可以任意的进行编辑,以及实时预览,HTML DOM用于将元素看作对象,从而使得元素可以被进行编辑以及获取,就比如我们声明一个message对象:

1
2
3
4
5
6
7
8
9
10
11
<div id="app">
{{message}}
</div>
<script>
var app =new Vue({
el: '#app',
data: {
message: 'hello,world!'
}
})
</script>

绑定元素属性

1
2
3
4
5
6
7
8
9
10
11
<div id="app">
<p v-bind:title="message">绑定标题</p>
</div>
<script>
var app = new Vue({
el: '#app',
data: {
message: "绑定属性为toLocaleDateString " + new Date().toLocaleDateString()
}
})
</script>

此时如果将鼠标移动到<p>元素内,则会提示信息为绑定属性为 toLocaleDateString()方法,这里所用到的v-bind为vue所提供的指令:

ID DA FA
v-bind 将这个元素点的标题属性和message 实例内的消息属性 绑定在一起
toLocaleDateString 区域日期字符串
toLocaleTimeString 区域时间字符串
toLocaleString 区域字符串

条件

vue.js支持提交语法的写入,本章主要先介绍下vue的特点以及语法等基础功能的实现,在后续我们会详细介绍vue的条件以及循环指令:

1
2
3
4
5
6
7
8
9
10
11
<div id="app">
<p v-if="message">Hello,world!</p>
</div>
<script>
var app = new Vue({
el: '#app',
data: {
message: true
}
})
</script>

与上述一样,vue不仅仅可以将数据绑定到DOM文本或属性,还可以绑定DOM的结构,这意味着你在控制台中将app.message=false可以将Hello,wirkd!所隐藏。

循环


在vue内,循环可以通过使用v-for指令来实现,与上述一样,vue也会将此绑定到DOM对象内,都可进行控制台内输入app.todos.push({text : "Hello,vue"})进行修改:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<div id="app">
<ol>
<li v-for="todo in todos">
{{todo.text}}
</li>
</ol>
</div>
<script>
var app = new Vue({
el: '#app',
data: {
todos: [
{ text: 'Hello,world'},
{ text: 'Hello,china'}
]
}
})
</script>

双向绑定

1
2
3
4
5
6
7
8
9
10
11
12
<div id="app">
<p>{{message}}</p>
<input v-model="message">
</div>
<script>
var app = new Vue({
el: "#app",
data: {
message: 'Hello,world'
}
})
</script>

通过使用v-model指令,我们可以非常方便的获取表单所输入的内容来实现双向绑定。

Node.js TCP Server

TCP Server 通过 Node 中的 net 模块来实现 TCP 四层模型(应用层、传输层、网络层、链路层)

Id Name Info Protocol
1 链路层(Link layer) 负责在以太网、Wifi 底层网络发送数据包,并通过网卡、使用 MAC 地址来标记网络上的设备 GPRS、IEEE、IPoe、Localhost、TRILL、IPoAC、FDDI、NAK\NACK、SEND、frame\frame relay、ATM、Data Link Layer、PPP、STP、L2TP、ARQ、CDPD、LLDP、LCP、Link Aggregation、HDLC
2 网络层\网络互联层(Internet layer) 定义了 IP 地址的概念,通过 IP 地址取代 MAC 地址,并将局域网、广域网链接成一个虚拟网络,主要目的就是 将 IP 地址翻译成 MAC 地址就可查找设备 IPsec、IPv4\IPv5\IPv6\IPv9、 IP Address、x.25、IPX、ICMP\ICMPv6、IGMP、DDP、Mobile IP、Network Layer、PPP、RSVP、Anti-replay
3 传输层(Transport layer) 保证数据在 IP 地址标记的两点之间进行传输 TCP、UDP、TLS\SSL、DCCP、SCTP、RSVP
4 应用层(Application layer) 直接与应用程序和接口结合,并提供常见的应用服务 DHCP、DNS、FTP、gopher、HTTP\HTTP-2、IMAP4、IRC、NNTP、XMPP、POP3、SIP、SMTP、SNMP、SSH、TELNET、RPC、RTCP、RTP、RTSP、SDP、SOAP、GTP、STUN、NTP、SSDP

对于每个网络层次,Node 都提供了相应的模块,如 http、net、tls/crypto、dgram 等,其中 net、http、dgram 模块分别实现和提供 TCP、HTTP 的通信,其中 http 为应用层模块,而 net 为传输层模块。

是一个综合模块,包含了 ```http/tls/crypto``` 等,这将用于确保传输的安全性。其中 ```net``` 模块是 node 中的核心模块,严谨的来说 ```http.Server``` 继承了 ```net.Server```,除此之外 ```HTTP SERVER/CLIENT``` 都以来与 ```net.Socket```,而 net 模块主要分为:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

1. net.Server:通过其内部的 ```socket``` 来提供客户端的通信
2. net.Socket:TCP/Localhost socket 的实现,并提供了全双工的 stream 接口

> Network socket (网络套接字)是指网络中不同主机上应用程序之间的通信端点对象,一个套接字就是一个的另一端,也就是以 IP 及端口所组成的地址被称之为套接字地址(socket address)

## Create TCP Server

```js
var net = require('net')

var server = net.createServer(function (socket) {
console.log('someone sonnects')
})

server.listen(8210, function () {
console.log('Create server on http://127.0.0.1:8210')
})

创建一个 TCP 服务可以通过 net.createServer 方法进行创建,可通过 server.listen 来设置服务的监听端口以及,当然一旦运行服务时除发的还是 server 下的 listening 事件,因此也支持另一种写法:

net.Server()

1
2
3
4
5
6
7
8
9
10
11
var net = require('net')

var server = net.createServer(function (socket) {
console.log('someone sonnects')
})

server.listen(8210)

server.on("listening", function () {
console.log("Create server on http://127.0.0.1:8210/")
})

除此之外 server 还支持另外几种 TCP 事件:

Id Name Info
1 listening 调用 server.listen(),即当开始监听时候除发
2 connection 当有新创建时后除发,回调的参数是 socket 连接的对象
3 close 当关闭时触发,回调函数没有参数
4 error 当 TCP 服务发生错误时触发,回调参数为 error
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
var net = require('net')

var server = new net.Server()

// 监听新的请求时返回回调函数
server.on("connection", function (socket) {
console.log("connects someone")
})

server.listen(8210)

// 监听时触发
server.on("listening", function () {
console.log("Create server on http://127.0.0.1:8210/")
})

// 关闭触发回调函数
server.on("close", function () {
console.log("close server")
})

// 错误时返回
server.on("error", function (err) {
console.log("server error!")
})

server.address()

如果需要监听 IP 套接字,也就是套接字地址(socket address),可以通过 server.address() 方法来实现,他主要提供了三个服务器绑定的 address 以及 familyport

1
2
3
4
5
6
7
8
9
10
11
12
var net = require('net')

var server = net.createServer(function (socket) {
console.log("connects someone")
})

server.listen(8210, function () {
var address = server.address()
console.log("TCP Server 所监听的端口号:" + address.port)
console.log("TCP Server 所监听的地址:" + address.address)
console.log("TCP Server IPv4 or IPv6:" + address.family)
})

需要注意的是如果监听的端口号是 IPv6 地址,那么 address 则返回 ::

server.getConnections()

.server.getConnections 方法用于异步获取服务器上的并发连接数,回调参数分为 errcount

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
const net = require("net")

/*
Create server on http://127.0.0.1:8210
someone connects
This is client count: 1
*/
var server = net.createServer(function (socket) {
console.log('someone connects')

server.maxConnections=3
server.getConnections(function (err,count) {
console.log("This is client count: " + count)
})
})

server.listen(8210, function () {
console.log("Create server on http://127.0.0.1:8210")
})

之后我们分别通过 GET 请求 http://127.0.0.1:8210/ 并通过代理访问 uri 则有三个 IP 访问,因此分别依次循环输出 This is Client count: 1~3

Server at Client 之间的通信

Server

服务端主要用于接收客户端所发送的消息,因此只需要通过 data 事件来接收数据时进行输出,在收到数据之前首先通过 address() 来将转换为 JSON 格式并输出,并通过 socket.bytesWritten 来计算客户端所发送的字节数,最后通过 socket.bytesRead 方法来统计书数据的大小。

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 net = require('net')

/*
Create server on http://127.0.0.1/8210
The is address is {"address":"::","family":"IPv6","port":8210} 以发送
消息大小为 62
Massage from client
数据的大小为:19
*/
var server = net.createServer(function (socket) {
var addresss = server.address()
var message = "The is address is " + JSON.stringify(addresss)

socket.write(message,function () {
var writeSize = socket.bytesWritten
console.log(message + " 以发送")
console.log("消息大小为 " + writeSize)
})

// 收到数据时回调
socket.on('data', function (data) {
console.log(data.toString())
var readSize = socket.bytesRead
console.log("数据的大小为:" + readSize)
})
})

server.listen(8210,function () {
console.log("Create server on http://127.0.0.1/8210")
})

Client

在 Server 与 Client 建立链接的过程中,客户端扮演的是数据发送者的身份,而他主要的作用就是与服务端建立链接并发送数据,之后监听服务端的状态,在这期间主通过 client.connect 来链接服务,之后使用其 client.write 来写入数据并发送。

net.Socket 类

API

对于 net.Socket 相关的 API Node 提供了多种方法:

Id Name Info
1 socket.address() 返回操作系统报告并绑定 address,和套接字的 port 以及 family 等
2 socket.bytesRead() 接收的字节数
3 socket.bytesWritten() 发送的字节数
4 socket.connect(options[,sonnectListener]) 指定套接字链接
options
port 套接字应该链接到的端口(必填)
host 套接字应连接到的主机(默认为 localhost
localAddress 套接字应该链接到本地地址
localPort 套接字应链接本地端口
family IP 堆栈的版本(通常是 IPv6、IPv4或0,而 0 表示 允许 IPv4/6 地址)
hints 可选的 dns.lookup() 提示(主机名)
lookup 自定义查找函数(默认 dns.lookup()
5 socket.connect(port[,host][,connectListener]) 指定套接字链接
port 客户端应该链接到的端口
host 客户端应该链接到的主机
connectListener 方法的常用参数,将被添加为 connect 事件监听
6 socket.destroyed 半关闭套接字,服务端关闭时发送数据包给客户端,客户端关闭
7 socket.localAddress 远程客户端链接本地 IP 地址(以 IP 地址的字符串形式表示,如 0.0.0.0
8 socket.localPort(socket.remotePort) 本地端口数字表示(如 8210
9 socket.remoteAddress 远程 IP 地址的字符串形式输出(如 74.125.127.1002001:4860:a005::68
10 socket.remoteFamily 远程 IP 系列的字符串形式表示(如 IPv6IPv4
11 socket.setTimeout(socket.timeout) 用于进行设置连接(如果 timeout 为 0,则禁用空闲超时)
12 socket.setKeepAlive() 用于设置和i长连接
13 socket.destroy()\socket.destroyed 当错误发生时,用于销毁 socket,以确保这个 socket 上不会有其他 IO 操作
事件
Id Name Info
1 close 当链接断开时触发,假设因为传输错误导致断开,则参数为 err
2 connect 当建立成功套接字链接时触发
3 data 接收到数据时触发
4 drain 当写缓存空了时触发
5 end 当另一端结束时触发
6 error 当错误时触发
7 lookup 当解析完主机名后触发
8 ready 当套接字准备好使用时触发
9 timeout 提示用户 socket 超时,需要手动关闭链接
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
const net = require('net')

/*
Connnect to Server
The is address is {"address":"::","family":"IPv6","port":8210}
*/
var client = new net.Socket()

// 链接服务器,"connect" 是指当成功建立连接时触发
client.connect(8210,'127.0.0.1', function () {
console.log("Connnect to Server")

// 与 Server 建立链接
client.write("Massage from client")
})

client.on("data", function (data) {
console.log(data.toString())
})

// 当套接字另一端断开链接时出发回调,从而结束套接字可读端
client.on("end", function () {
console.log("end Server")
})

Node.js Express at ejs

express 是一个高度包容和快速包容且极简的 Node.js Web 框架,主要用于其 API 路由以及对数据的集成支持,可通过 express 模块完成各种使用程序方法和中间件,方便快捷的创建一个强大的 API。

通过 express 和模板引擎 ejs 的配合,以 ejs 十分简单以及对 express 良好的集成,可以充分的完成前后端的相关开发需求,并以此来完成一个非常规范的 node start 写法,在此之前我们需要安装 express 以及 ejs 模块

npm install ejs \ npm install express

路由用于确定应用程序特定端点的客户端请求,主要通过 express 来进行实现,而这其中 app 与 HTTP 方法相对应的 Express 对象方法来定义路由,如 app.get() 用于处理 GET 请求,而 app.post 则用于处理 POST 请求。

Express

这些路由方法都指定了回调函数(或者:“处理程序函数”),当程序接收到指定的路由(端点)的时候(也就是说 HTTP 方法请求时被调用),来调用回调函数,换句话说就是应用程序监听与指定路由和方法匹配的请求,当检测到匹配时,他会调用对应的回调函数。

比如我们要创建一个非常符合 node 风格的项目并搭配 ejs 进行开发,可以目录结构可以为:

1
2
3
4
5
6
7
8
node/
├── server.js
├── package.json
├── package-lock.json
├── route
│ └── index.js
└── views
└── index.ejs

除去 node_modules 目录

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

var indexRoute = require('./route/index')

// app.set(name, value),将 set name 分配给 value,因此他可以存储任何想要的值
app.set('views', path.join(__dirname, 'views'))

// 指定渲染引擎使用使用 ejs
app.set('view engine', 'ejs')

// 路由将匹与 path 相同的路径
app.use('/', indexRoute)

app.listen(8021)

路由参数

接下来在 route/ 目录里面添加 use 所对应的文件,并引入 express 库,并通过 express.Router 创建模块化,之后以 router.get 实例来检测端点参数的访问,之后通过 res.render 将 HTML 字符串发送到客户端:

1
2
3
4
5
6
7
8
9
10
var express = require('express')
var router = express.Router()

router.get('/:name', function (req,res) {
res.render('index', {
name: req.params.name
})
})

module.exports = router

在上述 code 中,在 router.get 监控路由端点中所使用的路由参数(:name)是一个包含映射命名路由,其作用是捕获 URL 中位置所指定的值,并填充在 req.params 对象中,你可以随意定义:

1
2
3
4
5
6
7
8
9
10
11
var express = require('express')
var router = express.Router()

router.get('/user/:username/uid/:userid', function (req,res) {
res.render('index', {
username: req.params.username,
userid: req.params.userid
})
})

module.exports = router

http://localhost:8021/user/kunlun/uid/20


当然如果不需要 ejs 进行交互也可以通过其 res.render 方法 options参数来进行实现:

1
2
3
4
5
6
7
8
9
10
var express = require('express')
var router = express.Router()

router.get('/:name', function (req,res) {
res.render('index', function (err,html) {
res.send(html)
})
})

module.exports = router

http://localhost:8021/hehe

在这其中 res.send 是一个用于发送 HTTP 响应的方法,所描述的 body 可以是 buffer、string\boolean\array 对象等。

Ejs

ejs 试图中非常简单,我们只需要掌握 ejs 是那个非常关键的几个语法就可以进行使用:

Id Name Info
1 <% code %> 用于运行 JavaScript 代码
2 <%= code %> 显示转义后的 HTML 内容
3 <%- code %> 显示原始的 HTML 内容
1
2
3
4
5
6
7
8
9
10
<!DOCTYPE html>
<html>
<head>
<title>DOCTYPE</title>
</head>
<body>
<h1>User: <%= username%></h1>
<p>Uid: <%= userid%></p>
</body>
</html>

Node.js HTTP Server

HTTP(Hyper Text Transfer Protocol)即浏览器和服务器之间的传输协议,用于浏览器和服务器之间的通信问题,从握手开始处理 TCP 链接,为实现解析 HTTP 的问题,Node 可以通过其内置的 http 模块来解决此问题。

应用程序并不可以直接和 HTTP 进行一些操作,因此可以通过其 http 模块中所提供的 require(请求) 以及 response(响应) 来将 HTTP 响应返回服务器。

因为 require 封装了 HTTP 请求,因此可以通过使用 require 对象属性方法获取所有的 HTTP 请求信息

同样的因为 response 对象封装了 HTTP 响应,也可以通过此对象方法将 HTTP 响应返回给浏览器

因此 http 模块也可以有两种方式进行使用,服务端创建 HTTP 服务器以此来监听客户端的请求并返回响应,而作为客户端,将发起一个 HTTP 请求(客户端)来获取服务端的响应。

Request at Response

Request

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
const http = require('http')

// 创建本地服务器来从其接收数据
var server = http.createServer(function (req,res) {
// 获得 HTTP 请求的 method + url
console.log(req.method + ':' + req.url)
req.on('data', function (chunk) {
body.push(chunk)
})

// 向请求发送响应头
res.writeHead(200, {'Content-Type':'text/html'});
// 完成并发送请求
res.end('<h1>Hello,world!</h1>')
})

server.listen(8081)

console.log('Server is running at http://localhost:8081/');

HTTP 请求的本质上就是 Headers 以及 body 两者组成,因此我们首先通过了 http.createServer 来创建一个服务器,然后通过 server.listen() 的方法监听端口,当客户端发出请求时,服务器就会的回调函数就会i被调用,因此输出:

1
2
3
4
5
Server is running at http://localhost:8081/
GET:/
GET:/favicon.ico
GET:/
GET:/favicon.ico

HTTP 请求在发送服务器时,是一个字节一个字节的以数据流方式发送,HTTP 模块创建的 HTTP 服务器接受到完整的请求头后会调用函数,因此除了可以使用 request 对象访问请求头数据以外,还可以将此当作一个只读数据流来访问请求数据提

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const http = require('http')

http.createServer(function (req,res) {
var body = []

console.log(req.method)
console.log(req.headers)

req.on('data', function (chunk) {
body.push(chunk)
})

req.on('end', function () {
body = Buffer.concat(body)
console.log(body.toString())
})
}).listen(8081)

因此输出的是:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
GET
{ host: 'localhost:8081',
connection: 'keep-alive',
'sec-ch-ua':
'"Google Chrome";v="89", "Chromium";v="89", ";Not A Brand";v="99"',
'cache-control': 'no-cache',
'sec-ch-ua-mobile': '?0',
'user-agent':
'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.90 Safari/537.36',
'postman-token': '9c86d3ea-3df2-9485-75e6-f8d2af8c0b03',
accept: '*/*',
'sec-fetch-site': 'none',
'sec-fetch-mode': 'cors',
'sec-fetch-dest': 'empty',
'accept-encoding': 'gzip, deflate, br',
'accept-language': 'zh-CN,zh-TW;q=0.9,zh;q=0.8,en-US;q=0.7,en;q=0.6' }

Response

客户端发送请求并接受到完整的服务端响应头时,就会调用回调函数,在回调函数中,可以通过 response 对象访问响应头外,还可以将 Response 当作一个只读数据流,其中最为主要的是 http.ServerResponse 所提供的类方法:

Id Name Info
1 response.statusCode HTTP 响应状态码
2 response.headers HTTP 响应头
3 response.statusMessage 当前消息状态
4 response.complete 是否已经接收到了完整的 HTTP 请求头信息
5 response.httpVersion 链接到服务器的HTTP 版本
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
const http = require('http')

/*
HTTP 状态码: 200
<h1>Hello,world!</h1>
请求头: true
*/
http.get('http://localhost:8081/', function (response) {
var body = []

console.log("HTTP 状态码: ",response.statusCode)

response.on('data', function (chunk) {
body.push(chunk)
})

response.on('end', function () {
body = Buffer.concat(body)
console.log(body.toString())
console.log("是否已经接收完整的 HTTP 信息:" ,response.complete)
})
})

HTTP/2


HTTP/2 是于 19999 年的 HTTP 1.1 的改进版(RFC 2616)后在 2014 年 12 月由 互联网工程任务组(IETF)内的 HTTPBIS(Hypertext Transfer Protocol Bis) 小组进行开发,并由 IETF 递交给 IESG 进行讨论,在 2015 年 5 月 正式批准(RFC 7540)

于 HTTP 1.1 相比支持了多路复用(Multiplexing)、首部压缩(Headers)、服务推送(Server Push) 等特点,有效减少了大量的开销。

服务器推送即允许服务器在客户端缓存中填充数据,通过服务器推送的机制来提前进行请求,但需要安装 http 2 模块

由于 http2 是一个内置模块,与 npm 上的 http2 无关

1
2
3
4
5
6
7
8
9
10
node/
├── package.json
├── package-lock.json
├── public
├── src
│ └── server.js
└── ssl
├── localhost-cert.pem
└── localhost-privkey.pem

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
const http2 = require('http2');
const express = require('express')
const fs = require('fs');

const server = http2.createSecureServer({
key: fs.readFileSync('./ssl/localhost-privkey.pem'),
cert: fs.readFileSync('./ssl/localhost-cert.pem')
});
server.on('error', (err) => console.error(err));

server.on('stream', (stream, headers) => {
// stream is a Duplex
stream.respond({
'content-type': 'text/html; charset=utf-8',
':status': 200
});
stream.write("<h1>Hello,world!</h1>")
stream.end();
});

server.listen(8443);

之后即可通过 https://localhost:8443/ 直接进行访问,当然由于使用了 ssl 的原因 node 关闭了 http 的访问,因此只可以通过 https 进行,如果是自己生成的证书可能会报错,点击继续访问即可。

可直接通过 openssl 生成自己的本地证书

openssl req -x509 -newkey rsa:2048 -nodes -sha256 -subj ‘/CN=localhost’
-keyout localhost-privkey.pem -out localhost-cert.pem

Node.js File System

Node.js fs(file system) 即是内置的文件系统模块,用于负责文件的到做以及读取等,可通过 console fs = require("fs") 直接进行引入,因此为了更详细的分清该模块所提供的 API,主要分为:“文件属性读写、文件内容读写、文件操作” 等三种。

fs 模块所提供的方法最大不同之处在于同时还提供了 异步、同步 两个方法,这体现在了 fs.readFile()fs.readFileSync()

options], callback)``` 异步读取文件的全部内容
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
```fs.readFileSync(path[, options])``` 用于返回 path 内容即同步


| Id | Name | Info | Type |
| --- | ------------------------------------------------------- | ----------------------------------------------------------------------------------- | ---------------------------------------------------- |
| 1 | fs.readFile(path[, options], callback) | 用于异步读取文件 | 文件内容读写 |
| 2 | fs.readFileSync(path[, options]) | 同步读取文件 | 文件内容读写 |
| 3 | fs.read(fd, buffer, offset, length, position, callback) | 在异步模式下更加合理的读取文件 | 文件内容读写 |
| | fd | 通过 ```fs.open()``` 所返回的文件描述 | |
| | buffer | 数据写入缓冲区 | |
| | offset | 缓冲区写入数据写入的偏移量 | |
| | length | 需要读取的字节数 | |
| | position | 文件读取的起始位置 | |
| | callback | 回调函数 | |
| | | err | 返回错误信息 |
| | | bytesRead | 所读取的字节数 |
| | | buffer | 缓冲区对象 |
| 4 | fs.open(path[, flags[, mode]], callback) | 异步打开文件 | 底层文件操作 |
| | path | 文件路径 | |
| | flags | 文件的打开行为 | |
| | | ```a``` | 如果文件不存在则创建文件 |
| | | ```ax``` | 在 ```a``` 的基础上,如果文件路径不存在,就失败 |
| | | ```a+``` | 如果文件不存在就创建 |
| | | ```ax+``` | 在 ```a+``` 的基础上,如果文件路径不存在,则读取失败 |
| | | ```r``` | 以读取的方式打开文件,如果文件不存在则异常 |
| | | ```r+``` | 以读写模式打开文件,如果文件不存在就抛出异常 |
| | | ```rs``` | 以同步的方式读取文件 |
| | | ```rs+``` | 以同步的方式读取和写入文件 |
| | | ```w``` | 以写入模式打开文件,文件不存在则创建 |
| | | ```wx``` | 在 ```w``` 的基础上,如果文件路径存在,文件将会写入失败 |
| | | ```w+``` | 以读写模式打开文件,如果文件不存在则创建 |
| | | ```wx+``` | 在 ```w+``` 的基础上,如果文件路径存在,则文件写入失败 |
| | mode | 打开的权限 | |
| | callback | 回调参数 | |
| 5 | fs.writeFile(file, data[, options], callback) | 通过异步的方式写入文件 | 文件内容读写 |
| | file | 文件名 | |
| | data | 需要写入的数据 | |
| | options | 一个参数对象,包含了编码模式(utf-8)以及 flags(w,写入模式打开文件,文件不存在则创建) | |
| 6 | fs.writeFileSync() | 通过同步的方式写入文件 | 文件内容读写 |
| 7 | fs.stat() | 用于读取文件属性 | 文件属性读写 |
## readFile at readFileSync 读取文件
### fs.readFile()

```js
const fs = require('fs')

fs.readFile('data.txt', 'utf-8', function (err, data) {
if (err) {
console.log(err)
} else {
// File system!
console.log(data.toString())
}
})

data.txt

File system!

与 ```fs.readFileSync()``` 的主要区别就是一个支持异步而另一个仅仅是同步方法,这主要可以根据其 ```callback()``` 就可以分辨出两者的作用与功能上的区分。
1
2
3
4
5
6
7
8
9
10
11
12

这主要涉及到 ```err``` 和 ```data``` 两个参数,后者为 ```undefined```,也就是其 ```path``` 内的数据以字符串的形式进行读取,当发生错误时将会输出 ```err``` 对象。

### fs.readFIleSync()

```js
const fs = require('fs')

var data = fs.readFileSync('data.txt', 'utf-8')

// File system!
console.log(data)

通常默认情况下单个 fs.readFIleSync() 参数是不支持输出错误的,因此我们还需要输出函数的方式输出数据,可配合 try...catch 的方式来实现错误输出:

1
2
3
4
5
6
7
8
const fs = require('fs')

try {
var data = fs.readFileSync('data.txt', 'utf-8')
console.log(data)
} catch (err) {
console.log(err)
}

fs.read(fd, buffer, offset, length, position, callback) at fs.open and fs.close

Id Name Info Type
1 fs.read(fd, buffer, offset, length, position, callback) 在异步模式下更加合理的读取文件 文件内容读写
fd 通过 fs.open() 所返回的文件描述
buffer 数据写入缓冲区
offset 缓冲区写入数据写入的偏移量
length 需要读取的字节数
position 文件读取的起始位置
callback 回调函数
err 返回错误信息
bytesRead 所读取的字节数
buffer 缓冲区对象
可用于更加合理的文件操作,这其中主要包括了 **文件打开 -> 文件写入 —> 文件读取 -> 关闭文件** 的整一套流程,让整个对文件的操作更加合理和直观
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

```js
const fs = require('fs')
const buf = new Buffer.alloc(1024)

/*
准备打开文件
文件打开成功
开始准备读取文件
文件内容为: Hello, File system
文件关闭成功
*/

console.log("准备打开文件")

fs.open('data.txt', 'r+', function (err,fd) {
if (err) {
console.log(err)
}
console.log('文件打开成功\n开始准备读取文件')
fs.read(fd, buf, 0, buf.length, 0, function (err, bytesRead) {
if (err) {
console.log(err)
}

if (bytesRead>0) {
console.log('文件内容为:',buf.slice(0,bytesRead).toString())
}

fs.close(fd, function (err) {
if (err) {
console.log(err)
}
console.log('文件关闭成功')
})
})
})

fs.open()

Id Name Info Type
1 fs.open(path[, flags[, mode]], callback) 异步打开文件 底层文件操作
path 文件路径
flags 文件的打开行为
a 如果文件不存在则创建文件
ax a 的基础上,如果文件路径不存在,就失败
a+ 如果文件不存在就创建
ax+ a+ 的基础上,如果文件路径不存在,则读取失败
r 以读取的方式打开文件,如果文件不存在则异常
r+ 以读写模式打开文件,如果文件不存在就抛出异常
rs 以同步的方式读取文件
rs+ 以同步的方式读取和写入文件
w 以写入模式打开文件,文件不存在则创建
wx w 的基础上,如果文件路径存在,文件将会写入失败
w+ 以读写模式打开文件,如果文件不存在则创建
wx+ w+ 的基础上,如果文件路径存在,则文件写入失败
1
2
3
4
5
6
7
8
9
const fs = require('fs')

// 文件打开成功
fs.open('data.txt', 'r+', function (err, fd) {
if (err) {
return console.error(err)
}
console.log("文件打开成功")
})

fs.writeFile()

Id Name Info Type
1 fs.writeFile(file, data[, options], callback) 通过异步的方式写入文件 文件内容读写
file 文件名
data 需要写入的数据
options 一个参数对象,包含了编码模式(utf-8)以及 flags(w,写入模式打开文件,文件不存在则创建)
1
2
3
4
5
6
7
8
9
// 写入成功: Hello, File system
fs.writeFile('data.txt', data, function (err) {
if (err) {
console.log(err)
} else {
var write = fs.readFileSync('data.txt', 'utf-8')
console.log('写入成功:' ,write)
}
})

fs.writeFileSync()

.fs.writeFileSync()fs.writeFile() 的同步方法,同样也可以使用 try...catch 来支持返回 err 信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
const fs = require('fs')

var data = "Hello, File system"

fs.open('data.txt','r+', function (err,fd) {
if (err) {
return console.error(err)
} else {
fs.writeFileSync('data.txt', data)
// 写入成功 Hello, File system
console.log('写入成功', data )
}
})

获取文件属性 (fs.stat())

“获取文件属性”是指文件的大小、创建时间、修改时间等信息,可以通过 fs.stat(path[, options], callback) 来进行实现,在这个方法中存在着 fs.stats 类,用于获取文件的属性,常用的为:

Id Name Info
1 stat.isFile() 是否是文件
2 stat.isDirectory() 是否是目录
3 stat.size() 文件大小(以字节为单位)
4 stat.birthtime() 创建时间
5 stat.mtime() 修改时间
6 stat.ctime() 最后一次更改时间
7 stat.atimeMS() 最后一次访问时间
8 stat.mtimeMS() 最后一次修改时间
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
const fs = require('fs')

/*
是否是文件: true
文件大小: 18
创建时间: 2021-09-01T02:23:04.637Z
修改时间: 2021-09-01T02:23:04.637Z
最后访问: 2021-09-01T02:23:04.949Z
*/
fs.stat('data.txt', function (err, stat) {
if (err) {
console.log(err)
} else {
console.log("是否是文件:", stat.isFile())
if (stat.isFile()) {
console.log('文件大小:', stat.size)
console.log('创建时间:', stat.birthtime)
console.log('修改时间:', stat.mtime) // stat.ctime
console.log('最后一次访问时间:', stat.atime)
}
}
})

Node.js Buffer

Buffer

缓冲区是内存区域,和直接与内存交互的编程语言 C、C++、Go 等相比,JavaScript 对这个操作很微乎其微,因此为了实现与内存区域打交道,Buffer 由此而生。

Buffer class

Buffer 类主要的作用就是一个专门用于存放二进制数据的缓存区,是 Node 专门定义的一个类,用于弥补 JavaScript 自由的字符串类型无法满足大量二进制数据处理。

Buffer 是 Node 的一个核心库,因此为 Node 带来了另一种原始的数据存储方法(二进制),让 Node 处理 二进制数据。

所以 Buffer 库主要的用处就是需要 Node 处理 I/O 的时候,就可以通过 Buffer 库进行一个处理,当然也可以进行字符编码或数据类型的转换。

缓冲区可以通过使用 Buffer.[form(),alloc(),allocUnsafe()] 方法进行构建,这也涉及到了 Buffer 方法:

Id Name Info Type
1 buffer.alloc 可以创建任意大小的缓存区 type
2 buffer.from(arrayBuffer[, byteOffset[, length]) 能够通过字符串、缓冲区、数组等方式创建缓存区 type
byteOffset 需要暴露的地一个索引值
length 暴露的字节数
0x1 buffer.byteLength() 查询字节长度 method
0x2 buffer.compare(target[,target1,target..]) 比较两个缓冲区(buffer)对象是否相等,并返回 -1、0、1 method
0 与 target 相等返回 0
1 如果 target 的 size 不同以及 fill 相等,则返回 1
-1 如果 target 的 fill 不相等,则返回 -1
0x3 buffer.concat 将一个或多个缓存区对象合并为一个对象,合并后可以获取新的对象长度 method
0x4 buffer.entries() 用于返回缓冲区的 [index,byte] method
0x5 buffer.fill() 用于缓冲区填充 method
0x6 buffer.includes(values) 用于判断 values 是否在缓冲区(buffer)中 method
0x7 buffer.isEncoding 判断 Buffer 目前所支持的编码名称 method
0x8 buf.slice(start[,end]) 用于将 buffe 内的 fill 进行切片 method
0x9 buf.toJSON() 将数据输出为 JSON 格式 method
0x10 buf.write(string[,offset,length,encoding]) 将 string 写入 buffer 中 method

buffer.alloc

1
2
3
4
5
6
7
8
9
10
const buf = Buffer.alloc(10, 1)

// <Buffer 01 01 01 01 01 01 01 01 01 01>
console.log(buf)

// 返回字节的长度
var buflen = Buffer.byteLength(buf)

// 10
console.log(buflen)

创建一个大小为 10 的缓存区,且用 1 进行填充,并通过 Buffer.byteLength 来查询字节的长度。

length 的区别是 byteLength 不会考虑用于将字符串转换为字节编码

buffer.fill(arrayBuffer[, byteOffset[, length])

1
2
3
4
5
6
7
8
9
10
11
12
13
// 通过 Unit8Array 创建一个数组
const arrA = Uint8Array.from([0x61, 0x62, 0x63, 0x64])

const arrB = new Uint8Array(arrA.buffer, 0,3)

const buf = Buffer.from(arrB.buffer)

// abcd
console.log(buf.toString())

// or
const buff = Buffer.from([0x61, 0x62, 0x63, 0x64])
console.log(buff.toString())
buf.toJSON()
1
2
3
4
5
6
7
8
9
10
11
12
13
// 通过 Unit8Array 创建一个数组
const arrA = Uint8Array.from([0x61, 0x62, 0x63, 0x64])

const arrB = new Uint8Array(arrA.buffer, 0,3)

const buf = Buffer.from(arrB.buffer)

// { type: 'Buffer', data: [ 97, 98, 99, 100 ] }
console.log(buf.toJSON())

// or
const buff = Buffer.from([0x61, 0x62, 0x63, 0x64])
console.log(buff.toJSON())

buffer.compare(target[,target1,target…])

Id Name Info
0x2 buffer.compare(target[,target1,target..]) 比较两个缓冲区(buffer)对象是否相等,并返回 -1、0、1
0 与 target 相等返回 0
1 如果 target 的 size 不同以及 fill 相等,则返回 1
-1 如果 target 的 fill 不相等,则返回 -1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
const buf1 = Buffer.alloc(10,1)
const buf2 = Buffer.alloc(10,1)

// 0
var com = Buffer.compare(buf1,buf2)
console.log(com)

const buf3 = Buffer.alloc(10)
const buf4 = Buffer.alloc(10,1)

// -1
var com1 = Buffer.compare(buf3,buf4)
console.log(com1)

const buf5 = Buffer.alloc(10,1)
const buf6 = Buffer.alloc(10,1)

// 1
var com2 = Buffer.compare(buf5,buf6)
console.log(com2)

buffer.concat()

1
2
3
4
5
6
7
8
9
10
const buf = Buffer.alloc(3)
const buf1 = Buffer.alloc(1)
const buf2 = Buffer.alloc(1)

var arr = [buf, buf1, buf2]

var con = Buffer.concat(arr)

// 5 <Buffer 00 00 00 00 00>
console.log(Buffer.byteLength(con), con)

buffer.entries()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
const buf = Buffer.from('hello,world!')

/*
[ 0, 104 ]
[ 1, 101 ]
[ 2, 108 ]
[ 3, 108 ]
[ 4, 111 ]
[ 5, 44 ]
[ 6, 119 ]
[ 7, 111 ]
[ 8, 114 ]
[ 9, 108 ]
[ 10, 100 ]
[ 11, 33 ]
*/
for (var point of buf.entries()) {
console.log(point)
}

// 104
console.log(buf[0])
与直接通过 ```buf[0]``` 这种逐一输出更加的方便和快捷。
1
2
3
4
5
6
7
8
9
10
11
12
13


#### buffer.fill

```js
const buf = Buffer.alloc(5).fill('hello')

// hello
console.log(buf.toString())

// or
const buf2 = Buffer.alloc(5,'hello')
console.log(buf2.toString())

buffer.includes(values)

1
2
3
4
5

const buf = Buffer.from('This is Buffer')

// true false
console.log(buf.includes('is'), buf.includes('as'))

buf.slice(start[,end])

1
2
3
4
5
const buf = Buffer.from('abcd')
const sli = buf.slice(0, 2)

// ab
console.log(sli.toString())

buf.write(string[,offset,length,encoding])

1
2
3
4
const buf = Buffer.alloc(6);
const len = buf.write('buffer',0,6, 'utf8')

console.log(`byte: ${buf} length: ${len}`)

字符编码转换

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const buffer = Buffer.from('hello,world!', 'ascii')

// hello,world!
console.log(buffer.toString('ascii'))

/**
* 104 101 108 108
* h -> 104
* e -> 101
* l -> 108
* l -> 108
*/
console.log(buffer[0],buffer[1],buffer[2],buffer[3])

缓冲区是一个字节数据,因此可以像数组一样进行访问,除此它还支持数种编码的转换:

Id Name Info Type
1 uft8 (utf-8) 多字节的 Unicode 字符,许多网页和玩文档格式使用 UFT-8 也是 默认的字符编码(当 buffer 解码过程中如果 UTF-8 不是有效字符,将会以 unicode 替换字符 � 用于表示错误) 字符串
2 utf16le(utf-16le) 多字节编码的 Unicode 字符,与 utf8 不同的是字符串中每个字符都将使用 2个或4个 字节进行编码 字符串
3 latin1 (Latin-1) 即 ISO-8859-1 ,因此此编码仅支持 U+0000~U+00FF 的 Unicode 字符,每个字符都将使用单字节进行编码(一种一字节的字符串方式) 字符串
4 base64 一种基于 64 个可打印字符来表示二进制数据的方法,可打印的字母 a-z\A-Z 以及数字 0-9,此外还包含空白字符(空格、制表符或换行符)会被 buffer 忽略 二进制
5 base64url 一个对 base64 标准的修改,其目的是能将编码结果用于文件名或 URL 地址,当字符串通过 buffer 时,base64url 也将接受常规的 base64 编码转换。 二进制
6 hex(16 进制) 将每个字节转换为十六进制字符 二进制
7 ascii 仅用于 7 位数的 ASCII 数据,当通过 buffer 时,将会等效于 Lantin-1 旧字符
8 binary (Lation-1) Lation-1 的别名,通过此编码列出的所有编码都会在字符串和二进制之间转换 旧字符
9 ucs2(utf-16le) uft-16le 的别名,泛指 UTF-16 的一种变体,此编码不支持大于 U+FFFF 的字符 旧字符

可以通过使用 buffer.isEncoding 方法来进行判断 Buffer 是否支持该编码类型。

Node.js Model 引入流程

Node model 可以相互进行调用,Node.js 为此提供了一个简单的模块系统,这可以内置的也可以是通过 npm 进行安装的,因此 Node.js 提供了 exportsrequire 两个对象,其中 exports 用于模块公开的接口,而 require 则用于获取模块的 exports 对象:

1
2
3
4
5
6
7
8
9
10
function Hello() {
var name;
this.setName = function (thyName) {
name = thyName;
};
this.sayHello = function () {
console.log('Hey,' + name);
}
}
module.exports = Hello;

通过 exports 对象将 Hello 对象本身,因此在通过 require 对象进行引入的时候,就会输出 Hello 函数。

1
2
3
4
var Hello = require('./modelName')
hello = new Hello()
hello.setName('This is vievs file!')
hello.sayHello()

在 Node.js 模块中,模块主要分为 核心模块,这是 Node.js 所提供的模块,而另一个则是用户所编写的模块,被称之为 文件模块。这之间的区别就是核心模块部分在 Node 编译的过程中,同时编译了二进制的执行文件,所以在 Node 进程启动时,核心模块就会被加载到内存中,因此和因模块在引入的时候,文件定位以及编译执行就会被省略掉。

但用户所编写的文件模块则需要通过 查找路径,文件定位,编译执行 这三个流程,并在运行时进行动态加载,因此比较慢。所以和核心模块比起来,速度是自然赶不上人家内置的。

require 文件查找策略

文件缓存区

对于 require 的文件缓存区主要的作用就是会优先从文件模块中加载已经存在的模块这也就说明了 Node 对引入过的模块都会进行缓存,以减少第二次使用时的加载时间。

但与浏览器缓存静态脚本文件不同的是,Node.js 所缓存的是编译之后的对象,无论是核心模块还是文件模块,对于二次加载都一律采用缓存优先的方式进行。

不同之处在于核心模块的检查优于文件模块的缓存检查

原生模块加载

原生模块的加载仅次于检查文件缓存区是否存在,因此通过 require 解析完文件名之后,检查该模块是否在原生模块中。

http 模块,虽然会在项目目录下存在 http/http.js/http.node/http.json 文件,但 http 模块不会从这些文件中加载,而是从原生模块中进行加载。

因此原生模块内同样有也有一个缓存区,如缓存区没有被加载过,就会调用原生模块的加载方式进行加载和执行。

文件加载

当文件模块在缓存中并不存在是,而且还是原生模块的时候,require 方法所传入的参数就会被 Node.js 解析,并从文件系统中加载到实际的文件。对于没一个被加载的文件模块,创建这个模块对象的时候就会生成一个 paths 属性,也就是 /node_modules,因此我们可以直接通过 console.log(module.paths) 来根据当前文件的路径计算得到 module path

1
2
3
4
5
6
7
[ '/home/kunlun/Development/Web/Demo/node/node_modules',
'/home/kunlun/Development/Web/Demo/node_modules',
'/home/kunlun/Development/Web/node_modules',
'/home/kunlun/Development/node_modules',
'/home/kunlun/node_modules',
'/home/node_modules',
'/node_modules' ]

从当前文件目录内开始查找 node_modules 目录,然后依次进入查找父目录下的 node_modules,以此循环迭代到根目录下的 node_modules 目录。

如果 ```require`` 指向的是绝对路径的文件,查找时就直接作为当前的路径进行使用,如果不是则进入查找的流程。以 module_path 中取出地一个目录作为查找的基准:

  1. 直接尝试从当前目录进行查找,如果存在则结束流程,反而继续进行查找,并进入下一阶段
  2. 尝试添加扩展名后缀进行查找,如果存在就结束查找,否则进入下一个阶段
  3. 尝试将 require 的参数作为一个包进行查找,读取项目目录下的 package.json 文件
  4. 尝试添加扩展名或查找文件,如果存在就结束流程,否则进入下一个阶段
  5. 如果继续失败,则取出 module path 中下一个目录作为查找的基准,循环 1~5 步骤
  6. 如果仍然失败,则一直循环 1~6 步骤,知道 module path 中最后一个值
  7. 如果持续失败,则抛出异常。

Node.js at ECMAScript

首先我们需要知道 ECMAScript 是一种由 Ecma 国际(Ecma International)根据其标准定义的脚本语言规范,主要在 JavaScript 上运用广泛,而 ECMAScript 是在 2015 年 6 月所正式发布,这使得 JavaScript 可以用来编写复杂的大型应用程序,成为企业级开发语言。

ECMAScript 前身为欧洲计算机制造商协会(European Computer Manufacturers Association,ECMA),是一个国际性会员制度的信息和电信标准组织。

因此遵循 ES6 中的 JS 语法也是 Node.js 基础之一,因为 Node.js 本质上还是 JavaScript 所以通过了解并学习 ES6 中的方法可以快速掌握 JS。

数组

Id Name Info
1 array.push() 将一个或多个元素添加到数组的末尾,并返回数组的长度
2 array.pop() 删除最后一个访问最后一个数组
3 array.shift() 删除第一个数组,并返回删除后的第一个数组
4 arry.shift() 从数组起始位置添加元素,并返回添加后的长度
5 array.splice(start,deleteCount) 根据数组的索引删除数组,并返回删除的元素
6 array.concat() 将数组合并
7 array.split()) 用于将字符串形式转为数组
8 array.sort() 通过原地算法对数组进行排序,返回数组(根据 UTF-16 代码值进行排序)
9 array.reverse() 将方法数组中的元素颠倒,并返回数组,之后原本第一个数组将会变成最后一个
10 array.slice(start, end) 删除索引值 start ~ end 的数组(从 0 开始)
11 array.forEach(callback(currentValue [, index [, array]]) 用于遍历数组,并未每个元素执行一次函数( array.map 的区别就是不返回数组 ),该函数主要分为三个 :
index 索引号
value 元素
array 数组
12 array.map(callback(currentValue [, index [, array]]) 用于遍历数组,并未每个元素执行一次函数,并返回新的数组,该函数主要分为三个 :
index 索引号
value 元素
array 数组
13 array.filter() 返回满足需要的新数组
14 array.every() 判断数组元素是否满足,并返回布尔值
15 array.some() 一句条件判断数组中元素是否满足,并返回布尔值(数组内满足一向即可)
16 arr.reduce(callback, currentValue[, index[, array]])[, initialValue]) 通过定义的 reducer 函数来返回一个最终值
initialValue 执行数组中的每个值,如果提供了,则 previousValue(callback) 成为第一个值,等于 initialValue
currentValue 等于数组中的第一个值
17 Array.from() 将一个类似的数组转换为数组
18 Array.of() 将一组值转换为数组
19 array.copyWithin(target, start, end) 用于将当前位置的数组元素复制到目标位置并覆盖原来的元素
target 为目标位置,从 0 开始
start 是当前元素最初位置
end 当前元素结束的位置
20 array.find(index, value, array) 用于遍历数组,并根据表达式找出符合条件的数组成员,该函数主要分为三个 :
index 索引号
value 元素
array 数组
21 array.findIndex(index, value, array) 用于遍历数组,并根据表达式找出符合条件的数组成员( array.find() 区别在于如果未找到符合要求的则返回 “-1” ) ,该函数主要分为三个:
index 索引号
value 元素
array 数组
22 array.fill(value, start, end) 通过索引来添加元素 ,该函数主要分为三个:
value 待添加元素
start 开始位置索引
end 最终位置索引
23 array.includes(Value) 判断数组中是否包含 Value,并返回布尔值
24 array.keys() 遍历数组键名
25 array.values() 遍历数组键值
26 array.entries() 遍历数组的键名和键值

array.push

1
2
3
4
5
6
7
8
9
10
const arr = ['One','Two','Three']

// 3
console.log(arr.push())

// To arr add Four
arr.push('Four')

// [ 'One', 'Two', 'Three', 'Four' ]
console.log(arr)

array.pop

1
2
3
4
5
6
7
8
9
10
const arr = ['One','Two','Three']

// [ 'One', 'Two', 'Three' ]
console.log(arr)

// 删除最后一个元素
arr.pop()

// 访问最后一个元素:Two
console.log(arr + ' 当前数组最后一个元素是:' + arr.pop())

array.shift

1
2
3
4
5
6
7
8
9
10
const arr = ['One','Two','Three']

// [ 'One', 'Two', 'Three' ]
console.log(arr)

// 删除 One
arr.shift()

// 第一个元素为 Two
console.log(arr + ' 当前数组第一个元素是:' + arr.shift())

array.unshift

1
2
3
4
5
6
7
const arr = ['One','Two','Three']

// 4
console.log(arr.unshift('Start'))

// [ 'Start', 'One', 'Two', 'Three' ]
console.log(arr)

array.splice(start,deleteCount)

1
2
3
4
5
6
7
const arr = ['One','Two','Three']

// [ 'Two', 'Three' ]
console.log(arr.splice('1','3'))

// [ 'One' ]
console.log(arr)

array.concat

1
2
3
4
5
6
7
8
9
10
const arr = ['One','Two','Three']

// 新增 Four,Five
const arr__ = ['Four','Five']

// 合并两个数组
const __arr = arr.concat(arr__)

// [ 'One', 'Two', 'Three', 'Four', 'Five' ]
console.log(__arr)

array.split

1
2
3
4
const arr = 'Hello,world!'

// [ 'Hello,world!' ]
console.log(arr.split())

array.sort()

采用了原地算法(in-place algorithm),即不需要借助额外的数据结构就可以对输入的数据进行变幻,而 array.sort() 是根据 UTF-16 代码单元值序列进行构建的,因此还是取决与代码值的顺序,无法保证排序时间和空间的复杂性。

1
2
3
4
5
6
7
8
9
10
const arr = ['1','3','2','4','5','6']

// 默认情况下是从小到大进行排序
console.log(arr.sort())

// 将排序顺序改为从大到小
const arr__ = arr.sort((a,b) => b-a);

// [ '6', '5', '4', '3', '2', '1' ]
console.log(arr)

array.reverse

1
2
3
4
5
6
7
8
9
10
const arr = ['One','Two','Three','Fire','Five']

// Array: [ 'One', 'Two', 'Three', 'Fire', 'Five' ]
console.log('Array: ', arr)

// 将数组反转
const reversed = arr.reverse()

// Reverse: [ 'Five', 'Fire', 'Three', 'Two', 'One' ]
console.log('Reverse: ', reversed)

array.forEach

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
const arr = ['One','Two','Three','Fire','Five']
const arr_ = ['One','Two','Three','Fire','Five']

/*
index:0 value:One array: [ 'One', 'Two', 'Three', 'Fire', 'Five' ]
index:1 value:Two array: [ 'One', 'Two', 'Three', 'Fire', 'Five' ]
index:2 value:Three array: [ 'One', 'Two', 'Three', 'Fire', 'Five' ]
index:3 value:Fire array: [ 'One', 'Two', 'Three', 'Fire', 'Five' ]
index:4 value:Five array: [ 'One', 'Two', 'Three', 'Fire', 'Five' ]
*/
arr.forEach((value, index, array) => {
console.log(`index:${index} value:${value} array:`, array)
})

/*
index:0 value:One array: [ 'One', 'Two', 'Three', 'Fire', 'Five' ]
index:2 value:Two array: [ 'One', 'Two', 'Three', 'Fire', 'Five' ]
index:4 value:Three array: [ 'One', 'Two', 'Three', 'Fire', 'Five' ]
index:6 value:Fire array: [ 'One', 'Two', 'Three', 'Fire', 'Five' ]
index:8 value:Five array: [ 'One', 'Two', 'Three', 'Fire', 'Five' ]
*/
arr_.forEach((value, index, array) => {
index = index * 2
console.log(`index:${index} value:${value} array:`, array)
})

array.map

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
const arr = ['One','Two','Three','Fire','Five']
const arr_ = [1,2,3,4,5,6]

/*
index:0 value:1 array: NaN
index:1 value:2 array: NaN
index:2 value:3 array: NaN
index:3 value:4 array: NaN
index:4 value:5 array: NaN
index:5 value:6 array: NaN
undefined
*/
const foreach = arr_.forEach((value, index, array) => {
array = array * 2
console.log(`index:${index} value:${value} array:`, array)
})

console.log(foreach)

/*
index:0 value:1 array: NaN
index:1 value:2 array: NaN
index:2 value:3 array: NaN
index:3 value:4 array: NaN
index:4 value:5 array: NaN
index:5 value:6 array: NaN
[ undefined, undefined, undefined, undefined, undefined, undefined ]
*/
const map = arr_.map((value, index, array) => {
array = array * 2
console.log(`index:${index} value:${value} array:`, array)
})

console.log(map)

array.filter

1
2
3
4
5
6
7
8
9
10
11
12
const arr = ['One','Two','Three','Fire','Five']
const arr_ = [1,2,3,4,5,6]

// [ 1, 2, 3 ]
let starr = arr_.filter((i,v) => i <= 3)

console.log(starr)

// [ 'One' ]
let starr_ = arr.filter((i,v) => v <= 0)

console.log(starr_)

array.slice(start,end)

1
2
3
4
5
6
7
const arr = ['One','Two','Three','Fire','Five']

// [ 'Three', 'Fire', 'Five' ]
console.log(arr.slice(2))

// [ 'One', 'Two', 'Three' ]
console.log(arr.slice(0,-2))

array.every(i,v)

1
2
3
4
5
6
const arr = ['One','Two','Three','Fire','Five']

// true
let ifarr = arr.every((i,v) => v < 10)

console.log(ifarr)

array.some(i,v)

1
2
3
4
5
6
7
8
9
10
11
const arr = ['One','Two','Three','Fire','Five']

// false
let ifarr = arr.some((i,v) => i < 30)

console.log(ifarr)

const arr_ = [1,2,3,4,5,6,7,8,9,10]

// true
console.log(arr_.some(arr_ => arr_ >= 10 ))

array.reduce(previousValue, currentValue)

1
2
3
4
5
6
7
8
const arr = [1,2,3,4,5]

// 20
let arr_ = arr.reduce((previousValue, currentValue) =>
previousValue + currentValue,5
)

console.log(arr_)

Array

Array.from()

1
2
3
4
const arr = 'hey'

// [ 'h', 'e', 'y' ]
console.log(Array.from(arr))

Array.of()

1
2
3
4
const arr = 'hey'

// [ 'hey' ]
console.log(Array.of(arr))

array.copyWithin(target, start, end)

1
2
3
4
5
6
7
8
9
const arr = ['One','Two','Three','Fire','Five']

/** 用于将当前位置的元素复制到目标位置,并覆盖原来的元素
* target 为目标位置(0为开始) ·「1」
* start 是最初的位置 ·「0」
* end 是结束的位置 ·「1」
* [ 'One', 'One', 'Three', 'Fire', 'Five' ]
*/
console.log(arr.copyWithin(1,0,1))

array.find(index, value, array)

1
2
3
4
5
6
7
8
9
10
11
const arr = ['One','Two','Three','Fire','Five']

/** 根据表达式找出符合条件的数组成员
* @type {string}
* @private index 索引
* @private value 元素
* @private array 数组
* @return One
*/
let arr_ = arr.find((index, value, array) => index => 1)
console.log(arr_)

array.findIndex(index, value, array)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
const arr = ['One','Two','Three','Fire','Five']

/** 根据表达式找出符合条件的数组成员,如果未找符合要求的则返回 "-1"
* @type {number}
* @private index 索引
* @private value 元素
* @private array 数组
* @return -1
*/
let arr_ = arr.findIndex((index, value, array) => value > 199)
console.log(arr_)

/** 根据表达式找出符合条件的数组成员
* @type {string}
* @private index 索引
* @private value 元素
* @private array 数组
* @return undefined
*/
let arr_find = arr.find((index, value, array) => value > 199)
console.log(arr_find)

array.fill(value, start, end)

1
2
3
4
const arr = ['One','Two','Three','Fire','Five']

// [ 'Start', 'Two', 'Three', 'Fire', 'End' ] [ 'Start', 'Two', 'Three', 'Fire', 'End' ]
console.log(arr.fill('Start',0,1),arr.fill('End',4,5))

array.includes(Value)

1
2
3
4
5
6
7
const arr = ['One','Two','Three','Fire','Five']

/** 判断数组中是否包含 Value
* One: true
* Start: false
*/
console.log('One:',arr.includes('One'),'\nStart:', arr.includes('Start'))

for……in

array.keys()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const arr = ['One','Two','Three','Fire','Five']

let toarr = arr.keys()

/** 遍历数组键名
0
1
2
3
4
*/
for (let keys of toarr) {
console.log(keys)
}

array.values()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const arr = ['One','Two','Three','Fire','Five']

let toarr = arr.values()

/** 遍历数组键值
One
Two
Three
Fire
Five
*/
for (let values of toarr) {
console.log(values)
}

array.entries()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const arr = ['One','Two','Three','Fire','Five']

let toarr = arr.entries()

/** 遍历数组键名和键值
[ 0, 'One' ]
[ 1, 'Two' ]
[ 2, 'Three' ]
[ 3, 'Fire' ]
[ 4, 'Five' ]
*/
for (let keys of toarr) {
console.log(keys)
}

函数

通过 ECMAScript6 函数带来了很多扩展以及新的特性,这其中包括了新扩展的 REST 参数、解构参数、扩展运算符、箭头函数等,以及 NAME 属性。

参数默认值

ES6 允许作为函数的参数设置默认值,直接写才参数后面即可,而在此之前则需要加上参数的判断以及空字符的问题。

1
2
3
4
5
6
7
8
9
10
11
12
function log(x,y = 'world!') {
console.log(x,y)
}

/*
Hello world!
Hello ,world!
Hello
*/
log('Hello')
log('Hello',',world!')
log('Hello','');

除了简介的写法,我们还要注意的是,函数中生命的参数变量,都是 默认声明 的,因此通过 let/const 再次声明就会出现报错的问题:

1
2
3
4
function err (x = 1,y = 1) {
let x = 1; // error
let y = 2; // error
}

还有一个问题就是,只有在传递参数为 undefined 是,才会使用默认参数,而 null 、和空字符将会被认为有值传递

1
2
3
4
5
6
7
8
9
10
function na(x,y = 18) {
console.log(x+',',y)
}

/*
User, null
User,
*/
na('User',null)
na('User','')

解构赋值

解构赋值可以与参数默认值结合起来使用,通过解构赋值可以将值从对象中取出,赋值给其他的变量。

1
2
3
4
5
6
7
8
9
10
11
12
function na({x,y = 18}) {
console.log(x,y)
}

/*
undefined 18
1 18
1 1
*/
na({})
na({x:1})
na({x:1, y:1})

上述的 code 使用了对象的解构负值的默认值,只有当函数 na 的参数是一个对象时,变量 xy 才会通过解构赋值生成。

在下述的 code 中,两种写法分别都对函数的参数默认值进行了设定,one 的函数参数默认值是空对象,但是设置了对象解构默认值。而 two 则是有一个具体的属性的对象,但是没有设置对象解构的默认值,因此两者在输出时的差异如下:

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
function one({x = 0, y = 0} = {}) {
console.log([x,y])
}

function two({x,y} = {x:0, y:0}) {
console.log([x,y])
}

/*
[ 0, 0 ]
[ 0, 0 ]
*/
one()
two()

/*
[ 1, 2 ]
[ 1, 2 ]
*/
one({x:1, y:2})
two({x:1, y:2})

/*
[ 1, 0 ]
[ 1, undefined ]
*/
one({x:1})
two({x:1})

/*
[ 0, 0 ]
[ undefined, undefined ]
*/
one({})
two({})
不定参数(剩余参数)

不定参数也被称之为 ”剩余参数“,他主要用于表示不确定参数的个数,由 ...变量名 进行定义,具名参数只能放在参数组的最后,并且只能有一个不定参数。

1
2
3
4
5
6
function foo(...values) {
console.log(values.length)
}

// 5
foo('one','two','three','fore','fire')
rest at arguments

这里的 rest 并不是已经口口相传的 REST API,而是休止符的意思:

1
2
3
4
5
6
7
8
9
10
function add(...values) {
let sum = 0

for (var val of values) {
sum += val;
}
console.log(sum)
}

add(2,5,3)

正因为通过不定参数的作用,因此可以利用其向函数内传入任意数值并通过求和函数进行计算,通过使用 rest 的方法可以代替 arguments 变量,而他主要是传递函数的是类似的数组对象,但通过 Array.prototype.slice.call 可以转换为一个真正的数组对象,这也是他与 rest 的区别, rest 参数就是一个真正的数组,数组所特有的方法都可以使用

name 属性

函数的 name 属性,是在 ES6、ES5 中可以获取和调用函数的一个方法,他在 ES6 中的表现非常的方便以及快捷。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function foo() {}

// foo
console.log(foo.name)

// 匿名函数
var of = function () {}

// of
console.log(of.name)

// 将具名函数赋值给变量
var te = function st() {}

// st
console.log(te.name)

箭头函数

箭头函数主要使用到的还是一些运算符表达式中的几项组成,在函数中使用这些箭头函数可以显得更加简洁的函数书写方式。

1
2
3
4
5
6
7
8
9
function fn() {
setTimeout(() => {
console.log(this.a + 10);
},0)
}
var a = 10

// 20
fn.call({a});

需要注意的是,箭头函数内是不会包含 this 的,因此上述箭头函数体中的 this 对象是定义函数时的对象,而不是使用函数时的对象。

闭包

闭包(Closure)简单来说就是能够读取外部函数的变量,这个概念最早出现在 60 年代,其最早实现闭包的程序语言是 Scheme,之后开始被广泛的运用在了函数式编程语言之中。简单理解就是,当每创建一个函数时,闭包就会在函数创建的同时被创建出来。

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
/**
* 在 for 循环内部中定义了内部函数 i,并通过内部循环数组 push x5
* 因此内部函数可以获取到每次循环的值,执行了 close 函数并最后赋
* 值给了 test。
* @return {[i:5]}
*/
var close = function () {
var arr = [];
for(var i=0; i<5; i++) {
arr.push(function () {
return i * i;
})
}
return arr;
}

var test = close();

/* 开始通过 test 来调用函数,开始执行 close 内的 for 循环
25
25
25
*/
console.log(test[0]())
console.log(test[0]())
console.log(test[0]())

之后我们换一个写法,当 for 循环每次执行时,i 会作为参数实时传输到内部函数中(a),因此获取的实际上是数组,调用时通过 test 来调用函数,开始执行 close 内的 for 循环。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/**
* 当 for 循环每次执行时,i 会作为参数实时传输到内部函数中(a),因此获取的实际上是数组
* @return {[ 0, 1, 4, 9, 16, 25, 36, 49, 64, 81 ]}
*/
var close = function () {
var arr = [];
for(var i=0; i<10; i++) {
arr.push(function (a) {
return a * a;
}(i))
}
return arr;
}

var test = close();

/* 开始通过 test 来调用函数,开始执行 close 内的 for 循环
[ 0, 1, 4, 9, 16, 25, 36, 49, 64, 81 ]
*/
console.log(test)

从上述的 code 中,我们将外层的匿名函数(close)的返回值也变成了一个函数,并让内层函数的返回值成为了我们需要的结果,但是由于内层函数保持着对 i 的引用

这也导致了 i 的值在内存中并没有被释放,这就让我们之后通过test 来调用函数,开始执行 close 内的 for 循环时,也能获取到 i 的值。

let at var 实现闭包

在 MDN 文档中,对 var 的描述是:”声明一个变量,并可选的将其初始化为一个值“,而后者则是:”声明一个块级作用域的本地变量、语句或表达式,同样可以初始化为一个值“
,与 var 声明语句的区别是,var 声明的变量只可以是全局或者是整个函数块的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
var close = function () {
const arr = []
for (let i = 0; i < 5; i++) {
arr.push(function () {
return i*i;
})
}
return arr
}

var test = close();

/**
* 在输出时将会将 const arr = [] 和 let i = 0 替换,但由于 let 声明的因素(本地变量)
* 所以每次 for 的循环都会将i 直接固定下来而不会受到外部影响,实现了闭包的效果
*/
console.log(test[1]())
console.log(test[1]())
console.log(test[1]())

对象

属性的简写

基于 ES6 属性的扩展,因此可以直接写入变量和函数,作为对象的属性和方法(属性名就是变量名,属性值就是变量值)

1
2
3
4
5
var foo = 'bar'
var bar = {foo}

// { foo: 'bar' } 同等于 var bar = {foo: foo}
console.log(bar)

函数

还有一些比如我们认为非常正常的表达式,但是在 ES6 之前是非常繁琐的,来对比下简写和非简写:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// ES6 add
function a(x,y) {
return {x,y}
}

// { x: 1, y: 2 }
console.log(a(1,2))

// normal
function b(x,y) {
return{x:x, y:y}
}

// { x: 1, y: 2 }
console.log(b(1,2))

函数方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
var a = {
method() {
return "hey,world!";
}
}

// hey,world!
console.log(a.method())


var b = {
method: function () {
return "hey,world!";
}
}

// hey,world!
console.log(b.method())

变量属性简写

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 a = ',world!'

var toa = {
name: 'hello',
hey() {
console.log('info:', this.name + a)
}
}

// info: hello,world!
toa.hey()


var b = ',world!'

var tob = {
name: 'hello',
hey: function () {
console.log('info:', this.name + b)
}
}

// info: hello,world!
tob.hey()
迭代器
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
const a = {
* myGenerator() {
yield 'One'
yield 'Two'
yield 'Three'
yield 'Four'
yield 'Five'
}
};

/*
One
Two
Three
Four
Five
*/
let echoa = a.myGenerator()

console.log(echoa.next().value)
console.log(echoa.next().value)
console.log(echoa.next().value)
console.log(echoa.next().value)
console.log(echoa.next().value)

const b = {
myGenerators: function * () {
yield 'One'
yield 'Two'
yield 'Three'
yield 'Four'
yield 'Five'
}
};

/*
One
Two
Three
Four
Five
*/
let echob = b.myGenerators()

console.log(echob.next().value)
console.log(echob.next().value)
console.log(echob.next().value)
console.log(echob.next().value)
console.log(echob.next().value)

属性名表达式

属性名表达式是 JavaScript 所定义的属性,主要有两种方法进行定义其属性名(也就是将表达式放在 [] 内):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var a = {
foo: true,
boo: 1
}

// { foo: true, boo: 1 }
console.log(a)

let bkey = 'foo'

let b = {
[bkey]: true,
['b' + 'oo']: 1
}

// { foo: true, boo: 1 }
console.log(b)

需要注意的是,属性表达式不可以被简写,否则将会报错,还需要留意的是,如果属性表达式是对象,那么将会被转换为字符串 [object Object] ,并只显示最后一个属性,因为前几个都被 KeyC 覆盖掉了

1
2
3
4
5
6
7
8
9
10
11
12
const keyA = {a:1}
const keyB = {b:2}
const keyC = {b:3}

const myObject = {
[keyA]: 'ValueOne',
[keyB]: 'ValueTwo',
[keyC]: 'ValueThree'
}

// { '[object Object]': 'ValueThree' }
console.log(myObject);

方法的 name 属性

方法的 name 属性,是在 ES6、ES5 中可以获取和调用函数的一个方法,他在 ES6 中的表现非常的方便以及快捷,他同样还提供了函数的 name 属性。

1
2
3
4
5
6
7
8
var log = {
myName() {
console.log(this.name)
},
};

// myName
console.log(log.myName.name)

当然还有一些特殊的方法 bind() 使用此方法的函数,返回时将会连带 bound + 函数名 进行输出,需要注意的是 bind() 方法是可以对其绑定的:

1
2
3
4
var doSomething = function () {}

// bound doSomething
console.log(doSomething.bind().name)

Symbol at name

如果对象的方法是一个 symbol 数据类型的值,那么通过 name 书香将返回的是这个属性值的描述:

是一个基本的数据类型(Primitive data type),其中每个值都是唯一的,因此一个 symbol 值可以具有作为对象属性的标识符
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

```js
const keyA = Symbol('one')
const keyB = Symbol('two')

let obj = {
[keyA](){},
[keyB](){}
};

/*
[one]
[two]
*/
console.log(obj[keyA].name)
console.log(obj[keyB].name)

可参考其文档: https://www.typescriptlang.org/docs/handbook/symbols.html

对象扩展运算符

解构赋值 (rest)

对象的扩展运算符就是将解构赋值(REST)和扩展运算符(…)引入对象,这种操作在 Bable 编译器中已经支持这项功能。

Babel.js 是一个用于 Web 开发,且自由开源的 JavaScript 编译器以及转译器。

对象的解构赋值主要是用于从对象内进行取值,用于将所有可遍历但尚未完全读取的属性分配到指定的对象上,所有键值和他们的值都会拷贝到新的对象中。

1
2
3
4
5
6
7
8
9
10
let  {x, y, ...z} = {x:1, y:2, a:3, b:4}

// 1
console.log(x)

// 2
console.log(y)

// { a: 3, b: 4 }
console.log(z)

需要注意的是,因为解构赋值是 z,所以他获取等剩余的尚未被读取的对键,因此会将剩余的键全部输出(即: a at b)

因为解构赋值的写法,所以解构复制必须是最后一个参数,否则将会出错。

1
2
3
4
5
6
7
let obj = {a:{b:1}}

let {...x} = obj

obj.a.b = 5;

console.log(x.a.b)

在上述代码中,x 是解构赋值所在对象,引用了 obj 对象的 a 属性,而 a 属性同时引用了 b 属性,而之后还引用了 obj.a.b 属性,因此这个属性将会对解构赋值产生影响。

从而导致了最后输出时原本输出结果为 1,经过一系列引用输出结果被影响的问题,所以解构赋值不会拷贝继承自原型对象的属性(即 obj)

当然也可以通过一个更为简单的方法解决这个问题,从而避免了解构赋值被影响的问题:

1
2
3
4
5
6
7
8
let keyA = {a:1}
let keyB = {b:2}

let b3 = {...keyB}
let b4 = {...keyA}

// { b: 2 } { a: 1 }
console.log(b3,b4)

因为 b3\b4 都只是 keyA\keyB 的引用,因此也只是赋值了自身的属性,被没有可以被其他函数影响的余地。

扩展运算符 (…)

与 Object.assign 方法
1
2
3
4
5
6
7
let keyA = {name:'Aswl', age: '19',measurements: '100'}

// let getB = {...keyA}
let getB = Object.assign({},keyA)

// { name: 'Aswl', age: '19', measurements: '100' }
console.log(getB)

扩展运算符的的作用与 Object.assign 方法同等使用,不过扩展运算符的使用较为简单和便捷,他可以完成 Object.assign 方法无法完成的功能

合并对象
1
2
3
4
5
6
7
8
let keyA = {name:'Aswl', age: '19'}
let keyB = {measurements: '100'}

// let getB = {...keyA}
let getB = {...keyA, ...keyB}

// { name: 'Aswl', age: '19', measurements: '100' }
console.log(getB)

ES6 对象新方法

Id Name Info
1 Object.is() 用于判断两个对象值是否一样,同等于 ===
2 Object.assign() 将目标对象一个或多个分配到目标对象中,因此他也有合并对象的作用
3 Object.key() 返回对象的键名
4 Object.values() 返回对象的值
5 Object.entries() 用于返回对象
6 Object.setPrototypeOf() 将指定一个对象到另一个域,也就是设置一个对象的 prototype
7 Object.getPrototype() 用于读取和判断一个对象的 prototype
8 Object.create 用于创建一个对象使得更符合 prototype

Object.is

1
2
3
4
5
let keyA = '10'
let keyB = '10'

// true
console.log(Object.is(keyA,keyB));

通过 ES6 所提出的同值相等(Same-Value Equality)算法来解决严格比较运算符就基本行为一致,从而产出了 Object.is 方法,他与绝对运算符更让人容易理解,如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function keyC () {
return NaN;
}
let keyA = null
let keyB = null

// true
console.log(+0 === -0)
// false
console.log(keyC === keyB)

// false
console.log(Object.is(+0,-0))
// true
console.log(Object.is(keyC,keyC))

在绝对运算符中,+0-0 被认为是一样的,而在此 ES6 新提出的算法时将不会被认为这是等同的,即 false。

而同样的还有 Nan 以及 Null 在绝对运算符中被认为是不等同的,而在 Same-Value Equality 算法下中是等同的。

Object.assign

1
2
3
4
5
6
7
8
var target = {a:1}
var source_1 = {b:2}
var source_2 = {c:3}

Object.assign(target,source_1,source_2)

// { a: 1, b: 2, c: 3 }
console.log(target)

虽然他具有合并对象的作用,但还是需要注意,如果目标对象之间有同名的属性,那么后面的属性就会被覆盖:

1
2
3
4
5
6
7
8
var target = {a:1}
var source_1 = {b:2}
var source_2 = {b:3}

Object.assign(target,source_1,source_2)

// { a: 1, b: 3 }
console.log(target)

除此之外还需要注意,如果 Object.assion 的参数不是对象,那么他也会直接返回该参数。

但如果对象如果是 undefinednull,因为他们无法转为对象,所以会报错,但是如果将他们放置在 source 参数中,当发现无法转换为对象时,就会被跳过:

1
2
3
4
5
6
7
8
var target = {a:undefined}
var source_1 = {b:null}
var source_2 = {b:3}

Object.assign(target,source_1,source_2)

// { a: undefined, b: 3 }
console.log(target)
复制与拷贝

至于其他的值,如果不在首参数中,也不会被报错之类的,除了字符串会以数组的形式拷贝到目标对象外,其他的都不会产生效果:

1
2
3
4
5
6
var source = {a:1}

var obj = Object.assign({}, source)

// { a: 1 }
console.log(obj)

在上面的代码中,除了数值和之前的布尔值以及数值会合并到对象中,而字符串将会产生包装的现象:

1
2
// [Boolean: true] [Number: 123] [String: 'abc']
console.log(Object(true), Object(123),Object('abc'))

因此布尔值、数值、字符串的类型分别会转成包装对象进行输出,也可以看到他们的原始值类型,这些属性是不会被 Object.assign 拷贝的,因为他拷贝的是可枚举属性值:

1
2
3
4
5
6
7
8
9
var c = Object.assign({b:'c'},
Object.defineProperty({}, 'invisible', {
enumerable:false,
value: 'hey'
})
)

// { b: 'c' }
console.log(c)

上述代码中因为 inisible 并不是可枚举类型的, 因此他没有被 assign 拷贝进去。

浅拷贝与深拷贝

浅拷贝与沈拷贝的问题在于,Object.assign 方法所实现的是浅拷贝,而不是深拷贝,也就是说如果 source 是一个对象,那么也会得到这个对象的引用:

1
2
3
4
5
6
7
var source = {a: {b:1}}
var get = Object.assign({}, source)

source.a.b = 10

// { a: { b: 10 } }
console.log(get)

因为源对象的 a 属性是一个对象,因此拷贝得到的是一个对象的引用,因此在输出的时候会被影响,对于这种方法,一旦遇到了同名的属性,将会直接替换。

1
2
3
4
5
6
7
var source = {a: {b:1}}
var sources = {a: {b:'hey'}}

var get = Object.assign({}, source,sources)

// { a: { b: 'hey' } }
console.log(get)

在上述的 code 中,因为 sourcea 属性被 sources 内的 a 给替换了,因此就会得到 a: { b: 'hey' } } 的结果。

对数组的处理

除了对对象的使用,Object.assign 还可以处理对象,但是会把数组视为对象。

1
2
// [ 4, 5, 3 ]
console.log(Object.assign([1,2,3],[4,5]))

因为 Object.assign0、1、2 视为对象,那么只好输出 target 部分的最后一个数组,以及在 source 位的 4,5,因此 target 数组的 0 号属性覆盖了属性 4 的属性,所以为 [4, 5, 3]

Object.keys()

用于遍历对象,并返回对象的键名,但在 ES7 的新提案中,提出了一种配套的方法 Object.keys()、Object.value()、Object.entries ,而原理都是循环遍历:

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
let {keys, values, entries} = Object

let obj = {oneKey:"oneValue", twoKey:"twoValue", threeKey:"threeValue"}

/*
oneKey
twoKey
threeKey
*/
for (let key of keys(obj)) {
console.log(key)
}

/*
oneValue
twoValue
threeValue
*/
for (let value of values(obj)) {
console.log(value)
}

/*
[ 'oneKey', 'oneValue' ]
[ 'twoKey', 'twoValue' ]
[ 'threeKey', 'threeValue' ]
*/
for (let [key, value] of entries(obj)) {
console.log([key, value]);
}

对于 ES7新的提案中,就显得非常方便,只需要一个方法就能实现:

1
2
3
4
5

let obj = {key:"Value", keys:"Values"}

// [ 'key', 'keys' ]
console.log(Object.keys(obj))

Object.values()

1
2
3
4
let obj = {key:"Value", keys:"Values"}

// [ 'Value', 'Values' ]
console.log(Object.values(obj))

此方法只可以返回对象本身可以遍历对象,如果遇到数组的返回,那么返回的形式同样被打乱,索引为 0 的数将会被放到最后一个输出,而其他的索引则正常:

Object.entries()

1
2
3
4
let obj = {key:"Values", keys:"Values"}

// [ [ 'key', 'Values' ], [ 'keys', 'Values' ] ]
console.log(Object.entries(obj))

如果对象的属性是 Symbol 值的话,那么该属性就会选择忽略:

1
2
3
4
let obj = {key:"Values", keys:"Values", [Symbol()]: "null"}

// [ [ 'key', 'Values' ], [ 'keys', 'Values' ] ]
console.log(Object.entries(obj))

所以这个属性还衍生出了一种可以输出 Symbol 属性的 Reflect.ownEntries() 方法,返回对象自身的属性。而同时 Object.entries() 的主要作用就是返回对象的属性(单个)。

1
2
3
4
5
6
7
8
9
let obj = {key:"Values", keys:"Values", [Symbol()]: "null"}

/*
key:Values
keys:Values
*/
for (let [key, value] of Object.entries(obj)) {
console.log(`${key}:${value}`)
}

当然你还可以搭配 ${JSON.stringify()} 的配合让对象输出为 JSON 格式的数据。

除此之外他还可以通过配合 Map 方法来实现一个真正的 Map 数据结构:

1
2
3
4
5
6
7
8
let obj = {key:"Values", keys:"Values", [Symbol()]: "null"}

// Map { 'key' => 'Values', 'keys' => 'Values' }
var map = new Map(Object.entries(obj))
console.log(map)

// [ [ 'key', 'Values' ], [ 'keys', 'Values' ] ]
// console.log(Object.entries(obj))

Object.setPrototypeOf() at Object.getPrototypeOf and prototype

与 ```Object.getPrototypeOf``` 以及 ```Object.create()```三个对象是来自 ```__proto___``` 属性,很明显是这个属性的代替,分别写操作和读操作
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

该属性是一个内置的属性,但由于 ES6 标准明文规定必须部署这个属性,导致他才加入了 ES6 :

> 在 ECMAScript 2015(ES6)的规范要求中,支持__proto__ 是各大Web浏览器厂商的要求(虽然符合规范),但其他环境下因为历史遗留的问题,也有可能被使用和支持。

同时 MDN 也建议不要使用这个属性:”该特性已经从 Web 标准中删除,虽然一些浏览器目前仍然支持它,但也许会在未来的某个时间停止支持,请尽量不要使用该特性。“

所以在这个环境下,```Objcet.setPrototypeOf()``` 与 ```Object.getPrototypeOf``` 以及 ```Object.create()```三个对象就出现了,从而代替了 ````__proto__``` 属性。

##### prototype
Prototype 指的是 ”继承与原型链“,根据 MDN 的描述也非常简单粗暴直接点名重点:

> 对于使用过基于类的语言 (如 Java 或 C++) 的开发者们来说,JavaScript 实在是有些令人困惑 —— JavaScript 是动态的,本身不提供一个 class 的实现。即便是在 ES2015/ES6 中引入了 class 关键字,但那也只是语法糖,JavaScript 仍然是基于原型的。
>
> 当谈到继承时,JavaScript 只有一种结构:对象。每个实例对象(object)都有一个私有属性(称之为 __proto__ )指向它的构造函数的原型对象(prototype)。该原型对象也有一个自己的原型对象(__proto__),层层向上直到一个对象的原型对象为 null。根据定义,null 没有原型,并作为这个原型链中的最后一个环节。
>
> 几乎所有 JavaScript 中的对象都是位于原型链顶端的 Object 的实例。
>
> https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Inheritance_and_the_prototype_chain

如果你了解了这篇文章,那么 ```setPrototypeOf``` 和 ```getPrototypeOf``` 都会知道他们是干嘛的。

###### Object.setPrototypeOf()

```js
let proto = {};

let obj = {x:10}

/*
{ x: 10 }
20
40
10
20
40
*/
console.log(Object.setPrototypeOf(obj,proto));

console.log(proto.y = 20)
console.log(proto.z = 40)

// 因为 Object.setPrototypeOf 将 proto 对象的原型设置为 obj 所以可以读取
console.log(obj.x)
console.log(obj.y)
console.log(obj.z)

正因为 Object.setPrototypeOf 方法将指定一个对象(proto)到另一个域(obj) 所以可以读取。

Object.getPrototype() and Object.create
属性用于返回指定对象的原型,也就是依赖的函数:
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

```js
let proto = {};

let obj = {x:10}
/*
{ x: 10 }
20
40
10
20
40
*/
console.log(Object.setPrototypeOf(obj,proto));

console.log(proto.y = 20)
console.log(proto.z = 40)

//
console.log(obj.x)
console.log(obj.y)
console.log(obj.z)

var let = '10';

// true
console.log(Object.getPrototypeOf(obj) === proto)

// false
console.log(Object.getPrototypeOf(let) === proto)

当然你还可以使用更加标准的方式,通过 Object.create() 的方法来创建一个新的对象,完美符合了 MDN 在 Prototype 上所说的:

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
let proto = {};

let getS = {x:10}

// 创建一个新方法
let obj = Object.create(getS)

/*
{ x: 10 }
20
40
10
20
40
false
true
*/
console.log(Object.setPrototypeOf(getS,proto));

console.log(proto.y = 20)
console.log(proto.z = 40)

//
console.log(obj.x)
console.log(obj.y)
console.log(obj.z)

var let = '10';

// true
console.log(Object.getPrototypeOf(obj) === proto)

// false
console.log(Object.getPrototypeOf(getS) === proto)

当然,get.PrototypeOf 还可以读取 prototype 对象的属性:

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
let proto = {};

let getS = {x:10}

// 创建一个新方法
let obj = Object.create(getS)
/*
{ x: 10 }
20
40
10
20
40
*/
console.log(Object.setPrototypeOf(obj,proto));

console.log(proto.y = 20)
console.log(proto.z = 40)

//
console.log(obj.x)
console.log(obj.y)
console.log(obj.z)

var let = '10';

// { y: 20, z: 40 }
console.log(Object.getPrototypeOf(obj))

属性的可枚举性(getOwnPropertyDescriptor)

在 ES6 中,每个属性都会有自己的一个描述对象(Descriptor)用于控制该属性的行为,那么通过 Object.getOwnPropertyDescriptor 就可以获取对象的描述对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
let obj = {foo: 1234}

/*
value: 1234, 值
writable: true, 可写
enumerable: true, 可枚举
configurable: true 可配置
*/
console.log(Object.getOwnPropertyDescriptor(obj,'foo'))

/*
{ value: 'hey',
writable: false,
enumerable: false,
configurable: false }
*/
tes = {}
let sets = Object.defineProperty(tes,"inisible", {
value: 'hey',
writable: false,
enumerable:false
})

console.log(Object.getOwnPropertyDescriptor(sets,'inisible'))

在 ES5 中,当 enumerable 为 false 的属性会被忽略,其中主要有 :

Id Name Info
1 for...in 只循环遍历自身对象和继承的可枚举属性
2 Object.key 返回对象自身的所有可枚举的属性键名
3 JSON.stringify() 只串化对象自身的可枚举属性
4 Object.assign() 忽略自身为 enumerable 的属性
1
2
3
4
5
6
7
8
9
10
11
12
let obj = {foo: 1234}

console.log(Object.getOwnPropertyDescriptor(obj,'foo'))

let sets = Object.assign({b:'c'},
Object.defineProperty({},'inisible', {
enumerable:false,
value:'hey'
})
)

console.log(Object.getOwnPropertyDescriptor(sets,'inisible'))

在 Node.js 中,类是一个函数的集合体以及成员变量,但是他依然被认为称之为:“类(class)” 语法糖,他本质上让对象更加清晰。

定义

这是一个 ES6 中的语法糖,class 将会作为对象模板被引入,通过该关键字可以定义类,而他的本质就是 function ,只不过他可以让对象的原型更加清晰,更像面向对象的语法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 3
let example = class {
constructor(x,y) {
this.area = x + y;
}
}

console.log(new example(1,2).area)

// 3
class exmapleName {
constructor(x,y) {
this.area = x + y;
}
}

console.log(new exmapleName(1,2).area)

上述示例中主要分为匿名函数和命名函数,他们都实现了同样的效果和功能,但需要注意的是,不可以重复声明同一个类,如果声明将会抛出异常信息。

内部属性或静态属性

内部方法

在新的 ES6 规定中,class 内部只会有静态方法,不会有静态属性,而直接定义在类的内部属性(class.propname)将会被称之为静态属性,不需要实例化。

在 ES6 中 prototype 属性仍然存在,他本质还是定义在 prototype 上,且覆盖 prototype 属性,并在初始化时添加新的方法

1
2
3
4
5
6
7
8
class Example {
constructor() {
console.log('hello,world!')
}
}

// hello,world!
new Example()

需要注意的是,类的实例化必须是通过 new 关键字的

静态方法

类的 constructor 方法用于实例化对象时被调用,因此这个概念也被应用与大多数的面向对象编程语言,同时该方法下返回实例对象的 this.x 也可以指定返回的对象:

1
2
3
4
5
6
7
8
class Example {
static sum(x,y) {
console.log(x + y)
}
}

// 2
Example.sum(1,1)
decorators

装饰器 (decorators) 是ES7类的新提案,在转译器环境中被开发人员广泛的使用,实际上这是 ES5 所纳入的提案,但最这 ES6 中新引入的 class ,从而需要额外的功能来支持 注释或修改类和成员的场景

你可以可以在类、方法、属性、参数中进行声明,并使用 @expression 的形式调用函数。

你可以在 https://www.typescriptlang.org/docs/handbook/decorators.html#decorators 了解更多(ES5)

类的继承

extends

在类的内部中可以使用的 get 以及 set 关键字,对于某个睡醒内设置存值函数和取值函数:

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
class fatcher {
constructor() {}

// 绑定
get a() {
return this._a
}

// 调用
set a(a) {
this._a = a;
}
}

class child extends fatcher {
constructor() {
super();
}
}

let foo = new child();

foo.a = 10

// 10
console.log(foo.a)

class 可以通过 extends 关键字从而实现类的继承,并通过 getter/setter 分别对属性进行了绑定和调用,除此之外,child 通过 extends 继承了 fatcher ,所以可以被调用并输出数据,也就是说通过此关键字让两个类变得一样了。

super

super 关键字用于访问和调用对象中父对象上的函数,ES6 中要求子类和构造函数必须执行一次 super 函数,也就是说子类 constructor 方法中必须有 super。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class fatcher {
constructor() {}
static log() {
return 'Hey,'
}
}

class child extends fatcher {
static logDesc() {
return super.log() + 'world!';
}
}

// hey,world!
console.log(child.logDesc())

ES6 模块化

在 ES6 引入模块化之前,其模块化的使用是 RequireJS,用于 JavaScript 文件和模块加载,可以在浏览器环境中使用,可通过 Node 来实现模块化脚本加载来提高代码的编写质量和速度。

在 ES6 规范中引入的模块提案,其设计思想是在编译时候就能确定模块的依赖关系,以及输入和输出的变量,所以 ES6 中模块化主要分为导出(export)和导入(import)

添加 babel 支持

正因为使用的是 ES6,这在目前的版本中看样子很正常的语法但会因为一些语法糖的问题而出错,因此我们可以在 package.json 中以 babel 的转译器进行运行,并添加 ES6 支持:

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
{
"name": "node",
"version": "1.0.0",
"description": "",
"main": "index.js",
"dependencies": {
"babel-preset-es2015": "^6.24.1",
"reflect-metadata": "^0.1.13",
"ts-node": "^10.2.0"
},
"devDependencies": {
"@babel/cli": "^7.14.8",
"@babel/core": "^7.0.0-bridge.0",
"@babel/node": "^7.14.9",
"@babel/plugin-proposal-decorators": "^7.14.5",
"@babel/polyfill": "^7.12.1",
"@babel/preset-env": "^7.15.0"
},
"scripts": {
"test": "babel-node vievs.js"
},
"assumptions": {
"setPublicClassFields": true
},
"presets": [
[
"@babel/preset-env"
]
],
"plugins": [
[
"@babel/plugin-proposal-decorators",
"transform-decorators-legacy",
{
"legacy": true
}
],
[
"@babel/plugin-proposal-class-properties"
]
],
"author": "",
"license": "ISC"
}

这个 package.js 文件中主要用于安装 babel 的依赖,我们可以通过 npm init 来初始化一个,之后添加依赖即可,之后使用 npm install 等待安装完成即可。

安装后,我们需要新建一个 .babelrc 文件,添加一个 ES6 的支持:

1
2
3
{
"presets": ["es2015"]
}

export at import

1
2
3
4
5
6
let myName = "modelName";
let mySet = function () {
return "My in name:" + myName;
}

export {myName, mySet}

首先我们要创建一个文件,并写入方法,之后通过 export 来导出,最后在通过 ````import``` 进行引入:

1
2
3
4
import {myName, mySet} from "./modelName.js";

// Name: modelName My in name:modelName
console.log('Name:',myName, mySet())

我们可以直接通过 npm 运行:“npm run test”,也可以直接输入 bebel-node filename.js 运行。

as

.

可以说是 ```export……import``` 所附带的一个方法,他主要的作用就是 可以建立 ```export``` 的连接,你可以理解为赋值:
1
2
3
4
5
6
7
8

```js
let myName = "modelName";
let mySet = function () {
return "My in name:" + myName;
}

export {myName as exportName, mySet}
1
2
3
4
import {exportName, mySet} from "./modelName.js";

// Name: modelName My in name:modelName
console.log('Name:',exportName, mySet())

最后运行输出的还是与 exportName 建立链接的 myName,因此这也是 as 命令的作用。

📖 more posts 📖