ruirui's blog

ruirui's 备忘录


  • Tags

  • Archives

Linux 升级 Node

Posted on 2020-11-02 | Comments:

以下为通过二进制文件升级 node 的方式

升级 Node

  1. 通过官网下载 linux 二进制文件安装包
    推荐安装在/opt目录下

  2. 解压
    tar -xvf node-v14.15.0-linux-x64.tar

  3. 设置环境变量
    方式一: 设置 PATH 环境变量
    修改 /etc/profile 文件,在文件末尾添加以下内容

    1
    2
    export NODE_HOME=/xxx
    export PATH=$NODE_HOME/bin:$PATH

    修改完之后,打开新的命令窗口,或者执行 source /etc/profile 来生效。

    方式二: 设置软链
    通过 which node 查看 node 可执行文件路径,将本次 node 安装路径下的/bin/node 链接到 which node显示的路径下

    which 指令: 查看可执行文件的位置,会在环境变量 $PATH 设置的目录里查找符合条件的文件

    1
    2
    3
    ln -s /opt/node/bin/node /usr/local/bin/node 
    ln -s /opt/node/bin/npx /usr/local/bin/npx
    ln -s /opt/node/bin/npm /usr/local/bin/npm

其他

以下是在操作上述过程中遇到的一些问题及知识点,在此记录

通过 n 包管理器升级

清除 npm 缓存
npm cache clean -f
安装
npm install -g n
下载最新版本
n latest
下载稳定版
n stable
下载某个版本
n 版本号
切换版本
n

链接

硬链接:每个文件都有一个硬链接,硬链接和文件没什么区别。
软链接(符号链接):类似 window 的快捷方式,软链接可以关联一个目录,硬链接不行。

创建链接 ln
-s 表示创建软链

1
ln -s /usr/bin/npm /opt/soft/node-v14.15.0/bin/npm

查看文件路径 ls -l
如果文件或目录是软链,-> 后面是链接的真实路径

1
2
3
4
5
[[email protected] bin]# ls -l | grep node
lrwxrwxrwx 1 root root 27 10月 29 19:34 n -> ../lib/node_modules/n/bin/n
lrwxrwxrwx 1 root root 42 10月 29 20:49 node -> /opt/soft/node-v14.15.0-linux-x64/bin/node
lrwxrwxrwx 1 root root 41 10月 29 21:06 npm -> /opt/soft/node-v14.15.0-linux-x64/bin/npm
lrwxrwxrwx 1 root root 41 10月 29 21:07 npx -> /opt/soft/node-v14.15.0-linux-x64/bin/npx

single-spa 源码分析

Posted on 2020-03-26 | Comments:

在了解single-spa基本功能后,可以将其简单概括为:single-spa 的核心就是动态将子应用的资源文件插入到主应用中。那内部是如何管理子应用、如何做到子应用之间的动态切换。带着这些疑问探究 single-spa 源码。

源码主要分为三部分:app 应用、Navigation 路由、生命周期。

application

应用状态

为了更好的管理 app,每个app在运行期间都有自己的状态
每个应用在整个运行期间都有自己的状态,根据不同状态做对应的处理。

  • null : app 不存在
  • NOT_LOADED:已经注册还没加载
  • LOADING_SOURCE_CODE:正在加载 app 代码(registerApplication 的第二个参数)
  • NOT_BOOTSTRAPPED:已经加载还没启动,即未执行 app 的 bootstrap 生命周期函数
  • BOOTSTRAPPING:正在启动,执行 app 的 bootstrap 生命周期函数,只执行一次
  • NOT_MOUNTED:已经启动还没挂载
  • MOUNTING: 正在挂载,执行 app 的 mount 函数
  • MOUNTED:已经挂载
  • UNMOUNTING:正在移除挂载,执行 app 的 unmount 函数
  • UNLOADING: 正在卸载,还没完成
  • SKIP_BECAUSE_BROKEN:执行期间出错

注册应用

框架内部统一管理所有应用来进行应用之间的调度(应用的挂载卸载错误等处理),应用注册成功后,会放到内部统一管理应用的的数组 apps 里。
registerApplication:(appName,applicationOrLoadingFn,activeFn, customProps)

  • appName: 应用唯一标识。
  • applicationOrLoadingFn: 入口 js 文件,这就需要子应用做一些处理,需要打包成特殊的文件格式进行加载
  • activeFn:什么时候激活应用,根据 url 进行匹配。
  • customProps:给子应用传递的参数,例如登录信息权限控制等。

注册成功之后的单个 app 信息如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
apps.push({
loadErrorTime: null,
name: appName,
loadImpl,
activeWhen: activityFn,
status: NOT_LOADED,
parcels: {},
devtools: {
overlays: {
options: {},
selectors: [],
}
},
customProps
});

除了注册传递的参数以外,single-spa 给每个 app 增加了状态 status。注册后的状态为 NOT_LOADED,表示已经注册但未加载。
注册成功后,会执行 reroute 方法,reroute 内部判断是否已经 start,如果未启动,则找到匹配的应用(不报错的、还未 load 的)进行预加载,为挂载做准备。如果已经启动,则取消已经挂载的,找到当前匹配的进行挂载,reroute 是整个 single-app 的核心,后面还会详细分析。

卸载应用

unregisterApplication 会去调用 unloadApplication,然后在 apps 里找到对应的 app 将其删除。
在 unloadApplication 里,会先去看当前应用是否有正在被 unload,如果存在则直接返回。否则需要先 unmount 之后再去 unload。

Navigation

1
2
3
4
5
window.addEventListener('hashchange', urlReroute);
window.addEventListener('popstate', urlReroute);
function urlReroute() {
reroute([], arguments)
}

全局监听了 hashchange 和 popstate 事件来拦截 url 的变化,在路由事件到达应用框架(Vue、React)之前做应用的挂载卸载处理,当触发这两个事件后,也会执行 reroute,同时带上事件参数传递给 reroute。下面重点分析一下 reroute。

