2025年

@gaubee/util 包中的 delay 函数,相比于市面上的 delay 函数,有着很特别的能力,就是它不仅仅是可以传递一个数字(毫秒),还可以传递一个 timmer 对象。

以下是详细的能力介绍:

  1. delay(0) 0 毫秒,那么它不会使用 setTimeout 来计时,而是会使用 queueMicrotask 来创建延迟队列。然而你知道 await 关键词本身就是在创建一个 queueMicrotask 队列,不同的是,const delayer = delay(0) 这里的 delayer 对象是可以进行取消的 delayer.cancel(reason?)
  2. delay(10) 等同于 setTimeout/clearTimeout
  3. delay(timmers.raf) 等同于 requestAnimationFrame/cancelAnimationFrame
  4. delay(timmers.eventTarget<AnyEvent>(window,'scrollend')) 等同于 addEventListener/removeEventListener(浏览器 EventTarget)
  5. delay(timmers.eventEmitter<AnyArgs>(event,'scrollend')) 等同于 addEventListener/removeEventListener(nodejs 的 EventEmitter)
  6. delay(pureEvent<AnyType>().once); 可以将一个 pureEvent 的 once 函数直接传递进去
  7. 你可以可以完全自定义什么时候进行 resolve,并返回解构函数:
    delay((resolve, reject) => {
      some.on(resolve);
      return () => some.off(resolve);
    });
    

Minimal CSS-only blurry-image-placeholders(LQIPs) 这是一个天才般的想法,它把原本需要用 js 解码的工作,直接放到 css 表达式里,不仅仅是计算加快了,而且消除了很多中间成本。


我们项目有用到类似的需求。在我们项目中,图片名称(url)的一部分包含了 blurhash。 简单来说,我们使用文件名来存在 blurhash,然后将这个 blurhash 字符串解码成图片,但这是有代价的,需要用一个小 canvas 绘制:

  1. 首先用算法绘制出模糊图片然后将它绘制到 canvas 上(消耗 CPU);
  2. 然后将图片导出数据(消耗 CPU 和内存,这里做一次编码);
  3. 最后将数据转成 blob-url(消耗 IO);
  4. 最终设置 image-src(消耗 Network-IO,这里做一次解码)。

尽管我已经将大部分流程放到 webworker 中,使用 offscreencanvas 来生产最后需要的 blob-url,从而避免对主线程的消耗,但是总成本是不会减少的。

这篇文章的方案,直接将算法硬编码到 css 表达式中,直接输出图片。原本依赖 js 方案中,很多中间的 CPU 和 IO 成本都消解了。 本质是把 background-image 直接当作一个 canvas 来进行绘制。所以我说它是天才般的想法!!

确实,如果直接在 image 上放一个绘制 blurhash 的 canvas,那么也就没那么多 CPU 和 IO 成本了。 然而如果真的放 canvas,内存成本会非常多,而且不同浏览器对于 canvas 的数量还会有限制。


于此同时,css-only 方案还有一个巨大的改进,就是图片的精度。 我们知道 background-image 到 gradient 绘制,底层走的是 GPU 绘制,它是极快的。因此不论图片尺寸多大,它始终是可以非常高分辨率的(当然你也可以用 backgrond-size 来控制最终的分辨率)。 虽然是模糊图片,但是高分辨率的模糊和低分辨率的模糊是两码事情。 低分辨率的图片,放大的时候,浏览器走的是 图像插值算法 ​​(Image Interpolation)(通常使用双线性插值(Bilinear Interpolation)​​ 或 ​​ 双三次插值(Bicubic Interpolation),有一个 css 属性可以控制这种差值算法的使用:image-rendering: auto|smooth|crisp-edges|pixelated;)。 然而这种插值算法的效果并不是那么理想,特别是我们用 blurhash 存储的其实就是几个图片像素而已,所以直接对一个低分辨率的图片进行插值,会出现很明显的“✨”效果。这里给一个例子:

3x2 Rainbow Pixels PNG

然而在 js 方案中,我们如果要避免插值算法对模糊图的影响,只能是输出更高质量的图片,比如从 3*2 提升到 6*4 或者 12*8,然而代价输出的 DataURL 变长,也就意味着 CPU 和 IO 的消耗增多。 因此在 css-only 方案中,这个问题可以从根源上避免,不用再受插值算法的困扰,使用 gradient 进行高精度的矢量绘制,不用担心模糊图的质量问题。

View Transitions API (Level-1 single-document) 进阶

上次咱们聊了 View Transitions 的基础,那感觉就像发现新大陆,丝滑得不行。但真正在复杂的 SPA(单页应用)场景里用起来,尤其是想模拟原生 App 那种细腻的转场效果时,你可能会发现,这“丝滑”背后,可能藏着一些“蛋疼”的细节。

核心矛盾点View Transitions Level 1 的设计哲学是针对单个文档内 DOM 状态变化的视觉过渡。而 SPA 的常见模式是在单个文档里模拟多个“页面”的导航切换。这种模式上的错位,是许多复杂问题的根源。Level 1 并没有“页面”或“路由”的概念,它只关心“变化前”和“变化后”的 DOM 快照。

接下来,咱们通过一些实践案例,深入探讨在 SPA 中应用 View Transitions 的复杂性、局限性以及特定场景下的思考。

一、SPA 导航模拟:看起来很美,做起来费心

首先给出 DEMO 链接: ios-navigation demo by view-transition

咱们来看一个常见的需求:在 SPA 里模拟类似 iOS 的导航栏切换效果。这个效果细节不少:

  1. 页面整体:新页面从右侧滑入,覆盖旧页面。
  2. 返回按钮图标 (backIcon):在切换过程中,位置保持不动(视觉上像钉在那里)。
  3. 返回按钮文字 (backText):由旧页面的标题 (title) “变形”而来。它不是简单地淡入淡出,而是从旧标题的位置平滑移动并变成返回文字。
  4. 新页面标题 (title):随着新页面整体从右侧滑入。

听起来用 View Transitions 的 view-transition-name 标记一下对应元素,浏览器就该自动搞定了吧?比如 Compose 或 SwiftUI 里的 sharedElement / matchedGeometryEffect,声明一下就完事儿了。

Observable vs Signals:响应式江湖的两大流派深度对决

在探讨了 Observable 和 Signals 各自的理念与实现后,你可能会有些疑问:这两个家伙,都号称搞定“响应式”,它们到底有啥不一样?我该用哪个?

别急,这一篇,咱就来掰扯掰扯 Observable 和 Signals 这对“响应式双雄”,通过对比,帮你建立更直观的认知。

一、核心哲学:动态数组 vs 动态函数

要快速抓住两者的神髓,不妨来看一个有点“玄学”但颇为形象的比喻:

Observable ≈ 动态数组 (Array + 时间) Signals ≈ 动态函数 (Function + 动态参数)

这话怎么理解呢?

Observable:时间轴上的珍珠项链

想象一个数组 Array,它是一系列静态的值的集合。现在,给这个数组加上时间维度——这些值不是同时存在的,而是随着时间推移,一个接一个地“推送”给你。这就构成了 Observable 的核心意象:一个随时间发生的事件序列(Stream)

Observable 关注的是整个序列的处理。你像处理数组一样,可以对这个事件流进行 map(转换每个事件)、filter(过滤掉某些事件)、reduce(聚合整个流的结果)、take(只取前几个)、debounce(防抖动)等等操作。它的核心在于处理流经的数据,以及这些数据在时间维度上的模式和关系。你订阅一个 Observable,就像是在说:“嘿,这条项链上的每一颗珍珠(事件)来了,都告诉我一声,我好对它(们)做点什么。”

深入浅出 Signals:下一代 Web/JS 响应式编程基石?

在 Web 开发的江湖里,状态管理一直是各大门派(框架)潜心修炼的核心内功。从早期的手动 DOM 操作,到后来的 MVC/MVVM,再到 Redux、Vuex 等集中式状态管理,我们一直在寻找更优雅、更高效的方式来处理 UI 与数据的同步问题。

近几年,“响应式编程”的理念异军突起,而 Signals 作为其一种重要的实现模式,在众多现代前端框架(如 Solid, Qwik, Preact, Vue, Angular 等)中崭露头角,甚至可以说是蔚然成风。现在,TC39(负责制定 ECMAScript 标准的委员会)也正式将其纳入议程,提出了 JavaScript Signals 标准提案

这葫芦里卖的什么药?它跟我们熟悉的 useState, ref, computed, watchEffect 有什么异同?它真的能成为下一代 Web 响应式编程的统一基石吗?

别急,让我们一起深入浅出地探索 Signals 的世界。

一、灵魂拷问:我们为何需要 Signals?

技术总是在解决痛点中前进。要理解 Signals 为何诞生并受到青睐,我们得先看看没有它的时候,开发者们(尤其是框架开发者们)遇到了哪些“不爽”。

想象一下,我们要实现一个简单的计数器,并根据计数器的奇偶性显示文本。用原生 JavaScript,我们可能会这么写(参考提案中的例子):

let counter = 0;
const element = document.getElementById("parity-display");

// 状态变更函数,耦合了渲染逻辑

深入浅出 Observable:驯服 Web/JS 异步事件流的“瑞士军刀”?

今天,跟大家聊聊一个在 Web 平台“难产”多年,但江湖上早已流传其传说、众多框架和库默默拥抱的家伙——Observable

是不是感觉 addEventListener 用得有点腻歪了?回调地狱、手动移除监听、组合复杂逻辑时的捉襟见肘……这些痛点,就像鞋里的小石子,时不时硌得慌。Observable 提案,就像一位身怀绝技的武林高手,试图用一种更优雅、更“函数式”的姿态,来解决这些前端事件处理的“疑难杂症”。

这篇文章,从它想解决的问题出发,一路扒开它的前世今生、核心概念、实战技巧,最后再一起畅想下它的未来。准备好了吗?发车!

一、Observable 的灵魂拷问——它到底想干啥?

任何技术的出现都不是空穴来风。Observable 想解决的核心痛点,其实就是我们日常与异步事件打交道时的“不爽”。

想想看,我们用 addEventListener 是怎么操作的:

const controller = new AbortController();
const signal = controller.signal;

function handleMouseMove(e) {
  console.log("鼠标移动:", e.clientX, e.clientY);
}

View Transitions API (Level-2 cross-document)

上一篇文章咱们聊了 View Transitions API (Level-1 single-document) 如何优雅地解决了 SPA(单页应用)里那为了动画而扭曲 DOM、编写复杂 JS 的痛点。通过 document.startViewTransition、快照机制和神奇的伪元素树,它成功地将 DOM 状态更新视觉过渡动画 解耦,让开发者能轻松实现丝滑的同文档视图切换。

