linshuai

linshuai 查看完整档案

东莞编辑东莞理工学院  |  计算机科学与技术 编辑undefined  |  前端工程师 编辑 lin-xin.gitee.io/ 编辑
编辑

专注前端,文章首发于博客:https://lin-xin.gitee.io/

个人动态

linshuai 关注了用户 · 2020-11-27

阿里云云栖号 @yunqishequ_5aa899aad5395

阿里云官网内容平台!汇聚阿里云优质内容(入门、文档、案例、最佳实践、直播等)!如需转载或内容类合作,邮件yqgroup@service.aliyun.com 秒级回复!

关注 10649

linshuai 赞了文章 · 2020-11-27

2020双11养猫技术大揭秘

简介: 你养猫了没?

作者 | 淘系-珑晴

image.png
在电商领域,互动是一个重要的用户增长方案,在提升用户黏性、活跃以及拉新上都发挥着重要的作用。今年双11,淘系互动团队推出了“超级星秀猫”,我们不盖楼、不开车,全民参与养猫出道,3只风格各异的萌猫咪一经问世,瞬间俘获了无数消费者的心。通过 EVA 互动体系一整套解决方案,大幅提升研发效率,支撑全民养猫猫在手淘、猫客、支付宝等多个 APP 互通。借助客户端能力及 EVA 互动体系将性能与内存良好控制,让多数用户体验高清稳定的互动,实现 0 故障及秒开,同时星秀猫参与人数再创新高。这篇文章将主要从页面渲染基建、EVA 研发体系和全局稳定性方案 3 个方面,来具体阐述淘系互动前端团队是如何做到双11互动又快又好又稳的。
image.png

页面渲染基建

不知道大家有没有发现,今年的双11养猫互动(以下简称双11互动)页面打开特别快,具体可看下面与去年双11互动主页在 iPhone 11 PRO 机器手机淘宝上的主页加载对比视频。不仅如此,还有个明显的变化是以往互动页面的标配--进度条9027e752cfc24e2bbb54a8ac1c2da384.png没有了。

点击查看视频

互动主页加载对比,左:2019年双11,右:2020年双11,机型:iPhone 11 PRO

见多识广的你也许会问,是不是今年双11互动使用了 Native 版本?是不是今年双11互动使用了缓存方案?是不是今年双11互动也使用了预渲染技术?然而,答案是,都没有,今年双11互动与历年狂欢城一样,仍然是 web 页面,且资源全部走 CDN 无额外缓存。

那么,我们是如何做到如此顺滑的加载体验呢?这就要从 Solution 说起。

