哇,我的天呐,好谢谢,谢谢大家,哇我的天啊,今天的气氛,真的跟我想象中非常不一样哎,今天今天很嗨,今天很嗨,颇有那个两天前凯达格兰大道的气氛

不好意思,不好意思,那个我今天写了一个蛮认真的讲稿,所以大家请容忍我几分钟这样子

老师,各位同学大家好,我是建中61届的校友曾博恩,

非常开心,去年呱吉已经致过辞了,让我今年可以以备胎的身份回到母校致辞,

我很开心看到各位现在坐的椅子跟当年一样不舒服,

我毕业是刚好十年之前,那十年前呢,我跟各位一样坐在台下,忍受着隔壁同学的汗臭,嘲笑着还要考指考的同学

心里想着到底还要多久才可以离开这个无聊的典礼

但我知道任何演讲当中呢,大家也只是再次确认自己已经相信的东西,再次拒绝自己不相信的东西,你们的人生不会因为这10分钟有任何改变,所以我只有一个目标就是尽量不要把它搞得太尴尬


OK,你们高中毕业了,耶~~~

如果你们是其他杂七杂八的学校的话,我就会说恭喜

但是因为这里是建中,所以我就不侮辱各位了,因为我知道高中毕业是99%的人都办得到的事情

高中毕业比起你们将来可以达成的辉煌事迹根本不足挂齿,如果你知道一个人可以穿越撒哈拉沙漠你不会因为他跑完100公尺而恭喜他


今年毕业典礼的主题是end game,然后据说毕筹会呢,希望我讲一些跟这个主题有关的内容,但你们知道通常我写一个10分钟的段子,是可以收好几万块的,所以

所以我就帮建中写了一个10分钟的段子,因为,因为我没有什么真的钱可以回馈给母校,

这个终局之战啊,把endgame特别拆成两个字,我在猜它意味着游戏已经结束了,然后高中毕业大家开始认真了,那这个题目呢,基本上非常的高傲,因为你们都已经考上建中了,然后说这只是游戏而已啊

我猜的啦,我猜的啦

你们有考虑过延平高中同学的感受吗?

好,很好,很好,非常有建国中学的精神,高傲哈

但这个说的不错,因为在高中毕业之后,能玩的时间越来越少了,你们只剩大学四年可以玩了,或者你如果玩的够凶的话七年

啊,不过,不过这种心境上面的转变,说不玩了,不嬉闹了,要拿出真本事来面对这个世界,这个转变的确是蛮重要的,因为学如逆水行舟不进则退,将来这股涌流只会越来越湍急,而如果你不够认真,退的够快的话,说不定还会沦落到当Youtuber

不要,不要,不要像我们一样


但我想要在这边勉励各位毕业生啊,千万不要忘记怎么玩,因为活在这个世界但不玩乐,就像当一个政治人物但不a钱一样

你不免要问自己说你到底在干嘛,所以Don’t end the game,never end the game,

快乐的感觉是我们的基因为了要继续生存,吩咐我们的大脑欺骗我们说,这个吃力不讨好的世界,还有唯一值得活下去的原因,

所以请继续玩乐,要玩但是不要瞎玩,在玩乐的过程当中,要知道游戏的哪一个元素,是会让你感受到快乐的,因为这个元素有很大的机会也会让别人感受到快乐,而掌握让自己跟周遭人都快乐的秘诀,就是成功的快速通行证

你可以用这个能力换得财富、人际关系,或是一个超棒的高潮

我已经开始感受到有一些师长开始惊慌失措了

我已经侦测到有些指责的眼神像当年陳為廷的鞋子一样,直接朝我射过来

让我顺便带到下一点,建中同学你们出去之后要要懂得批判,但是不要只会批判,要点出问题非常简单,但是难的是找到更好的替代方案

你可以觉得刚才的笑话不登大雅之堂,但在你找到比高潮更贴切的比喻之前,我会继续讲下去


OK,这样会讲不完,这样真的会讲不完

我不知道这个end game是不是有别的意思

因为我不知道你们是故意要拆成两个字,还是只是不知道这本来是一个字

如果是后者的话那你们英文程度真的蛮差的,那个就跟那种会把vs点写成v点s点的人一样

但如果是这种几率性很低的书写错误的话,我也准备了一些跟endgame有关的勉励

如果你们会下西洋棋的话你就会知道endgame是残局的意思,那残局并不是一个大舌头的高雄人称呼他们前市长的名字是,他是残局不是,不是残局,是西洋棋下到最后只剩几颗棋子的局面

好那各位要知道,endgame最重要的是你要如何将有限的资源最大化,

那要做到这一点呢,你必须知道你有哪些资源,然后也要知道你的目标是什么

唯一的困难点是,在这个技术科技性发展的现在,你很难知道呃自己的目标是什么

在10年前,我们还在用Sony Erikson玩德州飞人的时候,有谁想得到今天只要漏奶学猫叫就可以月入几十万

所以在一个不确定性这么高的环境里面,我的经验是做你认为可以让这个世界变得更好的东西,叫他热情也罢,使命感也好在,我的作品里面通常反应比较好的,都是我真正感受到对现况的不满,而自然而然随之产生的

换句话说,你如果想要拍一个pornhub的影片呢,不会有人看,但是你如果拍了一个超好的影片,一定会有人帮你上pornhub


OK,最后1/3