但是,Level 1 的能力仅限于“家里面”(同一个文档)。一旦涉及到“出门串门”(跨文档导航,比如从 a.html 跳到 b.html),那熟悉的白屏闪烁又回来了。MPA(多页应用)的用户体验难道就只能停留在“上古时代”吗?

W3C 的大佬们显然不满足于此。于是,CSS View Transitions Module Level 2 应运而生,它的核心使命,就是将 Level 1 的丝滑体验,延伸到传统的跨文档导航场景,并在此基础上增加更多强大的功能!

今天,咱们就接着上一篇的步伐,重点探索 Level 2 的世界,看看它是如何打通跨文档的“任督二脉”,以及它带来了哪些令人兴奋的新特性!

一、初心不改:Level 2 的核心目标与设计哲学

Level 2 继承并扩展了 Level 1 的核心哲学:解耦 DOM 更新与视觉过渡。但它的目标更宏大:

  1. 拥抱 MPA: 正视 MPA 在 Web 生态中的重要地位,为其提供现代化的过渡体验。
  2. 声明式优先: 尽可能通过简单的 CSS(@view-transition 规则)来启用跨文档转场,降低接入成本。
  3. 生命周期钩子: 在跨文档导航的关键节点(旧页面卸载前、新页面展现前)提供 JS 事件 (pageswap, pagereveal),赋予开发者精细控制的能力。
  4. 能力增强: 不仅仅是解决跨文档问题,还基于实践反馈,加入了选择性转场、样式复用、自动命名、嵌套转场、分层捕获等一系列“武功秘籍”。

简而言之,Level 2 就是要在尊重并兼容传统 MPA 架构的前提下,将流畅转场的能力普及化、标准化,并让它变得更强大、更灵活。

View Transitions API (Level-1 single-document)

今天咱们来聊一个前端圈儿里越来越火的新玩意儿——CSS View Transitions。这东西就好比给你的网页换场加了个丝滑的电影转场特效,告别过去那种生硬的“啪嗒”一下切换页面的体验。

W3C 的大佬们捣鼓出的这个 CSS View Transitions Module Level 1 规范,目前已经是 CR(Candidate Recommendation)阶段,说明离咱们大规模用上不远了。咱们的目标是彻底搞懂它,从“这啥玩意儿?”到“哦豁,有点意思”再到“爷青回,这动画我自己写!”。

一、告别刀耕火种:View Transitions 要解决啥蛋疼问题?

想想以前,尤其是在 SPA(单页应用)里搞页面切换动画,那叫一个折腾:

  1. DOM 大乱炖:为了让新旧两个状态能同时存在并产生动画效果(比如旧的淡出,新的淡入),你可能得手动控制 DOM,让两个页面的内容在某个时间段内都挂在页面上。这 DOM 结构,简直是为了动画效果“牺牲色相”,乱七八糟。
  2. JS 胶水代码:得写一堆 JavaScript 来协调 DOM 的增删、CSS class 的切换、动画的开始结束监听。逻辑复杂,还容易出 bug。
  3. 性能与体验:DOM 结构复杂了,性能可能受影响;动画过程中,焦点管理、可访问性(ARIA)也容易出问题,用户体验可能打折。比如动画过程中,屏幕阅读器是读旧的还是新的?按钮能点吗?

核心痛点视觉过渡效果DOM 状态更新 这两件事,在过去是紧密耦合、互相掣肘的。为了视觉效果,我们不得不扭曲 DOM 结构和更新逻辑。

二、View Transitions 的核心思想与哲学:解耦!分离!

W3C 的大佬们说:“不行,这太 low 了!咱们得想个办法把这两件事分开!”

于是,View Transitions 的核心哲学诞生了:

CSS 锚定定位(Anchor Positioning)

今天我们要聊一个 CSS 世界里正在悄然兴起,但可能彻底改变我们布局方式的“大杀器”——CSS Anchor Positioning(锚定定位)。

你有没有遇到过这样的场景?鼠标悬浮在一个按钮上,想弹出一个 tooltip;点击一个输入框,希望下方出现一个建议列表;或者实现一个下拉菜单,它得不偏不倚地对齐触发按钮。

想象一个按钮和旁边恼人的 tooltip 定位问题

在过去,我们是怎么解决的?

  1. DOM 结构依赖: 把 tooltip/下拉菜单硬塞到按钮的父元素里,然后用 position: relative/absolute 各种计算。但这要求 DOM 结构必须“配合”,不够灵活。
  2. JavaScript 大法: 获取按钮的位置和尺寸 (getBoundingClientRect),计算 tooltip 应该放哪,监听滚动、窗口大小变化,重新计算... 心智负担重,性能还可能有问题。这感觉就像是为了拧个螺丝,结果造了台挖掘机。

这些方法都透露着一种“不得已而为之”的无奈。我们只是想让一个元素 相对另一个 元素定位,为什么就这么难?CSS 的 position: absolute 不是相对于包含块吗?如果我的触发元素和定位元素不在一个合适的包含块里,或者我压根不想关心它们的 DOM 结构关系呢?

Anchor Positioning 的核心哲学:解放定位,打破束缚

CSS Anchor Positioning 就像给 CSS 定位系统加了个“外挂”。它的核心思想简单粗暴但极其有效:

让一个元素(通常是绝对定位或固定定位的)可以显式地声明它想“锚定”到页面上的一个或多个其他元素,并基于这些“锚点”元素的位置和尺寸来定位或调整自身尺寸,而无需关心它们在 DOM 树中的关系或共同的包含块。

structuredClone 接口是用来结构化克隆 js 对象。 它需要 chrome 98+/safari 15.4+/firefox 94+开始支持。

之前只知道 messageChannel 的 postMessage 可以克隆对象,但它基于消息,是异步的。 今天我发现 history.replaceState 可以用来做 structuredClone 的代替,它是同步的!

当然 history.pushState 也是可以,但是它毕竟是 push,使用 replaceState 对 history 的影响更少。

const structuredClone =
  globalThis.structuredClone ??
  (<T>(data: T): T => {
    const oldState = history.state;
    history.replaceState(data, "");
    const clonedState = history.state;
    history.replaceState(oldState, "");
    return cloneState as T;
  });

深入理解 Navigation API

一、 设计哲学 (The "Why")

  1. 将导航的“语义”交还浏览器: 传统 SPA 路由(基于 history.pushState/replaceState)本质上是在“欺骗”浏览器。我们只是改变了 URL 和一些历史记录状态,但浏览器本身并不知道一次真正的“导航”正在发生。Navigation API 的核心哲学是让浏览器真正理解并参与到 SPA 的导航过程中。它不再仅仅是被动地记录历史条目,而是主动地管理导航生命周期。
  2. 以用户意图为中心,而非技术实现: pushState 是一个低级、命令式的操作。Navigation API 则更加声明式和事件驱动。它关注的是用户发起的导航意图(如点击链接、前进/后退按钮)或程序触发的导航请求 (navigation.navigate()),并围绕这个意图提供了一套完整的生命周期事件 (navigate, navigatesuccess, navigateerror, currententrychange)。这使得开发者可以更好地响应和控制导航流程。
  3. 标准化与健壮性: 在 Navigation API 出现之前,每个前端框架都需要在 history API 之上构建自己复杂的路由管理逻辑,包括处理并发导航、滚动恢复、焦点管理、可访问性(ARIA Live Regions 通知等)。这导致了实现碎片化和潜在的健壮性问题。Navigation API 旨在提供一个标准化的、更健壮的底层基础,让框架和开发者能在此之上构建更可靠、更一致的用户体验。
  4. 拥抱异步本质: 现代 Web 应用的导航往往涉及异步操作(代码分割加载、数据获取)。history API 对此无能为力。Navigation API 通过 NavigateEvent.intercept(handler) 明确地支持了异步导航处理,允许开发者在导航真正完成(URL 变更、DOM 更新)之前执行异步任务,并且可以优雅地处理成功、失败或取消。

“导航生命周期”的完整定义:

“导航生命周期”在 Navigation API 的语境下,指的是从用户或程序发起导航意图开始,到导航最终完成(成功或失败),并且浏览器状态(URL、历史记录、DOM)更新为止的整个过程,以及期间由浏览器管理和触发的一系列事件和状态。

其关键阶段和事件包括:

  1. 导航触发 (Initiation):

    1. 用户行为:点击链接 (<a>)、提交表单(如果未被阻止且目标是当前标签页)、点击浏览器前进/后退/刷新按钮。
    2. 程序化调用:navigation.navigate(), navigation.reload(), navigation.back(), navigation.forward(), navigation.traverseTo()

我找到一个比 css pointer-events: none 更好的方案: inert。 它可以阻止 手势输入、聚焦、文本选择,最关键的是,它可以从可访问性树中完全隐藏。

兼容性:chrome 102+, safari 15.5+, firefox 112+

这里给出一些兼容方案:

  1. 最简单的方案

    [inert] {
      pointer-events: none;
      user-select: none;
    }
    
  2. 考虑到更多边缘情况的兼容方案

    [inert] {
      pointer-events: none;
      cursor: default;
    }
    
    [inert],
    [inert] * {
      -webkit-user-select: none;
      -moz-user-select: none;
      -ms-user-select: none;
      user-select: none;
    }
    
  3. 官方 js 垫片方案 WICG/inert.js

    import "wicg-inert";
    

scrollbar-gutter: stable both-edges 有渲染上的 bug,它的本意是让元素的左右边都出现滚动条的宽度,从而保持视觉平衡。但是它的渲染逻辑存在 bug,应该是使用了硬件加速,但是层叠的顺序不对,导致右边会出现一个视觉上的截断,但是 DOM 属性上并没有截断。如图:

bug

要绕过这个 BUG,可以强制使用硬件加速渲染,让滚动视图的子元素,全部启用 3D 加速,比如:

.scrollbar > * {
  /* 3D加速可以顺便解决 scrollbar: both-edges 带来的边缘裁切的BUG */
  transform: translateZ(0);
}

如图: fixed

演示链接:scrollbar-both-edges bug demo

我发现 Solidjs 官方的 examples 页面,居然直接集成了 Chrome DevTools。 它应该是使用了 chii 这个库来实现的。

perchance.org/ai-icon-generator 是一个很好用的完全免费的 AI 图标生成器。 用法是最好先通过它的 ai-text 来生成完整精确的提示词,然后再去生成图标,这样效果会好很多。

《关于软件编程思路的一点借鉴》 这篇文章总结得很好,很有启发。

  • “软件工程不是编程,它是带有时间维度的编程”。

    这里的“时间”,我更倾向于“成本”这个词。

  • 在“团队领导”这一节,分成了“基层领导”和“大团队领导”,二者一个向下一个向前,都很有启发。

    但我仍然觉得,即便是“大团队领导”,也应该将“向基层工程师学习”纳入自己的成长规划中。这样可以倒逼自己不断蒸馏自己的思维链,避免和至简的大道脱钩。

