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

访问者模式中的分派概念

images.png

静态绑定

静态绑定就是指在编译期就已经确定执行哪一个方法。函数的重载(方法名相同而参数不同)就是静态绑定的,重载时,执行哪一个方法在编译期就已经确定下来(编译时多态)。

动态绑定

动态绑定实在运行时判断所引用对象的实际类型,根据其实际的参数类型调用其相应的方法(运行时多态) 。

双分派(double dispatch)

分派过程就是确定一个方法调用的过程,双分派就是见人说人话,见鬼说鬼话,将静态绑定和动态绑定结合起来,也就是说根据运行时传入对象的类型确定方法调用的过程。
重载是静态绑定,多态是动态绑定(运行时进行),双分派把多态放在重载之前,以实现在运行时动态判断执行那个子类的方法。因为编译器知道this所指向的实例的类型,因此将其作为参数传入到函数中就可以实现。

class Man : public Base {
public:
    int getSpeakRes(SpeakBase& speak) {
        return speak.speakWord(this);  //编译器知道this是哪个类型实例
    }
}

class Ghost : public Base {
public:
    int getSpeakRes(SpeakBase& speak) {
        return speak.speakWord(this);
    }
}

class Speak : public SpeakBase {
public:
    int speakWord(Man& man) {
        return "Man"; 
    }
    int speakWord(Ghost& ghost) {
        return "Ghost"; 
    }
}

参考文章:

https://www.cnblogs.com/loveis715/p/4423464.html
http://yaoyao.codes/c++/2015/04/26/cpp-double-dispatch
https://www.typescriptlang.org/docs/handbook/functions.html#overloads
https://www.mikealche.com/software-development/refactor-long-if-or-switch-blocks-with-double-dispatch-in-javascript

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'('+_+')()'})()
  • f=_=>`f=${f};f()`;f()

参考文章:

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