reroute

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
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
/*
reroute 接收两个参数
pendingPromises:表示正在队列中等待执行的 reroute。reroute 在执行期间,可能会有多个 reroute 被调用(路由触发或者应用注册)。
eventArguments:路由事件触发的 event。只有路由改变才会有这个参数
*/
export function reroute(pendingPromises = [], eventArguments) {
// appChangeUnderway 是一个开关,用来表示 reroute 是否正处于执行期间。
// 如果正处于执行期间还会有 reroute 要执行,则会将 reroute 放入队列里等待执行
if (appChangeUnderway) {
return new Promise((resolve, reject) => {
peopleWaitingOnAppChange.push({
resolve,
reject,
eventArguments,
});
});
}

appChangeUnderway = true;
// wasNoOp 为 true 表示没有应用发生变更。
let wasNoOp = true;

if (isStarted()) {
return performAppChanges();
} else {
return loadApps();
}

function loadApps() {
return Promise.resolve().then(() => {
const loadPromises = getAppsToLoad().map(toLoadPromise);
// 获取要 load 的 app(未出错的命中的),如果有说明应用发生了变更
if (loadPromises.length > 0) {
wasNoOp = false;
}
// 执行 load 并且直接 finishUpAndReturn
// 并不进行 mount,因为未 start,此时进行预加载
// 即:页面上没有挂载该应用,但是会去请求对应的资源文件
// 不会去调用应用文件里的bootstrap、mount、unmount等生命周期
return Promise
.all(loadPromises)
.then(finishUpAndReturn)
.catch(err => {
callAllEventListeners();
throw err;
})
})
}

function performAppChanges() {
return Promise.resolve().then(() => {
window.dispatchEvent(new CustomEvent("single-spa:before-routing-event", getCustomEventDetail()));
const unloadPromises = getAppsToUnload().map(toUnloadPromise);

const unmountUnloadPromises = getAppsToUnmount()
.map(toUnmountPromise)
.map(unmountPromise => unmountPromise.then(toUnloadPromise));

const allUnmountPromises = unmountUnloadPromises.concat(unloadPromises);
// 获取要 unload 以及要 unmout 的 app,如果有表示应用发生了变更
// 如果此时触发的是子应用内部的路由,则此时 allUnmountPromises 为[],表示没有应用的卸载或挂载
if (allUnmountPromises.length > 0) {
wasNoOp = false;
}

const unmountAllPromise = Promise.all(allUnmountPromises);

const appsToLoad = getAppsToLoad();
// 在其他应用 unmounting 期间将需要 load 的 app 执行 load、bootstrap(并行优势),等所有的 unmounting 都结束之后去挂载当前的 app
const loadThenMountPromises = appsToLoad.map(app => {
return toLoadPromise(app)
.then(toBootstrapPromise)
.then(app => {
return unmountAllPromise
.then(() => toMountPromise(app))
})
})
// 如果有要挂载的 app,也说明应用发生了变更
if (loadThenMountPromises.length > 0) {
wasNoOp = false;
}
// 获取已经 bootstrap 要去 mount 的 app,同上,等到其他 app unmounting 之后执行挂载
const mountPromises = getAppsToMount()
.filter(appToMount => appsToLoad.indexOf(appToMount) < 0)
.map(appToMount => {
return toBootstrapPromise(appToMount)
.then(() => unmountAllPromise)
.then(() => toMountPromise(appToMount))
})
// 同上
if (mountPromises.length > 0) {
wasNoOp = false;
}
// 所有要卸载的执行完之后,执行回调
return unmountAllPromise
.catch(err => {
callAllEventListeners();
throw err;
})
.then(() => {
callAllEventListeners();

return Promise
.all(loadThenMountPromises.concat(mountPromises))
.catch(err => {
pendingPromises.forEach(promise => promise.reject(err));
throw err;
})
.then(() => finishUpAndReturn(false))
})

})
}

function finishUpAndReturn(callEventListeners=true) {
const returnValue = getMountedApps();

if (callEventListeners) {
callAllEventListeners();
}
pendingPromises.forEach(promise => promise.resolve(returnValue));

try {
const appChangeEventName = wasNoOp ? "single-spa:no-app-change": "single-spa:app-change";
window.dispatchEvent(new CustomEvent(appChangeEventName, getCustomEventDetail()));
window.dispatchEvent(new CustomEvent("single-spa:routing-event", getCustomEventDetail()));
} catch (err) {
setTimeout(() => {
throw err;
});
}
// 打开开关,执行队列里的 reroute
appChangeUnderway = false;
// 虽然批量执行所有等待的 reroute,但这个地方还是需要递归的执行,因为在执行 reroute 期间可能又会有 reroute 进来
if (peopleWaitingOnAppChange.length > 0) {
const nextPendingPromises = peopleWaitingOnAppChange;
peopleWaitingOnAppChange = [];
reroute(nextPendingPromises);
}

return returnValue;
}

function callAllEventListeners() {
pendingPromises.forEach(pendingPromise => {
callCapturedEventListeners(pendingPromise.eventArguments);
});

callCapturedEventListeners(eventArguments);
}

function getCustomEventDetail() {
const result = {detail: {}}

if (eventArguments && eventArguments[0]) {
result.detail.originalEvent = eventArguments[0]
}

return result
}
}

画个简易的流程图如下:
reroute

lifecycles

single-spa 的亮点除了顶层路由的设计,另一个亮点就是生命周期的设计。生命周期的设计使得主应用更好的控制子应用。整个生命周期状态变更如下:
lifecycle

app 出错的状态有两个:SKIP_BECAUSE_BROKEN 与 LOAD_ERROR。SKIP_BECAUSE_BROKEN 表示在状态变更时出错,阻止往下个状态变更,LOAD_ERROR 表示加载错误,此时会记录当前的时间戳,当路由再次导航到对应用时还会尝试去加载(时间间隔大于200ms)。
挂载阶段出错时,在状态变成 SKIP_BECAUSE_BROKEN 之前需要先将状态变成 mounted,因为出错后要执行 toUnmountPromise 卸载应用,而 toUnmountPromise 会判断如果状态不是 MOUNTED 时会跳过。

single-spa-react

上面提到,在加载应用资源时,会去检查 app 的三个生命周期状态,single-spa 要求接入的应用都提供这三个生命周期,所以官方官方适配出了各个框架的工具。以single-spa-react 为例,react 应用的入口文件通过 single-spa-react 封装,暴露给 single-spa 三个生命周期函数,并且都是 Promise。

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
const defaultOpts = {
// required opts
React: null,
ReactDOM: null,
rootComponent: null,
loadRootComponent: null,
suppressComponentDidCatchWarning: false,
domElements: {},

// optional opts
domElementGetter: null,
parcelCanUpdate: true,
}
export default function singleSpaReact(userOpts) {
// ...
const opts = {
...defaultOpts,
...userOpts,
};
// ...
const lifecycles = {
bootstrap: bootstrap.bind(null, opts),
mount: mount.bind(null, opts),
unmount: unmount.bind(null, opts),
};
// ...

return lifecycles
}
  • bootstrap: 如果是 class 或者无状态组件,则直接返回。确保传入的是 React 根组件。
  • mount: 找到 single-spa 传递给应用的 DOM 节点(domElementGetter),执行 reactDomRender 进行渲染。
  • unmount: 从 DOM 中移除 React 应用。

2019年读书笔记

Posted on 2020-01-05 | Comments:

自从脱离了学校之后,发现自己对文字和语言的感知特别弱,和别人交流看过的书或者电影时,惊讶的发现大部分时候只记得书名,书里描述的具体场景及情节忘的一干二净哪怕当时阅读时感觉多么好看多么有共鸣,这种感受多了之后其实有点痛苦甚至失望,于是19年想着能把读过的书以及当时的感受记录下来,也算是一种积累或者回忆。

美妙的新世界

2018-12-20
这是我读的第一本乌托邦的书籍,看《奇葩说》辩论时詹青云提到的。

“新世界”相信一种理论:”道德教育都是不能诉诸理论的。” 因此都在下意识进行。
“新世界”有一条规定:”智力和工作是成年人,感情和欲望是孩子”

新世界的宗旨是: 一切为了稳定。当人们出现一点痛苦,服用唆麻便可以化解。
读完这本书,感觉到更多的是可怕和悲哀,新世界人们像是一个个流水线上的生产出来的机器,从最原始材料的不同就被划分不同的社会阶级,人和人之间不是平等的,每个人是有具体社会职责的,都是为了看似和谐美好高效的社会在运转。然而缩短劳动力的时间并不会使社会更稳定,提高效率并不会使每个人更幸福。

局外人

2019-01-01
在整个阅读的过程中,主人公默尔索一直给我这种感觉:无所谓,都行。母亲逝世后,默尔索并没有如别人一般表现痛苦难受,玛丽问他是否想要结婚时,他回答结不结都行,当无意枪杀阿拉伯人时,也没有急于为自己辩护,在将要执行死刑时,被一直逼问是否信仰上帝。

默尔索一切的举动在常人眼里就是不道德、政治不正确。在审判默尔索的罪行时,人们更多的在批判审视默尔索,任何的举动都要以世人道德标准来评判,不在乎事实过程,一遍遍的分析默尔索的动机,本是一个可以从轻处罚的案件,却最终以荒诞的结局来收场。

霍乱时期的爱情

2019-03
一直以来都是用 kindle 在上下班坐公交地铁的时间来读书,《霍乱时期的爱情》买的实体书来看,所以进度明显比用 kindle 慢了许多,看起来也断断续续的,读完不是很满意,以后有时间再二刷写感受吧。

玛格丽特小镇

2019-04-03
这本书的名字和之前做的一种饼干(玛格丽特饼干)名字几乎一样,就勾起了我的好奇心,用比较短的时间读完,读的过程中觉得整个叙述缺乏逻辑条理,随意切换场景,但好像正是这种平淡的叙述方式,读起来反而更加随心,更加吸引人。

某种程度上,你在一个地方认识的人,定义了那个地方对于你的意义。
爱情就像一个学步的贪婪孩子,只认得两个字,那就是”我的”。
对初恋的执恋从来都跟对象无关,都是人们对自己的怀念。
我跑到生命尽头看了看, 看到我们果然白头偕老了。
有时候,我们会言过其实。有时候,我们会说一些不是那么真实的话,暗自希望说出来后即会成真。

他们在小镇上碰到五个玛格丽特,有天真可爱的小孩子梅,青春叛逆的米亚,忧伤的玛吉,怪异的玛琪,还有耳聋的的老玛格丽特。而这些都是不同心静,不同时期的玛格丽特。
就像是每个人都会有多方面的自己,每个时期都可能变成不同的人,而爱一个人就要能接受她的全部,无论美丑琐碎。
看完后看评价才发现是《岛上书店》作者的新作,准备再重温一遍《岛上书店》。

读书的时候有一种状态会让人特别愉悦,就是读着读着,一些文字就会让心里波动一下,有时是温暖,有时是揪心,有时是绝妙,有时是感动,就像是在经历不同的事情,寻找内心的自己,这大概就是读书的乐趣吧。

岛上书店

2019-04-18
二刷《岛上书店》,看之前试着回忆一下故事情节,发现几乎想不起来了… 再刷的过程中,细节慢慢浮现出来。
(当时只记录了上面的话,想着之后补上后续的读书笔记,然而就是因为没及时写,之后想补已经忘的差不多了,我这可怕的记忆力)

挪威的森林

2019-04-28
大学的时候急于拓宽自己的知识面,囫囵吞枣读了一些书,有的是为了读而读,读书时急于看到直给的东西,大多数都体会不到读书的乐趣,《挪威的森林》就属于这种,迷迷糊糊读到一半可能就换下一本了。再次读虽说还是觉得迷糊,但也是有耐心从头看到尾。

读完想了想这到底是一部什么样的小说,气氛低沉、伤感、平静。直子自从姐姐的死、木月的自杀之后陷入抑郁症的痛苦中,渡边几乎是她唯一的希望,然而她最终还是没能拯救自己。初美是一个优秀完美的女性,永泽拈花惹草、放荡不羁的性格以及价值观,让他意识到自己配不上初美,甚至觉得和初美在一起也违背自己的价值观,最终通过伤害初美让初美主动离开,而初美最后的自杀也是让人很惋惜。绿子几乎是唯一一个热情单纯的人,一道光一样的存在,渡边一直心系直子,绿子的所有举动渡边都没放在心上,包括渡边搬家忘记告诉绿子,绿子换发型没注意到,这就是不爱吧,因为不爱所以不关注。

傲慢与偏见

2019-12
某天周末想看电影放松一下,随意打开两个高分电影,一眼就被这部电影的高清开场吸引了,电影里唯美的景色,光看背景都是一种享受,看到伊丽莎白与达西之间产生误会冲突也会跟着揪心,可能也是对美好爱情的向往,在看这部电影时觉得格外心动。

记得之前看完<<被嫌弃的松子的一生>>之后,想再去看电影回顾一下,发现电影破坏了当初看小说的内心想象,所以之后电影和小说基本都会二选一,《傲慢与偏见》是我看完电影强烈想要去读原著想要了解人物当下内心感受的书,并且在一周内很快读完。电影整体节奏比较快,在读完一半小说之后忍不住又去再看了一遍电影,很多场景也有了不一样的感受。

读完这本书之后想到,我对这本书又何尝不是一种偏见呢,很早就知道了这本书但从没想主动去看,甚至见到这本书也自然而然绕过,内心觉得名著都是晦涩难懂的,可能名著是和『学习』挂钩的,『学习』这个词就给人一种压力,总觉得任何需要『学习』的知识都不容易理解,都不是自然而然可以习到或得到,都需要克服一些条件,即使收获比较大过程也不是享受的。我这种潜意识不仅在是读书,在平时工作生活中也有很大的影响,即使现在意识到了也很难抹平现在及以后的影响,慢慢来吧。