Solution 是天马搭建体系(https://zhuanlan.zhihu.com/p/137470317 )的概念,主要解决的问题是将模块+数据组合成页面,简单理解就是负责页面渲染 Layout。

自去年618起,淘系互动团队全部业务都开始迁移到天马搭建体系,Solution 也使用了官方推荐的通用 Solution。但是,通用 Solution 由于其通用性,冗杂了淘宝/天猫 60% 以上业务逻辑(粗略估计),体积大的同时易受其他业务影响导致稳定性风险高;而互动由于其业务特殊性,很多优化甚至稳定性保障方案的实现也需要在 Solution 层面进行定制。基于此,淘系互动团队定制了自己的页面渲染方案,即互动专用 Solution,这也是今年双11互动之所以那么快的核心原因。

得益于天马团队提供的新版 Solution 解决方案,既提供了标准化端渲染机制,又提供了基于插件进行业务定制的能力,还提供了产品化平台。互动专用 Solution 是在上述解决方案的基础上扩展实现,主要做了以下事情:

  • 精简基础依赖/逻辑,去除非必要依赖及非必要逻辑
  • 根据互动场景定制模块加载顺序,提供分批加载能力(当前主要是首屏/非首屏)
  • 提供数据处理能力,通过去除渲染无关字段实现数据自动瘦身
  • 内置基于客户端评分的稳定性保障方案,保障互动页面稳定性,详见下面稳定性方案章节
  • 增加资源及模块加载监控,保障稳定性
  • 集成互动通用能力,包括规范化 CSS/通用的渲染干预能力/常用的移动端调试方案/页面上下线能力等

image.png

EVA 研发体系

今年双11互动,非常多的用户反馈猫猫们太好看了、太萌了、特别可爱,很多人都纷纷来咨询小编是如何开发出来的。
image.png
接下来将重点阐述双11星秀喵加工厂--EVA 研发体系,TA 是淘系互动团队提供的从素材上传到端上渲染的互动研发的一整套解决方案,以引擎、框架、工具、平台为支点,致力于为广大前端带来简单舒适的互动研发体验。

EVA 研发体系,提供互动研发一条龙服务,我们是认真的

EVA Store:素材上传服务

大家看到的星秀喵,并不是3D模型,而是如假包换的2D骨骼动画。它使用 Spine制作,通过网格自由变形和蒙皮技术在视觉上呈现“3D轴”的偏转,应用此类技术的动画软件还有大名鼎鼎的 Live2D。在 Spine 强大动画创作的支持下,双11的星秀喵才有了“3D化”的动画化表现力。一个标准的Spine动画文件包含一张雪碧图、一份骨骼数据以及一份动画数据,那么,我们应该如何上传到 CDN 呢?

EVA 为了解决互动业务中常见且频繁的动画和模型素材,提供了一站式的素材上传服务 EVA Store,包括帧动画、雪碧图、DragonBones、Spine、音频等,这些互动素材的协议标准是由阿里巴巴经济体互动小组统一制定。
image.png

EVA JS:2D 互动引擎

互动引擎是互动研发的基础,一款好的互动引擎能大大降低研发成本,EVA JS 应运而生。EVAJS 是一款轻量级,用于开发互动游戏的前端框架。EVA JS 支持插件体系,所有的互动游戏能力都是由插件提供的。EVA JS 一方面能够让前端互动游戏开发更加专业,另外一方面帮助前端开发者无需深度学习互动游戏技术即可开发互动游戏。TA 的整体设计是以游戏行业最佳实践的 ECS 设计模式为基础,拆分核心功能和组件能力,按需使用。

image.pngEVA JS 引擎

值得一提的是,EVA JS 还提供了无障碍化的支持。以往,游戏区实现无障碍需要手动在图层上添加辅助 DOM 来指定无障碍内容。今年双11,EVA JS 研制了 Canvas 上的无障碍技术,能够在手机淘宝/支付宝客户端上自动识别无障碍对象上的交互事件,降低研发成本的同时,让广大有障碍人士也能全方位体验到撸猫的乐趣,真正做到了互动无障碍体验。

640 (16).gif
双11养猫无障碍体验

EVA JS 计划在2021年6月份前完成开源,了解更多,可参考 淘系前端互动引擎EVAJS架构与生态实现

Rax EVA:Rax 解决方案

素材和引擎都准备好后,就可以上手开始写代码了。市面上大多数的游戏引擎无法和 JSX、Hooks 结合在一起,这样,开发者就需要学习“两套框架”,再加上游戏引擎的学习成本也不低,整体上增加了开发门槛。为解决门槛高、上手难的问题,淘系互动前端团队设计了一套用于开发互动的 Rax 解决方案--Rax EVA,TA 是一个能够在Rax技术体系下,利用 EVA JS 的游戏研发能力,开发动画、游戏类场景的框架。它可以让开发同学用熟悉的 JSX 和 Hooks 语法编写动画、游戏场景的代码。

例如,我们把上一章节上传好的 Spine 动画显示出来(此处为示意版伪代码)

 import {createElement, render} from 'rax';
import DriverUniversal from 'driver-universal';
import {Eva} from '@ali/rax-eva';
import Spine, {useSpineResource} from '@ali/rax-eva-spine';

function App() {
  const catSrc = useSpineResource({
    image: 'https://gw.alicdn.com/tfs/TB1YHC8Vxz1gK0jSZSgXXavwpXa-711-711.png',
    altas: 'https://pages.tmall.com/wow/eva/b8597f298a5d6fe47095d43ef03210d4.atlas',
    ske: 'https://pages.tmall.com/wow/eva/b5fdf74313d5ff2609ab82f6b6fd83e6.json',
  });

  return (
    <Eva
      width={750}
      height={1334}
    >
      <scene>
        <Spine
          resource={catSrc}
          animationName='idle'
          anchorX={0.5}
          anchorY={1}
        />
      </scene>
    </Eva>
  );
}

render(<App />, document.getElementById('root'), {
  driver: DriverUniversal,
});

另外,对于前端来说,DOM 和 CSS 有天然的排版能力,这也是它们的优势。而游戏引擎是通过图形引擎渲染的,并不具备 CSS 那样高级的排版能力。于是在整个游戏互动的项目里,即存在 Canvas 又存在 DOM+CSS,也就是所谓的混合开发。Rax EVA 也为这类混合开发提供了方便快捷的方式,在 EVA 组件内,使用background / hud 这两个原生标签划分了游戏区域Z轴方向的三层布局,在这两个标签内以及 EVA 组件外,任何DOM 标签或其他熟悉的 JSX 都可以照常使用。

image.png
互动项目分层最佳实践

EVA Ware:弹窗规模化生产

除了基础的游戏研发外,EVA 研发体系还提供了一系列的低代码工具或服务,其中表现尤为突出的就是在今年 618 互动中就表现优异的弹窗规模化生成方案(以下称 Super Modal)。

互动研发最大的工作量之一就是弹窗的开发,相比今年 618,今年双11互动弹窗数量更甚。得益于 Super Modal 在弹窗研发上的抽象,所有的弹窗样式都是在平台上简易拖拽复制生成,通过 DSL+Runtime 提供端上稳定的渲染服务。弹窗的样式、文案可以作为页面配置项快速修改,前端在开发弹窗功能时,不用过多关注弹窗的样式问题,专注于弹窗的显示、关闭逻辑即可。除此之外, Super Modal 还在今年 618 功能的基础上,增加了相对定位布局、自定义组件、弹窗队列管理器插件等功能,并提供了简单的项目管理能力,进一步降低了弹窗开发的成本。

image.png
image.png

了解更多,可参考 2020年618淘系技术分享-互动生产力进化之路

全局稳定性方案

细心的同学会发现,今年双11互动主页动效特别多,除了开场视频外,养猫和比拼的主界面上每个区域都在各种动。你一定在好奇,为什么你没有感觉到卡或者出现闪退的情况呢?这一切的背后,除了手机淘宝客户端架构组底层升级外,也有全局稳定性方案在护航。

点击查看双11互动主页动效展示视频

互动场景中存在大量动效、视频,而过高的内存占用可能会引发客户端 crash 影响业务结果。一边是客户端需要更炫酷的玩法去支撑业务发展,一边是一些设备性能较差的用户反馈卡顿,如何让所有设备都能流畅的参与互动呢?比起不顾一切的上动效或是一刀切的砍玩法,显然存在一个更合理的选项--体验分级。淘宝技术质量团队提供了名为 Kite 的获取设备评分的统一降级 SDK,结合互动特性,如下图所示,我们将设备划分为 4 个等级,其中,容灾等级时页面将进入到一个异常兜底页面,用于应对一些低版本或者兼容性较差的机器场景。至此,我们完成了体验分级。

image.png
互动设备分级

接下来,就是针对不同设备等级的机型做不同降级方案,这也是互动的稳定性保驾护航重要的一环。基于客户端架构组提供的稳定性指标,我们需要整体考虑页面的内存、帧率、CPU 3 个指标,而动效、图片尺寸、游戏区画布尺寸、FPS 等配置则直接影响这 3 个指标的结果。然而,众所周知,在实际的开发过程中,对每张图片、每个动效做针对性的降级往往需要各种配置项,且人人参与,操作起来非常繁琐和耗时。因此,淘系互动前端团队在上面体验分级的基础上,通过 1 份静态全局降级规则 + 1 个运行时获取配置的稳定性保障 SDK,设计了一套完整的全局稳定性保障方案。

image.png
全局稳定性保障方案

正是这套稳定性方案,让双11互动在极大程度做到了高清互动、符合了架构组验收标准而且在线 22 天全程 0 故障。TA 让互动稳定性保障更加系统,也已经成为营销互动的标配。

总结展望

今年双11整体节奏从之前的“光棍节”到今年的“双节棍”,在这样变化的背景下,今年双11互动依然做到了参与人数再创新高。未来,我们希望完善 EVA 体系,通过不同的技术方案不断地优化我们的开发方式和生产关系,逐渐让更多的人来开发互动,实现“人人可开发,处处有互动”。
image.png

原文链接
本文为阿里云原创内容,未经允许不得转载。

查看原文

赞 2 收藏 1 评论 1

linshuai 赞了文章 · 2020-11-27

留言讨论 「2020 谷歌开发者大会」拿精美礼品!

开奖啦!请以下获奖的小伙伴私信 思否编辑部 留下「姓名、电话、地址」,我们会尽快发放奖品,邮寄信息收集截止日期12月5日。

最有价值评论奖:@Raymond & @linshuai

image.png

image.png

最佳建议评论奖:@徐九 & @Yujiaao

image.png

image.png

最有趣评论奖:@走在大道满是花香 & @aefwee
image.png
image.png

积极参与奖:Peter谭老师 & @旺仔
image.png
image.png


Google 开发者大会 (Google Developer Summit) 是谷歌面向开发者展示最新产品和平台的年度盛会。2020 Google 开发者大会于 11 月 16 日 至 21 日举行,这是谷歌首次以全线上大会的形式与中国开发者相聚。本次大会以“代码不止”为主题,全面介绍了产品更新以及一系列面向本地开发者的技术支持内容,旨在赋能开发者高效创新、持续不断地创造愉悦的产品体验。

现在 2020 Google 开发者大会已经圆满结束,欢迎优秀的开发者们在本篇文章底部留言区,留言讨论Google 开发者大会中的所见所闻。

点击下方链接了解更多关于 Google 开发者大会信息

https://segmentfault.com/area...

我们将在高质量的留言中选择用户,送出以下礼品(每样奖品两个),以下几种类型评论为参考标准:

  • 最有价值评论 → Google 定制背包
  • 最佳建议评论 → Google 定制水杯
  • 最有趣评论 → Google 定制笔记本
  • 积极参与奖 → Google 周边小礼品(随机赠送)


Google 定制背包

Google 定制水杯


Google 定制笔记本


Google 定制贴纸


Google 定制笔

截止时间:11月28日24:00

活动截止后,我们会于11月30日在本文末尾公布获奖名单

请及时查看,我们会通过 思否编辑部 用私信的方式联系您填写邮寄地址等信息

↓抓紧在下方留言留言讨论吧↓

查看原文

赞 6 收藏 0 评论 14

linshuai 赞了文章 · 2020-11-26

🎯【深入解析】跨端框架的核心技术到底是什么?

image

本文是我在学习多个平台 UI 框架后的一些感触,受精力和技术水平所限,文中定有不足之处,请各位大佬多多指教

如果你觉得我的文章对你有帮助,在收藏的过程中,一定要记得点赞点在看哦,谢谢你,这对我真的很重要🌟!

一、前端三板斧

正式讨论「跨端开发」这个概念前,我们可以先思考一个问题:对大部分前端工作来说,前端主要干些啥?

我个人认为,无论环境怎么变,前端基本上就是做三件事情:

Fetch Data、Manage State、Render Page

  • fetch data(数据获取)
  • manage state(状态管理)
  • render page(页面渲染)

没了。

也许有人觉得我说的太片面,其实我们可以理一理。往近了说,现在知识付费搞的如火如荼,动不动就搞个「XXX 源码解析」,分析一下这些课程的主题和目录,你就会发现基本都是围绕着这三个方向展开讲的;往远了说,我们可以分析一下 Web 前端的发展历程:

  • 1995 年左右,用 HTTP/1.0 拉取数据,用第一版的 JavaScript 管理几个前端状态,用裸露的 HTML 标签展示页面
  • 2005 年左右,用 HTTP/1.1 和 AJAX 拉取数据,用 JavaScript 做做表单画画特效,用 CSS 美化页面
  • 2010 年左右,用 HTTP/1.1 和 AJAX 拉取数据,用 jQuery 操作 DOM 处理前端逻辑,用 CSS 美化页面
  • 2015 年左右,随着 HTML5 标准的推广和浏览器性能的提升,前端开始进入「学不动了」的时代:

    • 在 fetch data 层面,除了 HTTP/1.1 和 AJAX,HTTPS 来了,HTTP/2 来了,WebSocket 也来了
    • 在 manage state 层面,Angular、React 和 Vue 先后出现,从现在看,React 的状态驱动视图的理念直接影响了 Flutter 和 SwiftUI 的设计
    • 在 render page 层面,除了传统的 HTML + CSS,还加入了 CSS3、Canvas 等概念,音视频功能也得到加强
  • 最近几年,网络协议趋于稳定,几年内也不会有啥大的变动;国内 React 和 Vue 的地位基本稳固,一堆前端盯着 GitHub 进度条等版本更新;render 层出了不少幺蛾子,好不容易摆脱了 IE6,又来了各种小程序,同一套业务逻辑写好几遍不经济也不现实,这时候各种跨端方案就整出来了

经过一番分析,这个三板斧理论看上去已经有些道理了,我们顺着这个方向再向底层思考:这三大功能是怎么实现的?

  • fetch data 方向,最后要靠网络协议栈把数据发出去,但是让一个前端直接搞套接字编程是非常不现实的,所以我们需要把网络操作封装为库,让应用层调用
  • render page 方向,最后是把相关图元信息通过各种图形 API(OpenGL/Metal/Vulkan/DirectX)发给 GPU 进行渲染,很多前端的图形学路程最终都止于一个三角形,用这套技术栈去画 UI 也极其不现实,更不要说排版系统这种工程量浩大的工作,所以这些活儿都让相关的渲染引擎做了
  • manage state 方向,你可以用全局变量管理状态,最后的结局一定被同事打爆,现在主流方案都是采用各种框架和 runtime 进行状态管理,而这个 runtime 的宿主环境,往往就是某个语言的虚拟机,同时,fetch data 的起点,也是同一个虚拟机

虚拟机 渲染引擎

经过上面的分析我们可以看出,前端的主要技术核心就两个:虚拟机渲染引擎,这也意味着,如果我们想要搞跨端开发,就必须得统一虚拟机和渲染引擎

二、虚拟机和渲染引擎

1.网页:JS Engine + WebKit

前端三剑客

因为谷歌的 Blink 引擎 fork 自苹果的 WebKit,后文为了描述方便,统一用 WebKit 代替浏览器渲染引擎

网页是成本最低上手最快的跨端方案了。得益于互联网开放式理念,网页天生就是跨端的,无论什么渲染框架,WebView 都是必不可少的核心组件。

开发人员的接入成本也极低,主要技术就是 Web 开发那一套,前端主要头疼的是各个渲染引擎的适配问题性能问题

现在主流的 JS Engine 是苹果的 JavaScriptCore 和谷歌的 V8,主流的渲染引擎是苹果的 Webkit 和谷歌的 Blink。虽然 W3C 的规范就摆在那里,各个浏览器厂商再根据规范实现浏览器,这也是网页跨端的基础。问题在于浏览器内核实现总有细微差距,部分实现不合规范,部分实现本身就有 Bug,这也是前端摆脱不了适配需求的本质原因。

另一个是性能问题。其实 WebKit 本身的渲染速度还是很快的,但是受限于一些浏览器特性,比如说极其复杂极其动态的 CSS 属性,DOM 树和 CSSOM 的合并,主线程必须挂起等待 JS 的执行,这些都会大大降低性能,前端搞性能优化,一般得依据这些浏览器特性进行减枝处理,但是再怎么优化,在页面性能和交互体验上,和 Native 还是有很大的距离。

2.网页 PLUS:JS Engine + WebKit + Native 能力

直接拿个 URL 扔到 WebView 里是最简单的,其实这样也能解决大部分问题,毕竟前端 90% 的工作都是画 UI 写业务逻辑,但是还有 10% 的功能做不到,比如说要和 Native 同步状态,调用一些系统功能。

要实现客户端和网页双向通讯的话,一般都是借助 JSBridge 进行通信,《JSBridge 的原理》这篇文章总结的不错,感兴趣的同学可以看一下。

JSBridge 只是解决了 Native 和 Web 的互相调用问题,如果我想借助 Native 加强 Web 怎么办?这时候就有了一些探索:

  • 预热:提前创建和初始化 WebView,甚至实现 WebView 容器池,减少 WebView 的启动时间
  • 缓存:把常用的 Web 资源预先存在 Native 本地,然后拦截浏览器网络请求重定向到本地,这样就可以加快 Web 的资源加载速度(也叫“离线包”方案);
  • 劫持:比如说 Web 对网络加载的控制力比较弱,部分有能力的厂商会把所有的网络请求都劫持下来交给 Native 去做,这样做可以更灵活的管理 Web 请求
  • 替换:替换一般指替换 Web 的 Img 标签和 Video 标签,这个最常见的地方就是各大新闻类客户端。因为新闻的动态性和实时性,新闻都是由各个编辑/自媒体通过后台编辑下发的,这时候要利用 Web 强大的排版功能去显示文本内容;但是为了加载速度和观看体验,图片和视频都是 Native 组件替换的

经过上面几步,网页的速度基本可以达到秒开的级别,这里面最典型的就是几大新闻客户端,大家可以上手体验一下。

3.小程序:JS Engine + WebKit

各大小程序平台

小程序,国内的特色架构,本质上是微信成为流量黑洞后,想成为流量分发市场管理和分发自己的流量,所以这是个商业味道很重的框架。

小程序在技术上没什么特别的创新点,本质上就是阉割版的网页,所以微信小程序出来后各个流量寡头都推出了自己的小程序,正如有人吐槽的,小程序的实现方式有 9 种,底层实现多样化,各个厂实现还没有统一的标准,最后就是给开发者喂屎,我也没啥好介绍的,就这样吧。

4.React Native:JS Engine + Native RenderPipeLine

React Native 和 Hermes

React 2013 年发布,两年后 React Native 就发布了,前几种跨段方案基本都是基于浏览器技术的,RN 这个跨段方案的创新性在于它保留了 JS Engine,在渲染引擎这条路上,他没有自己造轮子,而是复用了现有的 Native 渲染管线。

这样做的好处在于,保留 JS Engine,可以最大程度的复用 Web 生态,毕竟 GitHub 上轮子最多的语言就是 JavaScript 了;复用 Native RenderPipeLine,好处在于脱离 WebKit 的历史包袱,相对来说渲染管线更短,性能自然而然就上去了。

那么问题来了,RN 是如何做到跨端的?这个其实全部仰仗于 React 的 vdom。

vdom

前端社区上有些文章讨论 vdom,总会从性能和开发便捷性上切入讲解,从纯 Web 前端的角度看,这些的确是 vdom 的特点,但是这不是 vdom 真正火起来的原因。vdom 更大的价值在于,人们从 vdom 身上看到跨端开发的希望,所以在 React 出现后 React Native 紧跟着出现是一件非常自然的事情。为什么这么说?这个就要先溯源一下 UI 开发的范式。

UI 开发主要有两大范式:Immediate Mode GUI(立即模式))Retained Mode GUI(保留模式)

简单来说,IMGUI 每帧都是全量刷新,主要用在实时性很高的领域(游戏 CAD 等);RMGUI 是最广泛的 UI 范式,每个组件都被封装到一个对象里,便于状态管理和复杂的嵌套布局。无论是网页、iOS、Android 还是 Qt 等桌面开发领域,都是基于 RMGUI 的。这两者的具体细节差异,可以看这篇知乎回答和这个 Youtube 视频

我们再回到 React Native 中,既然 iOS Android 的原生渲染管线都是 RMGUI 范式,那么总是有相似点的,比如说 UI 都是树状嵌套布局,都有事件回调等等。这时候 vdom 的作用就出来了:

vdom 作为一个纯对象,可以清晰的提炼出出布局的嵌套结构,而且这个抽象描述是平台无关的,那么我们就可以利用 JS 生成 vdom,然后将 vdom 映射到 Native 的布局结构上,最终让 Native 渲染视图,以达到跨平台开发的目的。

到这里如果你有些编译原理的知识,你就会发现 vdom 和 IR 有些类似,同样都是抽象于平台的中间态,vdom 上接 React 下接 Native RenderPipeLine,IR 上接编译器前端下接编译器后端,我们只要关心前半段的逻辑处理,脏活累活都让后半部分做。

Hermes

2019 年 Facebook 为了优化 React Native 的性能,直接推出了新的 JS Engine——HermesFB 官方博文介绍了很多的优点,我个人认为最大的亮点是加入 AOT,传统的 JS 加工加载流程是这样的:

Babel 语法转换Minify 代码压缩install 下载代码Parse 转为 ASTCompile 编译Execute 执行

Hermes 加入 AOT 后,BabelMinifyParseCompile 这些流程全部都在开发者电脑上完成,直接下发字节码让 Hermes 运行就行。

Bytecode precompilation with Hermes

这样做的好处在于,可以大大缩短 JS 的编译时间,不信的话大家可以用 Chrome 分析几个大型网站,JS 的解析加载时间基本占时都是 50% 以上,部分重型网站可能占时 90%,这对桌面应用来说还好,对于电量和 CPU 都要弱上一线的移动平台来说,这些都是妥妥的性能杀手,Hermes 的加入可以大大改善这一情况。

目前 React Native 0.64 也支持 Hermes 了,如果有做 RN 业务的同学可以玩一玩,看看在 iOS 上的性能提升有多大。

5.Flutter: Dart VM + Flutter RnderPipeLine

Flutter 和 Dart

Flutter 是最近比较火的一个跨端方案,也有不少人认为这是最终的跨端方案,毕竟桌面软件时代,最终胜出跨端方案就是 Qt,他们的共同特点就是自带了一套渲染引擎,可以抹平终端差异。

Flutter 的创造还是很有意思的,这里有个 Eric 的访谈,视频中说 Eric 差不多有十几年的 Web 渲染领域工作经验,有一次在 Chrome 内部他们做了个实验,把一些乱七八糟的 Web 规范去掉后,一些基准测试甚至能快 20 倍,因此 Google 内部开始立项,Flutter 的出现了。至于 Flutter 选择 Dart 的理由,坊间一直传说 Flutter 开发组隔壁就是 Dart 开发组,离得近就好 PY 交易,反正 Dart 也没人用,没啥历史包袱,可以很好的相应 Flutter 的需求。

Flutter 的架构也是比较清晰的:

  • 虚拟机用的 Dart VM,Dart 同时支持 JIT 和 AOT,可以同时保证开发效率和运行效率
  • 渲染引擎先把 Dart 构建的视图数据传递给 Skia,然后 Skia 加工数据交给 OpenGL/Metal 这两个图形 API,最终交给 GPU 渲染,整体上比 WebKit 的渲染流水线清晰不少

从纯粹程度上看,Flutter 是做的最彻底的,虚拟机和渲染引擎都没有用业内的成熟方案,而是自造了一套,好处就是没啥适配压力,坏处就是太新了,业务开发时往往会遇到无轮子可用的尴尬状态,如果谷歌大力推广,国内大厂持续跟进,前景还是很光明的。

6.其它方向的探索:JS Engine + Flutter RnderPipeLine?

社区里有一种声音,认为 Flutter 最大的败笔就是不能用 JavaScript 开发。这时候就会有人想,如果我们把 Web 技术和 Flutter 技术结合起来,用 JS Engine 对接世界上最大最活跃的 JS 社区,用 Flutter 渲染引擎对接高性能渲染体验,国安民乐,岂不美哉?

岂不美哉?

目前来说一些大厂还是做了一些探索,我看了一些分析和项目架构,感觉就是做了个低配版的 React Native,React Native 的现有架构有一个性能瓶颈就是跨语言调用成本比较高,而这些大厂的调用链路多达 4 步:JS -> C++ -> Dart -> C++,更加丧心病狂,目前看无论是上手和推广都是没有直接用 RN or Flutter 方便。

三、各跨端方案的不足之处

跨端方案不可能只有好处的,各个方案的坏处也是很明显的,我下面简单列一下:

  • 网页:性能是个过去不的坎儿,而且 Apple 明确指出不欢迎 WebView 套壳 APP,有拒审危险
  • 网页 PLUS:技术投入很高,基本只能大厂玩转
  • 小程序:对开发者不友好,技术半衰期极短
  • React Native:基本只能画 UI,一旦做深了,只会 JS 根本解决不了问题,Java OC 都得学,对开发者要求比较高
  • Flutter:Android 支持很好,但 iOS 平台的交互割裂感还是很强的,而且和 RN 问题一样,一旦做深了,必须学习客户端开发知识,对开发者要求比较高

总的来说,在牺牲一定用户体验的前提下,跨端方案可以提高开发者的开发效率和公司的运行效率,我个人认为,只要某个方案的 ROI 比较高,其实是还是可以投入到生产的。

四、总结

本文到此就结束了,我把各个跨端技术提炼为为虚拟机和渲染引擎技术,然后以这两个核心技术的角度去拆解各个跨端方案。一旦概念理清,在面对性能调优等技术场景时,就能抓住主要矛盾,更快更好的发现问题,解决问题。


如果你觉得我的文章对你有帮助,在收藏的过程中,一定要记得点赞点在看哦,谢谢你,这对我真的很重要🌟!

最后推荐一波我的个人博客:supercodepower.com, 目前专注前端技术,对图形学也有一些微小研究,欢迎大家来撩~

五、推荐阅读

【答疑解惑】为什么你的 Charles 会抓包失败?

webpack 中那些最易混淆的 5 个知识点

【全网最全】React Native 性能优化指南

【独家】React Native 版本升级指南

查看原文

赞 4 收藏 3 评论 2

linshuai 收藏了文章 · 2020-10-26

1024 程序员必备好物推荐 丨我和我的“格子衫”

前言

今天正好是 10月24日,每年一度的程序员节日。因为1024是2的十次方,二进制计数的基本计量单位之一。针对程序员经常周末加班与工作日熬夜的情况,部分互联网机构倡议每年的10月24日为1024程序员节,在这一天建议程序员拒绝加班。(然而我正在加班中。。。)

既然是节日,那在这我提前给大家拜个早年,祝大家头发多多,身体棒棒,bug少少。

好物推荐

回到主题,推荐程序员必备好物。

保温杯

image

某宝或某多多上,9块9包邮上楼的玻璃保温杯,必须带隔网的。

  • 程序员每天都坐在座位上对着电脑,缺少运动。而这款杯子,容量大约200-300ml,喝几口就喝完了,这时就需要起身走一走,去倒杯水,运动一下。
  • 带隔网呢,就是需要泡点什么了,像我平时就会泡几片西洋参,几朵小菊花,几颗大枸杞,几斤铁观音。
  • 外观方面呢,没什么特别,适合男女老少,四季通用。

对象

image

这里说的对象是真的象,一对象的桌面摆件。

  • 作为程序员,我们一般都是要面向对象编程。面向对象编程(Object Oriented Programming,OOP)是一种计算机编程架构。OOP的一条基本原则是计算机程序由单个能够起到子程序作用的单元或对象组合而成。OOP达到了软件工程的三个主要目标:重用性、灵活性和扩展性。
  • 把对象摆在电脑前,让程序员真正的面向对象编程,bug也可以少一些
  • 这个价格也小几十块,有点贵,超过9块9我就买不起,但我富裕的同事已经在桌面摆上了

image

这里说的书,也是真的书,只不过他的作用已经变了,不再是知识的提供者,而是颈椎的保护者。

  • 每天与我们最亲近的就是显示器,显示器过低的话,会导致我们一直低着头,对颈椎不好。而建议显示器的最佳的角度是目光与显示器平面相互垂直,垂足位于屏幕中心。
  • 而书,特别是不看的书,放在桌面占地方吃灰,还不如用来垫显示器,既不占地方,又能发挥它多余的作用,还能帮助我们的颈椎,何乐而不为。
  • 书的话就不用特地去买了,收集一下同事或自己不看的书就可以。
本文参与了 SegmentFault思否征文「1024 征文活动」,欢迎正在阅读的你也加入。
查看原文

linshuai 发布了文章 · 2020-10-24

1024 程序员必备好物推荐 丨我和我的“格子衫”

前言

今天正好是 10月24日,每年一度的程序员节日。因为1024是2的十次方,二进制计数的基本计量单位之一。针对程序员经常周末加班与工作日熬夜的情况,部分互联网机构倡议每年的10月24日为1024程序员节,在这一天建议程序员拒绝加班。(然而我正在加班中。。。)

既然是节日,那在这我提前给大家拜个早年,祝大家头发多多,身体棒棒,bug少少。

好物推荐

回到主题,推荐程序员必备好物。

保温杯

image

某宝或某多多上,9块9包邮上楼的玻璃保温杯,必须带隔网的。

  • 程序员每天都坐在座位上对着电脑,缺少运动。而这款杯子,容量大约200-300ml,喝几口就喝完了,这时就需要起身走一走,去倒杯水,运动一下。
  • 带隔网呢,就是需要泡点什么了,像我平时就会泡几片西洋参,几朵小菊花,几颗大枸杞,几斤铁观音。
  • 外观方面呢,没什么特别,适合男女老少,四季通用。

对象

image

这里说的对象是真的象,一对象的桌面摆件。

  • 作为程序员,我们一般都是要面向对象编程。面向对象编程(Object Oriented Programming,OOP)是一种计算机编程架构。OOP的一条基本原则是计算机程序由单个能够起到子程序作用的单元或对象组合而成。OOP达到了软件工程的三个主要目标:重用性、灵活性和扩展性。
  • 把对象摆在电脑前,让程序员真正的面向对象编程,bug也可以少一些
  • 这个价格也小几十块,有点贵,超过9块9我就买不起,但我富裕的同事已经在桌面摆上了

image

这里说的书,也是真的书,只不过他的作用已经变了,不再是知识的提供者,而是颈椎的保护者。

  • 每天与我们最亲近的就是显示器,显示器过低的话,会导致我们一直低着头,对颈椎不好。而建议显示器的最佳的角度是目光与显示器平面相互垂直,垂足位于屏幕中心。
  • 而书,特别是不看的书,放在桌面占地方吃灰,还不如用来垫显示器,既不占地方,又能发挥它多余的作用,还能帮助我们的颈椎,何乐而不为。
  • 书的话就不用特地去买了,收集一下同事或自己不看的书就可以。
本文参与了 SegmentFault思否征文「1024 征文活动」,欢迎正在阅读的你也加入。
查看原文

赞 4 收藏 1 评论 0

linshuai 收藏了文章 · 2020-10-16

VS Code插件开发介绍(一)

  • 前言

前段时间做了一个基于命令行的效率工具,可以自动生成组件的模板代码。自己用起来还觉得挺好,但在组内案例几次后大家都不愿意用,究其原因还是命令行工具使用起来门槛有点高,不方便。由于组内已经统一使用VS Code进行开发了,于是决定研究下VS Code的插件开发,让效率工具更方便的用起来。

  • 准备工作

为了降低开发门槛,微软做了一个Yeoman代码生成命令,可以很方便的生成插件开发需要的模板代码,可以通过以下命令安装:

// 安装
npm install -g yo generator-code

// 使用
yo code

运行完以上命令后会出现下面的操作界面,填入需要的信息即可。
clipboard.png

  • 运行示例代码

代码生成后,可以按F5开始调试插件,此时VS Code会新建一个实例并进入插件开发模式,开发中的插件可以在这个新的实例中使用。模版代码会生成一个名为Hello World的命令,按下⇧⌘P调出命令输入窗口,然后输入'Hello World'运行命令。如果找不到命令,重启下VS Code再重新运行。
clipboard.png

  • 修改代码

