默认分类
只是一个默认分类

JavaScript 中的 Quine

Quine:一个能够打印自己源代码的程序

你可以试试执行以下

!function $(){console.log('!'+$+'()')}()

为什么起作用?上面的代码使用了一些技巧。

获取源代码最简单的方式是使用String

function foo() { return "abc" }
String(foo)

// function foo() { return "abc" }

所以我们第一个版本 可以是这样

function $() { console.log(String($)) } ,但是我们需要再执行 $() 才可以。
所以我我们使用IIFE继续进行改造。

!function $() { console.log(String($)) }()

//function $() { console.log(String($)) }
//true (!undefined的结果为true)

最后加上感叹号即可。

看看下面这个,意思也一样((((function $(){console.log('(((('+$+'()))))')}()))))

更多有意思的例子:

  • (function _(){return'('+_+')()'})()
  • (_=()=>(_=${_})())()

参考文章:

http://rosettacode.org/wiki/Quine#JavaScript
https://2ality.com/2012/09/javascript-quine.html

Wall clock 与 Monotonic clock

reloj_indefinido.jpg
现代操作系统通常有两个时间,Wall clock 和 Monotonic clock。

Wall clock 就是我们一般意义上的时间,就像墙上钟所指示的时间。该时钟可能会发生变化。例如,如果它与NTP(网络时间协议)同步。在这种情况下,同步后,我们服务器的本地时钟可以在时间上向后或向前跳。因此,从挂钟开始测量持续时间会产生偏差。

Monotonic clock 字面意思是单调时间,实际上它指的是从某个点开始后(比如系统启动以后)流逝的时间,单调时间重要的不是当前值,而是保证时间源严格线性增加,因此对于计算时间差很有用。

Rust: std::time::Instant.

Ruby: Process.clock_gettime(Process::CLOCK_MONOTONIC). Or I wrote monotime as a nicer alternative.

Python: time.monotonic().

PHP: hrtime as of 7.3.

C: on POSIXy platforms, clock_gettime() with CLOCK_MONOTONIC. On Windows, there's QueryPerformanceCounter.

C++: std::chrono::steady_clock, though be wary about it on older Visual Studios.

Java: System.nanoTime() should be monotonic on most platforms.

最大潜在优先输入延迟(Max Potential First Input Delay)

maxresdefault.jpg

我们衡量一个页面的首屏,往往只关注了首屏的渲染速度,而忽视了交互维度的评价。

JavaScript在单线程环境中运行,当一个任务执行时间过长就会阻塞线程,其他所有任务都必须等待。在移动设备上情况更糟。任务可能要花费3-5倍的时间。

什么是最大潜在优先输入延迟

让我们定义第一个输入延迟的含义,因为它不包括所有用户交互。

FID可测量诸如单击,按键和在字段中输入文本之类的操作。它不包括滚动或缩放,因为它们可以由浏览器在单独的线程上运行。

评分规则

最大潜在FID时间(ms)颜色编码
0–130绿色(快速)
130-250橙色(平均)
超过250红色(慢)

与TTI的区别

TTI测量页面变为完全交互式所花费的时间。它与FID是比较相似的,但TTI是在没有任何用户在场的情况下进行测量的指标,是一种理想情况下指标,但是并不能真实的反应用户的体验。

如何优化

优化TTI的策略同样适用于FID,如果更有效地交付JavaScript以缩短互动时间,那么它也会缩短“首次输入延迟”。

我们与优化的核心思路是合理的加载JS,仅在必要时加载必要JS,推迟或删除不必要的JavaScript。最常见的做法是拆分代码并按照优先顺序排列要加载的代码,这样不仅可以缩短页面可交互时间,还可以减少耗时较长的任务。但是我们还可以把粒度做得更细,通常视窗的高度是小于整个页面的高度的,所以我们可以使用懒加载策略,让位于页面下方首屏不可见的组件和图片只在滚动到可见区域是动态加载。