发现一个很好用的绘图工具,Excalidraw

它还能可以快速选择部分内容复制成 svg,因此可以作为一个大的画板来使用。

简单解构 Iced 运行时模型与设计哲学

参考资料:

1. 架构总览:消息驱动的响应式系统

Iced 的核心架构启发于经典的 Elm 架构(The Elm Architecture)。 通过对界面系统的本质分析,我们可以识别出四个核心组件及其职责:

  • 模型(Model):应用程序的状态
  • 消息(Message):应用程序的交互(包含用户交互、系统事件、动画事件、组件之间通讯 等信号)
  • 更新逻辑(Update logic):定义着消息如何改变状态

The-Future-Outlook-of-AI-Software-and-Humanity

最近这段时间一直在思考这个问题。关于未来和人相关的问题,其实本质上都属于哲学的研究范畴。我本人对哲学一无所知,但同样的,我对 AI、对软件、对于人性,也相当的无知。只是在软件开发这个行业,有过一些记忆,仅此而已。 但我还是会使用我局限性的思维思考这个问题、讨论这个话题,这篇文章也是用来与自己对话。

首先这篇文章并不是要讨论“AI 抢人饭碗”这个问题,因此在文章开头,我直接把这个话题给简单终结掉,我的观点是从三点出发:

  1. AI 会替代人类的工作这是必然的,这是对资本奴役人类劳动力的解放运动。
  2. 人从来不是由工作来定义其价值,相反的,是人类来决定什么是有价值的工作。
  3. 任何前进都是有牺牲的,这本就是人生的一部分。

归根结底,我也会恐惧某些改变,因为当下的社会是残酷的、是互相踩踏的,哪怕我们都在向前走,但是当下一个相对错误的方向和选择,都有可能导致我们成为他人的脚踏石,这种踩踏是苦痛的,是非人道的,但是它客观发生在生活中的每一个角落。我们会恐惧,也应当恐惧,但也应该笃定,科技改变生活,虽然可能我们坚持不到这一天的到来,但只要确保自己是在这条路上,那便是一种希望,足以支撑人们幸福感的获取。


接下来,回到文章的话题上来,关于 AI 与软件与人三者的关系。

一些社会性相关技术的自我讨论

这篇文章写给自己的文章,是一篇关于“解构与重组”的文章,解构的对象,可能是技术,也可能是我自己。 不论如何,我不想让的阅读者(特别是任何时刻的我)在阅读的时候有太多的心理负担。 所以在此之前,我需要对自己做一个简单的结构,以此做为文章的起点,以确保接下来的文章内容能被阅读者简单明了的理解。 但我写作的风格是跳脱的,我不能确保文章上下文具有常理,哪怕对我来说,是的,它有常理。但这种常理并不适用于所有人。

首先我是一个记忆力很差的人,这点不是自我贬低,我也从来没有觉得这有什么不好(除了应试教育,但这已经是过去式了)。 而我之所以强调这点,是因为这个基因给我的思维方式带来了巨大的影响。 因为我可以瞬间遗忘自己认为不重要的信息(不论是主观的还是潜意识的),从而让自己时刻处于一种轻装上阵的状态。而对于需要记住的信息,我本能的遗忘让我不得不对这些信息进行一些预处理,以确保能保存在记忆中,因此长期依赖我形成了一种思维习惯,那就是用解构替代记忆:将记忆分解成更多细碎的模式,在需要的回忆的时候,将这些细碎的模式重新组合成记忆本身。而模式本身也是一种记忆,或者某种感受。 这种记忆模式在找代码 BUG 的时候,非常有优势,至少我没有从身边的其它同行中看到过类似的能力水平(当然很大原因是我的圈子很小,并没有接触到足够多优秀的人,所以我到目前为止,并没有觉得这是一个超能力,而只是类似在一个五六十人的班级中,我偏科了,仅此而已)。这种优势体现在,我看到一个模糊的错误信息,就能瞬间感知到错误来源或者范围,然后用二分法做测试,慢慢定位,或者将这个错误分解成更多个小错误。这也导致我带的项目很少去写测试(当然我也在努力改正这个问题,我知道这并不好),更多时候是依赖于我自己的思维能力去定位问题解决问题,其它人遇到问题,只能将问题抛给我去定位。 但这种能力的使用需要我完全的专注,包括我自己写代码的过程中,也一直在使用这种能力,让我一边开发,一边架构。因此我很佩服那些能一边开发一边听小说,一心两用的人,但往往他们的代码会被我否定掉大部分,然后我来重写。 但这种分解对于有逻辑的信息,是有严重的损耗的,特别是遇到复杂的事物,比如人心。也就意味着这种损耗会让我无视掉很多当下重要的信息,但同时,正如内耗无处不在,这些损耗从概率上来说正好抵消了内耗带来的影响,可以让我发现事物底层的脉络。 这并不是说我拥有了预知未来的能力,相反的,这让我客观的意识到未来是不可预知的。 但我不想把篇幅过多的放在“解释为什么”上面,我只想提出一些结论,大部分情况下这些结论只适用于这篇文章的目的短暂存在。 当下的 AI 技术,本质上是一个没有记忆的东西,哪怕它用了巨量的数据去训练。所以我很能感同身受,它所谓的智慧涌现,到底是一个什么玩意儿。

但不论如何,AI 是一次新的工业革命,它势必会替代人类的工作,也必然会替代人类工作。 但这是一个过程,不是一蹴而就,在这个过程中,政府需要解决人类资源的分配问题。 目前,大部分人使用工作来进行分配,少部分人使用资源来进行分配。 但我说过,我无法确切地预知未来,本质是因为少部分人或者说是少部分利益主体在主导着世界的进程。

跨平台技术的回顾与展望

在很久以前,高级编程语言还没出来的时候,硬件和软件其实是深度绑定,定制开发的。 随着高级编程语言的出现,以及操作系统的市场收敛,才有了跨平台开发这个概念的出现。

再往后,就是 Google 的 Chrome 推出,占领了 Web 技术的话语权,Web、Chrome、Google 三者共同发展了二十年左右,慢慢的,Web 成了高性价比跨平台开发技术的重要选择之一。 直到移动端的出现,除了 Safari(Webkit)这种与原生视图进行了深度绑定,Android 上的 Webview 技术仍然基于独立的绘制引擎,过深的技术路径,也导致了 Webview 技术在 Android 设备上的性能并不够好。当然 IOS 也没好到哪去,但至少它的技术路径使得它更容易做一些优化。再叠加安全防护的问题,导致一些高性能需求的软件,更不可能在 Mobile-Web 上落地,因此移动端上,更多是回归了原生开发或者混合开发(Native 为基础,部分场景使用 Web 或者 Web-Like 的混合开发)。

这里有必要要谈一下 WASM: 即便现在 WASM 的出现,它也只是画了一个大饼给开发者,但本质上,它们的性能并没有全面超越 js,因为它本质上只是给静态语言提供了一个编译目标,相比编译成 js,编译器的输出与 wasm 的指令更加贴近。但相比直接在 js 上开发,js 的性能和 wasm 的性能并没有太多差距,即便是一些算法相关的层面也是如此。目前 WASM 相关的大量提案还在逐步跟进,但是进展相对缓慢,但本质上它的意义只是能让其它编程语言不需要生产 js,而是直接编译成 wasm,从而进行 Web 开发。因此 WASM 并不是完全取代 JS,只能说给 Web 生态提供了更多的可能。 但在画了饼中,WASM 加入了一些更接近硬件的指令集,所以它的未来,至少在一些并行计算方面是可以超越 JS 的,当然这部分的工作更可能会被 WebGPU 给取代。 不过,WASM 有一个 JS 无法取代的优势,就是关于多线程的提案,它会比 WebWorker 更加的底层,具有更多的优化潜力。并且目前多核设备已经是标配,所以未来 WASM 可能真的会依靠多线程技术,取代 JS 开发的一些场景(在开发成本基本不变的基础上,提升应用性能)。

因为 Mobile-Web 客观的阻碍,所以跨平台开发,更多的变成了 Android+IOS+Desktop 这样的概念。 不过,在 Web 技术的发展的过程中,给开发者带来了一系列的开发工具套件,这些工具使得 Web 开发的体验远超任何其它编程语言的体验。这也就导致了即便 Web 性能上限不佳或者说难以优化,但是它的开发成本极低,迭代速度极快,仍然成立很多产品的选择方向,或者是魔改的方向,当然,也可能是“发展”的方向。 国内厂商大多选择“魔改”,其实“魔改”和“发展”,其实就在于它的开放程度与簇拥程度。你只要足够的开放,并且吸纳社区的建议,那么就是“发展”,否则通常会被人们嘲弄成“KPI 项目”。因此哪怕国内其实有很多厂商在做类似的发展,哪怕也开源了,但是没有和全球开发者接轨,没有听取他们的意见,仍然以自家开发者和产品需求为基础去推进项目,那么难免不被嘲讽。 这里举一些正向的发展例子,比如:react-native、ark-ui。 先说 react-native,它算是在 Web 的生态和标准上发展起来的,首先它由 react 的声明式的开发所孵化,引入了 jsx 语法,颠覆了过往布局文件和控制代码拆分开的模式。这也启发了一个全新的 UI 开发纪元,可以看到后来的 flutter、compose、swiftui、ark-ui 都是类似的开发方案。它的工作原理非常的聪明,是一种生成器的运作模式,以至于它的渲染性能可以优化得非常彻底。虽然它在 Web 上性能并不怎么样,这是因为 Web-DOM 所提供的接口是命令式的接口,但是在原生平台上,绘制本身就是一个只需不断的循环程序,加持上编程语言的优化,它的性能潜力非常巨大的,因此各家操作系统厂商都选择这个长远的方案。 现在 react-native 发展到了一定的程度,开始了 react-strict-dom 的发展,可以说它在做一种 mini-web 的标准:提取了 Web 的主要技术标准,做为一个现代 UI 开发的最小标准,将它移至到 Android/IOS 平台上。这样引擎标准将会更加干净通用,从而为跨平台提供了更多的可能性,未来新的原生平台,只需要根据这个小标准集合进行适配即可。这点本质上也是在解决我前文提到的 Mobile-Web 的痛点:过剩的技术路径,导致难以优化。现在有了 react-strict-dom,似乎这样的 Web 又可以作为一个跨平台标准了。 说完 react-native,再说 dart+flutter,它和 Web 平台有很深的渊源。首先是 dart 语言一开始是要更 typescript 做竞争的,所以本身它和 js 语言就非常的相似,再者是它的定位实在根 ts 太像了,引入了类型安全。不同的是,dart 有自己的 runtime,当初 chrome 甚至尝试直接集成了 dart-runtime,同时 v8 共存,想解决 js 的语言问题导致的性能瓶颈,且不说是否存在垄断的嫌疑,至少 WASM 是一个更加开放且更具长远未来的选择,因为大家知道,性能问题并不能通过某一个编程语言来解决,像 WASM 这种提供底层指令集,是一种更加彻底的解决方案。 为此,dart 被 chrome 抛弃后,这个项目几乎就快要结束了,这时候 flutter 这个项目拯救了它,因为恰好 flutter 需要一个跨平台的编程语言,索性把 dart 语言团队拉进来,一同发展 flutter。一开始 flutter 的底层是 skia,所以 flutter+dart,你可以类比成 html-canvas+js,只不过 flutter+dart 的技术路径更加清澈,所以当然不会有 html-canvas+js 的障碍,性能问题直接上 C++来解决。并且因为它开箱即用的 Weight 组件,以及和 js 相似的语法,使得大家对它的接受度出奇的高。几年下来,逐渐就发展成了一个跨平台开发的强力工具。现在它在 Web 上的渲染性能越来越好,当然这也是依赖 WASM 的发展,是的 dart 语言能编译成 wasm 而不是 js,从而获得更高的性能。渲染层面也从原来的 skia 发展成了现在的 impeller(目前支持 Android/iOS/Desktop,未来也会加入对 WebGPU 的支持)。 再有就是 ark-ui,它进一步糅合了 dart、swiftui、kotlin,直接在 ts 语法上进一步改造。它直接把 ts 当 dart 用,用自己的方舟引擎替代 dart-runtime。同时它的 API 设计,也是大量参考了 Web-API 的安全考虑。