插件的入口代码在extension.js这个文件中,主要是修改activate函数:

export function activate(context) {

    // Use the console to output diagnostic information (console.log) and errors (console.error)
    // This line of code will only be executed once when your extension is activated
    console.log('Congratulations, your extension "my-first-extension" is now active!');

    // The command has been defined in the package.json file
    // Now provide the implementation of the command with  registerCommand
    // The commandId parameter must match the command field in package.json
    let disposable = vscode.commands.registerCommand('extension.sayHello', () => {
        // The code you place here will be executed every time your command is executed

        // Display a message box to the user
        vscode.window.showInformationMessage('Hello World!');
    });

    context.subscriptions.push(disposable);
}

我插件的功能是用户通过右键点击导航栏,获取选中文件夹的绝对路径,然后提示用户输入新组件的名字,然后自动帮用户生成组件的模板代码。
clipboard.png

clipboard.png

开发的关键点是如何获取文件夹绝对路径和获取用户输入的组件名。尤其是获取路径,找了很久才找到,官网的文档只字未提。先看代码:

function activate(context) {
    console.log('Congratulations, your extension "react-template" is now active!');

    // 注册一个名为createFunctionalComponent的命令
    const fc = vscode.commands.registerCommand('extension.createFunctionalComponent', function (param) {
        // 文件夹绝对路径
        const folderPath = param.fsPath;

        const options = {
            prompt: "请输入组件名: ",
            placeHolder: "组件名"
        }
        
        // 调出系统输入框获取组件名
        vscode.window.showInputBox(options).then(value => {
            if (!value) return;

            const componentName = value;
            const fullPath = `${folderPath}/${componentName}`;

            // 生成模板代码,不是本文的重点,先忽略
            generateComponent(componentName, fullPath, ComponentType.FUNCTIONAL_COMP);
        });
    });
    
    context.subscriptions.push(pc);
}

代码很简单,就不做讲解了。为了显示Create Functional Component这个菜单项,我们需要修改package.json文件的contributes字段。activationEvents字段也要相应的改下。同时为了给插件配一个图标,要加一个icon字段:

    "icon": "images/icon.png",
    "activationEvents": [
        "onCommand:extension.createFunctionalComponent"
    ],
    "contributes": {
        "commands": [
            {
                "command": "extension.createFunctionalComponent",
                "title": "Create Functional Component"
            }
        ],
        "menus": {
            "explorer/context": [
                {
                    "command": "extension.createFunctionalComponent",
                    "group": "1_modification"
                }
            ]
        }
    },

详细的contributes配置可以看这里。配置好menus之后,registerCommand的第二个函数参数就能取到文件或文件夹的绝对路径了。

  • 发布插件

插件开发完后就可以发布了,需要安装vsce

npm install -g vsce

安装完后,需要去Azure DevOps注册并生成一个access token。详细的流程可以看这里。生成toke的时候有两个地方需要注意下:
clipboard.png

token生成后就可以登录和发布了。如果有任何的修改,要注意修改package.json里面版本号才能发布成功。发布成功后,很快就能在VS Code的插件市场搜到了。
clipboard.png

  • 总结

本文介绍了VS Code插件开发的基本流程,实现了一个简单的插件。本文只涉及到VS Code插件系统的冰山一角,更多的高级功能以后接触到的时候再作介绍。如果想再作进一步的了解,可以从这里开始。更多的 VS Code 开发技巧,可以看这个系列的第二篇:VS Code插件开发介绍(二)

查看原文

linshuai 提出了问题 · 2020-07-17

webview里如何支持PWA

在手机浏览器中,断网之后可以访问到PWA开发的页面,也就是离线缓存生效的。但是在同一手机的webview中,断网之后访问不了缓存,需要怎么样才能支持?

关注 2 回答 1

linshuai 发布了文章 · 2020-05-27

Vue3 插件开发详解尝鲜版

前言

vue3.0-beta 版本已经发布了一段时间了,正式版本据说在年中发布(直播的时候说的是年中还是年终,网上传闻说是6月份)。嘴上说着学不动,身体却很诚实地创建一个vue3的项目,兴致勃勃地引入 vue2 插件的时候,眉头一皱,发现事情并没有那么简单。

浏览器无情地抛出了一个错误:

Uncaught TypeError: Cannot set property '$toast' of undefined

不是说兼容vue2的写法吗,插件不兼容,这是闹哪样?发下牢骚之后还是得解决问题。研究插件的代码,是这么实现的

var Toast = {};
Toast.install = function (Vue, options) {
    Vue.prototype.$toast = 'Hello World';
}
module.exports = Toast;

vue2 插件的时候会暴露一个 install 方法,并通过全局方法 Vue.use() 使用插件。install 方法的第一个参数是 Vue 构造器,插件的方法就添加在 Vue 构造器的原型对象上。通过 new Vue() 实例化后,在组件中就能调用 this.$toast 使用组件了。(具体实现可以参考我之前的一篇文章:Vue.js 插件开发详解,下面也是基于这个插件demo对比修改)。

而 vue3 的 install 方法不是传入 Vue 构造器,没有原型对象,Vue.prototype 是 undefined,所以报错了。vue3 采用依赖注入的方式,在插件内部利用 provide 和 inject 并暴露出一个组合函数,在组件中使用。

插件实现

基本框架

下面先实现一个插件的基本框架。

import { provide, inject } from 'vue';
const ToastSymbol = Symbol();     // 用Symbol创建一个唯一标识,多个插件之间不会冲突
const _toast = () => {}        // 插件的主体方法
export function provideToast(config) {    // 对外暴露的方法,将 _toast 方法提供给后代组件
    provide(ToastSymbol, _toast);
}

export function useToast() {    // 后代组件可以从该方法中拿到 toast 方法
    const toast = inject(ToastSymbol);
    if (!toast) {
        throw new Error('error');
    }
    return toast;
}

组件使用

在 provideToast 方法中,提供了 _toast 方法,那在根组件中 调用 provideToast 方法,传入插件参数,子组件中就能使用 toast 功能了。

// app.vue 根组件
import { provideToast } from 'vue2-toast';
export default {
    setup() {
        provideToast({
            width: '200px',        // 配置toast宽度
            duration: 2000        // 配置toast持续显示时长
        });
    }
};

在 useToast 方法中,返回了 toast 方法,那在所有的后代组件中调用 useToast 方法,就能拿到 toast 方法了。

// child.vue 子组件
import { useToast } from 'vue2-toast';
export default {
    setup() {
        const Toast = useToast(); // 所有的子组件都要从useToast拿到toast方法
        Toast('Hello World');
    }
};

数据响应

想要控制 toast DOM 元素的显示隐藏,以及提示文本的变化,需要创建响应式的数据,在 vue3 中可以通过 reactive 方法创建一个响应式对象。

const state = reactive({
    show: true,        // DOM元素是否显示
    text: ''    // 提示文本
});

挂载DOM

在页面中显示一个 toast,那就免不了要创建一个 DOM 并挂载到页面中。在 vue2 中是通过Vue.extend 创建一个子类,将子类生成的实例挂载到某个元素中,而 vue3 中通过 createApp 方法来实现的。

const _toast = text => {
    state.show = true;
    state.text = text;
    toastVM = createApp({
        setup() {
            return () =>
                h('div', {
                    style: { display: state.show ? 'block' : 'none' }    // display 设置显示隐藏
                },
                state.text
            );
        }
    });
    toastWrapper = document.createElement('div');
    toastWrapper.id = 'toast';
    document.body.appendChild(toastWrapper);  // 给页面添加一个节点用来挂载toast元素
    toastVM.mount('#toast');
};

完整示例

上面的每一步是这个插件的关键内容,接下来就是完善下细节,比如用户配置插件的全局参数,设置 toast 的样式,持续时间,显示位置等,这里就不一一细讲了,完整示例如下:

import { provide, inject, reactive, createApp, h } from 'vue';
const ToastSymbol = Symbol();

const globalConfig = {
    type: 'bottom', // toast位置
    duration: 2500, // 持续时长
    wordWrap: false, // 是否自动换行
    width: 'auto' // 宽度
};

const state = reactive({
    show: false, // toast元素是否显示
    text: ''
});

let [toastTimer, toastVM, toastWrapper] = [null, null, null];

const _toast = text => {
    state.show = true;
    state.text = text;
    if (!toastVM) {
        // 如果toast实例存在则不重新创建
        toastVM = createApp({
            setup() {
                return () =>
                    h(
                        'div',
                        {
                            // 这里是根据配置显示一样不同的样式
                            class: [
                                'lx-toast',
                                `lx-toast-${globalConfig.type}`,
                                globalConfig.wordWrap ? 'lx-word-wrap' : ''
                            ],
                            style: {
                                display: state.show ? 'block' : 'none',
                                width: globalConfig.width
                            }
                        },
                        state.text
                    );
            }
        });
    }

    if (!toastWrapper) {
        // 如果该节点以经存在则不重新创建
        toastWrapper = document.createElement('div');
        toastWrapper.id = 'lx-toast';
        document.body.appendChild(toastWrapper);
        toastVM.mount('#lx-toast');
    }
    if (toastTimer) clearTimeout(toastTimer);
    // 定时器,持续时长之后隐藏
    toastTimer = setTimeout(() => {
        state.show = false;
        clearTimeout(toastTimer);
    }, globalConfig.duration);
};

export function provideToast(config = {}) {
    for (const key in config) {
        globalConfig[key] = config[key];
    }
    provide(ToastSymbol, _toast);
}

export function useToast() {
    const toast = inject(ToastSymbol);
    if (!toast) {
        throw new Error('error');
    }
    return toast;
}

总结

与 vue2 的插件比较大不同点在于,需要用到 toast 的每个组件中,都需要引入 useToast 方法,看起来会有点麻烦。不过其实这也是 vue 组合式 API 的目的,让代码更可读、更容易被理解,清楚的知道这个方法就是来自这里。正如标题所示,这是尝鲜版,vue3 正式版还未发布,未来会不会有更多的插件形式,咋也不敢问,至少这种还是比较稳的。

查看原文

赞 9 收藏 4 评论 4

linshuai 分享了头条 · 2020-03-13

前端常见的加密算法介绍

赞 0 收藏 32 评论 0

linshuai 发布了文章 · 2020-03-13

前端常见的加密算法介绍

一、前言

在信息安全越来越受重视的今天,前端的各种加密也变得更加重要。通常跟服务器的交互中,为保障数据传输的安全性,避免被人抓包篡改数据,除了 https 的应用,还需要对传输数据进行加解密。

目前常见的加密算法可以分成三类

  • 对称加密算法:AES、...
  • 非对称加密算法:RSA、...
  • Hash 算法:MD5、...

二、对称加密算法

对称加密(也叫私钥加密)指加密和解密使用相同密钥的加密算法。它要求发送方和接收方在安全通信之前,商定一个密钥。对称算法的安全性依赖于密钥,泄漏密钥就意味着任何人都可以对他们发送或接收的消息解密,所以密钥的保密性对通信的安全性至关重要。

特点

  • 优点:算法公开、计算量小、加密速度快、加密效率高。
  • 缺点:在数据传送前,发送方和接收方必须商定好密钥,然后双方保存好密钥。如果一方的密钥被泄露,那么加密信息也就不安全了
  • 使用场景:本地数据加密、https 通信、网络传输等

AES

AES:高级加密标准(Advanced Encryption Standard)为最常见的对称加密算法(微信小程序加密传输就是用这个加密算法的)。

image

密钥:用来加密明文的密码。密钥为接收方与发送方协商产生,但不可以直接在网络上传输,否则会导致密钥泄漏,通常是通过非对称加密算法加密密钥,然后再通过网络传输给对方,或者直接面对面商量密钥。密钥是绝对不可以泄漏的,否则会被攻击者还原密文,窃取数据。