今年下半年换了份工作,感受到了未有的压力和焦虑,更多的感受到交流沟通的困难以及思维方式不同带来的认知差异,度过了几个月满脑子都是工作没有生活乐趣的生活,与此同时读书时间也少了很多,《约翰克里斯朵夫》《漫长的告别》等都还是未完成的状态,希望2020年工作上能顺心点,生活上能尝试更多的可能性,阅读更自然的成为生活的一部分。

Webpack 分析系列-打包后 js 文件分析

Posted on 2019-11-10 | Comments:

网上的 webpack 分析系列文章已经很多了,但从自己理解的角度出发,进行记录和梳理,是一个知识重新构建的过程,更加有利于消化吸收。此次把 webpack 当作一个系列来记录,希望从整体的体系出发,加深对 webpack 的理解。
首先从 webpack 打包生成的文件进行简单分析。

本文代码地址

1
2
// b.js
export default B = 'b';
1
2
3
4
// a.js
import B from './b';
console.log(B);
export const A = 'a';
1
2
3
// index.js
import { A } from './a';
console.log(A);

执行 npm run build(webpack –mode development) 生成 main.js 如下

1
2
3
4
5
6
7
8
9
10
11
12
(function(modules) { // webpackBootstrap
// ...

   // Load entry module and return exports
   return __webpack_require__(__webpack_require__.s = "./src/index.js");
})(
{
"./src/a.js": (function(module, __webpack_exports__, __webpack_require__) {},
"./src/b.js": (function(module, __webpack_exports__, __webpack_require__) {},
"./src/index.js": (function(module, __webpack_exports__, __webpack_require__) {}
}
)

可以看到,打包出来的是一个立即执行函数,参数是一个 key-value 的对象,key 是引入文件的路径,value 是对应的文件生成的模块化函数,立即执行函数返回一个从入口文件开始执行的__webpack_require__函数,关键就是__webpack_require__函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// The require function
function __webpack_require__(moduleId) {

// Check if module is in cache
if(installedModules[moduleId]) {
return installedModules[moduleId].exports;
}
// Create a new module (and put it into the cache)
var module = installedModules[moduleId] = {
i: moduleId,
l: false,
exports: {}
};

// Execute the module function
modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);

// Flag the module as loaded
module.l = true;

// Return the exports of the module
return module.exports;
}

__webpack_require__ 根据 moduleId 来执行每一个 module 函数,当 installedModules 缓存中有该模块的对象,也就是该模块已经加载过,则从缓存中去取该对象的 exports,否则创建一个新的 module 对象执行 module 函数,module 对象有三个属性,i 表示 moduleId , l 表示模块是否已经加载过,exports 表示 module 的导出内容。通过 call 方法调用 module 的执行函数,执行函数的 this 指向 module.exports,后面三个是传入该函数的参数,__webpack_require__函数最后返回 module.exports。

回到最开始的立即执行函数,执行__webpack_require__(__webpack_require__.s = "./src/index.js"),对应的入口文件的 module 函数如下。

1
2
3
4
5
6
7
8
9
10
11
/***/ "./src/index.js":
/*!**********************!*\
!*** ./src/index.js ***!
\**********************/
/*! no exports provided */
/***/ (function(module, __webpack_exports__, __webpack_require__) {

"use strict";
eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _a__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./a */ \"./src/a.js\");\n\nconsole.log(_a__WEBPACK_IMPORTED_MODULE_0__[\"A\"]);\n\n//# sourceURL=webpack:///./src/index.js?");

/***/ })

把无关的注释都删掉,取出 eval 里的代码(eval 函数可执行其中的的 JavaScript 代码)

1
2
3
4
5
6
7
8
9
"./src/index.js":
(function(module, __webpack_exports__, __webpack_require__) {

"use strict";
__webpack_require__.r(__webpack_exports__);
var _a__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__("./src/a.js");
console.log(_a__WEBPACK_IMPORTED_MODULE_0__["A"]);

})
1
2
3
4
5
6
7
8
9
10
"./src/a.js":
(function(module, __webpack_exports__, __webpack_require__) {

"use strict";
__webpack_require__.r(__webpack_exports__);
__webpack_require__.d(__webpack_exports__, "A", function() { return A; });
var _b__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__("./src/b.js");
console.log(_b__WEBPACK_IMPORTED_MODULE_0__["default"]);
const A = 'a';
}),
1
2
3
4
5
6
"./src/b.js":
(function(module, __webpack_exports__, __webpack_require__) {
"use strict";
__webpack_require__.r(__webpack_exports__);
__webpack_exports__["default"] = (B = 'b');
}),

这样拆开来分析就比较一目了然了,从这三个文件可以看出,在执行模块时除了执行本身模块内容外,还会执行__webpack_require__.r,__webpack_require__.d等函数,从打包生成的 main.js 的立即执行函数中找这几个函数的定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// define __esModule on exports
__webpack_require__.r = function(exports) {
if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
}
Object.defineProperty(exports, '__esModule', { value: true });
};
// define getter function for harmony exports
__webpack_require__.d = function(exports, name, getter) {
if(!__webpack_require__.o(exports, name)) {
Object.defineProperty(exports, name, { enumerable: true, get: getter });
}
};
// Object.prototype.hasOwnProperty.call
__webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };

__webpack_require__.r 函数是 webpack 针对不同模块加了不同的标记处理,因为是 import 引入的,给 exports 对象加上ES harmony规范的标记。
执行__webpack_require__.d(__webpack_exports__, "A", function() { return A; }); 实际上就是生成__webpack_exports__.A = A;

从入口的 index.js 分析,首先对 index.js 进行了 esModule 的模块化标记,因为 index.js 引入了 a.js,接着对 a 模块执行__webpack_require__, 打印出 a 模块的执行结果。a 模块同样进行了 esModule 标记,并且生成了 module.exports.A = A ,将 A 变量导出,index.js 打印出 A 的结果。因为 b.js 使用的是export default,webpack 处理后,会在 module.exports 中增加一个 default 属性。

至此,我们看到,webpack 的输出文件,将各个模块以参数的形式传递给 IIFE 函数,从入口文件开始递归解析依赖,在解析的过程中,分别对不同的模块进行处理,返回模块的exports。

React 数据更新 与 Immutable

Posted on 2019-09-17 | Comments:

8月份入职新公司,刚入职就参与了一个迭代频繁的项目,也开始真正使用 React 做业务项目。接手的一个模块需要处理深层数据,在这过程中也爬了很多坑,终于搞明白了 React 的数据更新机制。在此做一个总结。

React 渲染

最开始熟悉项目的时候发现,在组件 render 函数的地方打印 console.log,会打印很多次,也就是一个组件会调用很多次 render,这样肯定会频繁的触发 React 的 diff 比较进行 patch 更新,这就很奇怪了,因为 Vue 里,render 函数只有在初始化和依赖的数据发生变化时才会触发,难道 React 有什么不同?于是开始研究 React 的渲染机制,什么时候会进行 render 呢?来一张官网的生命周期图。

