NodeJS Event Loop 学习笔记

最近想要深入了解下 NodeJS 中 Event Loop 的工作机制,但网上的文章重复性较高,还派生出一些很容易混淆的概念,而且有些文章里举的例子甚至无法自圆其说。所以自己参照 Node 官方给的一篇介绍文章,和 Medium 上看到的一个系列文章,很好地介绍了 Event Loop 的工作原理,现在把学习笔记做个梳理:

  1. Node 主要通过 libuv 来实现异步机制,libuv 主要提供了两个功能:
    • Event Demultiplexer:代理 Node 发起的异步 I/O 请求,部分功能通过不同 OS 提供的异步机制(如 epoll/kqueue/IOCP 等)来执行,部分没有原生异步 API 支持的 I/O 功能,libuv 实现了一个线程池(默认线程数为4)来实现异步。应尽量避免使用线程池中的线程来执行异步,因为会造成性能问题(也可以通过设置$UV_THREADPOOL_SIZE来修改默认线程数)。
    • Event Queue:每个 cb 被称为一个 event,libuv 通过设置 Event Loop 执行机制,和多个 Event Queue 来执行异步 cb。
  2. l


阅读全文 »

iOS 下 JavaScript 实现复制功能

1. 背景

以下是对 iOS 下复制功能的简单实现,注意在 iOS 10 以下的版本中,以下代码是无效的

最近要实现一个小需求:iOS APP下,用户点击一个按钮,系统自动复制一段文本到系统剪贴板。通过查资料发现,iOS 出于安全性的考虑,对 Clipboard API 的使用有诸多限制。但是在 iOS 10 及以上版本中,可以通过 hack 的方式来实现该功能。

2. 方案

主要参考的是 SO 上的这个答案,在 iOS 10 及以上版本中,使用复制功能有以下限制:

  1. 只能复制<input><textarea>元素中的文本;
  2. 如果包含待复制文本的元素没有包含在一个<form>中,那它的contentEditable属性必须为true
  3. 第2步中的元素同时不能是readonly
  4. 待复制文本必须是被选中状态。

要满足上述4个限制,代码中需要做到:

  1. 把待复制文本放入<input><textarea>类型的元素 A 中;
  2. 保存 A 元素的contentEditablereadonly属性,以


阅读全文 »

前端热力图系统实现

1. 背景

今年6月开发了一个前端热力图系统,目前已经应用在公司的主要业务中,一直没时间做个总结,现在梳理一下实现思路。

先说下为什么要做这样一个系统,电商网站的一种常用营销手段,就是配置眼花缭乱的活动页展现给用户,有的用来展示各种商品,有的供用户领取优惠券。但屏幕的展示空间有限,如何配置不同的模块才能最大化利用页面空间,一种比较好的方式就是采集用户点击数据,绘制出热力图,供产品、运营和设计同学参考,不断优化模块配置,有效提升点击转化。

2. 系统架构

热力图架构

先将热力图系统进行子功能拆分,可以得到以下几个部分:

  1. 用户点击数据采集:包括页面埋点、数据入库;
  2. 热力图绘制:包括:数据读取、数据加工、热力图绘制;
  3. 数据查询平台:主要是按日期和活动ID查询自定义区域的点击数。

2.1 数据采集

数据采集部分,主要通过事件代理在body上绑定click事件,采集数据主要包括:

  1. x:点击事件触发相对于 document 的横坐标,主要取自于event.pageX
  2. y:点击事件触发相对于 document 的纵坐标,主要取自于event.pageY


阅读全文 »

按钮加载效果实现

1. 背景

之前的加载大部分都是 toast 的形式,但现在一些 Native 的按钮点击都将加载动画做在了按钮上,给人感觉很好,比较常见的就是移动端支付宝的支付按钮,点击后有个旋转的圆圈表示加载。现在也来模拟一个该效果。

2. 实现

看到这种线段变化的动画,直接想到之前用的 svg 配合 strokeDasharray 属性来实现,具体效果如下:

See the Pen DynamicButton by zee (@bt404) on CodePen.


阅读全文 »

WebView 中判断键盘是否弹出

1. 背景

最近在开发中遇到这样一个问题:页面中有一个输入框input,一个 Modal 框modal,一个点击打开 Modal 框的按钮btnOpenModal,和一个提交表单的提交按钮submit。当input获得焦点,键盘弹出时,如果直接点击btnOpenModalinput会失去焦点键盘收回,导致页面高度改变,modal弹出后会有一个闪烁。所以需要在点击btnOpenModal时,判断当前键盘是否弹出。

2. 方案

最初,考虑在点击按钮时获取输入框元素,判断它是否处于focus状态,来判断键盘是否已弹出。但点击按钮之后,按钮就成为了document.activeElement。此时输入框必然已失去焦点,导致无法通过该方法判断键盘是否弹出。

考虑监听输入框的focusblur两个事件,当focus触发时,为当前视图容器this.cnt添加一个标识class,假设为KEYBOARD_STATUS;当blur触发时,则移除该class。当点击btnOpenModal时,判断this.cnt是否拥有KEYBOARD_STATUS这个class。如果有,则说明


阅读全文 »

