Dophin

Dophin 查看完整档案

杭州编辑  |  填写毕业院校  |  填写所在公司/组织 dophin.me 编辑
编辑

newbie

个人动态

Dophin 提出了问题 · 2018-08-03

git 无法识别部分文件的变更

我的git在某个特定的repo没法识别部分文件的变更,但是在另一个repo仍然work。
不过在这个repo也可以识别部分文件的变更,比如.gitignore
这个问题跟编辑器没关系,我试过不同的编辑器。

我在stack上找了一些答案,比如git update-index --no-assume-unchanged $(git ls-files | tr '\n' ' ')
这个确实可以临时解决问题,但commit之后就又出问题了。
我还试过重新clone下来,还是有问题。

不知道这个怎么发生的,也不知道怎么解决,来请教下各位大佬:D

关注 2 回答 1

Dophin 评论了文章 · 2018-07-05

通过javascript进行UTF-8编码

通过javascript进行UTF-8编码


javascript的字符集:

javascript程序是使用Unicode字符集编写的。UnicodeASCIILatin-1的超集,并支持地球上几乎所有的语言。ECMAScript3要求JavaScript必须支持Unicode2.1及后续版本,ECMAScript5则要求支持Unicode3及后续版本。所以,我们编写出来的javascript程序,都是使用Unicode编码的。

UTF-8

UTF-8(UTF8-bit Unicode Transformation Format)是一种针对Unicode的可变长度字符编码,也是一种前缀码。

它可以用来表示Unicode标准中的任何字符,且其编码中的第一个字节仍与ASCII兼容,这使得原来处理ASCII字符的软件无须或只须做少部分修改,即可继续使用。因此,它逐渐成为电子邮件、网页及其他存储或发送文字的应用中,优先采用的编码

目前大部分的网站,都是使用的UTF-8编码。

将javascript生成的Unicode编码字符串转为UTF-8编码的字符串

如标题所说的应用场景十分常见,例如发送一段二进制到服务器时,服务器规定该二进制内容的编码必须为UTF-8。这种情况下,我们必须就要通过程序将javascript的Unicode字符串转为UTF-8编码的字符串。

转换方法

转换之前我们必须了解Unicode的编码结构是固定的。
不信可以试一试 String 的 charCodeAt 这个方法,看看返回的 charCode 占几个字节。

  • 英文占1个字符,汉字占2个字符

然而,UTF-8的编码结构长度是根据某单个字符的大小来决定长度有多少。
下面为单个字符的大小占用几个字节。单个unicode字符编码之后的最大长度为6个字节。

  • 1个字节:Unicode码为0 - 127

  • 2个字节:Unicode码为128 - 2047

  • 3个字节:Unicode码为2048 - 0xFFFF

  • 4个字节:Unicode码为65536 - 0x1FFFFF

  • 5个字节:Unicode码为0x200000 - 0x3FFFFFF

  • 6个字节:Unicode码为0x4000000 - 0x7FFFFFFF

具体请看图片:
图片描述

因为英文和英文字符的Unicode码为0 - 127,所以英文在Unicode和UTF-8中的长度和字节都是一致的,只占用1个字节。这也就是为什么UTF8是Unicode的超集

现在我们再来讨论汉字,因为汉字的unicode码区间为0x2e80 - 0x9fff, 所以汉字在UTF8中的长度最长为3个字节。

那么汉字是如何从Unicode的2个字节转换为UTF8的三个字节的哪?

假设我需要把汉字"中"转为UTF-8的编码

1、获取汉字Unicode值大小

var str = '中';
var charCode = str.charCodeAt(0);
console.log(charCode); // => 20013

2、根据大小判断UTF8的长度

由上一步我们得到汉字"中"的charCode为20013.然后我们发现20013位于2048 - 0xFFFF这个区间里,所以汉字"中"应该在UTF8中占3个字节。

3、补码

既然知道汉字"中"需要占3个字节,那么这3个字节如何得到哪?

这就需要设计到补码,具体补码逻辑如下:
图片描述
好吧,我知道这个图你们也看不明白,还是我来讲吧!

具体的补位码如下,"x"表示空位,用来补位的。

  • 0xxxxxxx

  • 110xxxxx 10xxxxxx

  • 1110xxxx 10xxxxxx 10xxxxxx

  • 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx

  • 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx

  • 1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx

warning:有没有发现?补位码第一个字节前面有几个1就表示整个UTF-8编码占多少个字节!UTF-8解码为Unicode就是利用的这个特点哦~

我们先举个简单的例子。把英文字母"A"转为UTF8编码。
1、“A”的charCode为65
2、65位于0-127的区间,所以“A”占一个字节
3、UTF8中一个字节的补位为0xxxxxxx,x表示的是空位,是用来补位的。
4、将65转为二进制得到1000001
5、将1000001按照从前到后的顺序,依次补到1xxxxxxx的空位中,得到01000001
6、将11000001转为字符串,得到"A"
7、最终,"A"为UTF8编码之后“A”