如果大家看过复仇者4的这个电影,大家应该都看过了吧,还没还还没看过的话就代表你不你不在乎,所以呃,我继续讲

如果你看过复仇者4这部电影的话,你就会知道复仇者获胜的关键不只是周严的计划,面对突发状况的应变能力,最重要的是他们有钢铁人

哦,对,就只有钢铁人一个人重要而已,没有钢铁人其他人都是废物

钢铁人非常重要,因为钢铁人展现了一个人靠着超高的IQ也可以变成超级英雄

那这个角色用来勉励建中的同学我想是再适合也不过

因为在建中真的很有复仇者联盟的感觉,大部分的的同学都有超乎常人的能力,那当然班上还是会有一些鹰眼

就是,你不知道他是怎么加入这个大家庭的,但他就在那里

稍安勿躁

我要讲的重点是,当年的鹰眼就是我,在建中的时期是我人生最平庸但是也最快乐的时期

你们现在可能感受不到,但是在任何方面都可以碾压自己的同学,被这样子的同学包围是一个很幸福的感觉

因为在任何领域都有学习的对象,然后任何事情你几乎都不需要担责任

我在美国住了6年,我英文是母语程度,但是我当年的英语话剧是男配角,然后因为男主角呢,他在纽西兰住了7年,然后他同时身兼导演跟编剧,完全被碾压

所以建中会是你们接触到智商跟才华最密集的地方,对,就是建中,不是台大,相信我,我读过台大,台大有一堆智障

你们的人生,即将面临一个再一次的重大变化,因为在进入建中之前,大概所有的同学都是一方之霸,你们都是班上最优秀的学生

那经过三年的洗礼之后,大部分的人都已经忘记那种感觉了,现在你们要离开建中,大家要各据山头,你会有一种从勇士队的Kevin Duran变成湖人队的LeBron James的感觉

好,我知道,这样子讲有点不尊重所谓的多元智能,所以我换个比喻好了

以后你身边的人的专长,可能比较不是智能这一块,可能比较是像好客这种

那我在这边,要勉励各位同学,要当以后这个团队当中的钢铁人,维持清晰的头脑,不要忘记了那个是你们的超能力,要相信队友,但是内心同时也不要偷偷希望下一个队友可以帮你传球,帮你接球,帮你擦屁股

因为以前没有带钥匙妈妈可以帮你开门,以前期末报告没有想法老师可以给你提示,但是上大学之后呢没有人会当你的靠山,你自己要当那个最后的靠山,当团队所有人都在推卸责任事情只做一半的时候,你要当那个書擋

你要知道it all ends here,每一件事情到你这边结束,在关键的时刻时刻把责任扛下来,然后把事情解决好

有点剧透,当然最好的情况就是你们才华洋溢到刚才的建议你们都不需要用到

但是我觉得如果你们在这边已经忍受我废话够多的话,那祝福你们将来有个美丽辉煌璀璨的未来,谢谢大家

閱讀全文 »

oiTerminal

简介

能够通过命令行 打oj比赛

开发进度

  • codeforces 已经完全支持 分析题目,上传,获取评测结果
  • 其它oj准备开工

对任意oj都支持本地的代码测试了

相关项目

cf-tool

idne

cfTerminal

spider

开发过程中的一些想法

首先 cfTerminal 是基于idne开发的

閱讀全文 »

原则

这本书我看了下来,主要是生活和工作团队建设的指导。本文是个人总结的一些东西,建议阅读原书。

生活原则

这一部分本身是在 xmind上写的,但是图太大pdf也很大,xmind提供了自动转换为markdown的功能,感谢!

拥抱现实,应对现实

閱讀全文 »

笔记

个人笔记!不是文档!大家 尽量减少个人文档行为,多做公共wiki和文档翻译!

感谢

https://zh.javascript.info

https://javascript.info

有精力的朋友还是建议读上面链接,如果它们的文档有帮助,也建议购买它们的 epub/pdf 作为支持

本文是基于我C++的熟练,和一定时间js使用后的阅读知识补充整理,章节分化基本和上面链接的内容对应

JavaScript 基础知识 2

函数 2.15

函数表达式 vs 函数声明 2.16

function a(){} vs a = function(){}

当 JavaScript 准备运行脚本或代码块时,它首先在其中查找函数声明并创建函数。我们可以将其视为“初始化阶段”。

在处理完所有函数声明后,执行继续。

所以以下可以而正常运行

1
2
3
4
5
sayHi("John"); // Hello, John

function sayHi(name) {
alert( `Hello, ${name}` );
}

声明还是有代码块的问题

閱讀全文 »

Ubuntu推出的 软件管理程序(类似debian系列是apt,arch系列是pacman),跨linux的

linuxs

Install

Arch

1
2
3
4
sudo pacman -S snapd

# enable the snapd systemd service:
sudo systemctl enable --now snapd.socket

Debian (Sid only)

1
sudo apt install snapd

Ubuntu >16.04默认安装了

Fedora

1
sudo dnf install snapd

Gentoo

Install the gentoo-snappy overlay.
OpenEmbedded/Yocto
Install the snap meta layer.

openSuSE

1
2
sudo zypper addrepo http://download.opensuse.org/repositories/system:/snappy/openSUSE_Leap_42.2/ snappy
sudo zypper install snapd

start using

1
2
3
4
5
6
7
snap find hello
sudo snap install hello
hello
snap list
sudo snap refresh <snap name>
sudo snap refresh
sudo snap remove