单页视图引擎(SPA)添加视图切换动画

1. 背景

9月的时候,为单页试图引擎 Cyra 2.1.0 版本增加了一个创建视图切换动画的功能,使得 i 版页面之间切换也可以有一个滑入滑出的效果。一直没有时间做一个梳理,现在记录一下。最终的效果如图:

Cyra Animation

补充:悲剧,刚修复了一个 bug,如果想要设置ANIMATION_FUNC,则需要升级到v2.1.4

2. 思路

其实思路比较简单,就是在 Router 中控制。首先,将所有视图容器position都设置为absolute。当切入视图完全渲染出来后(也就是执行完willAppear钩子函数),设置当前展示视图和即将展示的视图两者的 animation CSS 属性,在 animation 中修改视图容器的translateX来实现动画效果。

因为每个视图容器是单例的(Mix 页除外),当项目中出现循环路径,如:

View A -> View B -> View C -> View A

或者当 A 和 C 是同一个视图,只是数据不同。这种情况下,如果视图右滑,也就是用户主动通过点击“返回”按钮进入(返回)前一个视图时,当前视图向右滑出。如果滑出后不修改它


阅读全文 »

Chrome devtools 扩展与模块间通信

1. 背景

为了方便 Cyra 用户查看/管理项目中的视图及视图间的跳转和数据传递,决定开发一个 Chrome extension(扩展)来方便展示。开发中,最关键的问题在于如何解决各模块之间的通信。最终的实现效果如下图:

Cyra devtools

2. 开发

2.1 模块划分

这部分网上已经有很多文章来介绍,在此不再赘述。但网上涉及到devtools类型的插件比较少,所以简单介绍下。首先,在 devtools 中创建的 panel 本质是一个 HTML 页面。代码主要分为以下4部分:

  1. background.js:Chrome 为扩展提供的一个独立的脚本运行环境,在本例中,主要作为 content_script.js 和 devtools.js 之间通信的桥梁,因为 Chrome 并没有提供后面二者直接通信的服务。
  2. content_script.js:用来向打开的页面注入脚本。
  3. devtools.js:用来在 devtools 中创建一个 panel,并实现该 panel 和 background.js 之间的通信。
  4. draw.js:devtools.js 中创建 pan


阅读全文 »

记一次 Nginx URI rewrite 优化

1. 背景

既上一篇文章记录了组内单页引擎升级路由为 History API 方式,考虑到不支持该方式的浏览器/WebView,需要多页降级。如果不对 Nginx 进行配置,就会出现404,因为多数通过pushState得到的 URL 并没有真实对应的资源。

2. URI 形式

采用 History API 方案的 URL 格式如下:

http[s]://hostname/resource/project/page[/view]?arg1=value1

其中 resource 为资源目录,该目录下放置各个不同的项目文件夹,每个项目对应一个 project 目录。一个 project 中可能会有多个单页应用,每个单页应用对应 URL 中的一个 page。一个单页应用下的不同视图分别对应一个 view。因为采用的单页引擎支持默认路由,所以 view 并非必需。

比如 A 项目下有一个 refund 的单页应用,对应的 HTML 资源为 refund.html,为了 URL 的美观,在 URL 中去掉 html 后缀名。refund 管理两个视图 detail 和


阅读全文 »

History API 路由方案 Nginx 配置

1. 背景

之前使用 History API 作为路由方案升级了组内单页视图引擎。完整的实现还需要 Nginx 的辅助配置,因为当页面刷新时,通过pushState到达的路由很可能并不存在对应的资源,所以要使同一个项目下的所有路由匹配到项目对应的单页资源。

2. 方案

其实主要的工作是对3种资源的 uri 配置 rewrite:

  1. HTML 资源
  2. 引用到的项目 JS 资源
  3. 引用到的项目 img 资源

对应的配置如下:

其中/resource/wa/为所有项目所处的统一目录,第二级目录为项目所在目录,第三级对应项目下的某个单页,第四级为可选,且表示某个单页项目下的某个具体 view。


阅读全文 »

使用 History API 升级 SPA 路由方案

1. 背景

组内现在使用组内自己开发的移动端 SPA 引擎 Cyra,版本为 1.2.x。框架很轻,使用 hash 做路由,然后统一管理各个 view(视图)的状态以及 view 之间跳转。

开发过程中,遇到很多坑都和使用 hash 做路由有关。当然,问题并不在 hash 本身,而是和客户端以及后端 RD 配合中遇到的问题。比如:

  1. 验签问题,客户端没有将 hash 计算进去;
  2. 客户端会在 WebView 中访问的 URL 上拼接一些参数,作为客户端和前端之间通信的一种手段。由于拼接算法有问题,导致部分参数拼接到了 hash 后面,使得 Cyra 多个 view 之间通过 URL 传递的参数格式被破坏;
  3. iOS 版 APP 的 WebView 中通过 jsbridge 来修改 title,但是 hash 的修改无法修改。

综上,考虑升级框架的路由实现方案,使用 History API 代替 hash,并配合 Session Storage 做多页降级,以此来解决上述问题。

2. 方案

为兼容 Cyra 1.x 开发的项目,此次升级仅修改路由的内部实现方式,


阅读全文 »