2024年

以华为的技术储备,做一款替代AR1的芯片和对应的产品,应该可以非常具有颠覆性的竞争力。25年相关的新技术也都可以量产了

Publishing Your Deno Project as a Monorepo using dnt

Publishing Your Deno Project as a Monorepo using dnt

Before providing theoretical guidance, let's look at how to achieve this in practice. After completion, I will explain the advantages of this project management solution.

Tools

  1. deno
  2. pnpm

Preparation

  1. Create your project:
    deno init dnt-mono
    # cd dnt-mono
    # code . # open in ide
    
  2. Initialize a git repository

使用 dnt 将你的 deno 项目发布成 monorepo 风格

使用 dnt 将你的 deno 项目发布成 monorepo 风格

在提供理论指导之前,我们先看具体的实践如何做到,完成后,我再说明这种项目管理方案的优势在哪里。

工具

  1. deno
  2. pnpm

准备工作

  1. 创建你的项目:
    deno init dnt-mono
    # cd dnt-mono
    # code . # open in ide
    
  2. 初始化 git 仓库

MutableSharedFlow 随记

MutableSharedFlow 作为一个建立在 Flow 基础上的设计,它的 Shared 特性其实与 Flow 的 collect 有着设计上的冲突。 因为 Shared 特性,它的 emit 与它的订阅者有关系,订阅者的消费速度决定着它的发射速度。然而如果没有消费者,就意味着它的 emit 会直接丢失,而没有被消费到。 举个例子:

val sharedFlow = MutableSharedFlow<Int>();
launch {
    sharedFlow.collect {
        println(it) // 这里通常不会有任何打印
    }
}
sharedFlow.emit(1)

因为 launch 的执行需要时间,在这段时间里,emit 可能已经执行完毕了,从而导致发射的值没有被任何人消费从而丢失。 这对于将 MutableSharedFlow 直接作为 EventEmitter 的替代者来说,会是一个很严重的设计缺陷。


2023年

我发现浏览器有一个很离谱的 BUG,我不知道它是出于什么原因

import { a } from "http:/127.0.0.1:8000/test.mjs";
console.log(a);

这个协议头不规范,居然能宽容地正确解析出来。 也就意味着在浏览器中,new URL("https:/qaq.dweb/index.ts") 能被合法解析成 new URL("https://qaq.dweb/index.ts"):

这个 bug,可以带来一个玩法。我可以利用这个 bug,用 node 实现类似 deno 的功能。因为 deno 近乎是完全使用浏览器的标准,所以说浏览器上面的这个 bug,在 deno 中同样也会有,也同样适用…… 在 nodejs 项目里,只需要在 node_modules 里头创建一个 https: 的文件夹。它完全不会报错,可以正确解析。

比如说以下 deno 代码:

import { Server } from "https:/deno.land/std@0.187.0/http/server.ts"; // 这里使用单斜杆,也会被认为是双斜杠

然后同样的代码,在 nodejs 项目中,不启用 deno,只添加一个 tsconfig.json,使用 ts5+ 来实现 .ts 文件后缀的支持

// tsconfig.json
{
  "compilerOptions": {
    "target": "ES2020",
    "module": "ESNext",
    "moduleResolution": "bundler",
    "allowImportingTsExtensions": true,
    "esModuleInterop": true,
    "forceConsistentCasingInFileNames": true,
    "strict": true,
    "skipLibCheck": true
  }
}

最终效果如下图:

这是社区的讨论:Add onclose event to MessagePort #1766 当初我提到一个垫片方案,那时是 2020 年,所以当初只有 chrome69+的内核能支持:

/// worker
const lockReqId = "process-live-" + Date.now() + Math.random();
navigator.locks.request(lockReqId, () => new Promise(() => {}));
postMessage(lockReqId);

/// master
worker.addEventListener("message", (me) => {
  if (typeof me.data === "string" && me.data.startsWith("process-live-")) {
    navigator.locks.request(me.data, () => {
      worker.dispatchEvent(new CloseEvent("close"));
    });
  }
});

现在已经普遍支持

caniuse-locks

Web Design Principles 这篇文章提供了 Web 平台的接口设计最佳实践

2022年

在本机配置 CNAME

  1. 安装 dnsmasq

    sudo apt-get install dnsmasq
    
  2. 配置 dnsmasq

    vi /etc/dnsmasq.d/test.conf # 随便开一个文件
    

    填入:

    cname=from.gaubee.com,to.gaubee.com
    
  3. 修改本地 dns 配置:

基于AsyncIterator的响应式编程

最近在重新思考响应式编程的一些事情,其实我很少使用 RxJS,往往是直接手撸各种异步策略。 因为我自己是更加倾向于使用原生的 async-await/generaor 来实现。因为会有更好的调式支持,性能也会更好。但可维护性可能就不一定,如果没有好好封装,别人读代码的时候,就会比较晦涩。 虽然 RxJS 在开始的时候也是晦涩,但是至少他们的高级的概念能够很好的复用。 而像我这种直接手撸的就往往是按照需求来进行编程,阅读者如果对需求没有足够的理解,那这种代码的可维护性可以说是相对比较低的。

但最近有打算把 RxJS 的一些常见概念和我自己的经验结合起来,写一个基于异步迭代器的响应式编程的库。 这篇文章就简单的讲一下这个库里头涉及到的一些有趣的经验点。

首先就是我异步编程时最常使用的 PromiseOut,它是对 promise 的再封装

class PromiseOut<T> {
  resolve: Function;
  reject: Function;
  promise = new Promise<T>((resolve, reject) => {
    this.resolve = resolve;
    this.reject = reject;
  });
}

2021年

尝试 Deploy to GitHub Pages

Event模块是用于快速记录一些小事件。比如一些想法;一些值得分享的链接;一些图片等等

Web 未来技术猜想(一)

对于近十年来 Web 技术的高速发展,很有多精彩的概念与设计涌现出来,但也有很多设计是建立在历史 Web 技术的架构上。

这间接地导致了浏览器的开发越来越难,现在还存活的浏览器内核也就只剩下 Webkit 和 Blink 了(Firefix 的 Servo 份额实在太小了,开发进度也实在缓慢)……

即便这两个内核的代码都是开源的,但并不意味着“不垄断”,Web 技术再这样发展下去,只会制造出越来越高的技术壁垒。因为开源并不意味着自由,技术标准的话语权还是掌握在别人手里,你想贡献代码,还得看社区是否“有时间”去接纳,还得有大量的条条框框在限制着你,而反观 Chrome 团队,它们则是能肆无忌惮地往 Chrome 中添加各种实验性功能。从技术层面上来说,技术人的贡献固然是令人尊敬值得肯定,但从资本的层面上来说,这些新技术的堆在这般的堆砌,制造技术壁垒、掌握标准话语权,不正是垄断牟利的老套路吗?

我这里大胆预测一下,未来 Web 技术一定带来突变。 或者说这不是预测,是我个人假设要去从头设计一个浏览器,我应该怎么去做。宏观上会分成两大种类的模块来开发:

第一种是功能性模块

比如蓝牙模块、HTTP1/2/3 协议模块、USB 模块、摄像头模块等等。对此可以理解成“驱动模块”,但不同的是,驱动模块目的只是将硬件被操作系统的接口所认知,功能性模块还加入了隐私保护的概念,所有的行为对于使用者来说必须是公开透明的。这不是单纯做好“功能授权”与“信息流向透明”就能解决的问题,还是确保用户的身份不被追踪,用户的偏好不被预测等等。 这类模块由两部分组成:一部分是“原子接口”,一部分是“应用接口”。

  1. 其中“原子接口”只能由操作系统提供,类似于操作系统的 API,但是要符合上文所提到的 Web 的隐私安全性的定义。

    Web 开发者可以直接在网页上进行使用 WASM/JS 围绕“原子接口”进行开发。

    比如说摄像头模块的原子接口,可以做到对相机预览功能的二次开发,或者直接拿到 YUV、RGB、RAW 等格式进行处理等等。但现实情况是,每一个物理硬件都有它的特性,我们只能说这些硬件在出厂的时候通过了可用性的测试,但并无法保证所有的硬件都是一致的,所以我们往往需要加入一个理想数据模型,来结合实际硬件的情况,加入一定的偏移与噪点来消除误差,这其实是需要硬件厂商和系统驱动要去解决的问题。

  2. 其次“应用接口”是基于“原子接口”开发出来的应用。首先操作系统会提供一套默认的“应用接口”,正因为将浏览器的开发成本嫁接到操作系统上,并将之模块化,才有可能将浏览器的开发成本大大降低。

CSS“文字”渐变,一种比background-clip通用性更好的方案,可以用于SVG中(CSS svg icon gradients, a more versatile solution than background-clip)

示例 Demo

起因 The Story

探究这个问题的起因,是源于我打算把公司的图标从 font 逐步转化成 svg。

My plan is convert the company's icon from font to svg gradually.

虽然绘制性能有所下降,但是整体的好处是比 font 多得多的:比如“按需引入”,“多色”,“动画”,“可访问性”等等。

Although the drawing performance maybe reduced, the overall benefits are much more than font: "dynamic import", "multi-color", "animation", "accessibility", etc.

但之前使用background-clip:text的方案就不好用了,因为默认情况下,svg 的 path 使用的是fill="currentColor"这样的写法。诸多原因,我不得不思考较好的替代的方案。

