Giter VIP home page Giter VIP logo

hax.github.com's Introduction

Homepage for hax.

hax.github.com's People

Contributors

hax avatar jacksky007 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

hax.github.com's Issues

Express的缺点

http://strongloop.com/strongblog/bypassing-express-view-rendering-for-speed-and-modularity/ (中译:http://idlelife.org/archives/810

  • 强制视图源文件必须在特定目录(如views)下,不是好的代码组织方式。这跟我总体上反对基于文件类型的目录结构(如常见的 /images、/stylesheets、/javascripts)是完全一致的。实际上haojing项目的template类已经做了改进,使得view的查找路径能先从controller同级目录开始逐级向上查找views目录,从而支持项目的模块化。
  • 不支持stream,这极大的限制了性能优化的可能(比如bigpipe),削弱了模板系统的能力。(我开始做jedi时就因此对express不太满意。)

各种可能的源文本(literal)

Number

42
17.34
0.4
64_000

不允许 .4 的写法,数字间的下划线(或dash?)忽略

Boolean

true/false
yes/no
on/off

T/#F

String

Tuple

Record

{x: 1, y: 2}
{[x]:1}
{x, y}
{+x, -y}

RegExp

/abc/g
///
a
b+
c*
///g

Ranges

1 to 5 / 1 ... 5
1 til 5 / 1 ..< 5
1 to 10 by 2

问题:
0. by 是否有足够用例

How you interpret this code? -- test of a imaginary JavaScript code

Note, this is NOT a valid code as current JavaScript syntax. So the choices below are not correct or wrong, please just choose what your intuition tell.

注意,下面的代码目前并不是合法的代码。所以后面的选择没有对错之分,请按照你的直觉选择。

var foo = 0;

class Whatever {
   var bar;

   constructor(v) {
      bar = v;
   }
   
   test() {
      ++foo;
      ++bar;
      return `${foo}:${bar}`;
   }
}

var x = new Whatever(10);
x.test(); // output 1:11
var y = new Whatever(20);
y.test(); // output 2:21

x.test(); // output?
y.test(); // output?

Please use 👍 or 👎 to vote. Use 👍 to choose what match your intuition, you could also use 👎 to denote you will never interpret the code like that. If you have other answer, please add comment. Thank you.

请用表情符号投票。👍 表示跟你的直觉相符,你也可以用 👎 表示完全不会这么想。如果你觉得有其他结果,可以留言。谢谢。

工作整理

架构类

  • jedi
  • my.js
  • es6-webapp-framework 用es6写的类似connect+express的webapp中间件和应用框架

特定领域库

  • zhi 手势库
  • pmc.js js cc
  • selector-utils 选择器子集计算
  • speck.js speck加密算法
  • contrail 轨迹
  • avos-chat leancloud的即时消息的js api
  • bx-chat 百姓实时聊天的js lib
  • bx-api 百姓API的node版

shim/sham、polyfill、基础库

  • my-promise Promise实现
  • mmclass ES6 class语义实现
  • es6-symbol-sham
  • better-es5-shim
  • feature.js shim/sham/polyfill 管理
  • typed-enum
  • typed-dictionary
  • meta.js
  • mwc-scrollable

工具

  • etab Elastic tabstops实现和github插件
  • semicolon-less.js js源码格式化工具
  • demoshare
  • my-module-builder
  • es6-module-directives

Demo

  • nara 类似mini-chaoge的node版
  • socket.io-test

偏门js面试题又一道

这次的题目是这样的:

'use strict'
var o1 = {
  f: function () {}
}
var o2 = {
  f() {}
}

请问 o1.fo2.f 有哪些差异。

2015年11月17日TC39会议摘要

是否批准 Async functions 进入 Stage 4?

否。需等待两个实现通过测试。目前有 babel 和 chakra(edge)两个实现,FF 也马上会实现,但还缺乏经过 review 的测试集。
BTW,babel 作为实现是否满足需要 case by case 的看,某些特性有 source2source 就可以,但有些不行(比如 proxy)。

Object.values/entries

批准进入 Stage 3

String.pad{Left,Right}

更名为padStart/padEnd,并批准进入 Stage 3 。相应增加 trimStart/trimEnd
注:start/end VS left/right 差别主要在于是否考虑语言书写方向。

Array.prototype.includes

加入 @@unscopables,并进入 Stage 4 。
注:@@unscopables 的作用是防止破坏 with 的兼容性。

function.sent

因为缺乏实现,所以没有反馈。

Object.observe

撤销提案

如果解构没有绑定是否应该抛异常

代码例子:

 let { } = obj;
 let { foo: {} } = obj;

保持现有行为(不抛异常)。
注:出现这样的代码很有可能是程序员误将 foo = {} 写为了 foo: {}。但这是linter可以解决的问题。

非严格模式下函数提升语义

https://docs.google.com/presentation/d/1tu8L4Askkqz-CojBlaiP7pf0hQRQNXZpS4cmml0obQ8/
未达成一致意见。

翻墙工具

2016年12月更新

目前在使用:

2015年7月

一直使用的曲径最近非常不稳定,昨天终于挂了。

现在已经换成了:

其他可能的备选:

偏门js面试题一道

『偏门』的意思是,这个题目基本没有什么卵用,不建议使用。

例外情形:候选人自称『精通JavaScript』或自评JavaScript水平8分以上(刘鑫老师说,程显峰出过一个面试标准,8分意味着达到标准委员会水平)。


JS中一个array(数组)的length属性的最大值是:
【更具有迷惑性的表述是:JS中一个array(数组)可以包含的element(数组元素)最多可以有多少个:】

A. 2**31
B. 2**32 - 1
C. 2**32
D. 2**53 - 1


该题的变形形式(更难一点):

JS中『array index』的最大值是:

A. Number.MAX_VALUE
B. Infinity
C. 规范没有定义
D. 下表中的 _______

n 2n - 2 2n - 1 2n
31 2**31 - 2 2**31 - 1 2**31
32 2**32 - 2 2**32 - 1 2**32
53 2**53 - 2 2**53 - 1 2**53
64 2**64 - 2 2**64 - 1 2**64

在production中使用webcomponents的限制

http://developer.telerik.com/featured/web-components-arent-ready-production-yet/ 此文是7月份的,但是大部分内容在可以预见的两三年里都是适用的。

TL;DNR

  1. 有些东西太难 polyfill —— 就是 shadow dom,特别是css相关
  2. 性能负担:如 polyfill 太大,html imports 可能发出很多请求

在这个文章之后, webcomponents.js 把 polymer 里的 platform.js 重组了,现在给出的是不带有 shadow dom 的 polyfill 。

我的基本想法:

  1. 除了shadow dom 其他的部分可以先用
  2. html imports 和 ES6 loader 的关系?或者在build步骤打包

PHP 为什么大小写规则是如此不规则?

大部分编程语言的符号是 case sensitive 的,少数(如 Basic)是 case insensitive 的。然而 PHP 两种都不是。试验下就会发现,PHP 的函数、方法以及关键字是 case insensitve 的,而变量、常量都是 case sensitive 的。

至于类,你猜是 sensitive 还是 insensitive?

我是占位而已

我是占位而已

我是占位而已

我是占位而已

答案是 insensitive。然而通常我们使用 classloader,如果以大小写不一致的方式使用,则在路径是 case sensitive 的 *nix 环境下,就很容易产生问题了。

我是占位而已

好吧,尽管 PHP 再次体现了奇葩,通常 PHP 程序员并不 care 这个问题,绝大多数人遵循“使用和 manual 一致的大小写”的代码风格。

不过这导致一些很不友善的 API 命名,比如 htmlspecialchars ,为什么不是 html_special_charshtmlSpecialChars

了解函数是 case insensitive 后,我们知道事实上后者的写法是可行的。实际上最初这个函数(在 PHP2 的文档中)就是以 CamelCase 方式命名的(HtmlSpecialChars)。但不知是出于什么原因,(或许是出于_统一_大小写风格?)PHP3 开始,文档中所有函数全部小写了。

我是占位而已

但是最初为什么函数被设计为 case insensitive?

PHP 之父 Rasmus 在一个访谈中说:“...I then wrote a very simple parser that would pick tags out of HTML files and replace them with the output of the corresponding functions in the C library... The simple parser slowly grew to include conditional tags, then loop tags, functions, etc. At no point did I think I was writing a scripting language. I was simply adding a little bit of functionality to the macro replacement parser. I was still writing all my real business logic in C.”

这里透露出许多信息。其实最初的 PHP 也许比今天更接近一个模板引擎!早期 PHP 就是“宏替换(macro replacement)”,而业务逻辑并不是它的职责。宏替换具体来说是将 HTML 中的某些 tag 替换为 C 写的对应函数的输出。所以爆栈的这个回答里说,因为 HTML tag 是 case insensitive 的,所以在查找对应函数时,就按照 case insensitive 的方式了。后来的 PHP 虽然已经变得完全不同,但函数名就一直 case insensitive 了。

这一原因很有可能是真相。实际上不仅函数,那些早期就有的结构,比如 if / for 之类的关键字,还有 echo、list 之类并非是函数的特殊指令,也都是 case insensitive 的。或许我们应该反过来问,为何在语言大部分元素都是 case insensitive 的情况下,变量名却是 case sensitive 的?

我是占位而已

下面我们讨论下 class 。class 的属性和常量是 case sensitive 的,方法是 case insensitive 的。这样和普通变量、常量、函数的大小写情况正好是一致的。(唔!一致性?!)

问题是类名本身为什么是 insensitive?(从直觉上,类名难道不是更接近常量?)

我是占位而已

想一想?

我是占位而已

想一想?

我是占位而已

想一想?

我是占位而已

下面揭晓答案:

我是占位而已

因为 PHP 类一开始其构造器并非现在的 __construct,而是和类同名的方法。因为方法是 insensitive,所以类名也就是 insensitive 的。(唔!一致性?!)

我是占位而已

我是占位而已

好了,我想现在你可以很自豪的说,自己终于搞清楚 PHP 中各种大小写规则了!然而且慢!

PHP 有 goto 语句!可以跳转到指定的 label。goto 作为关键字,我们可以肯定它是 case insensitive 的。可是 label 是 sensitive or insensitive 的?

大家一起来猜一猜罢 💩

Node.js 的版本

【最后更新 2016-10-27】

这里列出了 Node.js 所有的 stable 大版本的第一个版本和最近一个版本,以及 io.js 期间大版本的最后一个版本。

第一张表包括了 process.versions 的内容。第二张表是相同 V8 版本的 Node.js 和 Chrome 的版本对应。

