作者归档:c君不在

少数与多数

如果说封建王朝是少数人的暴力统治,那么民主制度则是多数人的暴力统治。

随想

进入一辆在烈日下暴晒了几个小时的大巴几秒后——马上体会到父母把孩子留在封闭的小车中到底有多危险了——差点当场烤熟了。

使用免费产品时,等价交换的筹码就是用户自己。歌手、演员等价交换付出的是自己的作品,而偶像的作品则是自己,这里包含了个人外在形象,表现出来的才艺;前者可以说是卖艺不卖身,后者则是“卖身”。

年少轻狂只想构建理想乡,却发现一文钱难倒英雄,毕竟未经历战争的人会发起战争来实现“理想”,而经历过战争的人则稳定压倒一切。

EventSource

EventSource 是浏览器端配合服务端事件(Server-Sent Events)使用的技术。和 WebSocket 相比,服务端事件是单向推送,而 WebSocket 是双向通信。

浏览器端

// 第二个参数是可选的,withCredentials 目前用于跨域 cookie,默认 false
const es = new EventSource(url, { withCredentials: true });
es.addEventListener('error', err => console.error(err));
// 监听 message 事件
es.addEventListener('message', evt => console.log(evt.data));
// 监听 type/custom 事件
es.addEventListener('type/custom', evt => console.log(evt.data));

EventSource 要求的是持续不断的输出,如果请求正常地结束终止了,那么客户端会再次自动发起连接,除非显式地调用 close() 方法来结束连接。

服务端

服务端的实现是返回标记为 Content-Type: text/event-stream 的持续内容,其内容的基本格式是:

event: evt-type
data: any message

其中,event 对应 EventSource 监听的事件类型,但该内容是可选的,如果没指定 event,那么则被 message 事件监听(可简化为 es.onmessage)。

紧邻的 data 字段会把内容合并起来,连接内容是一个空行。

直接以 : 开头的内容则是注释,不被解析使用。

继续阅读

Proxy in JavaScript

可以代理一个对象,对读取、赋值等操作做拦截,从而可额外地做一些事情,对于没有拦截的操作,则按原对象操作调用。

let p = new Proxy(target, handler);

通过 handler 来提供以下方法(this 指向 代理后的对象,该对象的 [[handler]] 指向 handler):

  • get(target, prop, receiver) 拦截属性读取操作
  • set(target, prop, val, receiver) 拦截设置属性值操作
  • has(target, prop) 可以看作是对 in 的操作的拦截
  • apply(target, thisArg, args) 拦截函数的调用,包括普通调用和 applycall 等。
  • construct(target, args, newTarget) 拦截 new 操作符
  • defineproperty(target, prop, descriptor) 拦截 Object.defineProperty 的操作
  • deleteProperty(target, prop) 拦截 delete 操作
  • getOwnPropertyDescriptor(target, prop) 拦截 Object.getOwnpropertyDescriptor
  • getPrototypeOf(target) 拦截原型对象的读取,包括 Object.getPrototypeOf()__proto__ 以及 instanceof
  • isExtensible(target) 拦截 Object.isExtensible()
  • ownKeys(target) 拦截 Object.getOwnPropertyNames() 以及 Object.keys()
  • preventExtensions(target) 拦截 Object.preventExtensions(obj)
  • setPrototypeOf(target, proto) 拦截 Object.setPrototypeOf

receiver,Proxy 或继承 Proxy 的对象,最初被调用的对象,通常就是 proxy 自身。但如果对象本身无该属性,原型链上有 Proxy 对象,那么这时对象本身就作为 receiver 参数传进来了。1

不仅使用于 Object,也使用于 Reflect,拦截具体对应的操作可能有所区别

Proxy.revocable(target, handler) 创建一个 proxy 并返回撤销该 proxy 的方法。

const { proxy, revoke } = Proxy.revocable(target, handler);
// .. do something with proxy
// 撤销代理
revoke();

参考

饭要一口口吃

人类能走到今天,是心底那渴望活得更好的念头,是利己之心。所以,在大千世界中看到有更好的东西时,总希望自己也能拥有。

但,路要一步步走,饭要一口口吃。大多数人没法 21 天变成编程高手,一个社会没法还在以小康为目标的情况下提供世界一流国家的福利。