lifecycle

可以看到,除了第一次挂载会初始化进行一次渲染外,在 props 或 state 有任何一个改变时,会根据 shouldComponentUpdate 值来判断是否进行 render,普通的 component 的 shouldComponentUpdate 默认会返回 true ,可以通过手写 shouldComponentUpdate 判断是否真正需要重新渲染来提高性能,也可以通过继承 React.PureComponent 来实现,PureComponent 内部进行浅比较(shallowEqual),比较前后两次 state 和 props 是否相等(如果是值类型,就进行值比较,引用类型比较地址是否相同),如果相等就不去更新。 这就会引入一个问题:

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
class ListOfWords extends React.PureComponent {
render() {
return <div>{this.props.words.join(',')}</div>;
}
}

class WordAdder extends React.Component {
constructor(props) {
super(props);
this.state = {
words: ['marklar']
};
this.handleClick = this.handleClick.bind(this);
}

handleClick() {
// 这部分代码很糟,而且还有 bug
const words = this.state.words;
words.push('marklar');
this.setState({words: words});
}

render() {
return (
<div>
<button onClick={this.handleClick} />
<ListOfWords words={this.state.words} />
</div>
);
}
}

点击按钮时,会通过 push 方法添加一个单词,words 的引用地址没变,而 PureComponent 会进行浅比较,shoulComponentUpdate 会返回 false , 所以 ListOfWords 不会被重新渲染,这就是可变数据带来的问题。

不可变数据

所以避免该问题的方式就是使用不可变数据,不可变数据就是一旦对象创建就不可再修改,当需要改变时不可直接修改状态,需要通过生成一个新对象的方式来修改。

上面例子的的解决方式就是不去直接修改 props 或者 state,而是通过生成新的引用来替换

  • 原生写法 Object.assign()、concat 等
  • ES6 扩展运算符

上述两种方法是处理简单对象常用的方式,Object.assign 和 对象扩展运算符 都是对对象做了一个浅拷贝,所以如果在深层嵌套对象里,要去改变嵌套对象里面的值,使用上述两种方式并达不到预期的效果,借用 这个例子 来看(一定要点开看啊!)

1
2
3
4
5
6
7
8
9
10
11
state = {
user: {
id: 1,
name: "Cory",
address: {
city: "Kansas City",
state: "Kansas",
modified: new Date().toLocaleTimeString() // 现在需要改变这个值
}
}
};

user 是个深层对象,需要改变 modified 的值,对 user 的引用有三种组件,分别是 A 组件(React.Component)、B 组件(React.PureComponent)、C组件(React.Component,里面嵌套了Address 组件(React.PureComponent))。对 modified 的修改分别采用三种方式,看对应的渲染情况。

  1. Mutate 直接修改
1
2
3
4
5
mutate = () => {
const user = this.state.user; // still mutating user since just a ref!
user.address.modified = new Date().toLocaleTimeString();
this.setState({ user });
};
  • A 组件:因为 shouldComponent 都会返回 true,会重新渲染。
  • B 组件:因为 user 的引用地址没有变化, B 组件使用 PureComponent,会进行浅比较,所以B组件不会重新渲染。
  • C 组件:同 A 组件会重新渲染,但是因为 address 的引用地址没变, Address 组件使用的 PureComponent, 所以 Address 不会重新渲染。
  1. Shallow 浅拷贝
1
2
3
4
5
shallow = () => {
const user = { ...this.state.user };
user.address.modified = new Date().toLocaleTimeString();
this.setState({ user });
};
  • A 组件:重新渲染。
  • B 组件:重新渲染。
  • C 组件:同 A 组件会重新渲染,但是因为 address 的引用地址没变, Address 组件使用的 PureComponent, 所以 Address 不会重新渲染。
  1. Deep 深拷贝
1
2
3
4
5
6
7
8
deep = () => {
const user = {
...this.state.user,
address: { ...this.state.user.address }
};
user.address.modified = new Date().toLocaleTimeString();
this.setState({ user });
};
  • A 组件:重新渲染。
  • B 组件:重新渲染。
  • C 组件:重新渲染,子组件 Address 因为 address 引用地址改变也会重新渲染。

不提倡使用深拷贝的方式,他的代价是昂贵的,并且深拷贝会导致 React 进行不必要的渲染,因为嵌套的每个对象引用地址都改变了,引用这些数据的组件全部都会重新渲染。对于深层对象的处理,我们要做的只是拷贝已经改变的对象。有一些库例如 immutability-helper、immer、immutable-js 等都可以实现不可变数据结构。

immutable.js

immutable.js 是 Facebook 推出的能让开发者建立不可变数据的函数库,内部实现了一套完整的持久化数据结构,也就是说,对数据的所有的更新操作最后都会生成一个新的数据结构,原有结构保持不变,这也意味着所有的数据都是不可变的,有了这个限制前提,更新操作就有了很多优化的空间,例如更新一个深层节点的数据, Immutable 的实现原理如下

immutable

当需要更新某个节点数据时,只需要顺着链路更新分支上的节点,尽可能的复用现有的节点,这样既提升了性能,也降低了内存开销,immutable 的这个特点也称为结构共享。

其他

除了上面说的渲染性能优化,因为 Redux 设计是以几个原则为优先的:状态可追踪,可重复,可维护,不可变数据也是 Redux 运行的基础,因为有了不可变数据,当 store 发生变化时,任何时候都能记录变化之前和变化之后的状态,方便计算 diff,平时开发调试用的 chrome 插件就是利用了此机制进行追踪。

扁平化

在项目开发中,我们应该尽可能的减少使用深层数据结构,尽量将 store 组织的扁平化和范式化,扁平化的意思是:只要不存在“实体下面再挂实体”的现象,应该就可以认为是扁平。

React 与 Vue 在渲染上的不同

  • React 和 Redux 都提倡不可变性,更新需要维持不可变原则,Vue 不需要。
  • React 应用需要考虑优化机制,当某个组件发生变化时,会以该组件为根,重新渲染整个组件子树,所以需要尽可能的使用 PureComponent 和 shouldComponentUpdate 方法,同时使用不可变数据结构来使的组件更容易被优化。
  • Vue 采用依赖追踪机制,能精确的知道哪些组件需要重新渲染,不会存在过渡重渲染的性能问题,默认就是优化状态。
    React 渲染功能依赖 jsx,Vue 支持 jsx,但更多的使用 template 模板,这两个在性能上也有点区别,jsx 属于动态渲染,所有的 DOM 节点都是动态生成的,所以页面节点越多,DOM开销就会越大,并且无法根据初始状态进行优化,template在初始编译时,会根据节点类型找出静态节点并进行标记,数据变化时可以跳过这些静态节点的对比,避免进行无意义的 diff。