好消息是snap每日自动在后台更新

软件默认安装的是stable版本

如果要其他版本则通过指定channel

1
2
sudo snap refresh hello --channel=beta
sudo snap refresh hello --beta

可选的channel有 stable稳定,candidate稍早于稳定版的版本,beta未完成但是一个里程碑的版本,edge每日构建的冒烟测试

版本回滚

1
sudo snap revert hello 

进阶snap用法

首先申请个账号https://login.ubuntu.com/

1
sudo snap login

登陆以后,你再使用snap就不需要sudo了,除非你执行了snap logout

可以看snap使用历史记录,以及一个历史记录的细节

1
2
3
4
snap changes
snap change <changes ID>
snap watch <changes ID>
snap abort <changes ID>

snap安装的流程

下载->验证->mount->备份老版本的一些data到另一个文件夹->The security profiles are set and snapd checks which interfaces to connect to.->添加到$PATH->设置aliases->运行snap中定义的services-> 运行snap中定义的 configure钩子

类似的可以查看refresh remove revert的过程

离线安装例子

1
2
snap download nethack
ls

会下载本身的(.snap)包以及对应(.assert)文件

直接 snap install XXXX.snap会报错 cannot find signatures

解决方案(误,如果你勇敢或疯狂 想无视assertion的install)snap install --dangerous XXXX.snap

--devmode内涵了--dangerous

正确的安装方式

1
2
snap ack XXX.assert
snap install XXX.snap

关于assert(大概了解 account-key, account, snap-declaration, snap-revision 各个部分的元数组)

1
cat XXX.assert

查看已知(即是ack 过的)

1
snap known snap-declaration snap-name=nethack

所有stable channel的是confined,相对的devmode是unconfined,意味有访问系统的权限,所以要么这个开发者是你,要么是你信任的

interfaces

先安装chuck-norris-webserver

interfaces是可插拔插槽模式

1
snap interfaces

你可以看到,chuck-norris-webserver绑定到了nextwork-bind 上,

但有一些连接的Slot是空的(如camera),这种 是认为非安全的,访问用户的摄像机

执行chuck-norris-webserver.cli 你可以看到 有一些权限访问是被拒绝了的

授权camera

1
snap connect chuck-norris-webserver:camera :camera

我们再执行snap interfaces可以看到camera被连接上了

取消连接

1
snap disconnect chuck-norris-webserver:camera

例如,服务和cli请求访问不同的接口定义:

1
2
3
4
5
6
7
8
node-service:
command: bin/chuck-norris-app
daemon: simple
restart-condition: always
plugs: [network-bind]
cli:
command: bin/chuck-norris-cli
plugs: [network, camera]

读写network,使用camera

devmode 是用于开发者的不是 devil 23333

保证devmode的不会在stable里,也就不会被snap find到

你如果要安装,可以强制–beta,–devmode,前提是你信任它

安装后你会在snap list的notes中看到 它的channel

一种看法是把它看成container式的样子,在它上面运行的未授权的看不到根目录, 重定位

--classic强制使用 未重定位的包,这种classic的包和重定位的包不兼容,这种classic的包是能够读到宿主系统的目录的

综上这么多模式虽然你可能不是开发者也可以了解一下,主要还是为开发设计的,

  1. 先用classic模式开发
  2. 再用重定位dev模式
  3. 最后用严格模式包

snaps&services的可用性,日志,状态

  1. 当一个snap被安装,意味着,该snap命令在用户$PATH中可访问
  2. snap中包括的services都被启动,它们会跟随系统启动和关闭,而启动关闭,当它们crash可以自动重启

你可以snap enable,snap restart,snap disable控制这些服务

[原来我机器上有apache,然后安了这个用来讲例子的webserver,需要先stop apache再重启这个webserver]

ll /etc/systemd/system/snap*

cat /etc/systemd/system/snap.chuck-norris-webserver.node-service.service

所以snap用的还是系统的systemd的工具来管理服务,所以可以用传统的systemctl来管理

systemctl status -l snap.chuck-norris-webserver.node-service

这里在讲systemctl就不记了

同样可以查看日志sudo journalctl -fu snap.chuck-norris-webserver.node-service

管理snap配置

snap set

snap set chuck-norris-webserver title="I can bind Chuck Norris to my will"

snap set chuck-norris-webserver port=81

这应该是看具体应用提供的key和value范围了

snap set <snap-name> key1=value1 [key2=value2...]

与set对应的就是 get,可以获取值 snap get <snap-name> key

snap run --shell chuck-norris-webserver.cli

--shell会提供一个subshell

subshell的环境env | grep SNAP

直接cd 和 touch都会无权,能够操作的是 ls $SNAP(可读 不可写),touch $SNAP_USER_DATA/foo(读+写),$SNAP_USER_COMMON (读+写)

实际路径echo $SNAP_USER_COMMON => /home/didrocks/snap/chuck-norris-webserver/common

你通过snap set配置的值在$SNAP_DATA/config

snap info chuck-norris-webserver ,相对于snap find可以看stable以及非stable的channel

这里举例如果1.0.3(–beta)版本的revision是22(–edge)

然后得到了一些反馈,修复了一些issues

作者再提交,则还是1.0.3(–beta),但revision会变化可能就成了24(–edge)

snap find --section

创建发布snap TODO

总有人想搞统一 :-)

参考