node v8 modules uv http parser ares zlib icu openssl
0.8.0 3.11.10.10 0.8 1.0 1.7.5-DEV 1.2.3 1.0.0f
0.8.28 3.11.10.26 0.8 1.0 1.7.5-DEV 1.2.3 1.0.0m
0.10.0 3.14.5.8 0.9 1.0 1.9.0-DEV 1.2.3 1.0.1e
0.10.48 3.14.5.11 11 0.10.37 1.2 1.9.0-DEV 1.2.8 1.0.1u
0.12.0 3.28.73 14 1.0.2 2.3 1.2.8 1.0.1l
0.12.17 3.28.71.19 14 1.6.1 2.3 1.2.8 1.0.1u
1.8.4 4.1.0.27 43 1.4.2 2.5.0 1.10.0-DEV 1.2.8 1.0.2d
2.5.0 4.2.77.21 44 1.6.1 2.5.0 1.10.1-DEV 1.2.8 1.0.2d
3.3.1 4.4.63.30 45 1.7.4 2.5.0 1.10.1-DEV 1.2.8 1.0.2d
4.0.0 4.5.103.30 46 1.7.3 2.5.0 1.10.1-DEV 1.2.8 1.0.2d
4.6.1 4.5.103.37 46 1.9.1 2.7.0 1.10.1-DEV 1.2.8 56.1 1.0.2j
5.0.0 4.6.85.28 47 1.7.5 2.5.0 1.10.1-DEV 1.2.8 56.1 1.0.2d
5.12.0 4.6.85.32 47 1.8.0 2.7.0 1.10.1-DEV 1.2.8 56.1 1.0.2h
6.0.0 5.0.71.35 48 1.9.0 2.7.0 1.10.1-DEV 1.2.8 56.1 1.0.2g
6.9.1 5.1.281.84 48 1.9.1 2.7.0 1.10.1-DEV 1.2.8 57.1 1.0.2j
7.0.0 5.4.500.36 51 1.9.1 2.7.0 1.10.1-DEV 1.2.8 57.1 1.0.2j
date node v8 chrome v8 date
2012-06-25 0.8.0 3.11.10.10 21.0.1180 3.11.10 2012-08
0.8.28 3.11.10.26
2013-03-11 0.10.0 3.14.5.8 24.0.1312 3.14.5 2013-01
0.10.48 3.14.5.11
2015-02-06 0.12.0 3.28.73 38.0.2125 3.28.71 2014-10
0.12.17 3.28.71.19
2015-01-14 1.0.1
1.8.4 4.1.0.27 41.0.2272 4.1.0 2015-03
2015-05-04 2.0.0
2.5.0 4.2.77.21 42.0.2311 4.2.77 2015-04
2015-08-04 3.0.0
3.3.1 4.4.63.30 44.0.2403 4.4.63 2015-07
2015-09-08 4.0.0 4.5.103.30 45.0.2454 4.5.103 2015-09
4.6.1 4.5.103.37
2015-10-29 5.0.0 4.6.85.28 46.0.2490 4.6.85 2015-10
5.12.0 4.6.85.32
2016-04-26 6.0.0 5.0.71.35 50.0.2661 5.0.71 2016-04
6.9.1 5.1.281.84 51.0.2704 5.1.281 2016-06
2016-10-25 7.0.0 5.4.500.36 54.0.2840 5.4.500 2016-10

Why Callback/Thunk Sucks and Promise Rocks

Callback 在大型编程时的一般性问题

Callback hell 就不谈了

无法区分异步任务 callback(针对单一结果的处理) 和 listener(针对一类结果的处理)

例子:fs.watch 在fs包中是特殊的

调用时是无法区分的,所以需要api文档说明。但是文档往往缺乏此说明。

无法区分 sync callback 和 async callback

请阅读:Designing APIs for Asynchronyshort version

调用时是无法区分的,所以需要api文档说明。但是文档往往缺乏此说明,且api设计者也很可能忽略这点而release zalgo。

Node callback convention 的问题

node callback 相关的签名是基于约定的:

function asyncCall(...params, asyncCallback)
function asyncCallback(err, ...params)

基于约定导致:

约定会有例外

  • fs.exists 的 callback 签名是 function (boolean)
  • Timers 的 callback 参数位置是在第一个而不是最后一个

实际上,Timers的情况是一个普遍问题。Timers来自于Web Platform API,而 Web API 不可能遵循 Node callback 约定,所以一切使用 Web API 或需要保持和 Web API 兼容的,都面临约定不一致问题。

err 参数应该是 Error 对象

参见:A String is not an Error

尽管此问题与callback本身并无必然关系(你也可以直接 throw 'string' ),但是 callback 形式更容易忽视。例子:async.js的代码示例

callback 也更难 enforce 此规则。比较:

function f(x) {
  if (invalid(x)) throw 'invalid x'
  ...
}
function f(x, callback) {
  if (invalid(x)) callback('invalid x')
  ...
}

对于前者,我们可以检查所有 throw 子句。而后者就困难多了。

特别注意,引擎其实可以对throw进行特殊处理,比如Chrome就是如此。

更容易 release Zalgo

前一个代码示例表明在做参数检查时很容易就同步调用callback从而破坏了前述async/sync区分的必要性。

thunks 的问题

具有前述所有问题(除了callback hell)

形式极简的API未必是好的

  • API形式的简单其实把复杂性留到了别处
  • API命名和概念上的不清晰:
    • thunk(一般概念 VS 特定所指——参数为node callback约定形式的函数)
    • thunks(包的export,实际是提供类似Domain的机制)
    • Thunk(构造器)
    • 链式调用返回thunk(接受的参数是node风格的回调,但是返回值处理有特例)
  • 构造器的签名和后续调用实际是不同的
  • 人类需要冗余信息来辅助阅读

微妙的语义差异

  • 不像then可以被调用多次
  • 如果thunk调用返回一个函数会被当成thunk,但是实际上无法区分这里需要thunk还是真的希望返回一个函数(实际上既和CPS风格不一致,又破坏函数式编程)

当前实现就是 releasing Zalgo 的!

而当前thunks的所谓性能优势(其实跟bluebird/when/rsvp等典型promise实现比完全没有优势)其实也来自于尽量同步执行,代价就是 releasing Zalgo。

由于实现方式的问题,当调用链过长时会 Maximum call stack size exceeded 。而原生Promise和所有主要的 Promise/A 实现都没有这个问题。

Thenjs的问题

名同实异

  • then 方法和 Promise 的 then 不一样

实现方式问题

  • 和 thunks 一样也有 call stack size 问题

Promise 的特点

语义清晰

  • 一定只针对单一结果
  • 必然是异步
  • 基于明确的API,而不是基于约定
  • 不需要语言外的错误处理机制

标准

  • 基于社区共识,大部分开发者对它有一致的认知,有充分的实践,也经过编程语言专家的充分研究和探讨而定型
  • 性能不是问题——现有的Promise/A的主流实现性能非常非常好,而native实现早晚会得到引擎的充分优化
  • 更好的错误处理——如chrome开发版中已经把reject和throw一样可以trace普通值
  • 未来语言设施(ES6 loader、ES 7 async/await 等)和新API(ServiceWorker、Stream等)的相容性

不足

  • Cancelable (ES8?)
  • Observable (ES7)

如何看待《React: CSS in JS》?

感谢 @liusong1111#21 话题讨论中提供facebook的这个slide:React: CSS in JS

slide的前半部分,我没想到facebook能在错误的工程化道路上走那么远(这个可以理解为赞美)。我在前文中“局域化CSS”一节写道:“覆写和扩展class钩子或可类比为某种接口使用,但实际运行时并没有任何约束,我们也很难进行静态检查”。但是facebook居然做到了显式接口和静态分析。这完全来自于f家强大的工程能力,体现了f家前端架构师的牛X。不过可惜的是,即使做到这个地步,这条路仍然是走不通的,他们自己最后也意识到了。BTW,这也基本上可以视作对OOCSS、SMACSS、BEM等方法论宣判的死缓。

后半部分是他们的一个解决方法。

包括:

  1. 使用inline style
  2. 使用JS表达样式组合,从而利用所有JS的特性(如模块化)
  3. 放弃使用selector的specificity机制,而是在插入inline style时使用JS的调用顺序来明确override样式的优先级(在这个点上有极高的灵活性,是完胜传统class钩子的关键点)

老实说,我一点也不意外这样的方案,我甚至惊讶于从Bootstrap流行开始花了这么久才走到这一步。实际上我一直认为“样式为中心”的开发方式最终应该回归到inline style上。

但是,这样的方案是不是胜过了我一直推荐的CSS preprocessor的方案?我认为没有。

此slide列出的CSS的scale问题(即大规模开发时的可维护性问题,或者通称“工程化问题”)如下:

  1. Global Namespace
  2. Dependencies
  3. Dead Code Elimination
  4. Minification
  5. Sharing Constants
  6. Non-deterministic Resolution
  7. Isolation

(待续...)

参数约束一例

http://www.zhihu.com/question/28940236/answer/42664451

DateTime.Today().Add(new TimeSpan(365,0,0,0))……写代码的哥们写成new DateTime(today.Year+1,today.Month,today.Day)。
经过各种测试运行一直都没问题。直到2012年2月29日,ArgumentOutOfRangeException。

看了这个贴除了会心一笑,又陷入思考。

我一下想到了JavaScript中的new Date的API,面临相同的代码,会容错,自动进位到3月1日。这样一种API设计是否好呢?

看上去这样的设计能避免出现该答案中服务挂了若干小时的问题。但是仔细想来,进位到3月1日从业务逻辑上讲未必符合,只是此case中时间的精确性并不重要。然而如果是金额非常巨大的金融系统,在闰年自动进位的一天或许就成了一个潜藏的漏洞。由此考虑,容错这件事情其实未必是好事。

下面有评论说此问题是测试不到位,测试应该注意边界条件。我觉得有一点道理,但从工程角度来说,如何让测试能避免这种遗漏?

这就想到之前看到的一些自动产生测试数据的方案。顺便提及,这也需要类型系统支持,只有知道传入参数是日期类型(甚至更强的范围约束),才能自动产生对应的测试数据。

BTW,这个例子又让我隐隐想起checked exception和unchecked exception的争议。在这个例子里也许编译器强制要求检查是否捕捉了可能的exception是更好的选择?

iframe 呈现受限内容的考虑

iframe 的(不太)新的属性 sandbox 可以用于呈现受限内容。默认情况下会视iframe内容在独立的origin中并禁用脚本、禁止插件、禁止各种有潜在风险的api。

根据 caniuse,除了 IE9- ,其他浏览器都支持。

IE6+ 可使用私有属性 security=restricted作为替代。


