Compare commits

...
25 Commits
Author SHA1 Message Date
ascoders 3a4b82bc76 Merge branch 'v2' of https://github.com/dt-fe/weekly into v2 2019-06-24 09:06:59 +08:00
ascoders 4f2ed7b3cb update 2019-06-24 09:06:55 +08:00
黄子毅 a72795b099 Merge pull request #168 from eos3tion/patch-1
Update 107.精读《Optional chaining》.md
2019-06-18 10:01:18 +08:00
ascoders dea2acc384 Merge branches 'v2' and 'v2' of https://github.com/dt-fe/weekly into v2 2019-06-17 10:46:28 +08:00
ascoders 74f539cc34 fix typo error 2019-06-17 10:44:18 +08:00
程方 1947b9c08c Update 107.精读《Optional chaining》.md
修正`.?`为`?.`
2019-06-17 10:34:16 +08:00
黄子毅 e84c85cc1d Merge pull request #167 from think2011/patch-2
Update 029.精读《JS 中的内存管理》.md
2019-06-17 09:27:00 +08:00
ascoders 170c0f4525 Merge branch 'v2' of https://github.com/dt-fe/weekly into v2 2019-06-17 09:23:37 +08:00
ascoders c836848228 107 2019-06-17 09:23:32 +08:00
曾浩 7b87d5c3c4 Update 029.精读《JS 中的内存管理》.md 2019-06-16 20:27:28 +08:00
黄子毅 add5bdd01e Merge pull request #166 from think2011/patch-1
Update 019.精读《最佳前端面试题》及面试官技巧.md
2019-06-14 14:27:15 +08:00
曾浩 0633764a44 Update 019.精读《最佳前端面试题》及面试官技巧.md 2019-06-14 13:47:36 +08:00
黄子毅 fcc183d523 Update 103.精读《为什么专家不再关心技术细节》.md 2019-06-11 10:46:30 +08:00
ascoders 627db8a249 update 2019-06-10 09:01:29 +08:00
ascoders b41530b37c Merge branch 'v2' of https://github.com/dt-fe/weekly into v2 2019-06-10 09:01:06 +08:00
ascoders 2062b652dd 106 2019-06-10 09:00:27 +08:00
黄子毅 365997863e Merge pull request #161 from xuhongbo/patch-1
Update 105.精读《What's new in javascript》.md
2019-06-03 14:57:32 +08:00
黄子毅 ab6978da4a Merge pull request #160 from Kerminate/v2
fix: 对大数描述的修正
2019-06-03 10:39:31 +08:00
leoxu f37d496f1b Update 105.精读《What's new in javascript》.md
缺少 `=`
2019-06-03 10:33:07 +08:00
Kerminate 3bf34602b4 fix: 对大数描述的修正 2019-06-03 10:20:54 +08:00
ascoders 274ffc0a9f Merge branch 'v2' of https://github.com/dt-fe/weekly into v2 2019-06-03 09:19:56 +08:00
ascoders 72e8b69dd7 104 2019-06-03 09:19:47 +08:00
黄子毅 62ac1d76b9 Merge pull request #158 from xuhongbo/patch-1
Update 101.精读《持续集成 vs 持续交付 vs 持续部署》.md
2019-05-27 14:06:12 +08:00
leoxu e537efe7bd Update 101.精读《持续集成 vs 持续交付 vs 持续部署》.md
格式问题,没有换行,导致读者会有疑惑,并不能直观的看到产出
2019-05-27 11:27:10 +08:00
ascoders f1871f59e5 fix 2019-05-27 10:13:26 +08:00
9 changed files with 1152 additions and 9 deletions
@@ -96,7 +96,7 @@
最后考察候选人的发展潜力与工作态度,我们一般通过询问简单的算法问题,进一步了解候选人是否对技术真正感兴趣,而不只是对前端工程感兴趣。同时,算法问题也考察候选人解决抽象问题的能力,或者让候选人设计一个组件,通过对组件需求的不断升级,考察候选人是否能及时给出解决方案。
最后工作态度,首先会考察人品,对不懂的知识点装懂是违背诚信的行为,任何团队都不会要的。同时,**不正视自己技术存在的盲点,将是技术发展的最大阻碍**。不过这里也不怕被候选人套路,如果全部都回答不懂那也不用考虑了。
最后工作态度,首先会考察人品,对不懂的知识点装懂是违背诚信的行为,任何团队都不会要的。同时,**不正视自己技术存在的盲点,将是技术发展的最大阻碍**。不过这里也不怕被候选人套路,如果全部都回答不懂那也不用考虑了。
# 3 总结
+2 -2
View File
@@ -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)
@@ -51,7 +51,9 @@ CI/CD 具体是个什么样的流程呢,如下图所示,差异仅在于是
- 需要有持续集成的基础,测试用例需要覆盖足够的代码
- 部署需要自动化,用户只需要手动触发,剩余的部署应该自动化
- 团队需要增加新特性标志,避免未完成的新特性进入待发布的产品
产出:
产出:
- 部署软件变得非常简单。团队不需要花费 n 天准备发布。
- 可以提高发布频率,加速新特性触达用户进程。
- 小的更改,对决策的压力要小得多,可以更快地迭代。
@@ -63,7 +65,9 @@ CI/CD 具体是个什么样的流程呢,如下图所示,差异仅在于是
- 测试必须要做到足够。测试的质量将决定发布的质量。
- 文档建设需要和产品部署保持同步。
- 新特性的发布需要协调其他部门,包括售后支持&市场&推广等。
产出:
产出:
- 快速的发布节奏,因为每个新特性一旦完成都会自动的发布给用户。
- 发布风险降低,修复问题更容易,因为每次变更都是小步迭代发布。
- 用户可以看到持续性的优化和质量提升,而不是非要等到按月,按季度,甚至按年
@@ -117,7 +117,7 @@ CEO 通过顶层设计调动了全公司资源,而业务线总裁通过任务
1. 技术细节学习难度不大,在需要深入的时候再深入了解最佳。
2. 想要做成事,需要更宏观的技术思维,所以专家渐渐变得眼光宽阔,格局很大。
3. 专家拥有快速学习技术细节的能力,只是这已不是其核心竞争力,所以与其写技术细节的文章,如写方法论的思考带来的价值更大。
3. 专家拥有快速学习技术细节的能力,只是这已不是其核心竞争力,所以与其写技术细节的文章,如写方法论的思考带来的价值更大。
4. 指引方向比走路更重要,专家都要逐渐成为引路人。
5. 技术最终为业务服务,懂技术细节和让业务先赢没有必然的关系,所以在深入技术细节之前,要先理解业务,把握方向,防止技术细节出现路线问题。
+3 -3
View File
@@ -271,7 +271,7 @@ function Counter() {
const log = () => {
setCount(1 + 1);
setTimeout(() => {
console.log(currentCount.current); // 此时 currentCount.current: 3
console.log(currentCount.current);
}, 3000);
};
@@ -293,7 +293,7 @@ function Counter() {
const log = () => {
setCount(2 + 1);
setTimeout(() => {
console.log(currentCount.current); // 此时 currentCount.current: 3
console.log(currentCount.current);
}, 3000);
};
@@ -315,7 +315,7 @@ function Counter() {
const log = () => {
setCount(3 + 1);
setTimeout(() => {
console.log(currentCount.current); // 此时 currentCount.current: 3
console.log(currentCount.current);
}, 3000);
};
+429
View File
@@ -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-1818-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)
+383
View File
@@ -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 literala?.\`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
View File
@@ -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)