延迟

延迟加载了JS,我们还可以考虑将一些延迟进行一些初始化计算。React 的 Fiber 渲染引擎就是在这方面做的优化。

对SSR项目的进一步优化

首先我们看一下SSR页面解析的过程:
hydrate@2x.png
为什么要对SSR页面额外进行关注?因为用户看见页面的时间提前了,但是客户端依然要解析执行JS,如果不做优化的话,可交互时间变化可能不大,所以之两个之间点的跨度反而变大了。

下面是一个实际SSR项目的截图:

当然我们可以继续用上述思路优化,但是对于SSR项目我们还可推迟 hydration (客户端激活),或者部分 hydrationPartial Hydration)。

客户端激活,指的是在浏览器端接管由服务端发送的静态 HTML,使其变为由 框架 管理的动态 DOM 的过程。

例如一些组件只包含文本和图像,但除了呈现内容外没有其他功能。如果这些组件的呈现的HTML输出无论如何都不会发生变化,没有必要加载JavaScript代码并对服务器端呈现的代码进行昂贵的 hydration处理。

下面是 vue-lazy-hydration 中选取的一段代码

 const id = requestIdleCallback(() => {
        requestAnimationFrame(() => {
          this.hydrate();
        });
      }, { timeout: this.idleTimeout });
 this.idle = () => cancelIdleCallback(id);

原理是提取出 lazy-hydration 部分的标签名,然后当作异步组件的占位符,在满足一定条件后再返回 child(这个条件可以是各种事件,或组件到可视区域,或者浏览器主线程处于空闲时)。

// vue 源码截取
export function isAsyncPlaceholder (node: VNode): boolean {
  return node.isComment && node.asyncFactory
}

// lazy-hydration render 截取
const child = this.$scopedSlots.default
      ? this.$scopedSlots.default({ hydrated: this.hydrated })
      : this.$slots.default[0];

if (this.hydrated) return child;

const tag = this.$el ? this.$el.tagName : `div`;
const vnode = h(tag);
vnode.asyncFactory = {};
vnode.isComment = true;

参考文章:
https://premium.wpmudev.org/blog/how-to-improve-google-pagespeed-user-interaction-metrics-in-wordpress/
https://philipwalton.com/articles/idle-until-urgent/
https://markus.oberlehner.net/blog/how-to-drastically-reduce-estimated-input-latency-and-time-to-interactive-of-ssr-vue-applications/
https://addyosmani.com/blog/rehydration/

git push -f 的找回

首先使用 Github’s Events API 来拿到丢失提交的 SHA。如果是 Private 项目需要配置 token 等。
curl https://api.github.com/repos/<user>/<repo>/events

然后用 Github’s Refs API 用这个提交的 SHA 新建一个 recover 分支.

curl -i -H "Accept: application/json" -H "Content-Type: application/json" -X POST -d '{"ref":"refs/heads/recover", "sha":"<orphan-commit-id>"}' https://api.github.com/repos/<user>/<repo>/git/refs

这一步如果遇到 "message": "Not Found" 错误,则需要到 Developer settings > Personal access tokens > 勾选repo 生成一个 token,然后用查询字符串拼在URL后面 ?access_token=

返回 201 则表示成功, pull一下就会出现 recover 分支。

参考文章:https://stackoverflow.com/questions/10098095/git-can-i-view-the-reflog-of-a-remote/35273807#35273807

Js 中的 CSP(Communicating Sequential Processes)

dd839ff45691d9737ce18a35a1f27f5c.jpg

什么是 CSP(Communicating Sequential Processes)?

CSP是由托尼霍尔在1978年发表的一种形式语言规范,中文翻译为通信顺序进程,用于描述并发系统中的通信模式。Go语言原生支持,Clojure有core.async。

并发的系统的实现