通过这个小例子,我们是否再次验证了UTF-8是Unicode的超集

好了,我们现在再回到汉字"中"上,之前我们已经得到了"中"的charCode为20013,二进制为01001110 00101101。具体如下:

var code = 20013;
code.toString(2); 
// => 100111000101101 等同于 01001110 00101101

然后,我们按照上面“A”补位的方法,来给"中"补位。
01001110 00101101按照从前到后的顺序依此补位到1110xxxx 10xxxxxx 10xxxxxx上.得到11100100 10111000 10101101.

4、得到UTF8编码的内容

通过上面的步骤,我们得到了"中"的三个UTF8字节,11100100 10111000 10101101
我们将每个字节转为16进制,得到0xE4 0xB8 0xAD;
那么这个0xE4 0xB8 0xAD就是我们最终得到的UTF8编码了。

我们使用nodejs的buffer来验证一下是否正确。

var buffer = new Buffer('中'); 
console.log(buffer.length); // => 3
console.log(buffer); // => <Buffer e4 b8 ad>
// 最终得到三个字节 0xe4 0xb8 0xad

因为16进制是不分大小写的,所以是不是跟我们算出来0xE4 0xB8 0xAD一模一样。

将上面的编码逻辑写到一个函数中。

// 将字符串格式化为UTF8编码的字节
var writeUTF = function (str, isGetBytes) {
      var back = [];
      var byteSize = 0;
      for (var i = 0; i < str.length; i++) {
          var code = str.charCodeAt(i);
          if (0x00 <= code && code <= 0x7f) {
                byteSize += 1;
                back.push(code);
          } else if (0x80 <= code && code <= 0x7ff) {
                byteSize += 2;
                back.push((192 | (31 & (code >> 6))));
                back.push((128 | (63 & code)))
          } else if ((0x800 <= code && code <= 0xd7ff) 
                  || (0xe000 <= code && code <= 0xffff)) {
                byteSize += 3;
                back.push((224 | (15 & (code >> 12))));
                back.push((128 | (63 & (code >> 6))));
                back.push((128 | (63 & code)))
          }
       }
       for (i = 0; i < back.length; i++) {
            back[i] &= 0xff;
       }
       if (isGetBytes) {
            return back
       }
       if (byteSize <= 0xff) {
            return [0, byteSize].concat(back);
       } else {
            return [byteSize >> 8, byteSize & 0xff].concat(back);
        }
}

writeUTF('中'); // =>  [0, 3, 228, 184, 173] 
// 前两位表示后面utf8字节的长度。因为长度为3,所以前两个字节为`0,3`
// 内容为`228, 184, 173`转成16进制就是`0xE4 0xB8 0xAD`
// 读取UTF8编码的字节,并专为Unicode的字符串
var readUTF = function (arr) {
    if (typeof arr === 'string') {
        return arr;
    }
    var UTF = '', _arr = this.init(arr);
    for (var i = 0; i < _arr.length; i++) {
        var one = _arr[i].toString(2),
                v = one.match(/^1+?(?=0)/);
        if (v && one.length == 8) {
            var bytesLength = v[0].length;
            var store = _arr[i].toString(2).slice(7 - bytesLength);
            for (var st = 1; st < bytesLength; st++) {
                store += _arr[st + i].toString(2).slice(2)
            }
            UTF += String.fromCharCode(parseInt(store, 2));
            i += bytesLength - 1
        } else {
            UTF += String.fromCharCode(_arr[i])
        }
    }
    return UTF
}

readUTF([0, 3, 228, 184, 173]); => '中'

另外一种将中文解析得到UTF8字节码的方法

另外一种比较简单的将中文转为UTF8字节码的方法比较简单,浏览器也提供了一个方法,而且这个方法大家都一直在用,是什么哪?就是encodeURI。当然,encodeURIComponent也是可以的。
没错,就是这个方法。那么这个方法是怎么将一个Unicode编码的中文转为UTF8的字节码嘞?

var str = '中';

var code = encodeURI(str);

console.log(code); // => %E4%B8%AD

有没有发现得到了一个转义后的字符串,而且这个字符串中的内容和我之前在上面得到的字节码是一样的~~~。

下面我们将%E4%B8%AD转为一个number数组。

var codeList = code.split('%');

codeList = codeList.map(item => parseInt(item,16));

console.log(codeList); // => [228, 184, 173]

如此简单,有木有~~~

这个简便方法的原理是什么?

这里就涉及到的URI中的querystring编码的问题了。因为按照规定,URI中的querystring必须按照UTF8的编码进行传输,而JavaScript是Unicode的,所以浏览器就给我们提供了一个方法,也就是encodeURI/encodeURIComponent方法。这个方法会讲非英文字符(这里考虑下,为什么是非英文字符?)先转为UTF8的字节码,然后前面加个%进行拼接,所以我们将汉字"中"转义下便得到了"%E4%B8%AD".
好吧,原理就这些,没有其他的了。

