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

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

关注 2153

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

Dophin 回答了问题 · 2016-04-15

解决webpack require绝对路径

关注 3 回答 3

Dophin 赞了问题 · 2016-04-12

webstorm 11打开以后底部总是出现一个不停运行的进程,显示scanning files to index

请教大神们是什么原因导致的?

关注 10 回答 7

Dophin 关注了问题 · 2016-04-12

webstorm 11打开以后底部总是出现一个不停运行的进程,显示scanning files to index

请教大神们是什么原因导致的?

关注 10 回答 7

Dophin 赞了回答 · 2016-04-02

解决cnpm使用出错

Node.js版本是多少?升级到v4或v5试试,或者直接用npm i --registry=https://registry.npm.taobao.org

关注 4 回答 3

Dophin 赞了回答 · 2015-11-21

解决在IE678下用什么方法解决伪类last-child?

有一种很简单的方法,纯CSS实现,支持IE7。
就是使用element+element即一个元素紧接着另一个元素选择器。因为last-child元素是没有紧跟这的元素的,element+element为CSS2选择器。
举个栗子:

<ul>
    <li>sample text</li>
    <li>sample text</li>
    <li>sample text</li>
    <li>sample text</li>
    <li>sample text</li>
    <li>sample text</li>
</ul>

我们希望在每条记录之间加上分隔符,一般会加border-bottom属性,但是最后一个<li>下面不能有,我们可以使用li+li选择符,改用border-top属性即可实现。