在项目中需要用到 AES 加密时,可以使用开源的 js 库:crypto-js

var CryptoJS = require('crypto-js');

var data = { id: 1, text: 'Hello World' };

// 加密生成密文
var ciphertext = CryptoJS.AES.encrypt(JSON.stringify(data), 'secret_key_123').toString();

// 解密得到明文
var bytes = CryptoJS.AES.decrypt(ciphertext, 'secret_key_123');
var decryptedData = JSON.parse(bytes.toString(CryptoJS.enc.Utf8));

三、非对称加密算法

非对称加密算法需要两个密钥:公开密钥(publickey:简称公钥)和私有密钥(privatekey:简称私钥)。公钥与私钥是一对,如果用公钥对数据进行加密,只有用对应的私钥才能解密。因为加密和解密使用的是两个不同的密钥,所以这种算法叫作非对称加密算法。

特点

  • 优点:非对称加密与对称加密相比其安全性更好
  • 缺点:加密和解密花费时间长、速度慢,只适合对少量数据进行加密。
  • 使用场景:https 会话前期、CA 数字证书、信息加密、登录认证等

RSA

RSA 加密算法是非对称加密算法最常见的一种。RSA 是 1977 年由 Ron Rivest、Adi Shamir 和 Leonard Adleman 一起提出的。RSA 就是他们三人姓氏开头字母拼在一起组成的。

image

在项目中需要用到 RSA 加密时,可以使用开源的 js 库:jsencrypt

// 使用公钥加密
var publicKey = 'public_key_123';
var encrypt = new JSEncrypt();
encrypt.setPublicKey(publicKey);
var encrypted = encrypt.encrypt('Hello World');

// 使用私钥解密
var privateKey = 'private_key_123';
var decrypt = new JSEncrypt();
decrypt.setPrivateKey(privateKey);
var uncrypted = decrypt.decrypt(encrypted);

四、Hash 算法

Hash,一般翻译做“散列”,也有直接音译为“哈希”的,就是把任意长度的输入(又叫做预映射, pre-image),通过散列算法,变换成固定长度的输出,该输出就是散列值。这种转换是一种压缩映射,也就是,散列值的空间通常远小于输入的空间,不同的输入可能会散列成相同的输出,而不可能从散列值来唯一的确定输入值。

简单的说就是一种将任意长度的消息压缩到某一固定长度的消息摘要的函数。

特点

  • 优点:不可逆、易计算、特征化
  • 缺点:可能存在散列冲突
  • 使用场景:文件或字符串一致性校验、数字签名、鉴权协议

MD5

MD5 是比较常见的 Hash 算法,对于 MD5 而言,有两个特性是很重要的,第一:明文数据经过散列以后的值是定长的;第二:是任意一段明文数据,经过散列以后,其结果必须永远是不变的。前者的意思是可能存在有两段明文散列以后得到相同的结果,后者的意思是如果我们散列特定的数据,得到的结果一定是相同的。

比如在登录时将密码进行 md5 加密再传输给服务器,服务器中的密码也是用 md5 加密后存储的,那么只要验证加密后的密文是否一致则可。

在项目中需要用到 MD5 加密时,可以使用开源的 js 库:JavaScript-MD5

var hash = md5('Hello World');
// b10a8db164e0754105b7a99be72e3fe5

五、Base64 编码

Base64 编码只是一种编码格式并不是加密算法,它可用于在 HTTP 环境下传递较长的标识信息。

特点

  • 可以将任意的二进制数据进行 Base64 编码
  • 数据加密之后,数据量会变大,变大 1/3 左右
  • 编码后有个非常显著的特点,末尾有个=号
  • 可进行反向解码
  • Base64 编码具有不可读性

现代浏览器都提供了 Base64 编码、解码方法,btoa() 和 atob()

var enc = window.btoa('Hello World');
// SGVsbG8gV29ybGQ=

var str = window.atob(enc);
// Hello World

六、总结

在业务 http 请求中,AES 的密钥在前端随机生成,从服务器获取 RSA 的公钥,对 AES 的密钥进行非对称加密,把加密后的密钥在请求头中传给服务器,用 AES 对 body 进行加密。服务器收到请求头中的加密后的密钥,用 RSA 的密钥进行解密,得到明文的 AES 密钥,即可对 body 进行解密。md5 有校验字符串一致性的特性,为避免请求被拦截后篡改 body,可在发请求时,将 body 字符串进行一个 md5 加密后在请求头传输,服务器收到请求后,解密 body 后再 md5 与请求头的进行校验,可验证是否请求被篡改。

查看原文

赞 39 收藏 32 评论 3

linshuai 提出了问题 · 2019-11-27

各位大佬有没有基于node的开源论坛推荐啊

想自己搭一个公司内部论坛,有没有好推荐的,已知nodeclub和nodebb,还有没有其他可推荐的啊

关注 3 回答 2

linshuai 发布了文章 · 2019-11-06

Rollup.js: 开源JS库的打包利器

前言

Rollup 是一个 JavaScript 模块打包器,说到模块打包器,自然就会想到 webpack。webpack 是一个现代 JavaScript 应用程序的静态模块打包器,那么在 webpack 已经成为前端构建主流的今天,为什么还要用 Rollup 呢?

Rollup 中文文档 中介绍到,它可以将小块代码编译成大块复杂的代码,例如 library 或应用程序。可以看到它的应用场景之一,就是打包 JS 库。自己写个 JS 库,在我们开发工作中和开源项目中还是比较常见的。可谓是生命不息,造轮子不止。如果还没写过,那就赶紧来提升下自己的技(逼)术(格)吧。

对比 webpack

用过 webpack 的都知道,它可以将所有的静态资源,导入到应用程序中,也是因为它强大的功能,所以打包 bundle 文件时,会产生很多冗余的代码,在大型的应用中,这点冗余代码就会显得微不足道,但是在一个小小的库中,就会显得比较明显了。比如这么一个类:

class People{
    constructor(){
        this.name  = 'linxin'
    }
    getName(){ return this.name; }
}
export default People;

经过 webpack 打包之后,变成下面这样(案例移除了具体内容),多出了很多方法,这显然不是我们想要的。

/******/ (function(modules) { // webpackBootstrap
/******/     var installedModules = {};
/******/     function __webpack_require__(moduleId) { **** }
/******/     __webpack_require__.m = modules;
/******/     __webpack_require__.c = installedModules;
/******/     __webpack_require__.d = function(exports, name, getter) { *** };
/******/     __webpack_require__.r = function(exports) { *** };
/******/     __webpack_require__.t = function(value, mode) { *** };
/******/     __webpack_require__.n = function(module) { *** };
/******/     __webpack_require__.o = function(object, property) { *** };
/******/     __webpack_require__.p = "";
/******/     return __webpack_require__(__webpack_require__.s = 0);
/******/ })
/******/ ([ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__);
class People{
    constructor(){
        this.name  = 'linxin'
    }
    getName(){
        return this.name;
    }
}
/* harmony default export */ __webpack_exports__["default"] = (People); }) ]);

而 Rollup 打包之后的代码跟源码基本一致,作为一个 JS 库,我们还是希望简洁一点,代码量少点。毕竟实现相同的功能,谁都不想去引入一个更繁重的库吧。

特性

ES模块

Rollup 使用 ES6 的模块标准,而不像 CommonJS 和 AMD,虽然也叫模块化,其实只是一种临时的解决方案。Rollup 的模块可以使我们开发时可以独立的开发每个小模块,代码小而简单,更加方便测试每个小模块,在构建时才打包成一个完成的 JS 库。

Tree-shaking

tree shaking 指的是移除 JavaScript 上下文中的未引用代码,它依赖于 ES2015 模块系统中的静态结构特性,例如 import 和 export。静态结构的 import 就好像是变量引用一样,不需要执行代码,在编译时就可以确定它是否有引用到,如果没引用,就不把该段代码打包进来。比如用到了一个第三方库的一个功能,但我肯定不要把它完整的库都打包进来,我只需要打包用到的代码即可,这时候 tree shaking 就可以发挥出它的作用了。

应用

开发一个 JS 库,我需要 Rollup 能为我提供些常用的功能:

  • 支持 ES6 转 ES5
  • 代码压缩
  • ESLint
  • 支持 Typescript

基本配置

Rollup 使用一个 rollup.config.js 文件进行配置。

// rollup.config.js
export default {
    input: 'src/index.js',
    output: {
        file: 'dist/bundle.js',
        format: 'umd'
    }
};

配置跟其他工具基本一致,从入口文件 index.js 打包后输出文件 bundle.js。format 是生成包的格式,可选有 amd,cjs,es,iife,umd,umd 是通用模块定义,打包后可以通过 <script> 标签引入,也可以通过 import 等方式引入,作为一个 JS 库要适用各个场景,应选择 umd 。

babel

Rollup 通过插件在打包的关键过程中更改行为,babel的插件就是 rollup-plugin-babel,需要先安装相关依赖

npm i rollup-plugin-babel@latest @babel/core @babel/preset-env -D

新建 .babelrc 文件,配置 babel

{
    "presets": ["@babel/preset-env"]
}

代码压缩

npm i rollup-plugin-uglify -D

因为 rollup-plugin-uglify 无法压缩 ES6 的语法,所以必须先用 babel 转。如果想直接压缩 ES6 的语法,可换成 rollup-plugin-terser

ESLint

开发一个 JS 库,不能乱七八糟,随心所欲的写代码,必须规范起来,当别人为你的开源库做贡献时,也必须遵循你的开发规范。安装 ESLint 插件

npm i rollup-plugin-eslint -D

然后初始化生成一个 ESLint 配置文件 ./node_modules/.bin/eslint --init

那么最终的 rollup.config.js 配置文件如下:

import babel from 'rollup-plugin-babel';
import { uglify } from 'rollup-plugin-uglify';
import { eslint } from "rollup-plugin-eslint";
export default {
    input: './index.js',
    output: {
        file: 'dist/bundle.js',
        name: 'People',
        format: 'umd'
    },
    plugins: [
        eslint({
            fix: true,
              exclude: 'node_modules/**'
        }),
        babel({
          exclude: 'node_modules/**'
        }),
        uglify()
    ]
};

TypeScript

如果使用 TypeScript 进行开发,则需要安装 rollup-plugin-typescript2 插件和相关依赖

npm i rollup-plugin-typescript2 typescript -D

然后初始化生成一个 tsconfig.js 配置文件 tsc --init,那么使用 TypeScript 的打包文件如下:

import typescript from 'rollup-plugin-typescript2';

export default {
    input: './index.ts',
    output: {
        file: 'dist/bundle.js',
        name: 'People',
        format: 'umd'
    },
    plugins: [
        typescript()
    ]
}

插件

除了以上用的这些插件之外,还有一些可能根据项目需求也有需要

  • rollup-plugin-commonjs:让 Rollup 识别 commonjs 类型的包,默认只支持导入ES6
  • rollup-plugin-node-resolve:让 Rollup 能识别 node_modules 中的包,引入第三方库,默认识别不了的
  • rollup-plugin-json:支持 json 文件
  • rollup-plugin-replace:支持字符串替换
  • rollup-plugin-sourcemaps:能生成 sourcemaps 文件

总结

以上只是介绍了 Rollup 的一些基本用法,更多的请参考官方文档。Rollup 已被许多主流的 JavaScript 库使用,包括 vue 和 react。它也可用于构建绝大多数应用程序,但是代码拆分和运行时态的动态导入这类高级功能,它还不能支持,如果需用这些功能,那就可以使用 webpack。案例可查看:sChart.js

查看原文

赞 10 收藏 5 评论 0

linshuai 回答了问题 · 2019-09-05

泰语在html页面中如何断字换行?

找到解决方法了吗

关注 1 回答 2

linshuai 评论了文章 · 2019-05-11

旗帜鲜明地抵制 CSDN 下载(盗版)站!

SegmentFault 上线付费课程以来,对于内容质量一直严格把关,讲师认真备课,课后为学员答疑,广受好评。然而近期有多位 SegmentFault 讲师反馈在 CSDN 下载频道出现了大量他的盗版课程。