不过,这种方法还有个缺点,那就是只会转义非英文字符,所以当我们需要将英文字符也格式化为UTF8编码时,这个方法是达不到我们需求的,我们还需要额外的将英文字符也给转义下。

那我想要解析回来应该怎么做哪?用decodeURI/decodeURIComponent就可以了。

var codeList = [228, 184, 173];

var code = codeList.map(item => '%'+item.toString(16)).join('');

decodeURI(code); // => 中

好了,到这里本文也就介绍完UTF8的编码了。
希望可以帮助大家了解到UTF-8编码的原理。

查看原文

Dophin 赞了文章 · 2017-08-19

腾讯祭出大招VasSonic,让你的H5页面首屏秒开

VasSonic成长历程


前言

2017.8.8 14时,SNG增值产品部Vas团队研发的轻量级高性能Hybrid框架VasSonic通过了公司最终审核,作为腾讯开源组件分享给大家。从当初立项优化页面加载速度,到不断摸索、优化,再到整理代码、文档,最终在Github上开源,并且在24小时内获取star数超过1600。我们非常高兴看到我们的成果收到这么多的关注,趁此机会,正好回顾一下VasSonic的成长历程,也希望能够让大家更了解VasSonic。

项目背景

Web相信大家再熟悉不过了,它具有快速迭代发布的天然优势,但也存在中一些让人诟病的问题,比如加载速度慢,体验差等。在此之前,手Q上很多页面首屏打开速度居高不下,甚至有些耗时达到3s以上,这意味着用户打开页面必须经过3秒之后才能进行交互操作,体验相当差,很多用户忍受不了这个漫长的时间直接流失掉了。

为了提升用户体验和业务用户留存率,我们很多业务一开始通过Web开发,等页面模型验证符合预期后,再将H5页面转化成原生界面。我们很快意识到这不是一种健康的可持续的开发模式,一方面存在重复人力浪费,另外一方面原生商城除了速度快一点,要运营活动改版都很难。

所以后来团队改了切入方向,安排人力专心研究如何加快页面打开速度,经过了一系列的摸爬滚打和优化探索,最终我们研发出了VasSonic框架,让H5页面首屏达到秒开,给用户一个更好的H5体验。下面就和大家分享VasSonic框架的发展历程。

业务形态

任何一个技术框架都是结合具体的业务形态来进行发展优化的,技术是为了更好地服务业务,业务也会驱动技术的发展。在此首先介绍一下业务形态,我们是来自手Q增值产品部门的VAS团队,负责手机QQ上很多深受年轻人喜欢的个性化增值服务,比如气泡、挂件、主题等等。手Q上大部分的业务还是基于H5开发的,大家对手Q的业务形态可能有简单的了解。比如下图的游戏分发中心、会员特权中心、个性化装扮商城等。这部分商城的特点比较明显,页面的很多数据都是动态的,是由我们的产品经理在后台配置的。
业务

这些都是很常见页面,我们通常将html/js/css等静态资源放到CDN上,然后页面加载后,再通过CGI去拉取最新的数据,进行拼接展示, 这样子可以利用到CDN的多地部署和就近接入等优势,同时提高了服务器的并发能力。这种传统模式的加载流程如下所示:
加载流程

  1. 用户点击后,经过终端一系列初始化流程,比如进程启动、Runtime初始化、创建WebView等等。
  2. 完成初始化后,WebView开始去CDN上面请求Html加载页面。
  3. 页面发起CGI请求对应的数据或者通过localStorage获取数据,数据回来后再对DOM进行操作更新

可以看出上述流程存在着几个问题:

  1. 从外网统计数据来看,用户的终端耗时在1s以上,这意味着在这1s多的时间里,网络完全是空闲在等待的,非常浪费;
  2. 页面的资源和数据完全依赖于网络,特别是用户在弱网络场景下,页面会出现很长时间的白屏,体验非常差;
  3. 因为页面的数据依赖于动态拉取,加载完页面后,往往是看到一些模块先转菊花,再展示,体验也是不好的。同时这里涉及到较多数据更新,经常要更新DOM,性能上也有不少开销。

所以针对以上几个问题,我们也对应做了很多优化和探索。

问题

VasSonic的前世

优化终端

针对终端耗时1s以上的情况,我们对手Q WebView框架进行了重构:

  1. 启动流程彻底拆分,设计为一个状态机按序按需执行
  2. View相关拆分模块化设计,尽可能懒加载,IO异步化
  3. X5内核在手Q中的独立进程中提前预加载
  4. 创建WebView对象复用池

关于第四点,我们想分享一些Android平台上的细节,由于Android系统的生态原因,导致用户的系统版本和系统Webkit内核处于极其分裂状态,所以我们公司在手Q和微信统一使用X5内核。相对系统WebView来说,首次启动X5内核时,创建WebView比较耗时,因此我们尽量想复用WebView,但是WebView却是与Activity Context绑定。销毁复用的时候,需要释放Activity的Context,否则会内存泄露。针对这种情况,有没有一种两全其美的办法呢?