为了设计并发系统,并发进程/任务/作业/应用程序必须能够进行通信。广泛采用的方法是使用公共存储(数据源)。但是,如果数据源以不需要的顺序写入和读取,则问题是竞争条件和不可靠性。

  • 公共存储

    • Threads
    • Locks
    • 互斥 Mutexes
  • 消息传递 (CSP and Actor Model)

    • Processes
    • Messages
    • No shared data

并发不是并行

例程A将消息放入某个通道。然后停止,直到例程B准备好接收该消息。在服用它之后,B运行直到它接下来接受指令并且退后,因此常规A可以再次运行并且它可以无休止地运行。

没有并行性的并发性:不是操作系统的线程,而是将一个操作系统线程分段使用。

csp_illustration2.png

下面使用js-csp中的一个例子,两个process之间打乒乓球:

import {
 go,
 chan,
 take,
 put,
} from 'js-csp'
const ping = chan()
const pong = chan()
go(function * () {
 yield take(ping)
 console.log('ping')
 yield put(pong, true)
})
go(function * () {
 yield take(pong)
 console.log('pong')
 yield put(ping, true)
})
put(ping, true)
// > 'ping'
// > 'pong'
// > 'ping'
// > 'pong'
// > 'ping'
// > 'pong'
...

CSP与 Actor 模型

Actor模型,又叫参与者模型,其”一切皆参与者(actor)”的理念与面向对象编程的“一切皆是对象”类似,但是面向对象编程中对象的交互通常是顺序执行的(占用的是调用方的时间片,是否并发由调用方决定),而Actor模型中actor的交互是并行执行的(不占用调用方的时间片,是否并发由自己决定)。

从actor自身来说,它的行为模式可简化为:

  • 发送消息给其它的actor
  • 接收并处理消息,更新自己的状态
  • 创建其它的actor

区别

  • Actor中实体和通信介质是紧耦合的,一个Actor持有一个Mailbox,而CSP中process和channel是解耦的,没有从属关系。从这一层来说,CSP更加灵活
  • Actor模型中actor是主体,mailbox是匿名的,CSP模型中channel是主体,process是匿名的。从这一层来说,由于Actor不关心通信介质,底层通信对应用层是透明的。因此在分布式和容错方面更有优势。

关键概念与实现

  • 顺序处理(processes)
  • 通过通道(channel)进行同步通信
  • 通道的多路复用与交替

channel 是一个队列,Processes 通过channel进行通信,Processes 可以暂停来等待 channel 中的值。
processer 是一个Pull模型的迭代器,它不断从 channel 取出最新的值,如果没有值就暂停执行。

Processes

async function process (inChannel, outChannel) {
  while (true) {
    const msg = await inChannel.take();
    // do stuff with msg
    await outChannel.put(res);
  }
};

我们使用[Symbol.asyncIterator]channer成为可迭代对象,然后使用for-await-of语法会更加简洁,同时通过这个while(true)不难理解 process 一直从channer拉取数据:

public async *[Symbol.asyncIterator](): AsyncIterableIterator<T> {
    while (true) {
        yield await this.take();
    }
}
async function process (inChannel, outChannel) {
  for await(const msg of inChannel) {
    // do stuff with msg
    await outChannel.put(res);
  }
};

Channel

在Channel中实现了messages putters takers racers 这几个队列。
put 操作返回一个Promise,在该Promise中 首先将传入的参数放到messages,然后将resolve函数放到putters,然后判断takers队列是否有值。

  • 如果已经有taker 或racers等待消息,则返回的承诺将立即得到解决
  • 如果taker 和racers都在等待消息,则优先权将给予将检索该消息的接受者

可以对channel队列进行操作,例如过滤,遍历等等,多个channel之间还可以广播,或者合并。

更多资料

https://arild.github.io/csp-presentation/
https://github.com/jfet97/csp
https://pusher.com/sessions/meetup/the-js-roundabout/csp-in-js
Javascript中的CSP快速入门