clipboard.png

不查不知道,一查我们发现——我们讲师辛辛苦苦花了上百个小时录制的付费课程,在 CSDN 下载频道竟有满满一屏幕的盗版存在(相关证据我们已经找律师团队取证),同时根据他们的关键词推荐我们发现在其博客频道也有大量的盗版内容,防不胜防,让人不吐不快。

clipboard.png

昨天我在朋友圈公开这个事情后,收到了大量业内同行的反馈,我们发现不仅仅是 SegmentFault,几乎我认识的所有同行以及我们熟悉的讲师的付费内容(包括不限于课程、图书、专栏)都有被 CSDN 下载频道侵权的经历,昨天,我们用了同行的一些关键词在 CSDN 下载频道进行检索,同样发现存在有大量的盗版内容存在。

clipboard.png

同时我们也收到一些用户的反馈在 CSDN 下载频道有大量 IT 相关技术书籍的扫描版文件,毫无疑问都是盗版资源。上传时期从 2016 年(或者更早)开始到至今一直存在。

我们通过关键词在 Google 检索后,发现最早控诉 CSDN 下载频道的内容《CSDN 首页鼓励盗版图书下载》是在 2005 年发布,在知乎等社区也有大量的讨论,如《如何看待 CSDN 利用用户上传的盗版资料卖积分赚钱?》《为什么 CSDN 能做到让用户花钱买积分下载自己网站的盗版资源?》……可见已在业内引起公愤。

clipboard.png

CSDN 做为中国最早的技术社区之一,我们认可其对开发者之间线上交流做出的贡献,但是其下载频道的存在大大助长了大量盗版侵权内容的产生。并非个例,长期存在,越发泛滥,从未被解决——这代表其产品存在根本上的机制问题。

CSDN 官方对此不知情吗?

不过是睁一只眼闭一只眼罢了——CSDN 的下载频道占了总社区超过 30% 的流量。靠着盗版和侵权他人优质内容,获取平台流量,再依靠平台流量进行广告和其他形式的变现。这不是非法牟利是什么?

我们不禁质疑号称中国最大技术社区的 CSDN 究竟拥有怎样的价值观?

纵容盗版,非法牟利;

无视用户隐私——曾经明文存储用户名密码导致用户数据泄露;

甚至欺骗客户——夸大网站流量,广告数据造假。还记得去年程序员广告代码刷量的乌龙事件吗?“博客详情页PC增加广告系统刷量代码”这句话写在了代码注释里面上线了。原来客户的广告数据都是刷出来的?(心疼投放广告的客户)

其实技术圈子非常小,很多同行可能碍于面子或者各种各样的原因,没有公开地去声讨 CSDN,一些声讨可能也并没有解决问题,这更助长了其纵容盗版的气焰。 SegmentFault 作为技术社区的一员,我们深知社区发展的不易,我们有责任帮助我们的讲师维护其付费内容的版权不被侵犯。

我们已经聘请专业的律师团队取证,同时我们也呼吁被 CSDN 下载站侵犯过内容版权的同行,讲师,书籍作者,广大开发者一起发声,在评论区留下你的声音和被侵权经历,通过曝光侵权甚至违法行为,共同净化行业环境。

一起举报

到中央网信办(www.12377.cn)

国家新闻出版广电总局官网(www.sapprft.gov.cn)

举报 CSDN下载站的盗版侵权行为。

图片描述

SegmentFault CEO:高阳Sunny 2019年5月9日凌晨于北京

查看原文

linshuai 赞了文章 · 2019-05-11

旗帜鲜明地抵制 CSDN 下载(盗版)站!

SegmentFault 上线付费课程以来,对于内容质量一直严格把关,讲师认真备课,课后为学员答疑,广受好评。然而近期有多位 SegmentFault 讲师反馈在 CSDN 下载频道出现了大量他的盗版课程。

clipboard.png

不查不知道,一查我们发现——我们讲师辛辛苦苦花了上百个小时录制的付费课程,在 CSDN 下载频道竟有满满一屏幕的盗版存在(相关证据我们已经找律师团队取证),同时根据他们的关键词推荐我们发现在其博客频道也有大量的盗版内容,防不胜防,让人不吐不快。

clipboard.png

昨天我在朋友圈公开这个事情后,收到了大量业内同行的反馈,我们发现不仅仅是 SegmentFault,几乎我认识的所有同行以及我们熟悉的讲师的付费内容(包括不限于课程、图书、专栏)都有被 CSDN 下载频道侵权的经历,昨天,我们用了同行的一些关键词在 CSDN 下载频道进行检索,同样发现存在有大量的盗版内容存在。

clipboard.png

同时我们也收到一些用户的反馈在 CSDN 下载频道有大量 IT 相关技术书籍的扫描版文件,毫无疑问都是盗版资源。上传时期从 2016 年(或者更早)开始到至今一直存在。

我们通过关键词在 Google 检索后,发现最早控诉 CSDN 下载频道的内容《CSDN 首页鼓励盗版图书下载》是在 2005 年发布,在知乎等社区也有大量的讨论,如《如何看待 CSDN 利用用户上传的盗版资料卖积分赚钱?》《为什么 CSDN 能做到让用户花钱买积分下载自己网站的盗版资源?》……可见已在业内引起公愤。

clipboard.png

CSDN 做为中国最早的技术社区之一,我们认可其对开发者之间线上交流做出的贡献,但是其下载频道的存在大大助长了大量盗版侵权内容的产生。并非个例,长期存在,越发泛滥,从未被解决——这代表其产品存在根本上的机制问题。

CSDN 官方对此不知情吗?

不过是睁一只眼闭一只眼罢了——CSDN 的下载频道占了总社区超过 30% 的流量。靠着盗版和侵权他人优质内容,获取平台流量,再依靠平台流量进行广告和其他形式的变现。这不是非法牟利是什么?

我们不禁质疑号称中国最大技术社区的 CSDN 究竟拥有怎样的价值观?

纵容盗版,非法牟利;

无视用户隐私——曾经明文存储用户名密码导致用户数据泄露;

甚至欺骗客户——夸大网站流量,广告数据造假。还记得去年程序员广告代码刷量的乌龙事件吗?“博客详情页PC增加广告系统刷量代码”这句话写在了代码注释里面上线了。原来客户的广告数据都是刷出来的?(心疼投放广告的客户)

其实技术圈子非常小,很多同行可能碍于面子或者各种各样的原因,没有公开地去声讨 CSDN,一些声讨可能也并没有解决问题,这更助长了其纵容盗版的气焰。 SegmentFault 作为技术社区的一员,我们深知社区发展的不易,我们有责任帮助我们的讲师维护其付费内容的版权不被侵犯。

我们已经聘请专业的律师团队取证,同时我们也呼吁被 CSDN 下载站侵犯过内容版权的同行,讲师,书籍作者,广大开发者一起发声,在评论区留下你的声音和被侵权经历,通过曝光侵权甚至违法行为,共同净化行业环境。

一起举报

到中央网信办(www.12377.cn)

国家新闻出版广电总局官网(www.sapprft.gov.cn)

举报 CSDN下载站的盗版侵权行为。

图片描述

SegmentFault CEO:高阳Sunny 2019年5月9日凌晨于北京

查看原文

赞 144 收藏 1 评论 58

linshuai 回答了问题 · 2019-03-20

解决js字符串去除指定字符

replace 会返回一个新的字符串而不会改变原有字符串,所以你那样写肯定就不会得到想要的结果。

关注 7 回答 8

linshuai 回答了问题 · 2019-03-01

解决请教vue异步返回数据问题

getList: function (id){
            this.getAxios('/demo/index',{id:id}).then(res => {
                console.log(res);
            });
            
        },
        
        getAxios: function(url,data){
            let _this = this;
            return axios({
                method:'get',
                url:url,
                params:data,
            });
        }

关注 3 回答 4

linshuai 赞了文章 · 2019-02-26

开源中国专访:Chameleon原理首发,其它跨多端统一框架都是假的?

开源中国专访:Chameleon原理首发,其它跨多端统一框架都是假的?

原创: 嘉宾-张楠

开源中国

以往我们说某一功能跨多端,往往是指在诸如 PC、移动等不同类型的设备之间都能实现;或者更加具体一点,指的是“跨平台”,可能是大到跨操作系统,比如 Windows、macOS、Linux、iOS 与 Android 等,可能是小到跨某个具体技术的不同实现库。

但是今天我们要介绍的是关于跨 MVVM 架构模式各种环境的场景。

clipboard.png

Chameleon 是一套开源跨端解决方案,它的目标是让 MVVM 跨端环境大一统,实现任意使用 MVVM 架构设计的终端,都能使用其进行开发并运行。

在这样一个 MVVM 环境中,涉及到了 Weex、React-Native、WebView/浏览器与 Flutter 等各种跨端技术,还有它们实现的具体业务产品,比如微信小程序、快应用、支付宝小程序、百度智能小程序、今日头条小程序与其它各类小程序。

clipboard.png

也许你发现了,这里提到了许多种“小程序”,虽然最早微信小程序的概念甚至早期版本出现的时候,有过不少不看好的声音,但是随着它不断发展,目前已经成为了大众生活不可或缺的应用形态。

马化腾透露过,截至 2018 年 11 月有 150 万微信小程序开发者,小程序应用数量超过 100 万,覆盖 200 多个细分行业,日活用户达到 2 亿。这样的成功经验与几乎触及到生活方方面面的巨大流量入口,大家都想入场,于是可以看到后来其它公司纷纷给出了类似的小程序方案。

另一方面,除了小程序百花齐放,2018 年小米、华为、OPPO 等 10 家安卓手机厂商还结成了快应用联盟,并且先后发布了一系列快应用。

Chameleon 目标就是要跨这些端,而随着各家不同实现越来越多,跨端场景也不断变得更加复杂。我们采访了 Chameleon 创始人张楠,请他为读者具体分享了 Chameleon 在这个过程中的成长。

项目地址:https://github.com/didi/chame...

本文是 Chameleon 首次对外公开实现原理!

干货超多,包括:

  • 终端开发未来的开发模式
  • Chameleon 跨端实现原理
  • 当前各种跨端方案原理对比(各种小程序、快应用等)
  • 与 Taro 的对比
  • 演进过程中遇到的困难与思考

当初为什么去研发 Chameleon?

关于这个问题可以从行业背景讲起。

中国互联网络信息中心(CNNIC)发布的《中国互联网络发展状况统计报告》显示,截至 2018 年 6 月,我国网民规模达 8.02 亿人,微信月活 10 亿 、支付宝月活 4 亿、百度月活 3.3 亿;另一方面,2018 Q3 中国 Android 手机占智能手机整体的比例超过 80%,月活约 6 亿。

BAT 与 Android 成为了中国互联网真正的用户入口。但凡流量高的入口级别 APP 都希望做平台,成为一个生态平台和互联网流量入口,大量第三方应用的接入,从业务层让公司 APP 关联上更多企业的利益,并且拥有更强的生命力;从技术层面可以利用“本地能力接口层”收集大量用户数据,从消费互联网到产业互联网需要大量各行各业基础用户数据线索进行驱动和决策。

在这么一种背景下,再结合计算机技术的发展历史,我们知道每一种新技术的出现都会经历“各自为政”的阶段,小程序技术也不例外,所以我们看到了其它各种小程序平台出现。

微信小程序作为首创者,虽然其它小程序都有在技术实现原理、接口设计上刻意模仿,但是作为一线开发者在不同平台发布小程序,往往还是需要重复开发、测试,从前 1 单位的工作量变成了 N 单位的工作量。而这还没算上快应用等其它入口。

这种情况下,滴滴的研发工程师是其中最显著的“受害者”之一,滴滴出行在微信钱包、支付宝、Android 快应用都有相关入口,而且用户流量占比不低。