计算机有一句经典的名言:计算机领域任何一个问题都可以通过引入中间层来解决。于是我们通过包装的方式,实现了一个Context的壳,真正的实现体包装在里面,逻辑调用真正调用到对应的实现体的函数。 经过实验发现,Android系统本身提供了这么一个MutableContextWrapper,作为Context的一个中间层。

我们会将Activity context包在MutableContextWrapper里面,destory的时候,会将WebView的Context设置为Application的Context,从而释放Activity Context。
类似如下:


//precreate WebView
MutableContextWrapper contextWrapper = new MutableContextWrapper(BaseApplicationImpl.sApplication);
mPool[0] = new WebView(contextWrapper);

//reset WebView 
ct =(MutableContextWrapper)webview.getContext();
ct.setBaseContext(getApplication());

//reuse WebView
((MutableContextWrapper)webview.getContext()).setBaseContext(activityContext);

静态直出

“直出”这个概念对前端同学来说,并不陌生。为了优化首屏体验,大部分主流的页面都会在服务器端拉取首屏数据后通过NodeJs进行渲染,然后生成一个包含了首屏数据的Html文件,这样子展示首屏的时候,就可以解决内容转菊花的问题了。
当然这种页面“直出”的方式也会带来一个问题,服务器需要拉取首屏数据,意味着服务端处理耗时增加。
不过因为现在Html都会发布到CDN上,WebView直接从CDN上面获取,这块耗时没有对用户造成影响。
手Q里面有一套自动化的构建系统Vnues,当产品经理修改数据发布后,可以一键启动构建任务,Vnues系统就会自动同步最新的代码和数据,然后生成新的含首屏Html,并发布到CDN上面去。

直出

离线预推

页面发布到CDN上面去后,那么WebView需要发起网络请求去拉取。当用户在弱网络或者网速比较差的环境下,这个加载时间会很长。于是我们通过离线预推的方式,把页面的资源提前拉取到本地,当用户加载资源的时候,相当于从本地加载,即使没有网络,也能展示首屏页面。这个也就是大家熟悉的离线包。
手Q使用7Z生成离线包, 同时离线包服务器将新的离线包跟业务对应的历史离线包进行BsDiff做二进制差分,生成增量包,进一步降低下载离线包时的带宽成本,下载所消耗的流量从一个完整的离线包(253KB)降低为一个增量包(3KB)。
带宽优化

经过一系列优化后,在Android平台上,点击到页面首屏展示的耗时从平均3s多降低为1.8s,优化40% 以上

数据对比

VasSonic的诞生

虽然通过静态直出和离线预推等方式优化后,速度已经达到1.8s,但还存在很大的优化空间,当我们准备持续深入优化时,我们的业务形态发生了新的变化。

之前我们页面内容的数据主要是由产品经理要配置的,用户看到的内容基本都是一样的。而现在页面为了更好地为用户推荐喜欢的内容,我们后台引入机器学习和随机算法来做智能个性化推荐。比如左边新用户推荐的是新货精选,而右边活跃用户展示的是潮品推荐。另外还有部分的内容是随机算法推荐的。这意味着不同用户看到的内容是不同的,同一个用户不同时间看到的内容也有可能不同。

新业务

所以为了满足业务的需求,我们只能实时拉取用户数据并在服务端渲染后返回给客户端,也就是动态直出的方案。

但是动态直出方案存在几个比较明显的问题:

  1. 服务端实时拉取数据渲染导致白屏时间长,因为服务器要先实时拉取个人数据,然后进行渲染直出,这个耗时不可控;
  2. 首屏无法使用离线预推等缓存策略,因为每个用户看到的内容不一样,我们无法通过静态直出的方式那样把Html全部发布到CDN;

虽然动态直出方案下,页面首屏无法通过离线预推等方式进行加载优化,但前面优化积累的经验给我们提供了思路:要优化白屏问题,核心还是得从提升资源加载速度方向入手。所以我们重点在资源加载方面进行了深度优化。

并行加载

首先在加载流程方面,我们发现这里WebView访问依然是串行的, WebView要等终端初始化完成之后,才发起请求。虽然终端耗时优化了不少,但是从外网的统计数据来看,终端初始化还是存在几百毫秒的耗时,而这段时间内网络是在空等的。

串行

因此性能上不够极致,我们优化代码,这两个操作并行处理,流程改为:

并行

并行处理后速度有所改善,但我们发现在某些场景下,终端初始化比较快,但数据没有完成返回,这意味着内核在空等,而内核是支持边加载边渲染的,我们在并行的同时,能否也利用内核的这个特性呢?

于是我们加入了一个中间层来桥接内核和数据,内部称为流式拦截:

