Rust初探:实现二叉树的增删与遍历

Rust 简介

实际上自己接触 Rust 的时间还是很有限的,这里也不会对 Rust 进行长篇大论地介绍,简单来说,Rust 是一个性能和 c++ 相近的系统级编程语言,同时,由于其所有权与变量生命周期等机制的设计,使其相对于 c++ 来说拥有内存安全的优势,几乎不会出现诸如悬垂指针、数组越界、段错误等问题,在微软、百度、字节跳动等公司均有所使用。

关于 Rust 的特性以及未来,知乎这个问题中的一些高赞回答以及相关的评论,非常值得一看。

本文会以二叉树这样一个具体的例子出发,来对 Rust 的一部分知识内容进行学习。

阅读更多

深入Vue源代码解决时序问题一

viola 是一个支持 Vue 的动态化框架,其 Vue 版本在 Vue 官方版本 2.5.7 上进行了少量改写,本文针对其进行具体分析。

最初,有使用者报告一个错误:在 iOS 系统,退出页面的时候,框架报错:

TypeError: undefined is not an object(evaluating 'e.isDestroyed"

接到这个错误之后,我首先进入 Vue 的 debug 版本,尝试获取更详细的信息:

TypeError: undefined is not an object(evaluating 'componentInstance.isDestroyed"

我们顺利地拿到了报错的变量名称,去 Vue 源代码中搜索,我们可以发现报错之处:

destroy: function destroy (vnode) {
    var componentInstance = vnode.componentInstance;
    if (!componentInstance._isDestroyed) { // 这里报错
      if (!vnode.data.keepAlive) {
        componentInstance.$destroy();
      } else {
        deactivateChildComponent(componentInstance, true /* direct */);
      }
    }
  }

这里是 componentInstance 为 undefined,这个实际上是 vnode 的实例,其为 undefined,说明该 vue 组件在之前的阶段就已经出错不正常了,这里并不是错误的根源所在,我们需要再次进行寻找报错原因。

于是我们查看业务代码的所有日志,又发现了这样一条报错:

[Vue warn]: Error in nextTick: "TypeError: undefined is not an object (evaluating 'vm.$options')" 

初始化阶段出现这样一个错误,我们怀疑 vm 就是上文的 componentInstance,于是,我们打印报错堆栈:

 调用栈:
function updateChildComponent(
    vm,
    propsData,
    listeners,
    parentVnode,
    renderChildren
  ) {
        //...
        var hasChildren = !!(
              renderChildren ||
              vm.$options._renderChildren || // 这里报错
              parentVnode.data.scopedSlots ||
              vm.$scopedSlots !== emptyObject
            );
    }

function prepatch(oldVnode, vnode) {
      var options = vnode.componentOptions;
      var child = vnode.componentInstance = oldVnode.componentInstance;
      updateChildComponent(
        child,
        options.propsData,
        options.listeners,
        vnode,
        options.children
      );
    }

function patchVnode(oldVnode, vnode, insertedVnodeQueue, removeOnly) {}
function patch(oldVnode, vnode, hydrating, removeOnly) {}
function (vnode, hydrating) {}
function () {
        vm._update(vm._render(), hydrating);
      }
function get() {}
function getAndInvoke(cb) {}
function run() {}
function flushSchedulerQueue() {}
function flushCallbacks() {}

调用栈实际上有点冗长,不过我们还是能发现两个有用的信息:

  • 初始化阶段为 undefinedvm,就是 componentInstance,也就是和 destroy 阶段的报错属于同一个原因。
  • 根据调用栈发现,这是一个更新阶段的报错。

这引发了我们的思考:更新阶段找不到 componentInstance 报错。

这里实际上有点阻塞了,因为一般来说,Vue 的源代码经过测试,应该不会出现这种问题的,那是不是我们的问题呢,我们回归到业务代码:

created() {
    this.getFeedsListFromCache();
},
methods: {
    getFeedsListFromCache() {
        viola.requireAPI("cache").getItem(this.cacheKey_feeds, data => {
            this.processData(data.list);
        });
    },
    processData(list = [], opt = {}) {
        if (this.list.length < cacehFeedsLength) {
        }
        this.list = [];
    },
}

我们对业务代码进行了抽象简化,上面是我们的最小问题 Demo,实际上我们就做了这样一件事情:

  • 在 created 执行方法,调用端的接口,再回调函数里面更新某个 data 中声明的数据。

首先,我们可以梳理下对一般 vue 组件的初始化更新,vue 是如何做的:

  • created 时实际上 vnode 已经建立完成,这个时候还没有 mount,但是数据监听已经建立了,这个时候如果改动数据,会把相关 update 函数放在一个名为 flushCallbacks 的函数队列中。
  • 该函数队列会通过默认为 Promise.then 的 microtask 方式来调度,当前阶段的 mount 流程会继续,mount 结束后,会执行 flushCallbacks 队列中的更新操作。

从代码层面上来讲,这几个流程应该是这样的:

 ├── callHook(vm, 'created'); // 执行created 钩子
 ├── proxySetter(val); // 改变数据,调用 proxy
 ├── Watcher.prototype.update; // 调用 Watcher,将 update 操作入栈
 ├── vm.$mount(vm.$options.el); // 执行 mount 流程
 ├── callHook(vm, 'beforeMount');
 ├──  callHook(vm, 'mounted'); // 依次调用 beforeMount 和 mounted
 └── flushCallbacks // 执行 更新

然后我们分析我们这里的流程,首先值得强调的是这个函数 viola.requireAPI("cache").getItem,这个函数是端注入的函数,但我们不能将其当作异步函数来对待,实际上,这是一个同步函数,(至于这个同步函数和 js 中的普通函数,是否有区别,还有待商榷,不过应该是有区别的,因为如果我们不用此函数的话,就不会出现该问题。)

接下来,我们打出详细的调用栈,根据顺序来分析实际的执行流程:

 ├── callHook(vm, 'created'); // 执行created 钩子
 ├── proxySetter(val); // 改变数据,调用 proxy
 ├── Watcher.prototype.update; // 调用 Watcher,将 update 操作入栈
 ├── flushCallbacks // 执行 更新
 ├── vm.$mount(vm.$options.el); // 执行 mount 流程 
 ├── callHook(vm, 'beforeMount');
 └── callHook(vm, 'mounted'); // 依次调用 beforeMount 和 mounted

我们发现,我们的执行流程出现了很大问题:在 mount 阶段未完成的时候就执行了 flushCallbacks,先执行更新操作,这里的顺序错乱导致了后续问题

我们可看下调用 flushCallbacks 的代码:

if (typeof Promise !== 'undefined' && isNative(Promise)) {
  var p = Promise.resolve();
  microTimerFunc = function () {
    p.then(flushCallbacks);
    // in problematic UIWebViews, Promise.then doesn't completely break, but
    // it can get stuck in a weird state where callbacks are pushed into the
    // microtask queue but the queue isn't being flushed, until the browser
    // needs to do some other work, e.g. handle a timer. Therefore we can
    // "force" the microtask queue to be flushed by adding an empty timer.
    if (isIOS) { setTimeout(noop); }
  };
} 

这里 microTimerFuncp.then,被同步执行了,也就是说,这里的微任务优先于当前事件循环的函数执行了(此时由于 mount 流程是同步的,mount 流程的相关函数理应在该事件循环中,优先于微任务执行)。

我们找到了根源,接下来就是分析解决方案和根本原因。

由于我们的问题在于 update 流程执行太快了,所以采用一种方式放慢一点即可:

  • 将 vue 的微任务模式(默认)改成宏任务模式:var useMacroTask = false; => true
  • 在 created 阶段的加一个 setTimeout(0)

不过对于根本原因,实际上本次仍然没有完全分析透彻,还留有如下疑问:

  • viola.requireAPI("cache").getItem 这个函数到底做了什么?其对事件循环有什么影响?
  • 在执行 microTimerFunc 的时候,为什么 p.then 优先于 vm.$mount 执行了?
  • 该错误仅在 iOS 系统出现,iOS 系统是否会在某些情况将微任务的优先级变高?

对于这些疑问,Vue 源代码中也做了一些评论:

// Here we have async deferring wrappers using both microtasks and (macro) tasks.
// In < 2.4 we used microtasks everywhere, but there are some scenarios where
// microtasks have too high a priority and fire in between supposedly
// sequential events (e.g. #4521, #6690) or even between bubbling of the same
// event (#6566). However, using (macro) tasks everywhere also has subtle problems
// when state is changed right before repaint (e.g. #6813, out-in transitions).
// Here we use microtask by default, but expose a way to force (macro) task when
// needed (e.g. in event handlers attached by v-on).

不过,这里始终都没有找到最本质的原因,也许这和 iOS JSCore 的微任务/宏任务的处理机制有关,具体原因,待下次探究。

web应用开发与部署

本文基于笔者在腾讯的项目经验,从真实场景出发分析一个中型 Web 应用从立项到上线稳定运行的平稳解决方案,力求既不太空泛以至于看完了仍然找不到落地的点,也尽量不会特别纠结于个别细节导致没有相关使用经历的同学无法感同身受,而是从宏观到方法论,分析整个流程中我们需要用到的工具、方法与规范,给大家提供一个参考。

本文适合具有一定经验的初中级前端开发者,如果有相关问题,也欢迎与我交流。

目录

  • 项目构建的搭建,关键词:webpackreact/vue clisteamer组件库
  • 代码的规范约束,关键词:typescripteslintprettier
  • 测试与测试部署,关键词:测试部署方案docker
  • 日志查看与脚本错误的监控,关键词:sentryvconsolemlogger
  • 版本发布更新,关键词:发布系统灰度发布
  • 访问量实时监控

起步:项目构建的搭建

使用 webpack 搭建脚手架

目前在一般的项目中,我们都会使用 webpack 作为搭建开发环境的基础,而 react 和 vue 也各自提供了 cli 工具用于开发一个中小型项目,react 提供了 eject 功能让我们可以更加自由的配置 webpack,而 vue 脚手架虽然没有提供类似命令,但是借助 webpack 工具链我们几乎也可以自由定制每一个角落。

不过,这里我的建议是,如果是个人项目或小型项目,我们可以基于 react 或 vue 的脚手架进行更改使用,对于一个具备一定规模的项目团队,建议还是自己维护一套单独的 webpack 构建环境,原因如下:

  • 由于我们一般需要在项目中接入各类司内工具、支持高级API和语法、同时支持 react/vue、构建目录定制化等各类工作,实际上 80% 以上的工作我们都需要在模版之上自行添加,这个时候我们再用脚手架带来的收益已经非常小了,反而还会受制于项目的初始目录结构。

我们在自定义脚手架的 webpack 构建的时候,也需要梳理出一定的目录规范与约束,这样也有利于提高后期脚手架的可维护性和扩展性,一般来说,我们也要对脚手架中的公共部分和项目私有部分进行分离,对于一个具体项目而言,可以不用改动 webpack 的项目公共部分,这样也有利于减少不同项目之间的切换成本,对于我们目前的项目,一般会有如下两个目录:

- project
    - project.js
- config
    - feature
    - plugins
    - rules
    - script.js
    - webpack.base.js 	

对于一个项目,只需更改 project 下的配置。

这里我也推荐一个前同事做的steamer研发体系,在从中也可以找到很多相关参考,最简单的方式,就是直接在steamer-simple 的基础上进行扩展。

定制生成目录

生成目录的格式,这里需要单独讲一下。

一般来说,我们生成目录的格式都是要跟发布系统进行结合的,不过也有的时候,我们做项目的时候还没有明确要接入发布系统,或者尚不知道发布系统的格式要求,但是一般情况下我们应当遵循下面的约定:

  • js/css/img 等资源文件和 html 文件分离,前者发布到 CDN,后者发布到 http 服务器。
  • html 中引入的文件地址,应当是在构建过程中更新的 CDN 地址,而不是相对路径地址。
  • 如果有离线包(offline 能力需要对应的客户端 webview 支持)等,需要单独一个目录。

对于我们目前的项目而言,一般情况下会有三个生成目录:

- cdn
- offline # 需要客户端支持该能力
- webserver

如果一开始我们把所有内容生成到一个目录中了,这给我们后期的改动和维护,都带来很大的隐患。

组件库

组件库这一部分,适合项目开始变得复杂的情况下进行启动,而不建议一开始进行过渡设计,建设组件库能够通过组件复用降低我们的开发成本,同时,组件库也需要专人维护,保持更新。

开发:代码的规范约束

对于 js 文件的代码格式,诸如要不要分号、两个还是四个字符缩进等,一只争议不断,本文也不对此进行讨论,但是对于一个团队的项目集合(而不是单个项目)而言,代码风格的统一,是一个非常有必要而且必须要做的事情。

typescript

关于 typescript 的相关文章实在太多了,这里也不对此进行详细的说明,其对代码的可读性、规范约束、降低报错风险等都有很好的改进,对于越复杂的项目其效果越明显。

另外, typescript 入门教程的作者也在我们团队中,这里我想说,如果现在还没有开始使用 typescript,请开始学习并使用 typescript 吧。

eslint 与 prettier

除了 typescript 以外,在代码格式方面还建议使用 eslint 和 prettier 来进行代码格式的约束,这里虽然 eslint 和 prettier 两者在某些情景下会有些重叠,但是两者的侧重点不同,eslint 侧重于代码质量的检查并且给出提示,在某种层面上,可以看作是 typescript 的补充,而 prettier 在格式化代码方面更具有优势,并且 prettier 在设计之初就考虑了和 eslint 的集成,所以你可以完全放心的在项目中使用两者,而不用担心出现无法解决的冲突。

另外,eslint 社区中已经形成了很多种最佳实践,而我们团队也设计出了自己的一套 eslint 规则,可以按需取用

p.s. 目前 tslint 后续计划不在维护,转向 eslint 增强,因此我们在项目中可以不再使用 tslint。

以上这几种代码风格方面的约束,适合项目之初即开始约束,这样在中后期会有巨大的时间成本的节省和效率的提升。

协作:使用 git

使用 git 进行协作这里其实包括两个点,使用 git 管理项目与自建 gitlab,后者是一个比较基础性的工作,并且实际上难度并不大,我认为每一个公司都可以使用自建的 gitlab 进行版本管理,这个实际上难度并不大,并且可以有效的保护公司的代码财产,如果你所在的公司还没有,那么这也是你的机会。

在具体的使用 git 中,对于git的分支/TAG管理、PR规范、提交文件约束等都应当有一套合理的流程,这里我对几点比较重要的进行说明:

  • 锁定主干与分支开发,我们在日常开发中禁止直接提交主干,而是只能在分支中进行开发,并且通过 MR 的方式提交到主干。
  • git hooks 检查:我们应该通过 git hooks 进行必要的检查,比如自动化测试、eslint 检查、以及一些项目中必要的检查。
  • MR 检查与 Code Review,这里建议在 Merge Request 的时候做两件事情,一件是 Code Review,不过这个在某些特殊情况下不具备条件,尤其是团队人力紧张的时候,另外一个则是 MR 的 HOOK 触发检查,这个一般需要借助一些持续集成工具来完成,可以说是我们代码在合并主干之前的最后一个关卡。

测试:测试与测试部署

测试是代码开发中重要的一个环节,但实际上对于前端开发来说,前端开发工程师一般较少书写测试用例,也并没有专业的测试开发工程师来辅助工作,不过,一般会有配备系统测试工程师在黑盒的情况下进行冒烟测试和功能测试以及整体链路的验收,俗称“点点点”。而这个时候,前端开发要做的就是把程序代码部署到测试服务器上,同时提供一个尽可能真实的场景供测试进行测试。

在笔者经历的项目中,虽然也使用了单元测试、端对端测试,不过这一部分体系并不十分完备,并且可能也不是大多数前端开发者感兴趣的内容,所以这里主要总结如何进行高效的测试部署与发布对接。

一般来说,我们一般会有一台到多台 Linux 测试机,供测试环境部署使用,对于前端项目而言,一般不需要特殊环境,可以进行 webpack 构建以及有 nginx 进行转发即可。

而测试环境的部署,如果是让我们手动登录去部署,显然是不合理的,如果我们纯粹使用 CI 来完成这件事,则对 CI 工具的能力和项目人员素质有一定要求,并且不具备可视化管理能力,容易出错,这里我建议可以维护一个可视化系统来进行测试环境的部署和管理,其整个环节应该是这样的:

本地代码 -> gitlab -> 测试系统部署 -> 对接发布系统

这里的测试系统,实际上是从 gitlab 拉取代码,并且本地执行 build 命令(一般是 npm run build)并把构建结果存储在 nginx 可代理的目录即可,出于系统完备性考虑,一般我们会有多台测试机,这里我建议一般拿其中的一台作为构建机,其他的测试机仅提供 nginx 代理能力即可,我们在一台构建机中进行构建,并且将构建结果通过系统命令发送到其他的测试机。

一台构建机可以服务于所有的项目,这里还可能涉及到 webpack、nodejs 版本的维护,我们需要约束各个测试项目构建处在一个相对独立的环境中,我们也可以使用过 Docker 来进行构建,保证隔离。

构建完成后,一般我们借助 Fiddler、Charles、Whistle 等任意一款代理工具,即可以进行测试。

监控:日志查看与脚本错误的监控

对于前端项目而言,即使我们已经使用了 typescript、eslint 并且也有了一些测试脚本和系统测试工程师进行的功能测试,我们还是免不了会出现 js 脚本错误,特别是 js 代码的运行环境和设备的多样化,很多错误我们并没有发现,但是产品、运营同学却出现了,或者到了线上在用户设备上出现了。

所以,有两个事情我们必须要做:

  1. 日志查看功能(手机端):现在我们写的大多数 TO C 页面都是在手机端进行,查看 console 非常不方便,我们需要一个线上实时查看 console 的工具。
  2. 我们需要脚本错误日志统计系统来进行错误统计管理与具体错误查看。

对于第一个功能,进行细分,我们需要做这样几件事情:

  • 嵌入一个 console 和 网络请求查看器,并且只在特殊情况下才能触发(比如连续点击标题十次、或者使用特定交互手势)
  • 在触发查看器的时候,可以将日志完整地进行上传并分析。
  • 同时可以对该用户进行染色,会自动上传并记录该用户一定时间内后续刷新后操作的全部日志。

不过这里并没有完全实现以上三点的开源库推荐,可以在 vconsole 或者 mlogger 的基础上进行适当扩展,完成相关功能。

对于第二个功能,我们需要一个完整的统计分析与管理的错误系统,这个如果自行开发的话,难度会比较大,这里强烈推荐 sentry,可以非常方便的使用 Docker 部署到服务器端,并且拥有非常强大的日志错误分析与处理能力,通过结合 JavaScript 的 sourcemap ,可以给我们的错误定位带来极大的方便。

总之,日志查看与脚本错误监控,是比较重要但是同时容易被忽视的地方,很多时候,我们的系统在线上使用了几个月了,真正有问题反馈了,我们才会考虑加上这些功能,但这个时候通常已经造成了损失。

发布:版本发布更新

发布系统,一般作为前端最后环节的系统,通常会和测试部署系统打通(或合二为一),一般的发布系统的必要功能如下:

  • 对于前端的发布,每次只发布有改变的文件,无变动的文件则无需发布。
  • 每次发布先发布 js/css/img 等资源文件,生效之后再发布 html 文件。
  • 发布系统保留线上旧版代码,出问题后可以快速一键回滚。

至于一些其他的日志、报表等辅助性功能,则根据需要满足,这里不再赘述。

灰度发布

灰度发布是大型项目在发布时的常见方法,指在发布版本时,初始情况下,只允许小比例(比如1-5%比例的用户使用),若出现问题时,可以快速回滚使用老版本,适用于主链路和访问量较大的页面。

对于前端的灰度,实际上有以下几种方案:

  • 在代码层面进行灰度,即通过 if/else 进行判断,这样无需发布系统关注,也可以自由配置规则、比例与白名单/黑名单。
  • 在入口层面进行灰度,比如 App 内嵌的 H5 则在客户端的对应入口进行回复,这样通常也无需发布系统关注。
  • 通过发布系统,按照比例灰度,比如我们有 10 台 webserver,如果我们先发布 1 台,这样我们的灰度比例为 10%。

访问量实时监控

最后一点,我们还需要一个访问量实时监控系统,我们上述有了错误查看与脚本监控系统,但是对于我们的各个页面的访问量、点击率等指标,通常是产品/运营同学比较关心的,同时访问量的波动情况也是项目健康度的一个表征(访问量突然大幅上涨或下跌,一般都有其特定原因),所以我们需要访问量实时监控系统。

而实际上访问量监控系统也有两种不同形态:

  • 对于每一个上报 key,只进行数量上的统计
  • 对于每一个上报 key,可以携带一些信息,对携带信息进行统计分析。

通常情况下,前者的功能是实时或者低延时的,而后者由于需要一部分统计分析,通常可以接受非实时情况(一般每天出前一天的报表)。

这部分内容,需要较强的后端接口稳定性,通常前端需要和对应岗位的同学共建。

总结

总结下来,我们一个稳定的前端项目,至少涉及到以下环节:

  • 完善的项目脚手架与代码约束规范
  • 内部 gitlab
  • 可视化管理的测试部署系统
  • 实时日志查看工具
  • 脚本错误统计管理系统
  • 发布管理系统
  • 访问量实时监控系统

如果你所在的团队哪个环节还没有或者不完善,那么这也是你的机会。

qlc解决了什么问题

目前遇到的问题

作为一名前端工程师,陆陆续续负责了不少项目,这些项目中,有一些是正在迭代的,其他同事同时在负责的项目,但是也有不少项目,要么就是老旧项目维护的同事已经离职或转岗了的,要么就是新项目从 0 开始的,再加上前端代码积累速度和迭代速度都比较快,其中暴露了不少问题。

我身边的大多数程序员都有一个特点,就是喜欢把具体的东西抽象化,我们通常会抽象出公共的函数或方法、公共的类或HOC,放在一起,集中在项目的某一个文件夹下,叫做 js 文件夹或 lib 文件夹(以下我们用 js 文件夹代表公共函数文件夹 )。

这样做的确带来了很多便利,但同时也有一些隐患:

  • js 文件夹下代码越来越多,而且大多数鹅厂小伙伴的作风是 0 文档 0 注释,这给新接手项目的同学熟悉项目带来了极大的麻烦。
  • 不同项目都有自己的 js 文件夹,在开发一个新项目时,我们通常的做法是:
    • 直接将原有项目的 js 文件夹拷贝到新项目中,这样在新项目中,我们也可以直接使用这些公共函数了。
    • 将原有项目的部分 js 文件拷贝到新项目中,并且随着新项目的开发,增量拷贝。
    • 以上两种做法,本质区别不大,前者会直接给新项目增加很多无用代码(原有项目中所谓的公共函数在新项目中并不一定会用到),而对这两种方式,如果我们要修复一个 bug,修改或升级公共代码中的一个文件,那么我们就要一个一个的,将修复好的文件拷贝到不同的项目中,如果项目多了并且已经交由不同的人维护了,这简直是一个灾难。
  • 由于公共 js 文件夹下内容比较多,并且有的开发同学习惯以 ‘urlUtils.js’、’strUtils.js’ 这种方式来整合一些小的函数集,这样会造成函数重复的隐患(毕竟我们一个文件一行行的去分析目前的公共库已经有了哪些能力是不现实的),我观察过之前自己接手的一个不算复杂的项目(潘多拉),其仅仅是从 url 解析 query 这种功能函数,就有多达 3个(甚至更多),分布在 js 文件夹以及 node_modules 里面,这显然是不同的维护人员由于信息不对称重复引入的。
  • 对于怎么样才能算作“公共”函数,目前是缺乏一个 review 过程的,任何项目开发人员,几乎都可以无限制地在公共 js 文件夹下增加内容,并在之后被携带着拷贝到其他项目中,这其中有些函数,也许并不适合在这里。
阅读更多

入门WebAssem并进行图像卷积处理

WebAssembly 出现有很长时间了,但是由于日常工作并无直接接触,因此一直疏于尝试,最近终于利用一些业余时间简单入门了一下,因此在此总结。

简介

首先我们需要知道 WebAssembly 是一个什么东西,其实际是一个字节码编码方式,比较接近机器码(但是又无法直接执行),这样可以方便地做到跨平台同时省去像 JavaScript 等语言的解释时间,所以是有一定优势的,使用起来其实也比较灵活,凡是可以转化成字节码的,都是可以使用 WebAssembly。

阅读更多

web跨端融合方案浅析

本文会对目前流行的基于 JavaScript 的 web 跨端融合方案进行总结和分析,目标人群为 web 方向的从业者但是对跨端融合方案了解不多的人。

web 跨端融合简介

在 2015 年 React Native 发布之前,web 在移动端 APP 上主要通过 WebView 进行承载,其有许多优点,可以快速迭代发布,不特别受 APP 版本的影响,因此,一些快速发展的业务(包括前期的手机QQ、手机淘宝)大量采用了 WebView 内嵌 H5 页面的形式来推动业务。

但是这种方式缺点也比较明显,主要体现在以下两点:

  • 加载时间较长,包括 WebView 初始化的时间、网络请求的时间。
  • HTML 页面在性能上天然不如 Native 页面,无论怎么进行性能优化。

在 2015 年,Facebook 推出了 React Native,从而打开了 web 跨端融合的大门,后续在此架构基础上又出现了阿里巴巴的 Weex(2016)、腾讯的小程序(小程序实际上更偏 web 一点,和其他几类稍有不同,本文不作介绍)、 Hippy(2018)、Taro(Taro 其实更偏向解释翻译,和其他几类定位不同)等跨端融合解决方案,并且渐渐被用到越来越多的项目中,目前,跨端融合开发已经是一种比较主流的 web 开发模式,在阿里系应用、腾讯的微信、QQ浏览器、手机QQ均已经进行了大规模应用。

阅读更多

Node.js 的 TCP 链接管理

在 Node.js 的微服务中,一般不同的服务模块我们会采用 TCP 进行通信,本文来简单谈一谈如何设计 TCP 服务的基础管理。

阅读更多

多组件单页列表应用的代码组织实践

本文主要对多组件单页面列表应用的代码组织实践进行总结,从而给相关应用的 Web 开发提供参考。

什么是多组件单页面列表应用?

目前,其实多组件单页面列表应用非常常见,也是我们日常生活中使用非常高频的一个类别的应用,最典型的比如新闻信息流产品腾讯新闻、今日头条等这类新闻应用,在这类新闻应用中,往往图片、图文、视频、问答、投票等多种模块混杂排列。再简单一点的话,知乎、豆瓣甚至一些论坛以及一些购物软件,也可以归为此类应用。

阅读更多

使用Nodejs打造多用户实时监控系统

背景概述

首先描述一下笔者遇到的问题,我们可以设定这样一个场景:现在有一个实时监控系统的开发需求,要求同时支持多个用户(这里我们为了简化,暂时不涉及登陆态,假定一个设备即为一个用户),对于不同的用户来讲,他们需要监控的一部分内容是完全相同的,比如设备的 CPU 信息、内存信息等,而另外一部分内容是部分用户重叠的,比如对某一区域的用户来说某些监控信息是相同的,而还有一些信息,则是用户之间完全不同的。

对于每个用户来讲,当其进入页面之后即表明其开始监控,需要持续地进行数据更新,而当其退出界面或者手动点击停止监控,则停止监控。

阅读更多

从源码分析sentry的错误信息收集

raven.js 是 sentry 为 JavaScript 错误上报提供的 JS-SDK,本篇我们基于其源代码对其原理进行分析,本篇文章只分析前端部分,对应的文件目录是https://github.com/getsentry/sentry-javascript/tree/master/packages/raven-js

首先抛出几个问题:

  • raven.js 是如何收集浏览器错误信息的?
  • raven.js 上报的错误信息格式是什么样的?又是如何把这些信息传给后端?支不支持合并上报?
  • 面包屑(breadcrumbs)是什么?raven.js 如何来收集面包屑信息?
  • raven.js 如何和框架配合使用(比如 vue、react)?
阅读更多