另外为了避免多一次请求,可使用 srcdoc 属性。但 IE/Edge 不支持该属性,并且无法用 data url(IE8+ 不支持用 data url 构造 html),但或可用 javascript url。

围绕 GitHub 的相关服务

  • shields
    各类徽标
  • travis ci
    持续集成服务,大量使用
  • david-dm
    npm 包依赖徽标
  • coveralls
    测试覆盖率徽标
    js方面配合istanbul和istanbul-coveralls使用
  • gemnasium
    监控项目依赖的更新和安全漏洞
    有待试用
  • renovatebot, greenkeeper
    类似上面
  • codacy
    代码质量检查
    目前感觉不够好:js基于jshint,不够;css相关规则大多数无用;可能要收费
  • codeclimate
    代码质量检查
    有待试用;貌似略贵
  • sonarqube
    代码质量检查

浏览器中隐私和特性的矛盾

最近这几年来,浏览器厂商对隐私的注重越来越提升。一些小众浏览器以此为卖点,比如Brave。而随着Chrome一家独大的情况愈演愈烈,有被边缘化趋势的其他浏览器也都着重以隐私为差异化点。比如Safari。

既然以此为差异点,言下之意就是Chrome的隐私保护就比较差一点,那为什么Chrome就差一点?有一种攻讦认为Google是依赖互联网广告的,因此不愿意『过度隐私保护』来削弱第三方广告主的利益。

但是,『过度』隐私保护或许确实达不到目标,比如当初 DNT(Do not track)的纷争。IE10默认打开了DNT,而这是不符合DNT规范的,DNT的一项重要要求是,开启DNT必须是用户主动的选择,而对于广告商的约束也是基于这是用户的主动选择。IE10实际上为了彰显其『保护隐私』而破坏了DNT的基础,最终反而对保护隐私不力,因为广告商会因为DNT并非用户的主动选择而选择忽略之。当然,DNT只是浏览器发了个http头,但本身从来就没有被广告商真正广泛支持(包括微软自己),所以马后炮来看,也很难说IE10在DNT失败上到底要付多大责任,反正最终DNT被证明是一个失败的尝试,而DNT设置自身反而会被作为一种信息用于fingerprinting,因此今年2月,Safari 12.1主动移除了这项功能。

除了认为Google主动『不太重视』隐私保护,另一个点可能是Chrome的功能跑得太快,Chromium的contributor比其他浏览器多一个甚至两个数量级,每天代码变更太多,这当然是Chrome对其他浏览器的极大优势,但也导致根本来不及review所有变更是否对隐私和安全造成了新的影响。据说微软放弃EdgeHTML转投Chromium之后,会着重投入到改善Chromium的隐私能力上。

回来说Safari,Safari对于事关隐私的特性是非常注重,不惜牺牲开发者所需要的功能。比如高精度计时,按照标准可以有5微秒的精度,但Safari只提供了100微秒的精度,比标准差了20倍,就是为了提高时间差分攻击的难度。

我最近在TPAC上听一个Google的人说,Safari的目标号称是要把可以被用作fingerprinting的entropy控制在8个字节bits以内。这是一个非常难以达到的目标。

也因此,Safari做了一些可能比较激进的改动。比如不允许CSS中指定本地字体。因为这可以被用来探测用户安装了哪些字体,并可被用作fingerprinting。对于开发者来说,这意味着除了基本font-family比如serif/sans-serif之外,就只能使用web fonts了,但是对于CJK来说,web fonts的开销还是非常大的。

