Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
63e2ca4d0d | ||
|
|
9dbd1fb7b9 | ||
|
|
7d8816c2c2 | ||
|
|
44ae420f52 | ||
|
|
683b22d1ae | ||
|
|
4e58379585 | ||
|
|
1342144fef | ||
|
|
a41f452df0 | ||
|
|
0950c4cdaf | ||
|
|
46ad26c1a4 | ||
|
|
6dd26bee54 | ||
|
|
37d6b5f3f0 | ||
|
|
f23e88338f | ||
|
|
3dda46d760 | ||
|
|
cd6a00bf3a | ||
|
|
6aad1da704 | ||
|
|
03a5a0f484 | ||
|
|
0066b890d4 | ||
|
|
e0caf77d0a | ||
|
|
1517e86999 | ||
|
|
c8f86df3f3 | ||
|
|
752d598d27 | ||
|
|
e5fd022f00 | ||
|
|
f78c4fb511 | ||
|
|
41a90e5991 | ||
|
|
a4efe24cca | ||
|
|
1a3b36074e | ||
|
|
8dad4956b7 | ||
|
|
852d35501c | ||
|
|
b9d95182a1 | ||
|
|
bfa3ab7b55 | ||
|
|
304e4d3aa3 | ||
|
|
62be743112 | ||
|
|
6d27e723cd | ||
|
|
704c9cda9e | ||
|
|
44dd8057e2 | ||
|
|
d956b15ad0 | ||
|
|
961d77b4d1 | ||
|
|
c763fc15bd | ||
|
|
f9f04d711f | ||
|
|
5d40208d45 |
@@ -0,0 +1,2 @@
|
||||
/node_modules
|
||||
/yarn.lock
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"excludeFiles": [],
|
||||
"rules": {
|
||||
"no-long-code": 0,
|
||||
"no-trailing-punctuation": 0
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
language: node_js
|
||||
node_js:
|
||||
- "10"
|
||||
before_install:
|
||||
- npm i -g lint-md-cli
|
||||
script: lint-md ./
|
||||
+5
-5
@@ -10,7 +10,7 @@
|
||||
|
||||
<img src="assets/1/cube.jpeg" alt="logo" width="500" />
|
||||
|
||||
> 如今,Javascript 模块化规范非常方便、自然,但这个新规范仅执行了2年,就在 4 年前,js 的模块化还停留在运行时支持,10 年前,通过后端模版定义、注释定义模块依赖。对经历过来的人来说,历史的模块化方式还停留在脑海中,反而新上手的同学会更快接受现代的模块化规范。
|
||||
> 如今,Javascript 模块化规范非常方便、自然,但这个新规范仅执行了 2 年,就在 4 年前,js 的模块化还停留在运行时支持,10 年前,通过后端模版定义、注释定义模块依赖。对经历过来的人来说,历史的模块化方式还停留在脑海中,反而新上手的同学会更快接受现代的模块化规范。
|
||||
|
||||
但为什么要了解 Javascript 模块化发展的历史呢?因为凡事都有两面性,了解 Javascript 模块化规范,有利于我们思考出更好的模块化方案,纵观历史,从 1999 年开始,模块化方案最多维持两年,就出现了新的替代方案,比原有的模块化更清晰、强壮,我们不能被现代模块化方式限制住思维,因为现在的 ES2015 模块化方案距离发布也仅仅过了两年。
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
|
||||
**外部依赖定义 (2007)**: 这种定义方式在 cocos2d-js 开发中普遍使用,其核心思想是将依赖抽出单独文件定义,这种方式不利于项目管理,毕竟依赖抽到代码之外,我是不是得两头找呢?所以才有通过 webpack 打包为一个文件的方式暴力替换为 commonjs 的方式出现。
|
||||
|
||||
**Sandbox模式 (2009)**: 这种模块化方式很简单,暴力,将所有模块塞到一个 `sanbox` 变量中,硬伤是无法解决明明冲突问题,毕竟都塞到一个 `sandbox` 对象里,而 `Sandbox` 对象也需要定义在全局,存在被覆盖的风险。模块化需要保证全局变量尽量干净,目前为止的模块化方案都没有很好的做到这一点。
|
||||
**Sandbox 模式 (2009)**: 这种模块化方式很简单,暴力,将所有模块塞到一个 `sandbox` 变量中,硬伤是无法解决命名冲突问题,毕竟都塞到一个 `sandbox` 对象里,而 `Sandbox` 对象也需要定义在全局,存在被覆盖的风险。模块化需要保证全局变量尽量干净,目前为止的模块化方案都没有很好的做到这一点。
|
||||
|
||||
**依赖注入 (2009)**: 就是大家熟知的 angular1.0,依赖注入的思想现在已广泛运用在 react、vue 等流行框架中。但依赖注入和解决模块化问题还差得远。
|
||||
|
||||
@@ -52,7 +52,7 @@
|
||||
|
||||
这篇文章所提供的模块化历史的方案都是逻辑模块化,**从 CommonJS 方案开始前端把服务端的解决方案搬过来之后,算是看到标准物理与逻辑统一的模块化**。但之后前端工程不得不引入模块化构建这一步。正是这一步给前端开发无疑带来了诸多的不便,尤其是现在我们开发过程中经常为了优化这个工具带了很多额外的成本。
|
||||
|
||||
从 CommonJS 之前其实都只是封装,并没有一套模块化规范,这个就有些像类与包的概念。我在10年左右用的最多的还是 YUI2,YUI2 是用 namespace 来做模块化的,但有很多问题没有解决,比如多版本共存,因此后来 YUI3 出来了。
|
||||
从 CommonJS 之前其实都只是封装,并没有一套模块化规范,这个就有些像类与包的概念。我在 10 年左右用的最多的还是 YUI2,YUI2 是用 namespace 来做模块化的,但有很多问题没有解决,比如多版本共存,因此后来 YUI3 出来了。
|
||||
|
||||
```javascript
|
||||
YUI().use('node', 'event', function (Y) {
|
||||
@@ -118,7 +118,7 @@ YUI3 的 sandbox 像极了差不多同时出现的 AMD 规范,但早期 yahoo
|
||||
### 补充阅读
|
||||
|
||||
- [JavaScript 模块化七日谈](https://huangxuan.me/2015/07/09/js-module-7day/)
|
||||
- [JavaScript模块化编程简史(2009-2016)](https://yuguo.us/weblog/javascript-module-development-history/)
|
||||
- [JavaScript 模块化编程简史(2009-2016)](https://yuguo.us/weblog/javascript-module-development-history/)
|
||||
|
||||
# 总结
|
||||
|
||||
@@ -136,4 +136,4 @@ YUI3 的 sandbox 像极了差不多同时出现的 AMD 规范,但早期 yahoo
|
||||
|
||||
至此,对于 javascript 模块化讨论已接近尾声,对其优缺点也基本达成了一致。前端复杂度不断提高,促使着模块化的改进,代理(浏览器、node) 的支持程度,与前端特殊性(流量、缓存)可能前端永远也离不开构建工具,新的标准会让这些工作做的更好,同时取代、增强部分特征,前端的未来是更加美好的,复杂度也更高。
|
||||
|
||||
**如果你想参与讨论,请[点击这里](https://github.com/dt-fe/weekly),每周都有新的主题,每周五发布。**
|
||||
**如果你想参与讨论,请[点击这里](https://github.com/dt-fe/weekly),每周都有新的主题,每周五发布。**
|
||||
|
||||
+1
-1
@@ -72,7 +72,7 @@
|
||||
|
||||
### 可访问性的反思
|
||||
|
||||
Accessibility 翻译过来是『无障碍访问』,是对不同终端用户的体验完善。每一个模态框,都要有通过键盘关闭的功能,通常使用ESC键。似乎我们程序员多少总会把我们自我的惯性思维带进实现的产品,尤其是当我们敲着外置的键盘,用着 PC 的时候。
|
||||
Accessibility 翻译过来是『无障碍访问』,是对不同终端用户的体验完善。每一个模态框,都要有通过键盘关闭的功能,通常使用 ESC 键。似乎我们程序员多少总会把我们自我的惯性思维带进实现的产品,尤其是当我们敲着外置的键盘,用着 PC 的时候。
|
||||
|
||||
下面的这些问题都是对可访问性的反思:
|
||||
|
||||
|
||||
+6
-6
@@ -17,7 +17,7 @@
|
||||
**前端渲染的优势**
|
||||
|
||||
- 局部刷新。无需每次都进行完整页面请求
|
||||
- 懒加载。如在页面初始时只加载可视区域内的数据,滚动后rp加载其它数据,可以通过 react-lazyload 实现
|
||||
- 懒加载。如在页面初始时只加载可视区域内的数据,滚动后 rp 加载其它数据,可以通过 react-lazyload 实现
|
||||
- 富交互。使用 JS 实现各种酷炫效果
|
||||
- 节约服务器成本。省电省钱,JS 支持 CDN 部署,且部署极其简单,只需要服务器支持静态文件即可
|
||||
- 天生的关注分离设计。服务器来访问数据库提供接口,JS 只关注数据获取和展现
|
||||
@@ -36,7 +36,7 @@
|
||||
|
||||
本次提出独到观点的同学有:[@javie007](http://link.zhihu.com/?target=https%3A//github.com/javie007) [@杨森](https://www.zhihu.com/people/c93b7957f6308990c7e3b16103c9356b) [@流形](https://www.zhihu.com/people/6c772f9726a914ed4a4b90c88010461c) [@camsong](https://www.zhihu.com/people/078cc0fb15845759ad8295b0f0e50099) [@Turbe Xue](https://www.zhihu.com/people/turbe-xue) [@淡苍](https://www.zhihu.com/people/5ac53c9c0484e83672e1c1716bdf0ff9) [@留影](https://www.zhihu.com/people/38c3c75795824de1bc5d99cff904a832) [@FrankFang](http://link.zhihu.com/?target=https%3A//github.com/FrankFang) [@alcat2008](http://link.zhihu.com/?target=https%3A//github.com/alcat2008) [@xile611](http://link.zhihu.com/?target=https%3A//github.com/xile611) [@twobin](http://link.zhihu.com/?target=https%3A//github.com/twobin) [@黄子毅](https://www.zhihu.com/people/3ec85a04bc9eaa35b1830874cc463a52) 精读由此归纳。
|
||||
|
||||
大家对前端和后端渲染的现状基本达成共识。即前端渲染是未来趋势,但前端渲染遇到了首屏性能和SEO的问题。对于同构争议最多,在此我归纳一下。
|
||||
大家对前端和后端渲染的现状基本达成共识。即前端渲染是未来趋势,但前端渲染遇到了首屏性能和 SEO 的问题。对于同构争议最多,在此我归纳一下。
|
||||
|
||||
### 前端渲染遇到的问题
|
||||
|
||||
@@ -46,7 +46,7 @@ SEO 很好理解。由于传统的搜索引擎只会从 HTML 中抓取数据,
|
||||
|
||||
### 同构的优点
|
||||
|
||||
同构恰恰就是为了解决前端渲染遇到的问题才产生的,至 2014 年底伴随着 React 的崛起而被认为是前端框架应具备的一大杀器,以至于当时很多人为了用此特性而[放弃 Angular 1 而转向 React](http://link.zhihu.com/?target=https%3A//blog.risingstack.com/from-angularjs-to-react-the-isomorphic-way/)。然而近3年过去了,很多产品逐渐从全栈同构的理想化逐渐转到首屏或部分同构。让我们再一次思考同构的优点真是优点吗?
|
||||
同构恰恰就是为了解决前端渲染遇到的问题才产生的,至 2014 年底伴随着 React 的崛起而被认为是前端框架应具备的一大杀器,以至于当时很多人为了用此特性而[放弃 Angular 1 而转向 React](http://link.zhihu.com/?target=https%3A//blog.risingstack.com/from-angularjs-to-react-the-isomorphic-way/)。然而近 3 年过去了,很多产品逐渐从全栈同构的理想化逐渐转到首屏或部分同构。让我们再一次思考同构的优点真是优点吗?
|
||||
|
||||
1. 有助于 SEO
|
||||
|
||||
@@ -65,7 +65,7 @@ SEO 很好理解。由于传统的搜索引擎只会从 HTML 中抓取数据,
|
||||
|
||||
1. 性能
|
||||
|
||||
把原来放在几百万浏览器端的工作拿过来给你几台服务器做,这还是花挺多计算力的。尤其是涉及到图表类需要大量计算的场景。这方面调优,可以参考 [walmart的调优策略](https://medium.com/walmartlabs/reactjs-ssr-profiling-and-caching-5d8e9e49240c)。
|
||||
把原来放在几百万浏览器端的工作拿过来给你几台服务器做,这还是花挺多计算力的。尤其是涉及到图表类需要大量计算的场景。这方面调优,可以参考 [walmart 的调优策略](https://medium.com/walmartlabs/reactjs-ssr-profiling-and-caching-5d8e9e49240c)。
|
||||
|
||||
个性化的缓存是遇到的另外一个问题。可以把每个用户个性化信息缓存到浏览器,这是一个天生的分布式缓存系统。我们有个数据类应用通过在浏览器合理设置缓存,双十一当天节省了 70% 的请求量。试想如果这些缓存全部放到服务器存储,需要的存储空间和计算都是很非常大。
|
||||
|
||||
@@ -97,7 +97,7 @@ export const isSsr = () => (
|
||||
|
||||
5. simple store(redux)
|
||||
|
||||
这个 store 是必须以字符串形式塞到前端,所以复杂类型是无法转义成字符串的,比如function。
|
||||
这个 store 是必须以字符串形式塞到前端,所以复杂类型是无法转义成字符串的,比如 function。
|
||||
|
||||
总的来说,同构渲染实施难度大,不够优雅,无论在前端还是服务端,都需要额外改造。
|
||||
|
||||
@@ -136,7 +136,7 @@ Next.js 是时下非常流行的基于 React 的同构开发框架。作者之
|
||||
|
||||
1. 巧妙地用标准化的解决了请求的问题。同构和页面开发类似,异步是个大难题,异步中难点又在接口请求。Next.js 给组件新增了 getInitialProps 方法来专门处理初始化请求,再也不用手动往页面上塞 DATA 和调用 ReactDOMServer.renderToString
|
||||
2. 使用 [styled-jsx](https://github.com/zeit/styled-jsx) 解决了 css-in-js 的问题。这种方案虽然不像 styled-component 那样强大,但足够简单,可以说是最小的成本解决了问题
|
||||
3. Fast by default。页面默认拆分文件方式打包,支持Prefetch页面预加载
|
||||
3. Fast by default。页面默认拆分文件方式打包,支持 Prefetch 页面预加载
|
||||
|
||||
全家桶式的的解决方案。简洁清晰的目录结构,这一点 Redux 等框架真应该学一学。不过全家桶的方案比较适合全新项目使用,旧项目使用要评估好成本
|
||||
|
||||
|
||||
+3
-3
@@ -22,7 +22,7 @@
|
||||
3. 局部与全局状态的归一
|
||||
4. 分形思想
|
||||
5. action 分散执行
|
||||
5. app级别数据处理,推荐前端 `Orm`
|
||||
5. app 级别数据处理,推荐前端 `Orm`
|
||||
|
||||
整体来看,核心思路是推荐组件内部完成数据流的处理,不用关心使用了 `Redux` `Mobx` 或者 `Rxjs`,也不用关心这些库是否有全局管理的野心,如果全局管理那就挂载到全局,但组件内部还是局部管理。
|
||||
|
||||
@@ -42,7 +42,7 @@
|
||||
|
||||
以 Mobx 为代表,轻前端用的较多,因为复杂度集中在后端,前端做好数据展示即可,那么直接拥抱 js 这种基于对象的语言,结合原生 `Map` `Proxy` `Reflect` 将副作用进行到底,开发速度快得飞起。
|
||||
|
||||
数据存储方式按照视图形态来,因为视图之间几乎毫无关联,而且特别是数据产品,后端数据量巨大,把数据处理过程搬到前端是不可能的(为了推导出一个视图形态数据,需要动辄几GB的原始数据运算,存储和性能都不适合在前端做)。
|
||||
数据存储方式按照视图形态来,因为视图之间几乎毫无关联,而且特别是数据产品,后端数据量巨大,把数据处理过程搬到前端是不可能的(为了推导出一个视图形态数据,需要动辄几 GB 的原始数据运算,存储和性能都不适合在前端做)。
|
||||
|
||||
#### 函数式
|
||||
|
||||
@@ -75,7 +75,7 @@
|
||||
|
||||
### 分形的缺点
|
||||
|
||||
对于聊天室或者在线IDE等,全局数据居多,很多交叉绑定的情况,就不适合分形思想,反而纯 Redux 思想更合适。
|
||||
对于聊天室或者在线 IDE 等,全局数据居多,很多交叉绑定的情况,就不适合分形思想,反而纯 Redux 思想更合适。
|
||||
|
||||
## 3.3 数据形态,是原始数据还是视图数据?
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
|
||||
Stack 部分主要在阐明 js 中函数调用栈的概念,它符合栈的基本特性『当调用时,压入栈顶。当它执行完毕时,被弹出栈』,简单看下面的代码:
|
||||
|
||||
```
|
||||
```plain
|
||||
function c() {
|
||||
try {
|
||||
var bar = baz;
|
||||
@@ -46,7 +46,7 @@ a();
|
||||
|
||||
## 如何使用堆栈追踪
|
||||
|
||||
该部分以 NodeJS 环境为例,讲解了 `Error.captureStackTrace `,将 stack 信息作为属性存储在一个对象当中,同时可以过滤掉一些无用的堆栈信息。这样可以隐藏掉用户不需要了解的内部细节。作者也以 Chai 为例,内部使用该方法对代码的调用者屏蔽了不相关的实现细节。通过以 Assertion 对象为例,讲述了具体的内部实现,简单来说通过一个 addChainableMethod 链式调用工具方法,在运行一个 Assertion 时,将它设为标记,其后面的堆栈会被移除;如果 assertion 失败移除起后面所有内部堆栈;如果有内嵌 assertion,将当前 assertion 的方法放到 ssfi 中作为标记,移除后面堆栈帧;
|
||||
该部分以 NodeJS 环境为例,讲解了 `Error.captureStackTrace`,将 stack 信息作为属性存储在一个对象当中,同时可以过滤掉一些无用的堆栈信息。这样可以隐藏掉用户不需要了解的内部细节。作者也以 Chai 为例,内部使用该方法对代码的调用者屏蔽了不相关的实现细节。通过以 Assertion 对象为例,讲述了具体的内部实现,简单来说通过一个 addChainableMethod 链式调用工具方法,在运行一个 Assertion 时,将它设为标记,其后面的堆栈会被移除;如果 assertion 失败移除起后面所有内部堆栈;如果有内嵌 assertion,将当前 assertion 的方法放到 ssfi 中作为标记,移除后面堆栈帧;
|
||||
|
||||
# 3. 精读
|
||||
参与本次精读的同学有:[范洪春](https://www.zhihu.com/people/fanhc/activities)、[黄子毅](https://www.zhihu.com/people/huang-zi-yi-83/answers)、[杨森](https://www.zhihu.com/people/yangsen/answers)、[camsong](https://www.zhihu.com/people/camsong/answers),该部分由他们的观点总结而出。
|
||||
@@ -83,7 +83,7 @@ describe('should.js和power-assert的区别', () => {
|
||||
|
||||
- 区分操作异常和程序员的失误。操作异常指可预测的不可避免的异常,如无法连接服务器
|
||||
- 操作异常应该被处理。程序员的失误不需要处理,如果处理了反而会影响错误排查
|
||||
- 操作异常有两种处理方式:同步 (try…catch) 和异步(callback, event - emitter)两种处理方式,但只能选择其中一种。
|
||||
- 操作异常有两种处理方式:同步 (try……catch) 和异步(callback, event - emitter)两种处理方式,但只能选择其中一种。
|
||||
- 函数定义时应该用文档写清楚参数类型,及可能会发生的合理的失败。以及错误是同步还是异步传给调用者的
|
||||
- 缺少参数或参数无效是程序员的错误,一旦发生就应该 throw。
|
||||
传递错误时,使用标准的 Error 对象,并附件尽可能多的错误信息,可以使用标准的属性名
|
||||
|
||||
@@ -81,7 +81,7 @@ react-css-modules 引入了 styleName,将本地变量和全局变量很清晰
|
||||
|
||||
另外,使用 react-css-modules,可以方便的覆盖本地变量的样式:
|
||||
|
||||
```
|
||||
```plain
|
||||
import customStyles from './table-custom-styles.css';
|
||||
|
||||
<Table styles={customStyles} />;
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
|
||||
# 2 内容概要
|
||||
|
||||
使用 Object.assign 作用于大对象时,速度会成为瓶颈,比如拥有 `100,000` 个属性的对象,这个操作耗费了 134ms。性能损失主要原因是 “结构共享” 操作需要遍历近10万个属性,而这些引用操作耗费了100ms以上的时间。
|
||||
使用 Object.assign 作用于大对象时,速度会成为瓶颈,比如拥有 `100,000` 个属性的对象,这个操作耗费了 134ms。性能损失主要原因是 “结构共享” 操作需要遍历近 10 万个属性,而这些引用操作耗费了 100ms 以上的时间。
|
||||
|
||||
解决办法就是减少引用指向的操作数量,而且由于引用指向到任何对象的损耗都几乎一致(无论目标对象极限小或者无穷大,引用消耗时间都几乎没有区别),我们需要一种精心设计的树状结构将打平的引用建立深度,以减少引用操作次数,`vector tries` 就是一种解决思路:
|
||||
|
||||
|
||||
+16
-16
@@ -7,29 +7,29 @@
|
||||
|
||||
我为什么要选这篇文章呢?
|
||||
|
||||
就在前几天的 Google I/O 2017上, Polymer 正式发布了 [Polymer 2.0](https://www.polymer-project.org/blog/2017-05-15-time-for-two) 版本.
|
||||
就在前几天的 Google I/O 2017 上, Polymer 正式发布了 [Polymer 2.0](https://www.polymer-project.org/blog/2017-05-15-time-for-two) 版本.
|
||||
|
||||
来看一下 Polymer 2.0 的一些变化:
|
||||
- 使用 Shadow DOM v1 代替 Polymer.dom. Shady DOM从 Polymer 中分离出来。
|
||||
- 使用 Shadow DOM v1 代替 Polymer.dom. Shady DOM 从 Polymer 中分离出来。
|
||||
- 使用 标准的 ES6 类和 Custom Elements v1 来自定义元素.
|
||||
- 还有数据系统的改进和生命周期的变更.
|
||||
|
||||
可以看到, Polymer 的这次升级主要是将 Shadow Dom 和 Custom Elements 升级到 v1 版本, 以获得更多浏览器的原生支持. 下一 代Web Components - v1规范,Chrome 已经支持了,Web Components 规范中的2个主要部分 - [Shadow Dom](https://www.chromestatus.com/feature/4667415417847808) 和 [Custom Elements](https://www.chromestatus.com/feature/4696261944934400). Safari在10版本中, 支持了 [Shadow DOM v1](https://webkit.org/status/#feature-shadow-dom) 规范并且完成了在Webkit内核中对 [Custom Elements v1](https://webkit.org/blog/7027/introducing-custom-elements/) 规范的实现;Firefox对 [Shadow DOM](https://platform-status.mozilla.org/#shadow-dom) 和 [Custom Elements v1规范](https://platform-status.mozilla.org/#custom-elements) 支持正在开发中;Edge也将对 [Shadow DOM](https://developer.microsoft.com/en-us/microsoft-edge/platform/status/shadowdom/) 和 [Custom Elements](https://developer.microsoft.com/en-us/microsoft-edge/platform/status/customelements/) 支持规划到他们的开发roadmap中。
|
||||
可以看到, Polymer 的这次升级主要是将 Shadow Dom 和 Custom Elements 升级到 v1 版本, 以获得更多浏览器的原生支持. 下一 代 Web Components - v1 规范,Chrome 已经支持了,Web Components 规范中的 2 个主要部分 - [Shadow Dom](https://www.chromestatus.com/feature/4667415417847808) 和 [Custom Elements](https://www.chromestatus.com/feature/4696261944934400). Safari 在 10 版本中, 支持了 [Shadow DOM v1](https://webkit.org/status/#feature-shadow-dom) 规范并且完成了在 Webkit 内核中对 [Custom Elements v1](https://webkit.org/blog/7027/introducing-custom-elements/) 规范的实现;Firefox 对 [Shadow DOM](https://platform-status.mozilla.org/#shadow-dom) 和 [Custom Elements v1 规范](https://platform-status.mozilla.org/#custom-elements) 支持正在开发中;Edge 也将对 [Shadow DOM](https://developer.microsoft.com/en-us/microsoft-edge/platform/status/shadowdom/) 和 [Custom Elements](https://developer.microsoft.com/en-us/microsoft-edge/platform/status/customelements/) 支持规划到他们的开发 roadmap 中。
|
||||
|
||||
这段时间, 大家都在讨论 react, vue, angular, 这些框架. 或者 该使用 redux 还 是 mobx 做数据管理. 在这个契机下, 我想我们可以不单单去思考这些框架, 也可以更多地去思考和了解 Web Components 标准. 对于 Web Components标准有一些思考. 所以我选了一篇关于 Web Components 的文章, 想让大家对于 Web Components 的发展, 和 Web Componets 与现在的主流框架如何协作有更多的思考和讨论.
|
||||
这段时间, 大家都在讨论 react, vue, angular, 这些框架. 或者 该使用 redux 还 是 mobx 做数据管理. 在这个契机下, 我想我们可以不单单去思考这些框架, 也可以更多地去思考和了解 Web Components 标准. 对于 Web Components 标准有一些思考. 所以我选了一篇关于 Web Components 的文章, 想让大家对于 Web Components 的发展, 和 Web Componets 与现在的主流框架如何协作有更多的思考和讨论.
|
||||
|
||||
|
||||
# 2 内容概要
|
||||
|
||||
**The broken promise of Web Components**
|
||||
原文作者dmitriid主要是在喷Web Components从2011年到2017年这6年间毫无进展, 一共产出了6份标准, 其中两份已经被弃用. 几乎只有一个主流浏览器(chrome) 支持.
|
||||
原文作者 dmitriid 主要是在喷 Web Components 从 2011 年到 2017 年这 6 年间毫无进展, 一共产出了 6 份标准, 其中两份已经被弃用. 几乎只有一个主流浏览器(chrome) 支持.
|
||||
|
||||

|
||||
|
||||
|
||||
- Web Components 这些规范强依赖 JS 的实现
|
||||
- Custom Elements 是 JS 脚本的一部分
|
||||
- HTML Templates 的出现就是为了被JS 脚本使用
|
||||
- HTML Templates 的出现就是为了被 JS 脚本使用
|
||||
- Shadow Dom 也需要配合 JS 脚本使用
|
||||
- 只有 HTML imports 可以脱离 JS 脚本使用
|
||||
- Web Components 操作 DOM
|
||||
@@ -38,16 +38,16 @@
|
||||
- 为了突破限制使用不同的方法来传递数据
|
||||
- CSS 作用域, 可以见上次精读[《请停止 css-in-js 的行为》](https://github.com/dt-fe/weekly/issues/12)
|
||||
|
||||
**来看一下Polymer 的 核心成员 Rob Dodson 对于本文的回应: Regarding the broken promise of Web Components**
|
||||
**来看一下 Polymer 的 核心成员 Rob Dodson 对于本文的回应: Regarding the broken promise of Web Components**
|
||||
|
||||
- Web Components 特性需要被浏览器支持,必须有平缓的过渡,良好的兼容,以及成熟的方案,因此推进速度会比较慢一些。
|
||||
- React 很棒, 但是也不要忽略其他基于 Web Components 的优秀库比如 [Amp](https://www.ampproject.org/)
|
||||
- 对于 DOM 更新的抽象比如 React/JSX很赞, 但是也带来了一些损耗. 在旧的移动设备上, 加载一个大的js 包性能依旧不理想, 最佳的做法是拆分你的 JS 包, 按需加载.
|
||||
- 使用 JSX 和 虚拟 DOM是很酷, 也可以直接把 JSX 用在 Web Components 内, 像[SkateJS](https://github.com/skatejs/skatejs)库, 已经在做这个事情了.
|
||||
- 没有标准的数据绑定, Polymer的数据绑定, 现在是基于[MDV](https://github.com/toolkitchen/mdv), 很多开发者更倾向于基于 Observables或者 ES6 Proxies的数据绑定方案.
|
||||
- 处理组件的字符串属性是很烦人, 但是由于每一个组件都是一个类的实例, 可以利用ES6 的 getters/setters来改变属性.
|
||||
- 对于 DOM 更新的抽象比如 React/JSX 很赞, 但是也带来了一些损耗. 在旧的移动设备上, 加载一个大的 js 包性能依旧不理想, 最佳的做法是拆分你的 JS 包, 按需加载.
|
||||
- 使用 JSX 和 虚拟 DOM 是很酷, 也可以直接把 JSX 用在 Web Components 内, 像[SkateJS](https://github.com/skatejs/skatejs)库, 已经在做这个事情了.
|
||||
- 没有标准的数据绑定, Polymer 的数据绑定, 现在是基于[MDV](https://github.com/toolkitchen/mdv), 很多开发者更倾向于基于 Observables 或者 ES6 Proxies 的数据绑定方案.
|
||||
- 处理组件的字符串属性是很烦人, 但是由于每一个组件都是一个类的实例, 可以利用 ES6 的 getters/setters 来改变属性.
|
||||
|
||||
Rob Dodson对于 Web Components 依然充满信心, 但是也承认推进标准总会有各种阻碍, 不可能像推荐框架一样快速把事情解决.
|
||||
Rob Dodson 对于 Web Components 依然充满信心, 但是也承认推进标准总会有各种阻碍, 不可能像推荐框架一样快速把事情解决.
|
||||
|
||||
# 3 精读
|
||||
|
||||
@@ -55,11 +55,11 @@ Rob Dodson对于 Web Components 依然充满信心, 但是也承认推进标准
|
||||
[@camsong](https://www.zhihu.com/people/078cc0fb15845759ad8295b0f0e50099) [@黄子毅](https://github.com/ascoders) [@杨森](https://www.zhihu.com/people/c93b7957f6308990c7e3b16103c9356b) [@rccoder](https://github.com/rccoder) [@alcat2008](https://github.com/alcat2008)精读由此归纳。
|
||||
|
||||
### 标准与框架
|
||||
Web Components 作为一个标准,骨子里的进度就会落后于当前可行的技术体系。正如文中所说,浏览器厂商 ship 一个新功能是很严肃的,很可能会影响到一票的线上业务,甚至会影响到一个产业(遥想当年 [Chrome Extension 禁用 NPAPI](https://blog.chromium.org/2013/09/saying-goodbye-to-our-old-friend-npapi.html)时的一片哀鸿遍野,许多返利插件都使用了这种技术)。那么 Web Components的缓慢推进也在情理之中了.
|
||||
即使真的有一天这个标准建立起来,Web Components作为浏览器底层特性不应该拿出来和React这类应用层框架相比较. 未来Web Components会做为浏览器非常重要的特性存在。API偏低层操作,会易用性不够. 在很长时间内开发者依旧会使用 React/Vue/Angular/Polymer 这样的框架,Web Components可能会做为这些框架的底层做一些 浏览器层面上的支持.
|
||||
Web Components 作为一个标准,骨子里的进度就会落后于当前可行的技术体系。正如文中所说,浏览器厂商 ship 一个新功能是很严肃的,很可能会影响到一票的线上业务,甚至会影响到一个产业(遥想当年 [Chrome Extension 禁用 NPAPI](https://blog.chromium.org/2013/09/saying-goodbye-to-our-old-friend-npapi.html)时的一片哀鸿遍野,许多返利插件都使用了这种技术)。那么 Web Components 的缓慢推进也在情理之中了.
|
||||
即使真的有一天这个标准建立起来,Web Components 作为浏览器底层特性不应该拿出来和 React 这类应用层框架相比较. 未来 Web Components 会做为浏览器非常重要的特性存在。API 偏低层操作,会易用性不够. 在很长时间内开发者依旧会使用 React/Vue/Angular/Polymer 这样的框架,Web Components 可能会做为这些框架的底层做一些 浏览器层面上的支持.
|
||||
|
||||
### 不需要 vendor 的自定义组件间调用
|
||||
在 Webpack 大行其道的时代,想在运行时做到组件即引即用变得很困难,因为这些组件大多是通过 React/Vue/Angular 开发的。不得不考虑引入一大堆 Vendor 包,这些 Vendor 里可能还必须包含 React 这类两个版本不能同时使用的库。目前我们团队在做组件化方案时就遇到这个问题,只能想办法避免两个版本的出现。你可以说这是 React 或 Webpack 引入的问题,但并没有看到 Web Compnents 标准化的解决方案。我想未来Web Components可能会作为浏览器的底层, 出现基于底层的标准方案来做组件间的相互应用的方法.
|
||||
在 Webpack 大行其道的时代,想在运行时做到组件即引即用变得很困难,因为这些组件大多是通过 React/Vue/Angular 开发的。不得不考虑引入一大堆 Vendor 包,这些 Vendor 里可能还必须包含 React 这类两个版本不能同时使用的库。目前我们团队在做组件化方案时就遇到这个问题,只能想办法避免两个版本的出现。你可以说这是 React 或 Webpack 引入的问题,但并没有看到 Web Compnents 标准化的解决方案。我想未来 Web Components 可能会作为浏览器的底层, 出现基于底层的标准方案来做组件间的相互应用的方法.
|
||||
|
||||
|
||||
### 为什么对 Web components 讨论不断
|
||||
@@ -69,7 +69,7 @@ Web Components 作为一个标准,骨子里的进度就会落后于当前可
|
||||
但使用前端框架的问题也日益暴露,随着前端框架种类的增多,同一个框架不同版本之间无法共存,导致组件无法跨框架复用,甚至只能固定在框架的某个版本,这与前端未来的模块化发展是相违背的,我们越是与之抗衡,就越希望 Web components 能站出来解决这个问题,因为浏览器原生支持模块化,相当于将 react angular vue 的能力内置在浏览器中,而且一定会向前兼容(这也是 Web components 推进缓慢的原因)。
|
||||
|
||||
# 4 总结
|
||||
我觉得 Web Components作为浏览器底层特性不应该拿出来和React, vue 这类应用层框架相比较. Web Components 的方向以及提供的价值都不会跟 应用框架一致. 而 Web Components 作为未来的 Web 组件标准 , 它在任何生态中都可以运行良好. 我倒是更加期待应用层去基于 Web Components 去做更多的实现, 让组件超越框架存在, 可以在不同技术栈中使用.
|
||||
我觉得 Web Components 作为浏览器底层特性不应该拿出来和 React, vue 这类应用层框架相比较. Web Components 的方向以及提供的价值都不会跟 应用框架一致. 而 Web Components 作为未来的 Web 组件标准 , 它在任何生态中都可以运行良好. 我倒是更加期待应用层去基于 Web Components 去做更多的实现, 让组件超越框架存在, 可以在不同技术栈中使用.
|
||||
|
||||
|
||||
> 讨论地址是:[精读《Web Components 的困境》 · Issue #15 · dt-fe/weekly](https://github.com/dt-fe/weekly/issues/15)
|
||||
|
||||
+1
-1
@@ -62,7 +62,7 @@ Chrome Dev Tools 非常强大,[dev-tips](https://umaar.com/dev-tips/) 列出
|
||||
|
||||
### 移动端控制台
|
||||
|
||||
- [Chrome远程调试](https://developers.google.com/web/tools/chrome-devtools/remote-debugging/webviews) app 支持后,连接 usb 或者局域网,即可通过 Dev Tools 调试 webview 页面。
|
||||
- [Chrome 远程调试](https://developers.google.com/web/tools/chrome-devtools/remote-debugging/webviews) app 支持后,连接 usb 或者局域网,即可通过 Dev Tools 调试 webview 页面。
|
||||
- [Weinre](http://people.apache.org/~pmuellr/weinre/docs/latest/Home.html) 通过页面加载脚本,与 pc 端调试器通信。
|
||||
- 通过内嵌控制台解决,比如 [eruda](http://eruda.liriliri.io/) [VConsole](https://github.com/WechatFE/vConsole)
|
||||
- [Rosin](http://alloyteam.github.io/Rosin/) fiddler 的一个插件,协助移动页面调试。
|
||||
|
||||
@@ -107,7 +107,7 @@ connect(props => ({
|
||||
|
||||
## HOC 的具体实践
|
||||
|
||||
HOC 在真实场景下的运行非常多,之前笔者在 [基于Decorator的组件扩展实践](https://zhuanlan.zhihu.com/p/22054582) 一文中也提过使用高阶组件将更细粒度的组件组合成 Selector 与 Search。结合精读文章,这次让我们通过 Form 组件的抽象来表现 HOC 具有的良好扩展机制。
|
||||
HOC 在真实场景下的运行非常多,之前笔者在 [基于 Decorator 的组件扩展实践](https://zhuanlan.zhihu.com/p/22054582) 一文中也提过使用高阶组件将更细粒度的组件组合成 Selector 与 Search。结合精读文章,这次让我们通过 Form 组件的抽象来表现 HOC 具有的良好扩展机制。
|
||||
|
||||
Form 中会包含各种不同的组件,常见的有 Input、Selector、Checkbox 等等,也会有根据业务需求加入的自定义组件。Form 灵活多变,从功能上看,表单校验可能为单组件值校验,也可能为全表单值校验,可能为常规检验,比如:非空、输入限制,也可能需要与服务端配合,甚至需要根据业务特点进行定制。从 UI 上看,检验结果显示的位置,可能在组件下方,也可能是在组件右侧。
|
||||
|
||||
@@ -115,7 +115,7 @@ Form 中会包含各种不同的组件,常见的有 Input、Selector、Checkbo
|
||||
|
||||

|
||||
|
||||
至于 HOC 在 Form 上的具体实现,首先将表单中的组件(Input、Selector...)与相应 validator 与组件值回调函数名(trigger)传入 Decorator,将 validator 与 trigger 相绑定。Decorator 完成了各种不同组件与 Form 内置 Store 间 value 的传递、校验功能的抽象,即精读文章中提到 Props Proxy 方式的其中两种作用:**提取state** 与 **操作props**
|
||||
至于 HOC 在 Form 上的具体实现,首先将表单中的组件(Input、Selector...)与相应 validator 与组件值回调函数名(trigger)传入 Decorator,将 validator 与 trigger 相绑定。Decorator 完成了各种不同组件与 Form 内置 Store 间 value 的传递、校验功能的抽象,即精读文章中提到 Props Proxy 方式的其中两种作用:**提取 state** 与 **操作 props**
|
||||
|
||||
```javascript
|
||||
function formFactoryFactory({
|
||||
|
||||
+1
-1
@@ -215,7 +215,7 @@ var bar = foo.bind(obj)
|
||||
bar()
|
||||
```
|
||||
|
||||
### 3.2.2 es6绑定
|
||||
### 3.2.2 es6 绑定
|
||||
|
||||
这种情况类似使用箭头函数创建成员变量,以下方式等于创建了没有挂载到原型链的匿名函数,因此 this 不会丢失。
|
||||
|
||||
|
||||
+21
-21
@@ -2,8 +2,8 @@
|
||||
|
||||
# 1 引言
|
||||
|
||||
随着前端ES6 ES7 的一路前行, 我们大前端借鉴和引进了各种其他编程语言中的概念、特性、模式;
|
||||
我们可以使用函数式Functional编程设计,可以使用面向对象OOP的设计,可以使用面向接口的思想,也可以使用AOP,
|
||||
随着前端 ES6 ES7 的一路前行, 我们大前端借鉴和引进了各种其他编程语言中的概念、特性、模式;
|
||||
我们可以使用函数式 Functional 编程设计,可以使用面向对象 OOP 的设计,可以使用面向接口的思想,也可以使用 AOP,
|
||||
可以使用注解,代理、反射,各种设计模式; 在大前端辉煌发展、在数据时代的当下 我们一起阅读了一篇设计相关的老文:
|
||||
《The DCI Architecture》
|
||||
一起来再探索和复习一下 相关的设计和思想
|
||||
@@ -11,15 +11,15 @@
|
||||
|
||||
# 2 内容摘要
|
||||
|
||||
DCI是数据Data 场景Context 交互Interactions 简称, 重点是关注 数据的不同场景的交互行为, 是面向对象系统 状态和行为的一种范式设计;
|
||||
DCI在许多方面是许多过去范式的统一,多年来这些模式已经成为面向对象编程的辅助工具。
|
||||
DCI 是数据 Data 场景 Context 交互 Interactions 简称, 重点是关注 数据的不同场景的交互行为, 是面向对象系统 状态和行为的一种范式设计;
|
||||
DCI 在许多方面是许多过去范式的统一,多年来这些模式已经成为面向对象编程的辅助工具。
|
||||
|
||||
尽管面向切面的编程(AOP)也有其他用途,但DCI满足了许多AOP的应用以及Aspects在解决问题方面的许多目标。根据AOP的基本原理,DCI基于深层次的反射或元编程。
|
||||
与Aspects不同,角色聚合并组合得很好。Context提供角色集之间的关联的范围关闭,而Aspect仅与应用它们的对象配对。
|
||||
在许多时候,虽然混合本身缺乏我们在Context语义中发现的动力 ,但DCI反映了混合风格策略。
|
||||
DCI实现了多范式设计的许多简单目标,能够将过程逻辑与对象逻辑分开。然而,DCI具有比多范式设计提供的更强大的技术更好的耦合和内聚效果
|
||||
尽管面向切面的编程(AOP)也有其他用途,但 DCI 满足了许多 AOP 的应用以及 Aspects 在解决问题方面的许多目标。根据 AOP 的基本原理,DCI 基于深层次的反射或元编程。
|
||||
与 Aspects 不同,角色聚合并组合得很好。Context 提供角色集之间的关联的范围关闭,而 Aspect 仅与应用它们的对象配对。
|
||||
在许多时候,虽然混合本身缺乏我们在 Context 语义中发现的动力 ,但 DCI 反映了混合风格策略。
|
||||
DCI 实现了多范式设计的许多简单目标,能够将过程逻辑与对象逻辑分开。然而,DCI 具有比多范式设计提供的更强大的技术更好的耦合和内聚效果
|
||||
|
||||
结合ATM 汇款场景案例,讲解了一下 DCI
|
||||
结合 ATM 汇款场景案例,讲解了一下 DCI
|
||||
角色提供了和用户相关 自然的边界,以转账为例,我们实际谈论的是钱的转移,以及源账户和目标账户的角色,算法(用例 角色行为集合)应该是这样:
|
||||
1.账户拥有人选择从一个账户到另外一个账户的钞票转移。
|
||||
2.系统显示有效账户
|
||||
@@ -35,12 +35,12 @@ DCI实现了多范式设计的许多简单目标,能够将过程逻辑与对
|
||||
2.源账户确认余额可用
|
||||
3.源账户减少其帐目
|
||||
4.源账户请求目标账户增加其帐目
|
||||
5.源账户请求目标账户更新其日志log
|
||||
5.源账户请求目标账户更新其日志 log
|
||||
6.源账户结束交易事务
|
||||
7.源账户显示给账户拥有人转账成功。
|
||||
|
||||
|
||||
```
|
||||
```plain
|
||||
template <class ConcreteAccountType>
|
||||
class TransferMoneySourceAccount: public MoneySource
|
||||
{
|
||||
@@ -88,7 +88,7 @@ DCI 尝试从人类思维角度出发,举一个例子:为什么在看电影
|
||||
2. 人或物发生了什么行为、交互?
|
||||
3. 现在在哪?厨房?太空舱?或者原始森林?
|
||||
|
||||
很快把这三件事弄清楚,我们就能快速理解当前场景的逻辑,并且**轻松理解该场景继续发生的状况**,即便是盗梦空间这种烧脑的电影,当我们搞清楚这三个问题后,就算街道发生了180度扭曲,也不会存在理解障碍,反而可以吃着爆米花享受,直到切换到下一个场景为止。
|
||||
很快把这三件事弄清楚,我们就能快速理解当前场景的逻辑,并且**轻松理解该场景继续发生的状况**,即便是盗梦空间这种烧脑的电影,当我们搞清楚这三个问题后,就算街道发生了 180 度扭曲,也不会存在理解障碍,反而可以吃着爆米花享受,直到切换到下一个场景为止。
|
||||
|
||||
当我们把街道扭曲 180 度的能力放在街道对象上时,理解就变的复杂了:这个函数什么时候被调用?为什么不好好承载车辆而自己发生扭曲?这就像电影开始时,把电影里播放的所有关于街道的状态都走马灯过一遍:我们看到街道通过了车辆、又卷曲、又发生了爆炸,实在觉得莫名其妙。
|
||||
|
||||
@@ -210,23 +210,23 @@ object MoneyTransferApp extends App {
|
||||
|
||||
- [Comparison of Architecture presentation patterns MVP(SC),MVP(PV),PM,MVVM and MVC](https://www.codeproject.com/Articles/66585/Comparison-of-Architecture-presentation-patterns-M)
|
||||
- [The DCI Architecture: A New Vision of Object-Oriented Programming](http://www.artima.com/articles/dci_vision.html)
|
||||
- [干净的架构The Clean Architecture](https://www.bbsmax.com/A/pRdBWY3ezn/)
|
||||
- [MVC的替代方案](https://gxnotes.com/article/71237.html)
|
||||
- [展示模式架构比较MVP(SC),MVP(PV),PM,MVVM和MVC](http://blog.csdn.net/lihenair/article/details/51791915)
|
||||
- [干净的架构 The Clean Architecture](https://www.bbsmax.com/A/pRdBWY3ezn/)
|
||||
- [MVC 的替代方案](https://gxnotes.com/article/71237.html)
|
||||
- [展示模式架构比较 MVP(SC),MVP(PV),PM,MVVM 和 MVC](http://blog.csdn.net/lihenair/article/details/51791915)
|
||||
- [Software Architecture Design](https://github.com/zenany/weekly/blob/master/resources/software_architecture.md)
|
||||
- [【译】什么是 Flux 架构?(兼谈 DDD 和 CQRS)](https://blog.jimmylv.info/2016-07-07-what-the-flux-on-flux-ddd-and-cqrs/)
|
||||
|
||||
|
||||
## 结合DCI 设想开发的过程中使用到一些设计方法和原则
|
||||
## 结合 DCI 设想开发的过程中使用到一些设计方法和原则
|
||||
|
||||
我们在开发的过程中多多少少都会使用到一些设计方法和原则
|
||||
DCI 重点是关注 数据的不同场景的交互行为, 是面向对象系统 状态和行为的一种范式设计;
|
||||
|
||||
它能够将过程逻辑与对象逻辑分开,是一种典型的行为模式设计;
|
||||
很好的点是 它根据AOP的基本原理,DCI 提出基于AOP 深层次的元编程(可以理解成面向接口编程), 去促使系统的内聚效果和降低耦合度;
|
||||
很好的点是 它根据 AOP 的基本原理,DCI 提出基于 AOP 深层次的元编程(可以理解成面向接口编程), 去促使系统的内聚效果和降低耦合度;
|
||||
|
||||
举个例子:
|
||||
在一个BI系统中, 在业务的发展中, 这个系统使用到了多套的 底层图表库,比如: Echarts, G2,Recharts, FusionChart; 等等;
|
||||
在一个 BI 系统中, 在业务的发展中, 这个系统使用到了多套的 底层图表库,比如: Echarts, G2,Recharts, FusionChart; 等等;
|
||||
|
||||
那么问题来了,
|
||||
1. 如何去同时支持 这些底层库, 并且达到很容易切换的一个效果?
|
||||
@@ -234,19 +234,19 @@ DCI 重点是关注 数据的不同场景的交互行为, 是面向对象系
|
||||
3. 如何去考虑扩展业务 对图表的日益增强的业务功能(如: 行列转换、智能格式化 等等)
|
||||
|
||||
带着这些问题, 我们再来看下 DCI 给我们的启示, 我们来试试看相应的解法:
|
||||
1. 图表的模型数据就是 数据Data , 我们可以把[日益增强的业务功能] 认为是各个场景交互Interactions;
|
||||
1. 图表的模型数据就是 数据 Data , 我们可以把[日益增强的业务功能] 认为是各个场景交互 Interactions;
|
||||
2. 接入更多类型的图表咋么搞?
|
||||
不同类型的图表其实是图表数据模型的转换,我们也可以把这些转换的行为过程作为一个个的切片(Aspect),每个切片都是独立的, 松耦合的 ;
|
||||

|
||||
|
||||
3. 接入多套底层库怎么搞? 每个图形库的 build方法,render 方法 , resize 方法,repaint 方法 都不一样 ,怎么搞 ? 我们可以使用 DCI 提到的元编程- 我们在这里理解为面向接口编程, 我们分装一层 统一的接口;
|
||||
3. 接入多套底层库怎么搞? 每个图形库的 build 方法,render 方法 , resize 方法,repaint 方法 都不一样 ,怎么搞 ? 我们可以使用 DCI 提到的元编程- 我们在这里理解为面向接口编程, 我们分装一层 统一的接口;
|
||||
利用面向接口的父类引用指向子类对象 我们就可以很方便的 接入更多的 implement 接入更多的图形库(当然,一个系统统一一套是最好的);
|
||||
|
||||
|
||||
|
||||
# 4 总结
|
||||
|
||||
DCI是数据Data 场景Context 交互Interactions的简称,DCI是一种特别关注行为的设计模式(行为模式),
|
||||
DCI 是数据 Data 场景 Context 交互 Interactions 的简称,DCI 是一种特别关注行为的设计模式(行为模式),
|
||||
DCI 关注数据不同场景的交互行为, 是面向对象 状态和行为的一种范式设计;DCI 尝试从人类思维,过程化设计一些行为;
|
||||
DCI 也会使用一些面向切面和接口编程的设计思想去达到高内聚低耦合的目标。
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
# 2 内容概要
|
||||
|
||||
### TC39是什么?包括哪些人?
|
||||
### TC39 是什么?包括哪些人?
|
||||
|
||||
一个推动 JavaScript 发展的委员会,由各个主流浏览器厂商的代表构成。
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
|
||||
### TC39 这群人主要的工作是什么?
|
||||
|
||||
制定ECMAScript标准,标准生成的流程,并实现。
|
||||
制定 ECMAScript 标准,标准生成的流程,并实现。
|
||||
|
||||
### 标准的流程是什么样的?
|
||||
|
||||
@@ -26,34 +26,34 @@
|
||||
|
||||
- stage0 `strawman`
|
||||
|
||||
任何讨论、想法、改变或者还没加到提案的特性都在这个阶段。只有TC39成员可以提交。
|
||||
任何讨论、想法、改变或者还没加到提案的特性都在这个阶段。只有 TC39 成员可以提交。
|
||||
|
||||
- stage1 `proposal`
|
||||
(1)产出一个正式的提案。
|
||||
(2)发现潜在的问题,例如与其他特性的关系,实现难题。
|
||||
(3)提案包括详细的API描述,使用例子,以及关于相关的语义和算法。
|
||||
(3)提案包括详细的 API 描述,使用例子,以及关于相关的语义和算法。
|
||||
|
||||
- stage2 `draft`
|
||||
(1)提供一个初始的草案规范,与最终标准中包含的特性不会有太大差别。草案之后,原则上只接受增量修改。
|
||||
(2)开始实验如何实现,实现形式包括polyfill, 实现引擎(提供草案执行本地支持),或者编译转换(例如babel)
|
||||
(2)开始实验如何实现,实现形式包括 polyfill, 实现引擎(提供草案执行本地支持),或者编译转换(例如 babel)
|
||||
|
||||
- stage3 `candidate`
|
||||
(1)候选阶段,获得具体实现和用户的反馈。此后,只有在实现和使用过程中出现了重大问题才会修改。
|
||||
(1)规范文档必须是完整的,评审人和ECMAScript的编辑要在规范上签字。
|
||||
(2)至少要在一个浏览器中实现,提供polyfill或者babel插件。
|
||||
(1)规范文档必须是完整的,评审人和 ECMAScript 的编辑要在规范上签字。
|
||||
(2)至少要在一个浏览器中实现,提供 polyfill 或者 babel 插件。
|
||||
|
||||
- stage4 `finished`
|
||||
(1)已经准备就绪,该特性会出现在下个版本的ECMAScript规范之中。。
|
||||
(2)需要通过有2个独立的实现并通过验收测试,以获取使用过程中的重要实践经验。
|
||||
(1)已经准备就绪,该特性会出现在下个版本的 ECMAScript 规范之中。。
|
||||
(2)需要通过有 2 个独立的实现并通过验收测试,以获取使用过程中的重要实践经验。
|
||||
|
||||
### 一般可以去哪里查看TC39标准的进程呢?
|
||||
### 一般可以去哪里查看 TC39 标准的进程呢?
|
||||
|
||||
stage0 的提案 https://github.com/tc39/proposals/blob/master/stage-0-proposals.md
|
||||
stage1 - 4 的提案 https://github.com/tc39/proposals
|
||||
|
||||
### 我们怎么在程序中应用这些新特性呢?
|
||||
|
||||
babel的插件:`babel-presets-stage-0` `babel-presets-stage-1` `babel-presets-stage-2` `babel-presets-stage-3` `babel-presets-stage-4`
|
||||
babel 的插件:`babel-presets-stage-0` `babel-presets-stage-1` `babel-presets-stage-2` `babel-presets-stage-3` `babel-presets-stage-4`
|
||||
|
||||
# 3 精读
|
||||
|
||||
@@ -617,7 +617,7 @@ const firstName = message.body?.user?.firstName || 'default'
|
||||
|
||||
```javascript
|
||||
const firstName =
|
||||
()message &&
|
||||
(message &&
|
||||
message.body &&
|
||||
message.body.user &&
|
||||
message.body.user.firstName) || 'default'
|
||||
@@ -627,7 +627,7 @@ const firstName =
|
||||
|
||||
### [Math.signbit: IEEE-754 sign bit](http://jfbastien.github.io/papers/Math.signbit.html)
|
||||
|
||||
当值为 负数 或 -0 时返回 `true`。由于 `Math.sign` 不区分 +0 与 -0,因此提案建议增加此函数,而且此函数在 c、c++、go语言都有实现。
|
||||
当值为 负数 或 -0 时返回 `true`。由于 `Math.sign` 不区分 +0 与 -0,因此提案建议增加此函数,而且此函数在 c、c++、go 语言都有实现。
|
||||
|
||||
### [Error stacks](https://github.com/tc39/proposal-error-stacks)
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# 1. 摘要
|
||||
|
||||
日期选择器作为基础组件重要不可或缺的一员,大家已经快习惯它一成不变的样子,输入框+日期选择弹出层。但到业务中,这种墨守成规的样子真的能百分百契合业务需求吗。这篇文章从多个网站的日期选择场景出发,企图归纳出日期选择器的最佳实践。这篇文章对移动端的日期选择暂无涉猎,都是PC端,列举出通用场景,每个类型日期选择器需要考虑的设计。
|
||||
日期选择器作为基础组件重要不可或缺的一员,大家已经快习惯它一成不变的样子,输入框+日期选择弹出层。但到业务中,这种墨守成规的样子真的能百分百契合业务需求吗。这篇文章从多个网站的日期选择场景出发,企图归纳出日期选择器的最佳实践。这篇文章对移动端的日期选择暂无涉猎,都是 PC 端,列举出通用场景,每个类型日期选择器需要考虑的设计。
|
||||
文章链接:Designing The Perfect Date And Time Picker
|
||||
感谢本期评论官 @黄子毅 @流形 @王亮 @赵阳 @不知名的花瓣工程师
|
||||
# 2. 设计原则
|
||||
@@ -21,9 +21,9 @@
|
||||
|
||||
2)用户自定义输入如何保证日期格式正确性?
|
||||
|
||||
3)是否需要提供预设场景输入? 比如昨天,三天前,七天前,30天前?像很多数据分析场景,分析师会关注数据周期,比如流量的周环比,月环比,年环比。
|
||||
3)是否需要提供预设场景输入? 比如昨天,三天前,七天前,30 天前?像很多数据分析场景,分析师会关注数据周期,比如流量的周环比,月环比,年环比。
|
||||
|
||||
4)是否需要包含默认值?如果有默认,应该是什么?像google flight 根据用户历史数据提供默认值,临近节假日默认填充节假日。同时像有些数据场景,数据存在延迟,需要默认提供T-1/T-2 ,避免用户选择当天。
|
||||
4)是否需要包含默认值?如果有默认,应该是什么?像 google flight 根据用户历史数据提供默认值,临近节假日默认填充节假日。同时像有些数据场景,数据存在延迟,需要默认提供 T-1/T-2 ,避免用户选择当天。
|
||||
|
||||
5)当用户激活输入框时,是否保留默认值?
|
||||
|
||||
@@ -60,7 +60,7 @@
|
||||
|
||||
2)用户选中后是否立刻做背景色提示?
|
||||
|
||||
3)当用户选择时,区间是否需要随着用户动作改变?比如用户hover时,动态改变选中区间。
|
||||
3)当用户选择时,区间是否需要随着用户动作改变?比如用户 hover 时,动态改变选中区间。
|
||||
|
||||
4)是否提供快捷键切换 日、月、年选择?
|
||||
|
||||
@@ -97,7 +97,7 @@
|
||||
|
||||

|
||||
|
||||
采用与用户交互的方式选择日期,如果今后应用上AI,单纯的日期选择器是不是会消失不见呢?..
|
||||
采用与用户交互的方式选择日期,如果今后应用上 AI,单纯的日期选择器是不是会消失不见呢?..
|
||||
## 3.5 特殊标识周末
|
||||

|
||||
在机票、旅行场景中,周末是大家最有可能出行的时间点,采用竖线划分的方式着重标注提醒。
|
||||
@@ -106,6 +106,6 @@
|
||||
|
||||

|
||||
|
||||
总得来说,日期选择器是一个业务组件,虽然现有很多组件库把它纳入UI基础组件。但在每个不通的业务场景和需求下的展现形式、交互都会有所有不同。首先一定一定要明确确定需要日期选择器的场景,尤其是与日期强关联的业务,比如机票定价、日程安排,结合到日期选择器中更直观,提高用户对信息的检索效率。满足用户需求场景的同时,尽量减少用户操作链路。
|
||||
总得来说,日期选择器是一个业务组件,虽然现有很多组件库把它纳入 UI 基础组件。但在每个不通的业务场景和需求下的展现形式、交互都会有所有不同。首先一定一定要明确确定需要日期选择器的场景,尤其是与日期强关联的业务,比如机票定价、日程安排,结合到日期选择器中更直观,提高用户对信息的检索效率。满足用户需求场景的同时,尽量减少用户操作链路。
|
||||
|
||||
看到最后点个赞呗,给你比小心心 ❤ ~~
|
||||
|
||||
@@ -40,7 +40,7 @@
|
||||
|
||||
正如上面所说,我推荐以开放性问题开场,这样便于了解候选人的经历、熟悉哪些技术点,便于后面的技术提问。如果开场就以准备好的题目展开车轮战,容易引起候选人心里紧张,同时我们问的问题不一定是候选人所在行的,技术问题不是每一个都那么重要,很多时候我们只看到了候选人的冰山一角,但此时气氛已经尴尬,很多时候会遗漏优秀人才。
|
||||
|
||||
开放性问题最好基于行为面试法询问(Star法则):
|
||||
开放性问题最好基于行为面试法询问(Star 法则):
|
||||
|
||||
- Situation: 场景 - 当时是怎样的场景
|
||||
- Task: 任务 - 当时的任务是什么
|
||||
@@ -83,7 +83,7 @@
|
||||
|
||||
面试主要是看候选人基础有多扎实,和思维能力。基础主要指的是,候选人提前了解了多少前端相关知识,比如对闭包的理解,对原生 api 的理解?如果候选人没接触过这两个知识点,会有两种情况:
|
||||
|
||||
- **这些知识点看完需要多久?如果是闭包和原生api的定义与用法,候选人这方面的缺陷可以通过5分钟来弥补,那么这种问题到底想考什么?我们真的在乎这5分钟看文档的时间吗?此时应该了解候选人对知识点的感悟,或者学习方式,因为这两点的差距可能几年都无法弥补**
|
||||
- **这些知识点看完需要多久?如果是闭包和原生 api 的定义与用法,候选人这方面的缺陷可以通过 5 分钟来弥补,那么这种问题到底想考什么?我们真的在乎这 5 分钟看文档的时间吗?此时应该了解候选人对知识点的感悟,或者学习方式,因为这两点的差距可能几年都无法弥补**
|
||||
- **如果候选人学习能力非常强,但几乎所有前端知识点都不了解,弥补完大概一共要花 1000*5 分钟,这时候量变引发质变了,是不是说明候选人本身对技术的热情存在问题?**
|
||||
|
||||
通过了基础问题还远远不够。甚至当问一个复杂的问题的时候,如果候选人瞬间把答案完美流畅表达出来,说明这个问题基本上白问了。
|
||||
|
||||
@@ -1,54 +1,54 @@
|
||||
# 精读《Web fonts: when you need them, when you don’t》
|
||||
本期精读让我们来聊一聊Web Fonts,文章地址:[https://hackernoon.com/web-fonts-when-you-need-them-when-you-dont-a3b4b39fe0ae](https://hackernoon.com/web-fonts-when-you-need-them-when-you-dont-a3b4b39fe0ae)
|
||||
本期精读让我们来聊一聊 Web Fonts,文章地址:[https://hackernoon.com/web-fonts-when-you-need-them-when-you-dont-a3b4b39fe0ae](https://hackernoon.com/web-fonts-when-you-need-them-when-you-dont-a3b4b39fe0ae)
|
||||
|
||||
## 文章简介
|
||||
文章分析了Web Fonts的优劣具体使用场景。
|
||||
文章分析了 Web Fonts 的优劣具体使用场景。
|
||||
|
||||
## 主要观点
|
||||
- 作者用一张流程图非常言简意赅地概括了文章的上半部分。
|
||||

|
||||
- 当然上半部分作者也讲了很多案例,其中一个很明显的案例就是维基百科利用字体来提升阅读体验,通过文章内的对比,能直观感受到这一点
|
||||
- 文章后半部分着力介绍了怎么解决Web Font的带来的弊端:认识FOUT带来的问题,如何使用现有的前端解决方案来尽可能避免这个问题,以及样式上优雅降级的几个方案。
|
||||
- 文章后半部分着力介绍了怎么解决 Web Font 的带来的弊端:认识 FOUT 带来的问题,如何使用现有的前端解决方案来尽可能避免这个问题,以及样式上优雅降级的几个方案。
|
||||
|
||||
## 把文章带入自己的开发环境
|
||||
作为一个中文开发者,在我们的开发技术栈中,Web Fonts绝对是属于使用频率比较低的那一类的。本次精读选择这篇文章,也正是一探这一个不常见的领域。
|
||||
作为一个中文开发者,在我们的开发技术栈中,Web Fonts 绝对是属于使用频率比较低的那一类的。本次精读选择这篇文章,也正是一探这一个不常见的领域。
|
||||
|
||||
对于英文字母,26个字母可以解决大部分的问题,算上大小写和基本符号,一张ASCII码标就可以包含住。让我再扩展一下,到大部分的西文书写系统,几百个字符就能解决多语言显示的问题了。但是对于汉语而言,Web Fonts真的是,想说爱你不容易,因为常用的汉字就有几千个(你想象中国还有《千字文》这种儿童读物……)。字体这东西跟字符数量直接挂钩,是很难通过压缩来获得性能提升的。
|
||||
对于英文字母,26 个字母可以解决大部分的问题,算上大小写和基本符号,一张 ASCII 码标就可以包含住。让我再扩展一下,到大部分的西文书写系统,几百个字符就能解决多语言显示的问题了。但是对于汉语而言,Web Fonts 真的是,想说爱你不容易,因为常用的汉字就有几千个(你想象中国还有《千字文》这种儿童读物……)。字体这东西跟字符数量直接挂钩,是很难通过压缩来获得性能提升的。
|
||||
|
||||
通常的想法就是用多少,取多少,但是这个方法也就只能适用于标题美化等场景。对于一个系统性的前端工程,我们不可能去实现一个动态字符的字体文件(就是统计这个页面上会产生多少个字符,为这个字符集去生成一个字符子集)
|
||||
|
||||
虽然汉字书写系统和西文书写系统天生存在差异,但是把作者在文章中提出来的几个问题再站在中文的角度上再来看一下,也可以得到一个比较客观的答案。以下是我作为一个普通开发者的自问自答:
|
||||
|
||||
1. **字体对你的品牌很关键吗?**(需要特性字体的中文LOGO基本都用PNG和SVG解决了,Web Font不实用。)
|
||||
2. **字体让你的文字阅读起来更容易了吗?**(我平时开发产品没有成片聚集的文字,用无衬线字体就能满足需求。Web Font很好,我选择“微软雅黑”。)(注:泛指那些好用的支持全字集的系统自带字体;成片的文字适用衬线字体,个人认为中文的衬线字体,不同的字体带来的阅读体验还是有明显差别的。)
|
||||
3. **你需要在不同设备上显示一样的字体吗?**(好像还没这么苛求吧……微软雅黑好看,安卓上的Roboto也很不错啊,Roboto这种字体还针对移动设备有优化,何乐而不为。)
|
||||
4. **用了Web Font你会更开心吗?**(在icon中使用iconfont让我们告别了PNG Sprite图,嗯这很开心。至于文字上用Web Font,有好用的系统字体你不用,你这是何苦呢)(作者也说了,可能折腾半天还没系统字体看着舒服,那就是一行font-family的事情)
|
||||
1. **字体对你的品牌很关键吗?**(需要特性字体的中文 LOGO 基本都用 PNG 和 SVG 解决了,Web Font 不实用。)
|
||||
2. **字体让你的文字阅读起来更容易了吗?**(我平时开发产品没有成片聚集的文字,用无衬线字体就能满足需求。Web Font 很好,我选择“微软雅黑”。)(注:泛指那些好用的支持全字集的系统自带字体;成片的文字适用衬线字体,个人认为中文的衬线字体,不同的字体带来的阅读体验还是有明显差别的。)
|
||||
3. **你需要在不同设备上显示一样的字体吗?**(好像还没这么苛求吧……微软雅黑好看,安卓上的 Roboto 也很不错啊,Roboto 这种字体还针对移动设备有优化,何乐而不为。)
|
||||
4. **用了 Web Font 你会更开心吗?**(在 icon 中使用 iconfont 让我们告别了 PNG Sprite 图,嗯这很开心。至于文字上用 Web Font,有好用的系统字体你不用,你这是何苦呢)(作者也说了,可能折腾半天还没系统字体看着舒服,那就是一行 font-family 的事情)
|
||||
|
||||
不同产品有着不同的场景,多像文章里问问自己会有最合适的答案。
|
||||
|
||||
## 关于FOUT和FOIT
|
||||
文章中大篇幅地在安利你使用Web Font,但是也很直白地指出了Web Font最大的问题,就是这个FOUT——Flash Of Unstyled Text。连作者毫不避讳地说了句:“噢我的老天,这太丑了!”
|
||||
## 关于 FOUT 和 FOIT
|
||||
文章中大篇幅地在安利你使用 Web Font,但是也很直白地指出了 Web Font 最大的问题,就是这个 FOUT——Flash Of Unstyled Text。连作者毫不避讳地说了句:“噢我的老天,这太丑了!”
|
||||
|
||||
具体表现是采用了Web Font的文案会存在闪动,这个的根本原因在于相比于系统字体,Web Font最大的弊端在于它是异步加载的,你没有办法避免下载它所用的时间。文章中举了一个例子,在一个图文为主的页面中,一个542KB的字体文件,在第9秒才加载完成。在那之前只能以系统字体来展示,而在第9秒加载完成的时候,还会出现替换字体的情况,文字会突然跳动。
|
||||
具体表现是采用了 Web Font 的文案会存在闪动,这个的根本原因在于相比于系统字体,Web Font 最大的弊端在于它是异步加载的,你没有办法避免下载它所用的时间。文章中举了一个例子,在一个图文为主的页面中,一个 542KB 的字体文件,在第 9 秒才加载完成。在那之前只能以系统字体来展示,而在第 9 秒加载完成的时候,还会出现替换字体的情况,文字会突然跳动。
|
||||
|
||||
比FOUT更为极端的情况的是FOIT——Flash Of Invisible Text。很多浏览器的行为,并不是默认展示系统字体,而是直接隐藏。那么即使在极快的网速下也很难避免存在一个几百毫秒的时间滞后。
|
||||
比 FOUT 更为极端的情况的是 FOIT——Flash Of Invisible Text。很多浏览器的行为,并不是默认展示系统字体,而是直接隐藏。那么即使在极快的网速下也很难避免存在一个几百毫秒的时间滞后。
|
||||
|
||||
不过好在,有一个font-display的属性,可以在声明@font-face的时候配合使用。对于未加载Web Fonts的时候,auto属性可以选择隐藏也就是会产生FOIT,swap会产生替换也就是会产生FOUT,还有fallback和optional可以控制先FOIT后FOUT来达到折中方案。
|
||||
不过好在,有一个 font-display 的属性,可以在声明@font-face 的时候配合使用。对于未加载 Web Fonts 的时候,auto 属性可以选择隐藏也就是会产生 FOIT,swap 会产生替换也就是会产生 FOUT,还有 fallback 和 optional 可以控制先 FOIT 后 FOUT 来达到折中方案。
|
||||
|
||||
还有一个思路,那就是预加载,对于字体,浏览器还是能够有效缓存的,如果能够做好预加载,还是不会太影响用户体验的。文章中就提到了一个方案,调用link的rel=preload来做预加载。因为通常加载字体是在CSS中的@font-face被读到的时候才去加载的,那么就会出现先加载CSS,后加载字体的情况。如果利用link预加载,那么在CSS中的@font-face被读到前就已经开始加载了,那么字体加载和CSS加载就可以同时加载,提升速度。
|
||||
还有一个思路,那就是预加载,对于字体,浏览器还是能够有效缓存的,如果能够做好预加载,还是不会太影响用户体验的。文章中就提到了一个方案,调用 link 的 rel=preload 来做预加载。因为通常加载字体是在 CSS 中的@font-face 被读到的时候才去加载的,那么就会出现先加载 CSS,后加载字体的情况。如果利用 link 预加载,那么在 CSS 中的@font-face 被读到前就已经开始加载了,那么字体加载和 CSS 加载就可以同时加载,提升速度。
|
||||
|
||||
当然JS是万能的,也有一些库在支持这方面功能,例如bramstein/fontfaceobserver这样的。
|
||||
当然 JS 是万能的,也有一些库在支持这方面功能,例如 bramstein/fontfaceobserver 这样的。
|
||||
|
||||
愚以为,FO*T这种情况既然无法避免还是要具体情况具体分析的。如果你的用户网速够快,那么隐藏文字会更好,用户无感知;如果网速不确定,而且是文章为主的内容,那么内容至上就应该先用替代字体显示;如果你正在将Web Font应用在图标等东西上,那么我们自然不愿意看到满屏的方框方框,这种时候就选择隐藏吧。
|
||||
愚以为,FO*T 这种情况既然无法避免还是要具体情况具体分析的。如果你的用户网速够快,那么隐藏文字会更好,用户无感知;如果网速不确定,而且是文章为主的内容,那么内容至上就应该先用替代字体显示;如果你正在将 Web Font 应用在图标等东西上,那么我们自然不愿意看到满屏的方框方框,这种时候就选择隐藏吧。
|
||||
|
||||
文章也提了一点,如果你的字体授权很贵,但用户端深受FO*T折磨,那你还费这钱干嘛。
|
||||
文章也提了一点,如果你的字体授权很贵,但用户端深受 FO*T 折磨,那你还费这钱干嘛。
|
||||
|
||||
## 结论
|
||||
如果能解决FO*T的副作用,Web Font怎么舒服怎么用。但是中文字体大,常用西文字体诸如Google字体库又时常被墙,对于中国开发者,Web Font想说爱你不容易。(还是乖乖用微软雅黑吧,逃……
|
||||
如果能解决 FO*T 的副作用,Web Font 怎么舒服怎么用。但是中文字体大,常用西文字体诸如 Google 字体库又时常被墙,对于中国开发者,Web Font 想说爱你不容易。(还是乖乖用微软雅黑吧,逃……
|
||||
|
||||
## 彩蛋
|
||||
文章里有一段很精彩的话,摘抄出来翻译一下:
|
||||
|
||||
> 如果这个世界上有这样一个Sketch或者Photoshop的插件,可以在你每次打开一个文件的时候,延迟十秒才显示出字体;那么世界上就没有那么多多余的字体了。
|
||||
> 如果这个世界上有这样一个 Sketch 或者 Photoshop 的插件,可以在你每次打开一个文件的时候,延迟十秒才显示出字体;那么世界上就没有那么多多余的字体了。
|
||||
>
|
||||
> (译者注:删光设计师的电脑里的奇怪字体也能达到相同目的,哈哈哈~)
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
<img src="assets/22/v8.png" width="500" alt="logo" />
|
||||
|
||||
定时刷新一下对 js 的三观,防止经验变成坑。(对IE等浏览器的三观需保持不变)
|
||||
定时刷新一下对 js 的三观,防止经验变成坑。(对 IE 等浏览器的三观需保持不变)
|
||||
|
||||
# 2 内容概要
|
||||
|
||||
|
||||
+5
-5
@@ -1,4 +1,4 @@
|
||||
本期精读的文章是:[API设计原则](https://coolshell.cn/articles/18024.html)
|
||||
本期精读的文章是:[API 设计原则](https://coolshell.cn/articles/18024.html)
|
||||
|
||||
# 1 引言
|
||||
|
||||
@@ -12,9 +12,9 @@
|
||||
|
||||
由于本文已经是翻译后的文章,概要只列出不涉及 c++ 概念的思路框架,细节请移步[译文](https://coolshell.cn/articles/18024.html)。
|
||||
|
||||
## 好 API 的6个特质
|
||||
## 好 API 的 6 个特质
|
||||
|
||||
极简且完备、语义清晰简单、符合直觉、易于记忆和引导API使用者写出可读代码。
|
||||
极简且完备、语义清晰简单、符合直觉、易于记忆和引导 API 使用者写出可读代码。
|
||||
|
||||
## 静态多态
|
||||
|
||||
@@ -76,7 +76,7 @@ function (const num) {
|
||||
|
||||
## 统一关键字库
|
||||
|
||||
所有api定义之前,先抽离业务和功能语义的关键字,统一关键字库; 可以更好的让多人协作看起来如出一辙, 而且关键字库 更能够让调用者感觉到 符合直觉、语义清晰; 关键字库也是项目组新同学 PREDO 的内容之一, 很有带入感;
|
||||
所有 api 定义之前,先抽离业务和功能语义的关键字,统一关键字库; 可以更好的让多人协作看起来如出一辙, 而且关键字库 更能够让调用者感觉到 符合直觉、语义清晰; 关键字库也是项目组新同学 PREDO 的内容之一, 很有带入感;
|
||||
|
||||
## 单一职责
|
||||
|
||||
@@ -119,6 +119,6 @@ const { setVisible } = this.props.store.article
|
||||
|
||||
最后,如果有精力,最好每半年重构一次(然后完整跑一遍测试)!
|
||||
|
||||
> 讨论地址是:[精读《API设计原则》 · Issue #34 · dt-fe/weekly](https://github.com/dt-fe/weekly/issues/34)
|
||||
> 讨论地址是:[精读《API 设计原则》 · Issue #34 · dt-fe/weekly](https://github.com/dt-fe/weekly/issues/34)
|
||||
|
||||
> 如果你想参与讨论,请[点击这里](https://github.com/dt-fe/weekly),每周都有新的主题,每周五发布。
|
||||
|
||||
+22
-22
@@ -9,14 +9,14 @@
|
||||
|
||||
我为什么要选这篇文章呢?
|
||||
|
||||
之所以选这篇文章, 是因为非常认同作者写这两篇文章的原因. 作者在文中说, 现代JavaScript 的很多概念和思想在快速被传播和扩展, 很多新概念出现在前端相关的博客和文档中, 这些概念对于很多前端开发人员来说, 仍然很陌生. 因此我们有必要来学习一下现代的这些 JavaScript的概念, 看这些概念在现在 JavaScript 的库或应用中是怎么被使用的.
|
||||
之所以选这篇文章, 是因为非常认同作者写这两篇文章的原因. 作者在文中说, 现代 JavaScript 的很多概念和思想在快速被传播和扩展, 很多新概念出现在前端相关的博客和文档中, 这些概念对于很多前端开发人员来说, 仍然很陌生. 因此我们有必要来学习一下现代的这些 JavaScript 的概念, 看这些概念在现在 JavaScript 的库或应用中是怎么被使用的.
|
||||
|
||||
# 2 内容概要
|
||||
|
||||
文章讲了很多现代JavaScript中的概念, 罗列如下:
|
||||
文章讲了很多现代 JavaScript 中的概念, 罗列如下:
|
||||
|
||||
## 纯函数和副作用
|
||||
在了解纯函数之前, 首先要了解副作用. 副作用是指改变了其作用域外的状态. 副作用的举例有调用了一个 API, 操作了一个 DOM节点, 弹出了一个弹窗, 或者改变了一条数据等.
|
||||
在了解纯函数之前, 首先要了解副作用. 副作用是指改变了其作用域外的状态. 副作用的举例有调用了一个 API, 操作了一个 DOM 节点, 弹出了一个弹窗, 或者改变了一条数据等.
|
||||
|
||||
而纯函数则是指 函数的返回值仅仅由参数决定, 当给同样的参数时, 返回值是固定的.
|
||||
|
||||
@@ -27,7 +27,7 @@ Stateless 无状态, 有点像纯函数, 不管理自己的数据或状态, 结
|
||||
|
||||
可变对象与不可变对象概念很清楚, 可变对象指的是在创建后值仍可以被改变, 不可变对象指的是创建后值无法被改变.
|
||||
|
||||
相比于其他语言, 可变对象与不可变对象在 JavaScript 中更加模糊, 当你了解函数式编程时, 你会听到很多不可变对象的好处. 在 JavaScript 中, 你可以通过Object.freeze(obj), 让一个对象变得不可变, 但是注意这是浅层的冻结对象, 如果有一个属性的值是个对象, 那这个对象中的属性是可以被修改的. 现在 JavaScript 也出现了 npm deep-freeze , Immutable.js 这些库来帮助你在 JavaScript 中实现不可变对象.
|
||||
相比于其他语言, 可变对象与不可变对象在 JavaScript 中更加模糊, 当你了解函数式编程时, 你会听到很多不可变对象的好处. 在 JavaScript 中, 你可以通过 Object.freeze(obj), 让一个对象变得不可变, 但是注意这是浅层的冻结对象, 如果有一个属性的值是个对象, 那这个对象中的属性是可以被修改的. 现在 JavaScript 也出现了 npm deep-freeze , Immutable.js 这些库来帮助你在 JavaScript 中实现不可变对象.
|
||||
|
||||
## Imperative and Declarative Programming(命令式和声明式编程)
|
||||
命令式编程, 描述一段代码的逻辑怎么被显式调用去改变程序的状态. 声明式编程, 描述一段代码的逻辑, 而不需要描述如何完成这段逻辑.
|
||||
@@ -46,9 +46,9 @@ JavaScript 可以同时被写为命令式和声明式编程方式, 但是随着
|
||||
- 声明式代码去管理副作用和执行命令式编程
|
||||
|
||||
## Hot and Cold Observables
|
||||
Observables 和数组类似, 只不过数组是被保存在内存中, 而Observables的每一个元素则是异步加入进来. 我们可以订阅这些 observables.
|
||||
Observables 和数组类似, 只不过数组是被保存在内存中, 而 Observables 的每一个元素则是异步加入进来. 我们可以订阅这些 observables.
|
||||
|
||||
Hot Observables 容易会被执行, 即使我们没有订阅它们. 比如说 用户的操作界面的 按钮点击事件, 鼠标移动, 窗口大小改变, 这些都是 Hot Observables. 而cold observable则是需要我们去订阅, 并且会在我们订阅的时候开始执行.
|
||||
Hot Observables 容易会被执行, 即使我们没有订阅它们. 比如说 用户的操作界面的 按钮点击事件, 鼠标移动, 窗口大小改变, 这些都是 Hot Observables. 而 cold observable 则是需要我们去订阅, 并且会在我们订阅的时候开始执行.
|
||||
|
||||
## 响应式编程 RP
|
||||
响应式编程, 可以看作是面向异步事件流的编程, 声明式的, 表述去做什么, 而不是怎么做.
|
||||
@@ -69,18 +69,18 @@ Hot Observables 容易会被执行, 即使我们没有订阅它们. 比如说
|
||||
|
||||
随着现在各种 SPA 框架的兴起, 理解数据流概念, 对于现在 JS 开发者越来越重要, React 被认为是单向数据流的典范, 使用 Model 作为唯一的数据来源, 控制 View 的渲染. 在 View 层用事件的方式通知 Model 更新, 在反应到 View 层的变化上. 数据沿着一个方向流动, UI 永远不会更新 Model, 而是通过事件或者 setState 方法.
|
||||
|
||||
在双向数据绑定中, 数据是在两个方向上流动的, JS可以更新 Model 数据, View 层 也可以更新 Model 数据. AngularJs 的1.x 版本是双向数据流的典型实现.
|
||||
在双向数据绑定中, 数据是在两个方向上流动的, JS 可以更新 Model 数据, View 层 也可以更新 Model 数据. AngularJs 的 1.x 版本是双向数据流的典型实现.
|
||||
|
||||
早在2009年, 双向绑定是 Angualr 最受欢迎的特性之一, 但是 Angular 把这一特性抛弃了. 现在很多流行的框架和库都使用了单向数据流(React,Angular,Inferno,Redux等). 单向数据流倡导的是清晰的架构, 数据流动更加清晰和易管理. 对于单向数据流来说说了点View自动更新数据的便利, 但也得到了清晰的数据流.
|
||||
早在 2009 年, 双向绑定是 Angualr 最受欢迎的特性之一, 但是 Angular 把这一特性抛弃了. 现在很多流行的框架和库都使用了单向数据流(React,Angular,Inferno,Redux 等). 单向数据流倡导的是清晰的架构, 数据流动更加清晰和易管理. 对于单向数据流来说说了点 View 自动更新数据的便利, 但也得到了清晰的数据流.
|
||||
|
||||
## JS框架中的变化侦测: 脏检查, getter 和 setter, 虚拟 DOM
|
||||
变化侦测对于现代 SPA应用来说很重要. 当用户更新一些内容时, 应用必须以一种方法知道这种变化, 并做出反应更新.
|
||||
## JS 框架中的变化侦测: 脏检查, getter 和 setter, 虚拟 DOM
|
||||
变化侦测对于现代 SPA 应用来说很重要. 当用户更新一些内容时, 应用必须以一种方法知道这种变化, 并做出反应更新.
|
||||
|
||||
AngularJS 1.x 使用的是脏检查的方式, 具体做法是对View 中涉及到的 Model 进行深度比较. 脏检查的优点在于它的简单和可预测, 不涉及到 API 和对象的变更. 但是正因为涉及到大量比较, 也很低效.
|
||||
AngularJS 1.x 使用的是脏检查的方式, 具体做法是对 View 中涉及到的 Model 进行深度比较. 脏检查的优点在于它的简单和可预测, 不涉及到 API 和对象的变更. 但是正因为涉及到大量比较, 也很低效.
|
||||
|
||||
Ember 和 Backbone 是使用getters 和 setters 来做变化侦测, 这样涉及到数据修改时, 都会触发变更事件. 而React 是使用了虚拟 Dom 来做变化侦测, React 通过 setState方法来通知变更, 使用虚拟 Dom 来比较是否发生了数据变化.
|
||||
Ember 和 Backbone 是使用 getters 和 setters 来做变化侦测, 这样涉及到数据修改时, 都会触发变更事件. 而 React 是使用了虚拟 Dom 来做变化侦测, React 通过 setState 方法来通知变更, 使用虚拟 Dom 来比较是否发生了数据变化.
|
||||
|
||||
## Web Components组件
|
||||
## Web Components 组件
|
||||
Web 组件是 Web 平台上可复用的基础组件, 而 Web Components 则定义了一些规范来实现这些可复用组件.
|
||||
规范包括:
|
||||
- 自定义元素
|
||||
@@ -88,20 +88,20 @@ Web 组件是 Web 平台上可复用的基础组件, 而 Web Components 则定
|
||||
- Shadow Dom
|
||||
- HTML imports 引入
|
||||
|
||||
Web Components本身并不能代替 SPA框架的功能, 但是它的想法和核心概念, 在很多 SPA 框架中都有体现.
|
||||
Web Components 本身并不能代替 SPA 框架的功能, 但是它的想法和核心概念, 在很多 SPA 框架中都有体现.
|
||||
|
||||
## Smart 和 Dumb 组件
|
||||
现在Web 的开发严重依赖组件, 而很多时候我们把组件分成 Smart 组件和 Dumb 组件.
|
||||
现在 Web 的开发严重依赖组件, 而很多时候我们把组件分成 Smart 组件和 Dumb 组件.
|
||||
|
||||
Smart 组件, 又叫容器组件, 在组件内处理各种业务逻辑, 通常也管理 Dumb 组件,响应 Dumb 组件的事件.
|
||||
|
||||
Dumb 组件, 又叫展示组件, 通常被写成纯函数, 依赖于外部的数据和方法, 专注于展现数据.
|
||||
|
||||
## JIT 编译
|
||||
Just-In-time(JIT)编译指的是代码的运行时, 被编译成机器代码的过程. 在JavaScript 运行时, JIT 能够找到代码的特定模式, 而这些模式可以让 JavaScript 更快的被执行.
|
||||
Just-In-time(JIT)编译指的是代码的运行时, 被编译成机器代码的过程. 在 JavaScript 运行时, JIT 能够找到代码的特定模式, 而这些模式可以让 JavaScript 更快的被执行.
|
||||
|
||||
## AOT 编译
|
||||
Ahead-Of-Time(AOT), 指的是编写的代码在运行之前, 被翻译成机器代码的过程. AOT给 tree shaking 带来了可能, 使用AOT 预编译, 对于生产环境下的代码有以下好处:
|
||||
Ahead-Of-Time(AOT), 指的是编写的代码在运行之前, 被翻译成机器代码的过程. AOT 给 tree shaking 带来了可能, 使用 AOT 预编译, 对于生产环境下的代码有以下好处:
|
||||
|
||||
- 更少的异步请求, 模板和样式内联在 JS 内
|
||||
- 更小的体积
|
||||
@@ -111,24 +111,24 @@ Ahead-Of-Time(AOT), 指的是编写的代码在运行之前, 被翻译成机器
|
||||
## Tree Shaking
|
||||
Tree Shaking 是指打包 JS 模块时, 通过对代码的静态分析, 排除掉不用的代码的机制.
|
||||
|
||||
Tree Shaking 技术建立在 ES2015模块的, import和 export上, 支持我们导入特定的内容,而不是整个库.
|
||||
Tree Shaking 技术建立在 ES2015 模块的, import 和 export 上, 支持我们导入特定的内容,而不是整个库.
|
||||
|
||||
```
|
||||
```plain
|
||||
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
|
||||
```
|
||||
|
||||
这样我们只导入了 BehaviorSubject, 而没有导入整个 Rxjs 库.
|
||||
# 3 精读
|
||||
文中讲到的现代 JavaScript 已经很多了, 再对理解的现代JavaScript补充几条:
|
||||
文中讲到的现代 JavaScript 已经很多了, 再对理解的现代 JavaScript 补充几条:
|
||||
|
||||
## Dependent injection(依赖注入)
|
||||
|
||||
通过控制反转,父级不需要关心子实现细节,将子类可能用到的实例都初始化好,由子类决定引入哪些依赖。还有一个好处是维持了单实例,这一点在数据流中尤为重要,如果 store 不是单例的,那数据流必然乱了套,既希望传给子类使用,又要维持单例,依赖注入是很好的解决方案。
|
||||
|
||||
## Symbol Reflect Proxy
|
||||
Symbol 是 ES6中加入的一种新的数据类型, 每一个 Symbol 都是独一无二的, 不与其它 Symbol 重复.
|
||||
Symbol 是 ES6 中加入的一种新的数据类型, 每一个 Symbol 都是独一无二的, 不与其它 Symbol 重复.
|
||||
|
||||
ES6中的 Proxy , 则是通过 Proxy 方法, 实现对于对象的一层拦截. 提供一种机制, 代理对象的操作. 而 Reflect 是一个内置的对象,它提供可拦截 JavaScript 操作的方法。方法与代理处理程序的方法相同。
|
||||
ES6 中的 Proxy , 则是通过 Proxy 方法, 实现对于对象的一层拦截. 提供一种机制, 代理对象的操作. 而 Reflect 是一个内置的对象,它提供可拦截 JavaScript 操作的方法。方法与代理处理程序的方法相同。
|
||||
|
||||
这三篇文章非常详细介绍了这三位 API:[symbol](https://www.keithcirkel.co.uk/metaprogramming-in-es6-symbols/) [reflect](https://www.keithcirkel.co.uk/metaprogramming-in-es6-part-2-reflect/) [proxy](https://www.keithcirkel.co.uk/metaprogramming-in-es6-part-3-proxies/)
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
# 精读加密媒体扩展(Encrypted Media Extensions,EME)
|
||||
|
||||
本期精读的文章是: [W3C发布加密媒体扩展(Encrypted Media Extensions,EME)正式推荐标准](http://www.chinaw3c.org/2017-09-pressrelease-eme-recommendation.html)
|
||||
本期精读的文章是: [W3C 发布加密媒体扩展(Encrypted Media Extensions,EME)正式推荐标准](http://www.chinaw3c.org/2017-09-pressrelease-eme-recommendation.html)
|
||||
|
||||
感谢 [xekri](https://github.com/xekri) 提供 hacker news 上的热门讨论帖:[https://news.ycombinator.com/item?id=15278883](https://news.ycombinator.com/item?id=15278883)
|
||||
|
||||
@@ -17,8 +17,8 @@
|
||||
>
|
||||
> —— 摘自《[HTML5 DRM 正式成为 Web 标准,EFF 辞职抗议](http://www.solidot.org/comments?sid=53884&op=reply&type=story)》
|
||||
|
||||
以上,是我17年9月19日晚收到的一条推送消息。我当时在写《[关于 React 系前端技术的思考](https://tingge.github.io/html/react-redux-reactrouter.html)》,可是它让我意识到,该关注下 <video /> 背后的故事了。
|
||||
17年下半年发生了两件有趣的撕 X 事件:Facebook 将部分开源项目的防专利流氓证书 “BSD + Pattern” 重新授权为 “MIT” 和 W3C 发布 HTML5 版权保护的 EME 推荐标准。一时,似乎著作权、版权和开源、分享,甚至普世、网络中立性,这些声音开始在不少人耳边盘绕。
|
||||
以上,是我 17 年 9 月 19 日晚收到的一条推送消息。我当时在写《[关于 React 系前端技术的思考](https://tingge.github.io/html/react-redux-reactrouter.html)》,可是它让我意识到,该关注下 <video /> 背后的故事了。
|
||||
17 年下半年发生了两件有趣的撕 X 事件:Facebook 将部分开源项目的防专利流氓证书 “BSD + Pattern” 重新授权为 “MIT” 和 W3C 发布 HTML5 版权保护的 EME 推荐标准。一时,似乎著作权、版权和开源、分享,甚至普世、网络中立性,这些声音开始在不少人耳边盘绕。
|
||||
|
||||
“无论如何,在当前的现实中,法律是保护著作权的。” 那么,我以 EME 为切入点,和大家聊聊 HTML 5 中如何保护知识产权吧。
|
||||
|
||||
@@ -34,24 +34,24 @@
|
||||
- EME:加密媒体扩展(Encrypted Media Extensions)是 W3C 提出的一种规范,用于在 Web 浏览器和 DRM 代理软件之间提供通信通道。
|
||||
|
||||
|
||||
- MSE:媒体源扩展(Media Source Extensions)是一项 [W3C](https://zh.wikipedia.org/wiki/%E4%B8%87%E7%BB%B4%E7%BD%91%E8%81%94%E7%9B%9F) 规范,它扩展了HTMLMediaElement,允许 JavaScript 生成媒体流以支持回放。这可以用于自适应流(adaptive streaming)及随时间变化的视频直播流(live streaming)等应用场景。
|
||||
- MSE:媒体源扩展(Media Source Extensions)是一项 [W3C](https://zh.wikipedia.org/wiki/%E4%B8%87%E7%BB%B4%E7%BD%91%E8%81%94%E7%9B%9F) 规范,它扩展了 HTMLMediaElement,允许 JavaScript 生成媒体流以支持回放。这可以用于自适应流(adaptive streaming)及随时间变化的视频直播流(live streaming)等应用场景。
|
||||
- CDM:内容解密模块(Content Decryption Module),客户端或者使用端软件或硬件提供的一个机制,可以播放加密内容。
|
||||
|
||||
#### 背景
|
||||
|
||||
长期以来,“多方利益”模式的 W3C ,以或标准化引领、或被各方优良实践推动再制定标准的方式,来影响着互联网的发展。
|
||||
|
||||
2011年时 Silverlight 、HTML5 及 Flash 还是最受热捧的 RIA (富互联网应用) 技术。当时,Silverlight 的PlayReady DRM、 Flash 的 Flash Media Rights Management(FMRM),在版权保护上已十分成熟。而 HTML5 还处于 <video> 未指明编码标准的萌芽状态、更谈不上版权保护。
|
||||
2011 年时 Silverlight 、HTML5 及 Flash 还是最受热捧的 RIA (富互联网应用) 技术。当时,Silverlight 的 PlayReady DRM、 Flash 的 Flash Media Rights Management(FMRM),在版权保护上已十分成熟。而 HTML5 还处于 <video> 未指明编码标准的萌芽状态、更谈不上版权保护。
|
||||
|
||||
随着移动互联网、视频直播、职能家电等等互联网快速发展,浏览器插件一度成为网络恶意攻击的重灾区,给网络用户安全性带来很大隐患。微软和许多企业都鼓励用户、开发者使用 HTML5 的通信协议,标准化通信可以极大增加网络安全性。其中包括 W3C 的 Media Source Extensions (MSE)、 Encrypted Media Extensions (EME),MPEG的 MPEG-DASH 和 Common Encryption (CENC)。
|
||||
随着移动互联网、视频直播、职能家电等等互联网快速发展,浏览器插件一度成为网络恶意攻击的重灾区,给网络用户安全性带来很大隐患。微软和许多企业都鼓励用户、开发者使用 HTML5 的通信协议,标准化通信可以极大增加网络安全性。其中包括 W3C 的 Media Source Extensions (MSE)、 Encrypted Media Extensions (EME),MPEG 的 MPEG-DASH 和 Common Encryption (CENC)。
|
||||
|
||||
终于,内容提供商(如 Netflix、Adobe、CableLabs 等)从 Flash、Silverlight 插件播放器过渡到统一的 HTML5 视频播放;各大浏览器公司(如 Google, Microsoft, Apple)也逐步抛弃了过时的媒体插件。
|
||||
|
||||
EME 作为 HTML 5 DRM 版权保护方案中的一员,虽然从2012年提案开始就颇多争议,但是事实上已被各浏览器以捆绑闭源的 CDM 的沙箱化方式“悄悄”分发。现在,W3C 只是给了它应有的名分罢了。
|
||||
EME 作为 HTML 5 DRM 版权保护方案中的一员,虽然从 2012 年提案开始就颇多争议,但是事实上已被各浏览器以捆绑闭源的 CDM 的沙箱化方式“悄悄”分发。现在,W3C 只是给了它应有的名分罢了。
|
||||
|
||||
#### EME 对 Web 产生的影响
|
||||
|
||||
W3C理事长 Tim Berners-Lee 在《[W3C Blog: 关于HTML5标准中的加密媒体扩展(EME)](http://www.chinaw3c.org/archives/1742/)》中阐述了 EME 对内容分发商、媒体、用户、开发者、安全技术研究人员的影响。
|
||||
W3C 理事长 Tim Berners-Lee 在《[W3C Blog: 关于 HTML5 标准中的加密媒体扩展(EME)](http://www.chinaw3c.org/archives/1742/)》中阐述了 EME 对内容分发商、媒体、用户、开发者、安全技术研究人员的影响。
|
||||
|
||||
对多数人的影响大概是,可以提供一个相对安全的在线环境使用户可以获取高品质商业级的 Web 音视频等内容,并便捷的就此进行在线互动。
|
||||
|
||||
@@ -69,7 +69,7 @@ W3C理事长 Tim Berners-Lee 在《[W3C Blog: 关于HTML5标准中的加密媒
|
||||
|
||||
以下是截取 caniuse 网站统计的 EME 和 ESM 的支持情况(点击图片可跳转到对应网址):
|
||||
|
||||
[ ](http://caniuse.com/#search=EME)
|
||||
[](http://caniuse.com/#search=EME)
|
||||
|
||||
|
||||
|
||||
@@ -95,7 +95,7 @@ W3C理事长 Tim Berners-Lee 在《[W3C Blog: 关于HTML5标准中的加密媒
|
||||
|
||||
今天,在传输工作室生产的付费内容的时候,DRM 是必要的。这些内容必须防止被盗,因此 DRM 的代码和工作过程都向终端用户和开发者屏蔽了。解密过的内容不会离开解码层,因此也不会被拦截。
|
||||
|
||||
为了标准化 DRM 以及为各平台的实现提供一定的互通性,几个 Web 巨头一起创建了通用加密标准[Common Encryption (CENC)]() 和通用的多媒体加密扩展[Encrypted Media Extensions](),以便为多个 DRM 提供商(例如,EME 可用于 Edge 平台上的 Playready 和 Chrome 平台上的 Widewine)构建一套通用的 API,这些 API 能够从 DRM 授权模块读取视频内容加密密钥用于解密。
|
||||
为了标准化 DRM 以及为各平台的实现提供一定的互通性,几个 Web 巨头一起创建了通用加密标准[Common Encryption (CENC)](.) 和通用的多媒体加密扩展[Encrypted Media Extensions](.),以便为多个 DRM 提供商(例如,EME 可用于 Edge 平台上的 Playready 和 Chrome 平台上的 Widewine)构建一套通用的 API,这些 API 能够从 DRM 授权模块读取视频内容加密密钥用于解密。
|
||||
|
||||
CENC 声明了一套标准的加密和密钥映射方法,它可用于在多个 DRM 系统上解密相同的内容,只需要提供相同的密钥即可。
|
||||
|
||||
@@ -111,7 +111,7 @@ CENC 没有规定授权的发放、授权的格式、授权的存储、以及使
|
||||
|
||||
- index.html:模拟内容服务商视频播放网页,获取 EME 设置(本例中 eme.js),通过调用 MSE 模块(本例中 mse.js) 逐块加载视频片段并控制播放。
|
||||
- resources.js:模拟 License(Key) server,与 CDM 模块交互并提供解密媒体资源所需的 `key`;
|
||||
- media:模拟Key System 和 Packaging service。主要功能是提供一种内容保护(DRM)机制,实际应用中常见的 Key System 有 Clear Key、Playready、Widevine 等;另外,作为 Packaging Service,提供编码并加密媒体资源以供发布和播放使用。
|
||||
- media:模拟 Key System 和 Packaging service。主要功能是提供一种内容保护(DRM)机制,实际应用中常见的 Key System 有 Clear Key、Playready、Widevine 等;另外,作为 Packaging Service,提供编码并加密媒体资源以供发布和播放使用。
|
||||
- eme.js: 模拟 EME 通信模块。主要包括监听 MediaKeys 的 message 和 keystatuseschange 变化;发起证书请求;最后,通过 License(key) 解密 video/audio 流;
|
||||
- mse.js:模拟媒体源扩展模块,通过调用浏览器提供的 MSE API,来控制视频流播放逻辑。
|
||||
|
||||
@@ -120,7 +120,7 @@ CENC 没有规定授权的发放、授权的格式、授权的存储、以及使
|
||||
| 开源的视频播放器 | 个人点评 |
|
||||
| ---------------------------------------- | ---------------------------------------- |
|
||||
| [video.js](https://github.com/videojs/video.js) 和其[插件](https://github.com/videojs/video.js/wiki/Plugins#community-plugins)。设备检测与配置逻辑的 [videojs-contrib-hls](https://github.com/videojs/videojs-contrib-hls) 、广告 [videojs-contrib-ads](https://github.com/videojs/videojs-contrib-ads) | 免费开源的 HTML5 和 Flash 播放器,通过强大的插件应用于 [400,000 网站](https://trends.builtwith.com/media/VideoJS)。采用 Apache License, Version 2.0 授权 |
|
||||
| [JW Player](https://github.com/jwplayer/jwplayer) | 号称世界上最流行的嵌入播放器,应用于200万网站、每月13亿播放次数。采用 [Creative Commons license](http://creativecommons.org/licenses/by-nc-sa/3.0/) 授权 |
|
||||
| [JW Player](https://github.com/jwplayer/jwplayer) | 号称世界上最流行的嵌入播放器,应用于 200 万网站、每月 13 亿播放次数。采用 [Creative Commons license](http://creativecommons.org/licenses/by-nc-sa/3.0/) 授权 |
|
||||
| [Shaka Player](https://github.com/google/shaka-player) | Google 开源的基于 MSE + EME 的 JavaScript 库,支持 DASH、HLS 等。采用 Apache License 2.0 授权 |
|
||||
| [dash.js](https://github.com/Dash-Industry-Forum/dash.js) | 一个支持 MPEG DASH 的参考实现,适合研究学习。采用 BSD 授权 |
|
||||
|
||||
@@ -130,5 +130,5 @@ CENC 没有规定授权的发放、授权的格式、授权的存储、以及使
|
||||
|
||||
期待随着标准的发布,注重著作权、版权的互联网能够很快地向有序方向发展。
|
||||
|
||||
> 讨论地址是:[精读《W3C发布加密媒体扩展(Encrypted Media Extensions,EME)正式推荐标准》 · Issue #37 · dt-fe/weekly](https://github.com/dt-fe/weekly/issues/37)
|
||||
> 讨论地址是:[精读《W3C 发布加密媒体扩展(Encrypted Media Extensions,EME)正式推荐标准》 · Issue #37 · dt-fe/weekly](https://github.com/dt-fe/weekly/issues/37)
|
||||
> 如果你想参与讨论,请[点击这里](https://github.com/dt-fe/weekly),每周都有新的主题,每周五发布。
|
||||
@@ -93,7 +93,7 @@ OOCSS 成为 css 的面向对象加强版,每个 class 只处理一件事:
|
||||
|
||||
## 3.4 SMACSS
|
||||
|
||||
### 为css分类
|
||||
### 为 css 分类
|
||||
|
||||
SMACSS 认为 css 有 5 个类别:
|
||||
|
||||
|
||||
+21
-21
@@ -1,6 +1,6 @@
|
||||
本期精读的文章是:[Front End Performance Checklist 2017](https://www.smashingmagazine.com/2016/12/front-end-performance-checklist-2017-pdf-pages/)
|
||||
|
||||
现在随着 web 应用的复杂性日益增加,其性能优化就会显得尤为必要,同时会给性能指标分析带来新的挑战,因为性能指标之间的差异性非常大,这取决于使用的设备、浏览器、协议、网络类型以及其它能够对性能产生影响的潜在因素(如:CDN、ISP、cache、proxy、firewall、load balancer、server等)。
|
||||
现在随着 web 应用的复杂性日益增加,其性能优化就会显得尤为必要,同时会给性能指标分析带来新的挑战,因为性能指标之间的差异性非常大,这取决于使用的设备、浏览器、协议、网络类型以及其它能够对性能产生影响的潜在因素(如:CDN、ISP、cache、proxy、firewall、load balancer、server 等)。
|
||||
|
||||
# 1 引言
|
||||
|
||||
@@ -18,11 +18,11 @@
|
||||
|
||||
根据 [psychological research](https://www.smashingmagazine.com/2015/09/why-performance-matters-the-perception-of-time/#the-need-for-performance-optimization-the-20-rule) 指出,网站最少在速度上比别人快 20%,才能让用户感觉到比别人的更快。这个速度说的并不是整个页面的加载时间,而是[启动渲染时间](http://www.websiteoptimization.com/speed/tweak/start-render/),[首次有效渲染时间](https://developers.google.com/web/tools/lighthouse/audits/first-meaningful-paint),[交互时间](https://developers.google.com/web/tools/lighthouse/audits/time-to-interactive)。
|
||||
|
||||
### 控制响应时间在100ms,控制帧速在60帧/秒
|
||||
### 控制响应时间在 100ms,控制帧速在 60 帧/秒
|
||||
|
||||
[RAIL performance model](https://www.smashingmagazine.com/2015/10/rail-user-centric-model-performance/) 提出的性能优化指标:务必在用户初始操作后的 100ms 内提供反馈。考虑到存在响应时间不足 100ms 的情况,页面最迟要在 50ms 的时候,把控制权交给主线程。
|
||||
|
||||
针对动画,其每一帧都需要在 16ms 内完成,这样才能保证每秒 60帧(一秒/60=16.6ms),如果可以的话最好能在 10ms 内完成。
|
||||
针对动画,其每一帧都需要在 16ms 内完成,这样才能保证每秒 60 帧(一秒/60=16.6ms),如果可以的话最好能在 10ms 内完成。
|
||||
|
||||
### 控制首次有效渲染时间在 1.25s,控制 SpeedIndex 在 1000
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
|
||||
### 做好构建工具的选型
|
||||
|
||||
不要过度使用那些酷炫的技术栈,坚持选择适合开发环境的工具,如Grunt、Gulp、Webpack、PostCSS,或者组合起来的工具。只要这个工具运行的速度够快,而且没有给项目维护带来太大问题,就够了。
|
||||
不要过度使用那些酷炫的技术栈,坚持选择适合开发环境的工具,如 Grunt、Gulp、Webpack、PostCSS,或者组合起来的工具。只要这个工具运行的速度够快,而且没有给项目维护带来太大问题,就够了。
|
||||
|
||||
### 渐进增强
|
||||
|
||||
@@ -40,11 +40,11 @@
|
||||
|
||||
### 前端框架
|
||||
|
||||
最好使用那些支持服务器端渲染的框架,如Angular,React,Ember 等。所选的框架要保证是被广泛使用并且经过考验的。不同框架对性能有着不同程度的影响,同时对应着不同的优化策略,所以要清楚的了解所选择框架的每个方面。
|
||||
最好使用那些支持服务器端渲染的框架,如 Angular,React,Ember 等。所选的框架要保证是被广泛使用并且经过考验的。不同框架对性能有着不同程度的影响,同时对应着不同的优化策略,所以要清楚的了解所选择框架的每个方面。
|
||||
|
||||
### AMP 或 Instant Articles
|
||||
|
||||
- Google 的 [AMP](https://www.ampproject.org/) 技术会提供一套可靠的性能优化框架(基于免费的CDN网络)
|
||||
- Google 的 [AMP](https://www.ampproject.org/) 技术会提供一套可靠的性能优化框架(基于免费的 CDN 网络)
|
||||
- Facebook 的 [Instant Articles](https://instantarticles.fb.com/) 技术可以在 Facebook 上提升网站的性能。
|
||||
|
||||
### 合理利用 CDN
|
||||
@@ -63,7 +63,7 @@
|
||||
|
||||
### micro-optimization 和 progressive booting
|
||||
|
||||
- 使用 [skeleton screens](https://twitter.com/lukew/status/665288063195594752) 代替loading indicator 展示
|
||||
- 使用 [skeleton screens](https://twitter.com/lukew/status/665288063195594752) 代替 loading indicator 展示
|
||||
- 使用能够加速 App 初始化渲染的技术,如 [tree-shaking](https://medium.com/@richavyas/aha-moments-from-ngconf-2016-part-1-angular-2-0-compile-cycle-6f462f68632e#.8b9afnsub)、[code-splitting](https://webpack.github.io/docs/code-splitting.html)
|
||||
- 针对[服务端渲染](https://www.smashingmagazine.com/2016/03/server-side-rendering-react-node-express)增加[预编译](https://www.lucidchart.com/techblog/2016/09/26/improving-angular-2-load-times/)环节
|
||||
- 使用 [Optimize.js](https://github.com/nolanlawson/optimize-js) 来加快初始加载速度,其原理是包装优先级高的调用函数
|
||||
@@ -71,7 +71,7 @@
|
||||
|
||||
### 正确设置 HTTP cache header
|
||||
|
||||
需要正确设置 expires、cache-control、max-age以及其它 HTTP 缓存响应头。请使用 Cache-control: immutable,可以参考 [Heroku’s primer on HTTP caching headers](https://devcenter.heroku.com/articles/increasing-application-performance-with-http-cache-headers)、[HTTP caching primer](https://developers.google.com/web/fundamentals/performance/optimizing-content-efficiency/http-caching?hl=en)以及[缓存之最佳实践](https://jakearchibald.com/2016/caching-best-practices/)。
|
||||
需要正确设置 expires、cache-control、max-age 以及其它 HTTP 缓存响应头。请使用 Cache-control: immutable,可以参考 [Heroku’s primer on HTTP caching headers](https://devcenter.heroku.com/articles/increasing-application-performance-with-http-cache-headers)、[HTTP caching primer](https://developers.google.com/web/fundamentals/performance/optimizing-content-efficiency/http-caching?hl=en)以及[缓存之最佳实践](https://jakearchibald.com/2016/caching-best-practices/)。
|
||||
|
||||
### 减少使用第三方库,异步加载 JS
|
||||
|
||||
@@ -98,16 +98,16 @@
|
||||
- 可以使用 service worker 来达到字体缓存持久化
|
||||
- [关于如何快速入门字体优化的教程](https://pixelambacht.nl/2016/font-awesome-fixed/)
|
||||
|
||||
### 快速推送 critical CSS文件
|
||||
### 快速推送 critical CSS 文件
|
||||
|
||||
为了保证能够让浏览器快速渲染,会将所有用于首屏渲染的 CSS 文件整合成一个文件(即 [critical CSS](https://www.smashingmagazine.com/2015/08/understanding-critical-css/)),以 `<style>` 的行内形式内嵌到 `<head>`,这样可以减少 [critical 渲染路径](https://developers.google.com/web/fundamentals/performance/critical-rendering-path/optimizing-critical-rendering-path?hl=en)。由于 HTTP 数据包大小的限制,因此 critical CSS 文件大小不能超过14KB。
|
||||
为了保证能够让浏览器快速渲染,会将所有用于首屏渲染的 CSS 文件整合成一个文件(即 [critical CSS](https://www.smashingmagazine.com/2015/08/understanding-critical-css/)),以 `<style>` 的行内形式内嵌到 `<head>`,这样可以减少 [critical 渲染路径](https://developers.google.com/web/fundamentals/performance/critical-rendering-path/optimizing-critical-rendering-path?hl=en)。由于 HTTP 数据包大小的限制,因此 critical CSS 文件大小不能超过 14KB。
|
||||
|
||||
HTTP/2 协议可以让 critical CSS 用单个 CSS 文件存储,通过服务器推送 CSS 文件的传输方式来减少HTML 文件数据量,由于存在高速缓存问题,因此需要建立[带有缓存的 HTTP/2 服务器传输机制](https://css-tricks.com/cache-aware-server-push/)。
|
||||
HTTP/2 协议可以让 critical CSS 用单个 CSS 文件存储,通过服务器推送 CSS 文件的传输方式来减少 HTML 文件数据量,由于存在高速缓存问题,因此需要建立[带有缓存的 HTTP/2 服务器传输机制](https://css-tricks.com/cache-aware-server-push/)。
|
||||
|
||||
### tree-shaking 和 code-splitting 机制减轻负载
|
||||
|
||||
- [Tree-shaking](https://medium.com/@roman01la/dead-code-elimination-and-tree-shaking-in-javascript-build-systems-fb8512c86edf) 机制能够帮助清理生产环境中的冗余代码。可以通过 [Webpack2 Tree-Shaking](http://www.2ality.com/2015/12/webpack-tree-shaking.html) 机制来清理冗余的 exports 代码或者使用 [UnCSS](https://github.com/giakki/uncss)、[Helium](https://github.com/geuis/helium-css) 工具来清理冗余的CSS代码
|
||||
- [code splitting](https://webpack.github.io/docs/code-splitting.html) 机制是Webpack 的另一个特性,它能够将构建的代码分成多个 chunk,并且对 chunk 按需载入。只要在代码中定义了分离点(split point),Webpack 便会处理好相关的输出文件,不仅能够较少文件数据量,而且还能对代码做到按需载入。
|
||||
- [Tree-shaking](https://medium.com/@roman01la/dead-code-elimination-and-tree-shaking-in-javascript-build-systems-fb8512c86edf) 机制能够帮助清理生产环境中的冗余代码。可以通过 [Webpack2 Tree-Shaking](http://www.2ality.com/2015/12/webpack-tree-shaking.html) 机制来清理冗余的 exports 代码或者使用 [UnCSS](https://github.com/giakki/uncss)、[Helium](https://github.com/geuis/helium-css) 工具来清理冗余的 CSS 代码
|
||||
- [code splitting](https://webpack.github.io/docs/code-splitting.html) 机制是 Webpack 的另一个特性,它能够将构建的代码分成多个 chunk,并且对 chunk 按需载入。只要在代码中定义了分离点(split point),Webpack 便会处理好相关的输出文件,不仅能够较少文件数据量,而且还能对代码做到按需载入。
|
||||
- 用 [Rollup](http://rollupjs.org/) 来 export 代码也能够取得不错的效果
|
||||
|
||||
### 提升渲染性能
|
||||
@@ -131,7 +131,7 @@ HTTP/2 协议可以让 critical CSS 用单个 CSS 文件存储,通过服务器
|
||||
|
||||
从目前来看,浏览器对 HTTP/2 支持度还不错,使用 HTTP/2 后,就可以利用 service worker 以及 HTTP/2 的服务器推送功能来获取更显著的性能提升。
|
||||
|
||||
在项目进行 HTTPS 改造时,需要评估 HTTP/1.1 项目的用户基数,需要针对这类用户构建并发送符合HTTP2规范的报头。
|
||||
在项目进行 HTTPS 改造时,需要评估 HTTP/1.1 项目的用户基数,需要针对这类用户构建并发送符合 HTTP2 规范的报头。
|
||||
|
||||
### 正确部署 HTTP/2
|
||||
|
||||
@@ -142,7 +142,7 @@ HTTP/2 协议可以让 critical CSS 用单个 CSS 文件存储,通过服务器
|
||||
|
||||
### 确保服务器的安全性
|
||||
|
||||
需要检查是否[正确设置 HTTP 请求头部](https://securityheaders.io/),如 strict-transport-security,[使用 Snyk 工具](https://www.w3ctech.com/www.smashingmagazine.com/2016/01/eliminating-known-security-vulnerabilities-with-snyk/)排除已知的漏洞以及[使用 SSL Server Test](https://www.ssllabs.com/ssltest/) 网站来检查证书是否失效。 尽量保证从外部引入的插件以及 js 脚本的载入是通过 HTTPS 协议的,发起 HTTP 请求同时设置 strict-transport-security 以及 content-security-policy HTTP请求头。
|
||||
需要检查是否[正确设置 HTTP 请求头部](https://securityheaders.io/),如 strict-transport-security,[使用 Snyk 工具](https://www.w3ctech.com/www.smashingmagazine.com/2016/01/eliminating-known-security-vulnerabilities-with-snyk/)排除已知的漏洞以及[使用 SSL Server Test](https://www.ssllabs.com/ssltest/) 网站来检查证书是否失效。 尽量保证从外部引入的插件以及 js 脚本的载入是通过 HTTPS 协议的,发起 HTTP 请求同时设置 strict-transport-security 以及 content-security-policy HTTP 请求头。
|
||||
|
||||
### 服务器和 CDN 是否支持 HTTP/2
|
||||
|
||||
@@ -151,7 +151,7 @@ HTTP/2 协议可以让 critical CSS 用单个 CSS 文件存储,通过服务器
|
||||
### Brotli 或 Zopfli 压缩算法
|
||||
|
||||
- [Brotli](https://samsaffron.com/archive/2016/06/15/the-current-state-of-brotli-compression),是 Google 开源的无损数据格式,其压缩效率要远高于 Gzip 和 Deflate
|
||||
- [Zopfli压缩算法](https://blog.codinghorror.com/zopfli-optimization-literally-free-bandwidth/),能够将数据编码成 Deflate、Gzip、Zlib 数据格式。用 Zopfli 算法压缩过后的文件能够比同样用 Zlib 算法压缩的文件小 3%-8%
|
||||
- [Zopfli 压缩算法](https://blog.codinghorror.com/zopfli-optimization-literally-free-bandwidth/),能够将数据编码成 Deflate、Gzip、Zlib 数据格式。用 Zopfli 算法压缩过后的文件能够比同样用 Zlib 算法压缩的文件小 3%-8%
|
||||
|
||||
### 激活 OCSP stapling
|
||||
|
||||
@@ -186,7 +186,7 @@ HTTP/2 协议可以让 critical CSS 用单个 CSS 文件存储,通过服务器
|
||||
|
||||
### 持续监控
|
||||
|
||||
在进行快速、无限制的测试时,最好使用一个个人的WebPageTest实例。建立一个能自动预警的性能预算监听。建立自己的用户时间标记从而测量并监测具体商用的数据。使用SpeedCurve对性能的变化进行监控,同时利用New Relic获取WebPageTest没法提供的数据。SpeedTracker,Lighthouse和Calibre都是不错的选择。
|
||||
在进行快速、无限制的测试时,最好使用一个个人的 WebPageTest 实例。建立一个能自动预警的性能预算监听。建立自己的用户时间标记从而测量并监测具体商用的数据。使用 SpeedCurve 对性能的变化进行监控,同时利用 New Relic 获取 WebPageTest 没法提供的数据。SpeedTracker,Lighthouse 和 Calibre 都是不错的选择。
|
||||
|
||||
部署私密的 [WebPageTest](http://www.webpagetest.org/) 测试环境,有助于快速构建测试用例。针对性能开销大的环节建立自动报警机制,可以使用 [SpeedCurve](https://speedcurve.com/) 对性能的变化进行监控,利用 [New Relic](https://newrelic.com/browser-monitoring) 获取 WebPageTest 无法提供的数据。
|
||||
|
||||
@@ -251,9 +251,9 @@ render 部分包括 Recalculate Style 和 Layout,如果发现 render 部分耗
|
||||
|
||||
#### 降低样式计算和复杂度
|
||||
|
||||
添加或移除一个DOM元素、修改元素属性和样式类、应用动画效果等操作,都会引起DOM结构的改变,从而导致浏览器需要重新计算每个元素的样式、对页面或其一部分重新布局(多数情况下),这就是所谓的样式计算。
|
||||
添加或移除一个 DOM 元素、修改元素属性和样式类、应用动画效果等操作,都会引起 DOM 结构的改变,从而导致浏览器需要重新计算每个元素的样式、对页面或其一部分重新布局(多数情况下),这就是所谓的样式计算。
|
||||
|
||||
因此需要减少执行样式计算的元素的个数,降低样式选择器的复杂度,使用基于 class 的方式,如以BEM (Block, Element, Modifier)的方式编写 CSS 代码,能达到最好的样式计算的性能,因为这种方式建议对每个DOM元素都只使用一个样式class。
|
||||
因此需要减少执行样式计算的元素的个数,降低样式选择器的复杂度,使用基于 class 的方式,如以 BEM (Block, Element, Modifier)的方式编写 CSS 代码,能达到最好的样式计算的性能,因为这种方式建议对每个 DOM 元素都只使用一个样式 class。
|
||||
|
||||
#### 避免大规模、复杂的布局
|
||||
|
||||
@@ -280,7 +280,7 @@ Timeline 中绿色部分就是 Paint 部分,Summary 会展示绘制的总体
|
||||
#### 如何优化 Paint
|
||||
|
||||
- 提升元素渲染层为合成层,页面的绘制并非是在单层画面里完成的,浏览器的渲染原理,是浏览器将 DOM tree 映射成 GraphicsLayer tree,中间是经过了 RenderObject、RenderLayer 的一系列映射。元素所在的层提升为合成层后可以减少 Repaint
|
||||
- 使用 transform 或 opacity 实现动画,对于独立的合成层应用 transform 和 opacity 是不会触发 Repaint的,因此尽量对 transform 或 opactiy 应用动画来实现效果
|
||||
- 使用 transform 或 opacity 实现动画,对于独立的合成层应用 transform 和 opacity 是不会触发 Repaint 的,因此尽量对 transform 或 opactiy 应用动画来实现效果
|
||||
- 减少绘制区域,对于不需要重新绘制的区域应尽量避免绘制,已减少绘制区域,比如一个 fix 在页面顶部的固定不变的导航 header,在页面底部某个区域 Repaint 时,整个屏幕包括 fix 的 header 也会被重绘,而对于固定不变的区域,期望其并不会被重绘,因此可以通过之前的方法,将其提升为独立的合成层
|
||||
- 降低绘制复杂度,对于无法避免的 Paint,需要尽可能的减少 Paint 的消耗,有些效果的 Paint 代价十分昂贵,比如绘制一个阴影可能就比绘制一个边框更加耗时,因此开发过程中,需要研究能够实现相同的效果,同时却能达到更小的 Paint 消耗的方法
|
||||
|
||||
@@ -311,6 +311,6 @@ Timeline 中绿色部分就是 Paint 部分,Summary 会展示绘制的总体
|
||||
|
||||
现在随着 web 应用的复杂性日益增加,其性能优化的重要性越来越突出,且性能优化的方法、技巧、工具也越来越丰富和复杂,本文所展示的内容仅仅只是管中窥豹,希望读者们可以在此讨论一些在实际场景中的性能优化问题以及解决方案。
|
||||
|
||||
> 讨论地址是:[精读《2017前端性能优化备忘录》 · Issue #39 · dt-fe/weekly](http://github.com/dt-fe/weekly/issues/39)
|
||||
> 讨论地址是:[精读《2017 前端性能优化备忘录》 · Issue #39 · dt-fe/weekly](http://github.com/dt-fe/weekly/issues/39)
|
||||
|
||||
> 如果你想参与讨论,请[点击这里](https://github.com/dt-fe/weekly),每周都有新的主题,每周五发布。
|
||||
|
||||
+11
-11
@@ -6,7 +6,7 @@
|
||||
|
||||
我为什么要选这篇文章呢?
|
||||
|
||||
sessionstack最近接连发了好几篇文章, 深入探讨JS, 以及 JS 中一些内部原理. 文中也讲到了, 伴随深入了解 JS 中的一些工作原理, 才有可能写出更好的代码和程序.
|
||||
sessionstack 最近接连发了好几篇文章, 深入探讨 JS, 以及 JS 中一些内部原理. 文中也讲到了, 伴随深入了解 JS 中的一些工作原理, 才有可能写出更好的代码和程序.
|
||||
|
||||
而 JS 中的内存管理, 我的感觉就像 JS 中的一门副科, 我们平时不会太重视, 但是一旦出问题又很棘手. 所以可以通过平时多了解一些 JS 中内存管理问题, 在写代码中通过一些习惯, 避免内存泄露的问题.
|
||||
|
||||
@@ -23,11 +23,11 @@ sessionstack最近接连发了好几篇文章, 深入探讨JS, 以及 JS 中一
|
||||
2. 使用分配到的内存(读, 写)
|
||||
3. 不需要时将其释放/归还
|
||||
|
||||
在 C语言中, 有专门的内存管理接口, 像`malloc()` 和 `free()`. 而在 JS 中, 没有专门的内存管理接口, 所有的内存管理都是"自动"的. JS 在创建变量时, 自动分配内存, 并在不使用的时候, 自动释放. 这种"自动"的内存回收, 造成了很多 JS 开发并不关心内存回收, 实际上, 这是错误的.
|
||||
在 C 语言中, 有专门的内存管理接口, 像`malloc()` 和 `free()`. 而在 JS 中, 没有专门的内存管理接口, 所有的内存管理都是"自动"的. JS 在创建变量时, 自动分配内存, 并在不使用的时候, 自动释放. 这种"自动"的内存回收, 造成了很多 JS 开发并不关心内存回收, 实际上, 这是错误的.
|
||||
|
||||
## JS 中的内存回收
|
||||
### 引用
|
||||
垃圾回收算法主要依赖于引用的概念. 在内存管理的环境中, 一个对象如果有访问另一个对象的权限(隐式或者显式), 叫做一个对象引用另一个对象. 例如: 一个Javascript对象具有对它原型的引用(隐式引用)和对它属性的引用(显式引用).
|
||||
垃圾回收算法主要依赖于引用的概念. 在内存管理的环境中, 一个对象如果有访问另一个对象的权限(隐式或者显式), 叫做一个对象引用另一个对象. 例如: 一个 Javascript 对象具有对它原型的引用(隐式引用)和对它属性的引用(显式引用).
|
||||
|
||||
### 引用计数垃圾收集
|
||||
这是最简单的垃圾收集算法.此算法把“对象是否不再需要”简化定义为“对象有没有其他对象引用到它”. 如果没有引用指向该对象(零引用, 对象将被垃圾回收机制回收.
|
||||
@@ -64,13 +64,13 @@ window.onload = function(){
|
||||
### 标记-清除算法
|
||||
这个算法把“对象是否不再需要”简化定义为“对象是否可以获得”.
|
||||
|
||||
这个算法假定设置一个叫做根`root`的对象(在Javascript里,根是全局对象). 定期的, 垃圾回收器将从根开始, 找所有从根开始引用的对象, 然后找这些对象引用的对象, 从根开始,垃圾回收器将找到所有可以获得的对象和所有不能获得的对象.
|
||||
这个算法假定设置一个叫做根`root`的对象(在 Javascript 里,根是全局对象). 定期的, 垃圾回收器将从根开始, 找所有从根开始引用的对象, 然后找这些对象引用的对象, 从根开始,垃圾回收器将找到所有可以获得的对象和所有不能获得的对象.
|
||||
|
||||
从2012年起, 所有现代浏览器都使用了标记-清除内存回收算法。所有对JavaScript垃圾回收算法的改进都是基于标记-清除算法的改进.
|
||||
从 2012 年起, 所有现代浏览器都使用了标记-清除内存回收算法。所有对 JavaScript 垃圾回收算法的改进都是基于标记-清除算法的改进.
|
||||

|
||||
|
||||
### 自动 GC 的问题
|
||||
尽管自动 GC 很方便, 但是我们不知道GC 什么时候会进行. 这意味着如果我们在使用过程中使用了大量的内存, 而 GC 没有运行的情况下, 或者 GC 无法回收这些内存的情况下, 程序就有可能假死, 这个就需要我们在程序中手动做一些操作来触发内存回收.
|
||||
尽管自动 GC 很方便, 但是我们不知道 GC 什么时候会进行. 这意味着如果我们在使用过程中使用了大量的内存, 而 GC 没有运行的情况下, 或者 GC 无法回收这些内存的情况下, 程序就有可能假死, 这个就需要我们在程序中手动做一些操作来触发内存回收.
|
||||
|
||||
### 什么是内存泄露?
|
||||
本质上讲, 内存泄露就是不再被需要的内存, 由于某种原因, 无法被释放.
|
||||
@@ -94,11 +94,11 @@ function foo() {
|
||||
// Foo 被调用时, this 指向全局变量(window)
|
||||
foo();
|
||||
```
|
||||
在这种情况下调用`foo`, this被指向了全局变量`window`, 意外的创建了全局变量.
|
||||
在这种情况下调用`foo`, this 被指向了全局变量`window`, 意外的创建了全局变量.
|
||||
|
||||
我们谈到了一些意外情况下定义的全局变量, 代码中也有一些我们明确定义的全局变量. 如果使用这些全局变量用来暂存大量的数据, 记得在使用后, 对其重新赋值为 null.
|
||||
#### 2. 未销毁的定时器和回调函数
|
||||
在很多库中, 如果使用了观察者模式, 都会提供回调方法, 来调用一些回调函数. 要记得回收这些回调函数. 举一个 setInterval的例子.
|
||||
在很多库中, 如果使用了观察者模式, 都会提供回调方法, 来调用一些回调函数. 要记得回收这些回调函数. 举一个 setInterval 的例子.
|
||||
```javascript
|
||||
var serverData = loadData();
|
||||
setInterval(function() {
|
||||
@@ -133,7 +133,7 @@ setInterval(replaceThing, 1000);
|
||||
|
||||
这个范例的关键在于, 闭包之间是共享作用域的, 尽管`unused`可能一直没有被调用, 但是`someMethod` 可能会被调用, 就会导致内存无法对其进行回收. 当这段代码被反复执行时, 内存会持续增长.
|
||||
|
||||
该问题的更多描述可见[Meteor团队的这篇文章](https://blog.meteor.com/an-interesting-kind-of-javascript-memory-leak-8b47d2e7f156).
|
||||
该问题的更多描述可见[Meteor 团队的这篇文章](https://blog.meteor.com/an-interesting-kind-of-javascript-memory-leak-8b47d2e7f156).
|
||||
#### 4. DOM 引用
|
||||
很多时候, 我们对 Dom 的操作, 会把 Dom 的引用保存在一个数组或者 Map 中.
|
||||
```Javascript
|
||||
@@ -153,7 +153,7 @@ function removeImage() {
|
||||
另外需要注意的一个点是, 对于一个 Dom 树的叶子节点的引用. 举个例子: 如果我们引用了一个表格中的`td`元素, 一旦在 Dom 中删除了整个表格, 我们直观的觉得内存回收应该回收除了被引用的 `td`外的其他元素. 但是事实上, 这个`td` 元素是整个表格的一个子元素, 并保留对于其父元素的引用. 这就会导致对于整个表格, 都无法进行内存回收. 所以我们要小心处理对于 Dom 元素的引用.
|
||||
# 3 精读
|
||||
|
||||
ES6中引入`WeakSet` 和 `WeakMap`两个新的概念, 来解决引用造成的内存回收问题. `WeakSet` 和 `WeakMap`对于值的引用可以忽略不计, 他们对于值的引用是弱引用,内存回收机制, 不会考虑这种引用. 当其他引用被消除后, 引用就会从内存中被释放.
|
||||
ES6 中引入`WeakSet` 和 `WeakMap`两个新的概念, 来解决引用造成的内存回收问题. `WeakSet` 和 `WeakMap`对于值的引用可以忽略不计, 他们对于值的引用是弱引用,内存回收机制, 不会考虑这种引用. 当其他引用被消除后, 引用就会从内存中被释放.
|
||||
|
||||
JS 这类高级语言,隐藏了内存管理功能。但无论开发人员是否注意,内存管理都在那,所有编程语言最终要与操作系统打交道,在内存大小固定的硬件上工作。不幸的是,即使不考虑垃圾回收对性能的影响,2017 年最新的垃圾回收算法,也无法智能回收所有极端的情况。
|
||||
|
||||
@@ -161,7 +161,7 @@ JS 这类高级语言,隐藏了内存管理功能。但无论开发人员是
|
||||
|
||||
所以在 JS 这类高级语言中,有必要掌握基础内存分配原理,在对内存敏感的场景,比如 nodejs 代码做严格检查与优化。谨慎使用 dom 操作、主动删除没有业务意义的变量、避免提前优化、过度优化,在保证代码可读性的前提下,利用性能监控工具,通过调用栈定位问题代码。
|
||||
|
||||
同时对于如何利用 chrome调试工具, 分析内存泄露的方法和技巧. 可以参考上期精读[精读《2017前端性能优化备忘录》](https://zhuanlan.zhihu.com/p/30349982)
|
||||
同时对于如何利用 chrome 调试工具, 分析内存泄露的方法和技巧. 可以参考上期精读[精读《2017 前端性能优化备忘录》](https://zhuanlan.zhihu.com/p/30349982)
|
||||
|
||||
# 4 总结
|
||||
|
||||
|
||||
@@ -6,13 +6,13 @@
|
||||
|
||||
我为什么要选这篇文章呢?
|
||||
|
||||
sessionstack最近接连发了好几篇文章, 深入探讨JS, 以及 JS 中一些内部原理. 文中也讲到了, 伴随深入了解 JS 中的一些工作原理, 才有可能写出更好的代码和程序.
|
||||
sessionstack 最近接连发了好几篇文章, 深入探讨 JS, 以及 JS 中一些内部原理. 文中也讲到了, 伴随深入了解 JS 中的一些工作原理, 才有可能写出更好的代码和程序.
|
||||
|
||||
而 JS 中 Event Loop, 我的感觉就像 JS 中的一门内科, 我们平时只注意外科创伤,却忽视了内科问题往往容易莫名其妙的生病。了解 JS Event Loop 的原理,对 `setTimeout` `Promise` 这种基础概念不再浮在表层,可以写出更可靠的代码,如果你是前端新人,不要总是因为这个问题挂在一面 :p。
|
||||
|
||||
# 2 内容概要
|
||||
|
||||
从前我对 Event Loop 的理解也并不透彻,通过仔细阅读此文后, Event Loop 、宿主环境、js线程三者之间关系更加透明了,希望读者读完后也能有所体会。
|
||||
从前我对 Event Loop 的理解也并不透彻,通过仔细阅读此文后, Event Loop 、宿主环境、js 线程三者之间关系更加透明了,希望读者读完后也能有所体会。
|
||||
|
||||
文中 Promise、async/await 部分就忽略了,本篇重点介绍 Event Loop 。
|
||||
|
||||
|
||||
@@ -1,21 +1,21 @@
|
||||
本期精读的文章是:[30行js代码创建神经网络](https://medium.freecodecamp.org/how-to-create-a-neural-network-in-javascript-in-only-30-lines-of-code-343dafc50d49)。
|
||||
本期精读的文章是:[30 行 js 代码创建神经网络](https://medium.freecodecamp.org/how-to-create-a-neural-network-in-javascript-in-only-30-lines-of-code-343dafc50d49)。
|
||||
|
||||
懒得看文章?没关系,稍后会附上文章内容概述,同时,更希望能通过阅读这一期的精读,穿插着深入阅读原文。
|
||||
|
||||
## 1 引言
|
||||

|
||||
|
||||
自从Alpha Go 打败了李世石,大家对深度学习的体感更加的强烈,人工智能也越来越多的出现在大家的生活之中。很多人也会谈论,程序员什么时候会被人工智能给替代?与其慌张,在人工智能的潮流下,不断学习新的人工智能相关技术,武装自己,才是硬道理。 本文介绍了如何使用[Synaptic.js](https://synaptic.juancazala.com/#/) 创建简单的神经网络,解决异或运算的问题。
|
||||
自从 Alpha Go 打败了李世石,大家对深度学习的体感更加的强烈,人工智能也越来越多的出现在大家的生活之中。很多人也会谈论,程序员什么时候会被人工智能给替代?与其慌张,在人工智能的潮流下,不断学习新的人工智能相关技术,武装自己,才是硬道理。 本文介绍了如何使用[Synaptic.js](https://synaptic.juancazala.com/#/) 创建简单的神经网络,解决异或运算的问题。
|
||||
|
||||
## 2 内容概要
|
||||
|
||||
### 神经网络中的神经元和突触
|
||||
|
||||
对神经网络有所了解的人都知道,神经网络就是构建类似人脑的神经系统,在人脑的神经系统中,存在一种非常重要的细胞,叫神经元。在神经网络中,你可以把神经元理解为一个函数,它接受一些输入返回一些输出结果,其中[Sigmoid神经元](https://en.wikipedia.org/wiki/Sigmoid_function)是一种非常常用的神经元,这种神经元以 Sigmoid 函数 作为[激活函数](https://www.zhihu.com/question/22334626)。Sigmoid 函数接受任意的数值,输出 `0` 到 `1` 之间的值,大家可以看看常见的几种 Sigmoid 函数的函数曲线。
|
||||
对神经网络有所了解的人都知道,神经网络就是构建类似人脑的神经系统,在人脑的神经系统中,存在一种非常重要的细胞,叫神经元。在神经网络中,你可以把神经元理解为一个函数,它接受一些输入返回一些输出结果,其中[Sigmoid 神经元](https://en.wikipedia.org/wiki/Sigmoid_function)是一种非常常用的神经元,这种神经元以 Sigmoid 函数 作为[激活函数](https://www.zhihu.com/question/22334626)。Sigmoid 函数接受任意的数值,输出 `0` 到 `1` 之间的值,大家可以看看常见的几种 Sigmoid 函数的函数曲线。
|
||||
|
||||

|
||||
|
||||
知道了 Sigmoid 函数了,我们可以看一个具体的 **Sigmoid神经元** 例子。
|
||||
知道了 Sigmoid 函数了,我们可以看一个具体的 **Sigmoid 神经元** 例子。
|
||||
|
||||

|
||||
|
||||
@@ -71,11 +71,11 @@ for (var i = 0; i < 20000; i++) {
|
||||
}
|
||||
```
|
||||
|
||||
简单而言,运行 `myNetwork.activate([0,0])` 时,`[0, 0]`是输入值,它对应的异或运算的结果是false, 也就是 `0`。 这个是前向的传播,所以称为 **激活** 网络,每次前向传播之后,我们需要做一次**反向传播**来更新权重和偏差。
|
||||
简单而言,运行 `myNetwork.activate([0,0])` 时,`[0, 0]`是输入值,它对应的异或运算的结果是 false, 也就是 `0`。 这个是前向的传播,所以称为 **激活** 网络,每次前向传播之后,我们需要做一次**反向传播**来更新权重和偏差。
|
||||
|
||||
反向传播就是通过这行代码来做的: `myNetwork.propagate(learningRate, [0])`,其中 `learningRate `是告诉神经网络如何调整权重的常量,第二个参数 `[0]`是异或运算的结果。
|
||||
|
||||
经过20000次学习,我们得到如下的结果:
|
||||
经过 20000 次学习,我们得到如下的结果:
|
||||
|
||||
```js
|
||||
console.log(myNetwork.activate([0,0]));
|
||||
@@ -129,7 +129,7 @@ console.log(myNetwork.activate([1,1]));
|
||||
|
||||
## 4. 总结
|
||||
|
||||
本文介绍了使用[Synaptic.js](https://synaptic.juancazala.com/#/) 创建简单的神经网络,解决异或运算的问题过程,也对**反向传播**的过程进行了简单的解释。文中实现神经网络的代码非常简单,包行注释也不超过30行,但是只会代码会有点囫囵吞枣的感觉,大家可以参考文章中给的引文,了解更多的算法原理。
|
||||
本文介绍了使用[Synaptic.js](https://synaptic.juancazala.com/#/) 创建简单的神经网络,解决异或运算的问题过程,也对**反向传播**的过程进行了简单的解释。文中实现神经网络的代码非常简单,包行注释也不超过 30 行,但是只会代码会有点囫囵吞枣的感觉,大家可以参考文章中给的引文,了解更多的算法原理。
|
||||
|
||||
### 相关资料
|
||||
|
||||
@@ -144,6 +144,6 @@ console.log(myNetwork.activate([1,1]));
|
||||
|
||||
### 更多讨论
|
||||
|
||||
> 讨论地址是:[精读《30行js代码创建神经网络》 · Issue #45 · dt-fe/weekly](https://github.com/dt-fe/weekly/issues/45)
|
||||
> 讨论地址是:[精读《30 行 js 代码创建神经网络》 · Issue #45 · dt-fe/weekly](https://github.com/dt-fe/weekly/issues/45)
|
||||
|
||||
**如果你想参与讨论,请[点击这里](https://github.com/dt-fe/weekly),每周都有新的主题,每周五发布。**
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
本篇是 《框架实现》。
|
||||
|
||||
本周精读的文章是 [dob文档](https://dobjs.github.io/dob-docs/v2/guide/introduction.html),如果不熟悉 API,可以简单读一读,文中有些地方会提到一些函数。
|
||||
本周精读的文章是 [dob 文档](https://dobjs.github.io/dob-docs/v2/guide/introduction.html),如果不熟悉 API,可以简单读一读,文中有些地方会提到一些函数。
|
||||
|
||||
## 1 引言
|
||||
|
||||
|
||||
@@ -88,7 +88,7 @@ git 的版本控制实际就是对文件进行管理和控制,其管理方法
|
||||
|
||||

|
||||
|
||||
其实是 40位 hash-key 的前两位作为目录名,后 38位 作为文件名,这个对象里面的内容就是 file1.js 的内容,可以查看该对象的内容和类型。
|
||||
其实是 40 位 hash-key 的前两位作为目录名,后 38 位 作为文件名,这个对象里面的内容就是 file1.js 的内容,可以查看该对象的内容和类型。
|
||||
|
||||
执行 git cat-file -p [hash-key] 可以查看已经存在的 object 对象内容;
|
||||
|
||||
@@ -141,7 +141,7 @@ blob 对象用于存储对应文件的内容,tree 对象可以理解为存储
|
||||
|
||||

|
||||
|
||||
目前每个目录里有一个对象,共5个对象,之前的总体 tree 图只包含了4个对象,执行 git log 查看 commit 记录。
|
||||
目前每个目录里有一个对象,共 5 个对象,之前的总体 tree 图只包含了 4 个对象,执行 git log 查看 commit 记录。
|
||||
|
||||

|
||||
|
||||
@@ -173,7 +173,7 @@ git 版本控制住要就是围绕这三类内部对象展开,分别为 blob
|
||||
|
||||
# 4 总结
|
||||
|
||||
本文从一个实际问题即如何使用 git 维护丢失的 commit 入手,并给出相应的解决思路和方案,以及通过git 内部三种对象来分析其内部工作机制,希望能过解决读者们对 git 存在困惑的地方,同时也希望读者们积极参与每周精度的讨论,各抒己见,分享自身在实际工作中遇到的问题及其解决思路。
|
||||
本文从一个实际问题即如何使用 git 维护丢失的 commit 入手,并给出相应的解决思路和方案,以及通过 git 内部三种对象来分析其内部工作机制,希望能过解决读者们对 git 存在困惑的地方,同时也希望读者们积极参与每周精度的讨论,各抒己见,分享自身在实际工作中遇到的问题及其解决思路。
|
||||
|
||||
> 讨论地址是:[精读《When You “Git” in Trouble - a Version Control Story》 · Issue #49 · dt-fe/weekly](http://github.com/dt-fe/weekly/issues/49)
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
# 摘要
|
||||
本期精读文章以一个简单的例子,抽丝剥茧细数讲述如何面向用户可视化设计,探索用户最终的目的,化繁为简,化多为少,揉和N张图至一张图,并传达更多的深意。本文原文:http://www.storytellingwithdata.com/blog/2017/12/14/how-we-position-and-what-we-compare
|
||||
本期精读文章以一个简单的例子,抽丝剥茧细数讲述如何面向用户可视化设计,探索用户最终的目的,化繁为简,化多为少,揉和 N 张图至一张图,并传达更多的深意。本文原文:http://www.storytellingwithdata.com/blog/2017/12/14/how-we-position-and-what-we-compare
|
||||
|
||||
下面是案例优化的具体步骤:
|
||||
# 庖丁解牛 - 可视化案例优化
|
||||
可视化的时候,一定要优先考虑用户能够比较什么,然后把这些数据放到一个基准上,并把要比较的东西放到最近的地方。这样用户就可以快速简便的处理比较数据。图中有一系列类似分面的柱状图,代表了 Q1 ~ Q3 三个季度的账户 targeted筛选 -> engaged订阅 -> pitched投放 -> adopted采用的四个状态的百分比,这四个状态是呈漏斗式的数据,分别是上一个数据的子集。这个案列中,用户希望能够在一张图中比较所有的数据。如下是基础原始图:
|
||||
可视化的时候,一定要优先考虑用户能够比较什么,然后把这些数据放到一个基准上,并把要比较的东西放到最近的地方。这样用户就可以快速简便的处理比较数据。图中有一系列类似分面的柱状图,代表了 Q1 ~ Q3 三个季度的账户 targeted 筛选 -> engaged 订阅 -> pitched 投放 -> adopted 采用的四个状态的百分比,这四个状态是呈漏斗式的数据,分别是上一个数据的子集。这个案列中,用户希望能够在一张图中比较所有的数据。如下是基础原始图:
|
||||
<div>
|
||||
<img src="http://images2017.cnblogs.com/blog/395937/201712/395937-20171224171406912-710179221.png" width = "500" height = "500" alt="图片名称" align=center />
|
||||
</div>
|
||||
@@ -11,9 +11,9 @@
|
||||
|
||||
1. 左上角,我们可以对比 Q1 北美 的四条柱状图,因为这四个数据相邻,并且是在一个坐标系中。
|
||||
|
||||
2. 最上面一排,我们可以对比 Q1 北美和EMEA engaged订阅(两条紫色柱子)的数据,这个对比需要我们横向用手指去比较,需要参照物,存在一定的门槛。
|
||||
2. 最上面一排,我们可以对比 Q1 北美和 EMEA engaged 订阅(两条紫色柱子)的数据,这个对比需要我们横向用手指去比较,需要参照物,存在一定的门槛。
|
||||
|
||||
基于第2点,我们可以考虑转换下思维,转换下数据呈现的类型可以更快速的比较。文中作者从自己在银行的职业生涯中发现线性图形的角度可以更快速的比较。所以就有了下面一张图
|
||||
基于第 2 点,我们可以考虑转换下思维,转换下数据呈现的类型可以更快速的比较。文中作者从自己在银行的职业生涯中发现线性图形的角度可以更快速的比较。所以就有了下面一张图
|
||||
<div>
|
||||
<img src="http://images2017.cnblogs.com/blog/395937/201712/395937-20171224215602928-500155458.png" width = "500" height = "500" alt="图片名称" align=center />
|
||||
</div>
|
||||
@@ -34,9 +34,9 @@
|
||||
|
||||
1. 每个阶段,北美的数据都比其他地区低
|
||||
|
||||
2. 相比其他区域,为什么APAC 的传播度有了突然的提升?
|
||||
2. 相比其他区域,为什么 APAC 的传播度有了突然的提升?
|
||||
|
||||
3. 最需要关注Q3的数据,以及最终阶段的数据
|
||||
3. 最需要关注 Q3 的数据,以及最终阶段的数据
|
||||
|
||||
# 最后
|
||||
从这篇文章的可视化案例优化样板,可以总结出以下步骤:
|
||||
|
||||
+49
-49
@@ -1,18 +1,18 @@
|
||||
本期精读的文章是: [coinhive官方文档](https://coinhive.com)及[Monero官方文档](https://getmonero.org/)
|
||||
本期精读的文章是: [coinhive 官方文档](https://coinhive.com)及[Monero 官方文档](https://getmonero.org/)
|
||||
|
||||
懒得看文章? 没关系.
|
||||
懒得看文章?没关系。
|
||||
|
||||
咦, 怎么是官方文档?
|
||||
咦,怎么是官方文档?
|
||||
|
||||
本期精读有所不同, 注重实操, 先操作获取感性认识, 然后再介绍相关的概念, 由浅入深, 力求不纠缠细节, 但不遮盖环节. 阅读本文需要对加密货币有一些基本常识 (如果你是一个开发者但是完全没了解过加密货币, 可以参考[这里](http://piotrpasich.com/introduction-bitcoin-for-developers/)). 希望同学们看完本文能对加密货币领域有一个更深更切实的体感. 如果要了解更多细节, 文末总结的延伸阅读链接列表是最好的开始.
|
||||
本期精读有所不同,注重实操,先操作获取感性认识,然后再介绍相关的概念,由浅入深,力求不纠缠细节,但不遮盖环节。阅读本文需要对加密货币有一些基本常识 (如果你是一个开发者但是完全没了解过加密货币,可以参考[这里](http://piotrpasich.com/introduction-bitcoin-for-developers/))。希望同学们看完本文能对加密货币领域有一个更深更切实的感受。如果要了解更多细节,文末总结的延伸阅读链接列表是最好的开始。
|
||||
|
||||
要注意一点, 文中很多说明是默认基于XMR和BTC的, 他们两个又同源, 机制非常相似. **所以很多命题判断并不适用于所有的成千上万的加密货币.** 正相反, 新的币种层出不穷, 几乎所有的惯例都被打破, 所有可能性都被尝试. 这一点以下不再做说明.
|
||||
要注意一点,文中很多说明是默认基于 XMR 和 BTC 的,他们两个又同源,机制非常相似。**所以很多命题判断并不适用于所有的成千上万的加密货币.** 正相反,新的币种层出不穷,几乎所有的惯例都被打破,所有可能性都被尝试。这一点以下不再做说明。
|
||||
|
||||
# 1 引言
|
||||
首先, tl;dr干货. 10行代码, 5分钟, 不需部署不需构建直接浏览器开挖.
|
||||
首先,干货。10 行代码,5 分钟,不需部署不需构建直接浏览器开挖。
|
||||
|
||||
- 本地创建文件test.html, 粘贴如下代码:
|
||||
```
|
||||
- 本地创建文件 test.html,粘贴如下代码:
|
||||
```plain
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
@@ -29,67 +29,67 @@
|
||||
</html>
|
||||
```
|
||||
|
||||
- 本地双击打开. 等待JS加载, 点击widget "Start Mining". 开始挖矿! 如下图
|
||||
- 本地双击打开。等待 JS 加载,点击 widget "Start Mining"。开始挖矿! 如下图
|
||||
|
||||

|
||||
|
||||
不错, 数字已经在跳动, 风扇开始工作, 永无尽头的挖矿已经开始了. 那么重要的是, 挖出来的加密货币在哪呢? 原来上面的代码里用的还是我的API key, 所以还没挖到你自己那里 :P 继续下面的步骤
|
||||
不错,数字已经在跳动,风扇开始工作,永无尽头的挖矿已经开始了。那么重要的是,挖出来的加密货币在哪呢?原来上面的代码里用的还是我的 API key,所以还没挖到你自己那里,所以请继续下面的步骤:
|
||||
|
||||
- 在coinhive注册账号并登陆. 它是做什么的? 别急, 后面会详细讲. 在coinhive/settings找到自己的API keypair, 把public key复制出来, 形如MUtCJzIDhrs01ERrf3qlqdawo35N0CYD
|
||||
- 在 coinhive 注册账号并登陆。它是做什么的?别急,后面会详细讲。在 `coinhive/settings` 找到自己的 `API keypair`,把 `public key` 复制出来,形如 `MUtCJzIDhrs01ERrf3qlqdawo35N0CYD`
|
||||
|
||||
- 替换上面代码中的data-key部分, 重新开始挖矿. 好了, 现在挖出的Monero (这是啥? 详见下一节) 已经会到你自己的coinhive账户中. 用下图来说明, 你名下总计算过的Hash个数为264K, 当前难度换算为0.00002个Monero(Symbol:XMR). 当前难度为66G一个block, 一个block reward 5.87 XMR, 得到一个XMR是11.268G. 264K/11.268G = 0.0000234. 这就是你目前的收益.
|
||||
- 替换上面代码中的 data-key 部分,重新开始挖矿。好了,现在挖出的 Monero (这是啥?详见下一节) 已经会到你自己的 coinhive 账户中。用下图来说明,你名下总计算过的 Hash 个数为 264K,当前难度换算为 0.00002 个 Monero(Symbol:XMR)。当前难度为 66G 一个 block,一个 block reward 5.87 XMR,得到一个 XMR 是 11.268G。264K/11.268G = 0.0000234。这就是你目前的收益。
|
||||
|
||||

|
||||
|
||||
- 查bitfinex可得现在XMR价格在375美元 (当你看到本文的时候, 价格可能早就又波动到不知哪里去了), 所以你 (以及你忠实勤劳的电脑) 获得的实际收益为0.000234 * 375 USD = 0.008775 USD, 快到一分钱了 :)
|
||||
- 查 bitfinex 可得现在 XMR 价格在 375 美元 (当你看到本文的时候,价格可能早就又波动到不知哪里去了),所以你 (以及你忠实勤劳的电脑) 获得的实际收益为 0.000234 * 375 USD = 0.008775 USD,快到一分钱了 :)
|
||||
|
||||
怎么样, 有没有一种浏览器点开即玩一刀999级的感觉. 以上操作的便捷直接, 是建立在无数前人大量的开发和基础设施建设之上的. 越是领域早期的工作, 越步履维艰, 收货也越丰厚. 如今加密货币已经走到了一个成年期, 逐渐稳定成熟起来.
|
||||
怎么样,有没有一种浏览器点开即玩一刀 999 级的感觉。以上操作的便捷直接,是建立在无数前人大量的开发和基础设施建设之上的。越是领域早期的工作,越步履维艰,收货也越丰厚。如今加密货币已经走到了一个成年期,逐渐稳定成熟起来。
|
||||
|
||||
接下来我们聚焦到上面过程的每个环节, 了解下拼图的每一块是怎样被构成全图的.
|
||||
接下来我们聚焦到上面过程的每个环节,了解下拼图的每一块是怎样被构成全图的。
|
||||
|
||||
# 2 聚焦
|
||||
让我们从最终端最接近用户的环节开始, 逐一聚焦, 最后走完整条链路.
|
||||
让我们从最终端最接近用户的环节开始,逐一聚焦,最后走完整条链路。
|
||||
|
||||
## 2.1 从浏览器说起
|
||||
本文标题叫浏览器挖矿, 也是和贴合前端的部分. 那么为什么可以在浏览器里挖矿? 为什么可以很多用户在多个终端浏览器同时为同一个人 (你) 挖矿?
|
||||
本文标题叫浏览器挖矿,也是和贴合前端的部分。那么为什么可以在浏览器里挖矿?为什么可以很多用户在多个终端浏览器同时为同一个人 (你) 挖矿?
|
||||
|
||||
我们知道, 挖矿是对加密货币产生机制的俗称. 主流大多采取Proof of Work (PoW) 机制. 最常见的PoW方式就是由网络中所有节点作为矿工, 每个节点都基于blockchain前面block已有信息计算一个新信息. 这个新信息的计算方式往往是某种hash function, 并且人为地被设置为需要巨大计算力和时间才能完成 (其具体难度一般也会实时调整). 当一个节点幸运地 (也依靠强大的算力) 第一个计算出结果后, 会把这个结果广播到网络中. 其他所有节点会验证这个结果 (我们知道非对称加密算法, 验证便宜而计算昂贵), 一旦证实就会停下手里的计算, 承认这个计算结果. 新的计算结果创造出新的块, 区块链的高度增加一层, 然后计算继续下去. 每一个块的生成一般在2-10分钟. 这个过程就被叫做挖矿.
|
||||
我们知道,挖矿是对加密货币产生机制的俗称。主流大多采取 Proof of Work (PoW) 机制。最常见的 PoW 方式就是由网络中所有节点作为矿工,每个节点都基于 blockchain 前面 block 已有信息计算一个新信息。这个新信息的计算方式往往是某种 hash function,并且人为地被设置为需要巨大计算力和时间才能完成 (其具体难度一般也会实时调整)。当一个节点幸运地 (也依靠强大的算力) 第一个计算出结果后,会把这个结果广播到网络中。其他所有节点会验证这个结果 (我们知道非对称加密算法,验证便宜而计算昂贵),一旦证实就会停下手里的计算,承认这个计算结果。新的计算结果创造出新的块,区块链的高度增加一层,然后计算继续下去。每一个块的生成一般在 2-10 分钟。这个过程就被叫做挖矿.
|
||||
|
||||
既然是通用计算, 既然是算一个hash值, 那么民用级CPU和GPU, 浏览器或任何沙盒, 虚拟机, 移动设备当然就都可以. 在我们的例子中, 计算过程被做成分布式, 每个用户可以各自计算, 结果按chunk发回master汇总. 这样就实现了终端用户 - 浏览器 - 共同贡献计算资源 - 换为XMR的过程.
|
||||
既然是通用计算,既然是算一个 hash 值,那么民用级 CPU 和 GPU,浏览器或任何沙盒,虚拟机,移动设备当然就都可以。在我们的例子中,计算过程被做成分布式,每个用户可以各自计算,结果按 chunk 发回 master 汇总。这样就实现了终端用户 - 浏览器 - 共同贡献计算资源 - 换为 XMR 的过程。
|
||||
|
||||
这就是对整个链路的一个描述. 从中我们会生出一些疑问, 比如:
|
||||
这就是对整个链路的一个描述。从中我们会生出一些疑问,比如:
|
||||
|
||||
> 给我看看具体算什么hash? 为什么要算XMR而不是比特币或者其他? 既然第一个算出的赢家通吃所有, 为什么我的收益却是线性的? 这种描述来看岂不是算力最大的一方**永远**都能算出结果而其他人颗粒无收吗?
|
||||
> 给我看看具体算什么 hash?为什么要算 XMR 而不是比特币或者其他?既然第一个算出的赢家通吃所有,为什么我的收益却是线性的?这种描述来看岂不是算力最大的一方**永远**都能算出结果而其他人颗粒无收吗?
|
||||
|
||||
要看具体算法, 没有问题. bitcoin在[这里](http://www.righto.com/2014/02/bitcoin-mining-hard-way-algorithms.html), XMR则看[CryptoNote Standard 008](https://cryptonote.org/cns/cns008.txt)
|
||||
要看具体算法,没有问题。bitcoin 在[这里](http://www.righto.com/2014/02/bitcoin-mining-hard-way-algorithms.html),XMR 则看[CryptoNote Standard 008](https://cryptonote.org/cns/cns008.txt)
|
||||
|
||||
读完两个算法我们就有了以上疑问的答案:
|
||||
|
||||
### 2.1.1 为什么要算XMR而不是比特币或者其他
|
||||
XMR不是唯一选择, 但是BTC是一个不可选的选择. 因为double SHA-265在专业级GPU上会比CPU上快10^4倍 ([更多信息](https://en.bitcoin.it/wiki/Why_a_GPU_mines_faster_than_a_CPU)). 这样一百万用户合力浏览器挖矿还不如一架子双路Titan, 就失去了分布到终端用户的意义. 而CryptoNight在GPU上只比同价值CPU快2倍. 另外CryptoNight算法也被设计为不适用[ASIC](https://en.wikipedia.org/wiki/Application-specific_integrated_circuit).
|
||||
### 2.1.1 为什么要算 XMR 而不是比特币或者其他
|
||||
XMR 不是唯一选择,但是 BTC 是一个不可选的选择。因为 double SHA-265 在专业级 GPU 上会比 CPU 上快 10^4 倍 ([更多信息](https://en.bitcoin.it/wiki/Why_a_GPU_mines_faster_than_a_CPU))。这样一百万用户合力浏览器挖矿还不如一架子双路 Titan,就失去了分布到终端用户的意义。而 CryptoNight 在 GPU 上只比同价值 CPU 快 2 倍。另外 CryptoNight 算法也被设计为不适用[ASIC](https://en.wikipedia.org/wiki/Application-specific_integrated_circuit)。
|
||||
|
||||
怎么实现的? CryptoNight算法开宗明义地写明, 运算主体是Memory-Hard Loop, 而不是Computation-Hard Loop. 每个循环都要在内存中检索. 实际运行CryptoNight时, CPU都会用最快, 最接近ALU也是容量最小的L3 Cache. 换到GPU, 显存虽然很大, 却没有L3 Cache一样的极致读写速度优化, 而且由于内存读写成了瓶颈, GPU中的大量ALU也没了用武之地. 下图简略地描述了CryptoNight循环体的结构:
|
||||
怎么实现的?CryptoNight 算法开宗明义地写明,运算主体是 Memory-Hard Loop,而不是 Computation-Hard Loop。每个循环都要在内存中检索。实际运行 CryptoNight 时,CPU 都会用最快,最接近 ALU 也是容量最小的 L3 Cache。换到 GPU,显存虽然很大,却没有 L3 Cache 一样的极致读写速度优化,而且由于内存读写成了瓶颈,GPU 中的大量 ALU 也没了用武之地。下图简略地描述了 CryptoNight 循环体的结构:
|
||||
|
||||

|
||||
|
||||
### 2.1.2 算力最大的一方永远都能算出结果
|
||||
看了比特币具体算法, 你应该明白了hash是靠不停改变nonce来生成的. 随机取一个值, 算了不对, 再随机取另一个nonce值... 既然是随机取, 就不会存在赢家恒赢.
|
||||
看了比特币具体算法,你应该明白了 hash 是靠不停改变 nonce 来生成的。随机取一个值,算了不对,再随机取另一个 nonce 值..。既然是随机取,就不会存在赢家恒赢.
|
||||
|
||||
### 2.1.3 为什么我的收益却是线性的
|
||||
这是一个隐蔽但是却非常重要的问题. 答案是, 本来确实是赢家通吃. 如果你的算力足够大, 挖矿时间足够长之后总会轮到你, 但是收益会有大幅波动.
|
||||
这是一个隐蔽但是却非常重要的问题。答案是,本来确实是赢家通吃。如果你的算力足够大,挖矿时间足够长之后总会轮到你,但是收益会有大幅波动.
|
||||
|
||||
就是因为如此, 矿工们逐渐建立了矿池组织. 大家把算力都投入到一起, 合力算, 然后不管这次实际是谁算出来, 都按照贡献的算力比例分配收益. 矿池是一个加密货币建立之初, 完全推崇去中心化时没有预料到的结构, 也产生了深远的影响. 现在来自中国矿池的算力早已超过网络50%, 他们会在挖出的块中打上矿池标记, 而这些矿池在加密货币的分叉, 路线图中也扮演举足轻重的角色.
|
||||
就是因为如此,矿工们逐渐建立了矿池组织。大家把算力都投入到一起,合力算,然后不管这次实际是谁算出来,都按照贡献的算力比例分配收益。矿池是一个加密货币建立之初,完全推崇去中心化时没有预料到的结构,也产生了深远的影响。现在来自中国矿池的算力早已超过网络 50%,他们会在挖出的块中打上矿池标记,而这些矿池在加密货币的分叉,路线图中也扮演举足轻重的角色.
|
||||
|
||||
所以你的算力并不是直接投入XMR网络中, 而是投入一个矿池. 在我们的例子里矿池就是coinhive, 只不过是一个比较特殊的矿池, 特殊在矿池成员都运作在浏览器中. 这就是为什么你会得到线性收益而不是all or none.
|
||||
所以你的算力并不是直接投入 XMR 网络中,而是投入一个矿池。在我们的例子里矿池就是 coinhive,只不过是一个比较特殊的矿池,特殊在矿池成员都运作在浏览器中。这就是为什么你会得到线性收益而不是 all or none.
|
||||
|
||||
自古以来各行业都会自发产生行业工会, 建立类似保险和行业守则 / 规范这些人人为我我为人人的机制. 在crypto行业也不例外. 这是意料之外而情理之中.
|
||||
自古以来各行业都会自发产生行业工会,建立类似保险和行业守则 / 规范这些人人为我我为人人的机制。在 crypto 行业也不例外。这是意料之外而情理之中.
|
||||
|
||||
## 2.2 在浏览器里发生了什么, 或, coinhive干了什么
|
||||
好, 我们搞清了一些基本的Monero挖矿机制, 下面来看看coinhive. 已经知道coinhive帮我们接入它的矿池, 让再小的算力也能按比例得到产出. 但是还有什么呢? 最关键的一点, coinhive是怎样把一个完整的mining过程拆分成小块, 让一个或许并不强大的设备上的浏览器, 也能快速接收task, 快速完成并且即时上传的呢?
|
||||
## 2.2 在浏览器里发生了什么,或 coinhive 干了什么
|
||||
好,我们搞清了一些基本的 Monero 挖矿机制,下面来看看 coinhive。已经知道 coinhive 帮我们接入它的矿池,让再小的算力也能按比例得到产出。但是还有什么呢?最关键的一点,coinhive 是怎样把一个完整的 mining 过程拆分成小块,让一个或许并不强大的设备上的浏览器,也能快速接收 task,快速完成并且即时上传的呢?
|
||||
|
||||
废话不多说, 打开源码. 本项目没有开源, 构建完成后的在https://coinhive.com/lib/coinhive.min.js. 先做初步format处理, 发现有些工作完成在后端, worker shard一侧. 以下用松散的伪码总结一下client side的[main success scenario](https://wiki.ihris.org/wiki/Understanding_Use_Cases)流程 (注意很多地方简化了):
|
||||
废话不多说,打开源码。本项目没有开源,构建完成后的在https://coinhive.com/lib/coinhive.min.js。先做初步 format 处理,发现有些工作完成在后端,worker shard 一侧。以下用松散的伪码总结一下 client side 的[main success scenario](https://wiki.ihris.org/wiki/Understanding_Use_Cases)流程 (注意很多地方简化了):
|
||||
|
||||
```
|
||||
```plain
|
||||
- start
|
||||
- loadWorkerResource
|
||||
- load worker-asmjs.min.js
|
||||
@@ -107,36 +107,36 @@ XMR不是唯一选择, 但是BTC是一个不可选的选择. 因为double SHA-26
|
||||
} else {
|
||||
this.worker.postMessage(job)
|
||||
}
|
||||
// 实例化若干个JobThread, 每个对应一个worker, worker实际执行asmjs.min.js
|
||||
- _connect // verify成功, 终于建立连接. 根据public key固定hash到一个shard池然后随机选一个shard, 建立websocket
|
||||
// 实例化若干个JobThread,每个对应一个worker,worker实际执行asmjs.min.js
|
||||
- _connect // verify成功,终于建立连接。根据public key固定hash到一个shard池然后随机选一个shard,建立websocket
|
||||
- websocket.onmessage: if (type==job) work()
|
||||
- work:
|
||||
do { hash(input, output) } while !(meetTarget(output));
|
||||
websocket.postMessage({nonce, output}) // hash done successfully. submit
|
||||
do { hash(input,output) } while !(meetTarget(output));
|
||||
websocket.postMessage({nonce,output}) // hash done successfully。submit
|
||||
```
|
||||
|
||||
看一下这个过程, 结合[cns003 XMR blockchain specs](https://cryptonote.org/cns/cns003.txt). XMR的整体hash input很小, 是:
|
||||
```
|
||||
- size of [block_header, Merkle root hash, and the number of
|
||||
看一下这个过程,结合[cns003 XMR blockchain specs](https://cryptonote.org/cns/cns003.txt)。XMR 的整体 hash input 很小,是:
|
||||
```plain
|
||||
- size of [block_header,Merkle root hash,and the number of
|
||||
transactions] in bytes (varint)
|
||||
- block_header,
|
||||
- Merkle root hash,
|
||||
- number of transactions (varint).
|
||||
```
|
||||
|
||||
这样用websocket发过来毫无问题. 之后就是完全独立的计算, 调整nonce来算不同的hash结果. target就是当前难度的一个指示.
|
||||
这样用 websocket 发过来毫无问题。之后就是完全独立的计算,调整 nonce 来算不同的 hash 结果。target 就是当前难度的一个指示.
|
||||
|
||||
这样整条链路就比较清晰了. 再思考一下以下问题:
|
||||
这样整条链路就比较清晰了。再思考一下以下问题:
|
||||
|
||||
### 2.2.1 为什么XMR适宜分布式客户端计算
|
||||
因为能够利用每个用户的CPU和其中的**高速L3 Cache**. 这是中心化执行难以具备的条件.
|
||||
### 2.2.1 为什么 XMR 适宜分布式客户端计算
|
||||
因为能够利用每个用户的 CPU 和其中的**高速 L3 Cache**。这是中心化执行难以具备的条件.
|
||||
|
||||
任何时候当考虑要不要把某项操作推到客户端进行时, 都要想明白可以利用客户端的哪个资源, 这个资源在客户端是否有明显优势, 是否比后端中心化执行更有利. **很多时候答案是, 优势并不明显. 那么引入的网络通信成本, 法规成本, 额外开销可能就并不值得.**
|
||||
任何时候当考虑要不要把某项操作推到客户端进行时,都要想明白可以利用客户端的哪个资源,这个资源在客户端是否有明显优势,是否比后端中心化执行更有利。**很多时候答案是,优势并不明显。那么引入的网络通信成本,法规成本,额外开销可能就并不值得.**
|
||||
|
||||
## 2.3 最后的步骤
|
||||
到了这里, 最后剩余步骤就很标准模式化了. coinhive作为矿场, 代管着用户生产的加密货币. 用户发请求提出XMR, 就需要提到一个自己的钱包地址保管, 比如[MyMonero](https://mymonero.com). 也可以直接提到Exchange交易所, 在其中交易成其他币种, 包括法币, 然后电汇等等形式提现.
|
||||
到了这里,最后剩余步骤就很标准模式化了。coinhive 作为矿场,代管着用户生产的加密货币。用户发请求提出 XMR,就需要提到一个自己的钱包地址保管,比如 [MyMonero](https://mymonero.com)。也可以直接提到 Exchange 交易所,在其中交易成其他币种,包括法币,然后电汇等等形式提现.
|
||||
|
||||
如果对钱包或者交易所感兴趣 (这两个也是很大的话题, 比如钱包分硬件钱包和软件钱包, 离线冷钱包和线上热钱包, private key和recover seeds. 交易所有多种多样的交易对, 有杠杆, 期货, 空和多, 多种挂单类型等等), 可以看[这里](https://www.blockchain-council.org/cryptocurrency/hot-wallet-vs-cold-wallet/)和[这里](https://github.com/txbits/txbits).
|
||||
如果对钱包或者交易所感兴趣 (这两个也是很大的话题,比如钱包分硬件钱包和软件钱包,离线冷钱包和线上热钱包,private key 和 recover seeds。交易所有多种多样的交易对,有杠杆,期货,空和多,多种挂单类型等等),可以看[这里](https://www.blockchain-council.org/cryptocurrency/hot-wallet-vs-cold-wallet/)和[这里](https://github.com/txbits/txbits).
|
||||
|
||||
# 3 更多讨论
|
||||
|
||||
@@ -152,7 +152,7 @@ https://en.wikipedia.org/wiki/Monero_(cryptocurrency)
|
||||
http://www.righto.com/2014/02/bitcoin-mining-hard-way-algorithms.html
|
||||
|
||||
https://cryptonote.org/cns/cns001.txt
|
||||
cns001-008是Specs集合
|
||||
cns001-008 是 Specs 集合
|
||||
|
||||
https://en.bitcoin.it/wiki/Why_a_GPU_mines_faster_than_a_CPU
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
## 引言
|
||||
2018 年初,蚂蚁金服 See Conf 上第一个分享《Ant Design 3.0 背后的故事》给很多人带来了启发。主题精彩又深刻,值得反复咀嚼。
|
||||
|
||||
## 内容概要
|
||||
## 内容概要
|
||||
### 设计体系
|
||||
Atomic Design 书中提到模块化思路以及原子级的模块抽象的方法启发了很多设计师。而解读者中较为说法较作者认同。设计体系是一个具包容性且充满生命力的东西。包容性指的是从组件库到设计语言到设计方法等所有和产品设计相关的方面。而生命力指的是它并非静态的内容,而是可以应对不断变化的环境,是一个不断进化的过程。
|
||||
|
||||
|
||||
+6
-6
@@ -1,14 +1,14 @@
|
||||
# 增强现实与可视化
|
||||
## 引言
|
||||
增强现实,Augmented Reality,简称AR。在VR的热潮已经褪去,AI当下正红的技术圈里,AR似乎已经成为了过气网红。但似乎在现在,我们可以来冷静地看待一下增强现实这个概念。
|
||||
增强现实,Augmented Reality,简称 AR。在 VR 的热潮已经褪去,AI 当下正红的技术圈里,AR 似乎已经成为了过气网红。但似乎在现在,我们可以来冷静地看待一下增强现实这个概念。
|
||||
|
||||
做前端的最终还是要和用户打交道的,增强现实是不是为用户界面带来了新的可能?可视化作为前端的一个细分领域,这篇文章正是从这个角度出发来重新认识增强现实与可视化。
|
||||
|
||||
## 内容概要
|
||||
|
||||
移动设备为我们带来了巨大的便利,但是在各种好处下,有一点不容忽视:屏幕太小,根本没有更多的空间来放置更多的内容。对于数据可视化而言,更大的空间意味着更好的数据分析能力。而在空间这件事情上,AR正是很好地解决了这个问题。当下,AR的主要形态还是将虚拟信息作为图层叠加在现实图像之上的形态,这样的AR其实更接近于混合现实。例如使用面部追踪给你脸上加上小动物的Snapchat Lenses,可以在沙发茶几上玩MineCraft的微软HoloLens,和苹果最近新出的ARKit都是这种。
|
||||
移动设备为我们带来了巨大的便利,但是在各种好处下,有一点不容忽视:屏幕太小,根本没有更多的空间来放置更多的内容。对于数据可视化而言,更大的空间意味着更好的数据分析能力。而在空间这件事情上,AR 正是很好地解决了这个问题。当下,AR 的主要形态还是将虚拟信息作为图层叠加在现实图像之上的形态,这样的 AR 其实更接近于混合现实。例如使用面部追踪给你脸上加上小动物的 Snapchat Lenses,可以在沙发茶几上玩 MineCraft 的微软 HoloLens,和苹果最近新出的 ARKit 都是这种。
|
||||
|
||||
所以要怎么通过增强现实来解决当下移动端屏幕空间不足的问题呢?在AR的概念下,一切皆屏幕,现实之上的任何地方都可以展示信息。对于增强现实的未来,文章给出了3个可能的方向。首先,需要提供更好的针对个人的视觉体验。其次,要更好地利用3D展示。最后,让屏幕悬浮在任何地方。
|
||||
所以要怎么通过增强现实来解决当下移动端屏幕空间不足的问题呢?在 AR 的概念下,一切皆屏幕,现实之上的任何地方都可以展示信息。对于增强现实的未来,文章给出了 3 个可能的方向。首先,需要提供更好的针对个人的视觉体验。其次,要更好地利用 3D 展示。最后,让屏幕悬浮在任何地方。
|
||||
|
||||
但是我们也不妨从反面重新理性地看到了增强现实。是不是有了足够空间展示东西我们就要把东西一股脑都展示出来呢?并不是的,增强现实是和真实世界的混合。所以增强现实下的可视化,也不是也不是简单地照搬网页。在增强现实下,我们所展示的每一个虚拟物体,是需要有现实依据的。但是也不必拘泥于现实,增强现实也可以改造所展示的世界。增强现实并不是简单的做加法,只能构建出各类虚拟物品叠加在世界上。同样也能改造世界,使事物变形和消失。
|
||||
|
||||
@@ -18,11 +18,11 @@
|
||||
|
||||
前端是什么?对于这样一个本源性的话题。每个人都有自己不同的理解。我也不会说书式得非得确定这个问题的答案。但是可以确定的是,如果一切的描述要带上设备端的前提,是短视的。
|
||||
|
||||
从PC Web到移动App,前端并没有消失,各大网站也都纷纷开始将自己的网站搬到了手机上以移动APP的形式存在。同样的,如果有一天增强现实来到我们的生活。前端作为和用户界面直接接触的工作,毫无疑问已经会是很重要的一部分。
|
||||
从 PC Web 到移动 App,前端并没有消失,各大网站也都纷纷开始将自己的网站搬到了手机上以移动 APP 的形式存在。同样的,如果有一天增强现实来到我们的生活。前端作为和用户界面直接接触的工作,毫无疑问已经会是很重要的一部分。
|
||||
|
||||
再看可视化。移动化的大潮让我们开始习惯移动设备,触屏之类的交互的确让我们的信息获取变得更加方便。但是这并不代表全部,特殊行业的人依旧离不开传统的PC,尤其是数据分析人员,对于他们而言,更大的屏幕等于一切。尤其是金融分析师,你给他们四块屏幕都不够用。增强现实下的可视化的确是对这些人的一个重要利好。原因无非就是增强现实的可展现区域相当于整个人眼的视角,远远大于任何我们可以感知到的屏幕。
|
||||
再看可视化。移动化的大潮让我们开始习惯移动设备,触屏之类的交互的确让我们的信息获取变得更加方便。但是这并不代表全部,特殊行业的人依旧离不开传统的 PC,尤其是数据分析人员,对于他们而言,更大的屏幕等于一切。尤其是金融分析师,你给他们四块屏幕都不够用。增强现实下的可视化的确是对这些人的一个重要利好。原因无非就是增强现实的可展现区域相当于整个人眼的视角,远远大于任何我们可以感知到的屏幕。
|
||||
|
||||
如果只是在我们的眼睛上盖上一层信息,那是远远不够的,这样的增强现实仅仅是对屏幕的扩展。增强现实最关键的一点是和现实世界的充分结合:例如配合地理信息和我们的地理定位所带来的信息可视化展示;通过图像识别对物体的感知后,在物体上进行信息可视化展示;等等。文章的几个建议中,最后一个特别有趣——让屏幕悬浮在任何地方。这有两层含义,一是要充分利用好上面所提到的增强现实下的空间,二是我们需要清楚地认识到,人类对于二维世界的感知能力还是远远超出三维世界的。过度利用现实世界所构建出的3D场景,可能并不能为我们的分析带来太多进步。所以,二维的图形信息展示可能依然是增强现实可视化中很重要的一部分。
|
||||
如果只是在我们的眼睛上盖上一层信息,那是远远不够的,这样的增强现实仅仅是对屏幕的扩展。增强现实最关键的一点是和现实世界的充分结合:例如配合地理信息和我们的地理定位所带来的信息可视化展示;通过图像识别对物体的感知后,在物体上进行信息可视化展示;等等。文章的几个建议中,最后一个特别有趣——让屏幕悬浮在任何地方。这有两层含义,一是要充分利用好上面所提到的增强现实下的空间,二是我们需要清楚地认识到,人类对于二维世界的感知能力还是远远超出三维世界的。过度利用现实世界所构建出的 3D 场景,可能并不能为我们的分析带来太多进步。所以,二维的图形信息展示可能依然是增强现实可视化中很重要的一部分。
|
||||
|
||||
这篇文章更加引起我兴趣的地方在于它对于可视化基本元素进行了分析——颜色,亮度,纹理,尺寸,方向、形状、位置……这些从我们平时的显示器屏幕或者手机屏幕上所展示的可视化元素会带来完全不一样的表现。增强现实的增强在于我们可以对现实的展示进行改造,纹理是很有趣的一块。一般来说我们平时做可视化分析,常用到阴影,虚线这些纹理,但是现实世界里的纹理就太丰富了,我们可以充分利用现实花纹了,让展现更直观更融合。
|
||||
|
||||
|
||||
@@ -48,7 +48,7 @@ Rekit Studio 以逻辑视图重新组织了项目,文件目录不见了,取
|
||||
|
||||
同时支持在线管理本地文件、集成了 [https://microsoft.github.io/monaco-editor/](monaco-editor) 在线编辑文件,以及在线构建、测试等功能。
|
||||
|
||||
同时利用和弦图分析了路由与数据流之间绑定关系,路由与文件绑定关系,可以很轻松找到被遗弃的孤立节点。项目维护时,以看图代替看代码,效率至少提升2 3倍。
|
||||
同时利用和弦图分析了路由与数据流之间绑定关系,路由与文件绑定关系,可以很轻松找到被遗弃的孤立节点。项目维护时,以看图代替看代码,效率至少提升 2 3 倍。
|
||||
|
||||
### Cli 与可视化等效
|
||||
|
||||
|
||||
+13
-13
@@ -1,33 +1,33 @@
|
||||
# 精读《快速上手构建ARKit应用》
|
||||
# 精读《快速上手构建 ARKit 应用》
|
||||
|
||||
原文地址: [how-to-make-your-own-arkit-app-in-5-minutes-using-react-native](https://medium.com/@HippoAR/how-to-make-your-own-arkit-app-in-5-minutes-using-react-native-9d7ce109a4c2)
|
||||
|
||||
## 引言
|
||||
ARKit是苹果推出的增强现实套装,而react-native-arkit是基于此的上层封装。对于前端开发而言,这可能是最快上手ARKit的方式了,本周精读让我们来初窥ARKit和React Native ARKit这个库。
|
||||
ARKit 是苹果推出的增强现实套装,而 react-native-arkit 是基于此的上层封装。对于前端开发而言,这可能是最快上手 ARKit 的方式了,本周精读让我们来初窥 ARKit 和 React Native ARKit 这个库。
|
||||
|
||||
## 概要
|
||||
本次精读我们带来的是一篇《快速上手构建ARKit应用》,原文链接如上。原文标题更加直接,直译的话是“如何在5分钟里利用react native搭建出你自己的ARKit应用”。确实,这篇文章整体也非常明确,以跑起整个ARKit Demo为最直接最主要的目的。
|
||||
本次精读我们带来的是一篇《快速上手构建 ARKit 应用》,原文链接如上。原文标题更加直接,直译的话是“如何在 5 分钟里利用 react native 搭建出你自己的 ARKit 应用”。确实,这篇文章整体也非常明确,以跑起整个 ARKit Demo 为最直接最主要的目的。
|
||||
|
||||
跑起ARKit,也很简单。硬件上,只要有一台iPhone 6S以上的手机;软件上,只要准备好最新版本的XCode和日常开发要用的Node环境了就好。按照`react-native-arkit`的里面的README就可以跑起来了。这个库不
|
||||
跑起 ARKit,也很简单。硬件上,只要有一台 iPhone 6S 以上的手机;软件上,只要准备好最新版本的 XCode 和日常开发要用的 Node 环境了就好。按照`react-native-arkit`的里面的 README 就可以跑起来了。这个库不
|
||||
|
||||
## 3 精读
|
||||
在开始精读前,我先抛出我的问题三连:Why AR? Why ARKit? Why React Native ARKit?
|
||||
|
||||
### 3.1 Why AR?
|
||||
在之前的第43期精读评论中,我们探讨了AR对于和前端结合的可能性。总的来说,AR把前端开发不再局限在有限的屏幕空间上,对于可视化等对前端展示空间有强烈需求的细分领域,AR是一个很值得研究的内容。如果对于这一块内容有兴趣,欢迎回看第43期精读评论 [《精读〈增强现实与可视化〉》](https://github.com/dt-fe/weekly/blob/master/43.%E7%B2%BE%E8%AF%BB%E3%80%8A%E5%A2%9E%E5%BC%BA%E7%8E%B0%E5%AE%9E%E4%B8%8E%E5%8F%AF%E8%A7%86%E5%8C%96%E3%80%8B.md)。
|
||||
在之前的第 43 期精读评论中,我们探讨了 AR 对于和前端结合的可能性。总的来说,AR 把前端开发不再局限在有限的屏幕空间上,对于可视化等对前端展示空间有强烈需求的细分领域,AR 是一个很值得研究的内容。如果对于这一块内容有兴趣,欢迎回看第 43 期精读评论 [《精读〈增强现实与可视化〉》](https://github.com/dt-fe/weekly/blob/master/43.%E7%B2%BE%E8%AF%BB%E3%80%8A%E5%A2%9E%E5%BC%BA%E7%8E%B0%E5%AE%9E%E4%B8%8E%E5%8F%AF%E8%A7%86%E5%8C%96%E3%80%8B.md)。
|
||||
|
||||
### 3.2 Why ARKit?
|
||||
为什么选择 ARKit 入手进行实验?其因有二。第一,相比于 Microsoft HoloLens 的价格,售价只有它三分之一的iPhone X无论是体积重量,还是性价比,抑或是保有量都是大大占优的。噢对,说到保有量,iPhone 6S及以上都支持ARKit。所以说iPhone是我们身边最容易接触到的AR设备是不为过的。第二,ARKit对于硬件的利用能力非一般的前端库可以做到的。大部分的AR前端库可以做到利用陀螺仪来构建一个三维立体空间。但是ARKit更进一步,他利用高频调用摄像头,通过对图像进行识别分析,可以进行空间感知,例如可以识别出一个平面。而这些都是ARKit所提供的,我们只需要调用它的能力就好了。对于开发者而言,ARKit会比一般的AR库更近一步。
|
||||
为什么选择 ARKit 入手进行实验?其因有二。第一,相比于 Microsoft HoloLens 的价格,售价只有它三分之一的 iPhone X 无论是体积重量,还是性价比,抑或是保有量都是大大占优的。噢对,说到保有量,iPhone 6S 及以上都支持 ARKit。所以说 iPhone 是我们身边最容易接触到的 AR 设备是不为过的。第二,ARKit 对于硬件的利用能力非一般的前端库可以做到的。大部分的 AR 前端库可以做到利用陀螺仪来构建一个三维立体空间。但是 ARKit 更进一步,他利用高频调用摄像头,通过对图像进行识别分析,可以进行空间感知,例如可以识别出一个平面。而这些都是 ARKit 所提供的,我们只需要调用它的能力就好了。对于开发者而言,ARKit 会比一般的 AR 库更近一步。
|
||||
|
||||
### 3.3 Why React Native ARKit?
|
||||
对于当下的前端开发,所有事情可以分为两种——0. 可以用 JavaScript 写的 1. 其他。至于为什么选择`react-native-arkit`这个库,原因自然也可以理解。相比于用原生的Swift来开发,React Native 的开发方式对于前端而言明显是更加容易上手了。对于尝试新东西,这也未尝不可。
|
||||
对于当下的前端开发,所有事情可以分为两种——0. 可以用 JavaScript 写的 1. 其他。至于为什么选择`react-native-arkit`这个库,原因自然也可以理解。相比于用原生的 Swift 来开发,React Native 的开发方式对于前端而言明显是更加容易上手了。对于尝试新东西,这也未尝不可。
|
||||
|
||||
### 3.4 About Demo
|
||||
相比于原文中从初始化开始的步骤,官方还提供了一个已经配置好的[官方Demo](https://github.com/HippoAR/ReactNativeARKit)。使用这个,如果环境没有问题,的确只需要5分钟就可以跑起来一个ARKit应用了。
|
||||
相比于原文中从初始化开始的步骤,官方还提供了一个已经配置好的[官方 Demo](https://github.com/HippoAR/ReactNativeARKit)。使用这个,如果环境没有问题,的确只需要 5 分钟就可以跑起来一个 ARKit 应用了。
|
||||
|
||||

|
||||
|
||||
上面的图片来自原文,可以看到,在`react-native-arkit`这个库里面的所支持的9种基本图形和文字。使用如下已经封装好的React Native组件就可以直接使用了。
|
||||
上面的图片来自原文,可以看到,在`react-native-arkit`这个库里面的所支持的 9 种基本图形和文字。使用如下已经封装好的 React Native 组件就可以直接使用了。
|
||||
|
||||
```javasctipt
|
||||
<ARKit.Box
|
||||
@@ -37,16 +37,16 @@ ARKit是苹果推出的增强现实套装,而react-native-arkit是基于此的
|
||||
```
|
||||
|
||||
[几何构造](http://v.youku.com/v_show/id_XMzUxMjk3NjUxMg==.html)
|
||||
上面的一个视频片段是我们在跑起来Demo后的立体效果。可以很清楚地看到,ARKit感知到了房间这个立方体空间后所构建出来的AR的效果。
|
||||
上面的一个视频片段是我们在跑起来 Demo 后的立体效果。可以很清楚地看到,ARKit 感知到了房间这个立方体空间后所构建出来的 AR 的效果。
|
||||
|
||||
[平面识别](http://v.youku.com/v_show/id_XMzUxMjk3OTc0NA==.html)
|
||||
而最后的这段视频会更加有趣一些,中央的红圈的出现逻辑是停留在最近识别出的一个平面上。我们可以看到首先识别出了地面,红圈随地面而动;再移向桌面时,很快又识别出了桌面,重新生成了一个停留在桌面上的红圈。通过这一段可以看出无论是明暗划分明显的地面,还是堆满杂物的桌面,ARKit都可以很轻松的识别出来。
|
||||
而最后的这段视频会更加有趣一些,中央的红圈的出现逻辑是停留在最近识别出的一个平面上。我们可以看到首先识别出了地面,红圈随地面而动;再移向桌面时,很快又识别出了桌面,重新生成了一个停留在桌面上的红圈。通过这一段可以看出无论是明暗划分明显的地面,还是堆满杂物的桌面,ARKit 都可以很轻松的识别出来。
|
||||
|
||||
## 4. 总结
|
||||
苹果的ARKit对空间平面的感知能力胜过了一般的AR渲染库。而iPhone 6S就能跑的特性又让我们觉得AR其实并没有那么遥远。在此基础之上的React Native封装`react-native-arkit`,让我们通过JS就拥有操作ARKit的能力。这的确是一个快速上手ARKit的方式。
|
||||
苹果的 ARKit 对空间平面的感知能力胜过了一般的 AR 渲染库。而 iPhone 6S 就能跑的特性又让我们觉得 AR 其实并没有那么遥远。在此基础之上的 React Native 封装`react-native-arkit`,让我们通过 JS 就拥有操作 ARKit 的能力。这的确是一个快速上手 ARKit 的方式。
|
||||
|
||||
|
||||
## 5 更多讨论
|
||||
讨论地址是:[精读《快速上手构建ARKit应用》 · Issue #70 · dt-fe/weekly](https://github.com/dt-fe/weekly/issues/70)
|
||||
讨论地址是:[精读《快速上手构建 ARKit 应用》 · Issue #70 · dt-fe/weekly](https://github.com/dt-fe/weekly/issues/70)
|
||||
|
||||
如果你想参与讨论,请[点击这里](https://github.com/dt-fe/weekly),每周都有新的主题,每周末发布。
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
# 1 引言
|
||||
|
||||
本周精读, 来一起总结web开发的环节, 知识块和技能点. 是不是像xx速成班宣传的一样, 培训三个月, 经验顶三年, 入职BAT, 年薪三十万?
|
||||
本周精读, 来一起总结 web 开发的环节, 知识块和技能点. 是不是像 xx 速成班宣传的一样, 培训三个月, 经验顶三年, 入职 BAT, 年薪三十万?
|
||||
|
||||
本文虽然是罗列知识点, 但我想很有意义. 对于学习的人来说, 提供一个路线图. 对从业者来说, 对全局有更好的把控, 利于看到自己的强项和不足. 对组建团队, 更能起到一个点将谱的作用.
|
||||
|
||||
在网上我没有搜到任何深入全面的总结, 提供的那几篇已经算稍微好一些的了. 其他的要么太过笼统(前端-后端-数据-运维, 完毕)要么太细太窄(并不是不好, 只是和本文性质不一样). Generalist和Specialist之间永远是一对辩证矛盾, 持续思考.
|
||||
在网上我没有搜到任何深入全面的总结, 提供的那几篇已经算稍微好一些的了. 其他的要么太过笼统(前端-后端-数据-运维, 完毕)要么太细太窄(并不是不好, 只是和本文性质不一样). Generalist 和 Specialist 之间永远是一对辩证矛盾, 持续思考.
|
||||
|
||||
本文提供了有层级的列表形式, 如果有兴趣的读者可以把它做成概念图形式, 相互关联与距离相关, 可能会有意料之外的效果.
|
||||
|
||||
|
||||
+2
-2
@@ -94,9 +94,9 @@ CJS 的做法很不同,主要是由于相对于通过网络请求从文件系
|
||||
|
||||

|
||||
|
||||
在浏览器中你只要将 `type="module" ` 放在 script 标签上。这会通知浏览器这个文件应该被转化为一个模块。同样,只有模块才能够被导入,浏览器也就知道了模块中有哪些引用。
|
||||
在浏览器中你只要将 `type="module"` 放在 script 标签上。这会通知浏览器这个文件应该被转化为一个模块。同样,只有模块才能够被导入,浏览器也就知道了模块中有哪些引用。
|
||||
|
||||
不过在 Node 中,并没有 HTML 标签,所以也没有地方声明 type 属性。社区内的一种方式就是使用 `.mjs` 扩展。使用这个扩展告诉 Node这个文件是一个模块。
|
||||
不过在 Node 中,并没有 HTML 标签,所以也没有地方声明 type 属性。社区内的一种方式就是使用 `.mjs` 扩展。使用这个扩展告诉 Node 这个文件是一个模块。
|
||||
|
||||
无论哪种方式,加载器将决定是否将文件转化为一个模块。如果是一个模块并且有导入的话,它就会开始处理直到所有的文件被获取和转化。
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
我们将一块语法规则称为 **产生式**,使用 “Left → Right” 表示任意产生式,用 “Left => Right” 表示产生式的推导过程,比如对于产生式:
|
||||
|
||||
```
|
||||
```plain
|
||||
E → i
|
||||
E → E + E
|
||||
```
|
||||
@@ -17,7 +17,7 @@ E → E + E
|
||||
|
||||
举个例子,比如 `SELECT * FROM table` 可以被表达为:
|
||||
|
||||
```
|
||||
```plain
|
||||
S → SELECT * FROM table
|
||||
```
|
||||
|
||||
@@ -31,7 +31,7 @@ S → SELECT * FROM table
|
||||
|
||||
终结符就是语句的终结,读到它表示产生式分析结束,相反,非终结符就是一个新产生式的开始,比如:
|
||||
|
||||
```
|
||||
```plain
|
||||
<selectStatement> ::= SELECT <selectList> FROM <tableName>
|
||||
|
||||
<selectList> ::= <selectField> [ , <selectList> ]
|
||||
@@ -43,7 +43,7 @@ S → SELECT * FROM table
|
||||
|
||||
对于有二义性的文法,可以通过 **上下文相关文法** 方式描述,也就是在产生式左侧补全条件,解决二义性:
|
||||
|
||||
```
|
||||
```plain
|
||||
aBc -> a1c | a2c
|
||||
dBe -> d3e
|
||||
```
|
||||
@@ -52,7 +52,7 @@ dBe -> d3e
|
||||
|
||||
上面表示,非终结符 `B` 在 `ac` 之间时,可以解析为 `1` 或 `2`,而在 `de` 之间时,解析为 `3`。但我们可以增加一个非终结符让产生式可读性更好:
|
||||
|
||||
```
|
||||
```plain
|
||||
B -> 1 | 2
|
||||
C -> 3
|
||||
```
|
||||
@@ -86,7 +86,7 @@ SELECT * from bees WHERE bee = 'red';
|
||||
|
||||
上面提到的推导符号 `=>` 在实际运行过程中,显然有两种方向左和右:
|
||||
|
||||
```
|
||||
```plain
|
||||
E + E => ?
|
||||
```
|
||||
|
||||
@@ -100,7 +100,7 @@ E + E => ?
|
||||
|
||||
比如 `select <selectList>` 的 `selectList` 产生式,它可以表示为:
|
||||
|
||||
```
|
||||
```plain
|
||||
<SelectList> ::= <SelectList> , <SelectField>
|
||||
| <SelectField>
|
||||
```
|
||||
@@ -115,7 +115,7 @@ E + E => ?
|
||||
|
||||
> Token 见上一期精读 [精读《手写 SQL 编译器 - 词法分析》](https://github.com/dt-fe/weekly/blob/master/64.%E7%B2%BE%E8%AF%BB%E3%80%8A%E6%89%8B%E5%86%99%20SQL%20%E7%BC%96%E8%AF%91%E5%99%A8%20-%20%E8%AF%8D%E6%B3%95%E5%88%86%E6%9E%90%E3%80%8B.md)
|
||||
|
||||
```
|
||||
```plain
|
||||
<SelectList> ::= <SelectField> <G>
|
||||
|
||||
<G> ::= , <SelectList>
|
||||
@@ -124,12 +124,12 @@ E + E => ?
|
||||
|
||||
这其实是一个通用处理,可以抽象出来:
|
||||
|
||||
```
|
||||
```plain
|
||||
E → E + F
|
||||
E → F
|
||||
```
|
||||
|
||||
```
|
||||
```plain
|
||||
E → FG
|
||||
G → + FG
|
||||
G → null
|
||||
@@ -139,7 +139,7 @@ G → null
|
||||
|
||||
笔者建议此处不要生硬的套公式,在套了公式后,再对产生式做一些修饰,让其更具有语义:
|
||||
|
||||
```
|
||||
```plain
|
||||
<SelectList> ::= <SelectField>
|
||||
| , <SelectList>
|
||||
```
|
||||
@@ -152,7 +152,7 @@ G → null
|
||||
|
||||
设想如下的 sql 文法:
|
||||
|
||||
```
|
||||
```plain
|
||||
<Field> ::= <Text> as <Text>
|
||||
| <Text> as<String>
|
||||
| <Text> <Text>
|
||||
@@ -161,7 +161,7 @@ G → null
|
||||
|
||||
其实 Text 本身也是比较复杂的产生式,最坏的情况需要对 Text 连续匹配六遍。我们将 Text 公因式提取出来就可以仅匹配一遍,因为无论是何种 Field 产生式,都必定先遇到 Text:
|
||||
|
||||
```
|
||||
```plain
|
||||
<Field> ::= <Text> <F>
|
||||
|
||||
<F> ::= <G>
|
||||
|
||||
@@ -131,7 +131,7 @@ const selectList =
|
||||
|
||||
显然这样做不具备通用性,因为我们将参数名与数量固定了。考虑到上期精读学到的[文法](https://github.com/dt-fe/weekly/blob/master/65.%E7%B2%BE%E8%AF%BB%E3%80%8A%E6%89%8B%E5%86%99%20SQL%20%E7%BC%96%E8%AF%91%E5%99%A8%20-%20%E6%96%87%E6%B3%95%E4%BB%8B%E7%BB%8D%E3%80%8B.md),我们可以这样描述 `selectList`:
|
||||
|
||||
```
|
||||
```plain
|
||||
selectList ::= word (',' selectList)?
|
||||
word ::= [a-zA-Z]
|
||||
```
|
||||
@@ -183,7 +183,7 @@ const field = () => word()
|
||||
|
||||
这时注意 `field` 作为一个字段,也可能是文本或函数,我们假设拥有函数处理函数 `functional`,那么用文法描述 `field` 就是:
|
||||
|
||||
```
|
||||
```plain
|
||||
field ::= text | functional
|
||||
```
|
||||
|
||||
@@ -228,7 +228,7 @@ function tree(...args: any[]) {
|
||||
|
||||
可选函数就是分支函数的一个特例,可以描述为:
|
||||
|
||||
```
|
||||
```plain
|
||||
func? => func | ε
|
||||
```
|
||||
|
||||
@@ -244,25 +244,25 @@ const optional = fn => tree(fn, () => true)
|
||||
|
||||
上面通过对 SQL 语句的实践,发现了 `match` 匹配单个单词、 `&&` 连接、`tree` 分支、`ε` 空字符串的产生式这四种基本用法,这是符合下面四个基本文法组合思想的:
|
||||
|
||||
```
|
||||
```plain
|
||||
G ::= ε
|
||||
```
|
||||
|
||||
空字符串产生式,对应 `() => true`,不消耗 Token,总是返回 `true`。
|
||||
|
||||
```
|
||||
```plain
|
||||
G ::= t
|
||||
```
|
||||
|
||||
单词匹配,对应 `match(t)`。
|
||||
|
||||
```
|
||||
```plain
|
||||
G ::= x y
|
||||
```
|
||||
|
||||
连接运算,对应 `match(x) && match(y)`。
|
||||
|
||||
```
|
||||
```plain
|
||||
G ::= x
|
||||
G ::= y
|
||||
```
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
为了更加详细的描述这个问题,举一个例子,存在以下岔路:
|
||||
|
||||
```
|
||||
```plain
|
||||
a -> tree() -> c
|
||||
-> b1 -> b1'
|
||||
-> b2 -> b2'
|
||||
@@ -174,7 +174,7 @@ ChainNode 是对链表节点的定义,这里给出了和当前文章内容相
|
||||
|
||||
整个链表结构可能是这样的:
|
||||
|
||||
```
|
||||
```plain
|
||||
node1 <-> node2 <-> node3 <-> node4
|
||||
|- function2-1
|
||||
|- matchToken2-1
|
||||
|
||||
+2
-2
@@ -12,7 +12,7 @@
|
||||
|
||||
<img src="assets/68/2.jpg" />
|
||||
|
||||
产品用户体验不仅是指交互视觉,我的理解用户体验反应用户与产品从认知,使用到传播整个情感的连接和反馈。能力上包括了产品设计与功能实现,用户交互界面,以及系统承载能力。
|
||||
产品用户体验不仅是指交互视觉,我的理解用户体验反映用户与产品从认知,使用到传播整个情感的连接和反馈。能力上包括了产品设计与功能实现,用户交互界面,以及系统承载能力。
|
||||
|
||||
上图是 CUBI Mobel:CUBI UX - User Experience Model。完整地说明了用户体验从内容、商业目标、交互、用户目标四个方面组合。
|
||||
|
||||
@@ -39,7 +39,7 @@
|
||||
我试着列了几类
|
||||
|
||||
1. 产品商业阶段性目标和最终目标。营收提高,优化结构,工程效率提高,质量提高,能力覆盖。
|
||||
2. 工程研发过程及能力。投入产出比,系统成本,系统稳定性,创新性?。
|
||||
2. 工程研发过程及能力。投入产出比,系统成本,系统稳定性,创新性。
|
||||
3. 用户可用性。满意度,过程效率,过程质量。
|
||||
|
||||
## 总结
|
||||
|
||||
@@ -249,7 +249,7 @@ const App = epitath(function*() {
|
||||
|
||||
通过 immutagen,依次调用 `next`,生成新组件,且下一个组件是上一个组件的子组件,因此会产生下面的效果:
|
||||
|
||||
```
|
||||
```plain
|
||||
yield <A>
|
||||
yield <B>
|
||||
yield <C>
|
||||
|
||||
@@ -49,7 +49,7 @@ runPromiseByQueue([
|
||||
|
||||
得到的输出是:
|
||||
|
||||
```
|
||||
```plain
|
||||
promise 1
|
||||
promise 2
|
||||
promise 3
|
||||
|
||||
@@ -214,7 +214,7 @@ class Component extends React.PureComponent<Props, State> {
|
||||
this.rootDom = ReactDOM.findDOMNode(this.rootDomRef) as HTMLDivElement;
|
||||
|
||||
this.chart = new G2.Chart({
|
||||
container: document.getElementById("chart"),
|
||||
container: this.rootDom,
|
||||
forceFit: true,
|
||||
height: 300
|
||||
});
|
||||
|
||||
@@ -259,7 +259,7 @@ const { loading, error, result } = useAsync(fetchUser, [id]);
|
||||
实现:在 Promise 的初期设置 loading,结束后设置 result,如果出错则设置 error,这里可以将请求对象包装成 `useAsyncState` 来处理,这里就不放出来了。
|
||||
|
||||
```tsx
|
||||
export function useAsync(asyncFunction) {
|
||||
export function useAsync(asyncFunction: any, params: any[]) {
|
||||
const asyncState = useAsyncState(options);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -326,8 +326,8 @@ const fetchUser = id =>
|
||||
});
|
||||
|
||||
function useFetchUser(id) {
|
||||
const asyncFetchUser = useAsync(fetchUser, id);
|
||||
return asyncUser;
|
||||
const asyncFetchUser = useAsync(fetchUser, [id]);
|
||||
return asyncFetchUser;
|
||||
}
|
||||
```
|
||||
|
||||
@@ -499,12 +499,23 @@ useEffect(() => {
|
||||
const update = useUpdate();
|
||||
```
|
||||
|
||||
实现:我们知道 `useState` 下标为 1 的项是用来更新数据的,而且就算数据没有变化,调用了也会刷新组件,所以我们可以把返回一个没有修改数值的 `setValue`,这样它的功能就仅剩下刷新组件了。
|
||||
实现:我们知道 `useState` 下标为 1 的项是用来更新数据的,但数据必须有变化才会触发 render,因此我们可以这样设计:
|
||||
|
||||
```tsx
|
||||
const useUpdate = () => useState(0)[1];
|
||||
const useUpdate = () => {
|
||||
const [, setState] = useState(0);
|
||||
return () => setState(cnt => cnt + 1);
|
||||
};
|
||||
```
|
||||
|
||||
或者利用 `useReducer` 做一个简单的 Action 来支持:
|
||||
|
||||
```tsx
|
||||
const [, forceRender] = useReducer(s => s + 1, 0);
|
||||
```
|
||||
|
||||
> 感谢:感谢用户 [cike8899](https://github.com/cike8899) 对此处的勘误,并提供示例代码。
|
||||
|
||||
> 对于 `getSnapshotBeforeUpdate`, `getDerivedStateFromError`, `componentDidCatch` 目前 Hooks 是无法模拟的。
|
||||
|
||||
#### isMounted
|
||||
|
||||
@@ -31,7 +31,7 @@ html`
|
||||
`;
|
||||
```
|
||||
|
||||
很显然,由于跳过了 JSX 编译,换成了原生的 [Template Strings ](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/template_strings) ,所以所有组件、属性部分都需要改成 `${}` 语法,比如:
|
||||
很显然,由于跳过了 JSX 编译,换成了原生的 [Template Strings](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/template_strings) ,所以所有组件、属性部分都需要改成 `${}` 语法,比如:
|
||||
|
||||
`<${Header}>` 这种写法略显别扭,但整体上还是蛮直观的。
|
||||
|
||||
|
||||
@@ -504,7 +504,7 @@ export default {
|
||||
|
||||
## 2.13. Field
|
||||
|
||||
与 Value 组件唯一的区别,就是
|
||||
与 Value 组件唯一的区别,就是支持了 `bind`。
|
||||
|
||||
### 用法
|
||||
|
||||
|
||||
@@ -247,7 +247,7 @@ const functionC = () => chain("y", "c")();
|
||||
|
||||
我们就得到了如下的链表:
|
||||
|
||||
```
|
||||
```plain
|
||||
ChainNode(main)
|
||||
└── FunctionNode(functionA) ─ TreeNode ─ FunctionNode(functionC)
|
||||
│── FunctionNode(functionB1)
|
||||
|
||||
@@ -163,7 +163,7 @@ export const backendMain = () => {
|
||||
|
||||
在文件夹视图下,可以做如下结构规划:
|
||||
|
||||
```
|
||||
```plain
|
||||
.
|
||||
├── client # 前端入口
|
||||
├── server # 后端入口
|
||||
|
||||
+1
-1
@@ -292,7 +292,7 @@ ReactDOM.render(
|
||||
|
||||
根据笔者的经验,**从上层业务到底层通用组件之间,本地状态数量是递增的:**
|
||||
|
||||
```
|
||||
```plain
|
||||
业务
|
||||
-> 全局数据流
|
||||
-> 页面(完全依赖全局数据流,几乎没有自己的状态)
|
||||
|
||||
@@ -71,7 +71,7 @@ function Counter() {
|
||||
|
||||
如果我们 **在三秒内连续点击三次**,那么 `count` 的值最终会变成 `3`,而随之而来的输出结果是。。?
|
||||
|
||||
```
|
||||
```plain
|
||||
0
|
||||
1
|
||||
2
|
||||
@@ -109,7 +109,7 @@ class Counter extends Component {
|
||||
|
||||
嗯,结果应该等价吧?3 秒内快速点击三次按钮,这次的结果是:
|
||||
|
||||
```
|
||||
```plain
|
||||
3
|
||||
3
|
||||
3
|
||||
@@ -505,7 +505,7 @@ function Counter() {
|
||||
|
||||
我们将 `count` 作为了 `useEffect` 的依赖项,就得到了正确的结果:
|
||||
|
||||
```
|
||||
```plain
|
||||
1
|
||||
2
|
||||
3
|
||||
@@ -964,7 +964,7 @@ function Parent() {
|
||||
}
|
||||
```
|
||||
|
||||
虽然 `Child` 可以通过 `memo` 或 `useMemo` 进行优化,**但当程序复杂时,可能存在多个函数在所有 Function Component 间共享的情况 **,此时就需要新 Hook: `useContext` 来拯救了。
|
||||
虽然 `Child` 可以通过 `memo` 或 `useMemo` 进行优化,**但当程序复杂时,可能存在多个函数在所有 Function Component 间共享的情况**,此时就需要新 Hook: `useContext` 来拯救了。
|
||||
|
||||
### 使用 Context 做批量透传
|
||||
|
||||
@@ -1130,7 +1130,7 @@ const Step = () => {
|
||||
一个普通的 Redux 组件:
|
||||
|
||||
```js
|
||||
const mapStateToProps = state => (count: state.count);
|
||||
const mapStateToProps = state => ({count: state.count});
|
||||
|
||||
const mapDispatchToProps = dispatch => dispatch;
|
||||
|
||||
|
||||
+2
-2
@@ -6,7 +6,7 @@
|
||||
|
||||
但读完这本书后,笔者发现不同人站在不同视角会有不同的理解:如果你是一名数据行业从业者,你可以理解数据在当今行业发展中如何起到作用;如果你是企业高管,你会领悟到商业平台发展的规则;如果你是一名创业者,你能体会到点线面体的存在,找到自己的定位;如果你是一名管理者,你能领域到管理模式正在发生的变化;如果你是一名传统行业从业者,你能体会到为什么互联网会对传统行业带来这么大的冲击;如果你是一名社会评论家,你会找到衡量智能时代对人类社会带来影响的标尺,等等。商业是推动人类社会发展的源动力,甚至也是文化与战争的源头,智能商业正因为将商业讲的通透,才摆脱了普通商业书籍枯燥的理论体系,从社会实践中总结理论,最终能上升到富有哲理的思考。
|
||||
|
||||
智能商业一书中有许多关键词,比如 “三浪叠加” “网络协同” “数据智能” “C2B” “S2B2C” “点线面体” “创造力革命” “网红” “互联网X” 等等,能将这些关键词串起来的,笔者认为是 “商业演化”,在近几十年范围内,商业模式存在一些不变底层逻辑(“三浪叠加” “网络协同” “数据智能”),而在大趋势下存在不断演变的商业模式(“C2B” “S2B2C” “点线面体” “创造力革命” “网红” “互联网X”)。
|
||||
智能商业一书中有许多关键词,比如 “三浪叠加” “网络协同” “数据智能” “C2B” “S2B2C” “点线面体” “创造力革命” “网红” “互联网 X” 等等,能将这些关键词串起来的,笔者认为是 “商业演化”,在近几十年范围内,商业模式存在一些不变底层逻辑(“三浪叠加” “网络协同” “数据智能”),而在大趋势下存在不断演变的商业模式(“C2B” “S2B2C” “点线面体” “创造力革命” “网红” “互联网 X”)。
|
||||
|
||||
读完书后会发现,这么多的关键词,最终都为了实现 “C2B” 这个商业最终演化目标,即便是远在十八世纪的工业革命,也在为 C2B 模式打下让物质资源极大丰富的生产力基础,而网络协同和数据智能,都为了让商业规模更大,精准度更强,可以个性化识别每个用户的需求。新的组织模式也是为了更高效服务用户,整合社会 “点线面体” 的生态关系最终可以形成 “C2B” 的服务网络,而网红、互联网 X 都是 C2B 转型在不同阶段、不同行业的尝试。
|
||||
|
||||
@@ -48,7 +48,7 @@
|
||||
|
||||
**关于未来**
|
||||
|
||||
第六章是对未来的判断,重点在互联网与传统产业如何碰撞,提出的 互联网x 概念背后有着更深刻的含义。如果你今年听说了 “产业物联网” 这个名词,可以甄别一下相应的企业,是仅仅将互联网技术运用到了传统行业,还是将传统行业从底层的运作逻辑就互联网化了呢?互联网不仅是一种技术,更是一种思维,互联网思维可以将被传统行业束缚住的各个流程逐渐还原到最原始、高效的模样。
|
||||
第六章是对未来的判断,重点在互联网与传统产业如何碰撞,提出的 互联网 x 概念背后有着更深刻的含义。如果你今年听说了 “产业物联网” 这个名词,可以甄别一下相应的企业,是仅仅将互联网技术运用到了传统行业,还是将传统行业从底层的运作逻辑就互联网化了呢?互联网不仅是一种技术,更是一种思维,互联网思维可以将被传统行业束缚住的各个流程逐渐还原到最原始、高效的模样。
|
||||
|
||||
比如说传统工程需要提前计算销量固化产能,但加入了互联网快速反馈的网络,就可以实时调整产能,当然这需要整个生产流程的互联网化,将整个环节都做到快速反馈。
|
||||
|
||||
|
||||
+1
-1
@@ -140,7 +140,7 @@ const App = epitath(function*() {
|
||||
|
||||
其核心是利用 `generator` 的迭代,将 React 组件的平级结构还原成嵌套结构,将嵌套写法打平了:
|
||||
|
||||
```
|
||||
```plain
|
||||
yield <A>
|
||||
yield <B>
|
||||
yield <C>
|
||||
|
||||
@@ -15,7 +15,7 @@ V8 升级带来了如下几个特性:
|
||||
- [更快的 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 带来了福音,也给会一定程度上提升网页的运行效率。
|
||||
可见 V8 引擎的升级不仅给 Node12 带来了福音,也会一定程度上提升网页的运行效率。
|
||||
|
||||
## TLS 1.3 更好的安全性
|
||||
|
||||
|
||||
@@ -0,0 +1,193 @@
|
||||
# 1. 引言
|
||||
|
||||
[谁在世界中心](https://book.douban.com/subject/27045287/) 是一本介绍地缘政治的书,这本书以海洋为连接世界的主要桥梁,介绍了当今全球视野下海洋争霸的政治格局。
|
||||
|
||||
谁征服了海洋,谁就征服了世界。陆地霸权注定无法拥有全球视野,只有海洋霸权才能征服世界,如今中国已成为海洋贸易霸主,但海洋的武力霸主仍然是美国,如果中国想成为新的全球霸主,就要突破旧的海洋霸权封锁,成为新的海洋霸主。
|
||||
|
||||
当然想成为海洋霸主是非常困难的,这涉及到多方政治力量的博弈,但我们可以通过《谁在世界中心》这本书了解地缘政治关系,让我们看清当下,布局未来。
|
||||
|
||||
《谁在世界中心》共五章,分别介绍了当下谁在主宰世界、东亚与西太平洋、东南亚与南海、南亚与印度洋、俄罗斯与北冰洋。
|
||||
|
||||
之所以标题都是地区与海的关系,是因为陆地与海洋的博弈就是海洋霸权的逻辑。本书需要结合地图理解,因此笔者会贴一些书中地图,围绕着地图讲解本书。
|
||||
|
||||
# 2. 精读
|
||||
|
||||
## 谁在主宰世界
|
||||
|
||||
现在 **美、俄、欧、中** 是这个舞台的主角,但谁也不能仅凭一个地区征服世界,因此与一些重要地区结盟,并成为地区的领导者才可能成为世界的霸主。**边缘地带理论** 就是指,控制了大陆板块的边缘地区,就可以对大陆进行封锁,进而控制大陆。在将眼光放到边缘地区之前,先看看现在世界舞台上的主要政治力量:
|
||||
|
||||
1. 俄罗斯 - 大陆的征服者。俄罗斯一直有扩张的野心,但是在苏联解体后,值保有大部分欧亚大陆中心地带,目前已经失去领衔主演的资格。
|
||||
2. 欧盟 - 世界的发现者。作为大航海时代的开启者,欧洲史就是浓缩的世界史,并且随着疯狂的资本掠夺积累了大量原始资本。但由于英国在欧洲板块处于海洋势力,无法完全控制大陆,因此极力避免欧洲土地上出现一家独大的情况,这导致了美国的崛起。当然现在欧盟的成立也标志着欧洲进入了漫长的整合时期,德国由于其较差的地缘位置(二战后海外利益尽失),更愿意以裹挟欧盟的方式让自己成为主角。
|
||||
3. 印度 - 低纬度地区的代言人。由于低纬度炎热的气候,印度人并不热衷于国际事务,但和美国一样,印度也发展了自己的地缘优势以及人口优势,希望代表低纬度地区参与大国游戏。但是要承受另一个边缘地区国家 - 中国的压力。
|
||||
4. 中国 - 世界中心最有力的挑战者。中国拥有极大的战略纵深,集体主义文化,拥有挑战世界霸主的潜力,但在这个道路上还需解决许多问题,尤其是如何突破由美国主导的 “新世界岛俱乐部” 的封锁。
|
||||
5. 美国 - **“新世界岛俱乐部”** 的缔造者。北美是新世界岛的中心地区,英国和日本是新世界岛的外围地区,分别用来控制 “欧亚大陆西边缘地区(西欧)” 与 “欧亚大陆东边缘地区(中国)”。
|
||||
|
||||
为什么拥有海洋就拥有了世界?“海权论” 有三个主要观点:
|
||||
|
||||
1. **谁掌握了世界核心的咽喉航道、运河和航线,谁就掌握了世界经济和能源运输之门。**
|
||||
2. **谁掌握了世界经济和能源运输之门,谁就掌握了世界各国的经济和安全命脉。**
|
||||
3. **谁掌握了世界各国的经济和安全命脉,谁就控制了全世界。**
|
||||
|
||||
但独霸海洋非常困难,因此美国奉行的是 “边缘地带理论”。也就是**通过控制欧亚大陆东西两端的边缘地带,进而控制了欧亚大陆核心地区,封锁住欧亚大陆的强国,以此保证美国世界霸主的地位。**
|
||||
|
||||
<img width=400 src="https://img.alicdn.com/tfs/TB1R76hca61gK0jSZFlXXXDKFXa-1816-2648.jpg">
|
||||
|
||||
<img width=400 src="https://img.alicdn.com/tfs/TB1gaHecXP7gK0jSZFjXXc5aXXa-1816-2648.jpg">
|
||||
|
||||
从上图可以看出,以北美为 “新世界岛” 的中心地区,通过控制日本与英国,牵制住西欧与中国。美国实际上也做到了这一点。而随着印度的崛起,美国也找到了澳大利亚作为遏制印度的桥头堡。
|
||||
|
||||
那么中国怎么崛起呢?很显然,中国需要组建属于自己的 “世界岛俱乐部”,取代由美国主导的 “旧世界岛俱乐部”:
|
||||
|
||||
1. **与欧亚大陆中心地带的大部分国家(主要是俄罗斯)结盟。**
|
||||
2. **将 “欧亚大陆南边缘地区”(印度)拉入同盟。**
|
||||
3. **寻找可能的 “世界岛外围地区”,并使之倒向同盟(日本、韩国、朝鲜等)。**
|
||||
|
||||
但就目前状况来看,中印关系竞争与合作同时上升,俄国由于前苏联的老大地位暂时不愿意放下身段,日本更处于美国为中心的俱乐部中,因此这条路困难重重。之所以将日本拉进来,一方面是因为与印、俄结盟不足以取得与 “旧世界岛俱乐部” 竞争的优势,一方面是中日地缘距离近,且日本国民性格敬仰强大的对手,另一方面日本是美国牵制中国的力量,拉拢过来不仅可以打消美国的算盘,还能增强东亚整体实力。
|
||||
|
||||
第一章总览了世界地缘政治关系的全貌,并为中国崛起指出了道路。后面几章则具体介绍各个存在联动的政治板块间的具体博弈情况,做到知己知彼。
|
||||
|
||||
## 东亚与西太平洋
|
||||
|
||||
<img width=500 src="https://img.alicdn.com/tfs/TB1DbnfcoY1gK0jSZFMXXaWcVXa-1746-1480.png">
|
||||
|
||||
参与东亚与西太平洋博弈的主要国家有:**中国、朝鲜、韩国、日本、俄罗斯**。中国是参与板块博弈的核心,比如俄罗斯会在朝鲜半岛问题方面发表意见,但不会干涉钓鱼岛问题,而中国都参与其中。
|
||||
|
||||
东亚平原如此广袤,以至于东亚地区的民族都认为控制了这片核心区就控制了世界中心。但随着西方殖民者从海路上到来,**中国人才明白自己并不是世界的中心**,但长期 “中央之国的心态” 影响着我们每一个人。
|
||||
|
||||
<img width=500 src="https://img.alicdn.com/tfs/TB1fqvHceL2gK0jSZPhXXahvXXa-1594-1368.png">
|
||||
|
||||
**中国农耕区域总是受到来自北方三个势力的威胁:**“东北森林渔猎民族”、“蒙古高原草原游牧民族”、“青藏高原高原游牧民族”,这是由于农耕的生产方式稳定,创造的财富大,因此源源不断吸引这些外来者的入侵,有趣的是,每一次农耕区域都能同化外来的入侵者,而 “中国” 的传统观念也是同化他们的重要因素。所以到后面会讲到为何印度人进取心不如中国强,原因就在中国需要长期与北方威胁斗争,而印度不需要,印度由于地缘位置,导致不会受到太多来自边远民族的入侵,这个在分析印度时会讲到。
|
||||
|
||||
**日本、朝鲜半岛由于地理阻隔,在东亚大陆统一时得保持独立**。而朝鲜半岛与大陆相连却一直没有被征服的原因是,从地图上看,想要入侵朝鲜半岛必须沿着海岸线,但通过辽西走廊进入辽河平原时,辽河平原地理气候的不稳定性容易切断朝鲜半岛与东亚核心区脆弱的地缘联系,导致渗入半岛的人口要么退回,要么融于当地族群。
|
||||
|
||||
<img width=500 src="https://img.alicdn.com/tfs/TB1JdHKcoz1gK0jSZLeXXb9kVXa-1582-1496.png">
|
||||
|
||||
东亚面临西太平洋区域被外包包夹形成四个 “内海”,可以形容为 **“第一岛链” 与 “第二岛链”**,美国正是通过控制这些岛链来控制 “欧亚大陆东边缘地区” 的。
|
||||
|
||||
**第一岛链包括:日本群岛、琉球群岛、冲绳岛、台湾岛、南至菲律宾群岛、大巽他群岛**。其中日本是势力最大的岛链,在日本 “大东亚共荣圈” 计划中,极盛时期控制的范围如下图所示:
|
||||
|
||||
<img width=500 src="https://img.alicdn.com/tfs/TB1GBzJceT2gK0jSZFvXXXnFXXa-1610-1452.png">
|
||||
|
||||
而中国想要成为世界霸主,**就要构建以中国为主导核心的 “东亚核心圈 + 东盟十国”**,见下图。
|
||||
|
||||
对日本来说,如今已没有实力做这个核心圈的老大,但最起码希望和中国共同主导,但核心圈只有一家独大才能发挥称霸世界的力量,中国与日本还有很多问题需要解决。相比欧盟,虽然也在融合(3 + 10 模式,即三个核心 - 法、德、英 + 10 个其他国家 不包含俄罗斯),但由于地缘特点不可能出现一家独大的情况。
|
||||
|
||||
<img width=500 src="https://img.alicdn.com/tfs/TB1USbGcaL7gK0jSZFBXXXZZpXa-1864-1606.png">
|
||||
|
||||
**第二岛链包括:从日本岛作为起点,南经小笠原诸国、火山列岛、马里亚纳群岛、关岛、雅浦岛、帕劳群岛,直至哈马黑拉岛等岛群。** 不过第二岛链的威胁远没有第一岛链大。
|
||||
|
||||
<img width=500 src="https://img.alicdn.com/tfs/TB1uRPKchD1gK0jSZFsXXbldVXa-1604-1134.png">
|
||||
|
||||
从西太平洋向东看看美国。**对美国来说,太平洋所有岛屿都是他进攻的跳板**。如上图所示,美国通过诸多岛屿作为跳板进攻,在二战中,甚至跳过了对某些战略要地的争夺,通过前沿岛屿作为基地,直接攻击日本本岛。
|
||||
|
||||
## 东南亚与南海
|
||||
|
||||
<img width=500 src="https://img.alicdn.com/tfs/TB1H7HOckH0gK0jSZPiXXavapXa-1870-1514.png">
|
||||
|
||||
**东南亚区域分位:中南半岛(缅甸、越南与印度支那、泰国)、南洋群岛、文莱、巴厘岛以及东帝汶、马六甲海峡等重要区域。**
|
||||
|
||||
首先看中南半岛:
|
||||
|
||||
<img width=500 src="https://img.alicdn.com/tfs/TB1exPKcaL7gK0jSZFBXXXZZpXa-1742-2534.png">
|
||||
|
||||
**中南半岛由 5 个国家组成,从西到东分别是:缅甸、泰国、柬埔寨、老挝、越南**,其中缅、老、 越与中国接壤,除了老挝外都有足够的海岸线。这些国家大部分是殖民时代的遗产,英法分别在缅甸、越南发力,将泰国定位缓冲国。法国人曾将柬埔寨、老挝、越南合并成 “印支联邦” 与英国对抗,虽然现在又分裂成三个国家,因此却为越南埋下了大国梦。
|
||||
|
||||
缅甸在位置上,可以在陆地及海洋延伸中国的地缘影响力,而且也曾成为支持中国抗战的重要援助物资运输线。在缅甸西边是 “金三角地区”:
|
||||
|
||||
<img width=500 src="https://img.alicdn.com/tfs/TB1iwnRchD1gK0jSZFKXXcJrVXa-1396-1266.png">
|
||||
|
||||
**金三角地区** 盛产鸦片,首先是金三角环境适合种植鸦片,其次由于所处缅甸、老挝、泰国交界处特别适合逃避法律打击。解决问题的办法就是联合执法,在 “湄公河惨案” 后,由中国主导的联合执法开发形成常态,**金三角成为中国拓展自己地缘影响力的重要抓手**。
|
||||
|
||||
中国与中南半岛虽然地缘上存在天然阻隔,但在云贵高原与克钦邦之间存在的南方丝绸之路、中印缅之间存在因抗日战争运输物资而修建了史迪威公路。这些重要的交通枢纽对维系中、缅两国的共同利益有着推动作用。
|
||||
|
||||
**越南** 一直想成为中南半岛的强国,但先后被清朝打压、后与法美中几大国相继开战,始终没有得到什么实际利益。越南狭长的地形使其一直存在南北分裂的风险。
|
||||
|
||||
**泰国** 之所以能在西方殖民者将土地瓜分完毕时仍保持独立,是凭借其高超的平衡技巧,成为了英法殖民地之间的缓冲国。
|
||||
|
||||
<img width=500 src="https://img.alicdn.com/tfs/TB17bYZcXY7gK0jSZKzXXaikpXa-1420-980.png">
|
||||
|
||||
克拉地峡是继马六甲海峡后另一个有价值的航线,是否能开挖取决于各方利益平衡,尤其是这样会切断泰国南北,导致加大泰国南部的分裂倾向。
|
||||
|
||||
<img width=700 src="https://img.alicdn.com/tfs/TB12hvTckP2gK0jSZPxXXacQpXa-2534-1734.png">
|
||||
|
||||
**南洋群岛由 6 个国家组成,分别是:印尼、马拉西亚、菲律宾、文莱、新加坡、东帝汶。**
|
||||
|
||||
“下南洋” 期间,在西方殖民者的推动下,大量华人下南洋开发,因此南洋群岛留着部分华夏民族血液。在新加坡,甚至因为华人占据了 75% 的人口,马来西亚为了在脱离英国殖民统治后保证马来人获得多数票,因此将新加坡排除在马来西亚联邦之外,才使得新加坡独立建国。
|
||||
|
||||
接着看文莱、巴厘岛和东帝汶:
|
||||
|
||||
<img width=500 src="https://img.alicdn.com/tfs/TB1ZB24cfb2gK0jSZK9XXaEgFXa-1434-1274.png">
|
||||
|
||||
**文莱** 在马来西亚中是个弹丸小国,但因为在西方殖民者接入之前,文莱的前身 “渤泥国” 的势力范围很大,因此在被殖民者打碎野心的情况下,文莱有着强烈独立的愿望,从争取 “保护国” 的地位到最终独立,文莱一路走来很不容易。但是文莱被 “林梦地区” 一分为二,马来西亚也不会容忍文莱有更多的领土要求,两者僵持不下。但我们相信,身处这种状况的文莱更希望获得外部力量的支持,作为与南海隔海相望的中国将会是其理想的盟友。
|
||||
|
||||
**巴厘岛** 不仅是度假胜地,在 14 世纪末至 15 世纪初,在伊斯兰教强大压力下,坚守印度教的少数马来人从爪哇岛移民至巴厘岛,因为宗教信仰的不同,这里引起恐怖分子的关注。
|
||||
|
||||
**东帝汶** 是欧洲殖民者划分殖民地的产物,南部的澳大利亚觊觎其丰富油矿资源而积极干涉东帝汶的事物。反过来想,如果中国控制了东帝汶区域,就可以对澳大利亚施加政治影响力。
|
||||
|
||||
<img width=600 src="https://img.alicdn.com/tfs/TB1TVY6cXT7gK0jSZFpXXaTkpXa-1736-2532.png">
|
||||
|
||||
相比南洋群岛,**南海** 离中国更近。如果要控制南海,就要分别控制位于南海五个方向的:**东沙群岛、西沙群岛、黄岩岛、中沙大环礁、南沙群岛**。
|
||||
|
||||
中国想要经略南海,首先要提升自己的综合实力。最近能够在南海问题上有所突破,本质上还是中国综合实力得到了提高。但经略南海不代表占领南海,而是要与南海周边的国家进行博弈,合纵连横。搁置争议,共同开发是最好的策略,如果中国能够掌握深海石油勘采技术,至少能在投资、技术层面让多方面获益。
|
||||
|
||||
<img width=500 src="https://img.alicdn.com/tfs/TB1gpr9coz1gK0jSZLeXXb9kVXa-1640-1146.png">
|
||||
|
||||
从中国海上突围角度来看,有三条航线可选:**南海-马六甲海峡-印度洋航线、印尼通道-印度洋航线、西太平洋-南太平洋-印度洋航线**
|
||||
|
||||
**马六甲海峡** 是南海的咽喉,被新加坡控制,且战时容易被封锁。备选方案印尼通道是个不错的选择,而且相比马六甲海峡三国(新加坡、马来西亚、印度尼西亚),**印尼通道** 只要和印尼搞好关系即可。然而印尼也可能被日本拉拢,但由于印尼与中国没有直接利益冲突,站队日本对印尼来说得不到什么好处。
|
||||
|
||||
## 南亚与印度洋
|
||||
|
||||
<img width=500 src="https://img.alicdn.com/tfs/TB1dC2_coH1gK0jSZSyXXXtlpXa-1734-2532.png">
|
||||
|
||||
**南亚包括 7 个国家**,分别是南亚次大陆的:**尼泊尔、不丹、巴基斯坦、印度、孟加拉国** 和印度洋上的岛国:**斯里兰卡、马尔代夫。**
|
||||
|
||||
由于 “印巴分治”,巴基斯坦于 1947 年独立,但由于东西距离太远,中间隔着印度,因此东边独立成了孟加拉国。不丹处于印度保护国状态,而斯里兰卡除了地理阻隔外,有意识的选择了不同的宗教,也是一直保持独立的重要原因。
|
||||
|
||||
再往南的马尔代夫给人留下的印象就是度假胜地,但这个海拔只有 1.2 米的岛国,随着气候变暖可能是最先消失的国家。
|
||||
|
||||
**印度** 之所以走上与中国不同的道路,主要因为外部压力相对较小。之前也介绍了中国长期受到北方民族的入侵,是因为中国北方有足够大的阶梯地形让北方民族适应 “低原反应”,而印度北方的山脉没有足够的缓冲区,为印度形成了天然的防护屏障。
|
||||
|
||||
虽然热带气候可以让文明较早发展,但没有边缘民族入侵压力,会让文明变得非常脆弱,也缺乏扩张的动力。从融合的角度来说,印度虽然也融合了其他民族,但相比 **中国的 “家天下”,印度属于 “种姓” 文明框架。** “家天下” 的模式每个人都有平等的机会,而 “种姓” 制度确保了阶级固化,加上热带地区物产丰富,不至于出现被饿死的情况,因此这种制度得以稳定下来。
|
||||
|
||||
**克什米尔** 是印度河的上游,在工业化时代,掌握了上游就可以控制下游的水资源,现在印巴两国共享上印度河平原,任何一方都不会轻易放弃这块战略要地。
|
||||
|
||||
<img width=500 src="https://img.alicdn.com/tfs/TB1R93bcoT1gK0jSZFhXXaAtVXa-1414-1102.png">
|
||||
|
||||
中国想要扩大自己在印度洋的影响力,就需要找到 **缅甸、巴基斯坦、斯里兰卡、东帝汶、肯尼亚** 这五个点做支持。如今中国一带一路计划,为东亚各国修筑高铁等基础设施,就是拓展中国外交空间的良好手段,加深经济的合作才有可能迎来政治合作。
|
||||
|
||||
## 俄罗斯与北冰洋
|
||||
|
||||
**俄国** 虽然北临北冰洋,但是没有不冻港是无法通航的。俄国人通过不平等条约使中国东部边界从 **库页岛** 移到了 **乌苏里江**,因此俄国成为了第二个同时可以对三个洋(太平洋、大西洋、北冰洋)施加地缘影响力的国家。
|
||||
|
||||
<img width=500 src="https://img.alicdn.com/tfs/TB1tf7ccmf2gK0jSZFPXXXsopXa-1410-1108.png">
|
||||
|
||||
不过好在中俄存在 “背靠背” 的战略伙伴关系,因此有合作的空间(俄国要应对西欧,中国要应对东南亚)。但俄罗斯的海岸线很短,这导致俄国在海权争霸的舞台只能当配角,但这个地缘结构不是一成不变的,如果全球变暖导致北冰洋融化,看到的将是另一个格局:
|
||||
|
||||
<img width=500 src="https://img.alicdn.com/tfs/TB1x6RGbKbviK0jSZFNXXaApXXa-1440-1230.png">
|
||||
|
||||
如果北冰洋融化后可以通航,俄罗斯将成为北冰洋地缘势力最强大的国家,其次是加拿大与阿拉斯加。如果俄国没有短视将阿拉斯加卖给美国,俄国将为成为北冰洋唯一的霸主。
|
||||
|
||||
# 3. 总结
|
||||
|
||||
《谁在世界中心》这本书一定要看着地图读,这样会发现板块运动随机产生的变化竟然会对世界政治格局产生这么重大的影响,一个国家能否独立最重要的还是看地缘位置。
|
||||
|
||||
这本书更是一本中国崛起的地缘解决方案指南,其中一些解决方案在商业逻辑中可以拿来借鉴:
|
||||
|
||||
1. 竞争是一个过程,唯有不断参与其中,才有可能掌握话语权,主导权,最终达到政治目的。任何领土都是通过与周边地区漫长博弈后逐渐确立下来的,想得到利益首先得参与到游戏中。
|
||||
2. 各玩家实力是动态变化的,即便是无法通航的北冰洋,都可能因为温室效应变成不冻港,因此提前看到趋势并提前准备是必须的。
|
||||
3. 想从对方获得利益,首先要了解对方想获得什么利益,自己有什么筹码,这样才容易促成合作。在寻找盟友前,先站在对方角度掂量一下自己是否合适。
|
||||
4. 不可能一家独大,想成为霸主,必须建立一个生态。以前是小弟听大哥的话,现在大哥得给小弟好处,才能得到小弟的忠诚。
|
||||
5. 已有霸主的地位不是一朝一夕就能摧毁的,就像中国想突破马六甲海峡的封锁,在不突破整体海洋封锁的前提下是不可能的,因为海洋霸权是一个全球化整体,美国封锁亚洲有完整的第一岛链、第二岛链逻辑,解决问题的视角要全面。
|
||||
|
||||
如今处于大变革的和平时代,国家之间看似和平,实则在进行经济扩张,大国之间要学会不撕破脸的竞争方式。而这个多方博弈的复杂性,使得阴谋几乎不可能得逞,大国的政策都是阳谋,比的是谁更能顺势而为,拉拢更多合作者。
|
||||
|
||||
> 讨论地址是:[精读《谁在世界中心》 · Issue #189 · dt-fe/weekly](https://github.com/dt-fe/weekly/issues/189)
|
||||
|
||||
**如果你想参与讨论,请 [点击这里](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,261 @@
|
||||
# 1. 引言
|
||||
|
||||
引用著名瑞典统计学家 Hans Rosling 的一句话:想法来源于数字、信息,再到理解。
|
||||
|
||||
分析数据的最好方式是可视化,因为可视化承载的信息密度更高,甚至可以从不同维度对数据进行交互式分析。今天要精读的文章就分析了经典可视化分析工具 Tableau:[data-visualisation-made-easy](https://www.analyticsvidhya.com/blog/2017/07/data-visualisation-made-easy/)。
|
||||
|
||||
# 2. 精读
|
||||
|
||||
[Tableau](https://www.tableau.com/) 是一款广泛用于智能商业的强大数据分析工具,通过不同可交互的图表和仪表盘帮助你获得业务洞见。
|
||||
|
||||
## 安装
|
||||
|
||||
Tableau 提供了三种使用方式:
|
||||
|
||||
**Tableau Desktop**
|
||||
|
||||
[拥有 14 天免费试用的桌面版](https://www.tableau.com/products/trial),可以将工作数据存储在计算机本地,如果你是学生或老师可以获得一年的免费使用权。
|
||||
|
||||
**Tableau Public**
|
||||
|
||||
[公开版完全免费](https://public.tableau.com/s/download),和桌面版的唯一区别是,所有数据都无法保存在本地,只能保存在 Tableau 服务器的云端,而且是公开的。
|
||||
|
||||
**Tableau Online**
|
||||
|
||||
[网页版也完全免费](https://sso.online.tableau.com/public/idp/SSO),是 Tableau Public 的网页版。
|
||||
|
||||
## 连接数据源
|
||||
|
||||
安装好 Tableau 后,第一步就是连接数据源。它支持连接本地或云端的数据源,本地最常用的数据源可以从 Excel 转换。这里是一份 [样例数据](https://github.com/pavleenkaur/TableauTutorial-On-AnalyticsVidhya/blob/master/Sample-Superstore.xls),包含了一个超市几年内的销售情况,我们可以用这份数据练手。
|
||||
|
||||
下载好这份数据后,选择从 Excel 导入,确认后将 **Orders** 表拖拽到右侧区域,如下图所示:
|
||||
|
||||
<img width=500 src="https://img.alicdn.com/tfs/TB1Cvh5cV67gK0jSZPfXXahhFXa-1440-900.png">
|
||||
|
||||
可以看到,导入的数据格式有些问题,这是因为这份 Excel 文件表头有一些描述信息干扰。勾选 **Use Data Interpreter** 后,可以开启数据解析功能,自动分析出你想要的表结构:
|
||||
|
||||
<img width=500 src="https://img.alicdn.com/tfs/TB1XLJ_c7T2gK0jSZPcXXcKkpXa-1440-900.png">
|
||||
|
||||
可以看到表结构已经正常了,在数据清洗的过程中,Tableau 强大的数据分析功能已经初见端倪。你甚至可以点击 **Review ths results** 看看它是如何清洗数据的:点击后会下载一份分析 Excel,其中过滤掉的数据会被标记,自动分析出的表结构会被高亮。
|
||||
|
||||
## 数据可视化
|
||||
|
||||
在页面最底部有几个切换项,依次是 **Data Source**:数据源、**Sheet**:工作簿,后面跟随的三个按钮可以继续创建多个 Sheet、Dashboard、Story,这些后面都会讲到。首先点击 Sheet 进入可视化分析的工作簿:
|
||||
|
||||
<img width=500 src="https://img.alicdn.com/tfs/TB1S2LzcebviK0jSZFNXXaApXXa-1440-900.png">
|
||||
|
||||
可以看到,Orders 表的字段已经被自动分析成 **维度** **度量** 了。维度和度量是数据分析中重要的概念:
|
||||
|
||||
- **维度:** 维度是不能被计数的字段,一般为字符串或离散的值,用来描述数据的维度。
|
||||
- **度量:** 度量是可以被计数的字段,一般为数字、日期等连续的值,用来描述数据的量。
|
||||
|
||||
右侧空白区域是图表展示区域,**可以响应拖拽交互**,顶部的 Columns、Rows 表示列与行,Filters 是过滤器,拖拽字段上去可以对此字段进行过滤,Marks 是标记,Tableau 将图表所有辅助标记功能都抽象为:颜色、大小、文本、具体值、工具提示。举个例子,如果将销量 Sales 字段拖拽到大小区域,那么任何能描述大小的图表,都会以销量的多少来决定大小,比如散点图。
|
||||
|
||||
右上角的 **Show Me** 是图表自动推荐区域,当你拖拽不同字段的时候,Tableau 会自动展示合适的图表,但你也可以点击 Show Me 进行图表切换。
|
||||
|
||||
那么开始动手吧!**首先我们要看看大盘数据如何,也就是这家超市的总利润、质量、销量:**
|
||||
|
||||
> 在左侧维度栏目下,最后一个字段 **Measure Names** 表示所有度量的集合。
|
||||
|
||||
1. 将 **Measure Names** 拖拽到画布的空白区域。
|
||||
2. 移除我们不关心的 Row ID, Discount 等字段。
|
||||
|
||||
<img width=500 src="https://img.alicdn.com/tfs/TB1caeXcYH1gK0jSZFwXXc7aXXa-1440-900.png">
|
||||
|
||||
可以看到,总利润大概是总销量的 10%。如果想展示横向表格,将 Measure Names 从 Rows 拖拽到 Columns 即可。
|
||||
|
||||
> Tips: 为了方便区分,Tableau 贴心的将维度标记为蓝色,度量标记为绿色。
|
||||
> 同时可以看到,Tableau 对于单指标拖拽,默认采取表格方式渲染。
|
||||
|
||||
**接下来我们要看每一年的详细销量与利润:**
|
||||
|
||||
1. 将 Order Date 与 Sales 拖拽到 Rows。
|
||||
2. 右键 Sales,将类型从连续改成非连续,这样就会自动变成表格展示。
|
||||
3. 为了展示利润,将 Profit 字段拖拽到 Marks 的 Text 字段上。
|
||||
|
||||
<img width=500 src="https://img.alicdn.com/tfs/TB1fxV_c4v1gK0jSZFFXXb0sXXa-1440-900.png">
|
||||
|
||||
我们可以看到,无论是销量还是利润都在逐年上升。**接下来我们想具体看看每个月份的数据**:
|
||||
|
||||
1. 右键 Order Date,将日期维度从年切换到月。
|
||||
|
||||
<img width=500 src="https://img.alicdn.com/tfs/TB1SCN9c.T1gK0jSZFrXXcNCXXa-1440-900.png">
|
||||
|
||||
我们可以看到,销量较高的月份分布在:3、9、11、12 月。注意由于没有对年份做筛选,这里的每月统计数据是整合了 2013~2016 四年份的。也就是 1 月的数据其实代表了 2013.1 + 2014.1 + 2015.1 + 2016.1 共四个 1 月份数据的总和。
|
||||
|
||||
**接下来我们想了解销量与利润增长的趋势:**
|
||||
|
||||
1. 将 Order Date 拖拽到 Columns。
|
||||
2. 将 Sales 拖拽到 Rows,此时会出现一条线。接下来将 Profit 拖拽到 **左 Y 轴**。
|
||||
|
||||
<img width=500 src="https://img.alicdn.com/tfs/TB1zVF_c7P2gK0jSZPxXXacQpXa-1440-900.png">
|
||||
|
||||
这里就涉及到线图拖拽交互设计了,线图一共有三种拖拽方式。如果将一个新字段拖拽到左 Y 轴,就会在左 Y 轴多出一条线;如果拖拽到中间图表区域,则这个字段会当作已有字段的工具提示;如果拖拽到右 Y 轴,则会自动变成双轴图。
|
||||
|
||||
从上图中能看到,销量增长明显,但利润增长缓慢,看来经营是存在一定问题的,还要继续分析问题在哪。
|
||||
|
||||
**我们再看看数据按月分布情况**,同样右击 Order Date,选择 月 粒度:
|
||||
|
||||
<img width=500 src="https://img.alicdn.com/tfs/TB1RmJ9c.H1gK0jSZSyXXXtlpXa-1440-900.png">
|
||||
|
||||
上图可以明显看到三个峰值出现在 3、9、11 月份,然而这段期间利润增长幅度却不大,可以看出这段期间采取了薄利多销的手段。
|
||||
|
||||
**再从地区维度分析数据:**
|
||||
|
||||
1. 将 Regions 和 Sales 拖拽到 Columns。
|
||||
2. 切换到饼图。
|
||||
3. 将 Sales 拖拽到 Marks Pane 的 Label 上。
|
||||
|
||||
<img width=500 src="https://img.alicdn.com/tfs/TB1KEJ_c7Y2gK0jSZFgXXc5OFXa-1440-900.png">
|
||||
|
||||
可以看到东西部地区是销量最高的区域。**接下来我们想看具体城市的销量:**
|
||||
|
||||
1. 将 States 拖拽到画布空白区域,此时会自动出现地图并定位到美国。将 Profits 拖拽到 Color。
|
||||
2. 将地区切换到 Filled Map,将 Profits 拖拽到 Label。
|
||||
|
||||
这样就绘制了一张地区,颜色越深利润越高,数字表示销量。
|
||||
|
||||
<img width=500 src="https://img.alicdn.com/tfs/TB1CFWbc.Y1gK0jSZFCXXcwqXXa-1440-900.png">
|
||||
|
||||
可以看到数值越大的区域一般颜色也越深,但这不是分析利润/销量性价比的最佳方式,我们先只看到加州和纽约是销售业绩最好的区域,而科罗拉多州虽然销量不错,但利润却是负的。
|
||||
|
||||
上面的地图对地形比较直观,但要分析销售健康度,还是用散点图更合适。**我们想看看城市销量/利润的健康度分布:**
|
||||
|
||||
1. Profit 拖拽到 Columns,Sales 拖拽到 Rows,此时散点图出现,但只有一个点(之所以出现散点图,是因为横纵轴拖拽的都是度量)。
|
||||
2. 我们想按城市下钻,只要把 State 拖拽到 Detail 即可。
|
||||
|
||||
<img width=500 src="https://img.alicdn.com/tfs/TB1EMl9cVY7gK0jSZKzXXaikpXa-1440-900.png">
|
||||
|
||||
可以看到,遥遥领先的城市有三个,加州是销售之王。
|
||||
|
||||
由于还没有介绍到筛选条件,这里简略介绍一下,其实还可以将年份拖拽到筛选条件,只看 2013 年的分布图,也可以点击或圈选其中某些点选择排除某些城市。
|
||||
|
||||
**现在需要进一步分析明细数据,将不同商品种类按年份细分,看按月的销量,并看看这些月份的利润如何:**
|
||||
|
||||
1. 此时需要用到高亮表格。首先将 Category 和 Order Date 拖拽到 Rows,简单的表格出现了。
|
||||
2. 将 Order Date 再拖拽到 Columns,并右键将其粒度改为月。
|
||||
3. 在 Show Me 中切换为 Highlight Table,重新将 Order Date(Year)拖拽回 Rows。
|
||||
4. 为了展示颜色与文字,将 Profit 拖拽到 Color,Sales 拖拽到 Label。
|
||||
|
||||
<img width=500 src="https://img.alicdn.com/tfs/TB1Ud07cWL7gK0jSZFBXXXZZpXa-1440-900.png">
|
||||
|
||||
可以看到,办公套件和科技产品业绩最好,其中办公套件在 2015 年 12 月销量利润双丰收,科技产品在 2015 年 10 月与 2016 年 3 月销量利润双丰收。整体来看前半年是淡季。
|
||||
|
||||
但这张图无法看到销量与利润性价比关系,**我们要找出利润率最高的商品和利润率最低的商品:**
|
||||
|
||||
1. 将 Proft 拖拽到 Columns。
|
||||
2. 将 Sub-Category 拖拽到 Rows。
|
||||
3. 切换到 Horizontal Bars。
|
||||
4. 将销量 Sales 拖拽到 Color。
|
||||
|
||||
<img width=500 src="https://img.alicdn.com/tfs/TB10T4.c9f2gK0jSZFPXXXsopXa-1440-900.png">
|
||||
|
||||
可以明显看到 Copiers 就是性价比之王,拥有最高的利润,但销量却不是很高(颜色深度中等),而桌子是性价比最低的,利润为负,而且销量不低。
|
||||
|
||||
## 其他功能
|
||||
|
||||
除了上面基本可视化分析能力之外,Tableau 还有许多辅助功能。
|
||||
|
||||
### 筛选器
|
||||
|
||||
在按月分布的折线图中,如果我们只想看某一年的,可以将 Order Date 拖拽到 Filters 区域,只勾选想要保留的年份:
|
||||
|
||||
<img width=500 src="https://img.alicdn.com/tfs/TB1jgWcc1H2gK0jSZJnXXaT1FXa-1440-900.png">
|
||||
|
||||
Tablueau 这种交互等价于 Sql 中 `in` 语句,当然 Tablueau 还支持更复杂的条件或代码表达式,这里只是将更友好的筛选方式优先展示区来。
|
||||
|
||||
### 上卷下钻
|
||||
|
||||
Tableau 支持任意维度之间的上卷下钻,只要你将他们分好组。
|
||||
|
||||
比如将 Order Date、Order ID、Ship Date、Ship Mode 拖拽到一起,成为 Orders 组;将 Category、Sub-Category、Product ID Product Name 形成 Product 组:
|
||||
|
||||
<img width=500 src="https://img.alicdn.com/tfs/TB11lmcc.Y1gK0jSZFCXXcwqXXa-1440-900.png">
|
||||
|
||||
我们就可以将 Product 直接拖拽到画布区域,并选择矩形树图,通过点击指标上的 “+” “-” 号进行上卷或下钻:
|
||||
|
||||
<img width=500 src="https://img.alicdn.com/tfs/TB1yud_cVT7gK0jSZFpXXaTkpXa-1440-900.png">
|
||||
|
||||
上卷下钻是顺序相关的,比如 Product - Order Date 表示在产品类目基础上,对每个类目按日期下钻。而 Order Date - Product 这个顺序,表示在日期分布的基础上,对日期按产品类目下钻,了解不同日期下每个产品的分布情况。
|
||||
|
||||
### 趋势线
|
||||
|
||||
为使用趋势线,先制作一个双轴图:
|
||||
|
||||
1. 将 Sales 与 Profit 拖拽到 Rows。
|
||||
2. 将 Order Date 拖拽到 Columns 并切换到月维度。
|
||||
3. 选择 Show Me 的 Dual Combination 即混合图。
|
||||
|
||||
<img width=500 src="https://img.alicdn.com/tfs/TB1YOeacV67gK0jSZPfXXahhFXa-1440-900.png">
|
||||
|
||||
点击 Analytics Tab,将 Trend Line 拖入 chart 中:
|
||||
|
||||
<img width=500 src="https://img.alicdn.com/tfs/TB1HL5gc.Y1gK0jSZFCXXcwqXXa-1440-900.png">
|
||||
|
||||
趋势图有几种算法,比如线性,Log 或指数,因此在做趋势分析前,首先要判断自己的业务属于哪种增长阶段,如果是爆发期可以选择指数,平稳期可以选择线性等等。
|
||||
|
||||
### 预测
|
||||
|
||||
回到按月分布的图表,如果我们想预测未来销量和利润的走势,可以使用预测功能:
|
||||
|
||||
1. 切换到 Analytics Tab,并将 Forecast 拖拽到图表中。
|
||||
2. 可以点击右键配置预测参数。
|
||||
|
||||
<img width=500 src="https://img.alicdn.com/tfs/TB1Cumcc4D1gK0jSZFsXXbldVXa-1440-900.png">
|
||||
|
||||
预测趋势有一个浅色区域,表示预测范围。
|
||||
|
||||
### 聚类
|
||||
|
||||
象限图的四象限是多维度综合判断的法则,然而 Tableau 支持的聚类分析可以自动做到这些:
|
||||
|
||||
1. 切换到 Analytics Tab,选择 Clusters。
|
||||
2. 可以选择自动聚类个数,也可以手动指定个数。
|
||||
|
||||
<img width=500 src="https://img.alicdn.com/tfs/TB1BsWgc1P2gK0jSZFoXXauIVXa-1440-900.png">
|
||||
|
||||
从上图可以看到,指定了 4 个分类,最右上角加州就是最突出的一组,整个聚类只有它一个元素,而画面偏左下角的也是一类,这些是业绩较差的一组数据。使用了 K 均值聚类算法,并且当你点击右键查看详细星系时,还能把组间、组内方差展示出来:
|
||||
|
||||
<img width=500 src="https://img.alicdn.com/tfs/TB1O7Oec1H2gK0jSZFEXXcqMpXa-1440-900.png">
|
||||
|
||||
## 仪表板
|
||||
|
||||
仪表板可以将多个 Sheets 内容聚合在一起并自由布局,但仪表板最精髓的功能是图表联动功能:
|
||||
|
||||
1. 点击任意图表,选择 “作为筛选条件”。
|
||||
|
||||
<img width=500 src="https://img.alicdn.com/tfs/TB1cVjIcebviK0jSZFNXXaApXXa-1440-900.png">
|
||||
|
||||
Tableau 的所有图表都支持点选,排除等操作,那么点选这类操作本质上其实是个筛选的过程,比如柱状图点击了某根柱子,可以认为是选择了这根柱子当前的维度值作为筛选条件。
|
||||
|
||||
当一个 Sheet 作为筛选条件后,类似点选这种操作产生的筛选就会作用于其他同数据集的图表,因此如上图所示,当点击了条形图的某一根柱子时,上面的销量地图也自动做了筛选,仅展示当前选中的产品的销量分布。
|
||||
|
||||
## 故事
|
||||
|
||||
Story 更像是 PPT,将分析后有价值或有意义的图表组合在一起,再配合上说明,得出一些结论:
|
||||
|
||||
<img width=500 src="https://img.alicdn.com/tfs/TB1wa1gc1L2gK0jSZFmXXc7iXXa-1018-870.png">
|
||||
|
||||
如上图所示,比如得到这家超市的大盘数据,这一般也是数据分析的最后一步,最后生成报表。
|
||||
|
||||
# 3. 总结
|
||||
|
||||
Tableau 的交互式分析思路印证了这句话:
|
||||
|
||||
数字、信息,再到理解最终才能产生 Idea。我们从拿到 Excel 导入数据集开始,数据就已经变成了维度和度量的信息,再经过主动思考,将同一份数据进行不同维度的展示,最终得出加州销量最好、家具销售业绩最差、而桌子是负利润的主要来源等等洞见。
|
||||
|
||||
通过原文对 Tablueau 功能的分析能看到,Tableau 的核心资产是具备交互式分析能力的图表,这些图表通过智能推荐的方式展示出来,可以在不知道如何分析数据时找到一些灵感,真正做到以数据角度思考,图表展示只是辅助的视觉效果。
|
||||
|
||||
目前国内还处于报表制作的时代,即先选择报表再配数据集,这种使用思路是展示数据优先,而不是分析数据优先,笔者认为原因在于国内大部分做报表的业务场景都处于最末端,也就是数据洞见已经有了,再使用 BI 将这个洞见还原出来。而 BI 工具真正想做的还是在前面 “分析洞见” 这一步,希望数据分析师能可以通过 BI 平台挖掘出商业洞见。
|
||||
|
||||
要走到这一步,需要国内 BI 平台与使用 BI 的人都发展到下一阶段,而这种探索式数据分析功能早在 2012 年就在国外由 Tableau 团队实现,相信未来三年内国内一定能迎来一波探索式数据分析浪潮!
|
||||
|
||||
> 讨论地址是:[精读《Tableau 入门》 · Issue #192 · dt-fe/weekly](https://github.com/dt-fe/weekly/issues/192)
|
||||
|
||||
**如果你想参与讨论,请 [点击这里](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,93 @@
|
||||
# 1. 引言
|
||||
|
||||
微软的市值已经突破一万亿美元了,我们很难想象当年僵化而封闭的微软是怎么涅槃重生的。从仅支持自家 Windows 到收购 Github、从失去移动操作系统市场到与 AWS 平分云服务市场、从 Windows 收费升级到 Win10 限时免费升级、从咒骂 Linux 是癌症到大部分云服务都跑在 Linux 操作系统上、从反垄断、数据隐私被诉讼大户,到 Facebook Google 被监管部门调查时却可以置身事外,微软一定从内部发生了彻底的变革。
|
||||
|
||||
新微软的变革经验值得我们学习,[《刷新》](https://www.baidu.com/link?url=tVqSngscNfJokBYi3Jwg66SDAJy8Sn5Y4nChDn1gJRAdsrbNYqXSQl43UyyryLtsNhlf4e0UjUfseUAUpPTuMK&wd=&eqid=c6d6c373000d8ee4000000045d58efd4) 就是一本介绍这场变革的书,它的作者是领导这场变革的现任微软 CEO 萨提亚·纳德拉。
|
||||
|
||||
这本书的关键词是:**同理心、文化变革、成长型思维**。
|
||||
|
||||
# 2. 精读
|
||||
|
||||
本书围绕家庭与事业两个层面展开,从家庭中得到的领悟帮助作者更好的工作。
|
||||
|
||||
## 萨提亚的家庭
|
||||
|
||||
作者萨提亚·纳德拉的母亲是一名教师,父亲是一个勤奋的印度高级官员,然而他的成长环境相当宽松,**使作者从小就懂得独立思考并按照自己的意愿做事**。母亲难以兼顾事业与家庭而选择放弃工作,**让作者体会到女性工作的不公平**,父亲**追求上进心态与行为帮助作者得到更多职业发展机会**。而孩子扎因天生的重度大脑性瘫痪**使作者学着站在孩子的角度思考问题,学会真正理解同理心。**
|
||||
|
||||
作者爱好的运动是板球,这是印度最受欢迎的运动。这项运动带给作者的除了热血沸腾之外,还有对团队合作的理解:**好的领导不仅自己能力要出色,还要能帮助队员提升信心,发挥队员的潜力**,而专业技能优秀的球员,如果不能进行良好的团队合作,最坏的情况甚至会损害团队整体利益。
|
||||
|
||||
## 微软面对怎样的危机
|
||||
|
||||
危机往往是多个维度体现的,且相辅相成。微软面临的两大主要危机分别是 **员工失去信心** 与 **业绩下滑**,员工失去信心是内因,引发了业绩下滑的外因。
|
||||
|
||||
在最糟糕的时候,微软内部帮派林立,各部门负责人只想巩固自己的地盘,这让微软失去了创新领域竞争的机会。科技行业的业务趋势总是处于 **三浪叠加状态**:
|
||||
|
||||
- **旧的领域业绩已经在下滑**,但基数大,往往也是公司发家的根基,对部门负责人自己来说,再吃几年老本对自己的利益最大,但这终将导致公司走向失败。
|
||||
- **当前领域增长已经逐渐放慢**,但未来仍有很大增长空间,这些业务被寄予了厚望。
|
||||
- **新的领域尚不清晰**,但一旦探索到正确的方向,增长速度甚至会年年翻番,这些业务会在未来几年内成为公司的收入支柱。
|
||||
|
||||
微软的个人计算机 Windows 操作系统太过成功,使微软在移动端浪潮下没能将足够的资源投入到移动端业务中,真正的创新部门被边缘化,旧领域部门掌握着绝对话语权,如果 CEO 不能作出改变,公司将走向不可逆的衰亡。
|
||||
|
||||
业务上,微软也在这三个主要方向全面落后:
|
||||
|
||||
- **操作系统领域**:微软个人计算机出货量和财务增长已陷入停滞,而苹果、谷歌的智能手机和平板电脑销量正在上升。
|
||||
- **搜索领域**:谷歌的搜索和在线广告收入也在持续增长,而微软的搜索技术才刚起步,市场份额只有竞争对手的零头。
|
||||
- **云技术领域**:亚马逊推出的 AWS 已经在市场建立起领导地位,微软由于 Windows 原因,不愿意接受云计费模式,还在固守一次性买卖思维,甚至连云产品都没有。
|
||||
|
||||
## 微软是如何转型的
|
||||
|
||||
站在首席执行官视角,转型一定是从文化转型开始的,只有转变了企业文化,才能充分激发每一个人的潜力,使公司朝着正确方向发展。作者在成为微软 CEO 后,在文化上作出的改变主要分为三点:
|
||||
|
||||
- **找到微软公司的新使命**。显然,让每个人都拥有一台电脑这个目标已经达成了,为了推动微软继续前进,作者将新的目标设定为:赋能大众,通过做平台、工具,来提升全社会各组织、团体的工作效率、医疗效率、组织效率等等。
|
||||
- **建立耳目一新、出人意料的伙伴关系**。不论是 Linux 、苹果公司还是亚马逊,一方面是强劲竞争对手,但另一些领域也有合作的价值,比如将微软办公套件通过 IOS 平台普惠到大众这种部分领域合作的心态是不可或缺的。微软封闭的文化也在这一点上真正转向了开放,独占的思维模式如果走不通,合作能带来更多的机会。
|
||||
- **同理心**。微软高级副总裁沈向洋在 2019 年极客大会的分享也提到了这一点,微软通过制造辅助设备帮助帕金森患者正常完成写字、绘画。从广义上说,微软正式通过同理心,站在用户角度思考,才领悟到如何才能真正的帮助用户,比如一位安卓用户需要在手机查看 Word 文档,那么让 Word 支持安卓平台,推出基于云平台的 Office 365 就是一个自然的行为。
|
||||
|
||||
在文化转型的推动下,微软在业务上也进行了一系列积极的调整:
|
||||
|
||||
- **将云业务放到核心位置**。这一点和阿里的云战略转型很像。云业务一开始都不怎么赚钱,需要大量资金和人才投入,在数年后才能看到回报,微软最大的问题是如何打破公司内资源分配不均匀的问题。通过一系列人事调整与战略制定,微软的云业务走上了正规,现在已经与 AWS 平分市场份额。
|
||||
- **在可能的领域与竞争对手达成合作**。除了推出 IOS 平台的 Office 套件外,必应还成为了雅虎搜索的搜索引擎,微软甚至放弃了排他性条款,允许雅虎同时使用其他公司的搜索引擎服务,即便如此,必应引擎现在仍驱动着大部分雅虎搜索功能,而良好的开放心态也加速必应搜索引擎能力的迭代。
|
||||
- **推动部门之间员工的协作**。随着文化变革,微软内部部门孤岛的情况有了好转,从不接收其他部门意见的 Windows 研发部门开始采纳其他部门员工提出的建议。笔者了解到 Facebook 的大部分源码每个员工都有充分权限参与修改,维护一个系统不只是相应业务线员工的特权,来自其他部门的创意往往更优秀。
|
||||
|
||||
## 三条领导原则
|
||||
|
||||
无论是推动文化变革,还是推动业务增长,都需要高级、中层管理人员的实施,作者给出了三点领导原则:
|
||||
|
||||
- **向共事的人传递明确信息**。传达信息是领导者每天都在做的事情,领导者应该把信息交流重点放在事情上,而不是人上,也就是关注如何把事情做好,而不是讨论谁更聪明。
|
||||
- **领导者要产生能量,不仅在自己团队中,还要在整个公司中**。领导者身处在多个圈子中,有自己管理的团队的圈子,也有来自上级组织的圈子,有来自公司级横向委员会的圈子,也有核心管理层的圈子,作者站在 CEO 的角度,要求领导者要将最高一层圈子放在首要地位,也就是整体利益大于局部利益。
|
||||
- **找到取得成功和让事情发生的方式**。也就是正确的做事,懂得平衡长期利益与短期利益,不走极端;让团队成员找到自己热爱的工作方式;能跨越边界,全球化思维。
|
||||
|
||||
## 其它
|
||||
|
||||
本文要突出的介绍的内容已经结束,本书还有最后几个部分笔者简要带过:
|
||||
|
||||
**三大变革:**
|
||||
|
||||
作者提出未来可能由技术引领行业变革的三个方向:混合现实、人工智能和量子计算。这就是跨越边界的思维方式,微软积极布局的这三个前沿领域,对准的是未来的 “第三浪”。
|
||||
|
||||
**隐私、安全和言论自由:**
|
||||
|
||||
捍卫隐私、安全与言论自由也是微软转型的重要内容,微软通过积极与监管部门合作,通过实际行动捍卫言论自由,使得微软从政府监管对象逐渐转变为监管原则的捍卫者,这也是近年来科技巨头纷纷作出一个改变。
|
||||
|
||||
**人与机器的关系:**
|
||||
|
||||
不要把机器与人想成竞争关系,要理解为机器辅助人类的关系。同时机器也是释放人类创造力的最重要方式,虽然在变革前期会导致大量失业,但消失的旧行业都是重复性高的,创造出来的新行业更能激发人类的创造力。有一句话笔者印象最深刻:机器替代人类工作的过程,也是人类逐渐拾回作为人的尊严的过程。人本就应该将时间用于思考与创造,而不是重复性劳动。
|
||||
|
||||
# 3. 总结
|
||||
|
||||
引导微软一系列变革的源泉可以认为是 “同理心”,因为同理心可以练就开放的性格,指引正确的方向。微软 CEO 萨提亚从家庭与生活中养成了同理心,并将其运用在公司的变革上,最终让微软每一位员工都能换位思考,利用同理心做正确的事,这种思想的传导是最难的一步,作者做到了。
|
||||
|
||||
对于我们的思考是,无论是公司的管理者,还是基层员工,都应该培养自己的同理心,因为有同理心的人不仅能更好的工作,在生活中也能更融洽的与人相处。
|
||||
|
||||
在工作中,同理心也是突破职业天花板的能力之一,想要提升为客户带来的价值,首先要接触并理解客户,站在客户视角思考问题,在面临内部矛盾或外部竞争时,仍能坚守为客户创造价值的目标,下一步改革的方向就会变得清晰,矛盾会逐渐化解,竞争也不会是一个问题,用户想要的不是竞争,而是被赋能,持有这种心态做事,与竞争对手合作就是利益最大化的选择了。
|
||||
|
||||
微软的首席执行官萨提亚正因为抱有同理心,才能作出超越竞争、封闭的决策,这对还没能掌握这一心智的公司来说,是种降维打击。一个用一切手段赋能用户、在核心能力不惧竞争(云计算)、在可合作领域充分合作的公司是极其强大的。
|
||||
|
||||
> 讨论地址是:[精读《刷新》 · Issue #196 · dt-fe/weekly](https://github.com/dt-fe/weekly/issues/196)
|
||||
|
||||
**如果你想参与讨论,请 [点击这里](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,541 @@
|
||||
# 1. 引言
|
||||
|
||||
Tableau 探索式分析功能非常强大,各种功能组合似乎有着无限的可能性。
|
||||
|
||||
今天笔者会分析这种探索式模型解题思路,一起看看这种探索式分析功能是如何做到的。
|
||||
|
||||
# 2. 精读
|
||||
|
||||
要掌握探索式分析,先要掌握探索式分析背后的思维模型。
|
||||
|
||||
## 理解数据
|
||||
|
||||
有分析意义的数据一般是表结构,即分为行与列,列定义了数据含义,行则构成了数据明细。
|
||||
|
||||
当我们将数据作为 “原材料” 使用时,需要将这些明细数据封装为 “数据集” 的概念来理解,数据集概念中,数据就是一个个字段,对于字段,要理解 “维度” 与 “度量” 这两个概念。
|
||||
|
||||
### 维度
|
||||
|
||||
维度是不能被计数的字段,一般为字符串或离散的值,用来描述数据的维度。
|
||||
|
||||
### 度量
|
||||
|
||||
度量是可以被计数的字段,一般为数字、日期等连续的值,用来描述数据的量。
|
||||
|
||||
<img width=172 src="https://cdn.nlark.com/yuque/0/2019/png/201572/1566632483137-9e0268d9-f890-45e6-a3e5-805355b35af9.png#align=left&display=inline&height=464&name=image.png&originHeight=1096&originWidth=406&size=83329&status=done&width=172">
|
||||
|
||||
我们首先要将数据集字段归类到维度与度量,才能提高数据分析的效率。**数据分析就是从不同维度下看度量值**,先想清楚要看的是什么数据,比如销量还是利润?这些字段都属于度量,然后想一想要怎么看这些度量,是看总数、拆解到年看、还是按地区看呢?这些字段都属于维度。
|
||||
|
||||
**维度和度量是可以单独看的,如果单看维度,那只能看这个维度的明细,比如看 订单日期 这个字段**:
|
||||
|
||||
<img width=190 src="https://cdn.nlark.com/yuque/0/2019/png/201572/1566633158647-cba541bd-673c-498c-95e6-53c89346c458.png#align=left&display=inline&height=149&name=image.png&originHeight=298&originWidth=380&size=24178&status=done&width=190">
|
||||
|
||||
需要注意的时,维度与度量字段还可以分为 **连续** 与 **离散** 。
|
||||
|
||||
### 连续
|
||||
|
||||
值是连续关系,即任意两个值之间可以计算差值。
|
||||
|
||||
### 离散
|
||||
|
||||
值是离散关系,即任意两个值之间无法计算差值,无法以连续的方式去理解。
|
||||
|
||||
**一般来说,维度字段都是离散的,度量字段都是连续的。**从字段类型意义上也能得出相同的结论:维度字段一般为字符串或日期类型,字符串类型都是离散的,度量字段一般为数字类型,数字天生就可以连续。
|
||||
|
||||
值得注意的是,连续与离散其实与字段类型、维度度量并无关系,比如维度的日期字段就是可连续的,而就算是字符串类型,也可以以字符串长度等方式 “定义” 一种连续的计算方式。对数字类型的度量字段来说,我们也可以忽略数字之间的联系,将数字看待为字符串,这样数字之间就是离散的。
|
||||
|
||||
**上图的 “离散方式看日期” 就是看维度的直观方式,但仍可以用 “连续方式看日期”:**
|
||||
|
||||
<img width=309 src="https://cdn.nlark.com/yuque/0/2019/png/201572/1566633194083-b17c1c2c-7023-47cd-a94c-48d57fd37217.png#align=left&display=inline&height=308&name=image.png&originHeight=616&originWidth=618&size=37644&status=done&width=309">
|
||||
|
||||
离散方式下单看维度只有一条条数据,数据间并无排序规则,而以连续方式看维度,维度就会以某种方式排序:比如上图以时间类型进行排序。此时展示方式也从表格切换为了柱状图,因为表格适合展示离散数据,柱状图的一根柱子就可以展示连续数据。
|
||||
|
||||
单看度量时,由于 **度量要依附于维度展示**,因此仅有度量时,只能看这个度量的 **聚合** 概念:
|
||||
|
||||
<img width=200 src="https://cdn.nlark.com/yuque/0/2019/png/201572/1566633468748-6ffb22b0-8c8d-4f6c-bdb6-a16616e29e70.png#align=left&display=inline&height=107&name=image.png&originHeight=214&originWidth=400&size=15238&status=done&width=200">
|
||||
|
||||
如上图所示,单看销量这个度量字段时,我们只能将数据集中所有销量字段聚合在一起来看,**但这种聚合方式也可以分成若干种计算类型 - 求和、平均值、中位数、计数、计数去重、最小值、最大值、方差等等:**
|
||||
|
||||
<img width=414 src="https://cdn.nlark.com/yuque/0/2019/png/201572/1566633611811-f0bef366-b9ca-47dd-adbc-4e2d311f52de.png#align=left&display=inline&height=502&name=image.png&originHeight=1004&originWidth=828&size=228956&status=done&width=414">
|
||||
|
||||
这些能力之间都是 “正交” 的,即单看度量这一个字段,可以以这么多种类型进行计算,那么按维度拆分后,度量依然可以享受如上不同的计算方式。
|
||||
|
||||
**也可以用连续方式看度量:**
|
||||
|
||||
<img width=184 src="https://cdn.nlark.com/yuque/0/2019/png/201572/1566633797820-b7980691-1be7-4644-a321-cac98cc36e5f.png#align=left&display=inline&height=304&name=image.png&originHeight=608&originWidth=368&size=23542&status=done&width=184">
|
||||
|
||||
与连续-维度不同,连续-度量图形中除了最后一个值,其他过渡数值都是无效的,因为连续-度量只有一个值。连续-维度也要注意,由于以连续的方式画出图形,中间不存在的点也被 “无缝连接” 了。
|
||||
|
||||
数据之间也可以存在父子级关系,有父子级关系就可以进行上卷下钻了,这种父子级关系被称为 “层系字段”:
|
||||
|
||||
<img width=209 src="https://cdn.nlark.com/yuque/0/2019/png/201572/1566634402929-9e23bff4-4810-4827-bf3b-c6af9ef617ea.png#align=left&display=inline&height=217&name=image.png&originHeight=434&originWidth=418&size=33356&status=done&width=209">
|
||||
|
||||
上图的 Orders 就是一个层系字段。层系字段是几个字段的排序组合,**由上到下依次构成下钻关系,从下到上则是上卷的关系。**
|
||||
|
||||
### 层系
|
||||
|
||||
**只有维度字段才能有层系,**因为度量是不能被拆分的,只有维度才可以被拆分。
|
||||
|
||||
维度的拆分可以是有逻辑含义的,也可以是任意的。
|
||||
|
||||
**有逻辑含义的层系**
|
||||
|
||||
最典型有逻辑含义的层系字段就是时间了。一个好的 BI 系统识别到日期字段后,应该将拿到的日期字段进行归类,比如判断日期字段粒度到天,则自动生成一个日期层系字段,自动聚合到年,并允许用户随意切换:
|
||||
|
||||
<img width=277 src="https://cdn.nlark.com/yuque/0/2019/png/201572/1566634723539-8f80cdd3-f8af-41a2-9e89-db94ecfa3200.png#align=left&display=inline&height=161&name=image.png&originHeight=322&originWidth=554&size=51469&status=done&width=277">
|
||||
|
||||
如果数据集字段值精确到月,则层系只能最多展开到月。
|
||||
|
||||
日期层系的逻辑含义在于,年、季度、月、天这种下钻关系是天然从大到小的关系,符合自然理解。
|
||||
|
||||
**任意层系**
|
||||
|
||||
如果层系字段不代表日期,就只能以业务含义组合层系字段了。**比如可以将层系按照 订单日期 -> 商品 ID -> 运货日期的方式组合:**
|
||||
|
||||
<img width=624 src="https://cdn.nlark.com/yuque/0/2019/png/201572/1566634964577-6dad1f7c-b01b-419a-8b7c-6e0832d6c8b6.png#align=left&display=inline&height=132&name=image.png&originHeight=308&originWidth=1454&size=41917&status=done&width=624">
|
||||
|
||||
这种下钻方式,可以看到每个订单日期下有哪些商品,每个商品分别运货日期是什么。
|
||||
|
||||
**也可以按照商品 ID 拆分出不同的订单日期与运货日期,这种层系组合方式就是以商品 ID 为主要视角:**
|
||||
|
||||
<img width=622 src="https://cdn.nlark.com/yuque/0/2019/png/201572/1566635114693-57a2b260-d7cc-4945-9050-361ce4608e09.png#align=left&display=inline&height=129&name=image.png&originHeight=302&originWidth=1456&size=44825&status=done&width=622">
|
||||
|
||||
可以看到,不同思维角度会按照不同的方式组合层系。比如一家大公司要查看财务问题,维度有:BU、日期,度量有:销量。
|
||||
|
||||
那么有两种下钻方式:BU -> 日期、日期 -> BU。无论哪种下钻方式,都能看到每个 BU 按日期销量的明细,但 BU -> 日期 能看到每个 BU 按日期聚合的总销量,而 日期 -> BU 能看到不同日期按 BU 聚合的总销量,前者更易对比出 BU 之间差异,后者更易对比出日期之间的差异。
|
||||
|
||||
## 理解配置
|
||||
|
||||
配置是探索式分析的入口,要理解分析模型首先得理解配置模型。
|
||||
|
||||
Table 主要配置分为行、列、标记与筛选。通过这四个配置区域可以组合成千变万化的数据洞察模型。既然如此,让我们看看这种配置思路是什么,以及为何这四种配置相互组合就能覆盖整个探索式分析场景?
|
||||
|
||||
我们不需要考虑三维数据分析场景,因为三维透视的关系,图形丢失了精确大小关系,没有精度的数据是没有分析价值的。由于在二位平面中分析数据,**大部分图表都可以用 “行、列” 方式进行配置**。
|
||||
|
||||
**也许有人会问,为什么不用维度与度量替代行列呢**?这是一个很好的问题,有数据分析经验的人会站在维度与度量角度思考问题,因此对于任意图表,只要配置维度、度量即可呀?笔者从三个方面说说自己的理解:
|
||||
|
||||
1. 探索式分析思路中,不关心图表是什么,也不关心图表如何展示,因此图表是千变万化的,比如折线图可以横过来,条形图也可以变成柱状图,因此 **你将维度放到列,就是一个柱状图,你将维度放到行,就是一个条形图** 。
|
||||
2. 将精力真正放到你要拖拽的字段上。由于字段已经有维度、度量的区别,配置区域就不要再限定维度与度量了,减少理解成本。
|
||||
3. 维度与度量可以同时放在行或列上,这是探索式分析的另一个精髓能力,看下图:
|
||||
|
||||
<img width=306 src="https://cdn.nlark.com/yuque/0/2019/png/201572/1566636365926-c5d37423-0e32-4382-ae7d-2b9760caeb97.png#align=left&display=inline&height=435&name=image.png&originHeight=1240&originWidth=872&size=94620&status=done&width=306">
|
||||
|
||||
做探索式分析功能时,要跳出思维定式:**为什么条形图的纵轴不能放维度呢?**如上图所示,如果行拖拽了两个不同的度量,那么可以出现两条线或者双轴图,但当拖拽一个维度一个度量时,可以对图表进行 **分面** ,比如观察 2013 ~ 2016 年不同顾客对销量的贡献。
|
||||
|
||||
### 行
|
||||
|
||||
表格类的行、图表类的纵轴。一般建议放置度量字段。
|
||||
|
||||
### 列
|
||||
|
||||
表格类的列、图表类的横轴。一般建议放置维度字段。
|
||||
|
||||
如上所示,无论行还是列,都可以进行任意维度度量组合,且字段数量不限,而且可以在任何层级进行下钻。**对图表来说,多个维度时需要进行分面处理:**
|
||||
|
||||
<img width=476 src="https://cdn.nlark.com/yuque/0/2019/png/201572/1566637370597-d52b7677-9ec4-40f3-aa54-a0382ac56de1.png#align=left&display=inline&height=428&name=image.png&originHeight=1448&originWidth=1612&size=106528&status=done&width=476">
|
||||
|
||||
如上图所示,将列放置两个维度字段成为柱状图,那么横轴就要同时表示两个维度,如上图所示。如果横轴还有更多的维度,可以再不断对横轴进行拆分。
|
||||
|
||||
横轴(列)多维度字段的顺序也会影响图表的展现。**上图最后一个字段是 Category 默认是离散的,所以这个离值就决定了图表使用柱状图,图表类型由维度周最后一个字段连续或离散决定。**
|
||||
|
||||
比如我们对调 Order Date 与 Category 会怎样?
|
||||
|
||||
<img width=476 src="https://cdn.nlark.com/yuque/0/2019/png/201572/1566637657990-0387a37c-f4dc-45ca-a53a-aa10b4573318.png#align=left&display=inline&height=420&name=image.png&originHeight=1456&originWidth=1620&size=137950&status=done&width=467">
|
||||
|
||||
我们得到了三个不同类目近 12 个月的趋势,之所以是折线图,因为图表的维度轴(列)是连续的。**如果我们对 Order Date 进行天级别的下钻:**
|
||||
|
||||
<img width=462 src="https://cdn.nlark.com/yuque/0/2019/png/201572/1566637772126-a9825860-4851-41ae-a56d-df984ea49b4c.png#align=left&display=inline&height=328&name=image.png&originHeight=1462&originWidth=2062&size=128020&status=done&width=462">
|
||||
|
||||
可以看到,**下钻功能本质上就是维度轴支持对多个维度字段拆分处理。只要图表支持了维度轴任意维度字段的分面展示,那么配置端就可以将下钻按照拖了多个字段的方式去理解了。**
|
||||
|
||||
**如果我们将折线图切换为表格,会发生什么?**
|
||||
|
||||
<img width=578 src="https://cdn.nlark.com/yuque/0/2019/png/201572/1566637986444-1a78b31f-6bb6-4c3e-b69b-0bd5a32c399f.png#align=left&display=inline&height=211&name=image.png&originHeight=738&originWidth=2018&size=111516&status=done&width=578">
|
||||
|
||||
我们会发现,原本存在于列的 Category 被自动挪到了行,原本存在于行的 Sales 被挪到了 “标记” 区域。在正式介绍 “标记” 区域前,先理解一下为何会发生这种转变:
|
||||
|
||||
**表格类组件是双维度组件,折线图是单维度组件。**也就是表格的行与列都是维度,而折线图横轴作为维度后,纵轴就要作为度量。上面的例子中,折线图维度有两个字段,虽然通过分面方式渲染出来了,但当切换为支持双维度的表格后, **可以将多余的一个维度挪到表格组件另一个维度区域中**。
|
||||
|
||||
而表格行与列都是维度的情况下,单元格的值就需要用 “标记” 中文本来表示,因此原折线图的度量字段自动转移到了 “标记” 区域。
|
||||
|
||||
### 标记
|
||||
|
||||
标记区域也采取字段拖拽的方式,即对字段进行标记。
|
||||
|
||||
标记区域分为 **颜色、大小、标签、详细信息、工具提示、路径。**标记正如其名,是作用于图表上的标记,**即不会对图表框架有实质性影响的辅助标记信息。**
|
||||
|
||||
对不同图表来说,影响最大的是行与列,它能决定用什么图表,如何拆分数据。而标记往往是改变图表中辅助性元素,比如文字或者颜色等等。
|
||||
|
||||
#### 工具提示
|
||||
|
||||
不影响任何图像显示,仅仅在提示信息中新增字段信息。
|
||||
|
||||
**对图表来说,指的是 Tooltip 提示信息增加对应的字段:**
|
||||
|
||||
<img width=424 src="https://cdn.nlark.com/yuque/0/2019/png/201572/1566655533184-4ea4560f-8fef-40aa-9910-c301842c53b4.png#align=left&display=inline&height=363&name=image.png&originHeight=1000&originWidth=1168&size=91870&status=done&width=424">
|
||||
|
||||
从上图可以看到,利润字段放在工具提示区域,则图表的 Tooltip 会新增利润这个字段的信息。**值得关注的是,Tableau 所有图表都支持 Tooltip 包括表格:**
|
||||
|
||||
<img width=623 src="https://cdn.nlark.com/yuque/0/2019/png/201572/1566655642537-e7a18aa6-f619-45c4-b2b3-f95274d40107.png#align=left&display=inline&height=157&name=image.png&originHeight=374&originWidth=1480&size=42938&status=done&width=623">
|
||||
|
||||
这保证了配置统一,行为统一。
|
||||
|
||||
#### 大小
|
||||
|
||||
控制图表大小。
|
||||
|
||||
对于线图,控制线的粗细;对于气泡图控制气泡大小;对于柱状图控制柱子粗细;但是对面积图与表格没有明显作用。这得益于 Tableau 将每个图表大小属性尽可能抽象出来。
|
||||
|
||||
<img width=360 src="https://cdn.nlark.com/yuque/0/2019/png/201572/1566655966495-9241a848-b8c0-48b7-b0a4-8fbcc44ff58c.png#align=left&display=inline&height=273&name=image.png&originHeight=748&originWidth=988&size=58386&status=done&width=360">
|
||||
|
||||
<img width=360 src="https://cdn.nlark.com/yuque/0/2019/png/201572/1566656245320-7f29da9d-363f-4fd3-95f3-15b0ec2a3522.png#align=left&display=inline&height=223&name=image.png&originHeight=982&originWidth=1602&size=99303&status=done&width=364">
|
||||
|
||||
#### 文本
|
||||
|
||||
即直接展示在图表上的文本。
|
||||
|
||||
对普通图表来说,文本体现为 Label,即直接展示在图表上的文字。比如柱状图默认是没有 Label 文字的,要将对应字段拖拽到文本标记上才会出现。
|
||||
|
||||
<img width=404 src="https://cdn.nlark.com/yuque/0/2019/png/201572/1566656520351-588602a5-db01-4e46-bfdd-6f771353d7a8.png#align=left&display=inline&height=379&name=image.png&originHeight=934&originWidth=996&size=77362&status=done&width=404">
|
||||
|
||||
这体现出与普通报表构思的不同。对普通报表来说,Label 是通过一个勾选项开启的,Label 对应的值就是图表度量这个字段的值。而 Tableau 将标签值以字段方式开放拖拽,就有了展示与值分开的可能性,可适用范围更广。
|
||||
|
||||
> 有人觉得长度和数字一定要对应上,这也是对数据理解不同导致的。Tableau 将文本(标签)列在标记里,说明文本和颜色、大小一样,都是一种附加的信息展示维度,很多时候不需要两种方式展示同一种信息,反而需要图形以更多方式以不同维度展示信息。
|
||||
|
||||
#### 颜色
|
||||
|
||||
控制图表的颜色。
|
||||
|
||||
比如在度量为销量时,可以将利润作为颜色,甚至再将折扣作为文本,通过一个折线图同时看多种度量信息:
|
||||
|
||||
<img width=386 src="https://cdn.nlark.com/yuque/0/2019/png/201572/1566656981186-8f2441d6-78d0-4d34-af7f-139b0f21cb30.png#align=left&display=inline&height=344&name=image.png&originHeight=888&originWidth=996&size=82343&status=done&width=386">
|
||||
|
||||
与之对比,我们可以将利润放在右 Y 轴作为双轴图达到相同的效果:
|
||||
|
||||
<img width=439 src="https://cdn.nlark.com/yuque/0/2019/png/201572/1566657023624-c01b0008-e7d7-4991-99c1-beddc8c72762.png#align=left&display=inline&height=319&name=image.png&originHeight=886&originWidth=1218&size=112190&status=done&width=439">
|
||||
|
||||
**标记就是为了在不增加行、列字段数量基础上,通过颜色、大小、标签、工具提示等维度展示出额外信息。**
|
||||
|
||||
#### 详细信息
|
||||
|
||||
如果将度量拖拽到详细信息,会发现完全没有作用。因为 “详细信息” 只有拖拽维度字段才生效。“详细信息” 其实是用作下钻的,拖拽一个维度字段后,可以按照这个维度进行下钻。
|
||||
|
||||
<img width=533 src="https://cdn.nlark.com/yuque/0/2019/png/201572/1566657906186-3348cdf7-823f-4111-9685-dbf5fb05c0f8.png#align=left&display=inline&height=376&name=image.png&originHeight=1054&originWidth=1496&size=102786&status=done&width=533">
|
||||
|
||||
如上图所示,将销售按照产品线拆解成三条线。但这三条线无法分辨,因此可以使用颜色来拆分维度:
|
||||
|
||||
<img width=533 src="https://cdn.nlark.com/yuque/0/2019/png/201572/1566657988073-e8e50316-feb2-4c7f-98dc-45f4a2ffd624.png#align=left&display=inline&height=372&name=image.png&originHeight=1052&originWidth=1510&size=112469&status=done&width=534">
|
||||
|
||||
这样就能将拆解的内容按不同颜色展示。因此, **对标记作用的字段如果是维度字段,且作用于颜色、大小、标签、详细信息时,会额外进行维度进行拆解,并对拆解后的内容进行颜色或大小区分。**
|
||||
|
||||
相信读到这里会有个疑问:按照维度进行拆解与维度拖拽多个字段进行字段有什么区别?我们试一下看看效果,将产品类目维度拖拽到销量所在的行,对销量进行销量维度的拆分:
|
||||
|
||||
<img width=570 src="https://cdn.nlark.com/yuque/0/2019/png/201572/1566658461645-fbcc1b14-0111-47b1-b643-d23f36f96ef2.png#align=left&display=inline&height=512&name=image.png&originHeight=1058&originWidth=1178&size=92443&status=done&width=570">
|
||||
|
||||
**可以看到,在行、列进行的多维度拆分使用的是分面策略,而在标记中对维度进行拆分使用的是单图表多轴方式来实现。**
|
||||
|
||||
除此之外的区别在于,在标记进行的维度拆分默认作用于度量,而行列上的多维度拆分可以任意作用于维度或度量。
|
||||
|
||||
> 同时配置端要限制 **能拆分的只有维度或离散状态的度量** ,也就是只有离散状态的字段可以被拆分。如上图所示,我们不能将 Category 拖拽到 Sales 右侧,除非将 Sales 设置为离散类型。
|
||||
> Tips:Tables 对维度与度量分别分配了蓝色、绿色,当我们将绿色度量字段设置为离散类型时,这个度量字段会变成蓝色,也就是当作了维度字段进行处理。
|
||||
|
||||
最后,标记区域不仅能拖拽字段,还可以单击后修改详细配置,比如修改颜色详细配置:
|
||||
|
||||
<img width=220 src="https://cdn.nlark.com/yuque/0/2019/png/201572/1566658916573-5c989763-bd8f-4f2d-8f57-9f52b9c39b20.png#align=left&display=inline&height=448&name=image.png&originHeight=896&originWidth=442&size=39310&status=done&width=221">
|
||||
|
||||
或者对工具提示的 Tooltip 内容进行定制:
|
||||
|
||||
<img width=557 src="https://cdn.nlark.com/yuque/0/2019/png/201572/1566658941101-e4eac6aa-d4f1-4397-9be2-ccc424211f00.png#align=left&display=inline&height=319&name=image.png&originHeight=910&originWidth=1590&size=207889&status=done&width=557">
|
||||
|
||||
### 筛选器
|
||||
|
||||
Tableau 将所有筛选条件都收敛到筛选器中,我们可以通过拖拽字段的方式对某个字段进行筛选:
|
||||
|
||||
<img width=635 src="https://cdn.nlark.com/yuque/0/2019/png/201572/1566659069046-3e4f195e-1c9d-492d-bfe7-6f6d89997692.png#align=left&display=inline&height=210&name=image.png&originHeight=494&originWidth=1494&size=138806&status=done&width=635">
|
||||
|
||||
如上图所示,比如只看办公用品与科技产品。但其实除了这个通用功能之外,Tableau 还支持更强大的图表交互功能,即点击或圈选图表后,可以对选中的点(字段值)进行保留或排除:
|
||||
|
||||
<img width=606 src="https://cdn.nlark.com/yuque/0/2019/png/201572/1566659178209-134b5fc5-b067-4481-bf99-ed36c8c458c7.png#align=left&display=inline&height=198&name=image.png&originHeight=442&originWidth=1350&size=55242&status=done&width=606">
|
||||
|
||||
**当我们选择排除这几个点时,会自动生成一份对维度字段的筛选条件排除掉选中日期,所以图表是完全数据驱动的:** 一般来说
|
||||
|
||||
<img wdith=576 src="https://cdn.nlark.com/yuque/0/2019/png/201572/1566659259441-22ef9da8-e3bc-4cef-b32e-c538c1dfe3e0.png#align=left&display=inline&height=268&name=image.png&originHeight=696&originWidth=1494&size=189252&status=done&width=576">
|
||||
|
||||
如果属性存在下钻关系会如何呢?无论是行列中对维度的下钻,还是通过标记对维度进行了拆解,筛选都是对 **字段层系** 生效的:
|
||||
|
||||
<img width=575 src="https://cdn.nlark.com/yuque/0/2019/png/201572/1566659420072-3e6e41f4-bc99-4d7d-a93f-553abe429a94.png#align=left&display=inline&height=169&name=image.png&originHeight=442&originWidth=1504&size=155974&status=done&width=575">
|
||||
|
||||
如上图所示,对下钻后的字段进行筛选,**那么筛选条件也会自动构造出临时的字段层系,并对这个临时层系进行筛选。** 可以看到,我们不仅能在字段配置区动态组成层系字段,在筛选器中也可以生成临时层系进行筛选,我们需要支持任意层系组合的字段,并作用于筛选器、行列,甚至是标记上。
|
||||
|
||||
顺带一提,我们还可以对设置了筛选的字段层系组合拖拽到任意地方使用:
|
||||
|
||||
<img width=393 src="https://cdn.nlark.com/yuque/0/2019/png/201572/1566659649713-3428813a-d156-4c80-935f-e40de333a8fe.png#align=left&display=inline&height=434&name=image.png&originHeight=1050&originWidth=950&size=88795&status=done&width=393">
|
||||
|
||||
要处理这种场景,**我们需要让所有字段都拥有筛选能力**,普通字段等于没有筛选条件,我们也可以对一个包含了筛选条件的字段拖拽到任何位置作用。
|
||||
|
||||
刚才是对维度进行的筛选,有没有对度量进行筛选的场景呢?有,但我们只能手动将度量字段拖拽到筛选器位置进行手动筛选:
|
||||
|
||||
<img width=613 src="https://cdn.nlark.com/yuque/0/2019/png/201572/1566659919516-77548c29-7c02-4857-9eec-fb1465826f9a.png#align=left&display=inline&height=312&name=image.png&originHeight=832&originWidth=1636&size=86512&status=done&width=613">
|
||||
|
||||
如果我们进行图表内的圈选操作,增加的筛选条件一定是按维度来的:
|
||||
|
||||
<img width=613 src="https://cdn.nlark.com/yuque/0/2019/png/201572/1566659955376-66830503-bce1-443d-ab98-9fbc90e54fdb.png#align=left&display=inline&height=273&name=image.png&originHeight=756&originWidth=1694&size=80004&status=done&width=612">
|
||||
|
||||
这么理解这一行为:维度是离散的,勾选操作能表达的含义有限,比如勾选折线图的某些点,如何知道我们要勾选的是维度的那几个月,还是度量的利润范围呢?
|
||||
|
||||
<img width=613 src="https://cdn.nlark.com/yuque/0/2019/png/201572/1566660078571-ecf7c466-44c1-46a5-9ef0-d3692bf22fb8.png#align=left&display=inline&height=555&name=image.png&originHeight=1468&originWidth=1620&size=113222&status=done&width=613">
|
||||
|
||||
**由于最终勾选操作落地在点上,而不是区间上(连续值也不适合进行圈选),所以默认按对维度进行筛选是最准确的理解。**如果上图的操作意图中,你想勾选的不是 6~12 月的区间,而是销量在 13k ~ 45.5k,则需要手动拖拽利润字段,并精确输入筛选范围:
|
||||
|
||||
<img width=482 src="https://cdn.nlark.com/yuque/0/2019/png/201572/1566660226577-102ca29f-b6ef-43a7-860a-041b04274da8.png#align=left&display=inline&height=353&name=image.png&originHeight=774&originWidth=1056&size=81734&status=done&width=482">
|
||||
|
||||
值得注意的是,对连续型度量进行筛选前,还可选择聚合方式:比如对求和的值进行范围筛选,或者对最大值进行范围筛选,功能十分强大。
|
||||
|
||||
## 理解图表
|
||||
|
||||
图表是数据可视化的载体,只有数据与配置,没有各式各样的图表,很难产生直观的数据洞察。
|
||||
|
||||
可以说, **按照探索式分析的思路,当配置好数据与配置后,可以有多种可视化载体去展示这种配置信息。** 比如行、列分别拖拽了日期与销量,那么折线图、表格、散点图、柱状图都可以满足需求,但如果行所在的字段是离散的,那么折线图、散点图就不适合了,这就需要图表推荐功能根据配置推荐合适的图形展示。
|
||||
|
||||
Tableau 内置的图表分为 N 大类 - **表格、地图、柱折面饼、散点/象限图** 、以及直方图、盒须图、甘特图、靶心图等。可见分析数据,不需要太多种类可视化展现方式,但对于每个图表组件来说,都需要修炼深厚的内功,做好一个表格、折线图并不简单。
|
||||
|
||||
### 行与列
|
||||
|
||||
表格、地图、柱折面饼、散点/象限图等都可以用行与列描述基本架构:
|
||||
|
||||
- 表格天然拥有行与列,对调后则代表转置。表格的行与列必须是维度字段,如果拖拽度量字段上去会自动切换为其他图表,再切回来则会把度量字段挪动到 “文本” 标记区域中。
|
||||
- 地图行与列就是经纬度,当维度字段放到 “详细信息” 时,根据地理映射表转化为经纬度自动生成经纬度放在行与列。
|
||||
- 柱折面饼、散点/象限图都是直角坐标系的图形,以维度字段作为维度轴,以度量字段作为度量轴。
|
||||
|
||||
#### 行列的下钻
|
||||
|
||||
在行或列存在多个维度字段时,图表要进行相应下钻。表格对于行下钻如下图所示:
|
||||
|
||||
<img width=529 src="https://cdn.nlark.com/yuque/0/2019/png/201572/1566699040802-40a23a43-7a23-43d1-9d06-27a6413803e8.png#align=left&display=inline&height=314&name=image.png&originHeight=856&originWidth=1440&size=106220&status=done&width=529">
|
||||
|
||||
**上图也可以理解为展示出 Order Date 与 Order ID 的明细数据,按照 Order Date 分组且列合并。**下钻就是一步步接近明细数据的过程,但目的不是为了看明细表,而是看某些维度下按其他维度拆分的详细信息。
|
||||
|
||||
图表下钻和表格思路是一致的:
|
||||
|
||||
<img width=529 src="https://cdn.nlark.com/yuque/0/2019/png/201572/1566699822756-b087948b-3104-4bd4-a76b-1cb2067edd65.png#align=left&display=inline&height=335&name=image.png&originHeight=1338&originWidth=2114&size=109757&status=done&width=529">
|
||||
|
||||
对于维度轴多维度下钻,将每个维度轴下钻到更细粒度。图表在行与列同时下钻时,与表格的表现稍有不同。仅从轴来看拆解方式是相同的,内部展示了多套轴:
|
||||
|
||||
<img width=667 src="https://cdn.nlark.com/yuque/0/2019/png/201572/1566700041429-03bcb68a-dd00-4cab-a256-f37234bb2300.png#align=left&display=inline&height=423&name=image.png&originHeight=1350&originWidth=2128&size=155697&status=done&width=667">
|
||||
|
||||
**可以认为,当行或列上最后一个字段为度量时,就会切换为图表展示,因为图表适合展示连续状态。**如果排除上图蓝色区域,剩下的区域就是个交叉表,交叉表只是行与列同时存在维度字段的场景,仅有行或列时就变成了普通表格;而图形的下钻和表格下钻机理相同,只是把 “单元格” 的文本换成了柱子或线。
|
||||
|
||||
**所以对任何图表的下钻,都是对轴的下钻,**相同的是单元格属性永远不会改变,表格的单元格是文本,图形单元格是图形,一个简单折线图可以理解为对整体行与列单元格进行 “连续打通”:
|
||||
|
||||
<img width=406 src="https://cdn.nlark.com/yuque/0/2019/png/201572/1566700448492-a7b96c99-b600-41b0-9159-e88412f4402b.png#align=left&display=inline&height=329&name=image.png&originHeight=1342&originWidth=1654&size=105794&status=done&width=406">
|
||||
|
||||
如果继续对行列添加维度进行下钻,其实是对轴进行下钻。**排除度量字段不看,就是一个交叉表的下钻过程,如下图所示蓝色框圈住的部分就是一组大的单元格**:
|
||||
|
||||
<img width=629 src="https://cdn.nlark.com/yuque/0/2019/png/201572/1566700520147-c644eae8-8c04-4c7d-82aa-b5e4e1c2e4d8.png#align=left&display=inline&height=467&name=image.png&originHeight=1340&originWidth=1806&size=163645&status=done&width=629">
|
||||
|
||||
由于最后一个字段是度量,因此在叶子结点的展开就不是表格模式的单元格,而是连续的线条了。
|
||||
|
||||
经过上面的总结,我们要意识到,在探索式分析场景对行列的下钻,表格与图表的逻辑是通用的,实现时也要整体考虑。**将轴功能抽离成通用部分来做,表格与图表的区别只是对最后一个字段单元格是离散处理还是连续处理。**
|
||||
|
||||
#### 层系的下钻
|
||||
|
||||
<img width=528 src="https://cdn.nlark.com/yuque/0/2019/png/201572/1566699587535-dd5e11f6-6fd2-42ce-be2b-e01d7dee650a.png#align=left&display=inline&height=300&name=image.png&originHeight=818&originWidth=1438&size=102881&status=done&width=528">
|
||||
|
||||
层系字段下钻与拖多个字段表现一致,但由于存在父子关系,因此在图表上可以展现出 “展开” “收起” 按钮,点击后并不是对图表本身进行操作,而是发送一个事件对 “行” 进行操作,最后通过数据驱动完成展开或收起动作。
|
||||
|
||||
#### 不适合行列的图表
|
||||
|
||||
饼图就不适合行列,因为饼图是根据离散维度进行拆分,扇叶大小可以由一个度量字段决定,因此对饼图来说,行就对应到 “颜色”、列就对应到新增的 “角度” 这个标记:
|
||||
|
||||
<img width=336 src="https://cdn.nlark.com/yuque/0/2019/png/201572/1566704242607-bab5e722-d27c-4ad5-8518-b470fddbf9da.png#align=left&display=inline&height=279&name=image.png&originHeight=558&originWidth=672&size=50344&status=done&width=336">
|
||||
|
||||
#### 没有维度轴的图表
|
||||
|
||||
只有行配置的图形推荐用表格,但柱状图、折线图也可以支持这种情况,只要把横轴忽略即可:
|
||||
|
||||
<img width=300 src="https://cdn.nlark.com/yuque/0/2019/png/201572/1566706245345-61b365db-793a-4d15-9677-b4e332381bda.png#align=left&display=inline&height=312&name=image.png&originHeight=912&originWidth=878&size=53419&status=done&width=300">
|
||||
|
||||
从样式上来看没有横轴,其实这种情况是把所有维度的横轴都聚合后的表现。
|
||||
|
||||
### 连续与离散值
|
||||
|
||||
我们分别看看连续与离散作用于维度和度量时的区别。
|
||||
|
||||
#### 作用于度量
|
||||
|
||||
图表要能适配对连续或离散值的处理。比如对销量来说,如果切换为离散值,则当成字符串展示:
|
||||
|
||||
<img width=632 src="https://cdn.nlark.com/yuque/0/2019/png/201572/1566662092893-32545363-82da-498a-868f-18a59bb41c24.png#align=left&display=inline&height=141&name=image.png&originHeight=472&originWidth=2122&size=63869&status=done&width=632">
|
||||
|
||||
如果将销量切换为连续值,则单元格就要使用线条长度代表值的大小,**即连续性的值要能够产生 “对比感”:**
|
||||
|
||||
<img width=632 src="https://cdn.nlark.com/yuque/0/2019/png/201572/1566662072534-23177d9d-f32c-4aa2-bea4-8fef0ab6e3c2.png#align=left&display=inline&height=161&name=image.png&originHeight=534&originWidth=2106&size=58097&status=done&width=635">
|
||||
|
||||
上图组件是表格,本身适合展示离散值,但可以看到对连续值展示做了适配。对于适合展示连续值的图形,则无法做离散适配:
|
||||
|
||||
<img width=200 src="https://cdn.nlark.com/yuque/0/2019/png/201572/1566703397222-9da012d8-9d13-4378-9d5a-50903f3ba777.png#align=left&display=inline&height=253&name=image.png&originHeight=976&originWidth=772&size=48952&status=done&width=200">
|
||||
|
||||
比如这个柱状图,如果将销量切换为离散,则会自动切换到表格,因为对于双离散值用柱折面饼展示是无意义的。
|
||||
|
||||
#### 作用于维度
|
||||
|
||||
如上图所示,就是维度使用了离散字段的例子,由于维度是离散的,因此使用柱状图展示,因为柱子间也是隔离的。
|
||||
|
||||
**对于连续型字段作用于维度,默认适合散点图,因为散点图的行与列都是度量,适合作为默认推荐:**
|
||||
|
||||
<img width=283 src="https://cdn.nlark.com/yuque/0/2019/png/201572/1566703779092-ce78066e-75ec-4b89-9fc1-f44c135e5b5d.png#align=left&display=inline&height=312&name=image.png&originHeight=1154&originWidth=1048&size=66333&status=done&width=283">
|
||||
|
||||
但能用散点图的就也能用线图, **当维度是连续日期字段时,适合用折线图而不是散点图。**因为日期虽然连续,但 **本身不适合做比较** ,因此作为一种连续型维度展示比较合适;而散点图两个轴都适合连续型度量,因此不适合方日期这种连续型维度字段。
|
||||
|
||||
<img width=406 src="https://cdn.nlark.com/yuque/0/2019/png/201572/1566703754082-6ec2f472-ea42-4003-aab3-c07f47b8a340.png#align=left&display=inline&height=289&name=image.png&originHeight=1166&originWidth=1636&size=96183&status=done&width=406">
|
||||
|
||||
当然也具备将折线图随时切换为散点图的能力,但这种图形没有什么业务价值:
|
||||
|
||||
<img width=406 src="https://cdn.nlark.com/yuque/0/2019/png/201572/1566704003468-7ba3e545-9abf-470f-841d-44dd42e1f754.png#align=left&display=inline&height=292&name=image.png&originHeight=1178&originWidth=1654&size=97442&status=done&width=410">
|
||||
|
||||
因此我们对折线图进行标记:行适合连续型维度字段,对散点图进行标记:行列都适合连续型度量字段,就可以根据配置 **实现推荐图表的功能**。
|
||||
|
||||
### 标记
|
||||
|
||||
除了饼图支持 “角度”、线图支持 “路径” 这些特殊标记外,所有图表都支持下面五种通用标记:“工具提示”、“大小”、“文本”、“颜色”、“详细信息”。
|
||||
|
||||
**工具提示** 比较简单,所有图表都支持鼠标 Hover 后弹出 Tooltip 即可,并且这个 Tooltip 允许自定义和拓展工具提示字段。
|
||||
|
||||
**大小** 则只有折、柱、散三种图支持,因为这三种图分别有可以描述的大小的线条粗细、柱子宽度、圆圈半径。
|
||||
|
||||
**文本** 对应柱折面饼的 Label、对应表格,矩形树状图,地图的 **单元格内容。**
|
||||
|
||||
**颜色、详细信息** 则比较特殊,下面详细说明:
|
||||
|
||||
**拖拽已有字段到详细信息 - 没有任何效果:**
|
||||
|
||||
<img width=476 src="https://cdn.nlark.com/yuque/0/2019/png/201572/1566705683330-21e04dfb-41f7-45dc-92b8-69dab98bf009.png#align=left&display=inline&height=249&name=image.png&originHeight=740&originWidth=1416&size=73008&status=done&width=476">
|
||||
|
||||
因为本身就在看这个字段的详细信息,因此没有效果。
|
||||
|
||||
**但如果拖拽已有字段到颜色,则可以根据数值大小或分类进行按颜色区分:**
|
||||
|
||||
<img width=479 src="https://cdn.nlark.com/yuque/0/2019/png/201572/1566705734424-b2bc735d-35fa-4cf9-8b67-e24e3dccd0f5.png#align=left&display=inline&height=250&name=image.png&originHeight=736&originWidth=1412&size=76772&status=done&width=479">
|
||||
|
||||
等于开启了图表筛选功能,当颜色筛选条件字段是连续型时,出现筛选滑块,**是离散型时,出现图例:**
|
||||
|
||||
<img wdith=479 src="https://cdn.nlark.com/yuque/0/2019/png/201572/1566705795546-6a5e79b7-b32a-4c37-90bc-43926a3a6283.png#align=left&display=inline&height=245&name=image.png&originHeight=720&originWidth=1408&size=80186&status=done&width=480">
|
||||
|
||||
**如果拖拽字段不存在于行和列上,对于度量字段,会根据值进行颜色排序(度量拖拽到详细信息依然没有效果):**
|
||||
|
||||
<img width=479 src="https://cdn.nlark.com/yuque/0/2019/png/201572/1566705874311-ebc4ec5d-021f-4601-9f8d-d5becfa9b1ce.png#align=left&display=inline&height=244&name=image.png&originHeight=720&originWidth=1422&size=79431&status=done&width=482">
|
||||
|
||||
如上图所示,我们可以从长度看利润,从颜色深度看销量。
|
||||
|
||||
**如果拖拽字段不存在于行和列上,且是维度字段,则会先进行维度拆分,之后如果选择的是 “颜色” 标记区域,还会对同一组的拆分标记颜色区分。**
|
||||
|
||||
**由于标记区域对维度的拆分是不分行于列的,因此每个图表会根据自身情况进行合适的拆分。**
|
||||
|
||||
比如条形图如果按某个新维度拆分,则会采取 “堆积柱状图” 的策略:
|
||||
|
||||
<img width=471 src="https://cdn.nlark.com/yuque/0/2019/png/201572/1566706116218-919d2ce7-3fb2-4a32-8624-a3f6cb3122fb.png#align=left&display=inline&height=327&name=image.png&originHeight=986&originWidth=1422&size=117354&status=done&width=471">
|
||||
|
||||
如果是折线图,则会采取 “多条线” 的策略:
|
||||
|
||||
<img width=471 src="https://cdn.nlark.com/yuque/0/2019/png/201572/1566706404581-01866c81-0553-4245-9b56-176bc5708bfe.png#align=left&display=inline&height=319&name=image.png&originHeight=960&originWidth=1416&size=127313&status=done&width=471">
|
||||
|
||||
如果是散点图,只要将拆分后多出来的点打散出来即可。由于散点图的维度拆分不像折线图和柱状图可以分段,因此如果不采用按颜色打散,是无法分辨分组的:
|
||||
|
||||
<img width=471 src="https://cdn.nlark.com/yuque/0/2019/png/201572/1566706443331-c817102a-5134-493f-bddf-04fc73f9548f.png#align=left&display=inline&height=319&name=image.png&originHeight=958&originWidth=1414&size=107140&status=done&width=471">
|
||||
|
||||
之所以说探索式分析的复杂度很高,是因为其可能性公式为:
|
||||
|
||||
**字段 x 离散连续 x 行列 x 行列下钻 x 标记种类 x 筛选 x 图表**
|
||||
|
||||
这种组合的笛卡尔积几乎是无穷无尽的。
|
||||
|
||||
### 轴交互
|
||||
|
||||
图表一些特定功能是隐藏在轴交互里的。拿折线图来说,一共有 5 个拖拽交互位置,如下图所示:
|
||||
|
||||
<img width=439 src="https://cdn.nlark.com/yuque/0/2019/png/201572/1566706982072-996f5f6e-b922-40ec-8066-761857fb2e30.png#align=left&display=inline&height=354&name=image.png&originHeight=1324&originWidth=1642&size=100488&status=done&width=439">
|
||||
|
||||
一般这些区域是用来拖拽度量字段的,所以如果拖拽了维度字段过来,最终会被归类到行列或标记上。
|
||||
|
||||
#### 拖拽维度
|
||||
|
||||
**维度拖拽到底部 1 区域等于替换列字段** :
|
||||
|
||||
<img width=195 src="https://cdn.nlark.com/yuque/0/2019/png/201572/1566707169109-6e38483a-123a-4064-81ff-fd0ed82010ed.png#align=left&display=inline&height=344&name=image.png&originHeight=1010&originWidth=572&size=44840&status=done&width=195">
|
||||
|
||||
**维度拖拽到图表中 4 区域等于拖到了颜色标记** :
|
||||
|
||||
<img width=387 src="https://cdn.nlark.com/yuque/0/2019/png/201572/1566707229005-d6ed84f8-8639-4dc6-96b0-ae2bebe70dc0.png#align=left&display=inline&height=331&name=image.png&originHeight=974&originWidth=1138&size=111933&status=done&width=387">
|
||||
|
||||
**维度拖拽到左侧 3 区域等于对行进行下钻:**
|
||||
|
||||
<img width=406 src="https://cdn.nlark.com/yuque/0/2019/png/201572/1566707279972-a22eb90b-2ebc-4727-911c-10c1d329a06c.png#align=left&display=inline&height=285&name=image.png&originHeight=1012&originWidth=1444&size=106415&status=done&width=406">
|
||||
|
||||
同理拖拽到最上面区域等于对列进行下钻。
|
||||
|
||||
#### 拖拽度量
|
||||
|
||||
让我们看看拖拽度量时的情况。度量能拖拽的范围更多。**比如拖拽到右轴 5 区域,则形成了双轴图:**
|
||||
|
||||
<img width=447 src="https://cdn.nlark.com/yuque/0/2019/png/201572/1566707391460-e528c513-bc22-4b02-9717-e9339b83d419.png#align=left&display=inline&height=360&name=image.png&originHeight=994&originWidth=1234&size=120248&status=done&width=447">
|
||||
|
||||
**拖拽到左侧 2 区域则表示在图中额外增加一个轴:**
|
||||
|
||||
<img width=571 src="https://cdn.nlark.com/yuque/0/2019/png/201572/1566707459746-8af4aa6e-acf4-4600-8fce-32c5fbe3400c.png#align=left&display=inline&height=333&name=image.png&originHeight=1020&originWidth=1748&size=126450&status=done&width=571">
|
||||
|
||||
要注意的是,上图的行显示 “度量值”,这是个特殊的字段,并通过筛选器筛选出拖拽的两个字段 Profit 和 Sales。除了拖拽以外,还可以通过将左侧 “度量值” 字段直接拖入行实现:
|
||||
|
||||
<img width=579 src="https://cdn.nlark.com/yuque/0/2019/png/201572/1566707593546-3670ea98-6e42-4fd2-8438-402c42c318d6.png#align=left&display=inline&height=275&name=image.png&originHeight=1042&originWidth=2190&size=243455&status=done&width=579">
|
||||
|
||||
如上图所示,将度量值放到行,并按度量名称进行颜色标记,就得到了拖拽度量到左侧 2 区域的效果。 **这也说明了所有图表交互最终都是通过映射到配置完成,所有能拖拽的操作都可以通过配置配出来** 。
|
||||
|
||||
对表格来说,能拖拽的区域是行、列、单元格:
|
||||
|
||||
<img width=521 src="https://cdn.nlark.com/yuque/0/2019/png/201572/1566707733747-55ea9f8c-d2c2-4766-b7b1-57d7abb351d2.png#align=left&display=inline&height=132&name=image.png&originHeight=358&originWidth=1416&size=29996&status=done&width=521">
|
||||
|
||||
拖拽到行或列于拖拽到字段配置区域的行或列没有区别,拖拽到单元格等于拖拽到文本标记区域。通过图表于配置区域结合的方式,即便不完全理解配置的人也可以通过将字段拖拽到图表上得到直观的操作感。
|
||||
|
||||
### 点击、圈选交互
|
||||
|
||||
所有图表都支持点击、圈选的方式选中 “点”。对表格来说,点就是单元格:
|
||||
|
||||
<img width=505 src="https://cdn.nlark.com/yuque/0/2019/png/201572/1566707894327-f5b88abb-d84e-4839-9c48-5fbbc9ccd929.png#align=left&display=inline&height=128&name=image.png&originHeight=356&originWidth=1408&size=42932&status=done&width=505">
|
||||
|
||||
对柱状图来说,点就是柱子:
|
||||
|
||||
<img width=307 src="https://cdn.nlark.com/yuque/0/2019/png/201572/1566707923552-680d890f-bc57-4692-b29e-ae787522bfb6.png#align=left&display=inline&height=268&name=image.png&originHeight=972&originWidth=1114&size=85024&status=done&width=307">
|
||||
|
||||
|
||||
对折线图来说,点就是节点:
|
||||
|
||||
<img width=427 src="https://cdn.nlark.com/yuque/0/2019/png/201572/1566707968058-fd8a598a-48fe-45c8-99f9-94c09f686435.png#align=left&display=inline&height=189&name=image.png&originHeight=640&originWidth=1428&size=59096&status=done&width=421">
|
||||
|
||||
对饼图来说,点就是扇叶:
|
||||
|
||||
<img width=374 src="https://cdn.nlark.com/yuque/0/2019/png/201572/1566707997384-1f1e4755-0863-48de-8abc-f56ae9feaad1.png#align=left&display=inline&height=169&name=image.png&originHeight=338&originWidth=748&size=26955&status=done&width=374">
|
||||
|
||||
所有的点被选中后都有基本高亮功能,最重要的是能对选中的点进行保留、排除、局部排序等等。
|
||||
|
||||
**比如我们可以对上图饼图选中的几个扇形区域进行从小到大排序:**
|
||||
|
||||
<img width=316 src="https://cdn.nlark.com/yuque/0/2019/png/201572/1566708115948-0dc7a73e-97bd-4099-b8bc-12dc16f56501.png#align=left&display=inline&height=243&name=image.png&originHeight=568&originWidth=740&size=48022&status=done&width=316">
|
||||
|
||||
我们也可以排除某些点,这个在配置章节有提到过,这个操作最终将转化为新增筛选条件:
|
||||
|
||||
<img width=283 src="https://cdn.nlark.com/yuque/0/2019/png/201572/1566708205782-b74a2d1f-8141-4a05-bea4-8824240a5e67.png#align=left&display=inline&height=280&name=image.png&originHeight=658&originWidth=666&size=52520&status=done&width=283">
|
||||
|
||||
最后,选中状态在单图表中看似只有高亮效果,但是在多图表联动时,高亮的选中区域会组成一个临时的筛选条件,作用于所有相同数据集的图表,并对这些图表的筛选结果做高亮处理。
|
||||
|
||||
# 3. 总结
|
||||
|
||||
理解了探索模型对数据、配置、图表的理解,就能学会探索式思维分析数据,对制作探索式 BI 也有借鉴意义。
|
||||
|
||||
> 讨论地址是:[精读《Tableau 探索式模型》 · Issue #199 · dt-fe/weekly](https://github.com/dt-fe/weekly/issues/199)
|
||||
|
||||
**如果你想参与讨论,请 [点击这里](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,111 @@
|
||||
> 作者:五灵
|
||||
|
||||
本周工作中遇到类似颜色主题的问题,在查资料的时候,看到这个视频,觉得讲得很清楚,而且趣味性丰富,所以想拿出来讲讲这个很有意思的主题。
|
||||
|
||||
视频链接: [CSSconf EU 2018 | Dag-Inge Aas & Ida Aalen: Generating Colors with JS and CSS Custom Properties](https://www.youtube.com/watch?v=zi6L0ZqrKfA)
|
||||
|
||||
## 1. 精读
|
||||
|
||||
### CSS 变量
|
||||
|
||||
CSS 变量及 CSS Variables(Custom Properties),目前几乎都已经被主流浏览器所支持,但是估计还有一部分读者不熟悉这个功能,简单列举一下使用方法:
|
||||
|
||||
```css
|
||||
:root {
|
||||
--bg-color: brown; // 定义颜色变量
|
||||
}
|
||||
.btn {
|
||||
// 直接使用颜色预定义的颜色变量
|
||||
background-color: var(--bg-color);
|
||||
}
|
||||
```
|
||||
|
||||
### Web 内容无障碍指南的对比度
|
||||
|
||||
Web 内容无障碍指南的对比度指的是 W3C 组织发布的 [《Web Content Accessibility Guidelines (WCAG)》](https://www.w3.org/TR/WCAG/#glossary),这个指南中涵盖了让 Web 内容更易于访问的各种建议,其中针对网页的颜色对比度发布了规范。
|
||||
|
||||
在 Chrome 中对于颜色编辑的时候,打开颜色选择器也会看到当前颜色的对比度值(Contrast ratio)。
|
||||
|
||||

|
||||
|
||||
网页颜色的对比度值在 1:1 到 21:1 之间,文本和图像文本的的对比度最小值为 4.5:1,也就是说低于这个值得对比度都不符合标准。 我们看一下列举的几种颜色对比度,对比度越高,也越有利于阅读。对比度越低,对于一些存在视力障碍或色觉缺陷的用户,可能就无法阅读。
|
||||
|
||||

|
||||
|
||||
### 演讲中的颜色解决方案
|
||||
|
||||
演讲在最开始首先讲了挪威的一个法律,不符合 Web 内容无障碍指南的站点在挪威是非法的,所以挪威的 Web 开发者非常注重站点的内容无障碍。
|
||||
|
||||
首先讲了使用 css 变量的方式,支持各种颜色主题的切换。 利用 js 去设置颜色变量,支持主题的颜色切换。
|
||||
|
||||
但是紧接着就提出了问题,如果用户可以随意切换颜色主题背景色,那一些按钮的文字可读性如何去保障呢?如果用户选择了与按钮颜色想接近的背景色,我们又该怎么处理了,紧接着这个演讲给出了根据明度决定按钮文字颜色是黑色还是白色的方案。
|
||||
|
||||
- 根据明度决定是黑色还是白色
|
||||
|
||||
具体代码如下,大致原理是把彩色转为灰度的颜色,有一个著名的心理学公式:`Gray = R*0.299 + G*0.587 + B*0.114`,然后在根据颜色灰度决定使用黑色的主题还是白色的主题。
|
||||
|
||||
```javascript
|
||||
if (red*0.299 + green*0.587 + blue*0.114) > 186 use #000000 else use #ffffff
|
||||
```
|
||||
|
||||

|
||||
|
||||
可读性的问题解决了,但是紧接着又遇到了一个问题,如果用户选取的颜色很浅呢,与背景颜色的对比度小于 4.5,该怎么处理呢。
|
||||
|
||||

|
||||
|
||||
- 寻找对比度更强的颜色,增强可读性
|
||||
|
||||
演讲中给出的解决方法是不断的加深当前用户选择的颜色,循环获取到对比度最高的同色系颜色。代码如下:
|
||||
|
||||

|
||||
|
||||
获取了一个更深的颜色后,通过给按钮加一个外边框的方式,优化整体的可读性。
|
||||
|
||||

|
||||
|
||||
文章最后还介绍了,通过给定一个主题色,获取第二第三主题色的方式,通过将颜色放到 HSL 的颜色轮上,转动 hue 的值 60 度,得到一个新的第二主题色。不过演讲者也没有说清楚为什么要这么做,只是说了这么做是出于经验,觉得这样能够得到一个恰当的主题色盘。
|
||||
|
||||
### 衍生的纯 css 解决方案
|
||||
|
||||
演讲中提供颜色变更的解决方案基本都是基于 JS 计算的,后来有人在 [css-tricks](https://css-tricks.com/switch-font-color-for-different-backgrounds-with-css/) 抛出一篇文章说,这个功能基于 css 就可以完全实现,其实关于颜色的原理都是一致的,只是觉得这个实现更加 magic,但是功能都能够完全满足。比如这篇文章中,关于根据明度决定按钮文字是黑色还是白色的代码如下:
|
||||
|
||||
```css
|
||||
:root {
|
||||
--light: 80;
|
||||
/* 文字颜色变化的临界值 */
|
||||
--threshold: 60;
|
||||
}
|
||||
.btn {
|
||||
/* 会被解析成黑色或者白色 */
|
||||
--switch: calc((var(--light) - var(--threshold)) * -100%);
|
||||
color: hsl(0, 0%, var(--switch));
|
||||
}
|
||||
```
|
||||
|
||||
### 可视化图表对于颜色的应用
|
||||
|
||||
在可视化图表当中,对于颜色的应用要比 Web 要谨慎的多。我们在做 Web 开发的时候,也不妨来看一下可视化图表当中对于颜色应用的一些规范。在可视化图表中,选择的颜色不可以过于随意,每次颜色的变更都是图表信息的改变,都为图表增加了新的数据,图表的每一种颜色也是要表达的信息。列举一些图表中的颜色使用规范,比如:
|
||||
|
||||
1. 不建议使用多种颜色表达同种数据
|
||||
2. 在多条行图表中,不要使用不同的颜色或颜色轮中对立面的颜色。颜色对比过强会使读者无法专心于数据。
|
||||
3. 一般而言,应避免颜色的主体性表现,避免使用具有特殊意义的颜色。比如使用红色和绿色表示销售额的变化。
|
||||
|
||||
当然对于可视化图表来说,并不是遵循了一些色彩使用的准则,就可以得到一个优雅呈现的可视化图表。注重图表呈现的最重要的视觉元素,在视觉信息角度减少用户,减少用户视觉疲劳也很重要。
|
||||
|
||||
## 3. 相关链接
|
||||
|
||||
CSS 前景背景自动配色技术简介: [https://www.zhangxinxu.com/wordpress/2018/11/css-background-color-font-auto-match/](https://www.zhangxinxu.com/wordpress/2018/11/css-background-color-font-auto-match/)<br />
|
||||
纯 css 解决方案:[https://css-tricks.com/switch-font-color-for-different-backgrounds-with-css/](https://css-tricks.com/switch-font-color-for-different-backgrounds-with-css/)<br />
|
||||
获取颜色的 Demo: [https://confrere.com/a11y/test/](https://confrere.com/a11y/test/)<br />
|
||||
颜色色盘推荐的文章:[https://blog.graphiq.com/finding-the-right-color-palettes-for-data-visualizations-fcd4e707a283](https://blog.graphiq.com/finding-the-right-color-palettes-for-data-visualizations-fcd4e707a283)
|
||||
|
||||
> 讨论地址是:[精读《使用 css 变量生成颜色主题》 · Issue #203 · dt-fe/weekly](https://github.com/dt-fe/weekly/issues/203)
|
||||
|
||||
**如果你想参与讨论,请 [点击这里](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,79 @@
|
||||
> 作者:五灵
|
||||
|
||||
## 简介
|
||||
|
||||
其实关于前端深水区的讨论,已经有了很多,也有了很多相关的文章。我也想借这篇关于深水区的讨论文章,讲一下自己对于深水区的理解。
|
||||
原文链接:[技术路线:前端开发已进入深水区](https://www.yuque.com/sxc/front/kvokg4)
|
||||
|
||||
本期精读,[@camsong](https://github.com/camsong)、[@arcthur](https://github.com/arcthur)、[@ascoders](https://github.com/ascoders) 都有贡献观点。
|
||||
|
||||
## 概述
|
||||
|
||||
原文对于深水区的想法,讲的很清楚,还是建议读者去读一下原文。
|
||||
对比 2010 年,整个前端生态已经翻新了好几遍,直到近几年的 Node BFF、IDE Cloud,抑或是客户端 AI,还是 Serverless 的建设,,前端想要深度参与的话,单纯依靠原来的 HTML/CSS/JS 三件套技能也远远不够了。再抛开技术,整个互联网创业生态也重构了好几遍。无论是技术层面还是意识层面,如今的前端开发已经进入深水区。
|
||||
|
||||
- 深水区需要哪些技能
|
||||

|
||||
深水区需要是四个核心能力,分别是:技术、产品、业务和管理能力。
|
||||
- 面对深水压力不需紧张
|
||||
其实何止前端开发,整个技术行业都已步入深水区,只是前端工程师的感知来的晚一些而已。只要把眼光投向深水区,问题就会一个接一个的浮上来,当越来越多问题浮起来的时候,就是你慢慢沉向深水区的时候,这时候不需要太过紧张。
|
||||
|
||||
## 精读
|
||||
|
||||
深水区的理解首先需要达成一致,并不只是一个维度的加深,而是全方位多方面的困难同时加击,压强升高、光线减少、温度剧变等等。
|
||||
|
||||
对应到文中总结的解法就是需要『技术创新、流程优化、团队合作、影响大盘、驱动业务、商业决策和团队管理』。但你展开想一下,把这个角色换成后端、无线端、甚至是 UED,是不是也能完美匹配。所以这些能力应该是技术人员发展到一定程度面临的普遍问题而不仅仅是前端。
|
||||
|
||||
但这些能力是否有个更好的概括?当然有,就是明确一个方向并带领一群人完成目标并实线商业价值。这其实就是商业或者说业务的整个运作过程。
|
||||
|
||||
这其实也在抛一个命题,前端发展到一定程度就一定要转业务吗?
|
||||
是也不是。当然要转,但并不是全转。全转业务你过去的积累有什么用?不转业务单纯前端能发挥的影响力就会受限。所以答案是利用前端技术优势同时补充业务能力推动商业流程。
|
||||
|
||||
所以此文并不是严格上讲前端技术的深水区,或者作者肯定认为他能接触的前端技术已经到瓶颈,且没有想到突破口。
|
||||
|
||||
怎么去定义深水区,@流形 认为是需要建立技术壁垒或学术壁垒。当我们看待一向技术,如果在投入一到两年就可以对齐,那么显然技术本身的深度是可观的,如果是十年才能对齐,这时候除了会影响经济或政治外,不会有人会去重做,只能使用。用另一个类似的概念反摩尔定律来对应深水区说,每隔两年,技术不能显著带来效能的成倍提升。
|
||||
|
||||
### 深水区值得关注的方向
|
||||
|
||||
#### 业务领导力
|
||||
|
||||
也就是原文提到的 “技术创新、流程优化、团队合作、影响大盘、驱动业务、商业决策、团队管理” 等能力,一个拥有领导力的人发挥的价值远超自身孤立的价值。
|
||||
|
||||
#### 业务价值
|
||||
|
||||
发挥业务价值是技术人的最终目标,比如数据库技术想发挥业务价值,就要做到高效、稳定,价值越大往往技术难度就越大。
|
||||
|
||||
值得庆幸的是,前端的业务价值与技术难度往往不成正比,有时候将客户的业务场景固化成一套模版,整合起来赋能给更多客户,这等于将商业模型作为能力赋予了其他客户,但本身并没有用到一些高级技术。前端能做的不仅是内部提效和外部体验,因为前端是人机交互的入口,才有机会将业务思考打包到代码中,直接透出给客户。
|
||||
|
||||
#### 端技术的发展
|
||||
|
||||
1. 数字孪生。那么在端上的仿真能力需要大幅提高,那么结合模型自动生成,不同物体的建模能力等都是很大挑战
|
||||
2. 虚拟实现。这点上就不赘述,从 FB 重点发展 Oculus,微软发展 HoloLens 可以看到这个趋势,从互动的未来来看,这不是终局,但是最适合今天要突破的技术。
|
||||
3. 可视分析。数据在人类面前还是过分难懂,结合数据的分析系统在各行各业正在渗透,端上结合可视化的能力就显得非常重要。
|
||||
4. 更多的,像边缘计算,前端安全等领域都是非常深入的领域。
|
||||
这些问题,已经不是一年就能完全突破的,需要 3-5 年,甚至 10 年时间。
|
||||
|
||||
#### 前端深入体系
|
||||
|
||||
1. 但对于我所处的大数据环境来说,确实接触了前端技术深水区。来源于端计算能力 + 网络基建 + 大数据的爆炸式增长。
|
||||
编辑器:复杂的开发离不开代码,前端们一直孜孜不倦的把 IDE 引入 web,VS Code 做了很成功的尝试但还是需要一层壳套着。且对于大数据处理这样的领域,需要定制的能力远超过通用的 Manaco editor 等能提供。
|
||||
2. 表格类数据处理能力:比尔盖茨最引以为豪的微软软件是 Excel。你永远不知道 Excel 有多少种酷的用法来解决用户问题。能否把 Excel 引入到 web?同时对数百万条数据做交叉分析,这对性能和架构都有很大的挑战。
|
||||
3. 可视化数据展现:大数据的一个典型特征就是价值稀疏性,如何把蕴含的价值展现出来,需要了解图形学、统计学、交互色彩等各种能力。大学老师教的内容终于能派生用场了。
|
||||
|
||||
## 总结
|
||||
|
||||
在局部领域前端已经有可能深入,当然前端技能上说这些也不能用 HTML, CSS, JS 来解决,需要开发者有深入学科的背景。但今天前端面向还是产品功能的需要,在端上更强调的还是产品功能为主。我们做一款复杂产品,更多还会在工程上纠结。如果没在功能的深入性上思考更多,以对应真正技术发展,那么深水区还远。
|
||||
|
||||
正如前面所说,深水区会压强升高、光线减少、温度剧变,需要自己发光发热和更多的坚持。
|
||||
|
||||
跨过深水区,让其他人处在浅水区就能做事,这或许就是你走出深水区的标志。就像 Alan Perlis 说的一句话『简单不先于复杂,而是在复杂之后』,也许未来看来你今天挣扎的深水区只是个小泥坑。
|
||||
|
||||
> 讨论地址是:[精读《前端深水区》 · Issue #193 · dt-fe/weekly](https://github.com/dt-fe/weekly/issues/193)
|
||||
|
||||
**如果你想参与讨论,请 [点击这里](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,290 @@
|
||||
## 简介
|
||||
|
||||
React 16.8 于 2019.2 正式发布,这是一个能提升代码质量和开发效率的特性,笔者就抛砖引玉先列出一些实践点,希望得到大家进一步讨论。
|
||||
|
||||
然而需要理解的是,没有一个完美的最佳实践规范,对一个高效团队来说,稳定的规范比合理的规范更重要,因此这套方案只是最佳实践之一。
|
||||
|
||||
## 精读
|
||||
|
||||
### 环境要求
|
||||
|
||||
- 拥有较为稳定且理解函数式编程的前端团队。
|
||||
- 开启 ESLint 插件:[eslint-plugin-react-hooks](https://www.npmjs.com/package/eslint-plugin-react-hooks)。
|
||||
|
||||
### 组件定义
|
||||
|
||||
Function Component 采用 `const` + 箭头函数方式定义:
|
||||
|
||||
```tsx
|
||||
const App: React.FC<{ title: string }> = ({ title }) => {
|
||||
return React.useMemo(() => <div>{title}</div>, [title]);
|
||||
};
|
||||
|
||||
App.defaultProps = {
|
||||
title: 'Function Component'
|
||||
}
|
||||
```
|
||||
|
||||
上面的例子包含了:
|
||||
|
||||
1. 用 `React.FC` 申明 Function Component 组件类型与定义 Props 参数类型。
|
||||
2. 用 `React.useMemo` 优化渲染性能。
|
||||
3. 用 `App.defaultProps` 定义 Props 的默认值。
|
||||
|
||||
#### FAQ
|
||||
|
||||
> 为什么不用 React.memo?
|
||||
|
||||
推荐使用 `React.useMemo` 而不是 `React.memo`,因为在组件通信时存在 `React.useContext` 的用法,这种用法会使所有用到的组件重渲染,只有 `React.useMemo` 能处理这种场景的按需渲染。
|
||||
|
||||
> 没有性能问题的组件也要使用 useMemo 吗?
|
||||
|
||||
要,考虑未来维护这个组件的时候,随时可能会通过 `useContext` 等注入一些数据,这时候谁会想起来添加 `useMemo` 呢?
|
||||
|
||||
> 为什么不用解构方式代替 defaultProps?
|
||||
|
||||
虽然解构方式书写 `defaultProps` 更优雅,但存在一个硬伤:对于对象类型每次 Rerender 时引用都会变化,这会带来性能问题,因此不要这么做。
|
||||
|
||||
### 局部状态
|
||||
|
||||
局部状态有三种,根据常用程度依次排列: `useState` `useRef` `useReducer` 。
|
||||
|
||||
#### useState
|
||||
|
||||
```tsx
|
||||
const [hide, setHide] = React.useState(false);
|
||||
const [name, setName] = React.useState('BI');
|
||||
```
|
||||
|
||||
状态函数名要表意,尽量聚集在一起申明,方便查阅。
|
||||
|
||||
#### useRef
|
||||
|
||||
```tsx
|
||||
const dom = React.useRef(null);
|
||||
```
|
||||
|
||||
`useRef` 尽量少用,大量 Mutable 的数据会影响代码的可维护性。
|
||||
|
||||
但对于不需重复初始化的对象推荐使用 `useRef` 存储,比如 `new G2()` 。
|
||||
|
||||
#### useReducer
|
||||
|
||||
局部状态不推荐使用 `useReducer` ,会导致函数内部状态过于复杂,难以阅读。 `useReducer` 建议在多组件间通信时,结合 `useContext` 一起使用。
|
||||
|
||||
#### FAQ
|
||||
|
||||
> 可以在函数内直接申明普通常量或普通函数吗?
|
||||
|
||||
不可以,Function Component 每次渲染都会重新执行,常量推荐放到函数外层避免性能问题,函数推荐使用 `useCallback` 申明。
|
||||
|
||||
### 函数
|
||||
|
||||
所有 Function Component 内函数必须用 `React.useCallback` 包裹,以保证准确性与性能。
|
||||
|
||||
```tsx
|
||||
const [hide, setHide] = React.useState(false);
|
||||
|
||||
const handleClick = React.useCallback(() => {
|
||||
setHide(isHide => !isHide)
|
||||
}, [])
|
||||
```
|
||||
|
||||
`useCallback` 第二个参数必须写,[eslint-plugin-react-hooks](https://www.npmjs.com/package/eslint-plugin-react-hooks) 插件会自动填写依赖项。
|
||||
|
||||
### 发请求
|
||||
|
||||
发请求分为操作型发请求与渲染型发请求。
|
||||
|
||||
#### 操作型发请求
|
||||
|
||||
操作型发请求,作为回调函数:
|
||||
|
||||
```tsx
|
||||
return React.useMemo(() => {
|
||||
return (
|
||||
<div onClick={requestService.addList} />
|
||||
)
|
||||
}, [requestService.addList])
|
||||
```
|
||||
|
||||
#### 渲染型发请求
|
||||
|
||||
渲染型发请求在 `useAsync` 中进行,比如刷新列表页,获取基础信息,或者进行搜索, **都可以抽象为依赖了某些变量,当这些变量变化时要重新取数** :
|
||||
|
||||
```tsx
|
||||
const { loading, error, value } = useAsync(async () => {
|
||||
return requestService.freshList(id);
|
||||
}, [requestService.freshList, id]);
|
||||
```
|
||||
|
||||
### 组件间通信
|
||||
|
||||
简单的组件间通信使用透传 Props 变量的方式,而频繁组件间通信使用 `React.useContext` 。
|
||||
|
||||
以一个复杂大组件为例,如果组件内部拆分了很多模块, **但需要共享很多内部状态** ,最佳实践如下:
|
||||
|
||||
#### 定义组件内共享状态 - store.ts
|
||||
|
||||
```tsx
|
||||
export const StoreContext = React.createContext<{
|
||||
state: State;
|
||||
dispatch: React.Dispatch<Action>;
|
||||
}>(null)
|
||||
|
||||
export interface State {};
|
||||
|
||||
export interface Action { type: 'xxx' } | { type: 'yyy' };
|
||||
|
||||
export const initState: State = {};
|
||||
|
||||
export const reducer: React.Reducer<State, Action> = (state, action) => {
|
||||
switch (action.type) {
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
#### 根组件注入共享状态 - main.ts
|
||||
|
||||
```tsx
|
||||
import { StoreContext, reducer, initState } from './store'
|
||||
|
||||
const AppProvider: React.FC = props => {
|
||||
const [state, dispatch] = React.useReducer(reducer, initState);
|
||||
|
||||
return React.useMemo(() => (
|
||||
<StoreContext.Provider value={{ state, dispatch }}>
|
||||
<App />
|
||||
</StoreContext.Provider>
|
||||
), [state, dispatch])
|
||||
};
|
||||
```
|
||||
|
||||
#### 任意子组件访问/修改共享状态 - child.ts
|
||||
|
||||
```tsx
|
||||
import { StoreContext } from './store'
|
||||
|
||||
const app: React.FC = () => {
|
||||
const { state, dispatch } = React.useContext(StoreContext);
|
||||
|
||||
return React.useMemo(() => (
|
||||
<div>{state.name}</div>
|
||||
), [state.name])
|
||||
};
|
||||
```
|
||||
|
||||
如上解决了 **多个联系紧密组件模块间便捷共享状态的问题** ,但有时也会遇到需要共享根组件 Props 的问题,**这种不可修改的状态不适合一并塞到 `StoreContext` 里**,我们新建一个 `PropsContext` 注入根组件的 Props:
|
||||
|
||||
```tsx
|
||||
const PropsContext = React.createContext<Props>(null)
|
||||
|
||||
const AppProvider: React.FC<Props> = props => {
|
||||
return React.useMemo(() => (
|
||||
<PropsContext.Provider value={props}>
|
||||
<App />
|
||||
</PropsContext.Provider>
|
||||
), [props])
|
||||
};
|
||||
```
|
||||
|
||||
#### 结合项目数据流
|
||||
|
||||
参考 [react-redux hooks](https://github.com/reduxjs/react-redux/blob/master/docs/api/hooks.md)。
|
||||
|
||||
### debounce 优化
|
||||
|
||||
比如当输入框频繁输入时,为了保证页面流畅,我们会选择在 `onChange` 时进行 `debounce` 。然而在 Function Component 领域中,我们有更优雅的方式实现。
|
||||
|
||||
> 其实在 Input 组件 `onChange` 使用 `debounce` 有一个问题,就是当 Input 组件 **受控** 时, `debounce` 的值不能及时回填,导致甚至无法输入的问题。
|
||||
|
||||
我们站在 Function Component 思维模式下思考这个问题:
|
||||
|
||||
1. React [scheduling](https://github.com/dt-fe/weekly/blob/v2/099.%E7%B2%BE%E8%AF%BB%E3%80%8AScheduling%20in%20React%E3%80%8B.md) 通过智能调度系统优化渲染优先级,我们其实不用担心频繁变更状态会导致性能问题。
|
||||
2. 如果联动一个文本还觉得慢吗? `onChange` 本不慢,大部分使用值的组件也不慢,没有必要从 `onChange` 源头开始就 `debounce` 。
|
||||
3. 找到渲染性能最慢的组件(比如 iframe 组件),**对一些频繁导致其渲染的入参进行 `useDebounce`** 。
|
||||
|
||||
下面是一个性能很差的组件,引用了变化频繁的 `text` (这个 `text` 可能是 `onChange` 触发改变的),我们利用 `useDebounce` 将其变更的频率慢下来即可:
|
||||
|
||||
```typescript
|
||||
const App: React.FC = ({ text }) => {
|
||||
// 无论 text 变化多快,textDebounce 最多 1 秒修改一次
|
||||
const textDebounce = useDebounce(text, 1000)
|
||||
|
||||
return useMemo(() => {
|
||||
// 使用 textDebounce,但渲染速度很慢的一堆代码
|
||||
}, [textDebounce])
|
||||
};
|
||||
```
|
||||
|
||||
使用 `textDebounce` 替代 `text` 可以将渲染频率控制在我们指定的范围内。
|
||||
|
||||
### useEffect 注意事项
|
||||
|
||||
事实上,`useEffect` 是最为怪异的 Hook,也是最难使用的 Hook。比如下面这段代码:
|
||||
|
||||
```tsx
|
||||
useEffect(() => {
|
||||
props.onChange(props.id)
|
||||
}, [props.onChange, props.id])
|
||||
```
|
||||
|
||||
如果 `id` 变化,则调用 `onChange`。但如果上层代码并没有对 `onChange` 进行合理的封装,导致每次刷新引用都会变动,则会产生严重后果。我们假设父级代码是这么写的:
|
||||
|
||||
```tsx
|
||||
class App {
|
||||
render() {
|
||||
return <Child id={this.state.id} onChange={id => this.setState({ id })} />
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
这样会导致死循环。虽然看上去 `<App>` 只是将更新 id 的时机交给了子元素 `<Child>`,但由于 `onChange` 函数在每次渲染时都会重新生成,因此引用总是在变化,就会出现一个无限死循环:
|
||||
|
||||
新 `onChange` -> `useEffect` 依赖更新 -> `props.onChange` -> 父级重渲染 -> 新 `onChange`...
|
||||
|
||||
想要阻止这个循环的发生,只要改为 `onChange={this.handleChange}` 即可,**`useEffect` 对外部依赖苛刻的要求,只有在整体项目都注意保持正确的引用时才能优雅生效。**
|
||||
|
||||
然而被调用处代码怎么写并不受我们控制,这就导致了不规范的父元素可能导致 React Hooks 产生死循环。
|
||||
|
||||
因此在使用 `useEffect` 时要注意调试上下文,注意父级传递的参数引用是否正确,如果引用传递不正确,有两种做法:
|
||||
|
||||
1. 使用 [useDeepCompareEffect](https://github.com/streamich/react-use/blob/master/docs/useDeepCompareEffect.md) 对依赖进行深比较。
|
||||
2. 使用 `useCurrentValue` 对引用总是变化的 props 进行包装:
|
||||
|
||||
```tsx
|
||||
function useCurrentValue<T>(value: T): React.RefObject<T> {
|
||||
const ref = React.useRef(null);
|
||||
ref.current = value;
|
||||
return ref;
|
||||
}
|
||||
|
||||
const App: React.FC = ({ onChange }) => {
|
||||
const onChangeCurrent = useCurrentValue(onChange)
|
||||
};
|
||||
```
|
||||
|
||||
`onChangeCurrent` 的引用保持不变,但每次都会指向最新的 `props.onChange`,从而可以规避这个问题。
|
||||
|
||||
## 总结
|
||||
|
||||
如果还有补充,欢迎在文末讨论。
|
||||
|
||||
如需了解 Function Component 或 Hooks 基础用法,可以参考往期精读:
|
||||
|
||||
- [精读《React Hooks》](https://github.com/dt-fe/weekly/blob/v2/079.%E7%B2%BE%E8%AF%BB%E3%80%8AReact%20Hooks%E3%80%8B.md)
|
||||
- [精读《怎么用 React Hooks 造轮子》](https://github.com/dt-fe/weekly/blob/v2/080.%E7%B2%BE%E8%AF%BB%E3%80%8A%E6%80%8E%E4%B9%88%E7%94%A8%20React%20Hooks%20%E9%80%A0%E8%BD%AE%E5%AD%90%E3%80%8B.md)
|
||||
- [精读《useEffect 完全指南》](https://github.com/dt-fe/weekly/blob/v2/096.%E7%B2%BE%E8%AF%BB%E3%80%8AuseEffect%20%E5%AE%8C%E5%85%A8%E6%8C%87%E5%8D%97%E3%80%8B.md)
|
||||
- [精读《Function Component 入门》](https://github.com/dt-fe/weekly/blob/v2/104.精读《Function%20Component%20入门》.md)
|
||||
|
||||
> 讨论地址是:[精读《React Hooks 最佳实践》 · Issue #202 · dt-fe/weekly](https://github.com/dt-fe/weekly/issues/202)
|
||||
|
||||
**如果你想参与讨论,请 [点击这里](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,175 @@
|
||||
## 简介
|
||||
|
||||
商业智能(Business Intelligence)简称 BI,即通过数据挖掘与分析找到商业洞察,助力商业成功。
|
||||
|
||||
一个完整的 BI 链路包含数据采集、数据清洗、数据挖掘、数据展现,其本质是对数据进行多维分析。前端的主要工作在数据展现环节,由于展示方式繁多、分析模型复杂且数据量大,前端环节的复杂度很高。
|
||||
|
||||
在 BI 做前端非常有挑战,开发者需要充分理解数据概念,而本身复杂度较高的可视化建站也只是 BI 的基础能力,想要建设 BI 的上层能力,比如探索式分析和数据洞察,都需要在前后端引入更复杂的计算模型。
|
||||
|
||||
本文作为一个引子,简单介绍笔者做 BI 的经验,后面如果有机会再写一个系列文章对细节进行阐述。
|
||||
|
||||
## 精读
|
||||
|
||||
国内目前处于 BI 1.0 阶段,也就是报表阶段,因此笔者将阐述这个阶段 BI 的核心开发概念。
|
||||
|
||||
> BI 2.0 探索式分析阶段是国内数据分析最前沿领域,这部分等开发完成后再分享。
|
||||
|
||||
BI 1.0 阶段的核心概念包括 **数据集、渲染引擎、数据模型、可视化** 这四个技术模块。
|
||||
|
||||
### 数据集
|
||||
|
||||
数据集即数据的集合,在 BI 领域更多指一种标准化的数据结构。
|
||||
|
||||
任何数据都可以封装成数据集,比如 txt 文本、excel、mysql 数据库等等。
|
||||
|
||||
数据集的基本形态是二维表格,列头表示字段,每一行就是一份数据,数据展示就是通过对这些数据字段进行多维度分析。
|
||||
|
||||
#### 数据集导入
|
||||
|
||||
一般来说数据集导入有两种方式,分别是本地文件上传与数据库链接。本地文件上传又分为多种文件类型处理,比如对 excel 的解析,可能还包括数据清洗;数据库链接分析可视化导入与 SQL 输入。
|
||||
|
||||
可视化导入需要提前对数据库进行结构分析,绘制出表结构与字段结构,不用理解 SQL 也可以进行可视化操作。
|
||||
|
||||
SQL 输入可以利用 [monaco-editor](https://github.com/microsoft/monaco-editor) 等 web 代码编辑器作为输入框,最好能结合智能提示提高 sql 编写效率。sql 智能提示可以参考往期精读 [精读《手写 SQL 编译器 - 智能提示》](https://github.com/dt-fe/weekly/blob/v2/085.%E7%B2%BE%E8%AF%BB%E3%80%8A%E6%89%8B%E5%86%99%20SQL%20%E7%BC%96%E8%AF%91%E5%99%A8%20-%20%E6%99%BA%E8%83%BD%E6%8F%90%E7%A4%BA%E3%80%8B.md)。
|
||||
|
||||
#### 数据集建模
|
||||
|
||||
数据集建模一般包含 **维度度量建模、字段配置、层系建模**。
|
||||
|
||||
维度度量建模需要智能分析出字段属于维度还是度量,一般会结合字段实际的值或者字段名来智能判断字段类型,如果数据库信息中已存储了字段类型,就可以 100% 准确归类。
|
||||
|
||||
字段配置即对字段进行增删或修改,还可以新增聚合字段或对比字段。
|
||||
|
||||
聚合字段是指将一个字段表达式封装为一个新字段,这里也会用到一个简单的 sql 编辑器,只需要支持四则运算、字段提示、以及一些基本函数的组合即可。
|
||||
|
||||
对比字段是指新增的字段是基于已有字段在某个时间周期内的对比,比如对 UV 字段的年同比就可以封装为一个对比字段。对比字段在前端技术上没有什么难度,仅需理解概念即可。
|
||||
|
||||
### 渲染引擎
|
||||
|
||||
渲染引擎包括了对报表进行编辑与渲染的引擎,理论上可以合二为一。
|
||||
|
||||
渲染引擎的重要模块包括:画布拖拽、组件编辑、事件中心。
|
||||
|
||||
画布拖拽其实包含了组件自定义开发流程,到 CDN 发布、CDN 加载、组件拖拽、画布排版等一系列技术点,每个点展开都有写不完的细节,但好在这套功能属于通用建站基础功能点,本文就不再赘述。
|
||||
|
||||
组件编辑中,基本属性的编辑与属于通用建站领域的表单模型范畴,一般通过 UISchema 来描述通用表单,这块也不再赘述。组件编辑的另一部分就是数据编辑,这部分在后面数据模型章节里详细讲。
|
||||
|
||||
事件中心是渲染引擎部分,此功能在编辑状态需要禁用。这个功能可以实现图表联动、上卷下钻等数据能力。一个通用事件中心一般包括 **事件触发** 与 **事件响应** 两部分,基本结构如下:
|
||||
|
||||
```typescript
|
||||
interface Event {
|
||||
trigger:
|
||||
| {
|
||||
type: 'callback';
|
||||
callbackName: string;
|
||||
}
|
||||
| {
|
||||
type: 'listener';
|
||||
eventName: string;
|
||||
}
|
||||
| {
|
||||
type: 'system';
|
||||
name: string;
|
||||
};
|
||||
|
||||
action:
|
||||
| {
|
||||
type: 'dispatch';
|
||||
eventName: string;
|
||||
}
|
||||
| {
|
||||
type: 'jumpUrl';
|
||||
url: string;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`trigger` 即事件触发,包括基本的系统事件 `system`,比如定时器或者初始化自动触发;组件的回调 `callback` 比如当按钮被点击时;事件监听 `listener` 比如另一个事件被触发时,这个事件可能来自于 `action`。
|
||||
|
||||
`action` 即事件响应,包括基本的事件触发 `dispatch`,可以触发其他事件,可以构成一个事件链路;其他的 `action` 就是数据相关,可以用来做条件联动、字段联动、数据集联动等等,因为实现各异这里不做介绍。
|
||||
|
||||
事件机制还需要支持值传递,即事件触发源的值可以传递到事件响应方。值传递可以在触发源内部进行,比如当触发源是回调函数时,函数参数就自然作为值传递过去,触发源通过 `...args` 方式接收。
|
||||
|
||||
#### 数据钻取
|
||||
|
||||
配置了层系的字段都可以进行数据钻取。层系可以在数据集配置,也可以在报表编辑页配置,可以理解为一个顺序有关的文件夹,将文件夹作为字段使用时,默认生效的是第一个子元素,之后可以按照顺序分别进行下钻。
|
||||
|
||||
比如 “地区” 层系包含了国家、省、市、区,那么就可以按照这个层级进行数据上卷下钻。
|
||||
|
||||
如果一个字段是层系字段,图表需要有对应的操作区域进行上卷下钻,数据编辑区域也可以进行同样操作。数据钻取的计算过程不在图表内部处理,而是触发一个状态后,由渲染引擎将这个层系字段实例状态改为下钻到第 N 层,并且每下钻一次就多拿到一列的数据,由图表组件进行下钻展示。
|
||||
|
||||
一般来说下钻后数据仍是全量的,有时候为了避免数据量过大,比如在柱状图点击某个柱子进行下钻,只想看这个柱子下钻后的数据:比如 2017、2018、2019 年三年的数据,下钻到月后数据量是 3 x 12 = 36 条,但如果仅在 2019 年进行下钻,只想看 2019 年的 12 条数据,可以转化为下钻 + 筛选条件的模式:全局下钻展开后 36 条,在 2019 年上点击下钻后,增加一个筛选条件(年 = 2019),这样就达到了效果,整个流程对图表组件是无感知的。
|
||||
|
||||
### 数据模型
|
||||
|
||||
与通用表单模型 UISchema 相对应,数据模型笔者称之为 CubeSchema,因为 BI 领域对数据的多维处理模型成为 Cube 立方体,数据配置即表示如何对这个立方体进行查询,因此其配置表单成为 CubeSchema。
|
||||
|
||||
不管是探索式分析还是 BI 1.0 的报表阶段,数据模型的基本概念是通用的(探索式分析固定了行列,且增加了标记):将字段放置到不同的区域,这些区域的划分方式可以按照功能:横轴、纵轴;按照概念:维度、度量;按照探索分析思路:固化为行、列等等。
|
||||
|
||||
这块可能涉及到的技术点有:拖拽、批量选择+拖拽、双击后按照维度度量自动添加、图表切换后区域字段自动迁移、对字段拖拽的系列配置:限制数量、限制类型、限制数据集、是否重复等等。
|
||||
|
||||
拖拽可以用 [react-beautiful-dnd](https://github.com/atlassian/react-beautiful-dnd) 等库,与渲染引擎拖拽方案基本类似,遇到有层系的数据集还需支持嵌套层级的拖拽。
|
||||
|
||||
图表切换后字段迁移,可以将每个拖拽区域设置若干类型:
|
||||
|
||||
```json
|
||||
{
|
||||
"dataType": ["dimension"]
|
||||
}
|
||||
```
|
||||
|
||||
这样在切换后,维度类型的字段可以自动迁移到维度类型区域,如果对应区域字段数量达到了 `limit` 限制,就继续填充到下一个区域,直到字段用尽或区域填充完为止。
|
||||
|
||||
如果在探索式分析场景里,需要提前对字段进行维度度量建模,在切换时按照图表情况进行相应的处理。比如折线图切换到表格的情况:折线图是天然一个维度(主轴) + N 个度量的场景,表格是天然两个维度(行、列)+ 1 个度量的场景(也可以支持多个,对单元格进行再切分即可),那么从折线图切换到表格时,度量就会落到标记的文本区域;如果从拥有行和列的表格切换到柱状图(之所以无法切换到折线图,是因为表格的度量值一般是离散的,而折线图度量值一般是连续的),表格的行与列的字段会落到柱状图的维度轴,表现效果是对维度轴进行下钻。
|
||||
|
||||
> [精读《Tableau 探索式模型》](https://github.com/dt-fe/weekly/blob/v2/117.%E7%B2%BE%E8%AF%BB%E3%80%8ATableau%20%E6%8E%A2%E7%B4%A2%E5%BC%8F%E6%A8%A1%E5%9E%8B%E3%80%8B.md) 了解更多探索式分析。
|
||||
|
||||
数据模型还包括数据分析相关配置,比如设置对比字段,或者均值线等分析功能。这些数据计算工作放在后端,前端需要将配置项整理到取数接口中,并按照数据驱动的方式展现。
|
||||
|
||||
对于对比字段等 “拓展字段” 的分析功能,可以拓展通用取数接口,图表组件无感知,相当于多添加了几个隐藏字段;去特殊值等对标准数据进行操作的情况图表组件也无需感知。
|
||||
|
||||
聚类、均值线等需要图表组件额外展示的部分抽象为一套固定的数据格式透传给图表组件,由图表组件自行处理。
|
||||
|
||||
可以看出来,都是取数 + 展示,普通的前端业务与 BI 业务开发的区别:
|
||||
|
||||
普通前端业务是以业务逻辑为核心的,根据业务需要确定接口格式;BI 业务是以数据为核心的,围绕数据计算模型确定一套固定的接口格式,取数不依赖组件,所有组件对标准数据都有对应的展现。
|
||||
|
||||
### 可视化
|
||||
|
||||
与普通可视化组件不同,BI 可视化组件需要对接 CubeSchema 模型,同时还要支持 **大数据性能优化、边界数据展示优化、交互响应**。
|
||||
|
||||
对接 CubeSchema 即统一对接二维表格的数据,大部分组件都是二维以上结构展示,因此对接起来并不困难,有一些一维数据结构的组件比如单指标块就要舍弃其中的某一维,需要确定一套规则。
|
||||
|
||||
二维以上部分是较为通用的,虽然计算模型是基于 Cube N 维的,但组件可以通过标准轴进行多维度展开,或者说下钻来实现类似效果。对于折线图来说,轴的含义有限,可以用分面的方式展示多维数据。当然也有一些组件只适合展示特定维度数量的数据。
|
||||
|
||||
#### 大数据性能优化
|
||||
|
||||
可视化组件特别需要关注性能优化,因为 BI 查询出的数据量可能非常大,特别是多层下钻或基于地理的数据。
|
||||
|
||||
技术手段包括 GPU 渲染、缓存 canvas、多线程运算等,业务手段包括数据抽样、按需渲染可视区域、限制数据条数等等。
|
||||
|
||||
#### 边界数据展示优化
|
||||
|
||||
永远不知道数据集会给出怎样的数据,因此 BI 边界情况特别多,可能点非常密集,也可能丢失一些数据导致渲染异常。图表组件需要利用避让算法将密集的数据打散或着色,目的是为了容易阅读,对于丢失的异常数据也要有保护性的补全机制。
|
||||
|
||||
#### 交互响应
|
||||
|
||||
包括上卷下钻、点选、圈选、高亮等交互操作,这些操作反馈到渲染引擎导致数据变化并将新的数据灌入图表组件。
|
||||
|
||||
业务逻辑上这些交互操作并不复杂,难点在使用的可视化库是否有这个能力,以及如何统一交互行为。
|
||||
|
||||
## 总结
|
||||
|
||||
BI 领域的四大方向:数据集、渲染引擎、数据模型与可视化都有许多可以做深的技术点,每一块都需要深入沉淀几年技术经验才能做好,需要大量优秀人才通力协作才有可能做好。
|
||||
|
||||
目前我们在阿里数据中台正在打造一款面向未来的优秀 BI 工具,如果 BI 领域让你觉得有挑战,随时欢迎你的加入,联系邮箱:ziyi.hzy@alibaba-inc.com
|
||||
|
||||
> 讨论地址是:[精读《前端与 BI》 · Issue #208 · dt-fe/weekly](https://github.com/dt-fe/weekly/issues/208)
|
||||
|
||||
**如果你想参与讨论,请 [点击这里](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,28 @@
|
||||
{
|
||||
"name": "weekly",
|
||||
"version": "1.0.0",
|
||||
"description": "前端界的好文精读,每周更新!",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/dt-fe/weekly.git"
|
||||
},
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"bugs": {
|
||||
"url": "https://github.com/dt-fe/weekly/issues"
|
||||
},
|
||||
"homepage": "https://github.com/dt-fe/weekly#readme",
|
||||
"dependencies": {
|
||||
"husky": "^3.0.4",
|
||||
"lint-md-cli": "^0.1.1"
|
||||
},
|
||||
"husky": {
|
||||
"hooks": {
|
||||
"pre-commit": "npx lint-md ./"
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user