Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
771bb9f914 | ||
|
|
96f850e29f | ||
|
|
349e7df2b6 | ||
|
|
b198d57cf6 | ||
|
|
03b8840291 | ||
|
|
60899c0bf8 | ||
|
|
648e113e66 | ||
|
|
61a5e74908 | ||
|
|
d2273e72f6 | ||
|
|
386b605c32 | ||
|
|
b60cca25d7 | ||
|
|
68e6c23f82 | ||
|
|
a9d2c1d47a | ||
|
|
cc364c4b2f | ||
|
|
f65b11ea6b | ||
|
|
35458365c6 | ||
|
|
dde997ac27 | ||
|
|
53ae9e077e | ||
|
|
30bdcf13dd | ||
|
|
12b1b02539 | ||
|
|
2a92487a5e | ||
|
|
b6487f8754 | ||
|
|
7a8e1b25dd | ||
|
|
44683fa1dc | ||
|
|
3a4b82bc76 | ||
|
|
4f2ed7b3cb | ||
|
|
a72795b099 | ||
|
|
dea2acc384 | ||
|
|
74f539cc34 | ||
|
|
1947b9c08c | ||
|
|
e84c85cc1d | ||
|
|
170c0f4525 | ||
|
|
c836848228 | ||
|
|
7b87d5c3c4 | ||
|
|
add5bdd01e | ||
|
|
0633764a44 | ||
|
|
fcc183d523 | ||
|
|
627db8a249 | ||
|
|
b41530b37c | ||
|
|
2062b652dd | ||
|
|
365997863e | ||
|
|
ab6978da4a | ||
|
|
f37d496f1b | ||
|
|
3bf34602b4 | ||
|
|
274ffc0a9f | ||
|
|
72e8b69dd7 | ||
|
|
62ac1d76b9 | ||
|
|
e537efe7bd | ||
|
|
f1871f59e5 | ||
|
|
5d08eabde3 | ||
|
|
7e79c60a12 | ||
|
|
9c051f6ab5 | ||
|
|
065fab1546 |
@@ -164,11 +164,11 @@ async function mount() {
|
||||
|
||||
```javascript
|
||||
async function mount() {
|
||||
const result = await Promise.all(
|
||||
const result = await Promise.all([
|
||||
fetch('a.json'),
|
||||
fetch('b.json'),
|
||||
fetch('c.json')
|
||||
);
|
||||
]);
|
||||
|
||||
render(...result);
|
||||
}
|
||||
|
||||
@@ -96,7 +96,7 @@
|
||||
|
||||
最后考察候选人的发展潜力与工作态度,我们一般通过询问简单的算法问题,进一步了解候选人是否对技术真正感兴趣,而不只是对前端工程感兴趣。同时,算法问题也考察候选人解决抽象问题的能力,或者让候选人设计一个组件,通过对组件需求的不断升级,考察候选人是否能及时给出解决方案。
|
||||
|
||||
最后时工作态度,首先会考察人品,对不懂的知识点装懂是违背诚信的行为,任何团队都不会要的。同时,**不正视自己技术存在的盲点,将是技术发展的最大阻碍**。不过这里也不怕被候选人套路,如果全部都回答不懂那也不用考虑了。
|
||||
最后是工作态度,首先会考察人品,对不懂的知识点装懂是违背诚信的行为,任何团队都不会要的。同时,**不正视自己技术存在的盲点,将是技术发展的最大阻碍**。不过这里也不怕被候选人套路,如果全部都回答不懂那也不用考虑了。
|
||||
|
||||
# 3 总结
|
||||
|
||||
|
||||
@@ -98,7 +98,7 @@ foo();
|
||||
|
||||
我们谈到了一些意外情况下定义的全局变量, 代码中也有一些我们明确定义的全局变量. 如果使用这些全局变量用来暂存大量的数据, 记得在使用后, 对其重新赋值为 null.
|
||||
#### 2. 未销毁的定时器和回调函数
|
||||
在很多库中, 如果使用了观察着模式, 都会提供回调方法, 来调用一些回调函数. 要记得回收这些回调函数. 举一个 setInterval的例子.
|
||||
在很多库中, 如果使用了观察者模式, 都会提供回调方法, 来调用一些回调函数. 要记得回收这些回调函数. 举一个 setInterval的例子.
|
||||
```javascript
|
||||
var serverData = loadData();
|
||||
setInterval(function() {
|
||||
@@ -174,4 +174,4 @@ JS 这类高级语言,隐藏了内存管理功能。但无论开发人员是
|
||||
|
||||
|
||||
|
||||
参考文章: [MDN 的内存管理介绍](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Memory_Management)
|
||||
参考文章: [MDN 的内存管理介绍](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Memory_Management)
|
||||
|
||||
@@ -94,7 +94,7 @@ const incrementAsync = async count => {
|
||||
|
||||
### 将 action + reducer 改为两种 action
|
||||
|
||||
redux 抽象的 action 与 reducer 的指责很清晰,action 负责改 store 以外所有事,而 reducer 负责改 store,偶尔用来做数据处理。这种概念其实比较模糊,因为往往不清楚数据处理放在 action 还是 reducer 里,同时过于简单的 reducer 又要写 action 与之匹配,感觉过于形式化,而且繁琐。
|
||||
redux 抽象的 action 与 reducer 的职责很清晰,action 负责改 store 以外所有事,而 reducer 负责改 store,偶尔用来做数据处理。这种概念其实比较模糊,因为往往不清楚数据处理放在 action 还是 reducer 里,同时过于简单的 reducer 又要写 action 与之匹配,感觉过于形式化,而且繁琐。
|
||||
|
||||
重新考虑这个问题,我们只有两类 action:`reducer action` 与 `effect action`。
|
||||
|
||||
|
||||
@@ -87,7 +87,7 @@ function getTokenBlockComment(restStr: string) {
|
||||
```typescript
|
||||
while (sqlStr) {
|
||||
token =
|
||||
getTokenWhitespace(sqlStr, token) | getTokenBlockComment(sqlStr, token);
|
||||
getTokenWhitespace(sqlStr, token) || getTokenBlockComment(sqlStr, token);
|
||||
|
||||
sqlStr = sqlStr.substring(token.value.length);
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
重回 “手写 SQL 编辑器” 系列。这次介绍如何利用缓存优化编译器执行性能。
|
||||
|
||||
可以利用 **Frist 集** 与 **Match 节点缓存** 这两种方式优化。
|
||||
可以利用 **First 集** 与 **Match 节点缓存** 这两种方式优化。
|
||||
|
||||
本文会用到一些图做解释,下面介绍图形规则:
|
||||
|
||||
@@ -44,7 +44,7 @@ Match 节点缓存,指在运行时,缓存节点到其第一个终结符的
|
||||
|
||||
拿 `select a, b, c, d from e` 这个语句做测试:
|
||||
|
||||
| node 节点访问次数 | Frist 集优化 | First 集 + Match 节点缓存优化 |
|
||||
| node 节点访问次数 | First 集优化 | First 集 + Match 节点缓存优化 |
|
||||
| ----------------- | ------------ | ----------------------------- |
|
||||
| 784 | 669 | 652 |
|
||||
|
||||
|
||||
@@ -57,7 +57,7 @@ interface VDom {
|
||||
props: {
|
||||
[attrKey: string]: string;
|
||||
};
|
||||
chindren: VDom[];
|
||||
children: VDom[];
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -149,9 +149,9 @@ Fiber 利用分片的思想,把一个耗时长的任务分成很多小片,
|
||||
因此,在组件更新时有可能一个更新任务还没有完成,就被另一个更高优先级的更新过程打断,优先级高的更新任务会优先处理完,而低优先级更新任务所做的工作则会完全作废,然后等待机会重头再来。所以 React Fiber 把一个更新过程分为两个阶段:
|
||||
|
||||
- 第一个阶段 Reconciliation Phase,Fiber 会找出需要更新的 DOM,这个阶段是可以被打断的;
|
||||
- 第二个阶段 Commit Phase,是无法别打断,完成 DOM 的更新并展示;
|
||||
- 第二个阶段 Commit Phase,是无法被打断的,完成 DOM 的更新并展示;
|
||||
|
||||
在使用 Fiber 后,需要要检查与第一阶段相关的生命周期函数,避免逻辑的多次或重复调用:
|
||||
在使用 Fiber 后,需要检查与第一阶段相关的生命周期函数,避免逻辑的多次或重复调用:
|
||||
|
||||
- componentWillMount
|
||||
- componentWillReceiveProps
|
||||
|
||||
@@ -51,7 +51,9 @@ CI/CD 具体是个什么样的流程呢,如下图所示,差异仅在于是
|
||||
- 需要有持续集成的基础,测试用例需要覆盖足够的代码
|
||||
- 部署需要自动化,用户只需要手动触发,剩余的部署应该自动化
|
||||
- 团队需要增加新特性标志,避免未完成的新特性进入待发布的产品
|
||||
产出:
|
||||
|
||||
产出:
|
||||
|
||||
- 部署软件变得非常简单。团队不需要花费 n 天准备发布。
|
||||
- 可以提高发布频率,加速新特性触达用户进程。
|
||||
- 小的更改,对决策的压力要小得多,可以更快地迭代。
|
||||
@@ -63,7 +65,9 @@ CI/CD 具体是个什么样的流程呢,如下图所示,差异仅在于是
|
||||
- 测试必须要做到足够。测试的质量将决定发布的质量。
|
||||
- 文档建设需要和产品部署保持同步。
|
||||
- 新特性的发布需要协调其他部门,包括售后支持&市场&推广等。
|
||||
产出:
|
||||
|
||||
产出:
|
||||
|
||||
- 快速的发布节奏,因为每个新特性一旦完成都会自动的发布给用户。
|
||||
- 发布风险降低,修复问题更容易,因为每次变更都是小步迭代发布。
|
||||
- 用户可以看到持续性的优化和质量提升,而不是非要等到按月,按季度,甚至按年
|
||||
|
||||
@@ -14,13 +14,16 @@
|
||||
|
||||
# 2. 精读
|
||||
|
||||
> Warn:本文所说的技术专家,仅针对研究上层技术的专家,不包括底层技术专家。
|
||||
> 在 Google 底层专家人数极少,大部分专家都要走业务技术的路线。
|
||||
|
||||
首先我们要明确技术员与科学家的区别,为业务提供技术支持都是技术员,所以前端是一门技术,不是科学。
|
||||
|
||||
另外,技术的发展需要商业推动,没有使用场景的国家是很难推动技术进步的,科学除外。
|
||||
|
||||
所以业务技术是具备可持续发展的路线,毕竟大家都要吃饭,有业务价值的项目会活下来,附着在业务上的技术才能活下来,才有可能开枝散叶。
|
||||
|
||||
本文将从三个点去解释,为什么专家看上去越来越原理技术细节。
|
||||
本文将从三个点去解释,为什么专家看上去越来越远离技术细节。
|
||||
|
||||
## 2.1 技术细节对个人的重要性是在变化的
|
||||
|
||||
@@ -114,7 +117,7 @@ CEO 通过顶层设计调动了全公司资源,而业务线总裁通过任务
|
||||
|
||||
1. 技术细节学习难度不大,在需要深入的时候再深入了解最佳。
|
||||
2. 想要做成事,需要更宏观的技术思维,所以专家渐渐变得眼光宽阔,格局很大。
|
||||
3. 专家拥有快速学习技术细节的能力,只是这已不是其核心竞争力,所以与其写技术细节的文章,比如写方法论的思考带来的价值更大。
|
||||
3. 专家拥有快速学习技术细节的能力,只是这已不是其核心竞争力,所以与其写技术细节的文章,不如写方法论的思考带来的价值更大。
|
||||
4. 指引方向比走路更重要,专家都要逐渐成为引路人。
|
||||
5. 技术最终为业务服务,懂技术细节和让业务先赢没有必然的关系,所以在深入技术细节之前,要先理解业务,把握方向,防止技术细节出现路线问题。
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,429 @@
|
||||
# 1. 引言
|
||||
|
||||
本周精读的内容是:[Google I/O 19](https://www.youtube.com/watch?v=c0oy0vQKEZE)。
|
||||
|
||||
2019 年 Google I/O 介绍了一些激动人心的 JS 新特性,这些特性有些已经被主流浏览器实现,并支持 polyfill,有些还在草案阶段。
|
||||
|
||||
我们可以看到 JS 语言正变得越来越严谨,不同规范间也逐渐完成了闭环,而且在不断吸纳其他语言的优秀特性,比如 WeakRef,让 JS 在成为使用范围最广编程语言的同时,也越成为编程语言的集大成者,让我们有信心继续跟随 JS 生态,不用被新生的小语种分散精力。
|
||||
|
||||
# 2. 精读
|
||||
|
||||
本视频共介绍了 16 个新特性。
|
||||
|
||||
## private class fields
|
||||
|
||||
私有成员修饰符,用于 Class:
|
||||
|
||||
```js
|
||||
class IncreasingCounter {
|
||||
#count = 0;
|
||||
|
||||
get value() {
|
||||
return this.#count;
|
||||
}
|
||||
|
||||
increment() {
|
||||
this.#count++;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
通过 `#` 修饰的成员变量或成员函数就成为了私有变量,如果试图在 Class 外部访问,则会抛出异常:
|
||||
|
||||
```js
|
||||
const counter = new IncreasingCounter()
|
||||
counter.#count
|
||||
// -> SyntaxError
|
||||
counter.#count = 42
|
||||
// -> SyntaxError
|
||||
```
|
||||
|
||||
虽然 `#` 这个关键字被吐槽了很多次,但结论已经尘埃落定了,只是个语法形式而已,不用太纠结。
|
||||
|
||||
目前仅 Chrome、Nodejs 支持。
|
||||
|
||||
## Regex matchAll
|
||||
|
||||
正则匹配支持了 `matchAll` API,可以更方便进行正则递归了:
|
||||
|
||||
```js
|
||||
const string = 'Magic hex number: DEADBEEF CAFE'
|
||||
const regex = /\b\p{ASCII_Hex_Digit}+\b/gu/
|
||||
for (const match of string.matchAll(regex)) {
|
||||
console.log(match)
|
||||
}
|
||||
|
||||
// Output:
|
||||
// ['DEADBEEF', index: 19, input: 'Magic hex number: DEADBEEF CAFE']
|
||||
// ['CAFE', index: 28, input: 'Magic hex number: DEADBEEF CAFE']
|
||||
```
|
||||
|
||||
相比以前在 `while` 语句里循环正则匹配,这个 API 真的是相当的便利。And more,还顺带提到了 `Named Capture Groups`,这个在之前的 [精读《正则 ES2018》](https://github.com/dt-fe/weekly/blob/v2/091.%E7%B2%BE%E8%AF%BB%E3%80%8A%E6%AD%A3%E5%88%99%20ES2018%E3%80%8B.md#22-named-capture-groups) 中也有提到,具体可以点过去阅读,也可以配合 `matchAll` 一起使用。
|
||||
|
||||
## Numeric literals
|
||||
|
||||
大数字面量的支持,比如:
|
||||
|
||||
```js
|
||||
1234567890123456789 * 123;
|
||||
// -> 151851850485185200000
|
||||
```
|
||||
|
||||
这样计算结果是丢失精度的,但只要在数字末尾加上 `n`,就可以正确计算大数了:
|
||||
|
||||
```js
|
||||
1234567890123456789n * 123n;
|
||||
// -> 151851850485185185047n
|
||||
```
|
||||
|
||||
目前 BigInt 已经被 Chrome、Firefox、Nodejs 支持。
|
||||
|
||||
## BigInt formatting
|
||||
|
||||
为了方便阅读,大数还支持了国际化,可以适配成不同国家的语言表达形式:
|
||||
|
||||
```js
|
||||
const nf = new Intl.NumberFormat("fr");
|
||||
nf.format(12345678901234567890n);
|
||||
// -> '12 345 678 901 234 567 890'
|
||||
```
|
||||
|
||||
记住 `Intl` 这个内置变量,后面还有不少国际化用途。
|
||||
|
||||
同时,为了方便程序员阅读代码,大数还支持带分隔符的书写方式,可以使用 `useGrouping` 属性配置,默认为 `true`:
|
||||
|
||||
```js
|
||||
const nf = new Intl.NumberFormat("fr", { useGrouping: true });
|
||||
nf.format(12345678901234567890n);
|
||||
// -> '12 345 678 901 234 567 890'
|
||||
```
|
||||
|
||||
目前已经被 Chrome、Firefox、Nodejs 支持。
|
||||
|
||||
## flat & flatmap
|
||||
|
||||
等价于 lodash [flatten](https://lodash.com/docs/4.17.11#flatten) 功能:
|
||||
|
||||
```js
|
||||
const array = [1, [2, [3]]];
|
||||
array.flat();
|
||||
// -> [1, 2, [3]]
|
||||
```
|
||||
|
||||
还支持自定义深度,如果支持 `Infinity` 无限层级:
|
||||
|
||||
```js
|
||||
const array = [1, [2, [3]]];
|
||||
array.flat(Infinity);
|
||||
// -> [1, 2, 3]
|
||||
```
|
||||
|
||||
这样我们就可以配合 `.map` 使用:
|
||||
|
||||
```js
|
||||
[2, 3, 4].map(duplicate).flat();
|
||||
```
|
||||
|
||||
因为这个用法太常见,js 内置了 `flatMap` 函数代替 `map`,与上面的效果是等价的:
|
||||
|
||||
```js
|
||||
[2, 3, 4].flatMap(duplicate);
|
||||
```
|
||||
|
||||
目前已经被 Chrome、Firefox、Safari、Nodejs 支持。
|
||||
|
||||
## fromEntries
|
||||
|
||||
`fromEntries` 是 `Object.fromEntries` 的语法,用来将对象转化为数组的描述:
|
||||
|
||||
```js
|
||||
const object = { x: 42, y: 50, abc: 9001 };
|
||||
const entries = Object.entries(object);
|
||||
// -> [['x', 42], ['y', 50]]
|
||||
```
|
||||
|
||||
这样就可以对对象的 key 与 value 进行加工处理,并通过 `fromEntries` API 重新转回对象:
|
||||
|
||||
```js
|
||||
const object = { x: 42, y: 50, abc: 9001 }
|
||||
const result = Object.fromEntries(
|
||||
Object.entries(object)
|
||||
.filter(([ key, value]) => key.length === 1)
|
||||
.map(([ key, value ]) => [ key, value * 2])
|
||||
)
|
||||
// -> { x: 84, y: 100 }
|
||||
```
|
||||
|
||||
不仅如此,还可以将 object 快速转化为 Map:
|
||||
|
||||
```js
|
||||
const map = new Map(Object.entries(object));
|
||||
```
|
||||
|
||||
目前已经被 Chrome、Firefox、Safari、Nodejs 支持。
|
||||
|
||||
## Map to Object conversion
|
||||
|
||||
`fromEntries` 建立了 object 与 map 之间的桥梁,我们还可以将 Map 快速转化为 object:
|
||||
|
||||
```js
|
||||
const objectCopy = Object.fromEntries(map);
|
||||
```
|
||||
|
||||
目前已经被 Chrome、Firefox、Safari、Nodejs 支持。
|
||||
|
||||
## globalThis
|
||||
|
||||
> 业务代码一般不需要访问全局的 window 变量,但是框架与库一般需要,比如 polyfill。
|
||||
|
||||
访问全局的 this 一般会做四个兼容,因为 js 在不同运行环境下,全局 this 的变量名都不一样:
|
||||
|
||||
```js
|
||||
const getGlobalThis = () => {
|
||||
if (typeof self !== "undefined") return self; // web worker 环境
|
||||
if (typeof window !== "undefined") return window; // web 环境
|
||||
if (typeof global !== "undefined") return global; // node 环境
|
||||
if (typeof this !== "undefined") return this; // 独立 js shells 脚本环境
|
||||
throw new Error("Unable to locate global object");
|
||||
};
|
||||
```
|
||||
|
||||
因此整治一下规范也合情合理:
|
||||
|
||||
```js
|
||||
globalThis; // 在任何环境,它就是全局的 this
|
||||
```
|
||||
|
||||
目前已经被 Chrome、Firefox、Safari、Nodejs 支持。
|
||||
|
||||
## Stable sort
|
||||
|
||||
就是稳定排序结果的功能,比如下面的数组:
|
||||
|
||||
```js
|
||||
const doggos = [
|
||||
{ name: "Abby", rating: 12 },
|
||||
{ name: "Bandit", rating: 13 },
|
||||
{ name: "Choco", rating: 14 },
|
||||
{ name: "Daisy", rating: 12 },
|
||||
{ name: "Elmo", rating: 12 },
|
||||
{ name: "Falco", rating: 13 },
|
||||
{ name: "Ghost", rating: 14 }
|
||||
];
|
||||
|
||||
doggos.sort((a, b) => b.rating - a.rating);
|
||||
```
|
||||
|
||||
最终排序结果可能如下:
|
||||
|
||||
```js
|
||||
[
|
||||
{ name: "Choco", rating: 14 },
|
||||
{ name: "Ghost", rating: 14 },
|
||||
{ name: "Bandit", rating: 13 },
|
||||
{ name: "Falco", rating: 13 },
|
||||
{ name: "Abby", rating: 12 },
|
||||
{ name: "Daisy", rating: 12 },
|
||||
{ name: "Elmo", rating: 12 }
|
||||
];
|
||||
```
|
||||
|
||||
也可能如下:
|
||||
|
||||
```js
|
||||
[
|
||||
{ name: "Ghost", rating: 14 },
|
||||
{ name: "Choco", rating: 14 },
|
||||
{ name: "Bandit", rating: 13 },
|
||||
{ name: "Falco", rating: 13 },
|
||||
{ name: "Abby", rating: 12 },
|
||||
{ name: "Daisy", rating: 12 },
|
||||
{ name: "Elmo", rating: 12 }
|
||||
];
|
||||
```
|
||||
|
||||
注意 `choco` 与 `Ghost` 的位置可能会颠倒,这是因为 JS 引擎可能只关注 `sort` 函数的排序,而在顺序相同时,不会保持原有的排序规则。现在通过 **Stable sort** 规范,可以确保这个排序结果是稳定的。
|
||||
|
||||
目前已经被 Chrome、Firefox、Safari、Nodejs 支持。
|
||||
|
||||
## Intl.RelativeTimeFormat
|
||||
|
||||
`Intl.RelativeTimeFormat` 可以对时间进行语义化翻译:
|
||||
|
||||
```js
|
||||
const rtf = new Intl.RelativeTimeFormat("en", { numeric: "auto" });
|
||||
|
||||
rtf.format(-1, "day");
|
||||
// -> 'yesterday'
|
||||
rtf.format(0, "day");
|
||||
// -> 'today'
|
||||
rtf.format(1, "day");
|
||||
// -> 'tomorrow'
|
||||
rtf.format(-1, "week");
|
||||
// -> 'last week'
|
||||
rtf.format(0, "week");
|
||||
// -> 'this week'
|
||||
rtf.format(1, "week");
|
||||
// -> 'next week'
|
||||
```
|
||||
|
||||
不同语言体系下,`format` 会返回不同的结果,通过控制 `RelativeTimeFormat` 的第一个参数 `en` 决定,比如可以切换为 `ta-in`。
|
||||
|
||||
## Intl.ListFormat
|
||||
|
||||
`ListFormat` 以列表的形式格式化数组:
|
||||
|
||||
```js
|
||||
const lfEnglish = new Intl.ListFormat("en");
|
||||
lfEnglish.format(["Ada", "Grace"]);
|
||||
// -> 'Ada and Grace'
|
||||
```
|
||||
|
||||
可以通过第二个参数指定连接类型:
|
||||
|
||||
```js
|
||||
const lfEnglish = new Intl.ListFormat("en", { type: "disjunction" });
|
||||
lfEnglish.format(["Ada", "Grace"]);
|
||||
// -> 'Ada or Grace'
|
||||
```
|
||||
|
||||
目前已经被 Chrome、Nodejs 支持。
|
||||
|
||||
## Intl.DateTimeFormat -> formatRange
|
||||
|
||||
`DateTimeFormat` 可以定制日期格式化输出:
|
||||
|
||||
```js
|
||||
const start = new Date(startTimestamp);
|
||||
// -> 'May 7, 2019'
|
||||
const end = new Date(endTimestamp);
|
||||
// -> 'May 9, 2019'
|
||||
const fmt = new Intl.DateTimeFormat("en", {
|
||||
year: "numeric",
|
||||
month: "long",
|
||||
day: "numeric"
|
||||
});
|
||||
const output = `${fmt.format(start)} - ${fmt.format(end)}`;
|
||||
// -> 'May 7, 2019 - May 9, 2019'
|
||||
```
|
||||
|
||||
最后一句,也可以通过 `formatRange` 函数代替:
|
||||
|
||||
```js
|
||||
const output = fmt.formatRange(start, end);
|
||||
// -> 'May 7 - 9, 2019'
|
||||
```
|
||||
|
||||
目前已经被 Chrome 支持。
|
||||
|
||||
## Intl.Locale
|
||||
|
||||
定义国际化本地化的相关信息:
|
||||
|
||||
```js
|
||||
const locale = new Intl.Locale("es-419-u-hc-h12", {
|
||||
calendar: "gregory"
|
||||
});
|
||||
locale.language;
|
||||
// -> 'es'
|
||||
locale.calendar;
|
||||
// -> 'gregory'
|
||||
locale.hourCycle;
|
||||
// -> 'h12'
|
||||
locale.region;
|
||||
// -> '419'
|
||||
locale.toString();
|
||||
// -> 'es-419-u-ca-gregory-hc-h12'
|
||||
```
|
||||
|
||||
目前已经被 Chrome、Nodejs 支持。
|
||||
|
||||
## Top-Level await
|
||||
|
||||
支持在根节点生效 `await`,比如:
|
||||
|
||||
```js
|
||||
const result = await doSomethingAsync();
|
||||
doSomethingElse();
|
||||
```
|
||||
|
||||
目前还没有支持。
|
||||
|
||||
## Promise.allSettled/Promise.any
|
||||
|
||||
`Promise.allSettled` 类似 `Promise.all`、`Promise.any` 类似 `Promise.race`,区别是,在 Promise reject 时,`allSettled` 不会 reject,而是也当作 fulfilled 的信号。
|
||||
|
||||
举例来说:
|
||||
|
||||
```js
|
||||
const promises = [
|
||||
fetch("/api-call-1"),
|
||||
fetch("/api-call-2"),
|
||||
fetch("/api-call-3")
|
||||
];
|
||||
|
||||
await Promise.allSettled(promises);
|
||||
```
|
||||
|
||||
即便某个 `fetch` 失败了,也不会导致 `reject` 的发生,这样在不在乎是否有项目失败,只要拿到都结束的信号的场景很有用。
|
||||
|
||||
对于 `Promise.any` 则稍有不同:
|
||||
|
||||
```js
|
||||
const promises = [
|
||||
fetch("/api-call-1"),
|
||||
fetch("/api-call-2"),
|
||||
fetch("/api-call-3")
|
||||
];
|
||||
|
||||
try {
|
||||
const first = await Promise.any(promises);
|
||||
// Any of ths promises was fulfilled.
|
||||
console.log(first);
|
||||
} catch (error) {
|
||||
// All of the promises were rejected.
|
||||
}
|
||||
```
|
||||
|
||||
只要有子项 fulfilled,就会完成 `Promise.any`,哪怕第一个 Promise reject 了,而第二个 Promise fulfilled 了,`Promise.any` 也会 fulfilled,而对于 `Promise.race`,这种场景会直接 rejected。
|
||||
|
||||
如果所有子项都 rejected,那 `Promise.any` 也只好 rejected 啦。
|
||||
|
||||
目前已经被 Chrome、Firefox 支持。
|
||||
|
||||
## WeakRef
|
||||
|
||||
WeakRef 是从 OC 抄过来的弱引用概念。
|
||||
|
||||
为了解决这个问题:当对象被引用后,由于引用的存在,导致对象无法被 GC。
|
||||
|
||||
所以如果建立了弱引用,那么对象就不会因为存在的这段引用关系而影响 GC 了!
|
||||
|
||||
具体用法是:
|
||||
|
||||
```js
|
||||
const obj = {};
|
||||
const weakObj = new WeakRef(obj);
|
||||
```
|
||||
|
||||
使用 `weakObj` 与 `obj` 没有任何区别,唯一不同时,`obj` 可能随时被 GC,而一旦被 GC,弱引用拿到的对象可能就变成 `undefined`,所以要做好错误保护。
|
||||
|
||||
# 3. 总结
|
||||
|
||||
JS 这几个特性提升了 JS 语言的成熟性、完整性,而且看到其访问控制能力、规范性、国际化等能力有着重加强,解决的都是 JS 最普遍遇到的痛点问题。
|
||||
|
||||
那么,这些 JS 特性中,你最喜欢哪一条呢?想吐槽哪一条呢?欢迎留言。
|
||||
|
||||
> 讨论地址是:[精读《What's new in javascript》 · Issue #159 · dt-fe/weekly](https://github.com/dt-fe/weekly/issues/159)
|
||||
|
||||
**如果你想参与讨论,请 [点击这里](https://github.com/dt-fe/weekly),每周都有新的主题,周末或周一发布。前端精读 - 帮你筛选靠谱的内容。**
|
||||
|
||||
> 关注 **前端精读微信公众号**
|
||||
|
||||
<img width=200 src="https://img.alicdn.com/tfs/TB165W0MCzqK1RjSZFLXXcn2XXa-258-258.jpg">
|
||||
|
||||
**special Sponsors**
|
||||
|
||||
- [DevOps 全流程平台](https://e.coding.net/?utm_source=weekly)
|
||||
|
||||
> 版权声明:自由转载-非商用-非衍生-保持署名([创意共享 3.0 许可证](https://creativecommons.org/licenses/by-nc-nd/3.0/deed.zh))
|
||||
@@ -0,0 +1,156 @@
|
||||
# 1. 引言
|
||||
|
||||
本周精读内容是:[《数据之上 智慧之光》](http://www.fanruan.com/2018/databook2),由帆软软件公司出品。
|
||||
|
||||
帆软公司是国内一家做大数据 BI 和分析平台的提供商,主打产品是 [FineBI](http://www.fanruan.com/finebi)。笔者所在阿里数据中台也处于数据分析应用的前沿,本次精读的文章就是帆软公司的 《数据之上 智慧之光 2018》,感谢提供这份国内数据市场研究报告,让我们更深入全面的了解国内数据市场的发展方向。
|
||||
|
||||
随着 5G 的逐渐推行,网速比 4G 提高了 100 倍,将会为物联网打下通信基础,未来的世界将人与物、物与物进行互联。随着越来越多的设备接入网络,产生数据,而未来还有 6G、7G 将网速继续提高至 1 万倍、1 百万倍,利用卫星实现全球网络覆盖,将现实与虚拟融合等等,无不需要强大的数据处理分析技术才能掌握。
|
||||
|
||||
数据的总量将呈几何倍数上升,如果不能提前对数据的存储、处理、挖掘和分析提出一套解决方案,那么 5G 时代的海量数据就是人类社会的累赘,如果有一套数据处理与分析的方案,我们就有可能掌握海量的数据为自己所用,利用数据进一步推动人类社会向前发展。
|
||||
|
||||
上面是对未来的畅想,那么我国现阶段国内的数据市场的容量、需求是什么样呢?《数据之上 智慧之光》这本书给了我们答案。
|
||||
|
||||
PS:本文使用 2018 年的数据。
|
||||
|
||||
# 2. 精读
|
||||
|
||||
## 大数据行业发展趋势
|
||||
|
||||
2018 年中国大数据产业规模预计 329 亿元人民币,同比增长 39.4%。可以看到增长速度逐年增加,预计在 2020 年数据市场规模可达 586 亿元人民币。
|
||||
|
||||
笔者查了一下,2018 年全国网上零售额为 90065 亿元,比数据市场规模多了一个数量级,所以我国的数据产业其实还在萌芽期,可能还需要 5 到 10 年才能完全成熟,这也意味着目前数据市场是一片蓝海,从后面的数据和国内数据应用使用情况也可以看出来。
|
||||
|
||||
另外,各企业在大数据领域的投入资金与部门组织都同比 2017 年有所增加,其中接近四成的受访企业已经在应用大数据,较 2016 年提升了 4.5%,暂不考虑大数据的企业从 2016 年 7.8% 下降到 6.8%。
|
||||
|
||||
从微观角度观察社会也能发现这样的趋势,近些年研究大数据的公司明显增多,许多公司都逐渐设立了 “数据分析” 岗位和部门,可视化大屏在 toB 与 toG 领域都越来越得到重视。
|
||||
|
||||
## 企业数据应用情况
|
||||
|
||||
数据应用分为数据采集、数据治理、数据处理、数据分析这四大阶段,其中数据采集是获取数据的最重要方式,而数据治理是将分散在各种不同形态数据库的文件用统一方式管理起来,比如形成数据联邦,这是数据使用前最重要的一步治理。数据处理就是将数据按照业务需求进行计算,而不同量级的数据计算方式会不同,特别是大数据场景要分为离线计算与实时计算,只有极为重要、实时性要求强的指标才进行实时计算,现在正处于离线与实时计算混合的混合计算转型期。数据分析一般通过 BI 平台完成,也是分析数据最重要的一步,BI 也经历了漫长的版本迭代,第一阶段是数据报表阶段,第二阶段是具备分析能力与数据挖掘能力的分析阶段,第三阶段是机器自动识别用户意图的智能化分析阶段。
|
||||
|
||||
从智慧之光的调查结果来看,只有 22.47% 的企业实用了 BI 系统,而使用 BI 系统的企业中,超过七成认为 BI 项目能较好的满足现在的需求。说明未来还会有更多企业使用 BI,BI 的市场还有 4 倍的增长空间。
|
||||
|
||||
在数据应用成熟度方面,仅有 3.5% 的企业处于数据盈利阶段,也就是大部分企业对数据的治理还在投入阶段,但无需质疑,持续对数据进行投入一定能得到回报,但短期来看会拖累财务报表。
|
||||
|
||||
再看目前企业的数据价值需求,看看业务方对 BI 工具的期望有哪些。
|
||||
|
||||
期望从高到低分别是:
|
||||
|
||||
- (72.8%) 整合多系统数据,打通数据壁垒
|
||||
- (69.1%) 提高报表数据效率,更快更准更省事
|
||||
- (53.7%) 辅助管理预测,提高决策成功率
|
||||
- (51.4%) 提高生产效率,降低人力成本
|
||||
- (50.0%) 数据结合管理,优化管理方式
|
||||
- (47.8%) 业务监管分析,促进业务增至
|
||||
|
||||
这个排列顺序基本上也是 BI 平台迭代的顺序。
|
||||
|
||||
BI 刚起步时都要先做数据整合,对于大部分公司,数据孤岛的情况还是很普遍的,甚至有大量数据分散在各自工作人员电脑的 Excel 文件中,已存在的各业务平台见数据无法打通也很普遍,如果不能将多套系统间数据打通,你就没有对数据的掌控力。像阿里云的 [Dataphin](https://www.aliyun.com/product/dataphin) 就可以帮助企业建立数仓,建立一套数据资产管理体系,其中第一步就是帮助你打通数据壁垒。
|
||||
|
||||
解决取数问题后,就可以建设 BI 平台了,BI 平台初期基本以构建报表为主,而构建报表的方式根据发展阶段也各有不同,下面是智慧之光中一张很经典的 BI 发展阶段:
|
||||
|
||||
<img width=500 src="https://img.alicdn.com/tfs/TB1szfQb.CF3KVjSZJnXXbnHFXa-2432-1104.png">
|
||||
|
||||
在 IT-完全主导型阶段,主要任务就是制作报表,而业务人员能配置的部分只有 BI 模版的 5%,剩余 95% 都需要 IT 人员参与开发,不仅浪费人力资源,而且对业务线的时间成本也很高。
|
||||
|
||||
IT-强主导型阶段,BI 平台具有一定的配置能力,业务有 20% 的自主配置权,而 IT 仍需完成 80% 的工作。
|
||||
|
||||
在业务强主导型阶段,BI 层 80% 的工作都可以由业务方完成,IT 人员只参与 20%,这 20% 可能包括复杂场景的定制,比如电子表格或者复杂的分析功能。这个阶段真正实现了更快更准更省事。
|
||||
|
||||
业务完全主导型阶段,基本上 BI 层不需要 IT 人员参与,业务同学可以完全主导对 BI 平台的拓展,或者 BI 平台已经能满足业务线几乎所有的诉求,同时业务还能参与数据模型的控制,让业务能力下沉到数据层。到这个阶段的企业已经非常少了,也许只有少数互联网巨头可以达到这个阶段。
|
||||
|
||||
智能自助型,这个阶段不需要 IT 人员参与,业务仅需参与 1%,原因是 99% 的需求都有人工智能自动分析出来,也就是将业务数据拿到后,计算机已经知道该怎么看这份数据了。智能自主型在国内还处于概念阶段,在国外 BI 工具比如 PowerBI 与 Tableau 已经在这个领域深耕多年了,然而门槛比较高,目前效果应该还不太理想,因为这个阶段一旦成熟,国内的 BI 企业将面临巨大冲击,之所以国内处于业务强主导阶段的 BI 平台依然存在,除了数据安全的理由之外,只能认为国外智能自助型 BI 平台依然 “不够智能”。
|
||||
|
||||
通过上面的分析可以总结出,BI 平台不仅业务发展阶段迥异,对技术人才的要求在不同阶段也不一样,技术层面需要以 后端 -> 前端 -> ETL -> AI 人才 的递进态势演变,对技术人员来说,如何在 BI 技术演变的过程中不断自我学习,满足下个阶段的技术要求,是非常严峻的挑战。
|
||||
|
||||
另一个值得关注的是企业数据来源,根据 2016 与 2017 年的对比,来自企业内部的数据正在逐渐增多,从外部购买的数据从 16.7% 降低到 15.1%,而从政府免费开放的数据比例从 13.5% 提升到了 14.6%。这表示企业正在逐渐摆脱对外部购买数据的依赖,转而产生更多自己业务的数据,而政府也在逐渐加强开放数据建设,努力减少各企业间数据资源的壁垒。
|
||||
|
||||
## 企业数据使用方式
|
||||
|
||||
根据调查显示:
|
||||
|
||||
- (70.0%)使用传统的 SQL + Excel 分析数据
|
||||
- (64.8%)使用业务系统自带的报表或分析功能
|
||||
- (35.6%)使用 BI 工具
|
||||
- (10.8%)手工写代码
|
||||
|
||||
首先频繁的手工写代码只有 10% 不到的比例,这是因为稍稍有点长远打算的企业,都会打造一支技术团队,而业务也会给技术团队打造一些生产效能提升的工具,只有 10% 左右的企业无法割舍短期利益,导致所有数据分析需求都要手工写代码。
|
||||
|
||||
大部分企业依然采用 SQL + Excel 分析数据,这个结果在情理之中,因为 SQL + Excel 都是现成的工具,不需要研发成本,而 Excel 的强大分析能力也基本满足了业务需求。但这种模式无法共享分析结果,存在数据安全隐患,且无法进入分析与智能阶段。
|
||||
|
||||
使用业务系统自带的报表或分析功能也占了 64.8% 的比例,笔者所了解到的中小型公司也的确属于这个阶段,公司内不同业务线都有自己的业务平台,每个业务平台内都有或多或少的数据分析和报表能力,这对大部分企业来说够用了,但对于要建立 **数据中台** 的企业来说,分散在各业务系统的数据与报表能力,反而是一种阻碍。PS:阿里数据中台已进入 2.0 阶段,但对大部分企业来说,是不可能越过数据中台 1.0,直接进入 2.0 的,就像不可能跳过 5G 做 6G 一样。
|
||||
|
||||
只有 35.6% 的企业在使用 BI 工具,因为使用 BI 工具需要一定门槛,比如做数据治理等,当然也可以直接订购阿里云的 [Dataphin](https://www.aliyun.com/product/dataphin) 快速接入 QuickBI。
|
||||
|
||||
在企业使用 BI 时,选型的考虑因素也很有意思:
|
||||
|
||||
- (69.1%)产品是否高效易用
|
||||
- (59.2%)产品是否稳定性高,性能好
|
||||
- (58.5%)产品是否拥有丰富强大的功能
|
||||
- (51.4%)产品是否具备大数据分析能力
|
||||
- (33.6%)采购成本
|
||||
- (31.2%)生态与学习资源
|
||||
- (24.4%)厂商本身的实力
|
||||
|
||||
可以看到,BI 工具靠自身实力吃饭的,而不依赖公司光环,因为业务方对实用性要求更大。
|
||||
|
||||
69.1% 的企业看中是否高效易用,说明目前国内企业对 BI 培训能力较弱,希望有高投入产出比,同时也说明了 BI 自身的特性,它是面向非技术人员的产品,如果易用性不强,只是功能强大是没有用的。
|
||||
|
||||
59.2% 的企业看中稳定性和性能,这是因为对数据分析来说,看报表是高频操作,业务方会使用 BI 查看 KPI 报表,发日报或月报,用户是无法忍受频繁使用的产品稳定性出现问题的。
|
||||
|
||||
第三点就是功能是否强大,对一款面向用户的工具来说,如果功能有欠缺,就意味着无法满足业务需求。比如对折线图做归一化,如果 BI 平台的折线图自身不支持这个功能,使用者也没办法立马拉上一名前端同学拓展出这个功能,因为 BI 平台表面看上去易用,但底层设计复杂,一旦遇到功能不支持,除了等待更新外,没有更好的办法。
|
||||
|
||||
最后一个超过 50% 的用户期待就是具备大数据分析能力,这是因为企业数据量级普遍都很大,而 BI 平台底层的多维建模一般采用 OLAP 查询,遇到海量数据可能要等上几十分钟,需要 BI 平台内置一些数据加速的功能。ROLAP 给予关系型数据库,特点是兼容性强、灵活性强,但查询速度慢,而 MOLAP 是实现将各维度数据计算好,查询时直接映射到多为数据库访问,性能好,但是对存储空间的依赖极高,需要付出大量的金钱才能支撑这种模式的查询。
|
||||
|
||||
下面是企业对 BI 功能要求:
|
||||
|
||||
<img width=500 src="https://img.alicdn.com/tfs/TB1itL4bW1s3KVjSZFAXXX_ZXXa-1676-1310.png">
|
||||
|
||||
可以看到,对报表能力需求量最大,说明报表是 BI 工具基础的要求,也说明我国对数据的使用方式还停留在最初级的阶段。
|
||||
|
||||
另一个就是移动 BI 需求,在移动端看报表,PC 端做报表已经非常普遍了。
|
||||
|
||||
之所以数据填报排到了第三名,是因为不同公司并不是所有数据都统一管理,BI 支持数据填报,就可以将遗漏的数据录入进去。
|
||||
|
||||
相信在未来,这个条形图最长边会逐渐移动到腰部。
|
||||
|
||||
最后是企业面临的综合挑战:
|
||||
|
||||
- (64.8%)数据的整合与治理
|
||||
- (58.1%)与管理层及业务部门的配合
|
||||
- (51.8%)数据人才的培养
|
||||
- (49.8%)数据分析工具的选择
|
||||
- (42.4%)IT 部门自身的能力提升
|
||||
- (38.1%)衡量数据分析的价值产出
|
||||
- (27.6%)公司重视程度或预算投入
|
||||
- (14.1%)项目风险的控制
|
||||
|
||||
数据整合与治理是最大问题再次反映了我国数据可视化处于较为初级阶段,第二名的 “与管理层及业务部门的配合”,也印证了这一点,如何将数据价值传达给管理层,让管理层认可前期投入在未来是可以得到回报的,是在企业里做数据分析比较头疼的问题,而其他业务部门如果不予配合,不将数据交给数据中台部门,又难以解决数据整合的问题,而这个往往又依赖管理层的决定,因此管理层与业务部门的配合问题是相辅相成的。
|
||||
|
||||
第三名是数据人才培养的问题,这个问题笔者认为还好,前几年流行大数据人才,近几年流行 AI 人才,我国数据人才应该有不少的储备。
|
||||
|
||||
后面几项最重要的就是 衡量数据分析的价值产出,任何做数据的部门,如果不能让数据为公司带来价值,这件事件就没有可持续性。笔者建议从数据整合后的管理提效,节省机器成本的角度计算出收益,从数据分析平台为其他业务部门提供的决策依据,计算出为业绩提高作出的贡献,再从对公司内部做报表、邮件的研发人力节省,管理层快速查看公司整体实时数据分析的角度计算出软贡献价值。
|
||||
|
||||
# 3. 总结
|
||||
|
||||
尽管 BI 平台与数据分析可以为公司带来巨大的价值,但制作 BI 平台的成本是相当大的,而且 BI 平台具有马太效应,目前国际第一梯队的 Tableau、PowerBI 无论是吸引的人才,投入的资源,市场份额都远超追赶者的总和。
|
||||
|
||||
<img width=800 src="https://img.alicdn.com/tfs/TB1xd_1b2WG3KVjSZFPXXXaiXXa-1860-1027.png">
|
||||
|
||||
从 17-18,18-19 的 BI 四维度对比可以看出,低端 BI 的角逐正在越来越激烈,行业龙头 PowerBI 与 Tableau 位置越来越稳,国内 BI 龙头 FineBI,以及正在逐渐发力的 QuickBI 希望能挤进国际梯队,在 BI 技术领域拉平与发达国家的差距。
|
||||
|
||||
> PS:目前国内市场的情况,反而不适应 PowerBI 与 Tableau 阶段的 BI 工具,给国产 BI 工具创造了发展机遇,我们要抓住这次机遇带领中国数据市场走向第三代增强分析型,并使国内 BI 工具在国际市场占有一席之地。
|
||||
|
||||
> 讨论地址是:[精读《数据之上·智慧之光 - 2018》 · Issue #162 · dt-fe/weekly](https://github.com/dt-fe/weekly/issues/162)
|
||||
|
||||
**如果你想参与讨论,请 [点击这里](https://github.com/dt-fe/weekly),每周都有新的主题,周末或周一发布。前端精读 - 帮你筛选靠谱的内容。**
|
||||
|
||||
> 关注 **前端精读微信公众号**
|
||||
|
||||
<img width=200 src="https://img.alicdn.com/tfs/TB165W0MCzqK1RjSZFLXXcn2XXa-258-258.jpg">
|
||||
|
||||
**special Sponsors**
|
||||
|
||||
- [DevOps 全流程平台](https://e.coding.net/?utm_source=weekly)
|
||||
|
||||
> 版权声明:自由转载-非商用-非衍生-保持署名([创意共享 3.0 许可证](https://creativecommons.org/licenses/by-nc-nd/3.0/deed.zh))
|
||||
@@ -0,0 +1,383 @@
|
||||
# 1. 引言
|
||||
|
||||
备受开发者喜爱的特性 [Optional chaining](https://github.com/tc39/proposal-optional-chaining) 在 2019.6.5 进入了 stage2,让我们详细读一下草案,了解一下这个特性的用法以及讨论要点。
|
||||
|
||||
借着这次精读草案,让我们了解一下一个完整草案的标准文档结构是怎样的。
|
||||
|
||||
一个新特性的文档,首先要描述 **起因** 是什么,也就是为什么要增加这个特性,大家不会没有理由的就增加一个特性。其次是**其他语言是否有现成的实现版本**,参考他们并进行归纳总结,可以增加思考角度的全面性。
|
||||
|
||||
第三点就是 **语法介绍**,也就进入了新特性的正题,这里要详细介绍所有可能的使用情况。第四点是 **语义**,也就是诠释语法的含义。
|
||||
|
||||
然后是可选的 **是否有不支持的情况**,对于不支持的点是否有意而为之,为什么?此处一般会留下讨论的 ISSUE。然后是 **暂不考虑的点**,是由于性价比低、使用场景少,或者实现成本高的原因,为什么某些已经想到的点暂不考虑,这里也会留下讨论的 ISSUE。
|
||||
|
||||
后面一般还有 “正在讨论的点”、“FAQ”、“草案进度”、“参考文献”、“相关问题”、“预先讨论资料” 等内容。
|
||||
|
||||
# 2. 概述&精读
|
||||
|
||||
首先让我们回顾一下什么是 **“Optional chaining”**。
|
||||
|
||||
## 起因介绍
|
||||
|
||||
当访问一个深层树形结构的对象时,我们总需要判断中间节点属性是否存在:
|
||||
|
||||
```js
|
||||
var street = user.address && user.address.street;
|
||||
```
|
||||
|
||||
而且很多 API 返回的属性都可能为 Null,而我们往往只想获取非 Null 时的结果:
|
||||
|
||||
```js
|
||||
var fooInput = myForm.querySelector('input[name=foo]')
|
||||
var fooValue = fooInput ? fooInput.value : undefined
|
||||
```
|
||||
|
||||
> 笔者这里补充,在人机交互的领域,可能为 Null 的情况很多。首先是交互行为模块很多,行为复杂,很容易导致数据分散且难以预测(可能为空),仅是 DOM 元素就需要太多兼容,因为 DOM 被修改的实际太多了,大家都在共享一个可变的结构;其次是交互过程中间状态很多,出现状态残缺的可能性也很大,就拿 SQL 解析为例:后端只要检测 Query 是否正确就可以了,但前端的 SQL 编辑器需要在输入不完整的情况下给出提示,也就是在语法树错误的情况下给出提示,因此需要进行容错。
|
||||
|
||||
而 Optional chaining 可以解决为了容错而写过多重复代码的问题:
|
||||
|
||||
```js
|
||||
var street = user.address?.street
|
||||
var fooValue = myForm.querySelector('input[name=foo]')?.value
|
||||
```
|
||||
|
||||
正如上面的例子:如果 `user.address` 为 `undefined`,那 `street` 拿到的就是 `undefined`,而不是报错。
|
||||
|
||||
配合另一个在 stage2 的新特性 [Nullish Coalescing](https://github.com/tc39/proposal-nullish-coalescing) 做默认值处理非常方便:
|
||||
|
||||
```js
|
||||
// falls back to a default value when response.setting is missing or nullish
|
||||
// (response.settings == null) or when respsonse.setting.animationDuration is missing
|
||||
// or nullish (response.settings.animationDuration == null)
|
||||
const animationDuration = response.settings?.animationDuration ?? 300;
|
||||
```
|
||||
|
||||
`??` 号可以理解为 “默认值场景下的 `||`”:
|
||||
|
||||
```js
|
||||
const response = {
|
||||
settings: {
|
||||
nullValue: null,
|
||||
height: 400,
|
||||
animationDuration: 0,
|
||||
headerText: '',
|
||||
showSplashScreen: false
|
||||
}
|
||||
};
|
||||
|
||||
const undefinedValue = response.settings?.undefinedValue ?? 'some other default'; // result: 'some other default'
|
||||
const nullValue = response.settings?.nullValue ?? 'some other default'; // result: 'some other default'
|
||||
const headerText = response.settings?.headerText ?? 'Hello, world!'; // result: ''
|
||||
const animationDuration = response.settings?.animationDuration ?? 300; // result: 0
|
||||
const showSplashScreen = response.settings?.showSplashScreen ?? true; // result: false
|
||||
```
|
||||
|
||||
`0 || 1` 的结果是 `1`,因为 `0` 判定为 `false`,而 `||` 在前面的变量为 `false` 型才继续执行,而我们想要的是 “前面的对象不存在时才使用后面的值”。`??` 则代表了 “前面的对象不存在” 这个含义,即便值为 `0` 也会认为这个值是存在的。
|
||||
|
||||
Optional chaining 也可以用在方法上:
|
||||
|
||||
```js
|
||||
iterator.return?.()
|
||||
```
|
||||
|
||||
或者试图调用某些未被实现的方法:
|
||||
|
||||
```js
|
||||
if (myForm.checkValidity?.() === false) { // skip the test in older web browsers
|
||||
// form validation fails
|
||||
return;
|
||||
}
|
||||
```
|
||||
|
||||
比如某个旧版本浏览器不支持 `myForm.checkValidity` 方法,则不会报错,而是返回 `false`。
|
||||
|
||||
## 已有实现调研
|
||||
|
||||
Optional chaining 在 C#、Swift、CoffeeScript、Kotlin、Dart、Ruby、Groovy 已经实现了,且实现方式均有差异,可以看到每个语言在实现语法时都是有取舍的,但是大方向基本是相同的。
|
||||
|
||||
想了解其他语言是如何实现 Optional chaining 的读者可以 [点击阅读原文](https://github.com/tc39/proposal-optional-chaining#prior-art)。
|
||||
|
||||
这些语言实现 Optional chaining 的差异基本在 **语法、支持范围、边界情况处理** 等不同,所以如果你每天要在不同语言之间切换工作,看似相同的语法,但不同的细节可能把你绕晕(所以会的语言多,只会让你变成一个速记字典,满脑子都是哪些语言在哪些语法讨论倾向哪一边,选择了哪些特性这些毫无意义的结论,如果不想记这些,基础语法都没有掌握怎么好意思说会这门语言呢?所以学 JS 就够了)。
|
||||
|
||||
## 语法
|
||||
|
||||
Optional Chaining 的语法有三种使用场景:
|
||||
|
||||
```js
|
||||
obj?.prop // optional static property access
|
||||
obj?.[expr] // optional dynamic property access
|
||||
func?.(...args) // optional function or method call
|
||||
```
|
||||
|
||||
也就是将 `.` 替换为 `?.`,但要注意第二行与第三行稍稍有点反直觉,比如在函数调用时,需要将 `func(...args)` 写为 `func?.(...args)`。至于为什么语法不是 `func?(...args)` 这种简洁一点的表达方式,在 FAQ 中有提到这个例子:
|
||||
|
||||
`obj?[expr].filter(fun):0` 引擎难以判断 `obj?[expr]` 是 Optional Chaning,亦或这是一个普通的三元运算语句。
|
||||
|
||||
可见,要支持 `?.` 这个看似简单的语法,在整个 JS 语法体系中要考虑的边界情况很多。
|
||||
|
||||
即便是 `?.` 这样完整的用法,也需要注意 `foo?.3:0` 这种情况,不能将 `foo?.` 解析为 Optional chanining,而要将其解析为 `foo? .3 : 0`,这需要解析引擎支持 lookahead 特性。
|
||||
|
||||
## 语义
|
||||
|
||||
**当 `?.` 前面的变量值为 `null` 或 `undefined` 时,`?.` 返回的结果为 `undefined`**。
|
||||
|
||||
```js
|
||||
a?.b // undefined if `a` is null/undefined, `a.b` otherwise.
|
||||
a == null ? undefined : a.b
|
||||
|
||||
a?.[x] // undefined if `a` is null/undefined, `a[x]` otherwise.
|
||||
a == null ? undefined : a[x]
|
||||
|
||||
a?.b() // undefined if `a` is null/undefined
|
||||
a == null ? undefined : a.b() // throws a TypeError if `a.b` is not a function
|
||||
// otherwise, evaluates to `a.b()`
|
||||
|
||||
a?.() // undefined if `a` is null/undefined
|
||||
a == null ? undefined : a() // throws a TypeError if `a` is neither null/undefined, nor a function
|
||||
// invokes the function `a` otherwise
|
||||
```
|
||||
|
||||
### 短路
|
||||
|
||||
所谓短路,就是指引入了 Optional chaining 后,某些看似一定会执行的语句在特定情况下会短路(终止执行),比如:
|
||||
|
||||
```js
|
||||
a?.[++x] // `x` is incremented if and only if `a` is not null/undefined
|
||||
a == null ? undefined : a[++x]
|
||||
```
|
||||
|
||||
第一个例子,如果 `a` 时 `null/undefined`,就不会执行 `++x`。
|
||||
|
||||
原因是这段代码部分等价于 `a == null ? undefined : a[++x]`,如果 `a == null` 为真,自然不会执行 `a[++x]` 这个语句。但由于 Optional chaining 使这个语句变得 “简洁了”,虽然带来了便利,但也可能导致看不清完整的执行逻辑,引发误判。
|
||||
|
||||
所以看到 `?.` 语句时,一定要反射性的思考一下,这个语句会触发 “短路”。
|
||||
|
||||
### 长“短路”
|
||||
|
||||
Optional chaining 在 JS 的规范中,作用域仅限于调用处。看下面的例子:
|
||||
|
||||
```js
|
||||
a?.b.c(++x).d // if `a` is null/undefined, evaluates to undefined. Variable `x` is not incremented.
|
||||
// otherwise, evaluates to `a.b.c(++x).d`.
|
||||
a == null ? undefined : a.b.c(++x).d
|
||||
```
|
||||
|
||||
可以看到 `?.` 仅在 `a?.` 这一层生效,而不是对后续的 `b.c`、`c(++x).d` 继续生效。而对于 C+ 与 CoffeeScript,这个语法是对后续所有 `get` 生效的(**这里再次提醒,不要用 `CoffeeScript` 了,因为对于相同语法,语义都发生了变化,对你与你的同事都是巨大的理解负担,或者说没有人愿意注意,为什么代码在 `CoffeeScript` 里不报错,而转移到 JS 就报错了,是因为 Optional chaining 语义不一致造成的。**)。
|
||||
|
||||
正因为 Optional chaining 在 JS 语法中仅对当前位置起保护作用,因此一个调用语句中允许出现多个 `?.` 调用:
|
||||
|
||||
```js
|
||||
a?.b[3].c?.(x).d
|
||||
a == null ? undefined : a.b[3].c == null ? undefined : a.b[3].c(x).d
|
||||
// (as always, except that `a` and `a.b[3].c` are evaluated only once)
|
||||
```
|
||||
|
||||
上面这段代码,对 `a?.b`、`c?.(x)` 的访问与调用是安全的,而对于 `b[3]`、 `b[3].c`、`c?.(x).d` 的调用是不安全的。
|
||||
|
||||
在 FAQ 环节也提到了,为什么不学习 C# 与 CoffeeScript 的语义,将安全保护从 `a?.` 之后就一路 “贯穿” 下去?
|
||||
|
||||
原因是 JS 对 Optional chaining 的理解不同导致的。Optional chaining 仅仅是安全访问保护,不代表 `try catch`,也就是它不会捕获异常,举一个例子:
|
||||
|
||||
```js
|
||||
a?.b()
|
||||
```
|
||||
|
||||
这个调用,在 `a.b` 不是一个函数时依然会报错,原因就是 Optional chaining 仅提供了对属性访问的安全保护,不代表对整个执行过程进行安全保护,该抛出异常还是会抛出异常,因此 Optional chaining 没有必要对后面的属性访问安全性负责。
|
||||
|
||||
笔者认为 TC39 对这个属性的理解是合理的,否则用 `try catch` 就能代替 Optional chaining 了。**让一个特性仅实现分内的功能,是每个前端从业者都要具备的思维能力。**
|
||||
|
||||
> PS:笔者再多提一句,在任何技术设计领域,这个概念都适用。想想你设计的功能,写过的函数,如果为了图方便,扩大了其功能,终究会带来整体设计的混乱,适得其反。
|
||||
|
||||
### 边界情况 - 分组
|
||||
|
||||
我们知道,JS 代码可以通过括号的方式进行分组,分组内的代码拥有更高的执行优先级。那么在 Optional chaining 场景下考虑这个情况:
|
||||
|
||||
```js
|
||||
(a?.b).c
|
||||
(a == null ? undefined : a.b).c
|
||||
```
|
||||
|
||||
与不带括号的进行对比:
|
||||
|
||||
```js
|
||||
a?.b.c
|
||||
a == null ? undefined : a.b.c
|
||||
```
|
||||
|
||||
我们会发现,由于括号提高了优先级,导致在 `a` 为 `null/undefined` 时,解析出了 `undefined.c` 这个必定报错的荒谬语法。因此我们不要试图为 Optional chaining 进行括号分组,这样会打破逻辑顺序,使安全保护不但不生效,反而导致报错。
|
||||
|
||||
### Optional delete
|
||||
|
||||
中文大概可以翻译为 “安全删除” 吧,也就是 JS 的 Optional chaining 支持下面的使用方式:
|
||||
|
||||
```js
|
||||
delete a?.b
|
||||
a == null ? true : delete a.b
|
||||
```
|
||||
|
||||
这样不论 `b` 是否存在,得到的都是 `b` 删除成功的信号(返回值 `true`)。
|
||||
|
||||
至于为什么要支持 Optional delete,草案里也有提到,笔者认为非常有意思:
|
||||
|
||||
讨论重点应该是 “我们为什么不支持 Optional delete”,而不是 “我们为什么要支持 Optional delete”,有点像反证法的思路。由于 Optional delete 具备一定的使用场景,而且支持方式零成本(改写为 `a == null ? true : delete a.b` 即可),所以就支持它吧!
|
||||
|
||||
## 不支持的特性
|
||||
|
||||
下面三个特性不支持,原因是没什么使用场景:
|
||||
|
||||
- 安全的 construction:`new a?.()`
|
||||
- 安全的 template literal:a?.\`string\`
|
||||
- 上面两者的结合:`new a?.b()`, a?.b\`string\`
|
||||
|
||||
首先看 new 一个对象,如果 new 出来的结果是 `undefined`,那这个返回值使用起来也没有意义。
|
||||
|
||||
对于第二个安全的 template literal 来说,比如下面的语法:
|
||||
|
||||
```js
|
||||
a?.b
|
||||
`c`
|
||||
```
|
||||
|
||||
会被解析为
|
||||
|
||||
```js
|
||||
a == null ? undefined : a.b`c`
|
||||
```
|
||||
|
||||
那么对于下面这种翻译结果:
|
||||
|
||||
```js
|
||||
a == null ? undefined : a.b `c`
|
||||
```
|
||||
|
||||
目前不会有人这么写代码,因为这种语法的使用场景一般都是 “前面的属性必定存在时的简化语法”,比如 `styled-components` 的:
|
||||
|
||||
```js
|
||||
div`
|
||||
width: 300px;
|
||||
`
|
||||
```
|
||||
|
||||
而如果解析为:
|
||||
|
||||
```js
|
||||
(a == null ? undefined : a?.b) `c`
|
||||
```
|
||||
|
||||
则更不会有人愿意尝试这种写法,所以安全的 template literal 这种需求是不存在的,自然第三种需求也是不存在的。
|
||||
|
||||
下面一个不支持的特性,虽然有一定使用场景,但依然被否定的:
|
||||
|
||||
- 安全的赋值:`a?.b = c`
|
||||
|
||||
[讨论 ISSUE](https://github.com/tc39/proposal-optional-chaining/issues/18)
|
||||
|
||||
笔者总结一下,一共有这几种令人烦恼的地方,导致大家不想支持 **安全赋值** 特性:
|
||||
|
||||
**短路特性导致的理解成本:**
|
||||
|
||||
比如 `a?.b = c()`,如果 `a` 为 `null/undefined`,那么函数 `c()` 就不会被执行,这种语法太违背开发者的常识,如果支持这个特性带来的理解负担会很大。
|
||||
|
||||
**连带考虑场景很多:**
|
||||
|
||||
如果支持了这种看似简单的赋值场景,那么至少还有下面五种赋值场景需要考虑到:
|
||||
|
||||
- 简单赋值: `a?.b = c`
|
||||
- 聚合赋值: `a?.b += c, a?.b >>= c`
|
||||
- 自增,自减: `a?.b++, --a?.b`
|
||||
- 解构赋值: `{ x: a?.b } = c, [ a?.b ] = c`
|
||||
- for 循环中的临时赋值: `for (a?.b in c), for (a?.b of c)`
|
||||
|
||||
总和这几种考虑,支持安全赋值会带来更多灵活的用法,导致代码复杂度陡增(想想你的同事大量使用上面的后四种例子,你绝对想要找他决斗,因为这种写法和乱用 window 变量一样,在 JS 允许的框架内写出难以维护的逻辑,像是钻了法律的孔子),因此 TC39 决定不支持这种用法,从源头上杜绝被滥用。
|
||||
|
||||
以上不支持的功能点会在静态编译时被禁止,但以后也许会重新讨论。
|
||||
|
||||
另外对于 Class 的私有变量是否支持 `a?.#b` `a?.#b()` 还在讨论中,这取决于私有成员变量草案是否能最终落地。
|
||||
|
||||
## 暂不讨论的点
|
||||
|
||||
目前有两个 Optional chaining 功能点暂不讨论,分别是 [Optional spread](https://github.com/tc39/proposal-optional-chaining/issues/55) 与 [Optional destructuring](https://github.com/tc39/proposal-optional-chaining/issues/74)
|
||||
|
||||
对于 Optional spread,建议是:
|
||||
|
||||
```js
|
||||
const arr = [...?listOne, ...?listTwo];
|
||||
foo(...?args);
|
||||
```
|
||||
|
||||
但由于可以结合 [Nullish Coalescing](https://github.com/tc39/proposal-nullish-coalescing) 达到同样的效果:
|
||||
|
||||
```js
|
||||
foo(...args ?? [])
|
||||
```
|
||||
|
||||
所以暂时不深入讨论,因为存在意义不大。
|
||||
|
||||
对于 Optional destructuring,建议是:
|
||||
|
||||
|
||||
```js
|
||||
// const baz = obj?.foo?.bar?.baz;
|
||||
const { baz } = obj?.foo?.bar?;
|
||||
```
|
||||
|
||||
也就是对于解构用法,在最后一个位置添加 `?`,使其能安全的解构。
|
||||
|
||||
但由于基于这个特性会演变出太多的使用变体:
|
||||
|
||||
```js
|
||||
const {foo ?: {bar ?: {baz}}} = obj?
|
||||
```
|
||||
|
||||
或者
|
||||
|
||||
```js
|
||||
const {
|
||||
foo?: {
|
||||
bar?: { baz }
|
||||
}
|
||||
} = obj;
|
||||
```
|
||||
|
||||
对开发者的理解成本压力较大,毕竟 Optional chaining 的出发点只是 `?.` 这么简单。而且对于默认值,我们又有 `??` 语法可以快速满足,因此这个特性的讨论也被搁置了。
|
||||
|
||||
## 余下的 Q&A
|
||||
|
||||
大部分 Q&A 在上面的解读都有提及,下面列出剩余的两个 Q&A:
|
||||
|
||||
### 为什么语法是 `?.` 而不是 `.?` ?
|
||||
|
||||
原因是与三元运算符冲突了,思考下面的用法:
|
||||
|
||||
```js
|
||||
1.?foo : bar
|
||||
```
|
||||
|
||||
在 js 中,`1.` 等价于 `1`,那么这就是一个标准的三元运算表达式,因此 `.?` 语法会产生歧义,只能选择 `?.`。
|
||||
|
||||
### 为什么 `null?.b` 的结果不是 `null` 呢?
|
||||
|
||||
由于 `.` 表达式不关心 `.` 前面对象的类型,因为它的目的是访问 `.` 后面的属性,因此不会因为 `null?.b` 就返回 `null`,而是统一返回 `undefined`。
|
||||
|
||||
最后,需要 TC39 最终审核后,Optional chaining 才能进入 Stage3,我们拭目以待吧!
|
||||
|
||||
# 3. 总结
|
||||
|
||||
写一篇 JS 特性草案的完整解读真的很累,以后也许很少有机会这么完整的解读草案了,但希望借着这次解读 Optional chaining 的机会,让大家理解 TC39 是如何制定草案的,草案都在讨论什么,怎么讨论的,流程有哪些。
|
||||
|
||||
同时,还希望让大家意识到,为一个语言添加一个看似简单的新特性有多么的不容易,一个简单的 `?.` 语法就牵涉到与三元运算符、分组、解构等等已存在语法的交织与冲突,所以想要安全又妥当的添加一个新特性,参与讨论的人必须对 JS 语言有完整全面的理解,同时也要对边界情况考虑的很周全,懂得对语法融会贯通。
|
||||
|
||||
最后,希望大家可以意识到,JS 这么重量级的语言,一个新的语法特性其实也是这么三言两语讨论下来的,其中不乏有一些拍脑袋的地方、对于“即可也可”的情况,稍稍结合一些具体案例就定下来其中一种的现象也是存在的,甚至对于某些规范点根本不存在一个完美的 “真理”,比如为什么语法是 `?.` 而不是 `a&.b`(Ruby 使用的就是 `&.`),认清了这种情况存在,就不会执着于 “语法的学习”,而转向更底层,更有用的 “语义的学习”,并能通过阅读 TC39 的草案了解其他语言的实现差异,从而快速掌握其他语言的语法。
|
||||
|
||||
> 讨论地址是:[精读《Optional chaining》 · Issue #165 · dt-fe/weekly](https://github.com/dt-fe/weekly/issues/165)
|
||||
|
||||
**如果你想参与讨论,请 [点击这里](https://github.com/dt-fe/weekly),每周都有新的主题,周末或周一发布。前端精读 - 帮你筛选靠谱的内容。**
|
||||
|
||||
> 关注 **前端精读微信公众号**
|
||||
|
||||
<img width=200 src="https://img.alicdn.com/tfs/TB165W0MCzqK1RjSZFLXXcn2XXa-258-258.jpg">
|
||||
|
||||
**special Sponsors**
|
||||
|
||||
- [DevOps 全流程平台](https://e.coding.net/?utm_source=weekly)
|
||||
|
||||
> 版权声明:自由转载-非商用-非衍生-保持署名([创意共享 3.0 许可证](https://creativecommons.org/licenses/by-nc-nd/3.0/deed.zh))
|
||||
+171
@@ -0,0 +1,171 @@
|
||||
# 1. 引言
|
||||
|
||||
[智能商业](https://book.douban.com/subject/30357931/) 是阿里巴巴前总参谋长曾鸣于 2018-11 出版的商业图书,对最近 20 年中国商业以及互联网发展有着深刻的总结,并描述了未来智能商业的蓝图。
|
||||
|
||||
笔者之所以读这本书,是因为笔者所在阿里巴巴数据中台,需要更深刻的理解数据,而《智能商业》就提到了数据时代的变革,对笔者工作有所帮助。
|
||||
|
||||
但读完这本书后,笔者发现不同人站在不同视角会有不同的理解:如果你是一名数据行业从业者,你可以理解数据在当今行业发展中如何起到作用;如果你是企业高管,你会领悟到商业平台发展的规则;如果你是一名创业者,你能体会到点线面体的存在,找到自己的定位;如果你是一名管理者,你能领域到管理模式正在发生的变化;如果你是一名传统行业从业者,你能体会到为什么互联网会对传统行业带来这么大的冲击;如果你是一名社会评论家,你会找到衡量智能时代对人类社会带来影响的标尺,等等。商业是推动人类社会发展的源动力,甚至也是文化与战争的源头,智能商业正因为将商业讲的通透,才摆脱了普通商业书籍枯燥的理论体系,从社会实践中总结理论,最终能上升到富有哲理的思考。
|
||||
|
||||
智能商业一书中有许多关键词,比如 “三浪叠加” “网络协同” “数据智能” “C2B” “S2B2C” “点线面体” “创造力革命” “网红” “互联网X” 等等,能将这些关键词串起来的,笔者认为是 “商业演化”,在近几十年范围内,商业模式存在一些不变底层逻辑(“三浪叠加” “网络协同” “数据智能”),而在大趋势下存在不断演变的商业模式(“C2B” “S2B2C” “点线面体” “创造力革命” “网红” “互联网X”)。
|
||||
|
||||
读完书后会发现,这么多的关键词,最终都为了实现 “C2B” 这个商业最终演化目标,即便是远在十八世纪的工业革命,也在为 C2B 模式打下让物质资源极大丰富的生产力基础,而网络协同和数据智能,都为了让商业规模更大,精准度更强,可以个性化识别每个用户的需求。新的组织模式也是为了更高效服务用户,整合社会 “点线面体” 的生态关系最终可以形成 “C2B” 的服务网络,而网红、互联网 X 都是 C2B 转型在不同阶段、不同行业的尝试。
|
||||
|
||||
# 2. 精读
|
||||
|
||||
智能商业全书分为六个章节,分别是 “智能商业”、“商业模式变革”、“战略变革”、“组织变革”、“案例分析”、“关于未来”。
|
||||
|
||||
笔者看过一些类似的书评,将书中的观点一一枚举出来,这样的解读笔者认为是难以抓到重点的。看似把重点一一提取了出来,但没有一条 “逻辑线” 将其贯穿,分散的理解任何一个知识点都不会有太大的帮助。而这条 “逻辑线” 其实就是作者的目录组织结构。
|
||||
|
||||
任何一本书,写作的目的是作者为了全面阐述一个观点,书中的重点都是一个个割裂的小观点,作者会通过目录方式组织一条最合理的逻辑路线,将这些重点串联起来,最终引出作者想阐述的大观点(智能商业),因此请跟着笔者从这本书的章节结构开始,有一个连贯的理解。
|
||||
|
||||
**前言**
|
||||
|
||||
前言笔者认为是最精彩的部分,因为提到了一个核心概念 “三浪叠加”,中国人口众多,土地广袤,互联网发展程度不均衡,因此任何互联网模式都可能存在,再加上互联网自身演化很快,当第二浪盖过第一浪时,第三浪已经悄然形成了,只从规模上可能难以分辨处于尾声的第一浪与处于巅峰的第二浪,更难分辨出还没有起色的第三浪在哪。读完这本书如果能看清楚中国商业发展的前三浪,并预测出未来三浪,目的就达到了。
|
||||
|
||||
**智能商业**
|
||||
|
||||
第一章的名字和书名一样,表示我们现在正处于智能商业时代。通过对中国社会的分析,解释了为什么商业时代发展的这么快,而且为什么创业方向那么多,有些行业快速崛起,有些行业快速衰退,而想要抓住未来,就要把握住互联网机遇,利用**网络协同**与**数据智能**实现智能商业,然后为什么这样的智能商业模式可以胜出。
|
||||
|
||||
**商业模式变革**
|
||||
|
||||
第二章讲的是商业模式由传统的 B2C 逐渐演变到 C2B,而在 C2B 演变的过程中,一种过渡阶段 S2B2C 正在快速崛起,而这些名词并非人为创造,而是商业发展自然演化而来的,能理解到 S2B2C 是通向 C2B 的自然演化路径,自然就能理解现在一些企业模式(比如网红、大搜车)等,也能自然理解 S2B2C 的不足(毕竟是过渡阶段),未来的战略方向自然就清晰了。
|
||||
|
||||
**战略变革**
|
||||
|
||||
前两章分别介绍了什么是智能商业,为什么要做智能商业,以及商业模式的演变,那第三章就自然要介绍企业战略变革了。第三章介绍了企业战略如何转型才能应对智能商业的节奏,比如何制定战略计划,以及通过 点-线-面-体 理解企业在市场中的定位,理解了这一点,不仅能理解各企业在市场中定位,还能理解之间相互关系,以及 点-线-面-体 的定位是可以改变的,抓住机遇的企业会逐渐向上发展,失去机遇的企业会逐渐向下退化。
|
||||
|
||||
理解了 点-线-面-体 的特性,可以更好的找准自己的定位,越往上资源越多,但排他性就越强,大部分时候,做一个深耕垂直行业的点,虽然同质化可能很多,但竞争不是排他性的,而且有线与面的平台支撑,特别是合理利用多个 “面” 后,可能爆发出强劲的商业价值,比如网红就是同时利用多个 “面” 的典型例子。
|
||||
|
||||
> 从战略变革这一章可以看到,这本书虽然前两章站在 BAT 高级战略的视角俯瞰商业演化,看似与普通企业,普通个人没什么关系,但读到战略变革这一章时,可以明显体会到理解 “面” 与 “体” 角度下商业思维后,可以给 “点” 与 “线” 带来巨大的战略价值。
|
||||
|
||||
**组织变革**
|
||||
|
||||
第四章是组织变革,因为当战略变革后,必须轮到组织变革了。工业革命带来了生产力的极大提高,那互联网则带来了创造力革命的浪潮,没有统一机器的约束,每个人都能充分发挥自己的创造力 - 前提是组织管理模式要支持。一个新的组织管理模式不是自上而下的分配任务,而是自下而上,充分发挥每个人创造力的 “赋能” 管理模式。都是互联网的管理模式是打平的,其实这是终极的理想情况,通过形成自组织协同网络,充分调动每一个人的创造力。
|
||||
|
||||
**案例分析**
|
||||
|
||||
读到第五章就没有多少新概念了,但第五章是真正把前四章理论映射到现实案例的实战环节,这一章我们能看懂许多企业战略背后的战略模式,都可以归纳到网络协同、数据智能的布局,商业模式都在向 C2B 转型,旧的面被新的面取代而下降为线,线抓住了机遇逐渐发展成面,多个面相互协同逐渐形成了 “体” 等多个维度的变化。
|
||||
|
||||
**关于未来**
|
||||
|
||||
第六章是对未来的判断,重点在互联网与传统产业如何碰撞,提出的 互联网x 概念背后有着更深刻的含义。如果你今年听说了 “产业物联网” 这个名词,可以甄别一下相应的企业,是仅仅将互联网技术运用到了传统行业,还是将传统行业从底层的运作逻辑就互联网化了呢?互联网不仅是一种技术,更是一种思维,互联网思维可以将被传统行业束缚住的各个流程逐渐还原到最原始、高效的模样。
|
||||
|
||||
比如说传统工程需要提前计算销量固化产能,但加入了互联网快速反馈的网络,就可以实时调整产能,当然这需要整个生产流程的互联网化,将整个环节都做到快速反馈。
|
||||
|
||||
**结语 - 新文明:感受未来已来**
|
||||
|
||||
印象最深的是引用了经济学家周其仁的一句话:“文明的一次次传承和复兴,就是一步步找回对人的尊重”。害怕机器取代人类的思想还是被局限在现有的世界观、价值观之中的,将工人固定在工厂流水线,或者程序员每天写着相似的业务逻辑,本身就是一种践踏人类尊严的行为,而计算机可以逐步取代这些低创造性的工作,可以理解为抢了那些人的饭碗,但站在历史长河的角度,何不是还给人类以尊严?
|
||||
|
||||
## 智能商业
|
||||
|
||||
首先是分析互联网巨头都至少做对了这三个方向中的两个:**在线化、智能化、网络化**。
|
||||
|
||||
在线化是指将业务都搬到互联网上,这基本是必备的一条。智能化是利用算法打造竞争优势,比如谷歌搜索算法。网络化就是形成多方共赢的协作网络,比如广告主与网站主通过谷歌搜索形成网络化协作。
|
||||
|
||||
简介提到的 **网络协同**与**数据智能** 就是指后两者,它们之间要形成一种反馈闭环就形成了智能商业的双螺旋:
|
||||
|
||||
**网络协同** 产生数据,通过 **数据智能** 进行学习,进一步优化 **网络协同**。
|
||||
|
||||
**网络协同** 需要建立起一张多角色之间的协同网,比如优步组织的司机与乘客的协同网。协同网络越复杂,经济效益越大、门槛越高,比如淘宝的协同网络非常复杂,体现在协同者多(买家,卖家,物流,客服,淘女郎)等等,他们之间也有相互关联,各角色对网络需求粘性强,网络的不可替代性就高。
|
||||
|
||||
**数据智能** 现在所有企业都没有充分利用数据,数据的潜在价值是无穷的,理论上可以利用数据做任何战略决策、管理决策。
|
||||
|
||||
而网络化与智能化叠加,会产生黑洞效应,也就是数据越多越吸附数据,网络协同越多就越容易扩张出新的协同。
|
||||
|
||||
作者对 **互** **联** **网** 这三个字的拆字解读也更容易让我们理解互联网的本质:
|
||||
|
||||
**联:** 联接,从 PC 互联网开始,到移动互联网,再到万物互联,联接内容越来越多。
|
||||
|
||||
**互:** 交互,从一对多的门户时代,到通过关注方式的微博时代,再到社交朋友圈时代,交互越来越简单,越来越频繁,也越来越精准。
|
||||
|
||||
**网:** 网络协同。
|
||||
|
||||
看了这么多概念,不知道你是否能理解智能商业的概念呢?也许每个人都有自己的体会,也许智能商业概念难以被定义,但 **网络协同**、**数据智能** 一定是核心,谁能充分利用这两股力量,将其充分发挥黑洞效应,形成一套更广泛的“互”,更多的“联”,更复杂的“网”络协同,谁就能更好利用互联网实现智能商业。
|
||||
|
||||
## 商业模式变革
|
||||
|
||||
商业领域较为常见的模式有 B2B、B2C、C2C。
|
||||
|
||||
B2B 代表企业是阿里巴巴、中化网,阿里巴巴是水平 B2B,是指企业与客户之间是平行关系;而中化网属于垂直 B2B,帮助企业寻找上下游合作伙伴。
|
||||
|
||||
B2C 代表企业是亚马逊、天猫、京东,也就是直接把商品卖给消费者。
|
||||
|
||||
C2C 代表企业是易贝、淘宝,即个人用户服务与个人,淘宝主要是个人用户开网店卖给个人。
|
||||
|
||||
而商业模式的变革,是指这些模式最终都要演化为 C2B 模式,即个人提出需求,企业快速满足。按照笔者理解,C2B 是由客户驱动的模式,虽然只是简单的单词调整位置,但背后需要企业做巨大的转型,不仅组织结构需要调整,还需要企业具有第一章说的 “智能商业” 属性,因为只有将服务在线化,通过数据智能与网络协同,才能精准触达每一位消费者,了解每个人的需求,快速服务与消费者。
|
||||
|
||||
然而快速服务消费者的需求还需要背后的供应链平台支持,所以 C2B 将以客户驱动的模式一直改造到背后的供应链逻辑。
|
||||
|
||||
然而 C2B 模式跨度太大,最近还诞生了一种过渡模式,就是 S2B2C 的模式,S 指的是供应平台,通过对小 B 的赋能,让小 B 直接服务于 C。这种模式是看场景的,因为只有 S2B 的价值大于单纯的 B,这个模式才行得通,所以在比如汽车、医药行业,小 B 急需 S 赋的业务场景可以做起来,而在本身就有大 B 存在的行业,就算有 S 赋能,小 B 依然竞争不过大 B,就不适合 S2B2C 这种模式。
|
||||
|
||||
另外 S2B2C 的模式也在升级,未来的产品可能会同时透出 S 于 B 的品牌,因为只透出 B 的品牌,可能导致 S 不能很好的掌握消费者需求,只透出 S 的品牌,就变成了传统加盟模式,而加盟模式最大的问题是无法发挥每个小 B 的积极性触达客户,加盟本质上还是 B2C,比如肯德基,一个大品牌对应每个消费者,就算加盟再多店铺也不会改变这一点,但是 S2B2C 比如网红模式,淘宝平台给网红赋能,网红通过自己的品牌吸引能力圈住一批客户,带来非常高的转化率,这就结合了两者优势。
|
||||
|
||||
另外也提到了云集,笔者以前认为云集是一种传销模式,和微商差不多,但其实云集要做的事情就是 S2B2C,将供应链完全打通后,包括网络系统一并提供给小 B,云集的小 B 就是任何有微信的用户,用户的资源就是他的朋友(朋友圈),所以云集号称没有商品就能做卖家,因为它的 S 服务做得好,集成性高,给小 B 带来的便利性就高。但问题是 小 B 到 C 环节是云集的弱势环节,拥有朋友圈的普通人与网红有本质的区别,普通人随意转发消息也许会带来朋友的反感与屏蔽,而普通人也不能为客户带来更大的价值,反观网红,他们可以得到粉丝的认可,成为粉丝的榜样,但是你愿意认可朋友圈里随便一个人成为你的榜样吗?
|
||||
|
||||
第二章总的来说解读了目前出现的网红现象,以及一些做的较好的独角兽(比如土巴兔、大搜车),其实他们都属于 S2B2C 的模式,而他们最终的目的地是 C2B。
|
||||
|
||||
## 战略变革
|
||||
|
||||
既然商业模式变革了,战略也要变革。之前也说过互联网处于三浪叠加状态,从 B2B 开始产生了很多新模式,从 C2B 到 S2B2C,比如 S2B2C 的模式也是在发展过程中逐渐发现的新模式,因此企业对战略的制定要采取一种高效反馈闭环,**核心在于做战略实验**。
|
||||
|
||||
首先确定几个未来可能的战略方向,各投入一些人力尝试,尝试一年后自然会发现正确的方向,此时再将其他方向合并到正确方向。比如 2011 年阿里巴巴独立了三个子公司 - 淘宝、天猫、一淘,是为了赌未来的局势到底是 B2C,还是 C2C,还是一个搜索引擎指向无数小 B2C。最终发现由于中国网络基础设施还不成熟,导致独立 B2C 成本太高,因此 一淘 就回到了阿里巴巴。
|
||||
|
||||
因此当你发现公司在同时做几个相似的业务时,先不要急着觉得公司傻,这样做是在浪费资源,但你是否能看清楚这几个业务间微妙的差别?也许你不能猜到哪一个才是未来方向(能猜到你就当 CEO 吧),但至少能理解公司这样做的战略意图,而不是做什么都是淘宝。
|
||||
|
||||
对于企业战略选择,作者给出的建议是 **点-线-面-体**。也就是企业一定要在这其中找到自己的定位。
|
||||
|
||||
根据笔者读后的理解,点就是各种各样服务的角色,比如卖家、模特、独立开发者都属于点。线就是连接点与面沟通桥梁,比如微商或微博大 V 都属于线,原因是他们联接了平台与点。面就是指平台,比如淘宝属于面,因为它撬动了整个行业的资源,对上面无数个点赋能,联接了无数个点,面也是竞争最激烈的一环,也就是所谓的生态竞争,如果面对点的赋能力度不够,点也许就被其他的面吸引过去了。体是最大的概念,由多个 **相互协同的面** 组成,比如物流平台、网购平台、支付平台这三个面之间相互协作,才能逐渐形成体。
|
||||
|
||||
顺带一提,体不是一开始就形成,面也不是谁设计出来的,而是先有一个简单构想,根据市场需求逐步演化过来的,比如淘宝就是由 BBS 演化过来的,那 BBS 就是淘宝的基因,因此淘宝可以协同那么多点,可以快速反馈用户需求,可以演化出支付、物流、云业务并各自独立发展成新的面。
|
||||
|
||||
点-线-面-体 定位越上升,拥有的资源就越多,但面对的变化挑战就越多,其中“面”的竞争最为激烈,比如传统媒体本来是面,但在门户网站出现有,就降维到了点,微博的出现又使门户网站降为成线,而微信的出现使微博降为成线。
|
||||
|
||||
所以看似风光的 BAT 都选择了最为艰难的 “体” 的打造,而笔者认为,到了体这个级别,将撬动巨量的社会资源,带来巨大的回报,但排他性也是最强的。一个最完整的 “体” 本质上就是一个全面的协同网络 - 国家,国家与国家之间的排斥性大家可以想象,因此留给体的位置并不多,而新体的出现必然会与旧体展开生死决战。因此如果创业,将自己定位为“点”是比较靠谱的,因为有大量的“面”资源可用,只要能找到自己的亮点,就算有竞争,也不会收到太大的影响。
|
||||
|
||||
## 组织变革
|
||||
|
||||
战略变革后,就轮到组织变革了。组织变革的目的是最大程度激发员工的创造力,因此自上而下的结构是不适合了,需要一种新的组织形态与管理思路。
|
||||
|
||||
这种新的管理思路就是 “赋能” 的思路,一方面,赋能的思路可以提升员工的自主程度,充分发挥其创造力,一方面,赋能可以转变管理者的管理方式,使一个经理能管理十几、二十几个下属。互联网行业的工资都很高,尤其是顶尖人才,对于金钱的渴望已经不是找工作的最大决定因素,“成就感” “使命感” 更容易受这些顶尖人才的青睐,因此 “赋能” 的管理思路也是招募到顶尖人才的方法。
|
||||
|
||||
最后作者提到了 “自组织协同网”,这是一个非常超前的概念,也源于企业最大的痛点 - 如何衡量 KPI。
|
||||
|
||||
随着商业环境复杂性提高,几个核心指标远不能反应一个企业真实情况。有句话说,如果你只看一个指标,那最后达成的方式一定是你最不愿意看到的,比如淘宝为了冲刺销量 KPI,出了全年免网购费用的年卡,也许一天就能完成全年 KPI,但未来一年内可能会亏空整个公司老本。因此利用数据,从多个维度衡量指标是唯一的解法,换个说法,就是用复杂性对抗复杂性。
|
||||
|
||||
通过将公司所有业务数据化,训练出一个逐步优化的模型,是可能从所有维度逐渐趋向最真实反馈公司表现的多维度指标的,衡量员工工作绩效方式也同理。
|
||||
|
||||
读完这一段,笔者感受到数据最终也会被用在员工身上这句话,简单来说就是晋升答辩不用写 PPT 了,年底通过上千、上万种维度对你进行综合测评,直接出结果。现在已经能感受到公司在这个方向发力了,第一步是将所有开发过程数据化,也许离这一天已经不远。
|
||||
|
||||
## 案例分析
|
||||
|
||||
案例分析十分精彩,由于篇幅限制,笔者就不洋洋洒洒的转述了,如果感兴趣强烈推荐读[原文](https://detail.tmall.com/item.htm?spm=a220m.1000858.1000725.1.5518639bdKutaT&id=587836001802&standard=1&user_id=832978172&cat_id=2&is_b=1&rn=ab05936e7d8ab2699ab0bbc07bb2cb3f),笔者至少还会再读一遍。
|
||||
|
||||
从案例分析中,有两个核心观点笔者在此处提一下。
|
||||
|
||||
第一个是平台演化的自然性,作者以淘宝的发展历程作为案例,说明了淘宝并不是顶层设计的产物,而是根据市场反馈的产物,唯有如此才能在高速变化的时代搭建一个平台。
|
||||
|
||||
第二个是网红案例,网红不仅完成了点到线的演化,而且是综合利用了多个“面”的案例,通过综合利用社交平台(微博),电商平台(淘宝),快速反应供应链平台(由网红推动产生的新型供应链),结合这三个平台,网红这个线被赋予前所未有的能量,带来了巨大收益。
|
||||
|
||||
## 关于未来
|
||||
|
||||
读完本书的目的,不仅是了解当下的智能商业,更是为了思考未来。
|
||||
|
||||
在这个大变革时代,未来战略是难以预测的,所以凭空去勾勒未来蓝图没有什么意义,我们要在通过战略实验快速试探出未来几年的方向,在第二浪即将到达巅峰时,找到第三浪并积极布局。
|
||||
|
||||
其实本书只能给出寻找战略方向的方法论,而不能给出具体的未来发展方向是什么,因为这套方法论本身就是通过战略实验快速寻找方向的过程,唯有投入资源去做尝试,仔细观察身边发生的变化,才能逐渐找到未来的新商业模式。未来的商业模式也是在逐步演变的,受到的影响因素太多,因此大概处于一种 “不可观测” 的状态,但至少未来十年内 C2B 的模式,笔者认为是一个固定的大方向,而传统行业与互联网结合的产业互联网也是新的发展机遇,利用互联网优化传统行业的各个环节,是一个确定的方向标。
|
||||
|
||||
无论未来商业怎么发展,都会为消费者带来越来越好的体验,这是一个消费为王的时代,根据消费者的需求,掀起从平台到供应链的全方位改造,目的是带来更好的消费体验。
|
||||
|
||||
# 3. 总结
|
||||
|
||||
读完了智能商业,笔者留下一个思考题:尝试站在智能商业的角度,分析你熟悉的公司各处于什么发展阶段,走的是什么商业模式?
|
||||
|
||||
> 讨论地址是:[精读《智能商业》 · Issue #169 · dt-fe/weekly](https://github.com/dt-fe/weekly/issues/169)
|
||||
|
||||
**如果你想参与讨论,请 [点击这里](https://github.com/dt-fe/weekly),每周都有新的主题,周末或周一发布。前端精读 - 帮你筛选靠谱的内容。**
|
||||
|
||||
> 关注 **前端精读微信公众号**
|
||||
|
||||
<img width=200 src="https://img.alicdn.com/tfs/TB165W0MCzqK1RjSZFLXXcn2XXa-258-258.jpg">
|
||||
|
||||
> 版权声明:自由转载-非商用-非衍生-保持署名([创意共享 3.0 许可证](https://creativecommons.org/licenses/by-nc-nd/3.0/deed.zh))
|
||||
@@ -0,0 +1,340 @@
|
||||
# 1. 引言
|
||||
|
||||
Vue 3.0 的发布引起了轩然大波,让我们解读下它的 [function api RFC](https://github.com/vuejs/rfcs/blob/function-apis/active-rfcs/0000-function-api.md#comparison-with-react-hooks) 详细了解一下 Vue 团队是怎么想的吧!
|
||||
|
||||
首先官方回答了几个最受关注的问题:
|
||||
|
||||
**Vue 3.0 是否有 break change,就像 Python 3 / Angular 2 一样?**
|
||||
|
||||
不,100% 兼容 Vue 2.0,且暂未打算废弃任何 API(未来也不)。之前有草案试图这么做,但由于用户反馈太猛,被撤回了。
|
||||
|
||||
**Vue 3.0 的设计盖棺定论了吗?**
|
||||
|
||||
没有呀,这次精读的稿子就是 RFC(Request For Comments),翻译成中文就是 “意见征求稿”,还在征求大家意见中哦。
|
||||
|
||||
**这 RFC 咋这么复杂?**
|
||||
|
||||
RFC 是写给贡献者/维护者的,要考虑许多边界情况与细节,所以当然会复杂很多喽!当然 Vue 本身使用起来还是很简单的。
|
||||
|
||||
> Vue 本身 Mutable + Template 就注定了是个用起来简单(约定 + 自然),实现起来复杂(解析 + 双绑)的框架。
|
||||
|
||||
**这次改动很像在模仿 React,为啥不直接用 React?**
|
||||
|
||||
首先 Template 机制还是没变,其次模仿的是 Hooks 而不是 React 全部,如果你不喜欢这个改动,那你更不会喜欢用 React。
|
||||
|
||||
PS: 问这个问题的人,一定没有同时理解 React 与 Vue,其实这两个框架到现在差别蛮大的,后面精读会详细说明。
|
||||
|
||||
下面正式进入 Vue 3.0 Function API 的介绍。
|
||||
|
||||
# 2. 概述
|
||||
|
||||
Vue 函数式基本 Demo:
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<div>
|
||||
<span>count is {{ count }}</span>
|
||||
<span>plusOne is {{ plusOne }}</span>
|
||||
<button @click="increment">count++</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { value, computed, watch, onMounted } from 'vue'
|
||||
|
||||
export default {
|
||||
setup() {
|
||||
// reactive state
|
||||
const count = value(0)
|
||||
// computed state
|
||||
const plusOne = computed(() => count.value + 1)
|
||||
// method
|
||||
const increment = () => { count.value++ }
|
||||
// watch
|
||||
watch(() => count.value * 2, val => {
|
||||
console.log(`count * 2 is ${val}`)
|
||||
})
|
||||
// lifecycle
|
||||
onMounted(() => {
|
||||
console.log(`mounted`)
|
||||
})
|
||||
// expose bindings on render context
|
||||
return {
|
||||
count,
|
||||
plusOne,
|
||||
increment
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
```
|
||||
|
||||
函数式风格的入口是 `setup` 函数,采用了函数式风格后可以享受如下好处:类型自动推导、减少打包体积。
|
||||
|
||||
`setup` 函数返回值就是注入到页面模版的变量。我们也可以返回一个函数,通过使用 `value` 这个 API 产生属性并修改:
|
||||
|
||||
```jsx
|
||||
import { value } from 'vue'
|
||||
|
||||
const MyComponent = {
|
||||
setup(props) {
|
||||
const msg = value('hello')
|
||||
const appendName = () => {
|
||||
msg.value = `hello ${props.name}`
|
||||
}
|
||||
return {
|
||||
msg,
|
||||
appendName
|
||||
}
|
||||
},
|
||||
template: `<div @click="appendName">{{ msg }}</div>`
|
||||
}
|
||||
```
|
||||
|
||||
要注意的是,`value()` 返回的是一个对象,通过 `.value` 才能访问到其真实值。
|
||||
|
||||
为何 `value()` 返回的是 Wrappers 而非具体值呢?原因是 Vue 采用双向绑定,只有对象形式访问值才能保证访问到的是最终值,这一点类似 React 的 `useRef()` API 的 `.current` 规则。
|
||||
|
||||
那既然所有 `value()` 返回的值都是 Wrapper,那直接给模版使用时要不要调用 `.value` 呢?**答案是否定的,直接使用即可,模版会自动 `Unwrapping`:**
|
||||
|
||||
```jsx
|
||||
const MyComponent = {
|
||||
setup() {
|
||||
return {
|
||||
count: value(0)
|
||||
}
|
||||
},
|
||||
template: `<button @click="count++">{{ count }}</button>`
|
||||
}
|
||||
```
|
||||
|
||||
接下来是 **Hooks**,下面是一个使用 Hooks 实现获得鼠标实时位置的例子:
|
||||
|
||||
```jsx
|
||||
function useMouse() {
|
||||
const x = value(0)
|
||||
const y = value(0)
|
||||
const update = e => {
|
||||
x.value = e.pageX
|
||||
y.value = e.pageY
|
||||
}
|
||||
onMounted(() => {
|
||||
window.addEventListener('mousemove', update)
|
||||
})
|
||||
onUnmounted(() => {
|
||||
window.removeEventListener('mousemove', update)
|
||||
})
|
||||
return { x, y }
|
||||
}
|
||||
|
||||
// in consuming component
|
||||
const Component = {
|
||||
setup() {
|
||||
const { x, y } = useMouse()
|
||||
const { z } = useOtherLogic()
|
||||
return { x, y, z }
|
||||
},
|
||||
template: `<div>{{ x }} {{ y }} {{ z }}</div>`
|
||||
}
|
||||
```
|
||||
|
||||
可以看到,`useMouse` 将所有与 “处理鼠标位置” 相关的逻辑都封装了进去,乍一看与 React Hooks 很像,但是有两个区别:
|
||||
|
||||
1. `useMouse` 函数内改变 `x`、`y` 后,不会重新触发 `setup` 执行。
|
||||
2. `x` `y` 拿到的都是 Wrapper 而不是原始值,且这个值会动态变化。
|
||||
|
||||
另一个重要 API 就是 **`watch`**,它的作用类似 React Hooks 的 **useEffect**,但实现原理和调用时机其实完全不一样。
|
||||
|
||||
`watch` 的目的是监听某些变量变化后执行逻辑,比如当 `id` 变化后重新取数:
|
||||
|
||||
```jsx
|
||||
const MyComponent = {
|
||||
props: {
|
||||
id: Number
|
||||
},
|
||||
setup(props) {
|
||||
const data = value(null)
|
||||
watch(() => props.id, async (id) => {
|
||||
data.value = await fetchData(id)
|
||||
})
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
之所以要 `watch`,因为在 Vue 中,`setup` 函数仅执行一次,所以不像 React Function Component,每次组件 `props` 变化都会重新执行,因此无论是在变量、`props` 变化时如果想做一些事情,都需要包裹在 `watch` 中。
|
||||
|
||||
后面还有 `unwatching`、生命周期函数、依赖注入,都是一些语法定义,感兴趣可以继续[阅读原文](https://github.com/vuejs/rfcs/blob/function-apis/active-rfcs/0000-function-api.md#dependency-injection),笔者就不赘述了。
|
||||
|
||||
# 3. 精读
|
||||
|
||||
对于 Vue 3.0 的 Function API + Hooks 与 React Function Component + Hooks,笔者做一些对比。
|
||||
|
||||
## Vue 与 React 逻辑结构
|
||||
|
||||
React Function Component 与 Hooks,虽然在实现原理上,与 Vue3.0 存在 Immutable 与 Mutable、JSX 与 Template 的区别,但逻辑理解上有着相通之处。
|
||||
|
||||
```ts
|
||||
const MyComponent = {
|
||||
setup(props) {
|
||||
const x = value(0)
|
||||
|
||||
const setXRandom = () => {
|
||||
x.value = Math.random()
|
||||
}
|
||||
|
||||
return { x, setXRandom }
|
||||
},
|
||||
template: `
|
||||
<button @onClick="setXRandom"/>{{x}}</button>
|
||||
`
|
||||
}
|
||||
```
|
||||
|
||||
虽然在 Vue 中,`setup` 函数仅执行一次,看上去与 React 函数完全不一样(React 函数每次都执行),但其实 Vue 将渲染层(Template)与数据层(setup)分开了,而 React 合在了一起。
|
||||
|
||||
我们可以利用 React Hooks 将数据层与渲染层完全隔离:
|
||||
|
||||
```jsx
|
||||
// 类似 vue 的 setup 函数
|
||||
function useMyComponentSetup(props) {
|
||||
const [x, setX] = useState(0)
|
||||
|
||||
const setXRandom = useCallback(() => {
|
||||
setX(Math.random())
|
||||
}, [setX])
|
||||
|
||||
return { x, setXRandom }
|
||||
}
|
||||
|
||||
// 类似 vue 的 template 函数
|
||||
function MyComponent(props: { name: String }) {
|
||||
const { x, setXRandom } = useMyComponentSetup(props)
|
||||
|
||||
return (
|
||||
<button onClick={setXRandom}>{x}</button>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
这源于 JSX 与 Template 的根本区别。JSX 使模版与 JS 可以写在一起,因此数据层与渲染层可以耦合在一起写(也可以拆分),但 Vue 采取的 Template 思路使数据层强制分离了,这也使代码分层更清晰了。
|
||||
|
||||
而实际上 Vue3.0 的 `setup` 函数也是可选的,再配合其支持的 TSX 功能,与 React 真的只有 Mutable 的区别了:
|
||||
|
||||
```jsx
|
||||
// 这是个 Vue 组件
|
||||
const MyComponent = createComponent((props: { msg: string }) => {
|
||||
return () => h('div', props.msg)
|
||||
})
|
||||
```
|
||||
|
||||
我们很难评价 Template 与 JSX 的好坏,但为了更透彻的理解 Vue 与 React,需要抛开 JSX&Template,Mutable&Immutable 去看,其实去掉这两个框架无关的技术选型,React@16 与 Vue@3 已经非常像了。
|
||||
|
||||
> Vue3.0 的精髓是学习了 React Hooks 概念,因此正好可以用 Hooks 在 React 中模拟 Vue 的 setup 函数。
|
||||
|
||||
关于这两套技术选型,已经是相对完美的组合,不建议在 JSX 中再实现类似 Mutable + JSX 的花样来(因为喜欢 Mutable 可以用 Vue 呀):
|
||||
|
||||
- Vue:Mutable + Template
|
||||
- React:Immutable + JSX
|
||||
|
||||
真正影响编码习惯的就是 Mutable 与 Immutable,使用 Vue 就坚定使用 Mutable,使用 React 就坚定使用 Immutable,这样能最大程度发挥两套框架的价值。
|
||||
|
||||
## Vue Hooks 与 React Hooks 的差异
|
||||
|
||||
先看 React Hooks 的简单语法:
|
||||
|
||||
```jsx
|
||||
const [ count, setCount ] = useState(0)
|
||||
|
||||
const setToOne = () => setCount(1)
|
||||
```
|
||||
|
||||
Vue Hooks 的简单语法:
|
||||
|
||||
```jsx
|
||||
const count = value(0)
|
||||
|
||||
const setToOne = () => count.value = 1
|
||||
```
|
||||
|
||||
之所以 React 返回的 `count` 是一个数字,是因为 Immutable 规则,而 Vue 返回的 `count` 是个对象,拥有 `count.value` 属性,也是因为 Vue Mutable 规则导致,这使得 Vue 定义的所有变量都类似 React 中 `useRef` 定义变量,因此不存 React `capture value` 的特性。
|
||||
|
||||
> 关于 capture value 更多信息,可以阅读 [精读《Function VS Class 组件》 Capute Value 介绍](https://github.com/dt-fe/weekly/blob/v2/095.%E7%B2%BE%E8%AF%BB%E3%80%8AFunction%20VS%20Class%20%E7%BB%84%E4%BB%B6%E3%80%8B.md#capture-props)
|
||||
|
||||
另外,对于 Hooks 的值变更机制也不同,我们看 Vue 的代码:
|
||||
|
||||
```jsx
|
||||
const Component = {
|
||||
setup() {
|
||||
const { x, y } = useMouse()
|
||||
const { z } = useOtherLogic()
|
||||
return { x, y, z }
|
||||
},
|
||||
template: `<div>{{ x }} {{ y }} {{ z }}</div>`
|
||||
}
|
||||
```
|
||||
|
||||
由于 `setup` 函数仅执行一次,怎么做到当 `useMouse` 导致 `x`、`y` 值变化时,可以在 `setup` 中拿到最新的值?
|
||||
|
||||
在 React 中,`useMouse` 如果修改了 `x` 的值,那么使用 `useMouse` 的函数就会被重新执行,以此拿到最新的 `x`,而在 Vue 中,将 Hooks 与 Mutable 深度结合,通过包装 `x.value`,使得当 `x` 变更时,引用保持不变,仅值发生了变化。所以 Vue 利用 Proxy 监听机制,可以做到 `setup` 函数不重新执行,但 Template 重新渲染的效果。
|
||||
|
||||
这就是 Mutable 的好处,Vue Hooks 中,不需要 `useMemo` `useCallback` `useRef` 等机制,仅需一个 `value` 函数,直观的 Mutable 修改,就可以实现 React 中一套 Immutable 性能优化后的效果,这个是 Mutable 的魅力所在。
|
||||
|
||||
## Vue Hooks 的优势
|
||||
|
||||
笔者对 RFC 中对 Vue、React Hooks 的对比做一个延展解释:
|
||||
|
||||
首先最大的不同:`setup` 仅执行一遍,而 React Function Component 每次渲染都会执行。
|
||||
|
||||
**Vue 的代码使用更符合 JS 直觉。**
|
||||
|
||||
这句话直截了当戳中了 JS 软肋,JS 并非是针对 Immutable 设计的语言,所以 Mutable 写法非常自然,而 Immutable 的写法就比较别扭。
|
||||
|
||||
当 Hooks 要更新值时,Vue 只要用等于号赋值即可,而 React Hooks 需要调用赋值函数,**当对象类型复杂时,还需借助第三方库才能保证进行了正确的 Immutable 更新。**
|
||||
|
||||
**对 Hooks 使用顺序无要求,而且可以放在条件语句里。**
|
||||
|
||||
对 React Hooks 而言,调用必须放在最前面,而且不能被包含在条件语句里,这是因为 React Hooks 采用下标方式寻找状态,一旦位置不对或者 Hooks 放在了条件中,就无法正确找到对应位置的值。
|
||||
|
||||
而 Vue Function API 中的 Hooks 可以放在任意位置、任意命名、被条件语句任意包裹的,因为其并不会触发 `setup` 的更新,只在需要的时候更新自己的引用值即可,而 Template 的重渲染则完全继承 Vue 2.0 的依赖收集机制,它不管值来自哪里,只要用到的值变了,就可以重新渲染了。
|
||||
|
||||
**不会再每次渲染重复调用,减少 GC 压力。**
|
||||
|
||||
这确实是 React Hooks 的一个问题,所有 Hooks 都在渲染闭包中执行,每次重渲染都有一定性能压力,而且频繁的渲染会带来许多闭包,虽然可以依赖 GC 机制回收,但会给 GC 带来不小的压力。
|
||||
|
||||
而 Vue Hooks 只有一个引用,所以存储的内容就非常精简,也就是占用内存小,而且当值变化时,也不会重新触发 `setup` 的执行,所以确实不会造成 GC 压力。
|
||||
|
||||
**必须要总包裹 `useCallback` 函数确保不让子元素频繁重渲染。**
|
||||
|
||||
React Hooks 有一个问题,就是完全依赖 Immutable 属性。**而在 Function Component 内部创建函数时,每次都会创建一个全新的对象,这个对象如果传给子组件,必然导致子组件无法做性能优化。** 因此 React 采取了 `useCallback` 作为优化方案:
|
||||
|
||||
```jsx
|
||||
const fn = useCallback(() => /* .. */, [])
|
||||
```
|
||||
|
||||
只有当第二个依赖参数变化时才返回新引用。但第二个依赖参数需要 lint 工具确保依赖总是正确的(关于为何要对依赖诚实,感兴趣可以移步 [精读《Function Component 入门》 - 永远对依赖诚实](https://github.com/dt-fe/weekly/blob/v2/104.%E7%B2%BE%E8%AF%BB%E3%80%8AFunction%20Component%20%E5%85%A5%E9%97%A8%E3%80%8B.md#%E6%B0%B8%E8%BF%9C%E5%AF%B9%E4%BE%9D%E8%B5%96%E9%A1%B9%E8%AF%9A%E5%AE%9E))。
|
||||
|
||||
回到 Vue 3.0,由于 `setup` 仅执行一次,因此函数本身只会创建一次,不存在多实例问题,不需要 `useCallback` 的概念,更不需要使用 [lint 插件](https://www.npmjs.com/package/eslint-plugin-react-hooks) 保证依赖书写正确,这对开发者是实实在在的友好。
|
||||
|
||||
**不需要使用 `useEffect` `useMemo` 等进行性能优化,所有性能优化都是自动的。**
|
||||
|
||||
这也是实在话,毕竟 Mutable + 依赖自动收集就可以做到最小粒度的精确更新,根本不会触发不必要的 Rerender,因此 `useMemo` 这个概念也不需要了。
|
||||
|
||||
而 `useEffect` 也需要传递第二个参数 “依赖项”,在 Vue 中根本不需要传递 “依赖项”,所以也不会存在用户不小心传错的问题,更不需要像 React 写一个 lint 插件保证依赖的正确性。(这也是笔者想对 React Hooks 吐槽的点,React 团队如何保障每个人都安装了 lint?就算装了 lint,如果 IDE 有 BUG,导致没有生效,随时可能写出依赖不正确的 “危险代码”,造成比如死循环等严重后果)
|
||||
|
||||
# 4. 总结
|
||||
|
||||
通过对比 Vue Hooks 与 React Hooks 可以发现,Vue 3.0 将 Mutable 特性完美与 Hooks 结合,规避了一些 React Hooks 的硬伤。所以我们可以说 Vue 借鉴了 React Hooks 的思想,但创造出来的确实一个更精美的艺术品。
|
||||
|
||||
但 React Hooks 遵循的 Immutable 也有好的一面,就是每次渲染中状态被稳定的固化下来了,不用担心状态突然变更带来的影响(其实反而要注意状态用不变更带来的影响),对于数据记录、程序运行的稳定性都有较高的可预期性。
|
||||
|
||||
最后,对于喜欢 Mutable 的开发者,Vue 3.0 是你的最佳选择,基于 React + Mutable 搞的一些小轮子做到顶级可能还不如 Vue 3.0。对于 React 开发者来说,坚持你们的 Immutable 信仰吧,Vue 3.0 已经将 Mutable 发挥到极致,只有将 React Immutable 特性发挥到极致才能发挥 React 的最大价值。
|
||||
|
||||
> 讨论地址是:[精读《Vue3.0 Function API》 · Issue #173 · dt-fe/weekly](https://github.com/dt-fe/weekly/issues/173)
|
||||
|
||||
**如果你想参与讨论,请 [点击这里](https://github.com/dt-fe/weekly),每周都有新的主题,周末或周一发布。前端精读 - 帮你筛选靠谱的内容。**
|
||||
|
||||
> 关注 **前端精读微信公众号**
|
||||
|
||||
<img width=200 src="https://img.alicdn.com/tfs/TB165W0MCzqK1RjSZFLXXcn2XXa-258-258.jpg">
|
||||
|
||||
> 版权声明:自由转载-非商用-非衍生-保持署名([创意共享 3.0 许可证](https://creativecommons.org/licenses/by-nc-nd/3.0/deed.zh))
|
||||
@@ -0,0 +1,196 @@
|
||||
# 1. 引言
|
||||
|
||||
本周精读的源码是 [inject-instance](https://github.com/ascoders/inject-instance) 这个库。
|
||||
|
||||
这个库的目的是为了实现 Class 的依赖注入。
|
||||
|
||||
比如我们通过 `inject` 描述一个成员变量,那么在运行时,这个成员变量的值就会被替换成对应 Class 的实例。这等于让 Class 具备了申明依赖注入的能力:
|
||||
|
||||
```js
|
||||
import {inject} from 'inject-instance'
|
||||
import B from './B'
|
||||
|
||||
class A {
|
||||
@inject('B') private b: B
|
||||
public name = 'aaa'
|
||||
|
||||
say() {
|
||||
console.log('A inject B instance', this.b.name)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
试想一下,如果成员函数 `b` 是通过 New 出来的:
|
||||
|
||||
```js
|
||||
class A {
|
||||
private b = new B()
|
||||
|
||||
say() {
|
||||
console.log('A inject B instance', this.b.name)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
这个 `b` 就不具备依赖注入的特点,因为被注入的 `b` 是外部已经初始化好的,而不是实例化 A 时动态生成的。
|
||||
|
||||
需要依赖注入的一般都是框架级代码,比如定义数据流,存在三个 Store 类,他们之间需要相互调用对方实例:
|
||||
|
||||
```js
|
||||
class A {
|
||||
@inject('B') private b: B
|
||||
}
|
||||
|
||||
class B {
|
||||
@inject('C') private c: C
|
||||
}
|
||||
|
||||
class C {
|
||||
@inject('A') private a: A
|
||||
}
|
||||
```
|
||||
|
||||
那么对于引用了数据流 A、B、C 的三个组件,**要保证它们访问到的是同一组实例 `A` `B` `C` 该怎么办呢?**
|
||||
|
||||
这时候我们需要通过 `injectInstance` 函数统一实例化这些类,保证拿到的实例中,成员变量都是属于同一份实例:
|
||||
|
||||
```js
|
||||
import injectInstance from 'inject-instance'
|
||||
|
||||
const instances = injectInstance(A, B, C)
|
||||
instances.get('A')
|
||||
instances.get('B')
|
||||
instances.get('C')
|
||||
```
|
||||
|
||||
那么框架底层可以通过调用 `injectInstance` 方式初始化一组 “正确注入依赖关系的实例”,拿 React 举例,这个动作可以发生在自定义数据流的 `Provider` 函数里:
|
||||
|
||||
```js
|
||||
<Provider stores={{ A, B, C }}>
|
||||
<Root />
|
||||
</Provider>
|
||||
```
|
||||
|
||||
那么在 `Provider` 函数内部通过 `injectInstance` 实例化的数据流,**可以保证 `A` `B` `C` 操作的注入实例都是当前 `Provider` 实例中的那一份**。
|
||||
|
||||
# 2. 精读
|
||||
|
||||
那么开始源码的解析,首先是整体思路的分析。
|
||||
|
||||
我们需要准备两个 API: `inject` 与 `injectInstance`。
|
||||
|
||||
`inject` 用来描述要注入的类名,值是与 Class 名相同的字符串,`injectInstance` 是生成一系列实例的入口函数,需要生成最终生效的实例,并放在一个 Map 中。
|
||||
|
||||
## inject
|
||||
|
||||
`inject` 是个装饰器,它的目的有两个:
|
||||
|
||||
1. 修改 Class 基类信息,使其实例化的实例能拿到对应字段注入的 Class 名称。
|
||||
2. 增加一个字段描述注入了那些 Key。
|
||||
|
||||
```ts
|
||||
const inject = (injectName: string): any => (target: any, propertyKey: string, descriptor: PropertyDescriptor): any => {
|
||||
target[propertyKey] = injectName
|
||||
|
||||
// 加入一个标注变量
|
||||
if (!target['_injectDecorator__injectVariables']) {
|
||||
target['_injectDecorator__injectVariables'] = [propertyKey]
|
||||
} else {
|
||||
target['_injectDecorator__injectVariables'].push(propertyKey)
|
||||
}
|
||||
|
||||
return descriptor
|
||||
}
|
||||
```
|
||||
|
||||
`target[propertyKey] = injectName` 这行代码中,`propertyKey` 是申明了注入的成员变量名称,比如 Class `A` 中,`propertyKey` 等于 `b`,而 `injectName` 表示这个值需要的对应实例的 Class 名,比如 Class `A` 中,`injectName` 等于 `B`。
|
||||
|
||||
而 `_injectDecorator__injectVariables` 是个数组,为 Class 描述了这个类参与注入的 key 共有哪些,这样可以在后面 `injectInstance` 函数中拿到并依次赋值。
|
||||
|
||||
## injectInstance
|
||||
|
||||
这个函数有两个目的:
|
||||
|
||||
1. 生成对应的实例。
|
||||
2. 将实例中注入部分的成员变量替换成对应实例。
|
||||
|
||||
代码不长,直接贴出来:
|
||||
|
||||
```ts
|
||||
const injectInstance = (...classes: Array<any>) => {
|
||||
const classMap = new Map<string, any>()
|
||||
const instanceMap = new Map<string, any>()
|
||||
|
||||
classes.forEach(eachClass => {
|
||||
if (classMap.has(eachClass.name)) {
|
||||
throw `duplicate className: ${eachClass.name}`
|
||||
}
|
||||
classMap.set(eachClass.name, eachClass)
|
||||
})
|
||||
|
||||
// 遍历所有用到的类
|
||||
classMap.forEach((eachClass: any) => {
|
||||
// 实例化
|
||||
instanceMap.set(eachClass.name, new eachClass())
|
||||
})
|
||||
|
||||
// 遍历所有实例
|
||||
instanceMap.forEach((eachInstance: any, key: string) => {
|
||||
// 遍历这个类的注入实例类名
|
||||
if (eachInstance['_injectDecorator__injectVariables']) {
|
||||
eachInstance['_injectDecorator__injectVariables'].forEach((injectVariableKey: string) => {
|
||||
const className = eachInstance.__proto__[injectVariableKey];
|
||||
if (!instanceMap.get(className)) {
|
||||
throw Error(`injectName: ${className} not found!`);
|
||||
}
|
||||
|
||||
// 把注入名改成实际注入对象
|
||||
eachInstance[injectVariableKey] = instanceMap.get(className);
|
||||
});
|
||||
}
|
||||
|
||||
// 删除这个临时变量
|
||||
delete eachInstance['_injectDecorator__injectVariables'];
|
||||
});
|
||||
|
||||
return instanceMap
|
||||
}
|
||||
```
|
||||
|
||||
可以看到,首先我们将传入的 Class 依次初始化:
|
||||
|
||||
```ts
|
||||
// 遍历所有用到的类
|
||||
classMap.forEach((eachClass: any) => {
|
||||
// 实例化
|
||||
instanceMap.set(eachClass.name, new eachClass())
|
||||
})
|
||||
```
|
||||
|
||||
这是必须提前完成的,因为注入可能存在循环依赖,我们必须在解析注入之前就生成 Class 实例,此时需要注入的字段都是 `undefined`。
|
||||
|
||||
第二步就是将这些注入字段的 `undefined` 替换为刚才实例化 Map `instanceMap` 中对应的实例了。
|
||||
|
||||
我们通过 `__proto__` 拿到 Class 基类在 `inject` 函数中埋下的 `injectName`,配合 `_injectDecorator__injectVariables` 拿到 key 后,直接遍历所有要替换的 key, 通过类名从 `instanceMap` 中提取即可。
|
||||
|
||||
> `__proto__` 仅限框架代码中使用,业务代码不要这么用,造成额外理解成本。
|
||||
|
||||
所以总结一下,就是提前实例化 + 根据 `inject` 埋好的信息依次替换注入的成员变量为刚才实例化好的实例。
|
||||
|
||||
# 3. 总结
|
||||
|
||||
希望读完这篇文章,你能理解依赖注入的使用场景,使用方式,以及一种实现思路。
|
||||
|
||||
框架实现依赖注入都是提前收集所有类,统一初始化,通过注入函数打标后全局替换,这是一种思维套路。
|
||||
|
||||
如果有其他更有意思的依赖注入实现方案,欢迎讨论。
|
||||
|
||||
> 讨论地址是:[精读《Inject Instance 源码》 · Issue #176 · dt-fe/weekly](https://github.com/dt-fe/weekly/issues/176)
|
||||
|
||||
**如果你想参与讨论,请 [点击这里](https://github.com/dt-fe/weekly),每周都有新的主题,周末或周一发布。前端精读 - 帮你筛选靠谱的内容。**
|
||||
|
||||
> 关注 **前端精读微信公众号**
|
||||
|
||||
<img width=200 src="https://img.alicdn.com/tfs/TB165W0MCzqK1RjSZFLXXcn2XXa-258-258.jpg">
|
||||
|
||||
> 版权声明:自由转载-非商用-非衍生-保持署名([创意共享 3.0 许可证](https://creativecommons.org/licenses/by-nc-nd/3.0/deed.zh))
|
||||
@@ -0,0 +1,128 @@
|
||||
# 1. 引言
|
||||
|
||||
前端展望的文章越来越不好写了,随着前端发展的深入,需要拥有非常宽广的视野与格局才能看清前端的未来。
|
||||
|
||||
笔者根据自身经验,结合下面几篇文章发表一些总结与感悟:
|
||||
|
||||
- [A Look at JavaScript’s Future](https://www.toptal.com/javascript/predicting-javascript-future)
|
||||
- [前端开发 20 年变迁史](https://mp.weixin.qq.com/s/yNg7Q0XNLJMnqffTIJhNUg)
|
||||
- [前端开发编程语言的过去、现在和未来](https://johnhax.net/2019/fe-lang/article1)
|
||||
- [绕过技术纷争,哪些技术决定前端开发者的未来?](https://mp.weixin.qq.com/s?__biz=MzUxMzcxMzE5Ng==&mid=2247491704&idx=1&sn=95ad66f7fe606801cdac74e296a41783)
|
||||
- [未来前端的机会在哪里?](https://mp.weixin.qq.com/s?__biz=MzIzOTU0NTQ0MA==&mid=2247490769&idx=1&sn=7ee6e01045a6fe7e15f16aa33afcc2ad&chksm=e92921dede5ea8c8e93489271e8877d2e8688bd511b32e22c287b6c468904c5466b40f6a2bec&xtrack=1&scene=90&subscene=93&sessionid=1562200039&clicktime=1562)
|
||||
|
||||
读完这几篇文章可以发现,即便是最资深的前端从业者,每个人看前端未来也有不同的侧重点。这倒不是因为视野的局限,而是现在前端领域太多了,专精其中某几个领域就足够了,适量比全面更好。
|
||||
|
||||
同时前端底层也在逐渐封闭,虽然目睹了前端几十年变迁的开发者仍会对一些底层知识津津乐道,但通往底层的大门已经一扇扇逐渐关闭了,将更多的开发者挤到上层区域建设,所以仅学会近几年的前端知识依然能找到不错的工作。
|
||||
|
||||
然而上层建设是不封顶的,有人看到了山,有人看到了星球,不同业务环境,不同视野的人看到的东西都不同。
|
||||
|
||||
有意思是的国内和国外看到前端未来的视角也不同:国内看到的是追求更多的参与感、影响力,国外看到的是对新特性的持续跟进。
|
||||
|
||||
# 2. 精读
|
||||
|
||||
前端可以从多个角度理解,比如规范、框架、语言、社区、场景以及整条研发链路。
|
||||
|
||||
看待前端未来的角度随着视野不同也会有变化,比如 Serverless 是未来,务实的思考是:前端在 Serverless 研发链路中仅处于使用方,并不会因为用了 Serverless 而提升了技术含量。更高格局的思考是:怎么推动 Serverless 的建设,不把自己局限在前端。
|
||||
|
||||
所以当我们读到不同的人对前端理解的时候,有人站在一线前端研发的角度,有人站在全栈的角度,也有人站在业务负责人的角度。其实国内前端发展也到了这个阶段,老一辈的前端开拓者们已经进入不同的业务领域,承担着更多不同的职能分工,甚至是整个大业务线的领导者,这说明两点:
|
||||
|
||||
1. 前辈已经用行动指出了前端突破天花板的各种方向。
|
||||
2. 同是前端未来展望,不同的文章侧重的格局不同,两个标题相同的文章内容可能大相径庭。
|
||||
|
||||
笔者顺着这些文章分析角度,发表一些自己的看法。
|
||||
|
||||
## 框架
|
||||
|
||||
在前端早期,也就是 1990 年浏览器诞生的时候,JS 没有良好的设计,浏览器也没有全面的实现,框架还没出来,浏览器之间就打起来了。
|
||||
|
||||
这也给前端发展定了一个基调:凭实力说话。
|
||||
|
||||
后面诞生的 Prototype、jquery 都是为了解决时代问题而诞生的,所以有种时代造就前端框架的感觉。
|
||||
|
||||
但到了最近几年,React、Angular、Vue 大有前端框架引领新时代的势头,前端要做的不再是填坑,而是模式创新。国内出现的小程序浪潮是个意料之外的现象,虽然群雄割据为开发者适配带来了一定成本,但本质上是中国在前端底层领域争取话语权的行为,而之所以各大公司不约而同的推出自己的小程序,则是商业、经济发展到了这个阶段的自然产物。
|
||||
|
||||
在原生开发领域,像 RN、Flutter 也是比较靠谱的移动端开发框架,RN 就长在 React 上,而 Flutter 的声明式 UI 也借鉴了前端框架的思路。每个框架都想往其他框架的领域渗透,所以标准总是很相近,各自的特色并没有宣传的那么明显,这个阶段只选用一种框架是明智的选择,未来这些框架之间会有更多使用场景争夺,但更多的是融合,推动新的开发方式提高生产力。
|
||||
|
||||
在数据驱动 UI 的方式上,具有代表性的是 React 的 Immutable 模式与 Vue 的 MVVM 观察者模式,前者模式虽然新颖,但是符合 JS 语言自然运行机制,Vue 的 MVVM 模式也相当好,特别是 Vue3.0 的 API 巧妙的解决了 React Hooks 无法解决的难题。如果 Vue 继续保持蓬勃的发展势头,未来前端 MVVM 模式甚至可能标准化,那么 Vue 是作为标准化的事实规范,还是和 JQuery 一样的命运,还需观察。
|
||||
|
||||
## 语言
|
||||
|
||||
JS 语言本身有满多缺陷的,但通过 babel 前端工程师可以提前享受到大部分新特性,这在很大程度上抵消了早期语言设计带来的问题。
|
||||
|
||||
横向对比来看,我们还可以把编程语言分为:前端语言、后端语言、能编译到 JS 的语言。
|
||||
|
||||
之所以有 “能编译到 JS 的语言” 这一类,是因为 JS Runtime 几乎是前端跨平台的通用标准,能编译到 JS 就代表了可跨平台,然而现在 “能编译到 JS 的语言” 除了紧贴 JS 做类型增强的 TS 外,其他并没有火起来,有工具链生态不匹配的原因,也有各大公司之间利益争夺的原因。
|
||||
|
||||
后端语言越来越贴场景化,比如 Go 主打轻量级高并发方案,Python 以其易用性占领了大部分大数据、人工智能的运算场景。
|
||||
|
||||
与此对应的是前端语言的同质化,前端语言绑定在前端框架的趋势越来越明显,比如 IOS 平台只能用 OC 和 Swift,安卓只能用 JAVA 和 Kotlin,Flutter 只支持 Dart,与其说这些语言更适合这些平台特性,不如说背后是谷歌、苹果、微软等巨头对平台生态掌控权的争夺。Web 与移动端要解决的问题是类似的:如何高效管理 UI 状态,现在大部分都采用数据驱动的思路,通过 JSX 或 Template 的方式描述出 UI DSL(更多可参考 [前端开发编程语言的过去、现在和未来](https://johnhax.net/2019/fe-lang/article1) UI DSL 一节)、以及性能提升:渲染和计算分离(这里又分为并发与调度两种实现思路,目的和效果是类似的)。
|
||||
|
||||
所以编程语言的未来也没什么悬念,前端领域如果有的选就用 JS,没得选只能依附所在平台绑定的语言,而前端语言最近正在完成一轮升级大迁徙:JS -> TS,JAVA -> Kotlin,OC -> Swift,前端语言的特性、易用性正在逐步趋同。需要说明的是,如果仅了解这些语言的语法,对编程能力是毫无帮助的,了解平台特性,解决业务问题,提供更好的交互体验才是前端应该不断追求的目标,随着前端、Native 开发者之间的流动,前端领域语言层面差异会会来越小,大家越关注上层,越倾向抹平语言差异,甚至可能 All in JS,这不是因为 JS 有多大野心,而是因为在解决的问题趋同、业务优先的大背景下,大家都需要减少语言不通带来的障碍,最好的办法就是统一语言,从人类语言的演变就可以发现,要解决的问题趋同(人类交流)、与国家绑定的小众语言一直都有生存空间、语法大同小异,但不同语言都有一定自己的特色(比如法语表意更精确)、跨语言学习成本高,所以当国际化协作频繁时,一定会催生一套官方语言(英语),而使用基数大的语言可能会发展为通用国际语言(中文)。
|
||||
|
||||
将编程语言的割裂、统一比作人类语言来看,就能理解现状,和未来发展趋势了。
|
||||
|
||||
## 可视化
|
||||
|
||||
前面也说过,前端的底层在逐渐封闭,而可视化就是前端的上层。
|
||||
|
||||
所以笔者很少提到工程化,原因就是未来前端开发者接触工程化的机会越来越少,工程化机制也越来越完善,前端会逐渐回归到自己的本质 - 人机交互,而交互的重要媒介就是图形,无论组件库还是智能化设计稿 To Code 都为了解放简单、模式化的交互工作,专业前端将更多聚集到图形化领域。
|
||||
|
||||
图形和数据是分不开的,所以图形化还要考虑性能问题与数据转换。
|
||||
|
||||
可视化是对性能要求最高的,因此像 web worker、GPU 加速都是常见处理手段,WASM 技术也会用到可视化中。具体到某个图表或大屏的性能优化,还会涉及数据抽样算法,分层渲染等,仅仅性能优化领域就有不少探索的空间。性能问题一般还伴随着数据量大,所以数据序列化方案也要一并考虑。
|
||||
|
||||
可视化图形学是非常学术的领域,从图形语法到交互语法,从一图一做的简单场景,到可视化分析场景的灵活拓展能力,再到探索式分析的图形语法完备性要求,可视化库想要一层层支持不同业务场景的需求,要有一个清晰的分层设计。
|
||||
|
||||
仅可视化的图形学领域,就足够将所有时间投入了,未来做可视化的前端会越来越专业,提供的工具库接口也越来越有一套最佳实践沉淀,对普通前端越来越友好。
|
||||
|
||||
BI 可视化分析就是前端深造的一个方向,跟随 BI 发展阶段,对前端的要求也在不断变化:工程化、组件化、搭建技术、渲染引擎、可视化、探索式、智能化,跟上产品对技术能力的要求,其实是相当有挑战性的。
|
||||
|
||||
## 编辑器
|
||||
|
||||
编辑器方向主要有 IDE(Web IDE)、富文本编辑器。
|
||||
|
||||
**IDE 方向** 国产做的比较好的是 HBuilder,国际上做的比较好的是 VSCode,由于微软还同时推出了 Web 版 MonacoEditor,让 Web IDE 开发的门槛大大降低。
|
||||
|
||||
作为使用者,现在和未来的主流可能都是微软系,毕竟微软在操作系统、IDE 方面人才储备和经验积累很多。但随着云服务的变迁,引导着开发方式升级,IDE 游戏规则可能迎来重大改变 - 云化。云化使得作为开发者拥有更多竞争的机会,因为云上 IDE 市场现在还是蓝海,现在很多创业公司和大公司内部都在走这个方向,这标志着中国计算机技术往更底层的技术发展,未来会有更多的话语权。
|
||||
|
||||
从发展阶段来说,前端也发展到了 Web IDE 这个时代。对大公司来说,内部有许许多多割裂的工程化孤岛,不仅消耗大量优秀的前端同学去维护,也造成内部物料体系、工程体系难以打通,阻碍了内部技术流通,而云 IDE 天生的中心化环境管理可以解决这个问题,同时还能带来抹平计算机环境差异、统一编译环境、源码不落盘、甚至实现自动的多人协作也成为了可能,而云 IDE 因为在云上,也不止于 IDE,还可以很方便的集成流程,将研发全链路打通,因此在阿里内部也成为了今年四大方向之一。
|
||||
|
||||
所以今年可以明显看到的是,前端又在逐步替代低水平重复的 UI 设计,从设计稿生成代码,到研发链路上云,这种顶层设计正在进一步收窄前端底层建设,所以未来会有更多专业前端涌入可视化领域。
|
||||
|
||||
**富文本编辑器方向** 是一个重要且小众的领域,老牌做的较好的是 UEditor 系列,现在论体验和周边功能完善度,做得最好的是语雀编辑器。开源也有很多优秀的实现,比如 Quill、DraftJS、Slate 等等,但现在富文本编辑器核心能力是功能完备性(是否支持视频、脑图、嵌入)、性能、服务化功能打通了多少(是否支持在线解析 pdf、ppt 等文件)、交互自然程度(拷贝内容的智能识别)等等。如果将眼光放到全球,那国外有大量优秀富文本编辑器案例,比如 Google Docs、Word Online、iCloud Pages 等等。
|
||||
|
||||
最好用的富文本编辑器往往不开源,因为投入的技术研发成本是巨大的,本身这项技术就是一个产品,卖点就是源码。
|
||||
|
||||
富文本编辑器功能强度可以分为三个级别:L0~L2:
|
||||
|
||||
- L0:利用浏览器自带的输入框,主要指 `contenteditable` 实现。
|
||||
- L1:在 L0 的基础上通过 DOM API 自主实现增删改的功能,自定义能力非常强。
|
||||
- L2:从输入框、光标开始自主研发,完全不依赖浏览器特性,如果研发团队能力强,可以实现任何功能,典型产品比如 Google Docs。
|
||||
|
||||
无论国内外都鲜有进入 L2 强度的产品,除了超级大公司或者主打编辑器的创业公司。
|
||||
|
||||
所以编辑器方向中,无论 IDE 方向,还是富文本编辑器方向,都值得深入探索,其中 IDE 方向更偏工程化一些,考验体系化思维,编辑器方向更偏经验与技术,考验基本功和架构设计能力。
|
||||
|
||||
## 智能化
|
||||
|
||||
笔者认为智能化离前端这个工种是比较远的,智能化最终服务前后端,给前后端开发效率带来一个质的提升,而在此之前,作为前端从业者无非有两种选择:加入智能化开拓者队伍,或者准备好放弃可能被智能化替代的工作内容,积极投身于智能化解放开发者双手后,更具有挑战性的工作。这种挑战性的工作恰好包括了上面分析过的四个点:语言、框架、可视化、编辑器。
|
||||
|
||||
类比商业智能化,商业智能化包括网络协同和数据智能,也就是大量的网络协同产生海量数据,通过数据智能算法促进更好的算法模型、更高效的网络协同,形成一个反馈闭环。前端智能化也是类似,不管是自动切图、生成图片、页面,或者自动生成代码,都需要算法和前端工程师之间形成协同关系,并完成一个高效的反馈闭环,算法将是前端工程师手中的开发利器,且越规模化的使用功效越大。
|
||||
|
||||
另一种智能化方向是探索 BI 与可视化结合的智能化,通过功能完备的底层图表库,与后端通用 Cube 计算模型,形成一种探索式分析型 BI 产品,Tableau 就是典型的案例,在这个智能化场景中,需要对数据、产品、可视化全面理解的综合性人才,是前端职业生涯另一个突破点。
|
||||
|
||||
# 3. 总结
|
||||
|
||||
本文列举的五点显然不能代表前端的全貌,还遗漏了太多方面,比如工程化、组件化、Serverless 等,但 **语言、框架、可视化、编辑器、智能化** 这五个点是笔者认为前端,特别是国内前端值得持续发力,可以做深的点,成为任何一个领域的专家都足以突破前端工程师成长的天花板。
|
||||
|
||||
最后,前端是最贴近业务的技术之一,业务的未来决定了前端的未来,创造的业务价值决定了前端的价值,从现在开始锻炼自己的商业化思考能力与产品意识,看得懂业务,才能看到未来。
|
||||
|
||||
> 讨论地址是:[精读《前端未来展望》 · Issue #178 · dt-fe/weekly](https://github.com/dt-fe/weekly/issues/178)
|
||||
|
||||
**如果你想参与讨论,请 [点击这里](https://github.com/dt-fe/weekly),每周都有新的主题,周末或周一发布。前端精读 - 帮你筛选靠谱的内容。**
|
||||
|
||||
> 关注 **前端精读微信公众号**
|
||||
|
||||
<img width=200 src="https://img.alicdn.com/tfs/TB165W0MCzqK1RjSZFLXXcn2XXa-258-258.jpg">
|
||||
|
||||
> 版权声明:自由转载-非商用-非衍生-保持署名([创意共享 3.0 许可证](https://creativecommons.org/licenses/by-nc-nd/3.0/deed.zh))
|
||||
+303
@@ -0,0 +1,303 @@
|
||||
# 1. 引言
|
||||
|
||||
[javascript-knowledge-reading-source-code](https://www.smashingmagazine.com/2019/07/javascript-knowledge-reading-source-code/) 这篇文章介绍了阅读源码的重要性,精读系列也已有八期源码系列文章,分别是:
|
||||
|
||||
- [精读《Immer.js》源码](https://github.com/dt-fe/weekly/blob/v2/048.%E7%B2%BE%E8%AF%BB%E3%80%8AImmer.js%E3%80%8B%E6%BA%90%E7%A0%81.md)
|
||||
- [精读《sqorn 源码》](https://github.com/dt-fe/weekly/blob/v2/073.%E7%B2%BE%E8%AF%BB%E3%80%8Asqorn%20%E6%BA%90%E7%A0%81%E3%80%8B.md)
|
||||
- [精读《Epitath 源码 - renderProps 新用法》](https://github.com/dt-fe/weekly/blob/v2/075.%E7%B2%BE%E8%AF%BB%E3%80%8AEpitath%20%E6%BA%90%E7%A0%81%20-%20renderProps%20%E6%96%B0%E7%94%A8%E6%B3%95%E3%80%8B.md)
|
||||
- [精读《Htm - Hyperscript 源码》](https://github.com/dt-fe/weekly/blob/v2/082.%E7%B2%BE%E8%AF%BB%E3%80%8AHtm%20-%20Hyperscript%20%E6%BA%90%E7%A0%81%E3%80%8B.md)
|
||||
- [精读《React PowerPlug 源码》](https://github.com/dt-fe/weekly/blob/v2/092.%E7%B2%BE%E8%AF%BB%E3%80%8AReact%20PowerPlug%20%E6%BA%90%E7%A0%81%E3%80%8B.md)
|
||||
- [精读《syntax-parser 源码》](https://github.com/dt-fe/weekly/blob/v2/093.%E7%B2%BE%E8%AF%BB%E3%80%8Asyntax-parser%20%E6%BA%90%E7%A0%81%E3%80%8B.md)
|
||||
- [精读《react-easy-state 源码》](https://github.com/dt-fe/weekly/blob/v2/098.%E7%B2%BE%E8%AF%BB%E3%80%8Areact-easy-state%20%E6%BA%90%E7%A0%81%E3%80%8B.md)
|
||||
- [精读《Inject Instance 源码》](https://github.com/dt-fe/weekly/blob/v2/110.%E7%B2%BE%E8%AF%BB%E3%80%8AInject%20Instance%20%E6%BA%90%E7%A0%81%E3%80%8B.md)
|
||||
|
||||
笔者自己的感悟是,读过大量源码的程序员有以下几个特质:
|
||||
|
||||
1. 思考具有系统性,主要体现在改一处代码模块时,会将项目所有文件串联起来整体考虑,提前评估影响面。
|
||||
2. 思考具有前瞻性,对已实现的方案可以快速评价所处阶段(临时 or 标准 or 可拓展),将边界情况提前解决,将框架 BUG 降低到最小程度。
|
||||
3. 代码实现更优雅,有大量源码经验做支撑,解决同样问题时,这些程序员可以用更短的行数、更合适的三方库解决问题,代码可读性更好,模块拆分更合理,更利于维护。
|
||||
|
||||
既然阅读源码这么重要,那么怎么才能读好源码呢?本周精读的文章就是一篇方法论文章,告诉你如何更好的阅读源码。
|
||||
|
||||
# 2. 概述
|
||||
|
||||
原文分三个部分:阅读源码的好处、阅读源码的技巧、以及 Redux Connect 的案例研究。
|
||||
|
||||
## 阅读源码的好处
|
||||
|
||||
阅读源码有助于理解抽象的概念,比如虚拟 DOM;有助于做方案调研,而不仅仅只看 Github star 数量;了解优秀框架目录结构的设计;看到一些陌生的工具函数,还可能激发你对 JS 规范的查阅,这种问题驱动的方式也是笔者推荐的 JS 规范学习方式。
|
||||
|
||||
## 阅读源码的技巧
|
||||
|
||||
最好的阅读源码方式是看文章,如果源码的作者有写源码解读文章,这就是最省力的方式。虽然直接看代码可以了解到所有细节,但当你不清楚设计思路时,仅看源码可能会找不到方向,而读源码的最终目的是找到核心的设计理念,如果一个框架没有自己核心设计理念,这个框架也不值得诞生,更不值得被阅读。如果框架的作者已经将框架核心理念写成了文章,那读文章就是最佳方案。
|
||||
|
||||
还有一种方式是断点,写一个最小程序,在框架执行入口出打下断点,然后按照执行路径一步步理解。虽然执行路径中会存在大量无关的函数干扰精力,但如果你足够有耐心,当断点走完时一定会有所收获。
|
||||
|
||||
原文还提到了一种看源码方式,即没有目的的寻宝。在寻找框架主要思路的过程中,遇到一些有意思的函数,可以停下来仔细阅读,可能会发现一些对你有启发的代码片段。
|
||||
|
||||
## Redux Connect 案例研究
|
||||
|
||||
原文以 Redux Connect 作为案例介绍研究思路。
|
||||
|
||||
首先看到 Connect 的功能 “包装组件” 后,就要问自己两个问题:
|
||||
|
||||
1. Connect 是如何实现包装组件后原样返回组件,但却增强组件功能的?(高阶组件知识)
|
||||
2. 了解这个设计模式后,如何利用已有的文档实现它?
|
||||
|
||||
通过创建一个使用 Connect 的基本程序:
|
||||
|
||||
```js
|
||||
class MarketContainer extends Component {
|
||||
|
||||
}
|
||||
|
||||
const mapDispatchToProps = dispatch => {
|
||||
return {
|
||||
updateSummary: (summary, start, today) => dispatch(updateSummary(summary, start, today))
|
||||
}
|
||||
}
|
||||
|
||||
export default connect(null, mapDispatchToProps)(MarketContainer);
|
||||
```
|
||||
|
||||
比如从生成 connect 函数的 [createConnect](https://github.com/reduxjs/react-redux/blob/master/src/connect/connect.js#L46) 我们就可以学习到 [Facade Pattern](http://jargon.js.org/_glossary/FACADE_PATTERN.md) - 门面模式。
|
||||
|
||||
从 `createConnect` 函数调用处:
|
||||
|
||||
```js
|
||||
export function createConnect({
|
||||
connectHOC = connectAdvanced,
|
||||
mapStateToPropsFactories = defaultMapStateToPropsFactories,
|
||||
mapDispatchToPropsFactories = defaultMapDispatchToPropsFactories,
|
||||
mergePropsFactories = defaultMergePropsFactories,
|
||||
selectorFactory = defaultSelectorFactory
|
||||
} = {})
|
||||
```
|
||||
|
||||
我们可以学习到解构默认函数参数的知识点。
|
||||
|
||||
总之,在学习源码的过程中,可以了解到一些新的 JS 特性,一些设计模式,这些都是额外的宝藏,不断理解并学会运用到自己写的框架里,就实现了源码学习的目的。
|
||||
|
||||
# 3. 精读
|
||||
|
||||
原文介绍了学习源码的两个技巧,并利用 Redux Connect 实例说明了源码学习过程中可以学到许多周边知识,都让我们受益匪浅。
|
||||
|
||||
笔者结合之前写过的八篇源码分析文章,把最重要的设计思路提取出来,以实际的例子展示阅读源码能给我们思维带来哪些帮助。
|
||||
|
||||
## Immerjs 源码的精华
|
||||
|
||||
Immer 可以让我们以 Mutable 的方式更新对象,最终得到一个 Immutable 对象:
|
||||
|
||||
```js
|
||||
this.setState(produce(state => (state.isShow = true)))
|
||||
```
|
||||
|
||||
> 详细源码解读可以阅读 [这里](https://github.com/dt-fe/weekly/blob/v2/048.%E7%B2%BE%E8%AF%BB%E3%80%8AImmer.js%E3%80%8B%E6%BA%90%E7%A0%81.md#3-%E7%B2%BE%E8%AF%BB)。
|
||||
|
||||
核心思路是利用 Proxy 把脏活累活做掉。上面的例子中,`state` 已经是一个代理(Proxy)对象,通过自定义 `setting` 不断递归进行浅拷贝,最后返回一个新引用的顶层对象作为 `produce` 的返回值。
|
||||
|
||||
从 Immerjs 中,我们学到了 Proxy 可以化腐朽为神奇的用法,比看任何 Proxy 介绍文章都直观。
|
||||
|
||||
## sqorn 源码的精华
|
||||
|
||||
sqorn 是一个 sql orm,举例来看:
|
||||
|
||||
```js
|
||||
const sq = require("sqorn-pg")();
|
||||
|
||||
const Person = sq`person`,
|
||||
Book = sq`book`;
|
||||
|
||||
// SELECT
|
||||
const children = await Person`age < ${13}`;
|
||||
// "select * from person where age < 13"
|
||||
```
|
||||
|
||||
> 详细源码解读可以阅读 [这里](https://github.com/dt-fe/weekly/blob/v2/073.%E7%B2%BE%E8%AF%BB%E3%80%8Asqorn%20%E6%BA%90%E7%A0%81%E3%80%8B.md#3-%E7%B2%BE%E8%AF%BB)
|
||||
|
||||
核心思路是在链式调用过程中创建 context 存储结构,并在链式调用的时候不断填充 context 信息,最终拿到的是一个结构化 context 对象,生成 sql 语句也就简单了。
|
||||
|
||||
从 sqorn 中,我们学到了如何实现链式调用 `init().a().b().c().print()` 最后拿到一个综合的结果,原理是内部维护了一个不断修改的对象。不论前端 React Vue 还是后端框架 Koa 等,一般都有内置的 context,一般实现这种优雅语法的框架内部都会维护 context。
|
||||
|
||||
## Epitath 源码的精华
|
||||
|
||||
Epitath 在 React Hooks 之前出来,解决了高阶函数地狱的问题:
|
||||
|
||||
```js
|
||||
const App = epitath(function*() {
|
||||
const { count } = yield <Counter />
|
||||
const { on } = yield <Toggle />
|
||||
|
||||
return (
|
||||
<MyComponent counter={count} toggle={on} />
|
||||
)
|
||||
})
|
||||
|
||||
<App />
|
||||
```
|
||||
|
||||
> 详细源码解读可以阅读 [这里](https://github.com/dt-fe/weekly/blob/v2/075.%E7%B2%BE%E8%AF%BB%E3%80%8AEpitath%20%E6%BA%90%E7%A0%81%20-%20renderProps%20%E6%96%B0%E7%94%A8%E6%B3%95%E3%80%8B.md#3-%E7%B2%BE%E8%AF%BB)
|
||||
|
||||
其核心是利用 `generator` 的迭代,将 React 组件的平级结构还原成嵌套结构,将嵌套写法打平了:
|
||||
|
||||
```
|
||||
yield <A>
|
||||
yield <B>
|
||||
yield <C>
|
||||
// 等价于
|
||||
<A>
|
||||
<B>
|
||||
<C />
|
||||
</B>
|
||||
</A>
|
||||
```
|
||||
|
||||
从 epitath 中,我们了解到 `generator` 原来可以这么用,正因为其执行是多次迭代的,因此我们可以利用这个特性,改变代码运行结构。
|
||||
|
||||
## Htm - Hyperscript 源码的精华
|
||||
|
||||
Htm 将模版语法很自然的融入到了 html 中:
|
||||
|
||||
```js
|
||||
html`
|
||||
<div class="app">
|
||||
<${Header} name="ToDo's (${page})" />
|
||||
<ul>
|
||||
${todos.map(
|
||||
todo => html`
|
||||
<li>${todo}</li>
|
||||
`
|
||||
)}
|
||||
</ul>
|
||||
<button onClick=${() => this.addTodo()}>Add Todo</button>
|
||||
<${Footer}>footer content here<//>
|
||||
</div>
|
||||
`;
|
||||
```
|
||||
|
||||
> 详细源码解读可以阅读 [这里](https://github.com/dt-fe/weekly/blob/v2/082.%E7%B2%BE%E8%AF%BB%E3%80%8AHtm%20-%20Hyperscript%20%E6%BA%90%E7%A0%81%E3%80%8B.md#3-%E7%B2%BE%E8%AF%BB)
|
||||
|
||||
其核心是怎么根据模版拿到 dom 元素的 AST?拿到 AST 后就方便生成后续内容了。
|
||||
|
||||
作者的办法是:
|
||||
|
||||
```js
|
||||
const TEMPLATE = document.createElement("template");
|
||||
TEMPLATE.innerHTML = str;
|
||||
```
|
||||
|
||||
这样 TEMPLATE 就自带了 AST 解析,这是利用浏览器自带的 AST 解析拿到了 AST。从 Htm 中,我们学到了 `innerHTML` 可以生成标准 AST,所以只要有浏览器运行环境,需要拿 AST 的时候,不需要其他库,`innerHTML` 就是最好的方案。
|
||||
|
||||
## React PowerPlug 源码的精华
|
||||
|
||||
React PowerPlug 是一个利用 render props 进行状态管理的工具库。
|
||||
|
||||
它可以在 JSX 中对任意粒度插入状态管理:
|
||||
|
||||
```js
|
||||
<Value initial="React">
|
||||
{({ value, set, reset }) => (
|
||||
<>
|
||||
<Select
|
||||
label="Choose one"
|
||||
options={["React", "Preact", "Vue"]}
|
||||
value={value}
|
||||
onChange={set}
|
||||
/>
|
||||
<Button onClick={reset}>Reset to initial</Button>
|
||||
</>
|
||||
)}
|
||||
</Value>
|
||||
```
|
||||
|
||||
> 详细源码解读可以阅读 [这里](https://github.com/dt-fe/weekly/blob/v2/092.%E7%B2%BE%E8%AF%BB%E3%80%8AReact%20PowerPlug%20%E6%BA%90%E7%A0%81%E3%80%8B.md#2-%E7%B2%BE%E8%AF%BB)
|
||||
|
||||
这个库的核心就是利用 render props 解决 JSX 局部状态管理的痛点,通过读源码了解 render props 的使用方式是这个源码带给你的最大价值。
|
||||
|
||||
## syntax-parser 源码的精华
|
||||
|
||||
syntax-parser 是一个 JS 版语法解器生成器,笔者也是作者,使用方式:
|
||||
|
||||
```js
|
||||
import { createParser, chain, matchTokenType, many } from "syntax-parser";
|
||||
|
||||
const root = () => chain(addExpr)(ast => ast[0]);
|
||||
|
||||
const addExpr = () =>
|
||||
chain(matchTokenType("word"), many(addPlus))(ast => ({
|
||||
left: ast[0].value,
|
||||
operator: ast[1] && ast[1][0].operator,
|
||||
right: ast[1] && ast[1][0].term
|
||||
}));
|
||||
|
||||
const addPlus = () =>
|
||||
chain("+"), root)(ast => ({
|
||||
operator: ast[0].value,
|
||||
term: ast[1]
|
||||
}));
|
||||
|
||||
const myParser = createParser(
|
||||
root, // Root grammar.
|
||||
myLexer // Created in lexer example.
|
||||
);
|
||||
```
|
||||
|
||||
> 详细源码解读可以阅读 [这里](https://github.com/dt-fe/weekly/blob/v2/093.%E7%B2%BE%E8%AF%BB%E3%80%8Asyntax-parser%20%E6%BA%90%E7%A0%81%E3%80%8B.md#2-%E7%B2%BE%E8%AF%BB)
|
||||
|
||||
syntax-parser 的核心是利用双向链表实现了可回溯的语法解析器,了解了这个库,你可以自己实现 JS 调用堆栈,并在任意时候返回某个之前的执行状态重新执行。同时这个库的源码也会加强你对链表的理解,以及拓展你对链表使用场景的想象。
|
||||
|
||||
## react-easy-state 源码的精华
|
||||
|
||||
react-easy-state 利用 Proxy 创建了一个简易的全局数据流管理方式:
|
||||
|
||||
```js
|
||||
import React from "react";
|
||||
import { store, view } from "react-easy-state";
|
||||
|
||||
const counter = store({ num: 0 });
|
||||
const increment = () => counter.num++;
|
||||
|
||||
export default view(() => <button onClick={increment}>{counter.num}</button>);
|
||||
```
|
||||
|
||||
> 详细源码解读可以阅读 [这里](https://github.com/dt-fe/weekly/blob/v2/098.%E7%B2%BE%E8%AF%BB%E3%80%8Areact-easy-state%20%E6%BA%90%E7%A0%81%E3%80%8B.md)
|
||||
|
||||
react-easy-state 利用了 [observer-util](https://github.com/nx-js/observer-util) 实现主要功能,从中我们能学到最有价值的就是 Proxy 与 React 结合的设计理念,即利用 `getter` `setter` 实现数据与视图的双向绑定,或者叫依赖追踪,更多细节就不在这里展开,感兴趣可以阅读笔者之前写的 [抽丝剥茧,实现依赖追踪](https://github.com/dt-fe/weekly/blob/master/35.%E7%B2%BE%E8%AF%BB%E3%80%8Adob%20-%20%E6%A1%86%E6%9E%B6%E5%AE%9E%E7%8E%B0%E3%80%8B.md#%E6%8A%BD%E4%B8%9D%E5%89%A5%E8%8C%A7%E5%AE%9E%E7%8E%B0%E4%BE%9D%E8%B5%96%E8%BF%BD%E8%B8%AA) 一节。
|
||||
|
||||
## Inject Instance 源码的精华
|
||||
|
||||
inject-instance 是一个 Class 实现依赖注入的库:
|
||||
|
||||
```js
|
||||
import {inject} from 'inject-instance'
|
||||
import B from './B'
|
||||
|
||||
class A {
|
||||
@inject('B') private b: B
|
||||
public name = 'aaa'
|
||||
|
||||
say() {
|
||||
console.log('A inject B instance', this.b.name)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
> 详细源码解读可以阅读 [这里](https://github.com/dt-fe/weekly/blob/v2/110.%E7%B2%BE%E8%AF%BB%E3%80%8AInject%20Instance%20%E6%BA%90%E7%A0%81%E3%80%8B.md#2-%E7%B2%BE%E8%AF%BB)
|
||||
|
||||
主要对我们有两个启发,第一可以利用装饰器为对象存储一些额外信息,这些信息在必要的时候我们可以用到;第二是依赖注入并不复杂,通过提前实例化后,可以解决循环依赖的问题,即所有循环依赖问题都可以通过加一个父级解决。
|
||||
|
||||
# 4. 总结
|
||||
|
||||
阅读代码不是目的,读懂源码背后要表达的核心设计思路才是目的。比如写脚手架,阅读了大量脚手架源码的人写出的代码,与一个没有经验的人写出的代码会有天壤之别,这之间的差距就是对一些设计模式、三方库、结构设计的经验差距。
|
||||
|
||||
只学习理论太空洞,只看代码又太局限,学会从代码中看出理论才是最佳学习方式。
|
||||
|
||||
> 讨论地址是:[精读《源码学习》 · Issue #179 · dt-fe/weekly](https://github.com/dt-fe/weekly/issues/179)
|
||||
|
||||
**如果你想参与讨论,请 [点击这里](https://github.com/dt-fe/weekly),每周都有新的主题,周末或周一发布。前端精读 - 帮你筛选靠谱的内容。**
|
||||
|
||||
> 关注 **前端精读微信公众号**
|
||||
|
||||
<img width=200 src="https://img.alicdn.com/tfs/TB165W0MCzqK1RjSZFLXXcn2XXa-258-258.jpg">
|
||||
|
||||
> 版权声明:自由转载-非商用-非衍生-保持署名([创意共享 3.0 许可证](https://creativecommons.org/licenses/by-nc-nd/3.0/deed.zh))
|
||||
@@ -0,0 +1,96 @@
|
||||
# 1. 引言
|
||||
|
||||
Node12 发布有几个月了,让我们跟随 [Nodejs 12](https://blog.logrocket.com/node-js-12/) 一起看看 Node12 带来了哪些改变。
|
||||
|
||||
# 2. 概述
|
||||
|
||||
Node12 与以往的版本不同,带来了许多重大升级,包括更多 V8 特性,Http 解析速度的提升,启动速度的提升,更好的诊断报告、内置堆分析工具,ESM 模块的更新等。
|
||||
|
||||
## V8 引擎升级
|
||||
|
||||
V8 升级带来了如下几个特性:
|
||||
|
||||
- [zero-cost async 堆栈信息](https://v8.dev/blog/v8-release-72#async-stack-traces) 原生支持了 async 堆栈信息,不会添加额外运行时内容。
|
||||
- [参数数量不匹配时性能优化](https://v8.dev/blog/v8-release-74#faster-calls-with-arguments-mismatch) 即便参数传递多了或少了,现在都几乎不会影响 Node 的执行速度。
|
||||
- [更快的 async](https://v8.dev/blog/v8-release-73#faster-await) async /await 已经比 promises 快了两个 microticks。
|
||||
- [更快的 Js 解析速度](https://v8.dev/blog/v8-release-72#javascript-parsing) 网页中的 V8 引擎一般花费 9.5% 时间在 JS 解析上,经过解析加速后,现在花费在 JS 解析上的时间降低到平均 7.5%。
|
||||
|
||||
可见 V8 引擎的升级不仅给 Node12 带来了福音,也给会一定程度上提升网页的运行效率。
|
||||
|
||||
## TLS 1.3 更好的安全性
|
||||
|
||||
随着 Node12 的发布,TLS 从 1.2 升级到了 1.3,更安全且更易配置。通过使用 TLS 1.3,Node 程序可以减少 Https 握手所需时间来提升请求性能。
|
||||
|
||||
## 默认堆被正确配置了
|
||||
|
||||
以前默认堆大小需要通过 `-max-old-space-size` 设置,而且默认值是一个固定值,现在这个默认值可以根据可用内存动态分配,这样当内存较小时,Node 不会让内存移除而报错,而是主动终止自己的进程。
|
||||
|
||||
## 默认的 http 解析器变为 llhttp
|
||||
|
||||
nodejs 的 [http-parser](https://github.com/nodejs/http-parser) 已经非常难以维护和优化了,因此 [llhttp](https://github.com/nodejs/llhttp#readme) 这个库,比 http-parser 快 156%,更重要的是,在 Node12 中,将默认解析器切换到了 llhttp。
|
||||
|
||||
## 提供诊断报告
|
||||
|
||||
Node12 有一项实验功能,根据用户需求提供诊断报告,包括崩溃、性能下降、内存泄露、CPU 使用高等等。
|
||||
|
||||
## 堆内存 dump
|
||||
|
||||
在以前,如果要将堆内存生成 dump 文件,需要在生产环境安装额外的模块,而 Node12 集成了这个功能。
|
||||
|
||||
## 更好的原生模块支持
|
||||
|
||||
C++ 拓展 [N-API](https://nodejs.org/api/n-api.html#n_api_n_api) 升级到版本 4,同时一个原生模块可以被 C++ 编写并发布到 npm,就像一个普通 JS 模块一样被引用。不过要注意一些区别:
|
||||
|
||||
| | | JS 模块 | 原生拓展 |
|
||||
| --- | -------------------------------------- | ------- | ------------------ |
|
||||
| 1. | ... 需要编译 | 否 | 如果预编译了则不用 |
|
||||
| 2. | ... 是否可以运行在所有平台 | 是 | 如果预编译了则可以 |
|
||||
| 3. | ... 是否兼容所有 Node 版本 | 是 | 否 |
|
||||
| 4. | ... 会被加载多次 | 是 | 否 |
|
||||
| 5. | ... 如果没有明确使用多线程,则线程安全 | 是 | 否 |
|
||||
| 6. | ... 可以被销毁 | 是 | 否 |
|
||||
|
||||
## Worker 被正式启用了
|
||||
|
||||
`--experimental-worker` 实验开关已取消,默认支持 `worker_threads`。
|
||||
|
||||
要注意的是,执行 CPU 密集型任务时适合用 worker(大量计算),而执行 I/O 密集型任务时,Worker 反而没有 Node 内置的 I/O 操作性能好(读写文件)。
|
||||
|
||||
## 启动速度优化
|
||||
|
||||
通过在构建时提前为内置库生成代码缓存,最终使启动时间加快 30%。
|
||||
|
||||
## 支持 ES6 module
|
||||
|
||||
Node12 对 ES6 module 的支持依然处于实验阶段,需要通过 `--experimental-modules` 开启。
|
||||
|
||||
简单来说,就是支持了 Import Export 语法,不需要再转成 `require` 了!如果在 `package.json` 增加 `"type": "module"` 的配置,Node 将按照 ES6 module 方式处理。
|
||||
|
||||
## 新的编译器和平台要求
|
||||
|
||||
由于升级到新的 V8 引擎以及内部改造,因此 Node12 在 Mac 与 Windows 之外的平台上,需要至少 GCC6 和 glibc 2.17。
|
||||
|
||||
# 3. 精读
|
||||
|
||||
对于 V8 引擎升级、TLS 升级、堆配置自动化、http-parser 升级到 llhttp、启动速度优化都属于被动优化,代码无需改动,只要升级 Node 版本就可以享受。
|
||||
|
||||
支持 ES6 module 这个特性其实比较鸡肋,毕竟源码用 Ts 写的话,这些升级并不会对源码产生影响。
|
||||
|
||||
`worker_threads` 可以被默认启用,就像以前支持 `async/await` 一样,会带来 Nodejs 多线程更广泛的使用。
|
||||
|
||||
Node12 更新了 V8 引擎,随着 V8 的更新,很多 ES 新规范也落地了,比如 Class 成员函数、私有成员变量等等。
|
||||
|
||||
# 4. 总结
|
||||
|
||||
Nodejs 仅有 10 年历史,但现在越来越被开发者欢迎,因为它可以让 JS 运行在服务端,是扩大 JS 生态的重要一环。从 Node 更新历史中可以看到,性能和语法能力稳步提升,一些服务端环境需要的诊断报告、堆栈分析能力都在逐渐完善,社区上也有 Alinode 与 egg、express、koa 等好用的服务框架,相对于前端翻天覆地的变化,对 Node 的评价只有一个字:稳。
|
||||
|
||||
|
||||
> 讨论地址是:[精读《Nodejs V12》 · Issue #184 · dt-fe/weekly](https://github.com/dt-fe/weekly/issues/184)
|
||||
|
||||
**如果你想参与讨论,请 [点击这里](https://github.com/dt-fe/weekly),每周都有新的主题,周末或周一发布。前端精读 - 帮你筛选靠谱的内容。**
|
||||
|
||||
> 关注 **前端精读微信公众号**
|
||||
|
||||
<img width=200 src="https://img.alicdn.com/tfs/TB165W0MCzqK1RjSZFLXXcn2XXa-258-258.jpg">
|
||||
|
||||
> 版权声明:自由转载-非商用-非衍生-保持署名([创意共享 3.0 许可证](https://creativecommons.org/licenses/by-nc-nd/3.0/deed.zh))
|
||||
Reference in New Issue
Block a user