捉到 Edge 的渲染 BUG 一只

最近测试同学发现,用 Edge 浏览器,访问问卷的新版设置界面,部分联动锁总是显示异常,该锁的没锁,不该锁的又锁上了。

期初以为是 React 在 Edge 里面有什么 bug,一圈排查下来,发现状态、样式都是正确的,偏偏就是显示出来的很诡异,鼠标在上面晃动一下又正常了(浏览器绘制BUG,以前 IE7 也遇到过)。

这个 BUG 的特征,当使用 :disabled ~ label 这种伪类 + 相邻元素进行样式定义的时候,如果动态修改 input.disabled 的值,其对应的 label 的表现就不会更新。

Demo

这个 Edge 的 BUG,会让 checkbox 美化的最佳实践受到影响

解决方案

修复起来也简单,除了用 :disabled 选择器外,再补一个 .disabled 的选择器进行兜底。

这年头各种状态驱动界面,class 用起来都不是个事。

测试版本号

  • Microsoft Edge 42.17134.1.0

React Key 在非数组、队列场景下的应用

猜猜这段代码会发生什么事?

以为仅仅是切换下按钮?

其实是会执行下面的流程

  1. isEditingfalse 的时候,点击 button
  2. isEditing 变成了 true
  3. 重新 render,在 virtualDOM 进行 diff 的时候,给 button 添加了 type=submit 以及 form='form-1'
  4. #form-1 表单被意外的 submit 了

来个 DEMO 验证下

基于 styled-components 实现一套皮肤系统

结构定义

styled-components 使用模版字符串特性,让我们可以保持原有 CSS 的书写习惯来编写 CSS,同时,利用 ${ props => props.theme.xxx } 的方式,实现皮肤系统中挖空填值的能力。

继续阅读“基于 styled-components 实现一套皮肤系统”

利用 Promise 实现任务流的自动重试

背景

微信小程序不支持 HTTP 的 cookie ,其会话机制是通过开发自己维护一个 session_id 在小程序的本地存储中,每次调用 wx.request 的时候都带上这个 session_id 来实现的会话机制。

那么,有会话机制,就会存在会话失效、更新等等问题。

传统的 HTTP cookie-session 机制,当会话失效的时候,可以在 HTTP 的返回头里面通过 setcookie 来静默返回一个新的 session_id ,小程序就比较麻烦。

传统的实现方案

1.理想的实现情况

2.为了容错,我们会添加返回判断,但错误的时候,再调用一次

上面这种方式,在接口的返回值中做一次判断,然后再执行一次,好像就解决问题了。

但如果我们业务的接口非常多,返回判断是不是要添加很多次呢?

继续阅读“利用 Promise 实现任务流的自动重试”

await 性能猜想

前言

以往我们推崇异步 I/O 来实现高并发下的高性能,如今 NodeJS 步入 8.x 时代,async await 可以用同步的写法来实现异步处理,不知道对性能是否会有影响,来做个简单的测试。

测试基准

Linode 1G 配置两台,一台用 ab 发请求,另一台跑4个测试用例。

先用 Nginx 跑一个默认服务,返回一个 html 文件,测试基准性能。

Nginx 访问默认的 html 文件,QPS 为 5162

同步访问文件

fs.readFileSync 是 fs.readFile 的同步版本

测试结果

QPS 为 3195

异步访问文件

测试结果

QPS 为 2945

使用 async 来封装异步操作

测试结果

QPS 为 2855

对比

  • 5162 Nginx
    • -1967
  • 3195 fs.readFileSync
    • -250
  • 2945 fs.readFile
    • -90
  • 2855 await promise fs.readFile

本来猜测的结果,应该是 Nginx >  fs.readFile > fs.readFIleSync > await + promise + fs.readFile

实际结果却是 Nginx > fs.readFileSync > fs.readFile > await + promise + fs.readFile,这下傻眼

 

尝鲜 Ubuntu 17.04

冲着自带 Kernel 4.10 去的

可以不用 apt-mark 来锁住内核版本,避免更新的时候又自动降级了

不知道是今晚线路波动还是其他原因,升级后,明显感觉 SSH 卡了很多,检查过 BBR 已经开启,起了怪了