But the previous solution of using background-clip:text doesn't work well, because by default, svg's path use fill="currentColor". For many reasons, I had to think of a better alternative.

初识go-wasm

2020年

Comlink-v2

我是Comlink-v1的重度用户,并在我的公司重努力推广它。

I am a heavy user of Comlink-v1 and have worked hard to promote it at my company.

它很棒,但仍然有一些问题,比如使用者必须知道它背后的工作原理,有时候还会因为参数传递时,对其进行序列化或者反序列化时引发一些低级的错误。

It's great, but still has some issues, such as the user having to know how it works behind the scenes, and sometimes triggering some low-level errors when serializing or deserializing it when passing parameters.

总的来说,Comlink-v1虽然有些瑕疵,但他解决了很多问题。

Overall, Comlink-v1 has some flaws, but it solves a lot of problems.

最近我重新思考Comlink-v1存在的一些缺陷,并且尝试对它进行重新实现。在几经尝试后,不得不说,那些缺陷真的很难规避,为此我不得不牺牲它的通用性。所以最终我还是将Comlink-v2给实现了出来。可惜的是我不能将源码公布出来,但我可以提供基础的实现思路。我相信,这个新的思路会给js领域带来新的魔力。

Recently I've been rethinking some of the flaws in Comlink-v1 and trying to re-implement it. After a few attempts, I have to say that those flaws are really hard to avoid, and for that I had to sacrifice its versatility. So I finally implemented Comlink-v2. Unfortunately I can't publish the source code, but I can provide the basic idea of the implementation. I believe that this new idea will bring new magic to the JS field.

效果预览 Effect Preview

这是已经通过测试的代码:

This is the code that has been tested:

Effect Preview

封装异步编程中时间的理念

异步编程,本质就是要充分利用时间。但现代异步编程对于时间仍旧是一个很片面的理解,比如关于“超时异常”,我们往往只是定义一个 30s,超过这个时间就是失败。而所谓“健壮的异步程序”,往往也只是堆砌地使用这些定时器而已,这里头缺乏了一个“系统地时间理念”来规范时间的使用与等待。

从业务或者功能等角度,可以定义出各种时间的概念,比如渲染的、网络的、磁盘的等待。 但进一步解剖,其实可以用两种时间概念来替代: “我自己花费的时间”“我等待别人的时间” 进一步简化就是:“计算时间”“等待时间”

这里我是以一个“程序包”的角度去理解时间,无关“线程/进程”、“网络”、“磁盘”等待。 接下来一边分享我的理解,一边进行编程所需要的设计封装。

计算时间(我自己花费的时间)

和人一样,如果自己是在做正确的事情,那么我们不会认为自己在浪费时间,自己也就没必要给自己“计算耗时”,毕竟“正确的事情”是最总要的,计算耗时反而会转移自己的注意力,不是“正确的事情”。 所以我们不会去给“计算时间”挂上计时钩子,而是一个程序的执行消耗多少时间也不是固定的,会被设备的状态所影响,比如低电量、一个 CPU 线程中有多个程序在切换调度互相争夺资源 等等。 但是程序之间可以互相统计对方消耗了多少时间,由此来做出自己的判断。不过这一步往往是“系统内核”在做的,因为是它在决策程序的调度,所以它应该统计并记录每一个程序的执行时间、压力状态。 这些信息都将帮助整个系统变得更加的稳健,而不是单一地使用超时来决策接下来的作业。

举个例子:

浅谈Web Worker关闭的问题

Web Worker是没有提供onclose事件的,但它有提供terminate函数。 可能官方很自信地觉得Worker只要是用户销毁的,那么就没必要onclose……但其实昨天就遇到这个问题了(在Cordova-Ionic-Webview里头),就是从后台唤起程序,WebWorker没响应了,被杀了……在调试控制台已经看不到这个Worker的身影。 解决办法我想有三个:

  1. 原生层面入手,去监控有什么系统层面的回调会触发
  2. 改成用ServiceWorker试一下
  3. 监控WebWorker的销毁

为了简单且通用起见,我先选择了3。 但其实在官方接口里头是没有相关的接口的,这就只能另辟蹊径。 一开始我想到的是研究MessageChannel。因为从接口层面来说,它们几乎是一出的,也许底层实现是一样的。 所以就去研究如何识别MessageChannel是close状态的。最糟糕的方式估计就是pingpong,但这就得额外增加脏代码。 后来忽然想到transferable这个标准,所以就有了以下的骚操作:

const b = new ArrayBuffer(1);
port1.postMessage(0,[b]);
console.log(b.byteLength);

如果MessagePort是开启的状态,内存对象会被顺利传输,从而打印“0”。否则如果打印“1”,就说明MessagePort已经被关闭。 用这个方法去实验WebWorker。理论上几乎是一个东西吧……事实却是即便WebWorker执行了terminate,ArrayBuffer仍旧会被传输过去……这就很恐怖了,错觉自己是不是遇上了浏览器内存泄漏的问题……一搜索其实github上三四年前就已经有人提出了,到现在仍旧没有音讯。实在不理解terminate居然没有销毁消息管道……那我发送到子进程的ArrayBuffer到底发到哪里了呢?

发现几个很棒的开源项目

Isomorphic-Git

用js实现的git,与git保持完全的兼容

Storybook

一个管理组件的工具,能部署在本地项目中,方便测试人员进行测试、开发人员互相了解、设计师进行审查

WarriorJS

编程游戏,用编程来通关

Promise.race会带来内存泄露

假若有两个promise: a, b,现在它们都Promise.race([a,b])所包裹。 此时,如果a先完成了resolve,race也就有了返回值。 然而,b却迟迟没有被resolve或者reject…… 结果会带来隐式的内存问题,就是a看上去被释放了,但其实没有。 v8论坛上有类似的bug提交:https://bugs.chromium.org/p/v8/issues/detail?id=9858

简单地模拟一下实现:

function race(...promises){
  return new Promise((resolve, reject)=>{
    for(const p of promises){
      p.then(resolve, reject)
    }
  })
}

我实际测试了一下,一些不同版本的v8似乎会有不同的表现,但终归是内存泄漏了,只是好像v12的是直接泄漏了捕捉不到,v13是能在内存堆栈里头看到。 我在工作的时候发现这个问题也是靠async_hooks的异步资源监控下才看到这个问题的存在。

小米10使用体验

老妈要换手机了,给她买了个小米10。 其实本来就是冲着它是一个比较均衡的体感配置才去尝试的,但试用后还是觉得不值这个价钱。 老婆说这个价格,再添一两千都能买美版的iPhone了😂。我知道iPhone的好处,但我需要的是MIUI的系统,我也不想去折腾刷机什么的,所以就买了小米。

一亿像素

首先是一亿像素。对比的产品不是所谓的“友商”,而是我自己在用的MIX2s。 说实话,差别真的和我MIX2s的1200万像素差别不大。我选择的是阳光明媚的条件进行拍摄,毕竟我妈妈没有拍夜景的需求。 以下样张是我将两种照片在手机上方大到最大比例然后截屏下来的。毕竟手机屏幕对于我妈来说就是最后的输出端。后面是我用电脑打开图片继续方大看到的东西。

MI10的一亿像素MIX2s的1200万
imageimage
imageimage

尽管说一亿跟一千万在二维图片上,应该是3:1的关系,但我实际看到的并没有预期的效果。可以看到这真实的场景下, 不过是一个糊糊锐化成另外一个糊糊罢了。虽然颜色捕捉有点长进,但想想它们1:3的价格……

90Hz屏幕

2018年

浅谈Async Generator

会议初学编程的时候,那时候学的还是asp.net,然后看到substrack一个演讲视频:Harnessing The Awesome Power Of Streams,觉得:“哇!流这种编程思想我要学!”,从此就开始入了nodejs的坑。然而直到现在2018年,async iterator的出现,才勉勉强强提供了一种语法层级的流编程的体验。

一开始,基于事件编程,开发者通过监听一个个事件(从on("data", fn)开始),来模拟事件流程。这勉强算是一种实现,它最大的问题是需要创建大量的callback,维护它的代价就是需要书写大量冗余的代码与抽象的封装才能勉强达到稳定可用的级别。 事情的转机从Generator的出现开始,与其并行推广的还有提早一阵子出现的Promise。大部分人对Generator的使用无非就是co这个库的骚操作。不过确实,单纯Generator这个语法特性,很难在jser里头引起什么大风浪,毕竟js里头基本都是异步编程,Generator除了模拟现在的async/await以外很难有大舞台。

Async Generator

有趣的在async iterator这个语法出台,Generator才算正式杀入js的异步编程。

入门

举个简单的例子:

const stream = fs.fs.createReadStream('./big-file');
for await(const data of stream){
  console.log(data)
}

这种写法相比on('data', fn)这种写法,最大的区别在于资源的控制与利用上:基于事件的监听,nodejs会尽可能也必须尽可能快速地去触发data事件,而并不知道你到底有多么需要这些data,反正它就是冲着榨干硬件资源的操作去无脑触发就是了(当然你也可以直接使用文件句柄来手控操作,也能规避这个问题)。

进阶

2017年

在JS中实现+=/-=操作符的重载

运用场景

这是一个语法糖,只是为了简化一些API的写法,比如:

this.list += item // array push
this.list -= item // array remove
this.on('click').events += cb; // add event
this.on('click').events -= cb; // remove event

如何实现

JS原生支持=操作符号的重载,即getter/setter:

const a = {
  get z(){ return 666; }
}

而+=操作符号,是针对于string、number这两种类型来做拼接与累加操作的。

Firebase在国内使用的一些方法

Firebase在国内有些地区是可用的,但只是有些,没法保证全国通用,这篇文章谈的是服务端(Node.js)的使用。

前提是电脑跑起来了代理软件,这个怎么搞我就不说了,代理服务器方面我建议用香港的代理服务器,那就很快很稳了。

首先是Firebase-tool这个命令行工具,源码中使用的是request这个库,所以只要在源码里头加上proxy属性就行了。

目前版本来说这个文件是api.js,找到_request这个函数,在里头加上一句:

  options.proxy = "http://127.0.0.1:8118";

比较麻烦的是Firbase-admin这个库,用的是原生的http/https这两个核心的,核心服务都是走https的,所以这篇文章主要就谈一谈原生库如何走代理。我已经将这个库整理到firebase-admin-proxyable这里了,国内用户可以安装这个并看着文档中DEMO的写法来配置自己的代理。

  1. 核心原理就是使用agent这个属性,具体看官方文档。

  2. 在npm里头,绝大多数的代理,核心都是这个库:tunnel-agent,点链接进去没文档,没关系,直接看源码,源码就单文件不到300行。

2016年

gRPC实践案例

类似Google这种大公司产出的产品,一般就两种情况,一种是面向小白用户的,如此让G粉簇拥而来找Bug优化产品思路,等到时机成熟再推出正式版或者取消产品,AngularJS就是这一类。还有一种就是Google自己的需求而总结出来的产品,优点什么的我就不吹了,gRPC就是这一类的。