https://tutorials.ubuntu.com/tutorial/basic-snap-usage

https://tutorials.ubuntu.com/tutorial/advanced-snap-usage

https://tutorials.ubuntu.com/tutorial/create-your-first-snap

感谢

https://scotch.io/tutorials/add-v-model-support-to-custom-vuejs-component

To let our component support v-model two-way binding, I stated earlier that the component needs to accept a value prop and emit an input event.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<template>
<input @input="handleInput" />
</template>

<script>
export default {
prop: ['value'],
data () {
return {
content: this.value
}
},
methods: {
handleInput (e) {
this.$emit('input', this.content)
}
}
}
</script>

个人其实建议阅读下方的参考链接zh.javascript.info,本篇整理的主要是基于个人已有经验进行提要,简化和再解释,不够完整

使用Promise 和简单解释

例子

1
2
3
4
5
6
let promise = new Promise(function(resolve, reject) {
// executor (生产者代码,"singer")
// 根据你自己的逻辑
// 调用resolve(value)
// 或者调用reject(error)
});

Promise的内部状态

  • state —— 最初是 “pending”,然后被改为 “fulfilled” 或 “rejected”,
  • result —— 一个任意值,最初是 undefined。

当 executor 完成任务时,应调用下列之一:

  • resolve(value) —— 说明任务已经完成:
    • 将 state 设置为 “fulfilled”,
    • sets result to value。
  • reject(error) —— 表明有错误发生:
    • 将 state 设置为 “rejected”,
    • 将 result 设置为 error。

状态图

总之,executor 应该完成任务(通常会需要时间),然后调用 resolve 或 reject 来改变 promise 对象的对应状态。

注意的是 状态一旦从pending变化后, 其它代码都会被忽略

let promise = new Promise(function(resolve, reject) {
resolve(“done”);

reject(new Error(“…”)); // 被忽略
setTimeout(() => resolve(“…”)); // 被忽略
});

then/catch 结果处理

then返回的是一个promise对象(准确的说是带有then方法的对象)

then(处理resolve的结果的函数,处理reject状态的函数)

then(处理resolve的结果的函数)

then(null,处理reject状态的函数) || .catch(处理reject状态的函数)

更确切地说,当.then/catch 处理器应该执行时,它会首先进入内部队列。JavaScript 引擎从队列中提取处理器,并在当前代码完成时执行 setTimeout(..., 0)

换句话说,.then(handler) 会被触发,会执行类似于 setTimeout(handler, 0) 的动作。

在下述示例中,promise 被立即 resolved,因此 .then(alert) 被立即触发:alert 会进入队列,在代码完成之后立即执行。

1
2
3
4
5
6
 // an immediately resolved promise
let promise = new Promise(resolve => resolve("done!"));

promise.then(alert); // 完成!(在当前代码完成之后)

alert("code finished"); // 这个 alert 会最先显示

因此在 .then 之后的代码总是在处理器之前被执行(即使实在预先解决 promise 的情况下)。通常这并不重要,只会在特定情况下才会重要。

Promise 链

首先因为then返回的是promise对象(准确说是带有then方法的对象),和原来的不同,从api角度看是一个新的对象,其中return改变了 结果值

和原来的不同,不会覆盖原来的

图意

然后处理程序的返回值里也可以 返回promise

1
2
3
4
5
6
7
8
new Promise(function(resolve, reject) {
setTimeout(() => resolve(1), 1000);
}).then(function(result) {
alert(result); // 1
return new Promise((resolve, reject) => { // (*)
setTimeout(() => resolve(result * 2), 1000);
});
})

所以假设loadScript = function(url){ return Promise ...; }

那么可以,来顺序加载脚本

1
2
3
4
5
6
7
8
9
10
loadScript("/article/promise-chaining/one.js")
.then(function(script) {
return loadScript("/article/promise-chaining/two.js");
})
.then(function(script) {
return loadScript("/article/promise-chaining/three.js");
})
.then(function(script) {
// work on one two three
});

链上的错误处理

promise 链在这方面做的很棒。当一个 promise reject 时,代码控制流程跳到链中最近的 rejection 处理程序。这在实践中非常方便。

隐式 try…catch ,如果在execute中出现 throw,那么会和 reject方法传递的类似,被promise丢入catch里,所以这里意味着非同步的代码不会被处理,例子

1
2
3
4
5
new Promise(function(resolve, reject) {
setTimeout(() => {
throw new Error("Whoops!");
}, 1000);
}).catch(alert);

同样 如果在过程中 throw 或发生错误,也就意味着返回一个rejected promise

在catch块中可以重新再抛出错误,也可以处理掉错误

1
2
3
XXX
.catch(e=>{/*处理*/})
.then(()=>{/*如果上面catch正常处理*/})

这里的建议是 继承实现你希望的Error类,在处理过程中控制错误对象的类型

如果没有错误处理,浏览器中 通过unhandledrejection捕获它

1
2
3
4
5
6
7
8
9
window.addEventListener('unhandledrejection', function(event) {
// the event object has two special properties:
alert(event.promise); // [object Promise] - the promise that generated the error
alert(event.reason); // Error: Whoops! - the unhandled error object
});

new Promise(function() {
throw new Error("Whoops!");
}); // no catch to handle the error

js+promise+cache example

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function loadCached(url) {
let cache = loadCached.cache || (loadCached.cache = new Map());

if (cache.has(url)) {
return Promise.resolve(cache.get(url)); // (*)
}

return fetch(url)
.then(response => response.text())
.then(text => {
cache[url] = text;
return text;
});
}