以这为目标,订下五年计划、一年计划,一步步走,一步步前进,这才是可行之法。

谁都有能力提出自己的不满、社会的不到位之处,能提出问题很好,倘若能进一步思考甚至得出可行的解决方案,这会功德无量。

居安思危

中兴事件、华为事件再次提醒我们,即使这是一个全球联通的社会,但自己还是需要分散风险,不要把鸡蛋放在一个笼子里,甚至核心技术自己需要有积累。

大到一个国家,核心技术就是必须要掌握的,从民生、能源、新技术都必须掌握,否则当世界霸主利用其影响力压制时将无法生存下去。同时,掌握了技术也不要断绝交流,否则又会陷入夜郎自大的危境。

OmniPlan 使用

基本流程

  • 创建项目
  • 设定开始时间/结束时间
    • 构思阶段用「未确定」日期
    • 普通用「从固定开始时间向前」
    • 倒推用「从固定结束时间后退」
  • 创建里程碑(重要变更或新阶段开始)
  • 创建任务
  • 编辑工作周
  • 设置计划表例外
  • 设置任务持续时间
  • 分组任务
  • 使用关联线连接任务(表示的是任务的依赖关系)
    • 完成->开始(A完成B才能开始)
    • 开始->完成(A开始B才能完成)
    • 开始->开始(A开始B才能开始)
    • 完成->完成(A完成B才能完成)
  • 创建资源
  • 分配资源
  • 个别设置计划表例外
  • 分级资源
  • 设置基线
  • 查看重要路径
  • 更新任务完成情况

其它信息

  • 重要路径
    • 关联性任务
    • 吊床任务(该吊床任务的时间受开始时间和结束时间约束。一般是前一个任务完成后才能开始该任务,且该任务的截止时间也被后面的任务约束了)
  • 分级确定占用资源优先级的顺序
    • 要求在特定时间结束的需求
    • 任务优先级,数字越大越优先
    • 任务大纲中的位置,越高越早

电子竞技

对电子竞技基本没有了解,不过偶尔玩玩游戏,经常看看直播。

电子竞技和普通的体育竞技有点不同,体育竞技,在一段时间内——特别是赛季进行中,规则和竞技内容都是比较固定不变的。但电子竞技不同,先不弹游戏的变更,只说同一个游戏。

一个电子游戏的竞技,在赛季中,规则还好,胜负的条件基本不会变更。但内容就不一定了,电子游戏会经常迭代更新,甚至赛季中就更新版本。

电子游戏的竞争,看上去就是一场“战争”,所以需要知己知彼。

需要了解游戏的内容,磨练自己的战术体系,探测对手的战术体系。而游戏的迭代更新,容易造就之前搭建的战术体系随着版本变更而不再适用,如果没能迅速在新版本中重新构建自己的战术,那么就很容易出现一支队伍之前战无不胜突然在新版本中就无所适从的状况出现。

虽然自己技术过硬才是重点,但涉及到对抗,那就必须要了解对手,了解他们的英雄体系以及基于这些因素而展现出来的战术体系。越是风光,越被别的竞争对手认真研究。相反,站在顶峰的人,容易只针对自己认为的对手来下功夫,而别的队伍则忽略了。这就容易看到所谓的“黑马”,如果黑马不仅仅是战术新颖,还用有了过硬的技术,那么就会变为太阳而不再是流星。

现代的战争是全方位的战争,成型的电子竞技也是全方位的竞争。选手需有练英雄练技术跟进版本变更,教练等需要基于选手技术以及版本来构建阵容和战术体系,后勤团队需要做好包括队员生理保养、心理调整等的俱乐部后勤支持,还有其它团队需要做好盈利问题。

一代版本一代神,每支队伍在输之前都是最强的组合。

基于ANSI转义序列来构建命令行工具

命令行工具在输出时,如果是简单的进度更新,可以使用 \r\b 来达成刷新行的效果,但如果要更复杂些的如字体颜色、背景颜色、光标位置移动等功能,那就需要使用 ANSI 转移序列了。

已有不少成熟的命令行工具库,如 ReadlineJLinePython Prompt Toolkit,基于这些库创造了如 mycliipython 等好用的工具。

ANSI 转义序列有比较悠久的历史,不同平台支持的功能不完全一致,这里学习到的是比较简单常用的,包括字体颜色、背景色和其它装饰的富文本和光标操作。

