zoukankan      html  css  js  c++  java
  • 实现前端资源增量式更新的一种思路

    “增量式更新”简单地说就是在版本更新的时候不需要重新加载资源,只需要加载一段很小的 diff 信息,然后合并到当前资源上,类似 git merge 的效果。

    “假设有一个超超超大型的Web项目,JS源代码压缩之后超过10MB(实际怎么可能会有这么大的嘛=。=),每次更新之后都需要用户重新加载JS是不可接受的,那么怎么样从工程的角度解决这种问题?”

    一开始立马想到了好几点解决方案,比如:

    1. 抽出基础的不常更新的模块作为长期缓存;
    2. 如果使用了 React 或者 Vue2.0 这样支持服务器端渲染的框架的话,就采用服务器端渲染然后再逐步分块加载 JS 的方法;
    3. 如果是 Hybrid 开发的话,可以考虑使用本地资源加载,类似“离线包”的想法(之前在腾讯实习的时候天天遇到这东西)。

    后来在面试官的引导下想到了一种“增量式更新”的解决方案,简单地说就是在版本更新的时候不需要重新加载资源,只需要加载一段很小的 diff 信息,然后合并到当前资源上,类似 git merge 的效果。

    1、用户端使用 LocalStorage 或者其它储存方案,存储一份原始代码+时间戳:

    { 
     
        timeStamp: "20161026xxxxxx", 
     
        data: "aaabbbccc" 
     
    }  

    2、每次加载资源的时候向服务器发送这个时间戳;

    3、服务器从接受到时间戳中识别出客户端的版本,和最新的版本做一次 diff,返回两者的 diff 信息:

    diff("aaabbbccc", "aaagggccc"); 
     
    // 假设我们的diff信息这样表示: 
     
    // [3, "-3", "+ggg", 3]  

    4、客户端接收到这个 diff 信息之后,把本地资源和时间戳更新到最新,实现一次增量更新:

    mergeDiff("aaabbbccc", [3, "-3", "+ggg", 3]); 
     
    //=> "aaagggccc"  

    实践

    下面把这个方案中的核心思想实现一遍,简单地说就是实现 diff 和 mergeDiff 两个函数。

    今天找到了一个不错的 diff 算法:

    GitHub – kpdecker/jsdiff: A javascript text differencing implementation.

    我们只需要调用它的 diffChars 方法来对比两个字符串的差异:

    var oldStr = 'aaabbbccc'; 
     
    var newStr = 'aaagggccc'; 
     
    JsDiff.diffChars(oldStr, newStr); 
     
    //=> 
     
    //[ { count: 3, value: 'aaa' }, 
     
    //  { count: 3, added: undefined, removed: true, value: 'bbb' }, 
     
    //  { count: 3, added: true, removed: undefined, value: 'ggg' }, 
     
    //  { count: 3, value: 'ccc' } ]  

    上面的 diff 信息略有些冗余,我们可以自定义一种更简洁的表示方法来加速传输的速度:

    [3, "-3", "+ggg", 3] 

    整数代表无变化的字符数量,“-”开头的字符串代表被移除的字符数量,“+”开头的字符串代表新加入的字符。所以我们可以写一个 minimizeDiffInfo 函数:

    function minimizeDiffInfo(originalInfo){ 
     
        var result = originalInfo.map(info => { 
     
            if(info.added){ 
     
                return '+' + info.value; 
     
            } 
     
            if(info.removed){ 
     
                return '-' + info.count; 
     
            } 
     
            return info.count; 
     
        }); 
     
        return JSON.stringify(result); 
     
    } 
     
      
     
    var diffInfo = [ 
     
        { count: 3, value: 'aaa' }, 
     
        { count: 3, added: undefined, removed: true, value: 'bbb' }, 
     
        { count: 3, added: true, removed: undefined, value: 'ggg' }, 
     
        { count: 3, value: 'ccc' } 
     
    ]; 
     
    minimizeDiffInfo(diffInfo); 
     
    //=> '[3, "-3", "+ggg", 3]'  
    用户端接受到精简之后的 diff 信息,生成最新的资源:
    
    mergeDiff('aaabbbccc', '[3, "-3", "+ggg", 3]'); 
     
    //=> 'aaagggccc' 
     
      
     
    function mergeDiff(oldString, diffInfo){ 
     
        var newString = ''; 
     
        var diffInfo = JSON.parse(diffInfo); 
     
        var p = 0; 
     
        for(var i = 0; i < diffInfo.length; i++){ 
     
            var info = diffInfo[i]; 
     
            if(typeof(info) == 'number'){ 
     
                newString += oldString.slice(p, p + info); 
     
                p += info; 
     
                continue; 
     
            } 
     
            if(typeof(info) == 'string'){ 
     
                if(info[0] === '+'){ 
     
                    var addedString = info.slice(1, info.length); 
     
                    newString += addedString; 
     
                } 
     
                if(info[0] === '-'){ 
     
                    var removedCount = parseInt(info.slice(1, info.length)); 
     
                    p += removedCount; 
     
                } 
     
            } 
     
        } 
     
        return newString; 
     
    }  

    实际效果

    有兴趣的话可以直接运行这个:

    GitHub – starkwang/Incremental

    使用 create-react-app 这个小工具快速生成了一个 React 项目,随便改了两行代码,然后对比了一下build之后的新旧两个版本:

    var JsDiff = require('diff'); 
     
    var fs = require('fs'); 
     
      
     
    var newFile = fs.readFileSync('a.js', 'utf-8'); 
     
    var oldFile = fs.readFileSync('b.js', 'utf-8'); 
     
    console.log('New File Length: ', newFile.length); 
     
    console.log('Old File Length: ', oldFile.length); 
     
      
     
    var diffInfo  
     
    = getDiffInfo(JsDiff.diffChars(oldFile, newFile)); 
     
    console.log('diffInfo Length: ', diffInfo.length); 
     
    console.log(diffInfo); 
     
      
     
    var result = mergeDiff(oldFile, diffInfo); 
     
    console.log(result === newFile); 

    下面是结果:

    可以看到 build 之后的代码有 21w 多个字符(212KB),而 diff 信息却相当短小,只有151个字符,相比于重新加载新版本,缩小了1000多倍(当然我这里只改了两三行代码,小是自然的)。

    一些没涉及到的问题

    上面只是把核心的思路实现了一遍,实际工程中还有更多要考虑的东西:

    1、服务器不可能对每一次请求都重新计算一次 diff,所以必然要对 diff 信息做缓存;

    2、用户端持久化储存的实现方案,比如喜闻乐见的 LocalStorage、Indexed DB、Web SQL,或者使用 native app 提供的接口;

    3、容错、用户端和服务器端的一致性校对、强制刷新的实现。

  • 相关阅读:
    ACMer第7天Falling Ants
    贪心初步-FatMouse' Trade
    贪心初步-A
    ACM集训第二天
    asp.net中遍历套用母版页的页面的控件
    a 标签中调用js的几种方法
    笔记
    html控件和web控件
    ASP.NET中GUID类
    (转)常见邮件服务器(接收服务器和发送邮件服务器)地址
  • 原文地址:https://www.cnblogs.com/baixiaoxiao/p/11025247.html
Copyright © 2011-2022 走看看