之后使用loadCached(url).then(…)

Promise.all

1
2
3
4
5
Promise.all([
new Promise((resolve, reject) => setTimeout(() => resolve(1), 3000)), // 1
new Promise((resolve, reject) => setTimeout(() => resolve(2), 2000)), // 2
new Promise((resolve, reject) => setTimeout(() => resolve(3), 1000)) // 3
]).then(alert); // 1,2,3 when promises are ready: each promise contributes an array member

在 3 秒之后被处理,然后它的结果就是一个[1, 2, 3]数组:

它们的相对顺序是相同的。尽管第一个 promise 需要很长的时间来解决,但它仍然是结果数组中的第一个。

这里可以看到,不同处理之间是并行的,结果位置是对应插槽的

实例,并行获取资源,统一等待结束

1
2
3
4
5
6
7
8
9
10
11
12
13
14
let urls = [
'https://api.github.com/users/iliakan',
'https://api.github.com/users/remy',
'https://api.github.com/users/jeresig'
];

// map every url to the promise fetch(github url)
let requests = urls.map(url => fetch(url));

// Promise.all waits until all jobs are resolved
Promise.all(requests)
.then(responses => responses.forEach(
response => alert(`${response.url}: ${response.status}`)
));

如果任何 promise 为 rejected,Promise.all 就会立即以 error reject。重要的细节是 promise 没有提供 “cancel” 或 “abort” 执行方法。因此,其他 promise 会继续执行,并最终为 settle,但它们的结果会被忽略。

所以建议的是,如果要使用all,那么在每个处理内部把错误处理掉,这样就不会单个的错误导致整个promise结束

1
2
3
4
5
Promise.all(
fetch('https://api.github.com/users/iliakan').catch(err => err),
fetch('https://api.github.com/users/remy').catch(err => err),
fetch('http://no-such-url').catch(err => err)
)

原始

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
let urls = [
'https://api.github.com/users/iliakan',
'https://api.github.com/users/remy',
'https://api.github.com/users/jeresig'
];

// make fetch requests
Promise.all(urls.map(url => fetch(url)))
// map each response to response.json()
.then(responses => Promise.all(
responses.map(r => r.json())
))
// show name of each user
.then(users => { // (*)
for(let user of users) {
alert(user.name);
}
});

改进

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
let urls = [
'https://api.github.com/users/iliakan',
'/',
'http://no-such-url'
];

Promise.all(
urls.map(url => fetch(url).catch(err => err))
)
.then(responses => Promise.all(
// if it's an error then pass on
// otherwise response.json() and catch errors as results
responses.map(r => r instanceof Error ? r : r.json().catch(err => err))
))
.then(results => {
alert(results[0].name); // Ilya Kantor
alert(results[1]); // SyntaxError: Unexpected token < in JSON at position 0
alert(results[2]); // TypeError: failed to fetch (text may vary)
});

Promise.race

相对于Promise.all类似,区别是只会等最快的完成

async/await

更舒适的方法使用promise

async

放在函数前,总会返回promise,如果不是则会把函数封装成promise

await

await 关键字使 JavaScript 等待,直到 promise 得到解决并返回其结果。

不能在常规函数中使用 await,await 在顶层代码中无效, 我们需要将 await 代码封装在一个async 函数中

await + promise/thenable函数

结果

正常的话 返回结果,否则抛出error

总结

img

ref

【翻译】Promises/A+规范

Promise Tutorial

Promise chaining

本文对应版本 b58d3d6a6426e901175a04bf6dcf206561cc82f5

获得代码

1
2
3
4
git init
git remote add origin https://github.com/vuejs/vuex.git
git fetch origin
git checkout b58d3d6a6426e901175a04bf6dcf206561cc82f5

总览

是个啥,状态管理工具

先了解如何用

前置知识

https://cn.vuejs.org/v2/guide/mixins.html

https://cn.vuejs.org/v2/guide/plugins.html

目录

1
2
3
4
5
6
7
8
9
10
11
12
13
./
├── helpers.js 简化 一些映射代码的编写,建议最后看【以下文档写的是按照我的阅读顺序写的】
├── index.esm.js 为了注入Vue
├── index.js 为了注入Vue
├── mixin.js 为了注入Vue
├── module
│   ├── module-collection.js 模块化的树状结构,主要对原来json配置的改为内部的实现结构
│   └── module.js 模块化的实现 单个模块
├── plugins
│   ├── devtool.js 辅助
│   └── logger.js 辅助
├── store.js 核心实现,实现commit,dispatch 等,对module分析结果 进行再解构,让访问简洁,同时做一些 访问保护检测
└── util.js 单纯工具与vuex 关系性极小

依赖(表面的)

文件 import 依赖
mixin
util
helpers
store.js 依赖见下方
index.js/index.esm.js store.js & helpers.js
plugins/devtool
plugins/logger util:deepCopy
module/module util:forEachValue
module/module-collection module:Module , util:{ assert, forEachValue }

之所以说是表面的,实际上比如logger是有调用store里面的方法的,比如subscribe

代码阅读

index/index.esm.js

主要是方法导出

1
2
import { Store, install } from './store'
import { mapState, mapMutations, mapGetters, mapActions, createNamespacedHelpers } from './helpers'