Javascript 面向对象、原型与继承

Posted on 2019-07-14 | Comments:

最近面试,有被问到关于原型链及继承的知识,于是系统的整理一下。

对象

编程语言的对象,成功的流派使用”类”的方式来描述对象。Javascript早期用『原型』的方式来描述对象。

对象的特点

  • 对象具有唯一标识性:即使两个完全一样的对象,也并非同一个对象。
  • 对象有状态:同一个对象可能处于不同的状态之下。
  • 对象有行为:对象的状态可能因为它的行为产生变迁。

唯一标识性:

1
2
3
var o1 = {a: 1};
var o1 = {a: 1};
console.log(o1 == 02); // false

状态和行为特征在 javascript 中统一抽象为”属性”。
除对象基本特征外,javascript 的对象具体高度的动态性,因为 javascript 赋予了使用者在运行时为对象添改状态和行为的能力。也就是说:

1
2
3
var o = {a: 1};
o.b = 2;
console.log(o.a o.b); // 1 2

可以在定义对象之后再去添加属性。
为了提高抽象能力,javascript 属性提供了数据属性和访问器属性(getter/setter) 两类。

创建对象

Javascript 通过 new 关键字来创建一个对象,new 关键字做了什么?

  • 创建一个对象
  • 将对象的原型(__proto__)指向构造函数的原型(prototype属性)
  • 将构造函数内部的 this 指向这个空对象,执行构造函数的逻辑
  • 如果构造函数内部没有返回对象,则返回创建的对象。

具体实现为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
function objectFactory(...args) {

// 取出第一个参数(构造器),并从参数中删除。
const Constructor = args.shift();

// 创建一个空对象,并将空对象的_proto_属性指向构造函数的 prototype 属性
// 不建议直接操作_proto_属性(obj.__proto__ = Constructor.prototype)
const obj = Object.create(constructor.prototype);

// 执行构造器, 将构造函数内的 this 指向为 obj
var result = Constructor.apply(obj, args);

// 如果构造器返回对象则返回这个对象,否则返回新建的对象。
return (typeof result === 'object' && result != null) ? result : obj;
};

// 使用方式
function Person(name) {
this.name = name
}
const person = new objectFactory(Person, 'season')
console.log(person)

原型及原型链

用 <>中的图来表示
prototype
总结下来:

  • 每个函数都有一个 prototype 指向其原型(构造函数.prototype === 原型)
  • 每个原型的 constructor 属性都指向该构造函数(原型.constructor === 构造函数)
  • 每个实例(对象)都有一个 __proto__ 属性指向该构造函数的原型(实例.__proto__ === 原型)

继承

原型链继承

关键点:子类原型继承父类的实例

1
Child.prototype = new Parent()

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function Child(name) {
this.name = name;
}
Child.prototype = new Parent()
function Parent(name) {
this.name = name;
this.list = [1, 2, 3];
}
Parent.prototype.getName = function() {
return this.name
}

var child1 = new Child('child1');
console.log(child1.getName()); // child1
child1.list.push(4);

var child2 = new Child('child2');
console.log(child2.getName()); // child2
console.log(child2.list); // [1, 2, 3, 4]

缺点:

  • 当父类构造函数有引用类型时,该引用类型会被所有子类所共享(修改 child1 的 list 属性时,chil2 也会修改)
  • 创建子类时,不能向父类构造函数传递参数

构造函数继承

关键点:在子类构造函数内调用父类构造函数方法,可以向父类传递参数。

1
2
3
4
function Child (args) {
// ...
Parent.call(this, args)
}

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function Child(name, args) {
this.name = name;
Parent.call(this, args)
}

function Parent(name) {
this.name = name;
this.list = [1, 2, 3];
}
Parent.prototype.getName = function() {
return this.name
}

var child1 = new Child('child1', 'parent1');
console.log(child1.getName()); // Uncaught TypeError: child1.getName is not a function
child1.list.push(4);
console.log(child1.list) // [1, 2, 3, 4]

var child2 = new Child('child2', 'parent2');
console.log(child2.list); // [1, 2, 3]

可以向父类传递参数,也解决了上述共享父类引用类型的问题
缺点:不能继承父类原型上的方法。(上述child1.getName()获取不到)

组合继承

关键点:结合前面两种方式,使用原型链实现对原型属性和方法的继承,通过借用构造函数实现对实例属性的继承。

1
2
3
4
5
6
7
function Child (args1, args2) {
// ...
this.args1 = args1
Parent.call(this, args2)
}
Child.prototype = new Parent()
Child.prototype.constrcutor = Child

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function Child(name, args) {
this.name = name;
Parent.call(this, args)
}
Child.prototype = new Parent();
Child.prototype.constrcutor = Child;
function Parent(name) {
this.name = name;
this.list = [1, 2, 3];
}
Parent.prototype.getName = function() {
return this.name
}

var child1 = new Child('child1', 'parent1');
console.log(child1.getName()); // parent1
child1.list.push(4);
console.log(child1.list) // [1, 2, 3, 4]

var child2 = new Child('child2', 'parent2');
console.log(child2.list); // [1, 2, 3]

缺点:会调用两次父类构造函数,Parent.call(this, args)会调用一次,Child.prototype = new Parent();还会调用一次。

原型式继承

原型式继承没有严格意义上的构造函数,他的想法主要是借助原型可以基于已有的对象创建新对象,也就是一个对象以另一个对象为基础,根据需求传入自己的属性。

1
2
3
4
5
function object(o) {
function F() {}
F.prototype = o;
return new F();
}

在 object 内部先创建了一个临时构造函数,将传入的对象作为构造函数的原型,返回临时构造函数的实例。
ECMAScript5 通过加入Object.create()方法规范化了原型式继承。
示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function object(o) {
function F() {}
F.prototype = o;
return new F();
}
var parent = {
name: 'parent',
list: [1, 2, 3]
}

var child1 = object(parent);
child1.name = "child1";
console.log(child1.name); // 'child1'
child1.list.push(4);
console.log(child1.list); //  [1, 2, 3, 4]

var child2 = object(parent);
console.log(child2.list); // [1, 2, 3, 4]

可以看到用 object 的方式,父类的引用类型共享的问题还是存在,因为 object 方法其实是做了一层浅拷贝,所以父类的引用类型始终会共享。
在不考虑构造函数,只想基于一个对象生成另一个对象的情况下,原型式继承是比较适用的。

寄生式继承

和原型式继承类似,也是通过创建一个封装继承过程的函数,在函数内部以某种方式增强对象,最后返回对象。