\u001b 即 ESC 的 ASCII 码,\u001b[0m 是清除之前的设定。

适用于 *nix 的系统。

富文本

前景色

8 色

\u001b[?m,其中 ? ∈ [30, 37]

  • 黑(black):\u001b[30m
  • 红(red):\u001b[31m
  • 绿(green):\u001b[32m
  • 黄(yellow):\u001b[33m
  • 蓝(blue):\u001b[34m
  • 品红(magenta):\u001b[35m
  • 蓝绿(cyan):\u001b[36m
  • 白(white):\u001b[37m
  • 重置(reset) :\u001b[0m

16 色

在 8 色的基础上对字体加粗,颜色加亮,得到另外 8 种,加起来就是 16 色。

\u001b[?;1m,其中 ? ∈ [30, 37]

  • 亮黑(black):\u001b[30;1m
  • 亮红(red):\u001b[31;1m
  • 亮绿(green):\u001b[32;1m
  • 亮黄(yellow):\u001b[33;1m
  • 亮蓝(blue):\u001b[34;1m
  • 亮品红(magenta):\u001b[35;1m
  • 亮蓝绿(cyan):\u001b[36;1m
  • 亮白(white):\u001b[37;1m
const out = process.stdout;

function colors8(pre, post, startCode = 30) {
  const codePointA = 'A'.codePointAt(0);

  let i = 0;
  while (i < 8) {
    const colorCode = startCode + i;
    const char = String.fromCodePoint(codePointA + i);
    out.write(`${pre}${colorCode}${post}${char} `);
    i++;
  }
  console.log('\u001b[0m');
}

function fgColors8() {
  colors8('\u001b[', 'm');
}

function fgColors8Bright() {
  colors8('\u001b[', ';1m');
}

fgColors8();
fgColors8Bright();

继续阅读

浏览器下载 PDF 文件

用浏览器访问一个指向 PDF 文件的链接,具体表现出来的行为会有所区别,最主要的区别就是在浏览器中打开(记作预览)或触发下载(记作下载)。

这些行为与请求响应结果中的 Content-Type 以及 Content-Disposition 这两个 header 有关。

Content-Typeapplication/pdfapplication/octet-stream 以及 application/custom-unknown 和无 Content-Disposition、取 inline 以及 attachment 来做组合实验,得到结果如下:

header Chrome Firefox Safari
Content-Type: application/pdf 预览 预览 预览
Content-Type: application/pdf
Content-Disposition: inline
预览 预览 预览
Content-Type: application/pdf
Content-Disposition: attachment
下载 下载 下载
Content-Type: application/octet-stream 下载 下载 下载
Content-Type: application/octet-stream
Content-Disposition: inline
下载 下载 下载
Content-Type: application/octet-stream
Content-Disposition: attachment
下载 下载 下载
Content-Type: application/custom-unknown 下载 下载 下载
Content-Type: application/custom-unknown
Content-Disposition: inline
下载 下载 下载
Content-Type: application/custom-unknown
Content-Disposition: attachment
下载 下载 下载

Content-Typeapplication/octet-stream 表示浏览器应当把这个文件作为二进制数据来对待。但实际上浏览器可能会自己根据 Content-Disposition 甚至文件内容来做些文件格式探测,从而使用浏览器内置的功能或插件功能来处理。上面用的 application/custom-unknown 就是特意设计一个不是通用的 type 来避免已有场景。

下载时,如果没通过 Content-Disposition 指定文件名(如 Content-Disposition:inline;filename=attach.pdf),那么下载后的文件的名称通过 url 来获取,如 /download.php 得到的是 download.php

Firefox 在触发下载时,如果 Content-Dispositioninline,那么会静默下载,而不会像 attachment 或无该 header 时那样弹出浏览器的保存弹框。

Safari 下载后,如果是 pdf 文件(通过 Content-TypeContent-Disposition 或其它方式识别出来),那么会自动打开该文件。

此处使用 pdf 来作为示例,对于其它类型的文件,浏览器和系统的规则推测应类似。

  • 操作系统:macOS Mojave 10.14.2
  • Chrome: 72.0.3626.121(正式版本) (64 位)
  • Firefox: 65.0.1 (64 位)
  • Safari: 12.0.2 (14606.3.4)

参考