另一个事情是cache。传统上http cache是共享的,比如大家都用jQuery,就可以从同一个CDN上加载。但是这也存在被利用来探测用户的风险,因此需要被隔离。这意味着每个网站都需要单独加载比如说jQuery,即使之前另一个网站是从同一个CDN以完全一致的URL加载过。Safari已经是这样了,FF和Chrome也打算跟进。(参见 https://github.com/shivanigithub/http-cache-partitioning/

我们还需要注意,这两个事情是有叠加效果的,一方面我们被迫更多使用web fonts,另一方面web fonts也不能跨站共享缓存,结果就是要个性化字体的成本就变得更高了。

本质上,特性和隐私可能是存在矛盾的。比如SharedArrayBuffer也是一个可以被变相用来提供高精度计时的机制。一些对底层硬件能力的暴露也很容易被用来做fingerprinting(比如canvas 2D和3D)。我们要么完全禁用之,要么必须付出一定的性能代价来消除可被利用的entropy,不同的浏览器还可能采取不同的策略。这里也包括一些国内厂商,比如UC等为了一些(不太确定的)理由就阉割掉了WebAssembly能力。

在这个过程中,开发者可能是最被动的。

跨浏览器测试云服务

sauce labs
browserstack
rainforest qa
spoon
litmus
mogotest
browsera
browserling
CrossBrowserTesting
Browsershots

JS中的函数式编程 === 垃圾

http://awardwinningfjords.com/2014/04/21/functional-programming-in-javascript-equals-garbage.html

标题有点耸人,但事实上很有意思的一篇短文。

总结:

  1. 纯函数式要求避免副作用。而深拷贝会导致大量中间变量,这些中间变量是一次性的,马上就要被垃圾回收。
  2. 没有尾递归优化,会很容易导致调用栈耗尽。如果用Trampolining,每次迭代都产生一个临时函数,于是又是一堆待回收的垃圾
  3. 函数compose和partial也导致每次调用产生中间变量(临时的参数数组和partial的函数),如果用大量的函数组合式编程,就产生一堆垃圾

但是这文章也并不是说不要用函数式编程。应对上述问题的方法是:

  1. 用Mori之类提供 immutable 数据结构的库。(应该是不需要每次做深拷贝,也许用原型来共享?)
  2. 底层迭代用while/for循环——虽然不够函数式。
  3. 此条文章没给出解决方法。其实这条就是讲创建闭包的代价,单纯函数组合本身并不是问题。

该文结论:Never forget that Javascript hates you.

又被PHP坑了一小下

通常变量名的构成都是有限制的,比如要避开keyword。
但PHP有个小trick,你可以用 ${'any string can be used'} = true;

于是我最近在改进编译jedi到PHP的代码时,就用了这个方式。

不料就踩坑了……

${'test'} = 'this is ok';
$x = function () use ($test) {}; // this is ok...
$x = function () use (${'test'}) {}; // this is NOT ok...

爆PHP语法错误。

这到底是为啥呢……

---- 无辜的分割线 ----

---- 无辜的分割线 ----

---- 无辜的分割线 ----

---- 无辜的分割线 ----

---- 无辜的分割线 ----

---- 无辜的分割线 ----

---- 无辜的分割线 ----

原来这个trick的本源就是“变量变量”(饶舌,好吧,合理的翻译是“可变变量”),比如:

$a = 'b';
$b = 'WTF';
echo $$a;

$$a${'test'} 其实都只是${expression}的特例而已。

use(...) 构造中只接受真正的 symbol 从实现的角度考虑倒也是可以理解的。不过从一致性的角度考虑,稍微花点力气让 use(...) 支持可变变量也不是做不到。只能说PHP一贯坑,这个真也不算什么了。

BTW,亚一程有个 use 别名的提案(https://wiki.php.net/rfc/useas ),但是貌似弃坑了。

关于参加技术活动收益的一点想法

宁JS刚刚结束,大家对于一些分享的质量略有微词,其实这很常见。我从十几年前就开始参加各种技术活动,最近两年的频度甚至达到了每年参加大小近20个活动(因为最近两三年前端大繁荣,技术活动也空前繁荣),对这件事情早就想得比较明白。刚才看到羡辙写的这篇,我觉得总结得很好。BTW,羡辙在很多方面具有超越其年龄的成熟。

我呢,就再说一点题外话。我知道大家都希望每场分享都有所收获,不过这不仅需要讲者将技术心得浓缩于几十分钟并很好的表达出来,也需要听者有足够的知识储备并连续几十分钟保持聚精会神。然而,人持续集中注意力的能力是有限的,吸收消化能力也是有限的。这是为什么到了下午常见不少听众打起瞌睡,真未必是分享者的水平就比之前的差。

我举个例子来说,像我这样英语比较差的人,听英文会耗费更大的精神,基本上我连续听英文分享超过25分钟之后,必然会专注力下降从而跟不上讲师,此时只能放弃一些段落,略做休息后再听后面的。(以此来说,日程安排上最好可以错开中英文演讲,可以照顾一下英语水平比较差的听众的脑力。PS. 学好英语很重要。)其实听中文也是一样,只是不用额外花费精神在语言上,所以注意力时间可以保持更长久,像一般一场演讲最多45分钟,中小学生一节课的时间,还是能坚持到,然后就可以休息一会儿。另外很多时候QA环节中的交流知识密度不会那么高(尤其是经常会有软性的问题),这个时候大脑很可能已经放松了。不过,像本次宁JS为了更好的控制时间,取消了QA(换为下午统一的table discussion),且多次取消休息直接开始下一场,对于听众的脑力消耗恐怕是加重了。建议以后还是再斟酌下。

说这些并不是为了那些不够好的分享开脱,实际上不够好的分享会浪费掉听众宝贵的专注力,是要努力避免的。之所以说这些,是希望明白,即使都是非常好的内容,你也不可能全程保持状态,或者当时感觉不错,但是实际已经吸收不进去了,转头就忘了。因此,不要高估参加技术会议的收益预期。

李如一写过一篇《幻灯片癖恋与乾货崇拜》,我认为大多数技术活动的参会者并不会像他说的那样理解“干货”(实际是“干尸”),不过打个预防针也是好的。

最后祝愿下一届JSConf能更好。

PHP的array、traversable、foreach等的设计失误(一)

《PHP恨你》这个系列停笔好久了,主要是一直没空,然后光吐槽也不产生价值。不过这个我新发掘的case,勉强可算语言设计上学到的一课,所以就写一下吧。

第一件小事是,PHP的typehint不支持union type,如果一个参数可以是多个类型,就只好用注释了,或者更常见的情况——就不写了,读代码的人你自己猜吧。

Traversable这个接口表示的是可以用foreach遍历。而array当然也是可以用foreach的。所以一个函数接受Traversable,没有什么道理不接受array。但是因为上面说的限制,导致你无法标注typehint,如果你标注了Traversable,传一个array进去就报错。这真是个悲伤的故事。

实际上Traversable并不是一个可以直接实现的interface,而是一个“internal engine interface”,也就是说它的用途只有typehint和instanceof。而标typehint的用途因为上述原因变得很鸡肋,也就只剩下instanceof。而instanceof代码也基本上就是

if( !is_array( $items ) && !$items instanceof Traversable )
  //Throw exception here

这个罗嗦样子。

其实让这个接口同时覆盖array又有什么问题呢?

注意,有人可能会说普通object其实都能foreach(行为是遍历所有public属性和值),那么Traversable应该可以覆盖所有object,但那样这接口就没意义了。

确实,Traversable接口的真实含义更接近:如果实现了此接口,就代表有定制化的遍历行为。不过遍历一个普通对象是比较少见的(因为PHP中array已经同时具备了关联数组的用途),通常是reflection的用法,而不像Traversable/array是频繁的集合遍历操作。PHP不是一向崇尚“实用主义”嘛,为什么这里不按照实用出发呢?

如果说第一件小事只是稍微带来不便,第二件事就非常蛋疼,且贻害无穷了。

(待续)

翻译对照

原文

Repeatability — Our test results should be highly reproducible. Tests run repeatedly should always produce the exact same results. If test results are nondeterministic, how would we know which results are valid and which are invalid? Additionally, reproducibility ensures that our tests aren’t dependent upon external factors issues like network or CPU loads.

翻译一

重复性 —— 我们的测试结果应该是高度重复性的。反复试验经常产生同样的结果。如果测试结果是不确定的,我们如何知道哪些结果是有效的,哪些是无效的?此外,可重复性,确保我们的测试是不依赖于外部因素的问题,如网络或处理器负载。

翻译二

可重用性 —— 测试结果应该是高度可再生的。多次运行测试应该产生相同的结果。如果测试结果是不确定的,那我们又如何知道哪些结果是有效的,哪些又是无效的呢?此外,可重现性可以确保我们的测试不依赖于外部因素,诸如网络或CPU负载。

翻译三

重复性 —— 我们的测试结果应该是高度可重复的。测试反复运行应该总是产生同样的结果。如果测试结果是不确定性,我们如何知道哪些结果是有效的,哪些是无效?此外,再现性确保了我们的测试不依赖于外部因素的问题,如网络或CPU负载。

翻译四

重复性 —— 我们的测试结果应该是高,重复性好。重复运行的测试应该总是产生完全相同的结果。如果测试结果具有不确定性,我们怎么会知道这结果有效,哪些无效?此外,可重复性,确保我们的测试不是取决于外部因素问题,如网络或CPU负载。

猜猜看

四段分别是三个机器翻译(谷歌翻译、百度翻译、必应翻译)和某译者翻译(我稍微去掉了编辑痕迹)。你觉得哪个翻译好?请给翻译质量排序,并猜猜哪个是人翻的?可以在下面留言。

注意,试一下就很容易知道翻译来源的答案,所以请勿公布在留言中,以免影响大家的判断。

关于exclusive range运算的符号

大概去年这个时候 Swift 语言把 half-open range operator .. 改为了 ..<,引起了一些讨论。

实际上..<运算符的最早先例是 Groovy 语言

而Groovy在初创之时,使用的是和 Ruby 一样的 range operator(.....),在2005年4月左右将 ... 改为了 ..<

而最早提出以 ..< 符号作为 exclusive range 运算符的,其实正是本人。这10年前的邮件记录可在此查看:http://marc.info/?l=groovy-dev&m=113684773506831

其实在 Swift 做了这个改进后,还是有很多人吐槽这个符号的,比如 1..<3 有点像“I love”?

不过我个人认为,如果一定要使用多个点来作为 range operator,那么 Swift 的这组符号选择是看下来最合理的——实际上我开发的 Jedi 在三年前就使用了 ... 作为 inclusive range op,..< 作为 exclusive range op,应该是世界上最早使用这个组合的了——幸好有git记录,后来人就不会以为我是抄 Swift 的了——实际上是 Swift 的开发者在10年后终于选择了我当初的设计……

不管这个符号的好坏如何,这应该是迄今为止我个人对整个编程界最大的影响了,而且随着Swift的流行,还会有更多的开发者使用我创造的这个符号,想想好像也挺有成就感的。

对于React体系的一点想法

这一年来react和react native火得不行。

我对react其实一直有保留。单看应用框架,我其实更喜欢Angular 2一点。

但是react-style (css in js)秒杀了所有所谓css方法论。尽管我对react-style也仍有保留,但是我确实发现这方案和react是能很好的互相结合解决那些关键痛点的。

这两天我看了尚未release的relay和graphql的介绍,我估计这两个出来会大火!因为也是真解决了工程上的痛点。它做的事情其实好几年前我就想过,但是我只停留在想法,facebook真的实现出来,并且和整个react的体系是一体的。这就是架构能力和工程能力了,不服不行。

所以这样比较下来,A2就差React不少了。毕竟A只是G家的一个边缘项目,而React及其整个体系是F家自己的狗食,投入程度和反馈不是一个级别的。

从整个工程体系上来看,Flux + React/ReactNative + React-Style + Relay/GraphQL,辅以ES6+/JSX/flow等语言设施,几乎已经构成闭环。

不过,这并不代表我要转向React阵营。尽管Facebook看上去确实完成了一个重新定义Web开发(甚至涵盖了移动客户端开发)的壮举,但是我的内心仍然不觉得这是final answer。相比当初jQuery的一统天下,我相信Web应用框架这个大领域的创新还远未结束。

各种可能的运算符(Operator)

普通四则运算

a + b => a + b
a - b => a − b
a * b => a × b
a / b => a ÷ b

整除取模

a div b
a mod b / a %% b
a divmod b
a quot b
a rem b / a % b
a quotrem b

问题:
0. xpath中div表示除法,而常见语言的divmod中div表示整除
0. 同时取两个值的运算 divmod / quotrem 除了性能优化是否有足够用例?

乘方运算

a ** b

相等性

a == b => a = b
a != b => a ≠ b
a === b => a ≡ b
a !== b => a ≢ b

顺序

a > b
a < b
a >= b => a ≥ b
a <= b => a ≤ b
a <> b
a <=> b
a > b > c
a >? b / max(a, b)
a <? b / min(a, b)

问题:
0. a <> b 和 a != b 的关系?
0. a <=> b 除了实现运算符重载外是否有足够用例?

匹配

a = b => a ≃ b
a !
= b => a ≄ b

逻辑运算

!a / not a => ¬ a
a && b / a and b => a ∧ b
a || b / a or b => a ∨ b
a xor b => a ⊻ b
a nand b => a ⊼ b
a nor b => a ⊽ b

从属关系

a in b => a ∈ b
a not in b => a ∉ b
a contains b => a ∋ b
a not contains b => a ∌ b

位运算

~ a
a & b
a | b
a ^ b
a << b
a >> b
a >>> b
a <<<< b 循环移位?
a >>>> b 循环移位?

管道、函数组合

a | f
a | f 100, _
a, b | f _1 + _2

g . f / g << f
f | g / f >> g

do f a, b, c

apex domain的cname/alias/aname

今天发现github pages build失败,原因是github pages换ip地址了,检测到ip没改时,通过这个方式告诉你该更新了。

但是根据https://help.github.com/articles/tips-for-configuring-an-a-record-with-your-dns-provider/ 最好是有alias/aname 记录。不过大多数 dns 不支持(比如坑爹的godaddy)。

于是找一下dns服务商(http://www.datanyze.com/market-share/dns
screen shot 2014-12-18 at 6 22 08 pm

虽然自从我用的everydns挂了以来,dns service的厂商已经很少提供free的服务了,我又不想用dnspod(尽量避免用兲朝厂商),但惊喜发现排名第一的cloudflare的服务是免费的,而且还支持aname(不过是以cname自动flatterning的形式)。更惊喜的发现2011年我就注册了它的服务……

所以用起来吧。

关于全栈培训班的一些想法

这是前一段时间的热门话题,我一直没针对这事在公开场合发表过意见,现就我所知谈些看法。


李笑来那个全栈班的讲师是 xdite ,我从一些渠道听闻这位还是不错的,不是一般培训班里不知道哪里请来的人充当老师。我也看了她的 blog,对她的教学方法、内容和目标略有了解,仅照她自己描述看,不是普通培训班可比,如描述大体不差,我觉得应该能学到东西。所以不要再简单的讲李笑来忽悠了。

当然,注意几点。

第一,这种等级的老师不常有。一般人无法鉴别老师的水平,万一你遇到的是李红米之流……那还真不如10000元拜师某当老师。

第二,这培训班因为报名人多(拜李笑来所赐),所以进班的人是经过筛选的。而一般培训班恨不得把所有能宰的对象都拉进来。所以我估计这班的学员平均水平应该比普通培训班要高出一大截。(即使同样零基础,你挑聪明程度高的人来教那效果也是天差地别。)毕竟某当老师收徒也是要挑选的。

第三,这班收费高(当然总有更贵的),比一般培训班至少高一倍甚至两倍。学员的努力程度应该也会更强。选择培训班的很多人,是缺乏足够自我驱动力的,而收费高通常能提高自我驱动力,从这个意义上说,高价其实是有正面作用的。

第四,这是全脱产的线下班,能建立让学员处于始终打鸡血(此处为褒义)的环境。非脱产班或周末班很难建立类似的长时间高强度的学习状态。

第五,你完成了这整个课程,能达到怎样的水平?其实不好说,但应该比一般培训班出来的人强。对于真正有潜力适合这一行的人来说,估计是能缩短其学习入门的时间的。(而应该不会像月影曾对某个培训班的学生说的,你们不学那2个月,现在能学得更快。)但恐怕还是难以通过比较好的公司的面试的。(我还真有兴趣想面一下李笑来培训班出来的人,或者听听看业内同仁面到他们的反馈,看看会不会打脸。)


这培训班在做了2期之后,现在新的形式是线上8周(目前是1万3)+线下5周(费用不清楚,但是我猜也要4、5万)。要参加线下必须先修完线上班。这个形式其实仍然保持甚至更好的发挥了前面描述的点。线上班具有更好的scale性,商业上赚钱效率也更高。线下班一期顶天只能几十人,线上班几百上千没问题。另一方面,线上班提供了更大的筛选基数,进入线下班的素质可能会比之前更高。从这个角度看,线下班很可能真的能挑选并培养出一些不错的人,不要说收学费了,若真的能不断的产出合格的工程师,这财富大了去了。从这个角度看,李笑来的这个商业计划我给💯分!

注意,一般培训班很难复制这个模式,一来找不到足够好的老师,二来就算能找到好老师,但没有李笑来的加持,很难吸引足够的学员。如果依赖一般培训班的渠道(地面广告和销售),很容易被渠道绑架而无法实施筛选策略。

我考虑下来,可以搞类似李笑来这种模式的,要么是本身就是线上的学习平台,如慕课之类,要么是某些互联网公司,比如360自己搞的培训班。但慕课这种平台本身是靠跑量的,充斥了大量免费和低价的内容,跟高价路线有矛盾。而互联网公司搞这个呢,一来是副业,二来现在他们都不收费(反而是倒贴的),恐怕已经无法改成收费模式更不要说高价(一改,甚或其他类似公司搞,都会被骂黑心,伤害公司形象)。注意我前面分析过,高价模式其实有正面作用。另外既然能创造更大的商业价值,按道理质量上也会扩大与其他竞争者的差距(比如能请更好的老师,比如我——终于找到机会自吹了)。转了一圈下来,最能搞这个的或许是StuQ,但StuQ的目标群体是已经在企业的人,而不是还没入行的人。

最后结论是,在这个领域,短期内李笑来是没有竞争对手的。另外,说起来,反正市场在那里,李笑来至少能挤压掉一些劣质培训班,与其给他们赚钱,不如给李笑来,给 xdite 赚。这对社会对行业应该还是有益的。

以上。

最后,本文不构成对任何人参加任何培训班的建议。


【本文同步在知乎专栏上发表:https://zhuanlan.zhihu.com/p/24245471 ,欢迎到那里给我打赏。】

我在D2夜场上对前端工程的预言,兼答“如何评价阮一峰关于前端工具变化快的言论?”

知乎答案链接:https://www.zhihu.com/question/34449620/answer/79028575

其实本答案并不是对工具变化快的直接评价,而是通过预言来间接给出评价:与我预言的即将发生的变革相比,阮老师所描述的变化是小巫见大巫。

我的这个预言或者说判断,在2014年年中即已成形,但我一直憋到2015年年底才来回答——因为在一周前D2的夜场上,我在即兴发言中终于憋不住抛出了我的预言:前端的构建部署即将发生革命性的变化——单独的构建环节将逐渐弱化乃至消失,构建所包含的实质性内容即脚本、样式等资源的编译转译等将云化和实时化。两年后(到2018年时),不要说Grunt/Browserify,Gulp/Webpack等也均将退出主流。

具体来说,我预言明年(2016年)将出现包含JS模块加载、包依赖自动处理、JS自动编译这三项核心功能,并基于HTTP/2的前端资源云服务(可理解为CDN的进化形态)。并且这类服务会在未来两年里快速成长并带动整个前端的大跃进。比如,通过ServiceWorker大幅提升浏览器缓存利用率,加上HTTP/2等协议级优化,Web应用的加载性能将获得划时代的提升;根据UA分发目标平台特定编译版本和自动polyfill,Web应用的浏览器兼容性将获得划时代的提升;提供各种增值服务如方便部署安全特性,安全性监控、灰度部署、包的平稳升级、前端(精确到应用和所依赖的包)的错误监控和分析、前端性能监控和分析等,Web应用的质量将获得划时代的提升,开发效率也将是划时代的提升……由此,前端工程师也会重新把很大一部分精力从构建部署相关的工程问题回归到网站/应用的交互体验本身。(不过应用框架和组件化相关的工程问题届时是否能有比较一致的答案,现在看还很难说。)

以上就是我的预言,立此存照。

BTW,如果2016年9月前我预言的事情还没开始发生,我会亲自动手。 ^_^

Web前端开发相关工具整理

https://github.com/codylindley/frontend-tools

目前所选择的工具:

Workflow/Builds/Assemblers/Task-runners/Dev Opts

gulpjs 在用
grunt 基本否定
broccoli 感觉不成熟

yeoman 熟悉中

Browser Package Managers

npm
bower 有一些问题,不建议,但因为流行,库要发布到bower
component 否定(转向其他)
jspm.io 待研究

normalize 观察中

总体 package management 的最佳实践可能会受到 loader 影响

Testing Frameworks

intern 有待尝试

Code Complexity & Reports

escomplex
注:parser要用eslint/espree或acorn,原本的esprima太严格

关于前端开发中“模块”和“组件”概念的思考

本文基于与 @fouber 讨论时的记录整理。

术语的重要性

首要是澄清术语。同事平时交流的时候,有比较多的上下文信息是双方已经预先知道的,所以容易推断对方要表达的意思,一定程度的术语混淆关系不大。但是和其他人交流的时候,如果不明确术语的内涵和外延,经常变成鸡同鸭讲的状况。

举个例子来说,上次看到有位同学老是骂别人的文章里哪里哪里不对,进而演变为完全否定他人。我后来发现他对某些术语的理解有诸多“与众不同”,即他自己从概念和定义上就否定了别人。而有这样问题的人常常不自知,或者被指出其术语运用存在的问题仍坚持认为是别人概念错误,跟这样的同学交流起来就特别令人痛苦和恼火。

所以让我们先明确定义。

模块(对应英文“module”)

通常所指“模块”是指编程语言所提供的代码组织机制,利用此机制可将程序拆解为独立且通用的代码单元。

对于JavaScript来说,在ES6之前,并没有语言内置的模块机制,但我们用一些方式自制了某种模块机制,像CommonJS / AMD甚至建立了普遍接受的社区标准。虽然它们都是模块机制,但会有一些重大或微妙的差异。故当我们提到JS模块时,如果没有足够的上下文,有时需要明确是CommonJS module或AMD module或ES6 module。

对于CSS来说,并没有普遍接受的“CSS模块”概念。一个CSS样式表里可以通过@import来引入其他样式表,但我们通常并不称之为“模块”。多份样式表以cascade机制结合,这和我们一般编程语言中模块互相调用的方式相当不同。且CSS的@import语义基本上就是最简单的include,也就是将@import语句替换为导入样式表的内容。而编程语言中的导入模块会在当前作用域导入命名空间、符号等,比简单的include要复杂许多。

有关“CSS模块”的问题,我们后面还会讨论。

注:在Web标准中,“CSS module”其实指CSS spec本身的模块化。这也是我们应该避免采用“CSS模块”来指代CSS代码的组织结构的重要原因。

其实我公司里对“模块”的用法也比较随便。比如我们有/static/js/modules/目录,其实下面就是一些脚本,并没有采用任何一种module规范。再如我们有/src/modules/目录,下面每个子目录是业务模块,里面包含了view、controller和相关的各种类。

这里一个是历史因素——目录结构不是我建立的,大家习惯如此,都知道我们讲的“module”是指业务模块,跟具体编程语言里的module没有直接的关系,只要沟通没有什么障碍,那也不必改了。不过当我们完成引入JS module loader和相关设施之后,很可能还是需要重新调整文档和目录命名,以避免可能的理解错位。

回到关于“模块”的定义讨论上,我建议运用此术语时尽量避免扩张性解释——即避免在脱离特定机制的general的“模块化”的意义上使用“模块”这个词。

比如,传统的JS代码组织方法之一,是挂在global上的层级命名空间。此严格上不好称之为“模块”。原因是namespace只提供逻辑划分,不解决代码本身的划分。如果没有其他机制,代码划分仍然是文件为单位,并由开发者自己指定script加载。同理,我们通常认为C++里没有模块(尽管有namespace和include),但是PHP我们认为有模块(因为它有autoloader可以根据namespace映射到目录去加载文件)。

当然,即便编程语言没有模块,我们仍然可以通过一些方式进行“模块化”编程,但这种模糊的用法有可能造成误解。在JS这边因为我们已经有很成熟的CommonJS / AMD / ES6 module了,更应避免模糊用法。

组件(对应英文“component”)

另一个概念是“组件”。大体上“组件”和“模块”的概念是类似的,只是“组件”通常指更high-level的东西。

我个人体会,“模块”指代码单元,其意义偏向静态的代码结构。而“组件”指功能单元,其意义偏向运行时的结构,并有更复杂的控制(如组件实例的生命周期管理)。

举例来说,在组件系统中,你应该可以比较容易的做到在运行时查找某种组件并替换为另一种组件(热插拔)。而这通常并不作为模块系统的需求——即使模块系统支持动态加载,通常也不支持注销旧模块;即使支持注销旧模块,通常也不支持替换所有旧模块的引用(意味着需要重新 实例化/初始化 模块依赖树上所有直接或间接引用此模块的模块)。

注:的确有某Node.js平台下的游戏框架设计以class作为模块单元,通过替换prototype来做到模块的热插拔。不过这其实要求非常多的编程方式约定,实际上可被视为使用的是JS的一个裁剪的特性子集,因而不具有普遍性。

组件与模块的关系

网页本身导入脚本、样式表、图片、组件等,继而组件导入其自己所需的脚本、样式表、图片、其他组件之类。这样的组件机制比较符合我们对于网页构成的一贯认知。Web Components相关规范中的HTML Imports大体就是这么个东西。

注意,(Web Components的)组件机制跟(JS的)模块机制是正交的。

所谓正交,就是两者并不互相依赖对方的机制——至少目前是这样的。HTML Imports导入的作为组件的HTML文件里,引入脚本(目前)仍然用的是script标签,并不需要ES6 module。

但是我们有两个问题。

第一,实践中的组件方案不止Web Components一种。(现在的情况实际是大多数人还没有用上Web Components。)

其他组件框架绝大多数基于JS,它们的代码本身需要被加载,那就有一个模块机制的问题——因为组件框架通常都足够复杂,不太可能用裸脚本方式。既然怎么样都需要某种模块加载器,那么组件框架很可能直接利用模块加载器来加载asset。这样模块机制就变成了组件机制的基础了。

另一方面,组件框架如何定义组件呢?无论过去还是现在,看下来大多数组件框架就以一个class来定义一个组件。最常见的代码组织惯例是,一个class对应一个模块,于是组件就变成了符合某种模式(如继承自某基类)的JS模块。

我们比较一下。原生的Web Components方案,开发者需要在document里加link rel="import",然后引用的组件HTML文件里写script/style/link标签,script里声明自定义标签和相关组件行为。比起直接document里加载JS模块,然后在JS模块里import / require其他的JS模块 / HTML template / CSS样式表的方式,好像后者反而更简单点?对此我们稍后再讨论。

前面讨论“模块”定义问题时,我们讲过要避免扩张性解释。“组件”可以被称为“模块”(通常会加限定词以区别于普通的JS模块,如“UI模块”)的原因只在于组件本身以JS来表达,因而可以对应到一个具体的JS模块。假如组件本身并不以JS来表达——像Web Components的组件的形式是一个特殊的HTML文件,则称之为“XX模块”就是对“模块”的扩张性解释。就算是前一种情况,为了概念清晰和保持一致性,我仍然会建议用“组件”一词。

第二,回到Web Components规范,尽管组件机制和模块机制可以是正交的,但是实际情况是资源的依赖、加载、执行(应用)等是两者共性的问题。当前相关的各项标准在这点上其实还未协调,故而标准社区有讨论是否需要统一以及如何统一的问题,而Firefox也因此暂未实现HTML Imports。怎么样才对,我现在也还没想清楚,社区也还没有一致的意见。

通过JS Module Loader加载CSS等资源

HTML Imports使用和传统网页较为一致的模型。与此相对的,从历史到现在一直有以JS为中心的方案。

之前我们讨论过JS的模块。语法上以import "a"require("a")来引入其他模块。但是到底这里的"a"表示什么,如何加载,如何执行,是由具体的loader(及其hook/plugin)处理的。这里就提供了从JS module loader加载其他资源的可能。比如RequireJS、Sea.js、SystemJS均(通过插件)支持加载CSS。

我们是否可以把被加载的CSS资源叫做“CSS模块”?我觉得是有问题的。现有loader的这些插件的实现实际上只是简单的创建link[rel=stylesheet]元素插入到document中。这种行为和通常引入JS模块非常不同。引入另一个JS模块是为了调用它所提供的接口,但引入一个CSS却并不“调用”CSS。所以引入CSS本身对于JS程序来说并不存在“模块化”意义,纯粹只是表达了一种资源依赖——即本JS模块所要完成的功能还需要某些asset。

loader其实可以加载任何东西。如果看loader的另一些插件,如允许import "a.png"的图片资源插件,它只是起到preload作用。字体插件亦然。所以没有人称其为图片模块和字体模块,而只是称之为资源。

CSS介于图片/字体和JS之间。CSS像JS的地方是在于其复杂性,现代Web应用的CSS的复杂度已经有点接近编程了。但是从loader的角度,它更像图片/字体。

我们进一步仔细分析可以发现,JS模块对其他JS模块的依赖是一种强依赖——在依赖项加载和执行完后才能执行自己,而其对加载的CSS、图片等的依赖是一种弱依赖——我们只是表达额外需要某种资源,但是加载顺序甚至是否加载成功且应用完毕都可能是不重要的。

所以我们或许应该认为存在一个更高阶的组件(即使它直接以这个JS模块本身表达),它同时需要这些JS代码逻辑和一些CSS资源。另一方面,现有的使用JS module loader来加载CSS、图片等的实践也许存在滥用和误用的状况。

BTW,hixie有一份草案是通过needs等属性表示资源的依赖关系和优先级,其中包含了延迟加载或空闲时才加载等特性(均可视为弱依赖关系)。抛开声明性和不依赖JS的优势不说,基于JS module loader的方案能否优雅的支持弱依赖关系,是有很大的疑问的。(当然needs提案也面临跟HTML Imports和ES6 module一样的问题,其底层的依赖处理机制需要协调统一。)

注意:loader可以支持import a from "a.png"然后a返回一个HTMLImageElement对象,import b from "b.css"然后b返回一个CSSStyleSheet对象。这样导出一些可以被JS操作的对象似乎使其更像JS模块一样具有强依赖的特征,这也许是一种合理的用法。不过这时我们可以注意到另一个行为上的差异——image插件其实并没有把HTMLImageElement插入到document中,而按照通常CSS插件的意图,却需要把CSSStyleSheet对象插入到document.styleSheets中。这反映了CSS不同寻常之处——它直接是全局生效的,与“模块化”的要求是正好抵触的。我们后面还会详细讨论这一点。

此外,loader不会多次加载和执行(应用)相同CSS——这是module loader的要点之一。而CSS自己的@import语义则正好相反,多次引入相同URL的样式表,都会在导入位置上应用。使用JS module loader的import的语义和CSS自己的@import语义不一致,这也许是个问题。

CSS的@import也支持media query和supports condition等特性,这是目前的JS module loader插件不支持的(至少我没见过支持的)。带有media query的CSS@import声明会在运行时根据media query是否匹配而动态应用,也就是除了依赖关系以外,还有其他因素共同决定是否加载,这和前面谈到的弱依赖是类似的。

要基于JS module loader实现@import "a.css" (min-width:500px)的效果,可能得这样写:

matchMedia('min-width:500px').addEventListener(mediaQueryList => if (mediaQueryList.matches) System.import('a.css').then(() => ...));

或者

import a from 'a.css'
assert(a instanceof CSSStyleSheet)
a.media.appendMedium('min-width:500px')

前者实在难看,且其依赖关系已经不是声明性的了(从而相当麻烦)。后者则可能在还未加上条件时已经开始下载了(从而不满足需求)。

总结一下。JS module loader虽然可以被利用来加载各种资源,但本质上就是一个dependencies tree和注册在其上的一些纯粹由依赖来驱动的callback / promise。对于JS模块来说,这样的设计恰如其分,但是对于其他种类的资源来说——它们可能具有比单纯依赖(即强依赖)更复杂的如优先级、动态条件、可fallback等需求,直接把JS module loader用作组件系统的基础可能并非合适方案。其实就算是加载JS,对于polyfill / shim,loader系统都可能是要开外挂而不在标准机制内。

回顾之前讨论过的“模块”概念,我们可以增加一个认识:“模块”术语暗示了强依赖——因为编程语言的模块都是强依赖的——即使许多人没有明确意识到这一点。

CSS局域化问题

我们对于CSS当然也有分而治之的需求。但是简单用“模块化”来表述可能是有问题的。

如前所述,传统上,CSS被插入文档中,其包含的样式规则是文档全局有效的,这和模块化本身是相抵牾的。

当然我们可以通过某种开发规则来达到效果的局部化。比如以特定id/class限定所有CSS rule的应用范围。

另一种似乎更常见的方式是:所有rule本身就只包含class选择器。从某种角度上说,可被视为这个样式表定义(导出)了一些可复用的样式,并以class来命名。是否能称这样一个样式表为一个“CSS模块”?

当我们讲“A模块依赖B模块”的时候,其实暗含A要使用B所导出的接口的意思。假如我们认为“CSS模块”暴露的是class钩子,可是一个CSS模块依赖其他CSS模块并不存在需要调用它的class钩子的情况;覆写和扩展class钩子或可类比为某种接口使用,但实际运行时并没有任何约束,我们也很难进行静态检查(比如我们无从判断A的代码中有一个B所不包含的class名字是有意扩展还仅仅是拼写错误)。JS依赖CSS的情况也是类似的。

另一方面,这导出的class及其样式声明,也未被限定于只能被声明依赖者使用,其效果仍然是全局性的。

所以不建议管这样的东西叫“CSS模块”,这在沟通中很容易造成误解。(虽然公司内部沟通的话可能问题不大。)

题外话:这种方式实际上滥用了class属性。因为CSS没有复用机制,所以只好拿class属性来充数,通过class来作为应用样式的钩子。这违背了HTML规范和CSS规范的要求。除了对规范的实质性违背之外,这种方式在工程上的一个后果是,将内容和样式的耦合点从样式表的selector转移到了HTML文档的元素属性上。这对于页面开发流程、分工协作方式和长期可维护性会有巨大的影响。此外和通常认知的不同,这样的开发方式其实对页面性能有负面作用。具体就不展开了,可另行讨论。

组件框架在CSS这块的需求我认为“局域化样式”(scoped style)是比“CSS模块”更准确的称呼。目前的具体实现方案除了class样式钩子外,更靠谱的方式是:

  1. shadow dom天生样式就是局域化的
  2. style元素的scoped属性
  3. 以特定id/class限定单个样式表中CSS rule的应用范围,并配合css3增加的all属性和unset值来确保不被其他样式表污染。

前两者目前都有浏览器支持的问题。但第三种方式配合CSS预处理器是完全可行的。

特别是如果讲CSS预处理器,因为它们是真的可以以mixin、函数等来进行抽象,因此讲“SASS模块”、“Stylus模块”、基于预处理器的“样式库/框架”,倒是可以接受的。

补充

上述思考来自和云龙讨论的整理。

云龙想对“模块”进行扩张性解释的原因,可能在于当我们描述依赖树的时候,每个单元叫什么好。标准社区里目前还没确定怎么统一(ES6 module、HTML Imports、needs属性等规范的)依赖机制。我翻了一下es-discuss里hixie与tc39的成员们关于loader的讨论,发现在讨论到loader作为统一设施时,基本上module是特指ES6 module,而用来加载其他东西会称为non-module,大多数时候是直接说stylesheet、image、font或统称resource。而HTML Imports规范里,只有import link tree,并不包含style和script。也就是暂时找不到对完整的依赖树上的单元有统一的称谓。我觉得参照标准社区如何运用这些词汇会比较妥当。这意味着也许并不需要一定要找一个词汇来统称它。如果在讨论依赖树的时候我们需要统一称呼,称之为“依赖项”或许就足够了。

github mac client 貌似不支持 .gitattributes 中定义的 filter

因此 clean/smudge 没有作用。

本身 github for mac 也没有提供编辑 .gitattributes 的地方。

不确定是否 github for mac 压根不考虑 .gitattributes 文件?

奇怪的是 github client 其实加入了 git-hawser 作为全局 filter。

另外,测了一下 sourcetree 是支持 filter 的。

11月30日知乎Live预告

面试,对许多程序员来说是一道(或很多道)难题;其实对面试官来说更是一个难题。从业十几年来,我看过做过各种形式的前端笔试题和面试题;自 2010 年以来,我本人又作为面试官进行了上百次的技术面试,也一直与同事和业界朋友交流技术面试的方式方法乃至具体的题目。 2013 年时,我偶然从日常工作中得到一个有趣案例,就直接拿来作为面试题,没想到效果不错。我在之后的面试实践中不断完善了这套面试题,一直使用到去年。

本次 Live 将围绕这套题来现场模拟一次完整的前端工程师岗位的技术面试——

  • 对于前端团队leader、技术面试官、负责技术招聘的HR:
    希望我的这套前端面试题和这场模拟技术面试所展示的技术面试思路对你们有启发;
  • 对于已经入行的初中级前端工程师:
    希望这次模拟面试结合我给出的评分标准,能让你们更准确的评估自己的能力,发现可以改进的方向;
  • 对于前端实习生、非专业前端岗但对前端有兴趣的工程师以及其他想投身前端领域的人:
    希望本次 Live 能开拓你的视野,加深对前端工作的认识,理解怎样才算优秀的前端工程师。

本次 Live 进行中也可能顺便回答这些问题:

  • 团队希望招怎样的前端工程师?
  • 技术面试应该考察哪些方面的能力?
  • 如何对付面霸?
  • 怎样才是好的前端面试题?
  • 要不要让面试者现场写代码?
  • 为什么面试者用百度搜索我会扣分?
  • 传说中 winter 老师的「大草原」到底是个什么梗?(误)
  • 其他技术面试相关的话题。

本次 Live 二十多天前就创建了,虽然今天才正式发布通告,但现在进入已经只有站票了(即无法在 Live 中提问)。不过 Live 当天会开一个微信群,我邀请了小爝担任管理员,会挑选微信群中的问题转发到 Live 中。

Live 入口:https://www.zhihu.com/lives/778991567569440768

Live 时间:11月30日 20:48 ~ 22:24

后面是个人简介和一些废话,略。想看的直接看知乎专栏全文 https://zhuanlan.zhihu.com/p/23870345

WHY “PROMISES ARE NOT NEUTRAL ENOUGH” IS NOT NEUTRAL ENOUGH

【知乎专栏上的文章链接:https://zhuanlan.zhihu.com/p/35082528 ,此处乃备胎。】


这篇文章的标题有点绕口,不过大家都懂的,这是一个吐槽手法。

本文就是要吐槽 Staltz 最近写的这篇文章《Promises are not neutral enough》


Staltz 作为 Cycle.js 的作者,也算是社区名人之一。最近他搞了一个大新闻叫 Callbag(Why we need Callbags),一看名字就是给 callback 招魂的。这篇我不打算吐槽 callbag(想看吐槽 callbag 的可移步:callbag和rxjs有什么区别?),就单吐槽一下 Staltz 对于 promise 的偏见。

Staltz 说 promise 是“opinionated primitive that introduce a lot of weirdness”,并列了四点 opinion:

  • Eager, not lazy
  • No cancellation
  • Never synchronous
  • then() is a mix of map() and flatMap()

我一点点来说。


第一点,promise 是 eager 立即求值而不是 lazy 延迟求值。

其实这个事情是有点扯的。因为所有语言、库里的 promise 抽象(有些叫 future 或 deferred,语义上有些差别,但是在此问题上不重要,所以这里不展开说)都是如此。也就是说如果还需要用户主动调用 x.run() 来开始计算,那就不是 promise 了。那叫 task(或 fiber,或类似的 thunk)。

(当然不排除世界上有些**库硬是要做一个 lazy future 之类的东西。其实你既然要提供不同的抽象,安安心心的叫 task 就好了,不要把概念搞乱行不行。)


到底 task 好还是 promise 好?这本身其实有点关公战秦琼。因为两者其实是不同的抽象。task 的抽象侧重于“执行(任务)”,而 promise 的抽象侧重于“(最终的)值”。这不同的抽象选择导致不一样的语义和 API,是一件非常自然的事情。若侧重于“执行”,那自然应该允许用户选择何时执行,也没有必要限制执行一定是同步的还是异步的,甚至无所谓是否在单独线程里跑 —— 直接抵达了 thread 的领域。而若侧重于“值”,那用户为什么要 care 这个值的运算过程?

其实如果你需要控制执行(sometimes you don’t want the promise to start right away),或重用异步任务(you may want to have a reusable asynchronous task),直接写一个返回 promise 的函数,或者一个 async 函数就好了啊!函数就是用来表达执行的啊!!!如此简单而自然!

Staltz 当然知道这一点,但他强词夺理说函数就不能用 then 来 chain 了。我擦,人家 promise 就是一个异步值的原语,then 方法只是为了在没有 async/await 的时代,提供你一个利用异步值的基础设施。(否则你压根没法用啊!)然而你为什么要让它去管函数链式调用?你如果要处理一般的函数链式调用,自己 compose 函数啊,或者等着 pipeline operator 啊!(在别的地方你倒知道吹 pipeline operator,怎么说起 promise 来就忘了??)

说什么“Eager is less general than lazy”,完全是胡说八道。你在一个 lazy 的语言比如 haskell 里这么说也就算了,你在一个明明全然是 eager 的语言里说“eager is less general”,颠倒黑白没有这么流利的吧?


第二点,没有 cancellation。确实 promise 没有内置这能力(cancelable promise 提案因为各种原因被撤销了)。但是现在有 cancelation 提案(tc39/proposal-cancellation)啊,而且最新版浏览器已经支持了一个非常类似的方案(DOM Standard: aborting-ongoing-activities)!(当然dom规范里的 AbortController/AbortSignal 如何跟语言规范里的机制协调可能是个棘手问题,有待处理,不过大方向是没有问题的。)

Staltz 说“I believe lack of cancellation is related to eagerness.”不好意思,全错。你后面提到的 cancel 在向上游传播时的问题,本质上在于向上传播本身就是概念混乱的产物,跟立即执行没有半毛钱关系。建议好好再学习一下 cancelation token 提案的 architechture 部分(tc39/proposal-cancellation#architecture)。


比较神奇的是

Try to pay attention to the words “opt-in”, “restriction”, “always”. When a behavior is opt-in, it is neutral. When a behavior is always forced, it is opinionated.

这段完全是稻草人攻击。实际上 cancellation 无论是当前提案还是 dom 规范里的设施,都是独立于 promise 的,所以必然是 opt-in 的。

其实前面的 eager 问题也是。显然返回 promise 的 function 就提供了所谓 lazy,且 promise 和 function 是独立特性,所以我们可以说你所谓的 lazy 是 opt-in 的。但是你反过来说这是 restriction??这双重标准是怎么玩的??


第三点,总是异步。这一点其实没有好多说的。node callback convention 也包含了这一点(只不过 callback 形式很难强制约束这一点,这是 callback 的缺陷之一)。对此有疑问的人建议再好好读 Isaac Z. Schlueter 多年前的经典文章:http://blog.izs.me/post/59142742143/designing-apis-for-asynchrony

所以 forEach 的例子正说明问题。forEach 明确的告诉你这里是同步的。promise 则明确的告诉你这里是异步的。这是为什么 promise 必须总是异步,且你应该在所有处理异步的地方都使用 promise。这样就不会出现你看到一个 callback 但是搞不清它是同步还是异步了。

为什么同步异步之分在 JS 里那么重要?因为 JS 不是 pure 函数式语言!JS 代码会依赖副作用,而副作用取决于代码的执行时序。JS 有 run-to-completion 语义,所以只要明确是同步还是异步,其执行时序是非常容易推断的。

下面忍不住要逐段打脸。

The impossibility of going back to synchronous once you convert to Promise means that using Promises in a code base will force code around it to be Promise-based even when it doesn’t make sense.

Promise 本来就是异步原语。异步当然不能被转换为同步啊!除非你用阻塞。而在 JS 里提供阻塞等于提供一把注定会打死你自己的枪。

promise 也并没有把所有代码都变成基于 promise 的,传给 then 的回调完全可以是纯同步的代码啊!

I can understand why async code forces surrounding code to become async too, but Promises make this effect worse by forcing sync code to become async. That’s yet another opinion inserted into Promises.

说来说去就是说异步的传染性。你要是依赖一个异步值,你的函数当然就得是异步的啊。但是你已经 await 到一个值之后所做的计算可以抽成一个纯同步的函数啊。自己模块化做不好,怪语言设施…… 再说你不是 observable 和 pipeline operator 玩得很溜嘛,又没说不许用。

A neutral stance would be to have the primitive make no claims whether the data will be delivered synchronously or asynchronously.

同样的话也可以用来批评 haskell,你们搞什么 pure,搞什么 lazy,完全不“中立”!

Promises are what I call a “lossy abstraction”, similar to lossy compression, where you can put stuff in that container, but when you take it out of the container, it’s not quite the same as it was before.

对“抽象”的理解简直一团屎。按照这说法,高级语言都是“lossy abstraction”,汇编才是无损纯真的代码!
说了半天其实 Staltz 就是有意忽略一点,Promise 对 JS 来说就是异步原语,由此施加额外约束是应有之义。你所谓“中立”的结果无非是给程序员留坑。


最后一点,Staltz 吐槽 then() 不是正宗原味 monad。

这算整篇文章比较有技术含量的部分了。然而……


首先,map 和 flatMap 的签名是:

M<T>.map(T => U): M<U>
M<T>.flatMap(T => M<U>): M<U>

而 then 的签名是:

Promise<T>.then(T => U): Promise<U>
Promise<T>.then(T => Promise<U>): Promise<U>

易见,then 实际上是自动 overload 版的 map/flatMap。


Staltz 吐槽点就是,干嘛不直接暴露 map/flatMap 呢?这样就可以跟其他 monad 小伙伴一起玩耍啦!
我先不说你是不是真的有场景要统一操作异种 monad,我先把你提到的“马上就要到来的”Array.prototype.flatMap 拿出来看一下。

Array<T>.flatMap(T => Array<U>): Array<U>

理想上其签名应该是这样的,然而,JS 不是静态类型语言啊!谁确保传进来的回调是 T => Array<U> 呢?如果返回值不是 Array,那就等于传进来了 T => U 啊。

于是你突然发现,Array.prototype.flatMap 明明跟 Promise.prototype.then 是一样的,自动 overload 了!

所以,在动态类型语言里,只要你不打算做运行时检查类型扔 TypeError 这种事情,flatMap 对回调的结果进行自动 wrap(从而 overload 了 map)是必然的选择。

所以 then 就是 flatMap。唯一的问题是为什么 promise 不像 array 一样提供单独的 map?


为什么要提供?


我先不说提供单独的 map 方法让你可以得到 Promise<Promise> 有毛个意义。

我们谈理论。


在 monad 鼻祖的 haskell 那里,定义 monad 只需要 2 个操作:returnbind

return 就是 wrap/unit,即从 T => M<T>

bind 就是 flatMap


所以 Promise 从 Haskell 本源意义上说千真万确就是一个 monad。


当然我们也可以用另一个方式定义 monad,使用 3 个操作:returnfmapjoin

fmap 就是 mapjoin 则是 flatten,即将 M<M<T>> 打平为 M<T>


所以本来你就有两种方式定义 monad,一种用 flatMap,一种用 map + flatten。实际上很容易理解,有了 map 和 flatten 你就可以实现出 flatMap。但是,反过来说,有 flatMap 我们也可以实现出 map 和 flatten。

function map(f) { return this.flatMap(x => wrap(f(x))) }
function flatten() { return this.flatMap(x => x) }


所以 promise 本身不提供 map 和 flatten 方法并没有任何问题。当然你可以吐槽 JS 没有内置的 mixin 语法或 extensive methods(其实都有提案),使得统一接口比较麻烦,但无论如何吐槽不到 promise 。


当然,promise 有特殊之处,比如 wrap 操作理论上不能直接用 Promise.resolve,因为 Promise.resolve(promise) 并不返回 Promise<Promise>。实际上在 JavaScript 中是不可能产生 Promise<Promise> 嵌套类型的。显而易见,这一限制是出于实际编程的考虑。但是 Staltz 直接否定了这一点。

So it’s better to recognize that Promises can practically be concatenated, so they should have the concat method.

问题是你不能简单的吹说“practically”,你得拿出真实 use cases 啊!嘴炮谁不会?你倒是真拿一个把 Promise 给 concat 起来的例子啊!妈蛋!!bullshit!!


结论部分。

上面我已经把 Staltz 的各点批驳完毕。

关键点在于,promise 的出发点是提供异步原语。有意无意的忽略这一点,所有论证就都乱来了。Promise 的设计总体上没有任何问题,Staltz 希望的:

  • 所谓 lazy
  • 直接在 promise 接口上提供 cancel()
  • resolve 时而同步时而异步
  • 提供无意义的 Promise<Promise>

才是 weird、unfortunately opinionated 的。

Promises were invented, not discovered. The best primitives are discovered, because they are often neutral and we can’t argue against them. For instance, the circle is such a simple mathematical concept, that’s why people discovered it instead of inventing it. You often can’t “disagree” with a circle because there’s no opinion embedded in it, and it occurs often in nature and systems.

说不清道理,就上比喻,文章里那无聊的 food 比喻我就不吐槽了,这里又拿圆形来比喻。一股浓郁的民科风。


实际上,编程设施全都是发明出来的。从最基本的二进制补码整数类型、IEEE754浮点数、Unicode字符,到复杂的数据结构如红黑树、bloom filter乃至神经网络,无一不是发明出来的。各种语言的语法语义也都是发明出来的符号系统。包括monad。我们发明它们用来表达运算逻辑。(其实真正搞数学的人,会告诉你数学里也是如此,符号公理系统都是发明出来的。)

Promise 是发明出来的,node callback conversion 或者 Staltz 自己搞的 callbag 显然也都是发明出来的。

或者我们换个正常点的词,这些东西是为了一定目的被设计出来的。

如果有人说我发现了某某,多数是谦辞,表示不是我牛逼,只是运气好而已。


真正可以被发现的,只有客观存在。

编程里有什么东西是真的发现出来的?估计只有 bug 吧。


最后,有人可能会问,你写这吐槽,(欺负)老外看不懂啊。是啊,谁让他不懂中文。同志们要有点自信啊。


【讨论建议移步知乎文章评论区:https://zhuanlan.zhihu.com/p/35082528

V8引擎迭代器bug一则

{ // code 1
	const [a, b, c, ...d] = test()
	print(d[0], d[1])
}
{ // code 2
	const [a, b, c, d, e] = test()
	print(d, e)
}

上述两段代码显然我们期望结果是一样的。

然而

function test() {
	let i = 0
	return {
		[Symbol.iterator]() {
			return {
				next() {
					return {value: ++i, done: i % 3 === 0}
				},
			}
		}
	}
}

结果 V8 前者返回 4, 5,后者返回 undefined, undefined。显然V8的实现有bug。

其他引擎中,SpiderMonkey、JavaScriptCore、Hermes、engine262 的结果是符合预期的(都是undefined, undefined)。XS 和 Chakra 返回的都是 4, 5 好歹是一致的。

Chrome下float的bug一则

float一直是浏览器bug的重灾区。今天确认了Chrome浏览器中存在一个严重的float的bug。定位和调试过程就略了。直接看我精简到最小的testcase。

<div>
    text
    <div id="r" style="float: right">right</div>
</div>

只要空间足够,右浮动的内容应该和 text 在同一行,即如下效果:

text                right

然而当我们有如下脚本

function test() {
    var s = document.getElementById('r').style
    s.float = s.float ? null : 'right'
}

执行两次test()本应不变,但是实际效果变成了:

text
                    right

上述bug可在360se 7(Chrome 31)里重现。不过最新版的Chrome已经修复了。

然而新版Chrome其实仍然存在这个bug,只是触发条件更苛刻了一点点:

<div>
    <div id="l" style="display: inline">text</div>
    <div id="r" style="float: right">right</div>
</div>
function test() {
    var s = document.getElementById('l').style
    s.display = s.display ? null : 'inline'
    s = document.getElementById('r').style
    s.float = s.float ? null : 'right'
}

以上。

Babel plugin 体系的缺点

  1. Babel 的 syntax plugins 毫无意义。仅仅是开启 babylon parser 的选项,并没有真正可插拔的语法扩展能力
  2. Babel 的 plugins 可以声明直接依赖关系(inherits),但无法声明其他关系(排斥、顺序要求),并且暂时拒绝此提议(https://github.com/thejameskyle/babel-plugin-handbook/issues/17
  3. Babel 的一些官方 plugins 的职责切分有问题,导致一些微妙的 bug,如
    • https://phabricator.babeljs.io/T2776#67632
    • https://phabricator.babeljs.io/T6779 (transform-es2015-shorthand-properties 和 transform-es2015-function-name 如何转换包含 super 的 method ?实际上 transform-2015-object-super 应该被合并到 transform-es2015-shorthand-properties,或者从 transform-es2015-shorthand-properties 拆分单独的 transform-es2015-shorthand-methods)

预处理器支持CSS的@support的可能性

@supports (text-shadow: 0 0 .3em gray) { 
    h1 {
        color: transparent;
        text-shadow: 0 0 .3em gray; 
    }
}

可编译为:

:root[data-cssrules-1] h1 {
  ...
}
cssSupports('text-shadow: 0 0 .3em gray')

let id = 0
function cssSupports(cssDecl) {
  let e = document.createElement('div')
  e.style.cssText = cssDecl
  if (e.style.cssText === cssDecl) {
    document.documentElement.dataset[`cssrules-${++id}`] = true
  }
}

注意:

  • 此代码为简单想法,未经验证。
  • :root应该可获得优化从而解决属性选择器慢的问题。
  • :root排除了所有不支持:root的老浏览器如IE6,我们一般可假设所有需要@supports的特性均是IE6不支持的。
  • 编译后specificity增加了2个类/伪类,可简单的在所有选择器前添加:root:root以保持一致。
  • cssText赋值实际会重新格式化,且需要考虑简写属性被展开为多个属性的情况。

offside-rule 的问题

王垠抨击过缩进语法。其中提到缩进很容易在编辑的时候弄坏,比如不小心(猫跳到键盘上?)输入或删去一些空格。而传统语法没有这个问题是因为恰好输入或删除成对的符号(花括号对或begin/end对)的概率小多了。比较来说,缩进是单点变更就会产生语义差异,因而危险。

需要承认这是一个合理的责难。其实可以和 rm -rf / temp 的空格惨案相类比。紧凑的语法往往造成这种问题。

不过实践上似乎并未看到或听到很多实际的惨案,即使coffee、yaml已经相当普及。

有一些方法来克服这一问题:

  • 受控的编辑环境
  • 语法冗余结构
    • 引入类似花括号或begin/end的要素
    • 引入类似/的terminator

富文本编辑器选型

project repo star/fork maintainer status deps
UEditor fex-team/ ueditor 2339/1024 @Phinome (baidu fex) 1.4.x stable
1.5.x dev
年后计划2.x?
UMEditor fex-team/ umeditor 797/275 @Phinome (baidu fex) 1.2.x 维护中
wangEditor wangfupeng1988/ wangEditor 1498/472 王福朋 (baidu 手百) 维护中
wangEditor-mobile wangfupeng1988/ wangEditor-mobile 170/63 王福朋 (baidu 手百) 暂停维护
KindEditor kindsoft/ kindeditor 873/376 @luolonghao 罗龙浩 (alipay) 已不维护
xhEditor yaniswang/ xhEditor 148/62 @yaniswang (alibaba) 已不维护
Kissy Editor kissyteam/ editor 1/3 ? 仅内部维护
CKEditor 4 ckeditor/ ckeditor-dev 3152/1521 10+ (CKSource) 维护中
CKEditor 5 ckeditor/ ckeditor5 142/24 5+ (CKSource) 开发中
TinyMCE tinymce/ tinymce 3927/1151 @spocke 4.5.x 维护中
Aloha Editor alohaeditor/ Aloha-Editor 2214/513 @GenticsDev 1.x维护中 但2.0版计划已废弃
ContentTools GetmeUK/ ContentTools 2598/248 @anthonyjb
MediumEditor yabwe/ medium-editor 9775/1163
wysihtml Voog/ wysihtml
wysiwyg.js wysiwygjs/ wysiwyg.js 380/116 ?
Substance substance/ substance 2062/88
WYMeditor wymeditor/ wymeditor
Froala froala/ wysiwyg-editor 871/150 @stefanneculai 1.x 停止维护
2.0 rc
Draft.js facebook/ draft-js 8609/708 ? (facebook) ? React
ProseMirror ProseMirror/ prosemirror 2322/154 @marijnh beta
Quill quilljs/ quill 11871/697 @jhchen (salesforce) 1.x
Slate ianstormtaylor/ slate 2680/123 @ianstormtaylor beta React

其他已经没有维护的

  • NicEdit
  • YUI Editor
  • Maqetta

浏览器兼容性

以下乃基于各自文档或测试说明,可能在低版本浏览器上部分功能会降级,或存在bug。
注1:E = Edge, Ch = Chrome, Saf = Safari, FF = Firefox
注2:~ 表示通常对 evergreen browser 的支持,即最近两个 stable 版本;某些项目的文档里写的是支持最新的 stable 版本,但应该不会有什么差别。

IE E Ch Saf FF Android iOS Others
UEditor 1.4.x IE6+ ?
UEditor 1.5.x IE8+ ?
UEditor 2.x IE9+ ?
UMEditor 1.2.x IE8+ ?
wangEditor IE8+
wangEditor-mobile Chrome ? UC, QQ, 微信
CKEditor 4 IE8+ ~ ~ ~ ~ Chrome iOS 6+
CKEditor 5 IE11 ? ~ ~ ~ ~ ~
TinyMCE IE8+ ? 1+ 5+ 3+
wysiwyg.js IE6+ ? 4+ 3.1+ 3.5+
Aloha Editor IE8+ ? 11+ 4+ 3.5+
ContentTools IE9+ ? ~ ? ~
MediumEditor IE9+ ~ ~ ~ ~
Subtance IE10+?
Froala V2 IE10+
ProseMirror IE11 ~ ~ ~ ~
Draft.js IE10+ ~ ~ ~ ~ ~ ~
Quill IE11 13+ 47+ 9+ 44+ 5.0+ iPhone 9.3+
Slate ? ? ~ ~ ~

应用框架一览

Node.js

  • express
  • hapi
  • tower
  • geddy
  • sails

Ruby

  • ror
  • sinatra
  • phoenixframework

Python

  • Flask

Lua

  • sailor

Scala

  • play

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.