wget 一个百兆文件,开 vnstat -l 进行统计

proxy_pass 的结尾要不要 “/” ?

这周有个临时需求,需要在我们的业务上面集成一个合作方的站点,这种事情用 proxy_pass 就非常方便

一开始用的第一个方法,结果一直提示 404,还以为 Location 规则没命中,反复查证,却忘了第一个配置会把 path 给透传到 proxy_pass 端,实际上我一直在访问 http://127.0.0.1:3000/3rd/ ,而这个地址显然是不存在的

后来排查后端的日志才发现,在结尾补上 “/” 后,终于把 /3rd/ 这个路径给吃掉了

Backbone源码研究 – Backbone.Model

前言

都因为 IE8 不支持 Object.defineProperty,但是业务还不能脱离 IE7 和 IE8,故研究下 Backbone.Model 的实现机制,找机会给主流的 MVVM 框架补丁

伪代码

先来看看 Model 的构造函数

很简单的代码,做了一些初始化赋值的事情。

用到了一个小技巧  attrs = _.defaults(_.extend({}, defaults, attrs), defaults);  来防止误传入的 undefined 覆盖掉默认的 defaults 值。

Backbone 的精粹都在 set(){} 这个函数里面。

整个 set 里面,实际干活的就是 unset ? delete current[attr] : current[attr] = val;  。

没看明白 this._changing 和 this._pending 的使用场景,感觉是一个当多个 set 同时执行时候的一个标记位,但是 JS 是单线程执行,里面又都是 for 语句,按理说可以不用这两个标记位。又或者是我的理解有误。

more

看到这,给各种Observer打补丁就有了可行性,支持 Object.defineProperty 就用 Object.defineProperty,不支持的则降级到走 Backbone 的这种 for in 方式。

涨姿势 – 不一样的服务端长连接方案 – 客户端代理

仔细再看一次腾讯云的小程序解决方案,发现一个新大陆。

传统的 LAMP 架构,PHP-CGI  这种方式是很难处理长连接的。要么写死循环的方式来握住请求,要么使用 swoole 这种,通过 C 拓展来支持。

而 NodeJS 由于官方 DEMO 就是支持跑一个 HTTP 服务,所以处理这些长连接会方便一些(大家好懂一些,我觉得 PHP-CLI 方式也是一样的)。

信道服务

建立连接过程

  1. 小程序请求业务服务器
  2. 业务服务器和信道服务建立连接
  3. 业务服务器告诉小程序你可以和信道服务建立 ws 了
  4. 小程序和信道服务建立 ws
  5. 信道服务请求业务小程序的 ws 建立完成

连接成功后的通信方式

  1. 小程序请求信道服务,信道服务转发请求给业务
  2. 业务请求信道服务,信道服务推送到客户端

我最开始看的时候,没注意信道服务是一个云服务,琢磨着腾讯云的 SDK 难道有什么新的黑魔法来实现 PHP 的长连接。

https://github.com/tencentyun/wafer-php-server-sdk/blob/master/lib/Tunnel/TunnelService.php

看里面的代码,各种 onConnect\onRequest,看着就很像长连接的 API,但他基于 CI 是怎么实现的长连接了?看 composer.json 里面没有用什么黑魔法,搜索代码里面没没看到任何死循环。

客户端代理

为什么信道服务的 icon 是一朵云?

原来腾讯云把这个信道服务抽象成了一个 PaaS 的云服务,这根本就是一个客户端代理。而开源出来的 PHP-SDK,里面没有任何长连接的实现方案。

  • 业务服务器 -> 小程序:业务服务器只需要请求信道服务即可,信道服务接收到业务的请求后,会将 HTTP Body 部分,转成 WS 的消息推送给小程序。
  • 小程序 -> 业务服务器:则正常通过 WS 发送消息给信道服务,信道服务转成 HTTP 请求转发到业务服务器。

对于一些使用 PHP 开发的历史业务,大规模的长连接改造是非常困难,但部署一个长连接转发服务却容易很多(比如用 NodeJS 来写一个)。