mixin.js

主要是按照vue的mixin规则,根绝vue版本,向vue里注入 vuex,也就是$store

规则是,有this.$options.store则 this.$store= $options.store/store() 根据是不是函数

没有的话,用this.$options.parent.$store

1
2
3
4
5
6
7
8
9
10
11
function vuexInit () {
const options = this.$options
// store injection
if (options.store) {
this.$store = typeof options.store === 'function'
? options.store()
: options.store
} else if (options.parent && options.parent.$store) {
this.$store = options.parent.$store
}
}

util.js

1
2
3
4
5
6
7
8
9
10
11
export function find (list, f) //list中f函数返回true的第一个值

export function deepCopy (obj, cache = []) // 带有处理指针循环结构的 deepCopy

export function forEachValue (obj, fn) // 遍历obj的所有k-v

export function isObject (obj)

export function isPromise (val)

export function assert (condition, msg)

plugins/

devtool

目测是一些调试相关的钩子挂载,TODO

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
const target = typeof window !== 'undefined'
? window
: typeof global !== 'undefined'
? global
: {}
const devtoolHook = target.__VUE_DEVTOOLS_GLOBAL_HOOK__

export default function devtoolPlugin (store) {
if (!devtoolHook) return

store._devtoolHook = devtoolHook

devtoolHook.emit('vuex:init', store)

devtoolHook.on('vuex:travel-to-state', targetState => {
store.replaceState(targetState)
})

store.subscribe((mutation, state) => {
devtoolHook.emit('vuex:mutation', mutation, state)
})
}

logger

嗯 第一行 // Credits: borrowed code from fcomb/redux-logger

借来的代码,依赖上 只用了util的deepCopy

基本上是输出 state状况用于调试的

module/

module

1
2
3
4
5
6
7
8
Module:
constructor (rawModule, runtime) {
this.runtime = runtime
this._children = Object.create(null)
this._rawModule = rawModule
const rawState = rawModule.state
this.state = (typeof rawState === 'function' ? rawState() : rawState) || {}
}

child 操作:对this._children的增删

update(rawModule):对namespaced,actions,mutations,getters 有则覆盖的更新

1
2
3
4
5
6
7
8
9
10
11
12
update (rawModule) {
this._rawModule.namespaced = rawModule.namespaced
if (rawModule.actions) {
this._rawModule.actions = rawModule.actions
}
if (rawModule.mutations) {
this._rawModule.mutations = rawModule.mutations
}
if (rawModule.getters) {
this._rawModule.getters = rawModule.getters
}
}

除此以外就是 在该class上 foreach再封装了

1
2
3
4
forEachChild (fn) 
forEachGetter (fn)
forEachAction (fn)
forEachMutation (fn)

module-collection

export:

