海屿文章列表

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.

弹性布局与最小宽度

By default, flex items won’t shrink below their minimum content size

这意味着项目的最小宽度被设置为其内容的宽度,并且不会缩小到该宽度,所以这里使用overflow: hidden无法让其收缩。

broken.png

flex 布局的元素会默认设置min-widthmin-width默认值为 0,但规范中对于flex项目则设置为auto。这可能会使块状元素占用比预期更多的空间,甚至使它们的容器超出小屏幕的屏幕边缘。

参考阅读:

这里是对规范的讨论 :https://github.com/w3c/csswg-drafts/issues/2248
最新规范:https://drafts.csswg.org/css-sizing-3/#valdef-width-auto

For width/height, specifies an automatic size. See the relevant layout module for how to calculate this.
For min-width/min-height, specifies an automatic minimum size. Unless otherwise defined by the relevant layout module, however, it resolves to a used value of 0. For backwards-compatibility, the resolved value of this keyword is zero for boxes of all [CSS2] display types: block and inline boxes, inline blocks, and all the table layout boxes. It also resolves to zero when no box is generated.

2019 的最后一天 🌌

illustration-future-with-2020-2030-year-numbers_105589-817.jpg
2019 年的最后一天,这一天平淡无奇,也毫无疑问在加班,十一点半到家,继续写这篇文章。所以我在想如果再经过 3650 个像今天一样的一天,那么在 2030 我会成为什么样的我?这期间可以想象的会换几份工作,最终找到一个稳定的工作,在某一天遇到另一半,又在某一天结婚某一天生子。
唯一可以确定的是2020 与下一个十年注定是我一生中最辉煌,最精彩的十年,不出以外的话下半生的基调也就由这十年定下。

回想2010至今这过去十年,并没有太多感觉,虽然也是平淡的校园生活,最后实习工作,但是对新知识有一种天然的渴望,无论环境如何,总有一种向上生长的力量,这股力量在将要迷失自我的时候总能让我找回方向,而这股力量我又要归上上个十年和我的父母所赐。那下个十年我要积累一些什么,来给下下个十年以力量,给父母回馈?

回到今天,最近年底感触最深的就是大家的焦虑,企业裁员,中年职场危机,职业天花板等等。
政治、经济形势也可以预期,只会更差。这些残酷的现实,裹挟着每一个人生活。如何在这洪流中不苟且,也许真的不是一件容易的事。

在职业发展上作为前端,其应用场景决定了这个职业上限是不高的,填坑是少不了的,也许实体行业中会好一些,但在互联网行业中,前端代码每个月都要替换上个月的样式或者逻辑再正常不过,如此往复,留下的很多所谓的"经验“。而这些所谓的经验又无非兼容,框架,真正经得起时间考验有能有都少?我个人并不想对互联网行业严格的区分前端和后端,都是计算机科学,一些范式与思想都是通用的,算法更不必说。所以无论何种岗位,只要是计算机相关的职业,唯一能都沉淀下来有价值的都是一些不变的知识,如数学,算法,计算机操作系统,计算机网络。

在朝九晚五为奢侈品的互联网行业中,每天加班,再加之未来成家立业之鸡毛蒜皮,自由时间少之又少,这些自由时间是将上层的经验逐渐沉淀下来的关键。虽然工资比较高,但是与生活成本和付出的时间精力,其实对多数人来说无非是透支一生中的最黄金十年。在企业眼中员工如同电池,如果没法输出峰值功率的时候,所以只有换掉的命运。

也许不沦为平庸就是锐意进取。而不平庸的关键是利用好自由时间。无论如何自由时间也不会和校园生活一样多,所以我们更需要一些方法论。假如我们沉淀下了这些底层的知识,但这其实还没有结束,知识还是死的,只有智慧是活的。
借用老子的一段话:

为学日益,为道日损 ——老子《道德经》第四十八章

“为学日益” 学问是靠知识、读书、经验,一点一滴慢慢累积起来的。
“为道日损” 学道与做学问相反,是要从学问中提炼,丢掉无关紧要的部分

下面是DIKW模型,也反映这个道理。

480px-DIKW_Pyramid.svg[1].png

写到这里我心中对未来也有了大致的规划,如果你在看这篇文章,希望你也能有所收获。

看看其他人的感悟💖
【编程随想】时间与人生——跨入本世纪20年代的随想

最大潜在优先输入延迟(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/

Promise 重试

Promise Retry (1).png

Promise cache 链

Promise 常见的用法是 .then() 链式调用,其实.cache() 也是可以链式调用的。
首先初始化一个Promise.reject() ,之后需要重试几次就后面添加几个cache,如果需要满足一定条件或需要延迟一定时间还可与再继续添加.then(test).catch(rejectDelay);

const max = 5;
let p = Promise.reject();
const attempt = () => {
    console.log(`attempt`)
    return Promise.reject('error')
};

for(var i=0; i<max; i++) {
    p = p.catch(attempt);
    // 可以继续追加 then(test).catch(rejectDelay);
}

p.then(res => console.log(res)).catch(e => console.error(e));

这种方式需要保证:

  • 只有在指定的最大重试次数下才有可能。(链的长度必须有限)
  • 建议使用较小的重试次数。(Promise 链消耗的内存与其长度成正比)

如果不满足这几种情况,使用递归才是最好的选择。

递归

递归方式比较简洁,本质与Promise cache 链的实现是一样的,区别只是在失败之后才添加下一个catch链。

function retry(fn, retries=3, err=null) {
  if (!retries) return Promise.reject(err);
  return fn().catch(err => {
      return retry(fn, (retries - 1), err,);
    });
}

参考文章:
promise-retry-design-patterns