桥接流

  1. 启动子线程请求页面主资源,子线程中不断讲网络数据读取到内存中,也就是网络流(NetStream)和内存流(MemStream)之间的转换;
  2. 当WebView初始化完成的时候,提供一个中间层BridgeStream来连接WebView和数据流;
  3. 当WebView读取数据的时候,中间层BridgeStream会先把内存的数据读取返回后,再继续读取网络的数据。

通过这种桥接流的方式,整个内核无需等待,继续做到边加载边解析。这种并行的方式让首屏的速度优化15%以上,进一步提升了页面加载速度。

动态缓存

通过并行加载,我们极大地提升了WebView请求的速度,但是在弱网络场景下白屏时间还是非常长,用户体验非常糟糕。于是我们在思考,是否能够将用户的已经加载的页面内容缓存下来,等用户下此点击页面的时候,我们先加载展示页面缓存,第一时间让用户看到内容,然后同时去请求新的页面数据,等新的页面数据拉取下来之后,我们再重新加载一遍即可。

动态缓存

保存页面内容这个工作很简单,因为现在我们资源读取都是通过中间层BridgeStream来管理的,只需要将整个读取的内容缓存下来即可。
于是我们就按动态缓存这种方案去实现了,但很快就发现了问题。用户打开页面之后,先是看到历史页面,等用户准备去操作的时候,突然页面白闪一下,重新加载了一遍,这种体验非常差,特别在一些低端机器上,这个白闪的过程太明显,非常影响体验,这是用户和产品经理都不能接受的。于是我们在思考,能否只做局部的刷新,仅刷新变化的元素呢?

通过分析,我们发现同一个用户的页面,大部分数据都是不变的,经常变化的只有少量数据,于是我们提出了模板(template)和数据块(data)的概念:页面中经常变化的数据我们称为数据块,除了数据块之外的数据称为模板。

页面分离

我们将整个页面html通过VasSonic标签进行划分,包裹在标签中的内容为data,标签外的内容为模版。

页面规范

首先我们对Html内容进行了扩展,通过代码注释的方式,增加了“sonicdiff-xxx”来标注一个数据块的开始与结束。
而模板就是将数据块抠掉之后的Html,然后通过{albums}来表示这个是一个数据块占位。
数据就是JSON格式,直接Key-Value。
当然,为了完美地兼容Html,我们对协议头部进行了扩展,比如增加accept-diff来标注是否支持增量更新、template-tag来标注模板的md5是多少等。OK,有了上面这个规则或者公式后,我们就可以实现增量更新了。

请求规范约定

VasSonic为了支持区分客户端是否支持增量更新等能力,对头部字段进行了扩展

字段说明请求头(Y/N)响应头(Y/N)
accept-diff表示终端是否支持VasSonic模式,true为支持,否则不支持YN
If-none-match本地缓存的etag,给服务端判断是否命中304YN
etag页面内容的唯一标识(哈希值)NY
template-tag模版唯一标识(哈希值),客户端使用本地校验 或 服务端使用判断是模板有变更YY
template-change标记模版是否变更,客户端使用NY
cache-offline客户端端使用,根据不同类型进行不同行为NY

cache-offline字段说明

字段说明
true缓存到磁盘并展示返回内容
false展示返回内容,无需缓存到磁盘
store缓存到磁盘,如果已经加载缓存,则下次加载,否则展示返回内容
http容灾字段,如果http表示终端六个小时之内不会采用sonic请求该URL

模式介绍

VasSonic根据本地是否有缓存以及本地缓存数据跟服务器数据的差异情况分为以下四种模式。

模式说明条件
首次加载本地没有缓存,即第一次加载页面etag为空值或template_tag为空值
完全缓存本地有缓存,且缓存内容跟服务器内容完全一样etag一致
数据更新本地有缓存,本地模版内容跟服务器模版内容一样,但数据块有变化etag不一致 且 template_tag一致
模版更新本地有缓存,缓存的模版内容跟服务器的模版内容不一样etag不一致 且 template_tag不一致

首次加载

我们会在请求头部带上支持accept-diff为true和sdk版本号等标识着首次加载的信息。当请求返回后,VasSonic会在延迟几秒后(避免激烈IO竞争)将页面抽离成模板和数据并保存到本地。此时终端缓存目录下,该页面将对应三个缓存文件xxx.html、xxx.template、xxx.data,其中xxx是该页面的唯一标识(即sonicSessionId)。

对于页面非首次加载场景,VasSonic优先加载本地缓存, 同时我们会在请求头部带上当前缓存和模板的md5,后台进行模板md5对比之后,分为以下几种情况:

非首次加载之完全缓存

本地有缓存,且缓存内容跟服务器内容完全一样.

非首次加载之增量数据

增量数据