研发同学在端内既追求 H5 的灵活性,也要追求性能趋近于原生。面对入口扩张,主端、独立端、微信小程序、支付宝小程序、百度小程序、安卓厂商联盟快应用,单一功能在各平台都要重复实现,开发和维护成本成倍增加。

迫切需要一个只维护一套代码就可以构建多入口的解决方案,于是我们着手去打造了 Chameleon(CML,卡梅龙)这么一个项目,真正专注于让一套代码运行多端。

Chameleon 核心是运用了 MVVM 架构,为什么它可以实现跨多端?

MVVM 也就是 Model View ViewModel,它本质上是 MVC( Model View Controller)的进化版本,将 View 的状态和行为抽象化,使得视图 UI 和业务逻辑分开。

它是一种让数据驱动反射视图的模式,发展到现在可能会偏离它的初衷了,更像是一个视图数据间的“通信协议”,让终端开发变得更加单纯,这是一种趋势,面向未来框架都采用这种模式。

clipboard.png

Facebook 在 2013 年开源 React,React 这个项目本身是一个 Web UI 引擎,随着不断发展,它衍生出 React Native 项目,用来编写原生移动应用。正是它给跨端方向带来了 MVVM 模式。

Vue.js 于 2014 年左右发布,逆流而上占据了大量用户群体,2016 阿里巴巴也基于它发布了 Weex 项目,使得可以用 Vue 编写 Native App。

Google 在 2018 年末正式发布了面向未来的跨 Android、iOS 端的 Flutter 1.0.0。

原理

我们知道终端开发离不开三大要素——界面表现(结构、外观)层、逻辑处理层与系统接口层(网络、存储与媒体等)。

clipboard.png

开发者编写代码时在初始化阶段(生命周期)调用“界面表现层”界面模型的接口绘制界面,当用户触摸界面时,“界面表现层”将事件发送给用户“逻辑处理层”,后者经过条件判断再处理并反馈到用户界面,处理过程可能需要调用“系统接口层”,反馈过程需要调用“界面表现层”的接口。

常规的终端开发架构模式下,无论是 Web 端、Android 端还是 iOS 端的项目开发,都强依赖各端的环境接口,特别是依赖界面相关模型设计。

iOS 系统下绘制界面基于 Objective-C 语言环境下的 UIKit 框架;Android 系统下用户绘制界面基于 Java 语言环境,由 LayoutInflater 处理 XML 结构层次树;Web 端使用 DOM 模型和 CSS 来描述绘制界面。  

MVVM 中的关键是它通过 ViewModel 这一层将界面和逻辑层彻底隔离开来,负责关联界面表现和逻辑处理层的响应事件(update/notify)关系,这一“隔离层”上下通信足够规范、足够纯净单一。  

Model 进行逻辑处理是纯业务响应逻辑,任何一种语言都可以实现,你可以用 Android 的 Java,也可以用 iOS 的 Objective-C,你心情好用“世界第一语言 PHP”也能实现。

之所以普遍选择 JavaScript,很大程度是因为在这个领域内它的优点显著,如学习成本低、天生具备跨端属性、虚拟机(V8、JavaScriptCore)和各方向组件建设较好、生态活跃。

而系统接口层则更简单了,只需穷举统一基础接口+可扩展接口能力即可。

各种 MVVM 方案

具体来看看各种 MVVM 方案都是怎么样的。

React Native、Weex 与快应用的 MVVM

clipboard.png

开发者编写的代码在虚拟机(V8、JavaScriptCore)里面运行,虚拟机容器里面包含扩展的系统基础接口。运行时,将描述界面的数据(主要是 CSS+DSL 所描述内容)通过通信层传递给 Android、iOS 端的渲染引擎,用户触摸界面时,通过通信层传递给虚拟机里面的业务处理代码,业务处理代码可能调用网络、储存与媒体等接口,最后再次反馈到界面。

Flutter 的 MVVM

Flutter 和 RN 的最大区别在于将“JavascriptCore/V8+JS”替换成“C++ 实现的 engine+Dart 实现的 Framework+静态类型 Dart+编译成机器码”。

Flutter 的方案如下图所示:

clipboard.png

Service 其实就是本地能力接口层,Widget 树是视图层模型。

clipboard.png

Flutter 和 RN 的使用面设计上类似,Flutter 文档中提到“In Flutter, almost everything is a widget.”,widget 的调用从 RN 的 JSX 变成 Flutter 的 widget 调用,UI 的外观描述从 RN 的 CSS(文本样式、布局模型、盒模型)到定制化 Flutter Widget(textStyle 、Layout Widget、Widget)。

clipboard.png

本质上 Flutter 也是 MVVM 架构,逻辑层通过 setState 通知视图层更新,一定程度上这也是为什么 Flutter 敢说能转成 Web 框架的原因,核心还是基于这类数据驱动视图架构模式,业务代码不会深度依赖任何一端特有的“视图模型”。

各类小程序的 MVVM

小程序本质上和 Weex、React Native 的设计思路基本一样,最大区别在于前者还是用浏览器 WebView 做渲染引擎,而后者是单独实现了渲染引擎(所以大量的 CSS 布局模型不支持)。

clipboard.png

具体到 Chameleon 上是怎么实现的?

首先任何一份应用层的高级语言代码块分成几层:语言层(Language)、框架层(Framewrok)与库层(Library):

  • Language —— 通俗来说,实现程序所需的基本逻辑命令:逻辑判断(if)、循环(for)与函数调用(foo())等。
  • Framewrok —— 通俗来说,完成一个 App 应用交互任务所需规范,例如生命周期(onLoad、onShow)、模块化与数据管理等。
  • Library —— 可以理解就是“方法封装集合”。比如 Web 前端中 Vue 更适合叫框架,而 jQuery 更适合叫库;Android 系统下 activity manager + window Manager  View System 等的集合叫框架,而 SQLite 、libc 更适合叫库。

对应到 Chameleon 就是这样:

clipboard.png

具体到实现原理全景架构图如下:

clipboard.png

你可以理解 Chameleon 为了实现“让 MVVM 跨端环境大统一”的目标做了以下工作:

  • 定义了标准的 Language(CML DSL)、Framework 与 Library(内置组件和 API)协议层。
  • 在线下编译时将 DSL 转译成各端 DSL,只编译 Language 层面足够基础且稳定的代码。
  • 在各个端运行时分别实现了 Framework 统一,在各个端尽量使用原有框架,方便利用其生态,这样很多组件可以直接用起来。
  • 在各个端运行时分别实现了 Library(内置组件和 API)。
  • 为用户提供多态协议,方便扩展以上几方面的内容,触达底层端特殊属性,同时提升可维护性。

clipboard.png

实现思路很简单,所有设计为了 MVVM 标准化,不做多余设计,所以宏观的角度就像 Node.js(libuv)同时运行在 Windows 和 macOS 系统,都提供了一个跨平台抽象层。

从 MVVM 角度来看的话:

  • View(展现层)
  • 第三方 Render Engine:各类框架已有框架,浏览器的 Vue、Webview 里的小程序引擎、Android、iOS 里面的 React Native/Weex 引擎、甚至 Flutter 里面的 Dart Framework。
  • Chameleon 内置组件库:多态协议定义统一组件 view、input、text、block 与 cell 等,它是界面组层的原始基类,衍生出多复杂界面功能。
  • ViewModel(关联层)Chameleon 语法转译
  • 组件调用
  • 循环
  • 条件判断
  • 事件回调关联
  • 父子关系
  • ……
  • Model(逻辑响应层)
  • JavaScript 代码
  • CML Runtime 框架
  • Chameleon API:多态协议定义统一接口,cml.request、cml.store 等

Chameleon 的跨多端方案给开发者的开发带来了极大的便利,具体表现是怎么样的?

一句话:基于 Chameleon 开发,效率会越来越高

各个端的涌现,让原本是 1 的工作量因为多端存在而变成 N 倍,使用 Chameleon,工作量会变回 1.2。这多出来的 0.2 工作量是要处理各端的差异化功能,比如以下场景:

  • 某业务线迁入 Chameleon 时,发现没有“passport登录组件”,在各类小程序里面能免密登录了,在 Web、Native 端是弹出登录框登录,不同业务用户交互形态不一样所以 Chameleon 没有提供组件;开发者需要基于多态协议扩展单独一个登录组件<passport/>,无论如何最后返回一个登录后的回调 token 即可,外部无需组件关心里面如何操作。
  • 用户需要分享功能,发现没有“share组件”,在微信 Web 端可以引导右上角分享,在小程序直接分享,不同业务用户交互形态不一样,用户需要基于多态协议扩展单独一个登录组件<share/>。

这种各端差异较大的例子,随着业务的积累,可以变成了一个个业务组件单独维护,后面也不需要重复开发了,且反推产品体验一致化,组件三层结构“CML框架内置组件->CML扩展组件->业务开发者自己扩展的多态组件”达成 100% 统一。

随着组件积累业务开发工作量越来少,工程师可以专注做更加有意义的事情,这就是 Chameleon 存在的目的。

基于统一的跨端抽象,用户在 Chameleon 项目持续维护过程中,Chameleon 发布新增一个端之后,你的业务代码基本不用改动即可无缝发布成新端。

比如这个 cml-yanxuan 项目开发时支持 3 个端,后面新增了百度、支付宝小程序端,原有代码直接能跑起来运行 5 个端,一端所见即多端所见。

开发时只能跑 3 个端

clipboard.png

原有代码无缝支持 5 个端

clipboard.png

另外特别强调的是,对于大公司团队,如果有很强的技术能力,希望开发的代码掌控在自己手里,对输出结果有更好控制能力。其实 Chameleon 内置组件和内置 API 是可以替换的,那么所有组件都是业务方自己开发了,哪天不想用了直接导出原生组件即可离开 Chameleon,如下图:

clipboard.png

目前跨多端统一的方案中,Taro 是比较亮眼的,能否具体对比一下 Chameleon 与 Taro。

我们觉得 Chameleon 与其它解决方案的最大区别在于其它框架都是小程序增强,即用 Vue 或者 React 写小程序,这些框架官方给的已接入例子也都是跑微信小程序。

它们更加类似 Chameleon 的前身 MPV(Mini Program View),即考虑如何增强小程序开发。2017 年微信小程序发布时,滴滴作为白名单用户首先开始尝试接入,开始面对重复开发的难题。

这时候我们专门成立了一个小项目组,完成一个名为 MPV 的项目,一期目标是“不影响用户发挥,不依赖框架方的原则性实现一套代码运行 Web 和微信小程序”。

看着很美好,用这样的方案实现 Web 端和小程序端,也确实完成了超过 90% 代码重用,总体上开发效率和测试效率都有了一定提升,但是却不是真正意义上的跨多端统一。

单独说到 Chameleon 与 Taro 的区别,总体上看,可以归为这样一个表:

clipboard.png

表中每一项都是在做跨端方案时需要考虑到的。我们说除了 Chameleon,其它方案都只是在对小程序进行增强,或者说是模仿微信小程序的 API 和组件的接口设计。

Taro 是通过将 JSX 转成小程序模板,在其它端模拟微信小程序的接口和组件,让其它端更像微信小程序,业务开发时不一致的地方需要环境变量判断差异分别调用,会造成端差异逻辑和产品逻辑混合在一起。

此外,它要跟随小程序更新,业务方会有双重依赖;其它端的和小程序不能保持一致,用户要各种差异化兼容,不利于维护。

那 Chameleon 呢?Chameleon 把这些问题都考虑到了,所以在早期伪跨端 MiniProgram View 成型之后不断演进的过程中,把它发展成为一个真正的跨多端方案。

前边的表格显示了,Chameleon 既考虑统一性,又考虑差异性,且差异性不会影响可维护性;当各端差异确实太大,那就不要用一套代码实现多个端同一页面,而是统一公用组件。