序言

今年年初的时候我就一直想做一款基于RPC实现组件化搭建网站的一款产品。目的就是为了让各种语言的程序员能以最低沟通、学习代价来进行快速、稳定开发产品。而当初做的时候为了速度摸坑,用了Nodejs来进行开发,做出了GQ这款产品,自己边用边总结,说真的坑是真的多,JSON是不够用的,还有bytes、流数据等等问题。几个月下来,发现这个东西是个史诗大坑,因为要考虑到各种语言的兼容、使用难易、不同类型组件(数据库组件、流文件处理组件等)通用接口等等问题,迟迟没有拿出一套跨语言的规范来。几乎要放弃。这项目被我搁置。 不巧,gRPC出来了。当初心想有搞头,但是当时文档不够健全,让那些爱折腾的人先去探探路吧。 现在我作为第二批吃螃蟹的人,上手一试,心中暗叹:厉害了我的Goggle

案例简介

一套基于路由注册的分发服务。这里使用Nodejs来快速上手。 服务端:注册HTTP端口,以及gRPC基础服务,通过基础服务,可以注册HTTP请求的处理权。 子服务:注册基础服务,实现对HTTP请求的处理。 流程如下:

HTTP请求--->服务端--/a/b-->子服务1
         |--/a/c-->子服务2
         +--/a/d-->子服务3

实现流程

推荐一个同步两地代码的东西

以前开发,是使用金山快盘,但是这软件停止了维护。后来就没用了。 这个同步需求发生在最近写Typescript的时候,感觉本地编译很吃CPU,有时候浏览器开着运行Canvas就要吃掉我双核四线程60%+的CPU了,这时候再编译Typescript,搞得两边都很卡。 所以就想着能不能用另外一台电脑单纯替代我本地的编译这一方面的工作。

一开始的想法是自己写一个服务同步两台电脑的脚本,后来想想预感会踩很多坑,所以就果断放弃这个想法, Google搜不出我要的软件,就上Github搜索了以下,结果就找到了这个:syncthing

跑起来后试用感觉很不错,唯一的不满就是,它是以轮询的方式来查看文件变动。不过开源软件给出API,所以就写了一个nodejs脚本来自动触发同步选项。

代码下面贴出,我默认是放在代码所在目录下:

// .syncthing.js
const fs = require("fs");
const child_process = require("child_process");
var ignore_keys = ["/ace", "/js/lib", "/typings"]; // 不参与监听的,注意这里不是目录,只是简单的字符串匹配,也就是说如果目录名有这个字符串的话就不监听。
var watch_deep = 4; // 监听的目录深度,int > 1。为了缩减代码所以就没有用fs的API
var watch_dirs = ["./"];
var ls_exec = "ls -d .";// 如果你的代码目录不深,或者需要监听所有的目录,可以直接用ls -R,不过你要自己编写代码处理输出的格式,这里不赘述(PS:我就是偷懒不想多写代码)
do {

单页应用 路由系统 与 链接跳转、页面渲染 三者之间的最佳实践

随着 Google Chrome 的升级改进,Polymer 上使用的接口接近稳定,所以我就将 Polymer 拿出来进行重新学习。 学习 Polymer 和其它框架不一样的是,其它框架,遇到问题,只能是阅读源码或者寻求社区帮助。Polymer 遇到问题,最重要的想法就是:这是原生的接口,用原生开发的思想去解决。那么问题往往就迎刃而解了。

这篇文章涉及到的是如标题所示的路由系统 与 链接跳转、页面渲染三者之间的关系。

以往的开发思维就是:绑定就是一切!大部分 MV*框架过来的人包括我的思维都有一个定视流程:链接的改变 → 触发路由改变 → 触发改变状态机 → 触发渲染页面上要显示那部分的内容。 这是一个很正确的流程,这种绑定是稳定的,就等于即便是触发链条上某一个节点,后面的节点也能正确的触发渲染。

然而这里有一个很重要的问题需要去重视:渲染性能。

就因为这是一条因果链,所以我们惯性思维往往就是使用统一的数据源来进行管理。这也导致了我跑到Polymer/paper-tabs下头发了一个相关的提问: (https://github.com/PolymerElements/paper-tabs/issues/182)。

但后来我从原生的角度去进行思考后,想到了问题所在,这不是框架或者组件的问题,而是思维方式的问题。纠正后,我现在的思维方式是这样的:

首先:链接、链接选择器(列表菜单、tabs 等带选择的组件) 这类的组件数据绑定归为一起。确保页面上的元素的联动关系不变:比如在窄屏有一个竖向的导航,宽屏有一个横向导航,二者的数据绑定是要一致的。

image

image

JS的心灵契约——Mide-Pact.js一个简洁又异常强大的Key-Value管理器

前言

mind-pact.js 这个东西,以前做过,但是做得不够好,而且是整合在以前开发的MVVM框架里头,作为Model层。 这两天整理了一下,凝练了核心的思想。

这个库,是一个key-value管理器。简单的说就是:

model.set("a.b",1);
model.get("a");//{ b:1 }

最重要的特性:支持表达式

model.get("a['b']");//1

PIXI实现粗虚线绘制

好久没写博文,今天打卡。 主题关于 canvas 虚线的绘制,或者说是一定路径的无限循环贴图的绘制,比如龙、蛇的身体绘制等等,网上都没有相关的实现,外国论坛也没有,所以索性就总结一下难点重点。

这里是围绕 PIXI 的接口来实现。

首先是准备的有:

  1. 一条线的一组点
  2. 要进行无限循环的贴图

实现需要基于PIXI.mesh.Mesh这个类来实现。 需要传入的参数有:texture, vertices, uvs, indices。(drawMode 使用原本默认即可)

  • texture 贴图对象 PIXI.Texutre
  • vertices 顶点对象 Float32Array,默认是[0, 0, 100, 0, 100, 100, 0, 100]
  • uvs 顶点贴图信息 Float32Array,0~1,代表贴图两个边缘,默认是[0, 0, 1, 0, 1, 1, 0, 1]
  • indices 顶点顺序 Uint16Array,默认是[0,1,3,2],代表一个长方形的绘制,那么会被绘制成两个三角形,分别是 0,1,2 和 1,3,2 。而这里的顶点顺序则拿去代表 vertices 数据的顺序,简单用程序表示那就是:indices.map((v,i)=>[ indices[i],indices[i+1],indices[i+2] ]),有这个数据我们通常用来把重复点的数据合并成一个重复使用。

vertices 的获取

使用WebGL作色器基础知识实现PIXI.js高斯三角模糊

官方给出的模糊滤镜效果不尽人意,所以就想自己写一个,顺带学习了一些 WebGL 的作色器相关的基础知识。 说真的网上的文章讲得不是很乱,以下是我总结出来的。

WebGL 作色器

作色器的基本理念我不赘述。不了解的看下面猜测一下也能猜出个大概。 这里从 PIXI.js 的源码中来看,用blurXFilter为例,顶点作色器的代码如下:

attribute vec2 aVertexPosition;
attribute vec2 aTextureCoord;
attribute vec4 aColor;

uniform float strength;
uniform mat3 projectionMatrix;

varying vec2 vTextureCoord;
varying vec4 vColor;
varying vec2 vBlurTexCoords[6];

QT使用代理在线安装的方法

QT 离线安装包对于把 Android 编译和 MSVC 编译混在一起搞,非常麻烦。而官方给的 MaintenanceTool.exe 有问题,没法用来增加额外的安装包,所以就必须使用在线安装。

而关于在线安装。官方给出的一个完整的包地址,里头的 URL 竟然是绝对路径而且还带 SHA1 校验了。

所以如果你用第三方镜像,会导致解析下载下来的 XML 文件后最后还是由于绝对路径跑到官方的站点下下载,而如果你自己篡改 XML 文件转发到镜像站点,就会发现各种莫名奇妙的问题,什么插件、依赖找不到之类的。

解决办法:

1. 修改系统 host 文件:

127.0.0.1 download.qt.io

download.qt.io这个域名不能在 XML 文件改变,那就把这个域名的控制器拿到手

2. 然后使用 nginx,加入配置:

npm install自定义argv解析

问题描述与解决的方向

问题来着一下这种需求出现的时候:

一个包,要面对不同的用户:Client 与 Server。

由于这个包中 Client 与 Server 共用部分代码,如果要拆分成 Client 包与 Server 包的话,那么就还要有一个公共 Common 包。

所以要实现以下效果:

默认为安装 Client 的包

npm install my_npm_pkg

增加--server参数为安装 Server 的包

2015年

nodejs的自定义全局模块

需求如下: 写了一个类:function A(){/*...*/},然后想给他暴露到全局中,作为一个可require的模块,无需再通过路径查找获取。 这里推荐三种方法: 1. 重写require函数,加一层请求拦截的包裹。 2. 根据process.mainModule.filename来获取对应的node_modules文件夹,在里面创建对应的临时文件来进行链接。 3. 将对象注册到底层模块列表中。 无论哪种方法,最重要的还是要避免跟系统模块名字冲突。其中第二种有点投机取巧,因为设计到文件的读写,进程意外中断导致文件残留等等不方便的因素导致我并不推荐。 而第一种和第三种都要涉及到一个对象:process.binding("natives");这里返回的将是原生模块的代码。 第三种无疑效率最高,实现方法如下:

var natives_modules = process.binding("natives");
function defineAs(module_name, obj){
    if (natives_modules.hasOwnProperty(module_name)) {
        throw Error("Module Name has be defined");
    }
    var __module_uuid = +new Date + Math.random().toString(32);
    global[__module_uuid] = obj;
    var scriptContent = 'module.exports = global["'+__module_uuid+'"]';

2014年

JS语法解读

以下内容都是由EsprimaJS中提取而出,确保完整性和准确性

声明

  • BlockStatement 代码块,存在于function,if-else,while,try等可以包裹代码块的地方
  • BreakStatement break当前循环或者break指定label的循环
  • ContinueStatement continue当前循环或者continue指定label的循环
  • DoWhileStatement do-while循环声明
  • DebuggerStatement debugger关键字声明,用过JS调试的都知道
  • EmptyStatement 如果;前面没有任何代码,就是空的
  • ExpressionStatement 表达式语句,其它声明以外都会用到这个来对表达式进行包裹
  • ForStatement for循环声明
  • ForInStatement for-in循环声明
  • IfStatement if声明,内部已经包含了consequent与alternate两部分的代码内容
  • LabeledStatement 标记声明,用于循环体前针对声明,使得break、continue等关键字能在嵌套循环体中明确控制标记声明的循环体
  • ReturnStatement 函数体返回值声明
  • SwitchStatement switch条件分支语句声明
  • ThrowStatement throw异常抛出声明
  • TryStatement try错误捕获声明
  • WhileStatement while声明
  • WithStatement with声明

[TED]George Whitesides: Toward a science of simplicity

Most of the talks 大多数的演讲

that you've heard in the last several fabulous days 你在前几天那些美妙的日子听到的

have been from people who have the characteristic 都是来自一些很有特点的人

that they have thought about something, 就是他们都已经思考过一些事

they are experts, they know what's going on. 他们都是这方面的专家,他们知道这个领域的现状

All of you know about the topic 你们都知道

that I'm supposed to talk about.

找寻JS一些小技巧

这些技巧更多主要针对JS效率方面而言(不一定)。持续更新,欢迎登录Github在Comment中共享您的一些发现。

谈谈单页应用于SEO

注: 本文章的文字内容来自鬼懿群 20:00 2014/3/10 的内容,并非访谈形式。内容有所删减。

第一话 各自的方案

@浩明 1999 现在网上大部分流传的方法是这样:

index.html 的代码如下:

<a href="Ajax.html?id=1" onclick="fun(1);return false;">1</a>
<a href="Ajax.html?id=2" onclick="fun(2);return false;">2</a>
<a href="Ajax.html?id=3" onclick="fun(3);return false;">3</a>

通过在 A 标签上 return false 来区别搜索引擎和用户。

那我有一个疑问,搜索引擎收录的页面链接是 Ajax.html?id=3,如果我在百度搜索到内容,点进去的应该是 Ajax.html?id=3 这个页面,而我们的期望并不是这样啊,我们是希望用户点击到 index.html 这个页面并触发 onclick="fun(3);

The Study of Folklore 民俗学

关于HTML5的文件类操作入门与实践

文件类的提供使得 JS 能够操作所能获取的各种文件的操作提供支持。 比如 Ajax 获取的文件,input[type='file']选中的文件,或者是用户自己生成的文件等等……。

推荐阅读:理解 DOMString、Document、FormData、Blob、File、ArrayBuffer 数据类型——张鑫旭

本文针对文件读取以及 Blob 对象的简单使用做一个事例。

文件读取

  • readAsText:以文字方式读文档內容,放到 result 属性,默认编码 UTF-8。
  • readAsDataURL:读取到的內容会编码成 data URL,放到 result 属性。
  • readAsArrayBuffer:result 属性会包含一個 ArrayBuffer 物件。
  • readAsBinaryString:以二进制方式读文档內容,放到 result 属性。

上面所说的 result 属性,指的是FileReader.result。比如下面代码:

var reader = new FileReader();
reader.onload = function (e) {

说说F.I.S背后的思想

注: 本文章的文字内容来自鬼懿群 19:08 2014/2/11 的内容,并非访谈形式。内容有所删减。 主要人物 @漂流瓶

首先,你要相信,有办法做到同一个页面,根据不同的 url 或者 ua 可以把它输出为 json 结构的数据或者传统页面 以 php 为例 稍等,我简单 coding 一下

image

这是一个传统 html 页面 有三个小部件 A、B、C 现在,我们希望用组件化的方式来维护他们,代码可能变成了

image

假设,这个页面的 url 是 /index 好了,我们是否能开发一种框架,使得: 1 当一个普通的浏览器用户或者网络爬虫用户访问 /index 的时候,输出一个传统的 html 内容

浅谈可穿戴设备

貌似现在的流行都有着“一目了然的优越感”。这也是个人定制这种服务发展起来动力不足的原因。否则ipod touch也不会被库克边缘化,不然苹果就能用一件功能阉割的电话产品配合ipod touch就达到iphone所没有的强悍。

毕竟英雄不是最强,但始终是最炫。折衷以上两点,才是可穿戴设备发展起来的原因:极端化了功能,类似定制;看起来很炫。但是格逼的成分太高。

我不得不说大部分人会认为对着Siri说话很不自然。因为人和机器的信任并不能一蹴而就,语音功能是最近发展起来的,不得不说并不完善,因为这种不完善使得人们不得不一定程度上扭曲自己的说话思维和方式,如果你能很快适应,说明你适合当程序员^_^。结果显而易见。

而微信折衷了,它没有太大改变人们通讯的习惯,虽然把原本电话这种实时性阉割了,但是却融合了网络文字聊天的方式,以极低的学习成本,将用户过度过来。

为新的行为建立信任的的最佳方式就是高效的实时性与稳定,至少这不会让你尴尬。要么就保持低调,就像现有很多可穿戴设备的软件还是以手机App的形式,而不像Gear。Gear这款产品应该说定位算是比较保守了,但也是没有很好流行起来的原因:你的高格和功能似乎没有在同一条水平线上,没有让这手表合情合理,这就是做作装逼(我并无贬义,仅仅以产品角度融合国内情形进行分析)。

说到可穿戴设备不得不提到Google Glass。十分优秀的产品,聪明。但是和语音搭上关系难免遭人排斥,好在加入了触摸功能。用上面所讲的分析这款产品:1、在新鲜事物上保留了更高的透明度与实时性,这是人与新事物搭建有效信任的有力基础;2、为了保证稳定性,Google的人员果断用限量公测的形式来逐步完善,这果断是Apple公司做不到的,因为这是将软件界的开发形式搬到硬件界来,Google做网络应用的最擅长用这手段妥妥的。3、有理有据的装逼,什么意思了,因为头戴式,为什么头戴式,因为她采用投影技术将屏幕内容投影到视网膜上,这点就直接决定了头戴式是最佳的选择(但这种思维并不代表实际萌生流程),所以首先在穿戴形式上,理解的人都会理所应当地接受它。

但Google Glass最大的软肋是她的入侵性。她是现有的把互联网与实际联系得最紧密的电子产品,没有之一。所以她的使用很难约束,或者说以现有的社会并不能完美融洽地结合。当初iphone4的出现成功在于他给了我们说需要的,而Glass,给了太多。因为现有社会并没有一种完善的通用的心理形态来赋予Glass合理性,这就直接导致了隐私问题和心理问题等等……

不过我现在却急需有一种电子产品,也就是文章开头所说的那件配合ipod touch达到超神的东西。它的核心功能就是电话与释放短距离wifi,应该说还有蓝牙(推送电话、短信到第三方产品上运作)。可能就现在的随身WiFi一般的小东西,别在衣服上。如果要ipod shuffle改装,倒也可以,短信就不用推送了,也就是蓝牙功能直接阉割……当然这种产品不适合发达国家。

【记录片】Helvetica

《传奇字体 Helvetica》——2007

介绍: 你可能没听过Helvetica,但你一定见过它,而且每天都要见个好几次,如果你有iPhone,说不定一天会看见它几十次甚至上百次。 Helvetica 存在于我们的周遭,却让人浑然不觉。不过对某一群人来说,Helvetica 绝对是耳熟能详的字眼。对他们而言,Helvetica 是老师、是朋友,同时也是敌人,Helvetica 是个摆脱不去的枷锁。

这群人是平面设计师与字体设计师(当然还有很多我没提到的,例如网页设计师、app 开发者)。而Helvetica 是一款字体的名字,一款被大量使用的字体。

2007年,传奇字体「Helvetica」诞生满五十周年。独立制片人,同时也是本片导演Gary Hustwit 拍摄这部以字体Helvetica 为主题的纪录片,他的理由很简单:「因为它就在我们的身边。」

Type is saying things to us all the time.

Typefaces express a mood,an atmosphere,

they give words a certain coloring.

Everywhere you look, you see typefaces.

But there's probably one you see more than any other one, and that's Helvetica.

2013年

MVVM框架中关于状态机的概念与实现动机

状态机是目前库中所存在的一个高级的概念,它一定程度上是现有功能的一个组合,使用字符串指令针对状态机的操作,以下是其运作流程:

绑定一个事件
    -> 这个事件是一个命令,目前有四种基础命令:
       (赋值)=,(添加)+,(移除)-,(切换)?
            -> 事件触发,动态解析命令对状态机进行相应的操作
               (某种程度上就限制了效率的保证)

这四个基础命令都是统一的格式:双目运算符的格式。 左边的参数是目标key,字符串类型,所以这是可动态的:

{{" {{key}} = {{"static value"}} "}}

可以看到一个命令是一个字符串,而后两个参数都需要用{{}}进行包裹,其中第一个参数作为目标key,第二个参数是赋值内容,在源码不到白行的实现中也是很明了的知道其命令最终编译出来的代码是:

textArea的placeholder不能换行的解决方案

JS解决方案当然是最万能的。 所以这里主要讲的是CSS解决方案,整理自鬼懿IT高级群的讨论 10-11-2013。 先上一段官方的说辞:

The placeholder attribute represents a short hint (a word or short phrase) intended to aid the user with data entry. A hint could be a sample value or a brief description of the expected format. The attribute, if specified, must have a value that contains no U+000A LINE FEED (LF) or U+000D CARRIAGE RETURN (CR) characters.

1

首先要知道的是HTML属性中的值会原封不动地输出到页面中,所以:

<textarea placeholder="line1  \n lin2 <br> line3 \A line4 
line5"></textarea>

是不会其任何作用的(这里line4和line5中的回车写法会被过滤掉,但是title属性就不会)。 所以就要借用到CSS。

2

先说webkit浏览器的解决方案:

Quete of the Day

C++ Primer 学习笔记(二)

笔记在comment中

C++ Primer 学习笔记(一)

笔记在comment中

「TED」Gavin Pretor-Pinney: Cloudy with a chance of joy

Clouds. Have you ever noticed how much people moan about them? They get a bad rap. If you think about it, the English language has written into it negative associations towards the clouds. Someone who's down or depressed, they're under a cloud. And when there's bad news in store, there's a cloud on the horizon. I saw an article the other day. It was about problems with computer processing over the Internet. "A cloud over the cloud," was the headline. It seems like they're everyone's default doom-and-gloom metaphor. But I think they're beautiful, don't you? It's just that their beauty is missed because they're so omnipresent, so, I don't know, commonplace,

「转」如何开始一个模块化可扩展的Web App

原文地址:http://avnpc.com/pages/start-a-modular-extensible-webapp 作者:Allo Vince

虽然从没有认为自己是一个前端开发者,但不知不觉中也积累下了一些前端开发的经验。正巧之前碰到一道面试题,于是就顺便梳理了一下自己关于Web App的一些思路并整理为本文。

对于很多简单的网站或Web应用来说,引入jQuery以及一些插件,在当前页面内写入简单逻辑已经可以满足大部分需要。但是如果一旦多人开发,应用的复杂程度上升,就会有很多问题开始暴露出来:

  • 数据源一般都与页面分离,那么App启动一般都需要等待数据源读入。
  • UI交互复杂时,需要将逻辑通过面向对象抽象后才能更好的复用。
  • 功能间一般都存在依赖关系,需要引入支持依赖关系的模块加载器。

那么如何解决这些问题,就以一个简单的订餐App为例,从零开始一个模块化可扩展Web App

这个简单的App基于HTML5 Boilerplate、requireJS、jQuery Mobile、Underscore.js,后端逻辑用jStorage模拟实现。完成后的成品在此。所有代码可以在github查看。下文将逐一介绍实现的思路与方法。

从选择一个好模板开始

开始一个Web项目,HTML的书写总是重中之重,一个好的HTML能从根源上规避大量潜在问题,所以Web App应该全部应用一个标准化的高质量HTML模板,而不是将所有页面交由开发人员自由发挥。

这里推荐使用HTML5 Boilerplate项目作为App的默认模板以及文件路径规范,无论是网站或者富UI的App,都可以采用这个模板作为起步。

最美人瑞这样走来

作者:柳鸣九 来源:《最爱北京人》


  早在做同事之前,在东四头条的社科院宿舍大院,我和杨绛先生就做过邻居,于是比起别人,我便多了一些熟悉与就近景仰的机会。按“翰林院”(中国社会科学院)不成文的规矩,对她这样的旧时代过来的海归大家,作为小字辈的我,要按其本名,尊称她“季康先生”。

  初见时,季康先生年过半百,精瘦娇小,举止文静轻柔,但整个人极有精神,特别是两道遒劲高挑而又急骤下折的弯眉,显示出一种坚毅刚强的性格。和其夫君锺书先生的不拘小节、有时穿着背心短裤就见客不同,她的衣着从来都整齐利索,即使在家不意碰见来访者敲门的时候。

  当时研究所有两位女士以注重形象着称。一是“九叶诗人”之一的郑敏,她是美国式的艳丽和浪漫风格;另一位则是杨季康,典雅华贵,冬天常披一件裘皮大衣,很是高雅气派。这二位都保持着西洋妇女那种特定的“尊重自己,也尊重别人”的习惯,每次在公共场合露面,都对面部做了不同程度的上妆,这在上世纪五六十年代的北京,是极罕见的。不过,前者的妆较浓,而季康先生的则几乎不着痕迹,似有似无。

  在公众场合,季康先生从来都是低姿态的,她脸上总是挂着一丝谦逊的微笑。在学习会以及其他重要的场合中,季康先生极少发言、表态,实在不得不讲几句的时候,她总是把自己的语言压缩到最少。当时我们想:杨老太这是在“刘备种菜园子”吧。多年后看到她以“点烦”原则(即把用词精简到不可能再精简的程度)翻译《堂吉诃德》,才发现,这不仅是真正发自内心地尊重人,而且真正做到了会尊重人。

  在我见到的大家名流中,钱、杨二位先生要算是最为平实,甚至最为谦逊的两位。季康先生虽然有时穿得雍容华贵,神情态度却平和得像邻里阿姨,而不像某些女才人那样,相识见面言必谈学术文化,似乎不那样就显不出自己的身份与高雅。认识久了,她对晚辈后生则有愈来愈多的亲切关怀,的的确确像一个慈祥的阿姨。

  但这个看似低调谦恭的阿姨,也有吃了熊心豹子胆的时候,且这个时候出现得无比不合时宜。“文革”之初,他们被造反派揪出来,挂了牌子押上批斗会。可杨季康对“天兵天将”的推推搡搡公然进行了反抗,而且怒目而视。这还了得!在批斗会上,那么多党内老资格的革命干部,哪个不是服服帖帖?于是盛怒之下的造反派对她狠加惩罚,给她剃了个阴阳头。我第一次惊奇地感到季康先生性格中的凛然。要知道,“牛棚”里有不少从火线上转业过来的老战士,没有一个敢于如此维护自己被践踏了的尊严。

  “文革”后期,钱、杨二位先生尚未获得平反,有家回不了,四处流转。更多像我们这样的“小人物”,也在苦等“落实政策”,精神备受煎熬。同是天涯沦落人,处境谁也不比谁强到哪儿去。说起来先生们在浩劫中失去的,远比我们要多得多,但对于这群甚至未能为他们说句公道话的晚辈,他们以极高的涵养、含蓄内敛且从不显于言辞的方式予以理解、宽容和无私帮助。

  有一次,我家因额外开支经济上一时告急,杨先生得知后主动支援了我们几百元钱。后来有一天,她的助手递给我一个小纸包,里面有二十元人民币,“这是先生要我交给你们的,补贴你们的家用,要你们收下,什么道谢的话都不要讲。”那个时期,我与妻子朱虹两人的工资加起来只有一百三四十元,承担着抚养两个孩子与赡养双方父母的责任,由于业务断了路,没有半点稿费收入,生活的确相当清苦。先生雪中送炭,我们只好恭敬不如从命。没有想到,到了第二个月,又有一个小纸包。然后,第三个月,第四个月……

【转、修】书写具备一致风格、通俗易懂 JavaScript 的原则

书写具备一致风格、通俗易懂 JavaScript 的原则

原文地址


无论有多少人在维护,所有在代码仓库中的代码理应看起来像同一个人写的。

下面的清单概括了我作为原作者的所有代码中使用的实践。在我创建的项目中的所有构建代码都必须遵循这些规则。

我并不想强制别人在其代码或项目中使用我个人所偏好的代码风格;如果已经存在一个通用编码规范,它必须受到尊崇。

"对风格的挑刺毫无意义可言。它们必须是指导原则,且你必须遵循。"

-- Rebecca Murphey

"成为一个优秀的成功项目管理者的一个条件是,明白按自己的偏好风格写代码是非常不好的做法。如果成千上万的人都在使用你的代码,那么请尽可能通俗易懂地写出你的代码,而非在规范之下自作聪明地使用自己偏好的风格。"

-- Idan Gazit

【TED】贫困,金钱与爱

贫困,金钱与爱

The stories we tell about each other

我们互相讲述的故事

matter very much.

非常重要

The stories we tell ourselves about our own lives matter.

我们谈论与自己生活相关故事是有意义的

And most of all,

而我认为最重要的是

I think the way that we participate in each other's stories

雅安地震-韩寒被删除的博文

韩寒

其实我本来不想写这篇文章的,因为写了肯定会引来口水之争的,而我已经不想再去和别人争论什么。我曾经说过,如果我愿意,我可以去颠覆你们二十多年来形成的价值观,因为生活中很多在你们看来是理所当然的观念都是错误的,但后来马上删掉了这句话,因为我不想引来争论,并且改正你们对这个世界的认识对我来说没有任何的益处,而不是我不能。西南大旱,近200天没有下雨了,对西南的百姓的生活造成了极坏了影响,于是乎,广大人民再一次涌现了爱心精神,捐款的捐款,捐水的捐水。这是在汶川地震之后,又一调动全国人民积极性的事情。

捐款捐水,属于献爱心的行为,是一种高尚的行为,本身并没有可以指责的地方,相反,这是我国人民巨大民族凝聚力的体现。但我想说,并不是好的出发点都能带来好的结果。表面上,很多人的善举是在帮助西南的百姓,但我想说,你们的爱心举动使这场灾害的主角政府退到了幕后,而你们的行为并不能给西南的抗旱带来多大的帮助。在某种程度上,你们在帮西南百姓的倒忙。

我不知道大家发现一个问题没有,中国的灾害都是突然降临的,突然的出现在全国人民的面前。如果说地震我还能理解的话,那么干旱我实在难以理解。干旱的形成不是一天两天形成的,等到媒体关注的时候已经180多天没下雨了,我不知道媒体为什么不是在170天的时候关注的,为什么不是在160天的时候关注的,而偏偏是在180天以后才开始关注,而且是齐刷刷的关注。

难道非要等到180天之后干旱才能算是干旱?180天之后的干旱才能对人的生活产生影响?前两年的河南干旱也一样,等河南的农作物要绝收了,政府突然一下子蹦了出来,说救旱。我就想问,政府早干什么去了?前几天开两会的时候西南的干旱怎么没人来关注?旱情不怎么严重的时候怎么不来关注?农作物还没有绝收的时候怎么没人来救旱?现在出来救旱,能有多大效果?这完全是政府的失责,而你们的热情掩盖了政府的失责。而这种失责不受追究的结果就是在以后,这种事情还会继续发生。

四川地震就是活生生的例子,汶川地震之后,对相关官员责任的追究最后不了了之,虽然经过地震之后,四川的学校建筑可能会比以前结实一点,但我想说,当下次的地震不再是四川,而换个别的地方,四川的悲剧依然会再现。

记得以前看过一篇文章,记者采访一个捐助者,问:如果你捐的钱会被人贪污了,你还会捐吗?那人回答说:会的,如果我捐了100,被贪污了90,至少还会有10元能到达那些需要帮助的人手中,而如果我不捐,那些人连一分钱都没有。报道发出后,很多人感动的一塌糊涂。从表面上看,是这样的。对于这样的人,我只能以好人来形容,而不能冠以对社会有贡献的人。我说过,好的出发点不一定就能带来好的结果。

如果你以为那些受灾的人拿到你捐的那一点钱之后你就成功的帮助了他们,我只能说,你真的很无知,虽然是个好人。因为,你的那一点捐助不是在帮助他们,而是间接的害了他们。因为有些事情由民众来做,其效果真的微乎其微。

德国在17世纪就开始推行全民义务教育,而日本在明治末年的义务教育入学率比中国2000年的义务教育入学率要高。所以,当别的国家早早就成为发达国家,而中国还在为“小康”奋斗时,不要心里不平衡,这很正常。

不要心里不平衡,这很正常。不要跟我说中国国情不同,我不知道对于日本这样一个人口众多,土地贫瘠,资源匮乏的“日本国情”十分突出的国家,是如何成为世界第二号强国的,他们似乎连成为发达国家都没有理由,但他们做到了,事在人为。对于那些“我跟他谈国情,他和我谈接轨,我和他谈接轨,他和我谈国情”的人,我只想说,你的智商,充其量只配在别人把你卖了之后帮别人数数钱。

js树形索引,多关键字并查

在博客园上看到EtherDreamJavaScript 上万关键字瞬间匹配这篇文章,感觉不错。于是改了DEMO里面的代码(耦合度是在是太高了,几乎得重新一遍才行……)

这种方法的有点就是:树形的结构擅长于同时匹配多个关键字。单个关键字来说,直接用indexOf来查询、切割字符串,速度更快。

改动主要在两个方面:

  • 对语句规范化(JSHint规范);
  • 改写了一部分语句,核心的语句效率几乎是达到最大,比如 if(match === true)if(match) 快,另外把得出的匹配结果可读化,这个有点耗资源,不过有它存在的必要性,在后期处理数据时更快。

核心代码:

var treeSearch = {
    makeTree: function(strKeys) {
        "use strict";
        var tblCur = {},
            tblRoot,
            key,
            str_key,
            Length,