如果模板发现没有变化,那么会在响应头部返回template-change=false,同时响应包体返回的数据不再是完整的html,而是一段JSON数据,及全部的数据块。我们现在需要跟本地数据进行差分,找出真正的增量数据,如上图中,后台返回了N个数据,实际上仅有一个数据是有变化的,那么我们仅需要将这个变化的数据提交到页面即可。一般场景下,这个差异的数据比全部数据要小很多。如果页面拆分数据得更细,那么页面的变动就更小,这个取决于前端同学对数据块的细化程度。

获得变化数据块(diff_data)后,客户端只需要通知页面页面设置的回调接口(getDiffDataCallback)进行界面元素更新即可。这里javascript的通信方式也可以自由定义(可以使用webview标准的javascript通信方式,也可以使用伪协议的方式),只要页面跟终端协商一致就可以。
提交增量

对于数据更新这种场景,终端还会将新的数据和模板拼接成为新的页面,保持缓存最新。当终端初始化比较慢的时候,WebView去加载缓存的时候,这个页面可能已经是最新的了,连数据刷新都不需要。

非首次加载之模板更新

与数据更新模式不一样,由于业务需求,页面的模板会发生更改。当终端在获取到新的模板和数据后,本地在子线程中进行合并,生成一个新的缓存,然后回调通知终端,刷新WebView来加载新的缓存。

我们来看一下最终的流程图,跟动态缓存对比,有不少细节优化:

整体流程

我们从第2步开始,SonicSession首先会去读取缓存。会抛个消息通知WebView读取缓存,如果Webview已经准备好,则直接加载缓存,如果没有,则缓存先放在内存里面。同时SonicSession也会带上模板等信息到后台拉取新的内容,后台经过Sonic-Diff之后,会返回新的数据。SonicSession拿到新的数据后,首先会跟本地数据进行Diff,如果发现WebView已经加载缓存,则直接提交增量数据给页面。否则继续拼接最新的页面,替换掉内存里面的缓存,同时保存到本地。这个时候WebView如果Ready,则直接进行第5步load最新的内容即可。

效果统计

效果统计

这个是我们外网的统计数据。在数据更新模式下,首屏的耗时在1s左右,相比普通的动态直出,优化了50%以上。模板更新这个会比首次高,是因为加载了两次页面,不过从模式占比上来看,我们大部分页面都是数据更新。针对模板更新这种耗时比较高的情况,前面优化积累的经验给我们提供了思路,核心还是从提前获取资源方向入手,因此我们优先考虑如何预加载模板更新。

预加载

实际上整个SonicSession在没有WebView的情况下,也是可以独立完成所有逻辑的,当用户点击页面的时候,我们在将WebView和SonicSession绑定起来即可。于是我们支持了两种预加载的模式,一种是通过后台push的方式,来提前获取数据。还有一种就是JSAPI,页面可以调用JSAPI来预加载用户可能操作的下一个页面。通过这两种方式,我们可以把需要的增量更新数据提前拉取回来
预加载

效果对比

Pic 1: 没有使用VasSonicPic 2: 使用VasSonic
default modeVasSonic mode

展望未来

开源只是故事的开始,我们仍会持续对 VasSonic 做改进,包括更易用的接口、更好的性能、更高的可靠性,同时快速响应解决开源后的issue和PR。这些改进最终也会原封不动地在手Q内使用,这一切都是为了更快的WebView加载速度。

Talk is cheap,read the fucking code. If you are interested in VasSonic, don't forget to STAR VasSonic.
Thank you for reading ~

查看原文

赞 97 收藏 346 评论 22

Dophin 关注了用户 · 2017-08-19

司徒正美 @situzhengmei

穿梭于二次元与二进制间的魔法师( ̄(工) ̄) 凸ส้้้้้้้้้้้้้้้้้้้้้้้้้้้้้้้้้้้้้

关注 2146

Dophin 赞了文章 · 2017-08-15

送给大家一个好看的简历神器

很多人看到里边有好看的东西就习惯性的点进来看看,还一边点一边想 —— 好看的简历我见多了,你这个又能好看到哪里去。我想差不多可以:

哪里

因为最近有在准备简历,就习惯性的找一找有没有现成的简历模板。结果全是付费的,丑的收5块,稍微讲究一点的就差不多要10块钱了,这让一个普通家庭出身的年轻人怎么负担得起。
于是就产生了写一个简历模板的想法,后来就有了这个轻量的简历神器。

vue-resume

地址:https://luosijie.github.io/vue-resume/#/

源码:https://github.com/luosijie/vue-resume

使用方法

简历模板的操作可以说很简单,基本上把握住这么几个原则

  1. 想改哪里点哪里
  2. 右键删除
  3. 加号增加

应用场景

简历作为图片下载后,可以

  1. 直接作为招聘网站的附件简历
  2. 打印出来:实际效果稍微有点模糊,不过在可接受范围

不足之处

写这个的初衷是做一个便捷好用的简历模板,但我觉得距离实现这个目标还有一段距离。目前还有以下缺陷

  1. 不能选择模板样式
  2. 不支持多页简历
  3. 不支持转换为PDF

功能实现