这还只是拿 Chameleon 与 Taro 的重合点进行了对比,但是别忘了 Chameleon 不仅仅是前端框架,它:

  • 还有统一的 Chameleon Native  SDK,Chameleon 不仅仅希望统一各类小程序,还要覆盖自家 APP,会持续通过 Native SDK 扩展 API 和组件,期望有与小程序一样的本地能力。理想情况下,一套代码就能在各类小程序、自家 APP 里面无缝平滑运行。
  • 还有待开源的后台管理系统。
  • 还有待开源的 XEdtior 非研发用编辑器,可以直接编辑跨端页面、直接发布。

另外,未来还将带来以下能力:

  • 后端统一接口(消息推送、分享与支付等)
  • 基于统一的 MVVM 标准,更有基于 Flutter 的原生 APP

当前的各类小程序和 Native 跨端框架,类似当年多个浏览器时,Safari、Chrome、Firefox、IE 6/7/8/9、Android 浏览器等盛行的时代。以这个来类比,那么 Chameleon 的接口组件设计上更像一个 jQuery。

网络请求有的是 XHRHttprequest 有的是 ActiveXObject,jQuery 考虑的是用户需要什么,需要一个网路请求接口,支持 get、post 等,所以 jQuery 写一个既非 ActiveXObject 又非 XHRHttprequest 的名为 $.ajax 接口,提供一个封装网络接口,你不用关心内部在不同端怎么调用的,jQuery 内部会帮你兼容。

Chameleon 也是一样的思路,所有的接口设计都是真正能兼容跨所有的端,没有差异性,而且只保留当前所在端的接口调用代码:IE 里面只保留 ActiveXObject,Chrome 只保留 XHRHttprequest。

Chameleon 的接口设计上比 jQuery 更强的地方在于,使用标准的多态协议,保障可维护性,性能上只保留当前端代码,且将多态协议暴露出来,让用户也能扩展自己想要的 API(类比 $.xxx)。

当然时代已经变了,监听视图不在是 $('#xxx').click(fn),而是 MVVM 数据驱动视图方式了,所以提供了 Chameleon 双向绑定这样的 VM 层。

前边讲到了 Chameleon 的前身 MPV,那具体分享一下 Chameleon 的整个演进过程吧。

出生期:选择转译还是模拟小程序环境?

前面讲到,2017 年的时候,我们完成一个名为 MPV 的项目,一期目标是不影响用户发挥,不依赖框架方的原则性实现一套代码运行 Web 和微信小程序。

当时缺乏小程序资料是遇到的最大问题(就更别提今天讲到的业内这么多解决方案了),当时唯一一个可以参考的开源项目是 WEPT,WEPT 是一个微信小程序实时开发环境,它的目标是为小程序开发提供高效、稳定、友好、无限制的运行环境。它的设计思路是在 Web 端模仿小程序环境执行。

于是我们在开发 MPV 时考虑了两种实现策略:

1、在 Web 端像 WEPT 一样 mock 小程序环境;就像微信开发者工具里面也模拟了小程序执行环境,WAServie、WAWebview 提供的两套环境源码做底层,在页面中开启三个独立运行环境运行并用 iframe 通讯模拟微信小程序的 3 个 Webview 之间的联通关系。

2、逐个转译代码支持小程序,缺点是可能会有 edge case 需要处理以及潜在的 bug 会比较多。

最终在看完 WEPT 源码和微信开发者工具的情况下,我们明确放弃了第 1 条实现策略,选择了逐个转译代码支持小程序的路线,主要原因是于 Web 端兼容微信所有的功能,尺寸过于庞大。

经过三个月紧锣密鼓的开发终于实现了第一版本 MPV:  

clipboard.png

经过实现几个 demo 之后,开始执行迁移计划:

clipboard.png
 

MPV 在 Webapp 上实践最终实现效果如下:

clipboard.png

最终实现效果挺美好,也确实完成了超过 90% 的代码重用,总体上开发效率和测试效率都有了明显提升。

但是在后续实践过程中,发现存在大量的问题,并且项目越大问题越凸显出来,总结如下:

  • 可维护性问题,没有隔离公用代码和各端差异代码。项目中不止有业务逻辑,还混杂着 Web 端和小程序端产品功能差异化逻辑。比如前边举过的例子,分享功能 Web 端无法实现(引导分享),小程序可以实现,意味着各种环境判断各种差异化逻辑,牵一发动全身,还要来回测试。
  • 方向选择错误,MPV 使用了小程序语法标准(小程序的生命周期、API 接口等),导致用户使用上无法清晰理解。
  • 不能直接使用各端已有生态组件,即缺乏标准规范接入某个端已有开源组件。比如 Web 端 pick.js 组件缺乏快速接入规范,用户要么重新开发,或者在模板和 js 代码中使用环境判断的方式针对引入。最终导致同一功能不同端的调用方式、输入与输出不一致。
  • 业务项目依赖 MPV 框架。框架依赖微信小程序接口(模板、生命周期与接口),扩展了统一接口。例如微信小程序更新了 wx.request 时,业务项目方无法立刻使用,需要等框架更新。
  • 文件夹结构混乱,混杂着多个端代码文件,且识别成本高。
  • 不支持 vuex、redux 等高效数据管理方式
  • 尺寸单位不统一,px 和 rpx 不一致
  • 周边小型差异点太多:
  • 协议不一致,例如 Web 端可以用 //:www.didiglobal.com/passenger/create ,小程序只能用 https://:www.didiglobal.com/passenger/create
  • 打开一个新页面时链接不统一,例如打开发单页时,Web 端是 //:www.didiglobal.com/passenger/create,小程序是 /page/create
  • 页面之间跳转时,传参不统一
  • debug 成本高,修改完代码之后两端需要测试
  • 两端界面效果不一致,基础内置组件统一性建设不足
  • 工程化建设落后,例如不支持 liveroload、数据 mock、资源定位、proxy、多端统一预览
  • 接口设计不完整,生命周期、组件分层、本地 API 设计等
  • 模板 DSL 语法不规范

成长期:从伪统一到大一统

在 MPV 的实践积累下,有了一定的底气和把握,后续的规划更加明确。2018 年 4 月我们把跨端项目规模进一步扩大,想要做一个真正跨 N 端的解决方案,目标是提供标准的 MVVM 架构开发模式统一各类终端。这就是 Chameleon 的出现契机。

Chameleon 真正想要一套代码运行多端,总结下来要解决几大问题:

  • 要全面完成端开发的所有细节的统一性,特别是界面统一性有大量细节要做
  • 要在完成上一条的前提下考虑差异化定制空间
  • 持续可维护

clipboard.png

目标理想业务形态是这样的:

clipboard.png

图中上半部分是传统开发方式,下半部分 Chameleon 的模式抽象出了 UI 渲染层和本地接口能力层,业务代码一部分简单页面由 XEditor(h5Editor 的前身)编辑工具产出,另一部分工程师使用 Chameleon 开发,不止解决跨端问题,还弥补改进了工程开发过程中的效率、质量、性能与稳定性问题,让工程师专注有意义的业务,成长更快。

首个 Native 渲染引擎选择——小程序架构、RN/Weex 架构

从 MPV 到 Chameleon,外界看来最明显的变化是从跨 2 端(Web、小程序)升级到跨多端(Web、小程序、Android、iOS),最开始纠结于首个端上版本的渲染引擎使用小程序架构还是 RN/Weex 架构。

RN/Weex 网上有大量资料可查,但是小程序方面则不然。千辛万苦搜索之后,根据一位知道内情的朋友的描述分享,才有了一定的了解。

 
clipboard.png

这里分享几个印象深刻的要点:

  • 小程序展现层使用 Webview,里面内置了一套 JS 框架用来和 Native 通信,真正业务代码执行在单独 JS 虚拟机容器实例中
  • JS 虚拟机容器使用情况,iOS 系统是 JavaScriptCore,Android 系统使用 QQ 浏览器的 X5 内核
  • 小程序的各个 TAG 组件使用的数据驱动用的是 Web Components
  • 显而易见,部分性能要求较高的使用原生控件(视频、键盘等等)插入到 Webview 里面。
  • 原生控件的具体位置 Native 怎么获取?答案是由嵌入到 Webview 的一套小程序框架通知给原生层
  • 原生控件怎么保证在内部可滚动的元素(Scroll-view)里面正常滚动?答案是 CSS 设置 -webkit-over-scroll:touch 时,iOS 的实现是原生的 UIScrollView,Native 可以通过一些黑科技找到视图层级中的 UIScrollView,然后对原生控件进行插入和处理;而 Android 直接绘制没办法做到这点。现在(截至 4 月)仅仅是直接覆盖到 Webview 最外层的 scrollview 上,由内置到 Webview 的一套 JS 框架控制原生控件位置

最终多方面分析如下:

clipboard.png

虽然小程序方案看起来很简单,但其实很多细节点需要大量打磨,从确认方案到真正可以跑起来可以线上发布,仅仅花费在终端上的研发人力为 20P*6 个月,微信小程序团队的目标和我们跨端目标不一样,他们投入这么多成本是值得的,我们为了跨端没必要投入这么高成本。

所以我们选择放弃小程序渲染方案,而使用已开源的 RN/Weex 方案

第一个版本最终使用 Weex,包括团队同学去看了 Weex 源码实现。

在整体设计上仅仅使用 Weex 渲染功能,外层包装接口,保障后续能有更高扩展性。

Chameleon Native SDK

针对 Native SDK 我们主要从原生能力扩展、性能与稳定等三个方面做了工作。 

clipboard.png

  • 原生能力扩展:无论是 Webview 还是 React Native、Weex 甚至 Flutter 都只提供渲染能力(以及一些最基础本地接口),更多完成业务功能所需本地环境的能力(例如分享到微信)需要 Android 和 iOS 的 Native 往容器去扩展。本地能力包含 2 种,涉及 UI 界面的统一叫组件(UI 组件如登录、支付),涉及到纯能力调用的统一叫 API(网络、存储等)
  • 性能:界面展现和交互耗时关键取决于 2 块,资源加载耗时(非打包到安装包部分代码)、执行耗时
  • 稳定:主要关注灰度发布(风险可控)和线上止损,主要工作是按用户灰度发布、可以快速降级到 H5

以下是性能方向中的首屏加载时间的优化数据,原有 H5 使用 SSR(Server Side Render)已经算是最快的 Web 首屏技术方案了(不考虑优化后端多模块耗时的 BIGPIPE),它保持在 1.5 秒以下,在优化后降到 0.5 秒左右。  

clipboard.png

 

性能优化中我们有一个关于执行速度的 TODO 计划。同样是跨端,Flutter 之所以比 Weex 和 RN 执行速度快,主要原因是前者是编译型,客户端机器运行前已经是 CPU 可识别的机器码;后者是解释型,到客户端运行前是字符串,边编译边执行,虽然做了 JIT 尽量优化,差距还是较大。其实在这中间还有一个抹平了不同 CPU 架构下机器码差异的中间码;当然前提是开发语言改成静态类型,这里不作展开。

原本分 5 次开发的 Web 端、支付宝小程序、快应用、微信小程序、Native 端变成了 1.2 次左右开发了。最重要的是随着业务级别各端差异化的多态组件和跨端组件积累,后续 1.2 工作量也会变成 0.8,0.4 的优化主要来自两个方面:

  • 0.2 是普通跨端组件的积累,复用度变高
  • 0.2 是各类业务级别的差异化多态组件,例如登录功能,在 Web端、Native 端和小程序端实现和交互是不一致的,这时候业务形态不一样,设计的 <passport> 组件也不一样,只能各业务线去封装。

介绍一下接下来的 roadmap。

我们的最终目标是提供标准的 MVVM 架构开发模式统一各类终端。

clipboard.png

接下来的具体 roadmap 如下表所示:

clipboard.png

欢迎有共同愿景的同学加入我们一起共建,往仓库贡献自己的代码。

项目地址:https://github.com/didi/chame...

QQ 群:

clipboard.png

公众号:

clipboard.png

采访嘉宾介绍

张楠,Chameleon 创始人,技术团队负责人,前百度资深工程师,终身学习者。

查看原文

赞 20 收藏 10 评论 0