1
2
3
4
5
6
7
function createAnother(original){
var clone = object(original);
clone.sayHi = function(){ // 以某种方式来增强这个对象,给该对象增加一个 sayHi 方法
alert("hi");
};
return clone;
}

同上面原型式继承类似,在不考虑构造函数的情况,寄生式继承是一种有用的模式。

寄生组合式继承

因为组合式继承会调用两次构造函数,寄生组合式继承通过寄生式继承来继承超类的原型,避免在指定子类原型的时候调用父类构造函数。

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
function object(o) {
function F() {}
F.prototype = o;
return new F();
}
function inheritPrototype(subType, superType){
var prototype = object(superType.prototype);
prototype.constructor = subType;
subType.prototype = prototype;
}
function Parent(name) {
this.name = name;
this.list = [1, 2, 3];
}
Parent.prototype.getName = function() {
return this.name
}

function Child(name, args) {
this.name = name;
Parent.call(this, args); // 通过构造函数来继承属性
}
inheritPrototype(Child, Parent); // 通过寄生式继承来继承父类的原型


var child1 = new Child('child1', 'parent1');
console.log(child1.getName()); // parent1
child1.list.push(4);
console.log(child1.list) // [1, 2, 3, 4]

var child2 = new Child('child2', 'parent2');
console.log(child2.list); // [1, 2, 3]

使用 Object.create() 替代上述的 object();

1
2
Child.prototype = Object.create(Parent.prototype);
Child.prototype.constructor = Child;

ES6 继承

ES6 通过 extend 实现继承。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Parent {
constructor(){
this.name = 'parent';
this.list = [1, 2, 3];
}
getName() {
return this.name
}
}
class Child extends Parent {
constructor(){
super()
}
}
var child1 = new Child();
console.log(child1.name); // parent
console.log(child1.getName()) // parent
child1.list.push(4) // parent
console.log(child1.list) // [1, 2, 3, 4]

var child2 = new Child();
console.log(child2.name); // parent
console.log(child2.getName()) // parent
console.log(child2.list) // [1, 2, 3]

区别

  • ES5 实现继承的方式是:先构造子类的实例对象 this,再将父类方法添加进去(parent.apply(this))。
  • ES6 通过 extend 实现继承,先将父类的属性和方法添加到 this 上(先调用 super 方法),再调用子类的构造函数修改 this

ES6 继承实际上是 ES5 原型的语法糖,将上述继承通过 babel 在线编译工具进行转换,得到

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
// ... 省略一些方法定义
function _inherits(subClass, superClass) {
// 父类必须是函数并且不能为null
if (typeof superClass !== "function" && superClass !== null) {
throw new TypeError("Super expression must either be null or a function");
}
// 通过Object.create实现继承,第二个参数用来修复子类的 constructor
subClass.prototype = Object.create(superClass && superClass.prototype, {
constructor: {
value: subClass,
writable: true,
configurable: true
}
});
// 设置子类的 __proto__ 属性指向父类
if (superClass)
_setPrototypeOf(subClass, superClass);
}
function _setPrototypeOf(o, p) {
_setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) {
o.__proto__ = p; return o;
};
return _setPrototypeOf(o, p);
}
// 两个参数,一个参数是指向子类实例的this,另一个参数是 调用父类构造函数的返回值
function _possibleConstructorReturn(self, call) {
// 如果父类返回的是对象或者函数,则返回 父类构造函数生成的this(Parent.call(this)),否则返回子类的this
if (call && (_typeof(call) === "object" || typeof call === "function")) {
return call;
}
return _assertThisInitialized(self);
}

function _getPrototypeOf(o) {
_getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) {
return o.__proto__ || Object.getPrototypeOf(o);
};
return _getPrototypeOf(o);
}


var Parent =
/*#__PURE__*/
function () {
function Parent() {
_classCallCheck(this, Parent);

this.name = 'parent';
this.list = [1, 2, 3];
}

_createClass(Parent, [{
key: "getName",
value: function getName() {
return this.name;
}
}]);

return Parent;
}();

var Child =
/*#__PURE__*/
function (_Parent) {
// 通过 Object.create 实现继承
_inherits(Child, _Parent);

function Child() {
// 确保类是通过 new 作为构造函数调用而不是直接调用
_classCallCheck(this, Child);
// _getPrototypeOf(Child) 返回 子类的原型(父类构造函数),通过子类 this 执行父类构造函数的方法
return _possibleConstructorReturn(this, _getPrototypeOf(Child).call(this));
}

return Child;
}(Parent);

简化:子类首先执行_inherits(Child, _Parent);,内部通过Object.create建立子类与父类原型链关系,然后再通过调用Parent.call(this)。

『转』Linux 安装 node 和 npm

Posted on 2019-06-04 | Comments:

原文来自https://jpanj.com/2019/install-node-and-npm-on-linux/

网上介绍 Node 如何安装的文章数不胜数,但我还是决定自己写一篇记录一下,最主要的原因是网上的文章比较混乱,有的建议通过包管理工具安装,还有的让一步步编译源码来安装。

通过包管理工具安装的通常版本不会太新,通过源码安装的方式非常麻烦,还需要提前安装 gcc 之类的,只有极少部分良心博主介绍了通过二进制文件直接安装的方式,但操作上都不是特别规范。

网上已有的文章还有一个很严重的问题,就是没有考虑国内的网络环境,不管从 Node 官方下载源码包还是二进制包,都巨慢无比,所以我把已经下载好的包放在 CDN 上供自己和大家之后使用。同时我还提供了其他常用软件的安装包,如 Nginx,Java,Neo4j 等等,后边有机会列个清单出来,并准备长期维护更新版本。


下边进入正题:

我推荐以下操作在 /opt 目录下进行

下载压缩包

wget http://developer.jpanj.com/node-v10.15.3-linux-x64.tar.xz

解压为 tar 包

xz -d node-v10.15.3-linux-x64.tar.xz

解压

tar -xvf node-v10.15.3-linux-x64.tar

当前目录下软链一个 node 目录出来

这样做的好处是,未来升级版本非常方便,只需要更新这个软链就行

ln -s ./node-v10.15.3-linux-x64 ./node

通过软链接,将可执行程序放入系统环境变量的路径中

  • 查看当前系统中都有哪些环境变量路径
1
2
# echo $PATH
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/root/bin

可以看到我的列表中有:

  • /usr/local/bin
  • /usr/bin

大家约定成俗逻辑是:

  • /usr/bin下面的都是系统预装的可执行程序,会随着系统升级而改变。
  • /usr/local/bin 目录是给用户放置自己的可执行程序的地方

所以我推荐将软链放在 /usr/local/bin 目录下:

1
2
ln -s /opt/node/bin/node /usr/local/bin/node
ln -s /opt/node/bin/npm /usr/local/bin/npm