这是一个基于Vue的项目,从最后实现来看,本身没什么技术难点。不过也要考虑实际用户需求和应用场景,然后将这些和 自己的技术水平愿意投入的时间成本 做一个平衡。

Logo设计

Logo设计无关技术,只不过项目无论大小,我感觉有一个Logo才完整。不过关于Logo设计,大多数人是不太懂的, 我自己总结了一条规律:只要你设计的Logo让人一眼没看懂,基本上就成功了一半了。 就好像你看出下面的Logo是字母 “V”“R” 的结合体吗。

Logo

下面是Logo的设计过程

Logo_process

功能分析

这个项目最基本的单元是 图片文字 然后组成各个 list组件 ,包括 About me, Skill, Education, Working Experience等。

图片文字组件

图片和文字只要实现可编辑功能就可以了。

文字

<p contenteditable="true">
      Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris blandit metus in libero rutrum congue aliquam eu libero. Donec tristique est pharetra fringilla sollicitudin. Etiam eu ipsum vitae nulla tincidunt scelerisque semper id arcu. Phasellus quam tellus, laoreet id felis a, dignissim facilisis orci. Mauris feugiat vulputate quam quis tincidunt. In eleifend augue eu tristique bibendum. Donec gravida, eros sed iaculis iaculis, magna est finibus tortor, ultricies accumsan diam lorem non neque.
</p>

图片

edit-image.vue

基本上就是将本地图片上传并转为base64格式

<template>
  <div class="edit-image" :style="{ width: width + 'px', height: height + 'px'}">
    <img :data-original="imgSrc" alt="image" :class="{ circle: isCircle }">
    <input type="file" accept="image/gif,image/jpeg,image/jpg,image/png" @change="changeImage">
  </div>
</template>
<script>
  export default {
    name: 'EditImage',
    props: {
     ...
    },
    data: function () {
      return {
        imgSrc: this.src
      }
    },
    methods: {
      changeImage: function (evt) {
        let _this = this
        let reader = new FileReader()
        let file = evt.target.files[0]
        reader.readAsDataURL(file)
        reader.onload = function (evt) {
          _this.imgSrc = evt.target.result
        }
      }
    }
  }
</script>

List组件

context-list.vue

List-item组件

List组件要实现 About me, Skill, Education, Working Experience 这些组件的通用功能。也就是:

  1. 标题
  2. 内容
  3. 增加条目
<template>
  <div class="context-list" :class="{ 'icon-margin-bottom': icon }">
    <div class="list-heading" :class="{ 'icon-class': icon }">
      <div class="title">
        <EditImage v-if="icon" :data-original="icon" height="36" width="36" class="img"></EditImage>
        <h2 class="title" contenteditable="true">{{title}}</h2>
      </div>
      <button class="add" @click="add" :class="{ 'icon-margin-right': icon }">+</button>
    </div>
    // 实现内容功能
    <ul id="luo">
      <ListItemAbout v-if="title == 'About me'" v-for="item in arry" :key="item.id"></ListItemAbout>
      <ListItemSkill v-if="title == 'Skill'" v-for="item in arry" :key="item.id"></ListItemSkill>
      <ListItemEducation v-if="title == 'Education'" v-for="item in arry" :key="item.id"></ListItemEducation>
      <ListItemExperience v-if="title == 'Working Experience'" v-for="item in arry" :key="item.id"></ListItemExperience>
      <ListItemInfo v-if="icon" v-for="item in arry" :key="item.id"></ListItemInfo>
      <slot name="listItem"></slot>
    </ul>
  </div>
</template>
<script>
  ...
  export default {
    name: 'ContextList',
    components: {
      EditImage,
      ListItemAbout,
      ListItemSkill,
      ListItemEducation,
      ListItemExperience,
      ListItemInfo
    },
    props: {
      // 实现标题功能
      title: {
        type: String,
        default: 'Title'
      },
      icon: {
        type: String,
        default: ''
      }
    },
    data: function () {
      return {
        arry: []
      }
    },
    methods: {
      showAdd: function () {
        this.add = true
      },
      // 实现条目增加功能
      add: function () {
        this.arry.push(1)
      }
    }
  }
</script>
<style>
  ...
</style>

List-item组件

list-item.vue

List-item组件

List-item组件 主要实现每个条目的删除功能, 然后不同类型的条目在 list-Item组件 的基础上定义各自的内容

<template>
  <li class="list-item" @contextmenu.prevent="showControl" v-if="listItem">
    <slot></slot>
    <div v-if="listControl" class="list-control">
      <span @click="deleteControl">delete</span>
      <span @click="cancelControl">cancel</span>
    </div>
  </li>
</template>
<script>
  export default {
    name: 'ListItem',
    data: function () {
      return {
        listControl: false,
        listItem: true
      }
    },
    methods: {
      // 显示控件
      showControl: function () {
        this.listControl = true
      },
      // 取消操作
      cancelControl: function () {
        this.listControl = false
      },
      // 删除控件
      deleteControl: function () {
        this.listItem = false
      }
    }
  }