1
2
3
4
5
export default class ModuleCollection {
constructor (rawRootModule) {
// register root module (Vuex.Store options)
this.register([], rawRootModule, false)
}

这边一个runtime有啥用,这里传的false,下面默不传是true,在Module里只是存一下,TODO

function makeAssertionMessage // 生成 assert错误message

使用

1
2
3
4
5
6
7
8
9
10
11
const functionAssert = {

const objectAssert = {

const assertTypes = {
getters: functionAssert,
mutations: functionAssert,
actions: objectAssert
}

function assertRawModule (path, rawModule) {

来检测是否是个合法的RawModule

这里实现可以看到,rawModule也可以不含getters/mutations/actions,如果含有则需要分别是function/function/object(function / obj.handler == function)

然后发现一个有点意思的东西,之前没看过源码还不知道, process.env.NODE_ENV !== 'production'才会assert

也就是开发环境才会assert,产品环境去掉

get:按照 从this.root开始,按照上面设计的Module Class的层级访问, this.root._children[path[0]]._children[path[1]]...

解释register

1
register (path, rawModule, runtime = true) {

靠外部传来的rawModule,作用是把 rawModule中描述的层级关系的modules,递归的解析成为内部实现的Module,这里的path只是配合内部实现的get,方便查找树状上的Module,是临时生成的一个传递变量

这里也可以看到,实现过程中重名的话是后者覆盖前者.

unregister (path) {//通过path定位 也就是上面get的办法,然后删除,需要满足 路径上的runtime都为true TODO

getNamespace (path) {

看起来像是 path.join('/')实际上,是只会join有namespaced的每一层

update (rawRootModule) / function update (path, targetModule, newModule) {

这两个update的作用就是在已经建立Module树上,进行更新,其中如果遇到树的结构不同则忽略掉,更新的过程不会变更树结构,只会对namespaced、actions、mutations、getters进行替换(更新)

helpers

export:

1
2
3
4
5
6
export const createNamespacedHelpers = (namespace) => ({
mapState: mapState.bind(null, namespace),
mapGetters: mapGetters.bind(null, namespace),
mapMutations: mapMutations.bind(null, namespace),
mapActions: mapActions.bind(null, namespace)
})

function normalizeMap (map) 把array,obj转化成[{'key':,'val':},...]

function normalizeNamespace (fn) { // 函数参数预处理(namespace,map) -> fn(namespace,map)

function getModuleByNamespace (store, helper, namespace) { return store._modulesNamespaceMap[namespace]

上面封了一层以后,每个map××× 被export出的都是单参数了

看了具体实现

1
2
3
4
mapState: .vuex=true {支持devtools}
mapMutations:
mapActions:
mapGetters: .vuex=true {支持devtools}

除了getter,其它都是有namespace用namespace所指的,没有就用全局的this.$store里的

getter 特殊在全是在this.$store.getters[]里,如果有namespace则是namespace+val构成新的val

Store

最后,最大的一个,505行

1
2
3
4
import applyMixin from './mixin'
import devtoolPlugin from './plugins/devtool'
import ModuleCollection from './module/module-collection'
import { forEachValue, isObject, isPromise, assert } from './util'

export:

install && Class Store

先看install,也就是调用applyMixin(Vue)进行注入

Store上这里分析源码,省略一些错误判断的解释了,只要说说逻辑

Store:

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
constructor (options = {}) {
const {
plugins = [],
strict = false
} = options

// store internal state
this._committing = false
this._actions = Object.create(null)
this._actionSubscribers = []
this._mutations = Object.create(null)
this._wrappedGetters = Object.create(null)
this._modules = new ModuleCollection(options) // 这里调用前面实现的 递归解析options中的modules
this._modulesNamespaceMap = Object.create(null)
this._subscribers = []
this._watcherVM = new Vue() // TODO 这个是什么用

// bind commit and dispatch to self // 这一段没有看懂,是为了兼容哪个版本的js吗,这两个本身已经实现了在下面了啊 怎么还要再绑定一次 TODO
const store = this
const { dispatch, commit } = this
this.dispatch = function boundDispatch (type, payload) {
return dispatch.call(store, type, payload)
}
this.commit = function boundCommit (type, payload, options) {
return commit.call(store, type, payload, options)
}

// strict mode //控制是否启用StrictMode store._vm.$watch(function () { return this._data.$$state }, 严格要求禁止在mutation handler以外的地方修改_data.$$state

实现原理是 利用vue进行watch

在允许的函数修改的时候修改`_committing 为 true` 环绕 见`_withCommit`

当watch到修改时,判断是否`_committing=true`这样就可以判断是否被外部修改

this.strict = strict

const state = this._modules.root.state // 获取的是 生成的module的根部的state

// 递归初始化 root module 以及所有子modules.
// 并且收集所有module getters mutation等 到store._下面 比如`_wrappedGetters`
installModule(this, state, [], this._modules.root)

// 初始化 store._vm , 作为回调的使用
// (also registers _wrappedGetters as computed properties)
resetStoreVM(this, state)

// 使用插件
plugins.forEach(plugin => plugin(this))

const useDevtools = options.devtools !== undefined ? options.devtools : Vue.config.devtools
if (useDevtools) {
devtoolPlugin(this)
}
}

function unifyObjectStyle (type, payload, options) { //感觉是特殊处理type的情况 返回{type,payload,options}

makeLocalContext(store,namespace,path)

如果没有namespace 就用root上的

如果有则建立 本地化 的 dispatch commit getters 和 state

建立的实现过程 一个是调用unifyObjectStyle处理type的特殊情况,另一个就是给type 加上namespace

getters和state,使用defineProperties写到local上,注释说是因为它们会被vm更新,应该lazily的获取

其中getters的makeLocalGetters的原理是 namespace对比,和从store.getters[type]中读取

makeLocalContext的结果会写入到module.context

installModule

如果当前路径有namespace, 那把它丢到store._modulesNamespaceMap[namespace] 和之前的获取对应

有点没看懂 set state,按理说 在 new ModuleCollection(options)就应该已经 在 new Module的部分设置了state,这里是为了触发Vue.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
27
28
29
module.forEachMutation((mutation, key) => {
const namespacedType = namespace + key
registerMutation(store, namespacedType, mutation, local)
// 简单的讲 就是把 所有mutations压入 `store._mutations[namespace+mutationname],之所以是压入,是因为 可能出现多个相同的 namespace+mutationname
})

module.forEachAction((action, key) => {
const type = action.root ? key : namespace + key
const handler = action.handler || action
registerAction(store, type, handler, local)
// 入口参数和上面还是类似,这个是塞入 store._actions
// 但是看了源码才知道,action的交付的第二个参数
// {
// dispatch: local.dispatch,
// commit: local.commit,
// getters: local.getters,
// state: local.state,
// rootGetters: store.getters,
// rootState: store.state }

})

module.forEachGetter((getter, key) => {
const namespacedType = namespace + key
registerGetter(store, namespacedType, getter, local)
// 入口参数和上面还是类似,这个是塞入 store._wrappedGetters
})

并对所有子module installModule()

整个install就是把 构建出的module 拆出所有的 操作函数

resetStoreVM

store._vm = new Vue({data:{$$state:state},computed}) 其中 computed是 上面 提取出的_wrappedGetters改动的,改动出的computed将 有和getters相同的keys,区别是 它会从store._vm[key]的方式去读数据

在这一步设置_vm时,临时设置Vue.config.silent

如果原来有._vm则调用销毁

vuex API

至此 内部的基本结构就结束了,来看看调用api对应的实现

先看 官网的图 VUEX

store.state.xxx

1
2
3
get state () {
return this._vm._data.$$state
}

commit(type,payload,options)

这里实现的过程,通过解构出的this._mutations[type]得到type对应的mutations 的函数句柄,

然后调用有withcommit包围的,进行 函数 执行

所有执行以后,对所有_subscribers 通知(mutation,new state) , 简单的订阅模式

1
2
3
grep -r "subscribe" *
plugins/logger.js: store.subscribe((mutation, state) => {
plugins/devtool.js: store.subscribe((mutation, state) => {

dispatch (type, payload)

同理 通过解构出的this._actions[type]获得actions

区别是 action的订阅者 可以指定 before 和 after,分别会在action发生前后调用,

subscribeAction (fn) 如果fn是函数则 默认变为订阅action发生前 ,否则fn应该是类似{'before':()=>{},'after':()=>{}}的格式

关于订阅的调用,目前只在测试里看到有,其它部分没有调用

除了环绕的订阅以外,就是执行对应的action,entry,如果是多个 调用Promise.all(),如果是一个直接entry0

剩余的

剩余的 hotUpdate,replaceState等 应该属于调试时使用的不属于核心功能了,watch 基本是靠的Vue的$watch

总结

  1. 整个源码阅读完后,才发现原来根本没有用到module这一部分,甚至都不知道,看来是没仔细读文档,原来这一块已经实现好了。
  2. 从Module部分可以看到,实现rawModule相关 过程是 加了一层套把rawModule套了一层,保护了树的结构,做到期望内的更新
  3. 潜在bug? 从外部传入rawModule 的第一次构建是 _rawModule=rawModule,而在后面update的过程中是修改_rawModule的字段,可能导致 期望外的修改? 不过根据代码,这一部分只会发生在hotUpdate

个人其它收获

  1. 实际实现的代码 除了实现想法外还有很多细节,这些细节感觉也蛮庞大?的
  2. 这样path的写法? 省一些指针?感觉每次get代价会大一些
  3. reduce 的用法(http://www.runoob.com/jsref/jsref-reduce.html)
  4. bind的用法(https://blog.csdn.net/kongjunchao159/article/details/59113129)
  5. es6 destructuring (https://www.deadcoderising.com/2017-03-28-es6-destructuring-an-elegant-way-of-extracting-data-from-arrays-and-objects-in-javascript/)
  6. get,defineProperties , 都是在调用时才计算,区别是get在原型上,而defineProperties 是在实例上(https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/get#Smart_self-overwriting_lazy_getters)
  7. 同时 其中实现,有些注释错误,有些函数的默认参数没有设置 =false,不过个人对js不太熟,要是c++的强迫症话 感觉还是要加的
  8. getters ,函数->计算属性
  9. 普通对象变化不会影响刷新,需要有getter和setter的,简单的方法就是用 new Vue({data:{xxx}});来包裹会自动加上 getter和setter
  10. 发布订阅
  11. strict通过 包裹限制 只有mutation改动数据, 但建议仅在开发时使用,在发布时为了性能不使用strict

其他:

感觉要去看看Promise的源码 以及 Vue的源码了

TODO

Module 里的runtime有啥用

重复注册state、mutation等等? 有文章说是 因为module的原因,

modules使用

modules{
模块名:{
state{
x:1,
y:1
}
},
模块名2:{

}
}

模块内的mutations,getters,actions 不同层级都会被调用!? 都会被合并到根上

所以要做的是方法映射出来,但是 把每个方法的所属模块的state绑定过去

但是 只有mutations和actions是数组,getters是由 defineProperties来的所以只会有一个

TODO

namespace:true 可以让模块化,不让actions等绑定在global上 通过module/module路径访问,实现就是 根下的 map[ 拼接的module namespace路径实现的]

registerModule 动态注册模块

1
2
3
4
5
6
registerModule (path, rawModule, options = {}) {

this._modules.register(path, rawModule)
installModule(this, this.state, path, this._modules.get(path), options.preserveState)
// reset store to update getters...
resetStoreVM(this, this.state)

store.subscribe() vuex中间件?? ,也是发布订阅模式

注册的是 和 函数指针放入数组,返回移出数组的函数,这个和vue-router里的registerHooks一样

commit后

1
2
3
4
5
6
7
8
commit里
this._withCommit(() => {
entry.forEach(function commitIterator (handler) {
handler(payload)
})
})
// 提交后 调用 所有订阅
this._subscribers.forEach(sub => sub(mutation, this.state))

参考:https://blog.csdn.net/qq_38128179/article/details/85273522

父组件

1
2
3
4
5
<Child>
<span slot="header">我是header</span>
<span slot="footer">我是footer</span>
<span slot="abcdef">我是abcdef</span>
</Child>

Child

1
2
3
4
5
6
7
8
<template>
<div>
<slot name="abcdef"></slot>
<slot name="header"></slot>
<h1>我是子组件</h1>
<slot name="footer"></slot>
</div>
</template>

父组件中没有标注slot=""的会被插入到 没有标注的子组件的<slot></slot>


父组件反过来解构slot-scope

Child

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<template>
<div>
<slot :data="data"></slot>
</div>
</template>
<script>
export default {
data() {
return {
data: ['Neinei','Laoba','Demi','Feiyan']
}
}
}
</script>

父组件 这里的.data 和子组件的:data对应

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<template>
<!-- 循环数据列表 -->
<Child>
<div slot-scope="msg">
<span v-for="item in msg.data">{{item}} </span>
</div>
</Child>

<!-- 直接显示数据 -->
<Child>
<div slot-scope="msg">
<span>{{msg.data}} </span>
</div>
</Child>

<!-- 不使用其提供的数据, 作用域插槽退变成匿名插槽 -->
<Child>
<div>我是插槽</div>
</Child>
</template>
0%