li+li{border-top: 1px dotted #999;}

效果图:
图片描述

关注 18 回答 13

Dophin 赞了回答 · 2015-11-21

解决在IE678下用什么方法解决伪类last-child?

仅仅是分隔的场景的话,可以用first-child啊,让first-child没有上/左边框就好

关注 18 回答 13

Dophin 赞了文章 · 2015-11-20

IE8下实现兼容rgba

昨天遇到一个问题,要实现一个背景透明的效果,用CSS3用rgba()就能实现,即

background: rgba(0,0,0,.5);

但是要兼容到IE8,就发现没有透明效果,因为IE8不支持rgba()函数。下面我们总结一下rgba()函数的含义。

rgba的含义,r代表red,g代表green,b代表blue,a代表透明度。红绿蓝是三原色,所有颜色都可以由这三种颜色拼合而成。比如rgba(0,0,0,.5)就是透明度为0.5的黑色。现代浏览器是支持rgba的,但是在IE8等古董级浏览器中是不支持rgba的,IE8只能勉强支持rgb()函数(即去掉了透明度,只能表示颜色)。

不过网上有这样的解法

background: rgb(0, 0, 0);    /*不支持rgba的浏览器*/
background: rgba(0,0,0,.5);  /*支持rgba的浏览器*/
filter:progid:DXImageTransform.Microsoft.gradient(startColorstr=#7f000000,endColorstr=#7f000000);    /*IE8支持*/

第二句话的意思就是当上一行的透明度不起作用的时候执行。这句话的意思本来是用来做渐变的,但是这个地方不需要渐变。所以两个颜色都设置成了相同的颜色。

解释下#7f000000,第一部分是#号后面的7f。是rgba透明度0.5的IEfilter值。从0.1到0.9每个数字对应一个IEfilter值。对应关系如下:
图片描述
第二部分是19后面的六位。这个是六进制的颜色值。要跟rgb函数中的取值相同。比如rgb(0,0,0,)对应#000;都是黑色。

到这里,rgba的用法就可以兼容IE8了。

最近看到,不直接在样式里面添加filter,而是利用<!--[if IE]><![endif]-->来为ie添加filter兼容。在过后又发现IE9同时支持RGBA和filter,导致两个重叠,透明效果变差,所以需要改为<!--[if lt IE 9]><![endif]-->,
具体做法如下:

// 此方法感觉略显麻烦,需要维护两个地方的代码。改为下方做法
<!--[if lt IE 9]>

   <style type="text/css">

   .color-block {
       background:transparent;
       filter:progid:DXImageTransform.Microsoft.gradient(startColorstr=#7f000000,endColorstr=#7f000000);
       zoom: 1;
    }

    </style>

<![endif]-->

IE9同时支持RGBA和filter,会导致两个重叠,透明效果异常。有网友评论,建议用 :root 来去除 ie9+ 浏览器的 filter,也是很不错的办法,在此改为以下做法

    .color-block {
        background: rgb(0, 0, 0);    /*不支持rgba的浏览器*/
        background: rgba(0,0,0,.5);  /*支持rgba的浏览器*/
        filter:progid:DXImageTransform.Microsoft.gradient(startColorstr=#7f000000,endColorstr=#7f000000); 
    }
    
    :root .color-block {
        filter: none;
    }
查看原文

赞 12 收藏 43 评论 5

Dophin 赞了文章 · 2015-11-18

web端文件上传功能的思考

概述

文件上传是一个很常见的需求,实现文件上传的技术也很多。下面就谈谈一些常见的上传技术以及它们的优劣。

传统表单上传

传统表单文件上传估计是运用最广泛和最简单的技术了,说它简单是因为只要指定表单的enctypemultipart/form-data,就行了。简单可靠所以被运用的广泛。传统表单上传示例如下所示:

<form action="test.php" target="" method="post" enctype="multipart/form-data">
    <input type="file" name="file" id="file" />
    <input type="submit" id="J_submit"  value="submit" />
</form>

表单中的action参数指的是后台处理上传文件的接口。target参数规定了规定在何处打开 action URL,常见的参数有_blank、_self、__top或者指定的iframe。method参数规定了表单提交的方式,这里只能使用**post**方式提交,而不能用**get**方式。最后enctype的参数规定了在发送到服务器之前应该如何对表单数据进行编码方式,常见的参数有**application/x-www-form-urlencoded**、**text/plain**和示例中的**multipart/form-data**,因为上传的文件都是非纯文本传输,所以指定的类型必须只能是**multipart/form-data**。

这种方式的局限性是不能批量处理,而且表单上传是同步的,表单已提交,页面就会刷新。

第三方插件处理文件上传

如果你想实现异步提交文件,且可以进行批量处理文件,那么只能通过第三方插件来实现了,第三方插件实现技术有很多,比如Flash、java applet、ActiveX等技术,其中Flash技术是运用最广泛、最成熟的一种方案。

不过第三方插件已经不属于前端开发范围了,所以不会详细细说。不过到时可以我常用的FLash上传插件有SWFupload、uploadfile、百度的webupload等。

第三方插件的好处是能做批量处理、异步提交。缺点也是显而易见的是要浏览器支持。

模拟异步上传文件

说到异步,可能有人会说,能不能通过ajax来实现文件的异步上传呢?想法很美好,现实却很残酷,ajax与后端通信 只能传送字符串,是无法传递实体文件的,所以用ajax是无法实现直接文件上传的。不过我们却可以在页面“埋”一个隐藏iframe来模拟文本的异步提交。

具体的原理是,在点击提交按钮时,动态的生成一个隐藏iframe加入到页面上,并且 将form 的 target指向该隐藏iframe,服务端就接收到上传的file文件,并进行相应的操作,然后将返回结果返回到隐藏的iframe里面,这时我们可以与后端开发约定返回结果的格式,可以是json格式,便于我们前端操作,然后前端部分就可以用iframe.contentWindow.document.body.innerHTML来获取后端返回的结果,进行相应的parseJSON处理,即可像处理ajax返回的json一样,处理数据。

示例代码如下所示:

/**
 * 模拟ajax无刷新文件上传
 */
var fileUpLoad = function(config) {

    var ifr = null,
        fm = null,
        defConfig = {
            submitBtn: $('#J_submit'), //提交按钮
            complete: function(response) {}, //上传成功后回调
            beforeUpLoad: function() {}, //点击提交未上传时回调
            afterUpLoad: function() {} //点击提交上传后回调
        };

    //静态变量
    var IFRAME_NAME = 'fileUpLoadIframe';

    //配置
    config = $.extend(defConfig, config);

    //绑定submit事件
    config.submitBtn.bind('click', function(e){
        e.preventDefault();

        //点击提交前触发事件, 函数返回false可阻止提交表单,用于file为空判断
        if (config.beforeUpLoad.call(this) === false) {
            return;
        }

        //生成一个隐藏iframe,并设置form的target为该iframe,模拟ajax效果
        ifr = $('<iframe name="'+ IFRAME_NAME +'" id="'+ IFRAME_NAME +'" style="display:none;"></iframe>');
        fm = this.form;

        ifr.appendTo($('body'));
        fm.target = IFRAME_NAME; //target目标设为ifr

        //上传完毕iframe onload事件
        ifr.load(function(){
            var response = this.contentWindow.document.body.innerHTML;

            config.complete.call(this, response);
            ifr.remove();
            ifr = null; //清除引用
        });

        fm.submit(); //提交表单

        //点击提交后触发事件
        config.afterUpLoad.call(this);

    });

};

调用方式如下:

   fileUpLoad({
    submitBtn: $('#J_submit'),
    complete: function(response){ //上传成功后处理回调
        var d = $.parseJSON(response);

        alert('返回成功')
        console.log(d);
    },
    beforeUpLoad: function() {
        alert('上传前');
    },
    afterUpLoad: function() {
        alert('上传后');
    }
}); 

这种方式的好处是,虽然不是异步提交,但是给人的感觉好像是通过异步方式上传了文件。缺点是依然不能进行批量处理文件。

使用FormData对象发送文件

XMLHttpRequest Level 2添加了一个新的接口FormData.利用FormData对象,我们可以通过JavaScript用一些键值对来模拟一系列表单控件,我们还可以使用XMLHttpRequest的send()方法来异步的提交这个"表单".比起普通的ajax,使用FormData的最大优点就是我们可以异步上传一个二进制文件.其兼容性如下所示:

图片描述

有图可知,这个接口兼容性在IE上表现的并不是很好,最新只支持IE10+,不过IE10+的市场份额现在还不是很多,如果你考虑兼容性的话,建议不要使用这个接口。

那怎样通过FormData上传文件呢。可以参考下面的代码。
html结构:

<form enctype="multipart/form-data" method="post" name="fileinfo">
  <label>Your email address:</label>
  <input type="email" autocomplete="on" autofocus name="userid" placeholder="email" required size="32" maxlength="64" /><br />
  <label>Custom file label:</label>
  <input type="text" name="filelabel" size="12" maxlength="32" /><br />
  <label>File to stash:</label>
  <input type="file" name="file" required />
</form>
<div id="output"></div>
<a href="javascript:sendForm()">Stash the file!</a>   

脚本代码:

function sendForm() {
  var oOutput = document.getElementById("output");
  var oData = new FormData(document.forms.namedItem("fileinfo"));

  oData.append("CustomField", "This is some extra data");

  var oReq = new XMLHttpRequest();
  oReq.open("POST", "stash.php", true);
  oReq.onload = function(oEvent) {
    if (oReq.status == 200) {
      oOutput.innerHTML = "Uploaded!";
    } else {
      oOutput.innerHTML = "Error " + oReq.status + " occurred uploading your file.<br \/>";
    }
  };

  oReq.send(oData);
}    

这个属性弥补了ajax1不能异步上传文件的不足。

帮助文档

模拟AJAX无刷新的文件上传功能
页面无刷新ajax上传文件--模拟iframe,超简单
为什么上传文件的表单里要加个属性enctype
FormData接口对象的介绍
使用FormData对象

ps:涉及文章侵权,请邮件告知。

查看原文

赞 5 收藏 24 评论 4

Dophin 赞了文章 · 2015-11-18

iframe实现无刷新上传文件

HTML代码 index.html

<html>
   <body>
      <form action="upload.jsp" id="form1" name="form1" encType="multipart/form-data"method="post" target="hidden_frame" >
         <input type="file" id="file" name="file" style="width:450">
         <INPUT type="submit" value="上传文件"><span id="msg"></span>
         <br>
         <font color="red">支持JPG,JPEG,GIF,BMP,SWF,RMVB,RM,AVI文件的上传</font>
         <iframe name='hidden_frame' id="hidden_frame" style='display:none'></iframe>
      </form>
   </body>
</html>

<script type="text/javascript">
function callback(msg) {
   document.getElementById("file").outerHTML = document.getElementById("file").outerHTML; 
   document.getElementById("msg").innerHTML = "<font color=red>"+msg+"</font>"; 
} 
</script>

JAVA代码 upload.jsp

<%@ page language="java" contentType="text/html; charset=gb2312" %>
<%@ page import="com.jspsmart.upload.SmartUpload"%>
<% 
//新建一个SmartUpload对象 
SmartUpload su = new SmartUpload();
//上传初始化 
su.initialize(pageContext);
// 设定上传限制 
//1.限制每个上传文件的最大长度。
su.setMaxFileSize(10000000);
//2.限制总上传数据的长度。
su.setTotalMaxFileSize(20000000);
//3.设定允许上传的文件(通过扩展名限制),仅允许doc,txt文件。
su.setAllowedFilesList("doc,txt,jpg,rar,mid,waw,mp3,gif");

boolean sign = true; 
//4.设定禁止上传的文件(通过扩展名限制),禁止上传带有exe,bat,jsp,htm,html扩展名的文件和没有扩展名的文件。 
try { 
    su.setDeniedFilesList("exe,bat,jsp,htm,html");
    //上传文件 
    su.upload(); 
    //将上传文件保存到指定目录 
    su.save("c:\\");
} catch (Exception e) {
    e.printStackTrace();
    sign = false;
}

if(sign==true) {
    out.println("<script>parent.callback('upload file success')</script>");
} else {
    out.println("<script>parent.callback('upload file error')</script>"); 
} 
%>

jar文件

在列表中找到jspsmartupload.jar(提取码:c3c3)并下载。

查看原文

收藏 3 评论 1

Dophin 赞了文章 · 2015-11-09

用npm-run自动化任务

自动构建javascript有不少好工具。不过其实很少有人知道,npm run命令就能很好地完成这一任务,配置起来也很简单。

npm

James Halliday在博客上分享了使用npm run自动化任务的一些经验:

script

npm 会在项目的 package.json 文件中寻找 scripts 区域,其中包括npm testnpm start等命令。

其实npm testnpm startnpm run testnpm run start的简写。事实上,你可以使用npm run来运行scripts里的任何条目。

使用npm run的方便之处在于,npm会自动把node_modules/.bin加入$PATH,这样你可以直接运行依赖程序和开发依赖程序,不用全局安装了。只要npm上的包提供命令行接口,你就可以直接使用它们,方便吧?当然,你总是可以自己写一个简单的小程序。

构建javascript

为了便于组织代码和利用npm上的包,写代码的时候往往使用module.exportsrequire()browserify可以将这些一起打包成单一的脚本。使用browserify很简单,只需在package.json中加入一个['build-js']条目,类似这样:

"build-js": "browserify browser/main.js > static/bundle.js"

如果是用于生产环境,还需要压缩一下。我们只需要将uglify-js加入devDependency,然后直接通过管道传递一下即可:

"build-js": "browserify browser/main.js | uglifyjs -mc > static/bundle.js"

监视 javascript

为了能在修改文件之后自动重新生成javascript文件,只需将上面的browserify命令换成watchify并加上一些参数。

"watch-js": "watchify browser/main.js -o static/bundle.js -dv"

这里加了-d-v两个参数,这样就可以看到详细的调试信息。

构建CSS

cat就可以搞定:

"build-css": "cat static/pages/*.css tabs/*/*.css > static/bundle.css"

监视CSS

和上面用 watchify 监视 javascript 类似,我们用catw监视CSS文件的改动:

"watch-css": "catw static/pages/*.css tabs/*/*.css -o static/bundle.css -v"

序列化子任务

很简单,npm run每个子任务,然后用&&连接起来就成。

"build": "npm run build-js && npm run build-css"

并行子任务

类似地,我们用&并行子任务:

"watch": "npm run watch-js & npm run watch-css"

完整的package.json例子

将上面提到的内容组合起来,package.json大致就是这个样子:

{
  "name": "my-silly-app",
  "version": "1.2.3",
  "private": true,
  "dependencies": {
    "browserify": "~2.35.2",
    "uglifyjs": "~2.3.6"
  },
  "devDependencies": {
    "watchify": "~0.1.0",
    "catw": "~0.0.1",
    "tap": "~0.4.4"
  },
  "scripts": {
    "build-js": "browserify browser/main.js | uglifyjs -mc > static/bundle.js",
    "build-css": "cat static/pages/*.css tabs/*/*.css",
    "build": "npm run build-js && npm run build-css",
    "watch-js": "watchify browser/main.js -o static/bundle.js -dv",
    "watch-css": "catw static/pages/*.css tabs/*/*.css -o static/bundle.css -v",
    "watch": "npm run watch-js & npm run watch-css",
    "start": "node server.js",
    "test": "tap test/*.js"
  }
}

生产环境下,只需运行npm run build。如果是本地开发,就用npm run watch

你也可以坐下扩展。比方说,如果你希望在运行start前先运行build,那么你只需写上这么一行:

"start": "npm run build && node server.js"

也许你想同时启动watcher?

"start-dev": "npm run watch & npm start"

当事情变得非常复杂的时候

如果你发现在单个scripts条目中塞了一大堆命令,那你可以考虑重构一下,把一些命令放到别的地方,比如/bin

你可以用任何语言编写这个脚本,比如bashnodeperl。只需要在脚本上加上合适的#!行。还有,别忘了chmod +x

#!/bin/bash
(cd site/main; browserify browser/main.js | uglifyjs -mc > static/bundle.js)
(cd site/xyz; browserify browser.js > static/bundle.js)
"build-js": "bin/build.sh"

Windows

你可能会吃惊的是,相当多的类bash语法可以在Windows上工作。不过我们至少还需要让;&可以正常工作。

James Halliday分享过一些在Windows兼容的经验,这些经验也适用于本文的主题,可以参考。此外要推荐下win-bash,这是一个很方便的Windows平台上的bash实现。

总结

James Halliday希望这个使用npm run的方式能吸引一部人对现有的前端自动化任务工具不满意的人。James Halliday比较偏好unix体系下的那些学习曲线陡峭的工具,比如git,或者类似 npm 这种在 bash 的基础上提供极简界面的工具。也就是说,不需要很多仪式化操作和配合的工具。非常简单的工具,已经足够胜任通常的任务。

如果你对npm run风格不感冒。你也许可以考虑下Makefiles,一个稳定而简单,不过多少有点怪异的替代品。

原文 task automation with npm run
编译 SegmentFault

查看原文

赞 22 收藏 130 评论 7