</script>

先这样了 欢迎star

查看原文

赞 79 收藏 487 评论 32

Dophin 赞了回答 · 2017-08-11

在不同浏览器上用svg效果不同,如何解决?

是这样的,你这个的svg图片里面,应该不是用path在画这两个字母,而是用的文字的方式。这样,当系统没有某种字体的时候就会出现这种情况。
你可以尝试用svg在线编辑器把文字用path表示出来,这样即使机器上面没相应字体也不怕。

关注 1 回答 3

Dophin 赞了回答 · 2016-12-16

解决怎样正确使用rem、em与px

我觉得不能一概而论,如果你需要在PC上显示大字,而手机上显示小字的时候,你就用rem,但如果你需要在PC上也显示小字,手机上也显示小字的时候,就直接用px就好了,比如通常正文的字体大小不论在手机上还是PC上都是14px,如果PC上显示14px,而手机上等比例缩放成7px的话,那字就小的没法看了,所以像这种情况就不要用rem,而统一用14px就好了。

关注 9 回答 4

Dophin 赞了回答 · 2016-12-16

解决怎样正确使用rem、em与px

rem 是相对于根元素的字体大小,em 是相对于父级元素的字体大小,px 是直接设置像素大小

一般来说,如果需要统一字体大小,建议用 rem,但有些局部,相对动态需要根据父元素字体来设置大小的,会选用 em。另外还有一些比较特殊的情况,比如需要与固定像素的图片配合布局等,那就要用到 px 了。

具体用哪个需要根据实际情况而定,完全没必要统一。必要的情况下甚至需要按响应式设计。

关注 9 回答 4

Dophin 赞了文章 · 2016-04-20

Vue.js 的一些资源索引

Vue.js 是刚出来的一个轻型的 MVVM 框架, 借鉴了现有的各种框架,
官网 http://vuejs.org/
Github https://github.com/yyx990803/vue

作者个人网站挺漂亮的 http://evanyou.me/
作者微博 @尤小右 http://weibo.com/arttechdesign
作者是 Google 员工, 目前 Vue 已经在 Google Creative Lab 一些项目尝试

按 Commits 去年 8 月已经开始了项目:
https://github.com/yyx990803/vue/graphs/contributors
项目主页是今年 2 月上线的, 作者记录了上线一周的情况:
http://blog.evanyou.me/2014/02/11/first-week-of-launching-an-oss-project/

文档主要是在官网上, 非常清晰, 目前还是比较简短的
另外在"前端乱炖"上作者通过专栏更新了不少的教程, 中文的哦:
http://www.html-js.com/article/user/1152

按文档说的, Vue 只是一个用来组合的前端模块, 不是完整的框架,
但也提到 Router 和 Resource 层的模块在计划中
作者比较推荐 Component 这种管理前端模块的方式
而 Vue 其实提供了多种前端模块化方式的支持

TodoMVC 上更新了 Vue 写的 Todo 的例子,
加上注释 130+sloc , 双向绑定, 直接的 JS Object 操作, 比较清晰
https://github.com/tastejs/todomvc/pull/825
http://todomvc.com/labs/architecture-examples/vue/
这里提到了和 Ractive 的区别, 概括得比较好:

AngularJS was too heavy and intrusive for that purpose; Backbone simply doesn't offer much help in the view layer. Ractive.js was a valid choice, but it wasn't composable and we do like Anguler's POJO models. The result is Vue.js, which is simpler, faster, uses POJO by default, and offers composable ViewModels.

Hacker News 和 Reddit 上的讨论比较少,, 其中一句话让我想到 Backbone:
https://news.ycombinator.com/item?id=7169288

It also does not include routing/ajax/REST resource parts and focuses on the interface only. It is designed to be module ecosystem friendly (e.g. Component/Browserify) so you can easily leverage other libraries to fill in the missing pieces.

Vue 目前的版本是 0.8.6 , 计划中 0.9 会增强动画, 后边还有一些
看 Roadmap: https://github.com/yyx990803/vue/issues/78


返回博客首页: http://blog.tiye.me

查看原文

赞 10 收藏 89 评论 8

Dophin 赞了回答 · 2016-04-20

解决配置webpack-dev-server失败报错,求大侠路见不平

你应该设置 output.path 是一个绝对路径

output: {
        path: __dirname + '/assets/',
        publicPath: './assets/',
        filename: 'bundle.js'
    },

关注 5 回答 4

认证与成就

  • 获得 28 次点赞
  • 获得 241 枚徽章 获得 11 枚金徽章, 获得 92 枚银徽章, 获得 138 枚铜徽章

擅长技能
编辑

(゚∀゚ )
暂时没有

开源项目 & 著作
编辑

(゚∀゚ )
暂时没有

注册于 2014-05-25
个人主页被 1.2k 人浏览