腾讯云的这个思路可以帮助各种业务快速支持包括 SSE 和 WS 这两种长连接方案。并且由于业务服务器本身是 HTTP 方式,可以很容易给低版本 IE 做轮训兼容。

最后感谢腾讯云开源了他们的 wafer 方案。

Linux Kernel 4.9 & BBR

Google 真是共享了个好东西,先来看看效果。

Kernel 4.9

Linode 自带的 Kernel 4.9 没有编译 BBR 模块进来,直接切换是不行的,得手动替换 Kernel。

虽然 Ubuntu 官网的 Kernel 还没升级到4.9,但是他们官方的 ppa 已经支持4.9了,可以手动下载一个回来安装。

http://kernel.ubuntu.com/~kernel-ppa/mainline/v4.9/

根据自己的32位或者64位环境,wget 个新的 linux-imag-4.9下来

安装好之后,去 linode 后台,把 [Boot Settings] 中的的 [Kernel] 改成 [GRUB 2] ,这样 Linode 的 vps 重启后,就会使用我们刚刚安装的内核了。

BBR

网上开启 BBR 的方式,都是 echo "net.ipv4.tcp_congestion_control=bbr" >> /etc/sysctl.conf  这种暴力又不美观的方式,我是不喜欢。

仔细看了下,发现一个 /etc/sysctl.d/ 的目录,里面还有一个 README

这是一个带权重的配置目录,新的 php-fpm 目录下面也是通过这种方式来维护模块配置的。

这里面补一个 60-bbr.conf ,把开启 BBR 的规则放在这个 60-bbr.conf 里面就 ok 了。

根据 README ,执行 service procps start 就能看到效果。

More

在 /etc/sysctl.d/目录下还无意发现一个 10-network-security.conf,里面默认开启了 tcp_syncookies 来防范常见的 SYN-flood 类型的 DDoS。

JavaScript Cookbook 2nd 之 Function

昨晚翻了一下,虽然都是一些旧知识,不过深入下去对照着其他资料一起看,还是能发现一些有意思的地方。

函数式编程

反正之前我是没搞懂函数式和命令式的区别,也很疑惑函数式编程中,如果出现分支怎么办,昨晚总算弄明白了。

传统的命令式编程,我们会这样写业务逻辑

而函数式编程,我们则可以这样写业务逻辑。

这里可能会有一个疑惑,互斥的 processA 和 processB 怎么进入了同一个处理流程,这样和需求就不符合了?

在这种情况下,我们还需要在 processA 和 processB 的内部,把退出条件补上。

调用栈

JS 在执行的时候,有一个函数调用栈,栈里面放着一个个的函数调用帧,这些帧保存着所属函数所需的所有变量信息。

函数调用会在内存形成一个“调用记录”,又称“调用帧”(call frame),保存调用位置和内部变量等信息。如果在函数A的内部调用函数B,那么在A的调用帧上方,还会形成一个B的调用帧。等到B运行结束,将结果返回到A,B的调用帧才会消失。如果函数B内部还调用函数C,那就还有一个C的调用帧,以此类推。所有的调用帧,就形成一个“调用栈”(call stack)。

浏览器拦截 window.open

我们发现有时候执行 window.open(),能正常打开新窗口或者新的标签页,而有时却又不行,会被浏览器拦截。

其原因是浏览器会根据当前调用栈,找到最初的caller,如果不是用户触发的,则拦截。

尾调用优化

由于函数调用的时候会生成新的调用帧,当递归调用的时候,调用栈中的调用帧增长会非常厉害,最终导致内存耗尽而触发 RangeError。

如果把函数调用放在函数块的最后一条语句,且不在使用外层函数的变量了,则外层函数所占用的调用帧已无存在意义。在进入内层函数的时候,可以直接用内层函数的调用帧替换掉外层函数的调用帧,从而大大减少内存占用。

其他

Partial Application 的模式,用来做 Library 和 SDK 都挺好的。一个可定制性高的底层接口,再通过类似 _.partial() 的方式,提供一个开箱即用的上层接口。就像 $.ajax()$.get() 一样。

_.defer()_.memoize() 可以用在有大运算量的业务场景。