检查是否安装成功

1
2
3
4
[[email protected] ~]# node -v
v10.15.3
[[email protected] ~]# npm -v
6.4.1

Done

整理 Linux 常用命令

Posted on 2019-06-01 | Comments:

查看某个端口号被什么程序占用并杀掉此进程:

1
2
3
1. 查看某个端口的网络连接情况:`lsof -i:<port>`
2. 根据返回结果中的进程号检查进程名称 `ps -ef | grep <pid>`
3. 确认进程无误后杀掉该进程 `kill -9 <pid>`

scp 复制

1
2
3
4
# 文件
scp local_file [email protected]_ip:remote_folder
# 整个目录
scp -r local_folder [email protected]_ip:remote_folder

查看某个ip的端口号是否连通

1
telnet <ip> <port>

查看本机出口网络ip

1
curl ip.gs

压缩 tar.gz

1
tar -zcvf xxx.tar.gz xxx/

解压 tar.gz

1
tar -zxvf xxx.tar.gz

给一个文件添加可执行权限

1
sudo chmod +x xxx.sh

查看磁盘使用情况

1
df -h

查看内存使用情况

1
free -h

你不知道的Javascript(下卷)笔记

Posted on 2019-05-23 | Comments:

深入编程

Javascript 是解释型的,实际上是动态编译程序,然后立即执行编译后的代码。
编写代码是为了给开发者阅读的,代码应该解释为什么,而非是什么。

深入 Javascript

Javascript 中”假”值的列表:

  • “” (空字符串)
  • 0, -0, NaN (无效数字)
  • null、undefined
  • false

任何不在”假”值列表中的值都是”真”值,例如[],{}等。
在Jvascript中,闭包最常见的应用是模块模式。模块模式允许你定义外部不可见的私有实现细节(变量、函数),同时也可以提供允许从外部访问的公开API。

ES6及更新版本

transpiling:通过transpiling(transformation+compiling,转换+编译)的技术,将ES6代码转化为等价(或近似)的可以在ES5环境下工作的代码。
let+for

1
2
3
4
5
6
7
var funcs = [];
for(let i = 0; i < 5; i++) {
funcs.push(function() {
console.log(i);
})
}
funcs[3](); // 3

for循环头部的let i 不只是为for循环本身声明了一个i,而是为循环的每一次迭代都重新声明了一个新的i。这意味着loop迭代内部创建的闭包封闭的是每次迭代中的变量。

spread/rest

ES6引入的新的运算符…,通常称为spread/rest运算符,取决于在哪/如何被使用。

1
2
3
4
// spread
var a = [2,3,4];
var b = [1, ...a, 5];
console.log(b); // [1,2,3,4,5]

1
2
3
4
5
6
7
8
9
10
// rest
function foo(x, y, ...z) {
console.log(x, y, z)
}
foo(1,2,3,4,5) // 1 2 [3, 4, 5]
// 如果没有明明参数的话,会收集所有的参数
function foo(...args) {
console.log(args);
}
foo(1,2,3,4,5); // [1,2,3,4,5]

这种用法最好的一点是,为类数组 arguments 提供了非常可靠的替代形式。

箭头函数

箭头函数总是函数表达式,并不存在箭头函数声明,箭头函数是匿名函数表达式,它们没有用于递归或者事件绑定/解绑定的命名引用。
箭头函数的主要设计目的是以特定的方式改变this的行为特性。

1
2
3
4
5
6
7
8
9
var controller = {
makeRequest: function() {
var self = this;
btn.addEventListner('click', function(){
//
self.makeRequest(...);
}, false)
}
}

因为 this 是动态的,通过变量 self 依赖于词法作用域的可预测性。在箭头函数内部,this 绑定不是动态的,是词法的。这是箭头函数的主要设计特性。
除了词法 this,箭头函数还有词法 arguments,它们没有自己的 arguments 数组,而是继承自父层–词法 super 和 new.target 也是一样。

代码组织

模块

  • ES6 使用基于文件的模块,也就是一个文件一个模块。
  • ES6 模块的 API 是静态的。也就是说,需要在模块的公开 API 中静态定义所有最高层导出,之后无法补充。
  • ES6 模块是单例。
  • 模块的公开 API 暴露的属性和方法并不仅仅是普通的值或引用的赋值。他们是到内部模块定义中的标识符的实际绑定(几乎类似于指针)。
  • 导入模块和静态请求加载(如果还没加载的话)这个模块是一样的。

import 和 export 都必须出现在使用它们的最顶层作用域。举例来说,不能把 import 或 export 放在 if 条件中,它必须出现在所有代码块和函数的外面。
比较下面两段代码:

1
2
3
4
function foo() {
// ...
}
export default foo;

1
2
3
4
function foo() {
// ...
}
export { foo as default };

第一段代码中,导出的是此刻到函数表达式的绑定,而不是标识符foo,也就是说,export default… 接受的是一个表达式,如果之后 foo 赋了一个不同的值,模块导入得到的仍然是原来导出的函数,而不是新的值。
第二段代码,默认导出绑定实际上绑定到 foo 标识符而不是它的值,所以如果之后修改了 foo 的值,导入的值也会更新。

模块依赖环

import 语句使用外部环境(浏览器、Node.js等)提供的独立机制,来实际把模块标识符字符串解析成可用的指令,用于寻找和加载所需的模块,这个机制就是系统模块加载器。浏览器中环境提供的模块加载器会把模块标识符解析为URL,在Node.js这种服务器上就解析为本地文件系统路径。

类

// TODO

Docker 常用命令整理

Posted on 2019-05-22 | Comments:

image

列出所有镜像

1
docker images

删除所有 none 镜像

1
2
docker rmi $(docker images | grep "none" | awk '{print $3}')
docker images | grep none | awk '{print $3 }' | xargs docker rmi

生成镜像

1
docker image build -t {{containerName:version}} .

container

列出容器

1
2
docker ps 
docker ps -a

查看所有正在运行容器状态

1
docker stats

运行容器

1
docker run [-d] --name {{containerName}} [--restart=always] -p 8080:8080 {{imageName}}

启动容器

1
docker start {{containerName}}

重启容器

1
docker restart {{containerName}}

停止容器

1
docker stop {{containerName}}

停止所有容器

1
docker stop $(docker ps -aq)

删除所有已停止的容器

1
docker rm $(docker ps -aq)

强制停止并删除容器

1
docker rm -f {{containerName or containerID}}

进入交互式容器

1
docker exec -it {{containerName or containerID}} bash

查看容器日志

1
docker logs -f {{containerName}}
12…4

ruirui

35 posts
7 tags
© 2020 ruirui
Powered by Hexo v3.9.0
|
Theme – NexT.Muse v6.5.0