AlloyTeam

AlloyTeam

Copyright © Tencent AlloyTeam. All Rights Reserved.
AlloyTeam 腾讯全端 AlloyTeam 团队 Blog
  • 首页
  • Web 开发
    • 前端资讯
    • HTML5
    • CSS3
    • JavaScript
    • Node.js
    • 移动 Web 开发
    • 用户体验设计
    • Web 前端优化
    • 资源工具
  • 移动开发
    • Android 开发
    • iOS 开发
    • 移动 Web 开发
  • Alloy 实验室
    • 作品
    • HTML5 游戏
  • 关于
    • 团队
    • Github
    • 留言
    • 友情链接
  • RSS
  • 使用 Xposed 强制 androidwebView 开启 debug 模式
    In Web开发 on 2015年04月01日 by TAT.fishineyuan view: 4,875
    0

    从 https://developer.chrome.com/devtools/docs/remote-debugging 我们可以知道在 android 4.4+可以通过在 apk 中使用下面的代码开启 webview 的 chrome 远程调试

    1
    2
    WebView.setWebContentsDebuggingEnabled(<span class="keyword">true</span>);
     

    但我们开发中接触的 apk 往往是第三方的,没谁会为我们开启 webContentsDebuggingEnabled。而 Xposed 能强制做到这一点

    Xposed

    Xposed 能够勾住 (Hook) Android 应用程序对象的方法,实现 AOP,一个简单的例子:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    <span class="keyword">public</span> <span class="keyword">class</span> WebViewHook <span class="keyword">implements</span> IXposedHookLoadPackage {
        <span class="comment">// handleLoadPackage 会在android加载每一个apk后执行</span>
        <span class="keyword">public</span> void handleLoadPackage(LoadPackageParam lpparam) throws Throwable {
            <span class="comment">// 可以从lpparam中获取当前apk的名字</span>
            <span class="keyword">if</span> (! lpparam.packageName.equals(<span class="string">"com.tencent.mobileqq"</span>)) {
                <span class="keyword">return</span>;
            }
            XposedBridge.log(<span class="string">"WebViewHook handleLoadPackage: "</span> + lpparam.packageName);
            <span class="comment">// 勾住 WebView 所有的构造器</span>
            XposedBridge.hookAllConstructors(WebView.<span class="keyword">class</span>, <span class="keyword">new</span> XC_MethodHook() {
                @Override
                <span class="keyword">protected</span> void beforeHookedMethod(MethodHookParam param) throws Throwable {
                    <span class="comment">// 打开webContentsDebuggingEnabled</span>
                    XposedHelpers.callStaticMethod(WebView.<span class="keyword">class</span>, <span class="string">"setWebContentsDebuggingEnabled"</span>, <span class="keyword">true</span>);
                    XposedBridge.log(<span class="string">"WebViewHook new WebView(): "</span> + packageName);
                }
            });
        }
    }
     

    上面的代码可以为 QQ 打开 WebView 的 webContentsDebuggingEnabled

    Xposed 工作原理可以参考文档: https://github.com/rovo89/XposedBridge/wiki/Development-tutorial

    没有详细的 API 页面, 因为 API 也就几个,可以查看源代码: https://github.com/rovo89/XposedBridge/tree/master/src/de/robv/android/xposed

    馋图

    拿来主义

    1、需要 android 4.4+ Root 手机

    2、安装 Xposed 框架

    3、已开启 QQ WebView 的 Apk: webviewdebughook.Apk

    继续阅读

  • 翻译:Node.js 十大常见的开发者错误
    In Node.js on 2015年04月01日 by TAT.polar view: 9,390
    0

    原文地址:http://www.toptal.com/nodejs/top-10-common-nodejs-developer-mistakes

    原文作者:MAHMUD RIDWAN

    转载此译文请注明原文及译文出处,如译文有翻译不当之处还请各位看官指出。

     

    自 Node.js 面世以来,它获得了大量的赞美和批判。这种争论会一直持续,短时间内都不会结束。而在这些争论中,我们常常会忽略掉所有语言和平台都是基于一些核心问题来批判的,就是我们怎么去使用这些平台。无论使用 Node.js 编写可靠的代码有多难,而编写高并发代码又是多么的简单,这个平台终究是有那么一段时间了,而且被用来创建了大量的健壮而又复杂的 web 服务。这些 web 服务不仅拥有良好的扩展性,而且通过在互联网上持续的时间证明了它们的健壮性。

     

    然而就像其它平台一样,Node.js 很容易令开发者犯错。这些错误有些会降低程序性能,有些则会导致 Node.js 不可用。在本文中,我们会看到 Node.js 新手常犯的十种错误,以及如何去避免它们。

    继续阅读

  • 简单玩转 manifest.json
    In Web开发 on 2015年03月31日 by TAT.huhu view: 7,127
    6

    回想最早年资源版本控制,是不是类似如下

    1
    2
    3
    <script src="a.js?t=20140404"></script>
    <script src="a.js?v=1.1.0"></script>
     

    这是一个最常见的古老的版本控制方式,简单直观易用,缺点就是我们常常会因为缓存因为覆盖造成发布外网时出现资源同步问题。对于没啥访问量的小型站点折腾折腾也就过了,如果项目很大访问很大,一但出现问题波及很广你自然少不了被领导被上级抓去捡肥皂的命运。

    继续阅读

  • 拥抱 sass,抛弃 compass
    In Web开发 on 2015年03月31日 by TAT.ycxu view: 1,180
    0

    为什么要用 sass

    在选择 sass 之前,我们先说下为什么要使用 CSS Preprocessor。

    大概两年前,CSS Preprocessor 其实没有这么热,而了解 sass,less,stylus 的人也还没那么多(当时三者占比 less 还是拥有绝对优势的),但很多时候就是那么 duang 的一下,然后改变就发生了,就如 html5&css3,仿佛一夜之间就遍地开花。当然这其中质变肯定是有道理值得去说道说道的。下面我们一起来对比下 css 和 CSS Preprocessor(以 sass 为例),了解下其中的优劣。

    CSS 无层级嵌套机制

    因为 css 无嵌套机制,所以造成层级方面的阅读及折叠方面极为不便,如下代码,使用 scss 就能更好的管理代码层级关系

    1
    2
    3
    4
    5
    6
    7
    8
    9
    <span class="comment">// css</span>
    .<span class="keyword">parent</span>{}
    .<span class="keyword">parent</span> .child{}
     
    <span class="comment">// scss</span>
    .<span class="keyword">parent</span>{
        .child{}
    }
     

    css 本身缺少变量机制

    举个最简单的例子,每个站点都有个主色,如果没有变量的话,我们只能每次使用都拷贝颜色,当然也有神人是可以把颜色的六位数记住,但多数肯定是记不住。下面以文本色及链接色为例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    <span class="comment">// css</span>
    body{
        color:<span class="comment">#333;</span>
    }
    a{
        color: <span class="comment">#188eee;</span>
    }
    .dark a{
        color: <span class="comment">#333;</span>
    }
    .dark a:hover{
        color: <span class="comment">#188eee;</span>
    }
     

    有了变量呢,那就简单了,直接定义一个变量,然后需要的时候调用变量即可:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    <span class="comment">// scss</span>
    <span class="variable">$textColor</span>: <span class="comment">#333 !default;</span>
    <span class="variable">$lickColor</span>: <span class="comment">#188eee !default;</span>
     
    body{
        color:<span class="variable">$textColor</span>;
    }
    a{
        color: <span class="variable">$lick</span>-color;
    }
    .dark a{
        color: <span class="variable">$textColor</span>;
    }
    .dark a:hover{
        color: <span class="variable">$lickColor</span>;
    }
     

    注:css 变量已经正在开发中,现在的火狐其实已经支持最新的 css 变量了,而且比所有的 CSS Preprocessor 都好用,感兴趣的同学可以去尝个鲜。

    @import 不是我们所期望的功能

    随着业务功能增加及复杂性增强,多人员合作及组件开发模式是必然的。而现有的 CSS 的 @import 与我们所要的 @import 不是一个概念。为了表示两者的区别,我们直接在 page.scss 中导入一个 css 文件和一个 scss 文件:

    page.scss

    1
    2
    3
    4
    5
    6
    @import <span class="string">"reset.css"</span>;
    @import <span class="string">"mod-a"</span>;
    p{
      background: <span class="comment">#0982c1;</span>
    }
     

    _mod-a.scss

    1
    2
    3
    4
    5
    6
    <span class="comment">//_mod-a.scss</span>
    <span class="comment">//-------------------------------</span>
    .hello {
      color: <span class="comment">#eee;</span>
    }
     

    最终编译出来的 page.css 文件:

    1
    2
    3
    4
    5
    6
    7
    8
    @import <span class="string">"reset.css"</span>;
    .hello {
      color: <span class="comment">#eee;</span>
    }
    p{
      background: <span class="comment">#0982c1;</span>
    }
     

    可以看到,@import "reset.css" 没有发生改变,而 moda-a 的 scss 文件则被合进了 page.css,这才是我们需要的结果,需要的时候调用想用的 scss 文件,然后最终合并到一个 css 文件中。

    对可重用的代码缺少重复使用机制

    css 对于相同或相似的代码,除了一遍遍的拷贝复制或组合申明之外,不可以定义一些规则或函数,去简单重复使用,如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    <span class="comment">// 组合申明</span>
    .center-block,
    .container{
        margin-left: auto;
        margin-right: auto;
    }
    .container{
        margin-bottom: <span class="number">20</span>px;
        width: <span class="number">1200</span>px;
    }
     
    <span class="comment">// 拷贝使用</span>
    .fixed-top{
        position: fixed;
        left: <span class="number">0</span>;
        right: <span class="number">0</span>;
        top: <span class="number">0</span>;
    }
    .fixed-bottom{
        position: fixed;
        left: <span class="number">0</span>;
        right: <span class="number">0</span>;
        bottom: <span class="number">0</span>;
    }
     

    而使用 scss 之后则如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    <span class="comment">// %,解析后组合申明样式</span>
    %center-block{
        margin-left: auto;
        margin-right: auto;
    }
     
    .center-block{
        @extend %center-block;
    }
    .container{
        @extend %center-block;      
        margin-bottom: <span class="number">20</span>px;
        width: <span class="number">1200</span>px;
    }
     
    <span class="comment">// @mixin, 解析后拷贝样式 </span>
    @mixin fixed(<span class="variable">$pos</span>: <span class="number">0</span>) {
        position: fixed;
        left: <span class="number">0</span>;
        right: <span class="number">0</span>;
        @<span class="keyword">if</span> <span class="variable">$pos</span> == bottom {
            bottom: <span class="number">0</span>;
        }
        @<span class="keyword">else</span> {
            top: <span class="variable">$pos</span>;
        }
    }
    .fixed-top{
        @<span class="keyword">include</span> fixed;
    }
    .fixed-bottom{
        @<span class="keyword">include</span> fixed(bottom);
    }
     

    除此之外,CSS Preprocessor 还有条件判断,循环等高大上的东西,这些都是 css 目前不具备的,当然 CSS 也正在一步步变化,为更好而革新,相信在不久的将来,CSS 也会 duang 的一下,给你眼前一亮。

    说完为什么要选择 CSS Preprocessor,接下来我们说下为什么选择 sass 吧。

    其实几个 CSS Preprocessor 的基本功能都差不多,都能胜任日常的开发,但如果是做基础的 css 框架及组件开发的话还是 sass 略强点。

    1. sass 的语法不容易混淆,@mixin,%,@function 定义各种用途,很清楚明白
    2. 原先被人诟病的 sass 的变量机制也完善了,!default 变量和!global 变量双剑合璧,解决一切所需。
    3. 自从 map 类型数据出现后,sass 处理数据方面更加突出。
    4. sass 的函数多多,应有尽有,各种选择器函数,颜色函数,判断条件,循环函数等,是你构建基础框架的得力助手

    总之,就目前来说 sass 是个很好的选择。当然也许有一天 less 或其他的会超越它,或者直接到了某一天 css 本身就有了这些功能,根本不需要这些 CSS Preprocessor。而所有这些都是有极可能的。

    为什么要抛弃 compass

    用雕爷的一个字评价 compass 就是——学习成本比较高,更新太慢,东西太多,实用的却很少。

    作为以 sass 为基础构建的 css 框架,compass 还是非常优秀的,其思想及设计都值得借鉴。但是鉴于它的更新频率及里面的 css 代码,还是不得不吐槽下:

    跟不上 sass 的更新节奏

    sass 之所以能够在 2 年内反超 less,成为现在的首选,就是因为从版本 3.2.0 之后,不断更新,开发并优化更好的功能,吸引更多的关注。而 compass 却迟迟跟不上 sass 的脚步,严重影响 sass 的体验。

    跟不上 css 的脚步

    看下 compass 的重置,html5 的几乎没有,现在框架几乎都是 normalize+reset 的天下了;再看下其 inline-block 的 mixin 居然还有 display: -moz-inline-stack;,虽然穿着的是 sass 前沿的华衣,走的却是怀旧风格,怎么着都有点诡异。

    CSS3 mixin

    相信很多人用 compass 是奔着这烦人的 css3 前缀来的,可是弱弱的说句,它也过时了,现在都是基于 can i use 的数据来自动生成前缀或兼容了,各大自动化工具如 grunt/gulp 都有其相应的插件 autoprefixer,就算是不用这些自动的前缀,也有很多专门针对 css3 前缀的 scss 文件供调用,如 css3-scss

    sprite 自动生成雪碧图

    当然还有更大部分使用者是朝着这个功能来的,如果你仅是为了使用这个功能呢,替代的工具同样有的是,同样配置下自动化工具生成 sprite 分分钟搞定。

    所以你为什么还在坚持使用 compass?

    最后问题来了,如果选择了 sass,抛弃了 compass,用哪个做基础的框架比较合适?

    请听下回分解。

    继续阅读

  • bigpipe 性能优化
    In Web开发 on 2015年03月31日 by TAT.moonye view: 5,329
    0

    背景

    当前网速越来越快,但是随着网页内容越来越丰富,其实我们打开网页的速度并未得到什么提升,相反,过多的内容会导致网页打开速度变慢。于是,出现了一些性能优化的方法。
    1. 合并文件,如 css,js 等
    2. 将 js 文件放在文档的底部
    3. 将服务器部署到离用户近的地方,如 cdn 技术
    4. 缓存技术
    5. 负载均衡
    6. 文档直出
    等等

    有这么多通用的方式能够适用于我们的产品中,每一种实现的技术难度不大,当我们都应用了这些技术,发现网站性能依然不那么乐观的时候,会考虑到一些非常规、适用于某些特定场景的优化技术

    另一种思路

    Facebook 的研究科学家 changhao jiang 提出了一个优化方案,名字叫做 bigpipe,应用了此项技术的 facebook 首页,访问速度提升一倍。它极大的提升了 fackebook 的性能。它是怎么做到的。

    传统的一个打开页面的步骤

    1. 浏览器发送 HTTP 请求
    2. 服务器接收到 HTTP 请求,解析请求,从存储层拉取数据,拼接 HTML,发回一个 HTTP 响应
    3. 这个请求通过网络传输到浏览器
    4. 浏览器解析接收到的数据,构造 DOM 树,下载 CSS 和 JavaScript
    5. 浏览器下载了 CSS 之后,开始解析 CSS,渲染页面
    6. 下载 JavaScript 之后,开始解析 JavaScript,执行 JavaScript

    Bigpipe 的思路

    1. Request parsing:服务器解析和检查 http request
    2. Datafetching:服务器从存储层获取数据
    3. Markup generation:服务器生成 html 标记
    4. Network transport : 网络传输 response
    5. CSS downloading:浏览器下载 CSS
    6. DOM tree construction and CSS styling: 浏览器生成 DOM 树,并且使用 CSS
    7. JavaScript downloading: 浏览器下载页面引用的 JS 文件
    8. JavaScript execution: 浏览器执行页面 JS 代码

    看着 bigpipe 的步骤和普通的方式没什么区别,其实,它只是其中一个模块(pagelet)的流程而已。而一个页面是可以分解成 N 个模块,多个模块以流水线式的方式运行

    demo

    一个 node 实现的 demo,实现基本的分块的思想
    模拟服务器部分

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    http.createServer(<span class="keyword">function</span>(request, response) {
        <span class="comment">// Write the document</span>
        response.writeHead(<span class="number">200</span>, {<span class="string">"Content-Type"</span> : <span class="string">"text/html"</span>});
        response.write(<span class="string">'<!DOCTYPE html>'</span>);
        response.write(<span class="string">'<head><script type="text/javascript">function arrived(id,text) { var b=document.getElementById(id); b.innerHTML = text; }</script>'</span>);
        response.write(<span class="string">"</head><body><div>Progressive Loading"</span>);
        <span class="keyword">for</span>(<span class="keyword">var</span> i = <span class="number">0</span>; i < <span class="number">6</span>; i++) {
            response.write(<span class="string">"<div id='"</span> + i + <span class="string">"'>"</span> + i + <span class="string">"</div>"</span>);
        }
        response.write(<span class="string">"</div>"</span>);
     
        <span class="keyword">var</span> down = <span class="number">6</span>;
        <span class="keyword">for</span> (i = <span class="number">0</span>; i < <span class="number">6</span>; i++) {
            http.get(<span class="string">"http://localhost:2000/?id="</span> + i, <span class="keyword">function</span>(res) {
                res.on(<span class="string">'data'</span>, <span class="keyword">function</span>(chunk) {
                    response.write(chunk, <span class="string">'binary'</span>);
                });
     
                res.on(<span class="string">'end'</span>, <span class="keyword">function</span>() {
                    console.log(<span class="string">"down"</span>+down)
                    <span class="keyword">if</span>((--down )== <span class="number">0</span>) {
                        response.end();
                    }
                })
            });
        }
        response.write(<span class="string">"</body></html>"</span>);
     
    }).listen(<span class="number">8080</span>);
     

    模拟请求的代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    http.createServer(<span class="keyword">function</span>(request, response) {
        <span class="comment">// Some delay upto upto 2 seconds</span>
        <span class="keyword">var</span> delay = Math.round(Math.random() * <span class="number">2000</span>);
     
        setTimeout(<span class="keyword">function</span>() {
            <span class="keyword">var</span> params = url.parse(request.url, <span class="keyword">true</span>);
            <span class="keyword">var</span> id = params.query.id;
            response.writeHead(<span class="number">200</span>, {<span class="string">"Content-Type"</span> : <span class="string">"text/html"</span>});
            <span class="keyword">var</span> content = <span class="string">"<span>Content of Module "</span> + id + <span class="string">"</span>"</span>;
            response.write(<span class="string">"<script>"</span> +
                <span class="string">"arrived('"</span> + id + <span class="string">"', '"</span> + content + <span class="string">"');"</span> +
                 <span class="string">"</script>"</span>);
            response.end();
        }, delay);
    }).listen(<span class="number">2000</span>);
     

    一个框架

    https://github.com/bigpipe/bigpipe

    BigPipe is a radical new web framework for Node.JS. The general idea is to decompose web pages into small re-usable chunks of functionality called Pagelets and pipeline them through several execution stages inside web servers and browsers. This allows progressive rendering at the front-end and results in exceptional front-end performance.

    框架会有些重,小型开发场景下可以考虑自己实现

    存在的问题

    1.seo
    设置 useagent,爬虫来的时候给完整页面(有淘宝同学说这里存在 seo 问题,是否有 seo 问题还待确认,因为本身页面是在一个请求内完成)

    继续阅读

  • TAT.jessedeng 移动 WEB 调试利器——Rosin
    In Web开发,作品,移动 Web 开发,资源工具 on 2015年03月30日 by TAT.jessedeng view: 17,826
    18

    前言

    随着 Web 前端开发由 PC 转向 Mobile,作为前端工程师,除了需要去学习掌握移动端的新特性外,还需要面对大量移动端特有的难题,其中之一就是——怎么调试移动端页面? 针对调试的问题,现在已经有一些很好的解决方案,比如:weinre、chrome remote、手 Q 浏览器 Inspector,不过这些方案的门槛都较高,需要满足一些额外的条件。目前,在日常的开发调试工作中,打日志依然是一种常见的调试手段,通过日志内容来判断当前页面运行的状态,而日志的输出形式又有很多种,有 img 请求、dialog 弹窗、alert 等等,但这些输出方式都不能很好的满足我们的需要,甚至存在一些问题,所以 Rosin 诞生了。

    关于 Rosin

    Rosin 是一个 Fiddler 插件,它能接收页面中的 console 的输出,将内容持久存储在本地,并展现在 Fiddler 面板。 如果你的项目是通过 Fiddler 代理来开发调试手机页面,那么 Rosin 将会是你的好帮手。
    项目地址:  http://alloyteam.github.io/Rosin/

    继续阅读

  • AppCan
    In Web开发 on 2015年03月30日 by TAT.helondeng view: 1,752
    0
    • AppCan 是 HTMl5 移动开发应用平台,支持跨平台,可以简单、快速、高效开发移动应用。
    • 倡导 Hybrid App 开发。
    • 为 HTML5 开发提供底层 Native 交互能力。

    getcimg

    主要优势

    • 跨平台
    • 原生体验,引入 Native UI 控件和交互
    • 模拟调试:提供模拟器和调试工具
    • 多窗口机制,秒杀 phoneGap
    • 一键打包
    • …

    ###开发工具 IDE
    AppCan IDE 基于 Eclipse 定制,支持跨平台、本地打包、模拟调试、真机同步等功能

    模拟调试

    default

    左边部分是手机的一些模拟器。
    中间部分是手机屏幕,支持横竖屏切换和分辨率选择。
    右边是 chrome 调试窗口,这里内置了 chromium。

    本地打包

    支持将 web 应用直接打成 APK(android)安装包。
    qq 20150322142036

    真机同步

    将应用生成 “AppCan 调试中心” 模式的安装包,使用手机安装,确保手机和 pc 在同一个网络内。手机上安装成功后,打开 app,输入 pc 的 IP,登录成功后,可以看到应用的 List,打开调试的应用,可以 pc 上 IDE 的控制台上看到请求。

    1. 打开手机上的 “AppCan 调试中心” APP,输入 pc 的 ip 地址,登录,即可看到应用的 List。
      screenshot_2015-03-22-14-23-15
      screenshot_2015-03-22-14-24-52
    2. 手机上打开应用后,pc 上 IDE 控制台看到调试信息
      request

    插件 API

    AppCan 提供了丰富的底层插件,包括网络通讯,界面布局,功能扩展,第三方 SDK 等能力。API 文档可以参考 [这里], 覆盖了移动开发应用中的常用场景。(http://newdocx.appcan.cn/index.html?templateId=315)。

    JS SDK

    AppCan 将常用的 UI 和功能性模块封装成了一个开发库,开发者只需要引入 appcan.min.js 即可,另外把对 backbone, zepto, underscore 的依赖也打包在基础库中了

    screenshot_2015-03-22-14-37-41
    screenshot_2015-03-22-14-38-33

    JS SDK 文档参考这里

    ###总结

    AppCan 是一种基于 H5 实现跨平台 App 开发的解决方案,封装了一些常用的 UI 组件供 js 调用,提供了实现一整套流程的 IDE,包括调试,打包等。

    继续阅读

  • ES6 Generator 介绍
    In HTML5,JavaScript,Node.js,Web开发 on 2015年03月30日 by TAT.云中飞扬 view: 15,041
    5

    1
    2
    3
    4
    5
    6
    7
    function* generateNaturalNumber() {
        var i = 0;
        while(i <= 100) {
            yield i;
            i++;
        }
    }

    继续阅读

  • iScroll 学习小结
    In Web开发 on 2015年03月29日 by TAT.lqlongli view: 1,453
    1

    前言

    最近项目需要实现一个 fixed 标题栏的功能,很普通的功能,实现核心也是在 sroll 事件中切换到 fixed 状态即可,但是在某些版本 ios 的某些内核中,在惯性滚动过程中不执行任何 js 代码,亦即不会触发 scroll 事件,基本任何事情都做不了,为了解决这个问题不得不使用 div 内滚动,然后使用 iscroll 库实现滚动逻辑。

    基于使用过程中的一些问题,抱着学习的态度,稍微看了一下源代码,现把学习所得记录如下。

    源代码学习

    核心实现

    滑动相关组件(如 swipe 库)的实现基本都是类似的,就是通过 3 个核心事件:touchstart,touchmove,touchend 完成操作。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    <span class="keyword">switch</span> ( e.type ) {
        <span class="keyword">case</span> <span class="string">'touchstart'</span>:
        <span class="keyword">case</span> <span class="string">'mousedown'</span>:
            <span class="keyword">this</span>._start(e);
            <span class="keyword">break</span>;
        <span class="keyword">case</span> <span class="string">'touchmove'</span>:
        <span class="keyword">case</span> <span class="string">'mousemove'</span>:
            <span class="keyword">this</span>._move(e);
            <span class="keyword">break</span>;
        <span class="keyword">case</span> <span class="string">'touchend'</span>:
        <span class="keyword">case</span> <span class="string">'mouseup'</span>:
            <span class="keyword">this</span>._end(e);
            <span class="keyword">break</span>;
    }
     

    注:下面的源码只罗列核心部分,而且只展示 y 轴方向

    touchstart 需要做的事情有:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    <span class="keyword">function</span> _start(e) {
        <span class="keyword">var</span> point       = e.touches ? e.touches[<span class="number">0</span>] : e;
        
        <span class="comment">//[1]</span>
        <span class="comment">//初始化相关数据,一般是开始滑动的位置基点,时间基点</span>
        <span class="comment">//还有相关的变量</span>
        <span class="keyword">this</span>.moved      = <span class="keyword">false</span>;
        <span class="keyword">this</span>.distY      = <span class="number">0</span>;
        <span class="keyword">this</span>.directionY = <span class="number">0</span>;
        <span class="keyword">this</span>.startTime  = utils.getTime();
        <span class="keyword">this</span>.startY     = <span class="keyword">this</span>.y;
        <span class="keyword">this</span>.pointY     = point.pageY;
     
        <span class="comment">//[2]</span>
        <span class="comment">//如果正在滑动中,需要对此做处理,一般策略有:</span>
        <span class="comment">//1. 在当前滑动状态的基础上,叠加新的滑动状态</span>
        <span class="comment">//2. 立刻停止当前的滑动,开始新的滑动</span>
        <span class="comment">//iscroll使用的是方案2</span>
        <span class="comment">//方案1对于状态处理,滑速计算等方面略偏复杂,但这是更加合理的处理策略(原生的scroll也是这样的)</span>
        <span class="comment">//这有点类似开车时踩油门的场景,想象一下就清楚了。。。</span>
        <span class="keyword">if</span> ( !<span class="keyword">this</span>.options.useTransition && <span class="keyword">this</span>.isAnimating ) {
            <span class="keyword">this</span>.isAnimating = <span class="keyword">false</span>;
            <span class="keyword">this</span>._execEvent(<span class="string">'scrollEnd'</span>);
        }
    }
     

    touchmove 需要做的事情有:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    <span class="keyword">function</span> _move(e) {
        <span class="comment">//[1]</span>
        <span class="comment">//计算位置和时间,各种增量</span>
        <span class="keyword">var</span> point       = e.touches ? e.touches[<span class="number">0</span>] : e,
            deltaY      = point.pageY - <span class="keyword">this</span>.pointY,
            timestamp   = utils.getTime(),
            newY, absDistY;
     
        <span class="keyword">this</span>.pointY     = point.pageY;
        <span class="keyword">this</span>.distY      += deltaY;
        absDistY        = Math.abs(<span class="keyword">this</span>.distY);
        
        <span class="comment">//[2]</span>
        <span class="comment">//判定是否是标准滑动,防止手抖干扰</span>
        <span class="comment">//干扰有时候是很大的,特别是有惯性滑动逻辑的时候就更甚了,所以这个细节是少不了的</span>
        <span class="keyword">if</span> ( timestamp - <span class="keyword">this</span>.endTime > <span class="number">300</span> && (absDistX < <span class="number">10</span> && absDistY < <span class="number">10</span>) ) {
            <span class="keyword">return</span>;
        }
     
        newY = <span class="keyword">this</span>.y + deltaY;
     
        <span class="comment">//[3]</span>
        <span class="comment">//判断滑动是否超出范围了</span>
        <span class="comment">//自从ios出现了负向滚动效果之后,各种滑动组件都跟着实现了这种bounce效果</span>
        <span class="keyword">if</span> ( newY > <span class="number">0</span> || newY < <span class="keyword">this</span>.maxScrollY ) {
            newY = <span class="keyword">this</span>.options.bounce ? <span class="keyword">this</span>.y + deltaY / <span class="number">3</span> : newY > <span class="number">0</span> ? <span class="number">0</span> : <span class="keyword">this</span>.maxScrollY;
        }
     
        <span class="comment">//[4]</span>
        <span class="comment">//触发scrollStart事件</span>
        <span class="comment">//一个健全的组件肯定有相关的插口,一般都是用事件机制实现的</span>
        <span class="comment">//这里的细节是,开始事件是要在判定为标准滑动才会触发的,并且只触发一次</span>
        <span class="comment">//如果考虑不细的话,很容易会在touchstart事件中触发事件</span>
        <span class="keyword">if</span> ( !<span class="keyword">this</span>.moved ) {
            <span class="keyword">this</span>._execEvent(<span class="string">'scrollStart'</span>);
        }
     
        <span class="keyword">this</span>.moved = <span class="keyword">true</span>;
     
        <span class="comment">//[5]</span>
        <span class="comment">//万事俱备,让页面(元素)滑过去吧!</span>
        <span class="keyword">this</span>._translate(newX, newY);
    }
     

    touchend 需要做的事情有:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    <span class="keyword">function</span> _end(e) {
        <span class="comment">//[1]</span>
        <span class="comment">//进行必要的计算</span>
        <span class="keyword">var</span> duration = utils.getTime() - <span class="keyword">this</span>.startTime,
            newY = Math.round(<span class="keyword">this</span>.y),
            distanceY = Math.abs(newY - <span class="keyword">this</span>.startY);
     
        <span class="keyword">this</span>.endTime = utils.getTime();
     
        <span class="comment">//[2]</span>
        <span class="comment">//最后的位置也要滑过去</span>
        <span class="keyword">this</span>.scrollTo(newX, newY);  <span class="comment">// ensures that the last position is rounded</span>
     
        <span class="comment">//[3]</span>
        <span class="comment">//实现惯性滑动</span>
        <span class="keyword">if</span> ( <span class="keyword">this</span>.options.momentum && duration < <span class="number">300</span> ) {
            momentumY = <span class="keyword">this</span>.hasVerticalScroll ? utils.momentum(<span class="keyword">this</span>.y, <span class="keyword">this</span>.startY, duration, <span class="keyword">this</span>.maxScrollY, <span class="keyword">this</span>.options.bounce ? <span class="keyword">this</span>.wrapperHeight : <span class="number">0</span>, <span class="keyword">this</span>.options.deceleration) : { destination: newY, duration: <span class="number">0</span> };
            newY = momentumY.destination;
            time = Math.max(momentumX.duration, momentumY.duration);
            <span class="keyword">this</span>.isInTransition = <span class="number">1</span>;
        }
        
        <span class="keyword">if</span> ( newX != <span class="keyword">this</span>.x || newY != <span class="keyword">this</span>.y ) {
            <span class="keyword">this</span>.scrollTo(newX, newY, time, easing);    
            <span class="keyword">return</span>;
        }
     
        <span class="comment">//[4]</span>
        <span class="comment">//触发滑动结束事件</span>
        <span class="keyword">this</span>._execEvent(<span class="string">'scrollEnd'</span>);
    }
     

    基本所有滑动相关的组件所做的事情都是这些,都可以借鉴一二的。

    特殊 css prefix 缓存

    用 js 处理特殊 css 的时候,可以先缓存 prefix,这样就不用每次都操作所有的内置属性

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    <span class="keyword">var</span> _elementStyle = document.createElement(<span class="string">'div'</span>).style;
    <span class="keyword">var</span> _vendor = (<span class="keyword">function</span> () {
        <span class="keyword">var</span> vendors = [<span class="string">'t'</span>, <span class="string">'webkitT'</span>, <span class="string">'MozT'</span>, <span class="string">'msT'</span>, <span class="string">'OT'</span>],
            transform,
            i = <span class="number">0</span>,
            l = vendors.length;
     
        <span class="keyword">for</span> ( ; i < l; i++ ) {
            transform = vendors[i] + <span class="string">'ransform'</span>;
            <span class="keyword">if</span> ( transform in _elementStyle ) <span class="keyword">return</span> vendors[i].substr(<span class="number">0</span>, vendors[i].length-<span class="number">1</span>);
        }
     
        <span class="keyword">return</span> <span class="keyword">false</span>;
    })();
     
    <span class="keyword">function</span> _prefixStyle (style) {
        <span class="keyword">if</span> ( _vendor === <span class="keyword">false</span> ) <span class="keyword">return</span> <span class="keyword">false</span>;
        <span class="keyword">if</span> ( _vendor === <span class="string">''</span> ) <span class="keyword">return</span> style;
        <span class="keyword">return</span> _vendor + style.charAt(<span class="number">0</span>).toUpperCase() + style.substr(<span class="number">1</span>);
    }
     

    事件绑定

    addEventListener 绑定事件可以传入一个对象而不是一个 cb 函数,事件触发的时候,就会调用该对象的 handleEvent 方法来处理事件。例如:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    <span class="keyword">var</span> event = {
        handleEvent: <span class="keyword">function</span>(e) {
            <span class="keyword">switch</span> ( e.type ) {
                <span class="keyword">case</span> <span class="string">'touchstart'</span>:
                    <span class="keyword">this</span>._start(e);
                    <span class="keyword">break</span>;
                <span class="keyword">case</span> <span class="string">'touchmove'</span>:
                    <span class="keyword">this</span>._move(e);
                    <span class="keyword">break</span>;
                <span class="keyword">case</span> <span class="string">'touchend'</span>:
                    <span class="keyword">this</span>._end(e);
                    <span class="keyword">break</span>;
            }
        },
        _start: <span class="keyword">function</span>() {},
        _move: <span class="keyword">function</span>() {},
        _end: <span class="keyword">function</span>() {}
    }
    el.addEventListener(<span class="string">'touchstart'</span>, event);
    el.addEventListener(<span class="string">'touchmove'</span>, event);
    el.addEventListener(<span class="string">'touchend'</span>, event);
     

    这种绑定方式的优点有:

    1. 删除事件方便
    2. 事件集中处理
    3. 程序结构清晰

    还记得那种绑定事件时 bind(this) 的日子吗。。。
    这种方式也方便了实现事件代理

    事件触发频率调整

    对于一些触发频率较高的事件,我们通常会控制一下事件处理的频率,例如 scroll,resize 事件。
    另一方面,在实现一个公共组件的时候可以考虑从组件本身来解决这个问题,iScroll 通过配置来设置 scroll 事件的触发频率

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    <span class="comment">//下面代码在_move方法里</span>
    <span class="comment">//probeType == 1 则300ms才会触发一次scroll</span>
    <span class="keyword">if</span> ( timestamp - <span class="keyword">this</span>.startTime > <span class="number">300</span> ) {
        <span class="keyword">this</span>.startTime = timestamp;
     
        <span class="keyword">if</span> ( <span class="keyword">this</span>.options.probeType == <span class="number">1</span> ) {
            <span class="keyword">this</span>._execEvent(<span class="string">'scroll'</span>);
        }
    }
     
    <span class="comment">//probeType > 1 则一直触发</span>
    <span class="keyword">if</span> ( <span class="keyword">this</span>.options.probeType > <span class="number">1</span> ) {
        <span class="keyword">this</span>._execEvent(<span class="string">'scroll'</span>);
    }
     

    缺点与使用问题

    下面是针对版本 5.1.3 的 iscroll 使用过程中的一些问题

    1. 没有插件版

    iScroll 没有 zepto/jquery 插件版本,一些基础方法都需要自己实现,导致了库的体积偏大。

    2. 没有暴露停止滑动(惯性滑动)的接口

    通过查看源代码找到了停止滑动的方法,如下:

    1
    2
    3
    4
    5
    <span class="keyword">var</span> iScroll = <span class="keyword">new</span> IScroll({ <span class="comment">/* ... */</span> });
    <span class="comment">//直接通过修改iScroll对象的状态来停止滑动</span>
    <span class="comment">//通过这种方式停止动画是不会触发scrollEnd事件的!</span>
    iScroll.isAnimating = <span class="keyword">false</span>
     

    3. 调用 scrollTo 方法不会触发 scroll 事件

    可以通过 scrollTo 方法来手动滑动,但是这样的滑动过程是不会触发 scroll 事件的。

    总结

    在使用 iScroll 的过程中遇到不少坑,但使用起来还是比较容易的,文档也比较齐全。
    iScroll 在实现上也非常成熟,里面许多实现细节都是值得学习的

    继续阅读

  • TAT.felix 在浏览器端和服务器端下载并打包文件
    In JavaScript,Node.js,Web开发 on 2015年03月24日 by TAT.felix view: 9,319
    5

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    假设我们有以下目录结构:
     
    <a href="http://www.alloyteam.com/wp-content/uploads/2015/03/dir.png"><img class="alignnone size-full wp-image-6423" alt="dir" src="http://www.alloyteam.com/wp-content/uploads/2015/03/dir.png" width="330" height="258" /></a>
     
    用户可能需要打包这个目录下的所有文件,或其中一些文件的组合(在定制组件的场景下)。
     
    我们一般的做法是,提供一个页面,让用户进行选择,然后可以有两种做法:
     
    1. 提交请求到服务器端,服务器对定制化的文件组合进行合并之后打包,返回给客户端进行下载。
    2. 在客户端下载所需要的文件,自行进行合并之后打包,保存到本地。
     
    在服务器端合并打包文件应该是比较常见的做法了,这里主要介绍一下浏览器端下载。
    <!--more-->
    我们先来了解一下 Blob 对象。
     
    <strong>了解 Blob 对象</strong>
     
    一个 Blob 对象一种原生数据的封装,只读,可以用于文件操作。基于 Blob 对象实现的有 File 对象。
     
    创建一个 Blob 对象很简单,使用构造函数:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    // 参数 array 是 ArrayBuffer、ArrayBufferView、Blob、DOMString 对象的一种,或这些对象的混合。
    // 参数 options 含两个属性:
    var array = ['<div id="myId"><a href="http://alloyteam.com">Alloyteam</a></div>']
    var options = {
      type: '',                 // 默认为空,指定 array 内容的 MIME 类型
        endings: 'transparent'  // 默认为 'transparent', 指定遇到包含结束符 '/n' 的字符串如何写入
                                // 'native' 表示结束符转化为与当前用户的系统相关的字符表示,
                                // 'transparent' 表示结束符直接存储到 Blob 中,不做转换。
    };
    var myBlob = new Blob(array, options);
    console.log(myBlob);

    1
    2
    3
    4
    5
    6
    7
    创建了一个 Blob 实例后,它具有两个属性和一个方法
     
    - 两个属性:
     
        - size - 大小,单位为字节
     
        - type - MIME 类型,若构造时不指定则默认为空

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    // DOMString 类型数据
    var array = ['<div id="myId"><a href="http://alloyteam.com">Alloyteam</a></div>']
    // 生成 Blob 对象并指定 MIME 类型
    var myBlob = new Blob(array, {type: 'text/html'});
     
    // 输出看看有啥
    console.log(myBlob);
     
    // 大小,单位为字节
    var size = myBlob.size;
    // MIME 类型,创建时不设置则为空
    var type = myBlob.type;
     
    console.log('size: ', size);
    console.log('type: ', type);

    1
    2
    3
    4
    5
    6
    <a href="http://www.alloyteam.com/wp-content/uploads/2015/03/c.png"><img class="alignnone size-full wp-image-6424" alt="c" src="http://www.alloyteam.com/wp-content/uploads/2015/03/c.png" width="820" height="132" /></a>
    - 一个 slice 方法
     
    我们可以用这个方法在旧的 Blob 对象基础上切割出一个新的 Blob 对象,
     
    这个方法和 Array.prototype.slice() 用法类似:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    // 切割, slice 后返回一个新的 blob 对象
    // 第一个参数指定切割开始的位置
    var myBlob2 = myBlob.slice(10);
    console.log(myBlob2);
     
    // 第二个参数指定切割结束的位置
    var myBlob3 = myBlob.slice(10, 30);
    console.log(myBlob3);
     
    // 第三个参数可指定 MIME 类型,若不指定,则继承自 myBlob 的类型

    1
     

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    <a href="http://www.alloyteam.com/wp-content/uploads/2015/03/e.png"><img class="alignnone size-full wp-image-6425" alt="e" src="http://www.alloyteam.com/wp-content/uploads/2015/03/e.png" width="819" height="152" /></a>
    slice 方法能干啥?
     
    当我们需要上传一个大文件时,可以用它来将一个文件切割为多个,然后分段上传到服务器。
     
    <strong>使用 Blob 对象</strong>
     
    创建 Blob 对象时,我们传入数据并指定 MIME 类型。
     
    配合 FileReader 我们可以将 Blob 导出几种形式
     
    - 导出为 ArrayBuffer, 定长的二进制数据

    1
    2
    3
    4
    5
    var myReader1 = new FileReader();
    myReader1.onload = function () {
        console.log('readAsArrayBuffer: ', myReader1.result);
    };
    myReader1.readAsArrayBuffer(myBlob);

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    <a href="http://www.alloyteam.com/wp-content/uploads/2015/03/g.png"><img class="alignnone size-full wp-image-6426" alt="g" src="http://www.alloyteam.com/wp-content/uploads/2015/03/g.png" width="815" height="35" /></a>
     
    打印出来是空的,是因为 console.log 没法显示这种数据类型。
     
    那我们看看这个 ArrayBuffer 的大小,发现它确实是存在的:
    <a href="http://www.alloyteam.com/wp-content/uploads/2015/03/h.png"><img class="alignnone size-full wp-image-6427" alt="h" src="http://www.alloyteam.com/wp-content/uploads/2015/03/h.png" width="601" height="145" /></a>
     
    - 导出为 Text, 纯文本
     
    我们输入的是一个字符串且类型为 text/html, 那输出也自然是原来的字符串文本:

    1
    2
    3
    4
    5
    var myReader2 = new FileReader();
    myReader2.onload = function () {
        console.log('readAsText: ', myReader2.result);
    };
    myReader2.readAsText(myBlob);

    1
    2
    3
    <a href="http://www.alloyteam.com/wp-content/uploads/2015/03/j.png"><img class="alignnone size-full wp-image-6428" alt="j" src="http://www.alloyteam.com/wp-content/uploads/2015/03/j.png" width="822" height="37" /></a>
     
    - 导出为 DataURL

    1
    2
    3
    4
    5
    var myReader3 = new FileReader();
    myReader3.onload = function () {
        console.log('readAsDataURL: ', myReader3.result);
    };
    myReader3.readAsDataURL(myBlob);

    1
    2
    3
    4
    5
    6
    7
    8
    <a href="http://www.alloyteam.com/wp-content/uploads/2015/03/n.png"><img class="alignnone size-full wp-image-6429" alt="n" src="http://www.alloyteam.com/wp-content/uploads/2015/03/n.png" width="819" height="49" /></a>
    我们熟悉的小图片转化为内嵌的 base64 则可以使用 DataURL 来处理
     
    - 导出为 ObjectURL 形式
     
    与 DataURL 不同的是,ObjectURL 创建的是一个躺在内存里的 DOMString,
     
    它不像 DataURL 编码后数据保存到那一串字符串里,DOMString 依赖浏览器环境才能显示

    1
    2
    3
    4
    var array = ['<div id="myId"><a href="http://alloyteam.com">Alloyteam</a></div>']
    var myBlob = new Blob(array, {type: 'text/html'});
    var url = URL.createObjectURL(myBlob);
    console.log(url);

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    <a href="http://www.alloyteam.com/wp-content/uploads/2015/03/p.png"><img class="alignnone size-full wp-image-6430" alt="p" src="http://www.alloyteam.com/wp-content/uploads/2015/03/p.png" width="817" height="50" /></a>
    我们得到一个以 'blob:' 开头的串,如果我们把它复制到浏览器地址栏中回车,将会发现:
    <a href="http://www.alloyteam.com/wp-content/uploads/2015/03/q.png"><img class="alignnone size-full wp-image-6431" alt="q" src="http://www.alloyteam.com/wp-content/uploads/2015/03/q.png" width="822" height="185" /></a>
    这有什么用呢?下载文件!
    比如我们需要下载这个 myBlob 的话,可以配合 a 标签的 download 属性,
     
    将 URL.createObjectURL 返回的数据复制给 a 标签的 href 属性,
     
    再给 a 标签添加 download 属性,则触发点击这个 a 标签后,将会下载文件。
     
    <a href="http://www.alloyteam.com/wp-content/uploads/2015/03/r.png"><img class="alignnone size-full wp-image-6432" alt="r" src="http://www.alloyteam.com/wp-content/uploads/2015/03/r.png" width="1080" height="840" /></a>
    若需要指定下载的名字,则给 download 属性赋值,如

    1
    <a href="blob:xxx" download="myName">下载</a>

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    dataURL 和 objectURL 有啥区别呢?<a href="http://www.alloyteam.com/wp-content/uploads/2015/03/s.png">
     
    <img class="alignnone size-full wp-image-6433" alt="s" src="http://www.alloyteam.com/wp-content/uploads/2015/03/s.png" width="1012" height="526" />
    </a> 刷新页面会发现 dataURL 不再变化,而 objectURL 会不断变化:
     
    <a href="http://www.alloyteam.com/wp-content/uploads/2015/03/t.png"><img class="alignnone size-full wp-image-6434" alt="t" src="http://www.alloyteam.com/wp-content/uploads/2015/03/t.png" width="1015" height="526" /></a>
    原因是,dataURL 创建的是实际的数据,而 objectURL 既然是 DOMString,依赖浏览器环境,
     
    当这个页面一旦关闭(销毁),objectURL 将从内存中删除。
     
    我们可以验证一下: 在浏览器地址栏输入 objectURL:
     
    <a href="http://www.alloyteam.com/wp-content/uploads/2015/03/u.png"><img class="alignnone size-full wp-image-6435" alt="u" src="http://www.alloyteam.com/wp-content/uploads/2015/03/u.png" width="755" height="189" /></a>
    当我们把创建 objectURL 的页面关闭后,再刷新会发现:
    <a href="http://www.alloyteam.com/wp-content/uploads/2015/03/v.png"><img class="alignnone size-full wp-image-6436" alt="v" src="http://www.alloyteam.com/wp-content/uploads/2015/03/v.png" width="812" height="171" /></a>
    数据不见了!因为浏览器页面关闭后回收了这段内存,那这段 blob: 引用的 DOMString 不再存在。
     
    当然我们也可以手动调用 URL.revokeObjectURL() 的方式来回收。
     
    (当多次调用 URL.createObjectURL 用完后,即时释放内存很重要)
     
    在浏览器地址栏输入 dataURL, 发现 dataURL 是真实的数据,不会随页面关闭而消失:
     
    <a href="http://www.alloyteam.com/wp-content/uploads/2015/03/w.png"><img class="alignnone size-full wp-image-6437" alt="w" src="http://www.alloyteam.com/wp-content/uploads/2015/03/w.png" width="1137" height="186" /></a>
    dataURL 是真实的数据,可以用于对小图片进行编码等操作;
     
    objectURL 可以将一个文件转化为 URL 的形式,让我们获得操作文件的能力。
     
    <strong>服务器端与客户端下载文件</strong>
     
    介绍了那么多 Blob, 是不是跑题了。。。
     
    <strong>服务器端下载文件</strong>
     
    服务器端下载文件主要有几步:
     
    1. 根据请求将用户所需要的文件添加到一个临时文件夹 download/xxx
     
    2. 将临时文件夹压缩 download/xxx.zip
     
    3. 将压缩包返回 res.sendFile('download/xxx.zip')
     
    4. 删除临时文件 fs.unlink('download/xxx.zip');
     
    对于不同的用户,有不同的定制化要求时,生成临时文件夹也必须唯一,所以需要生成唯一的文件夹名。
     
    压缩包生成后,立即删除临时文件夹,并在压缩包成功传回客户端后,立即删除压缩包文件,以节省硬盘空间。
     
    <a href="http://www.alloyteam.com/wp-content/uploads/2015/03/x.png"><img class="alignnone size-full wp-image-6438" alt="x" src="http://www.alloyteam.com/wp-content/uploads/2015/03/x.png" width="425" height="86" /></a>
    在这里对每个定制化的请求返回一个压缩包响应后,立即删除了临时文件。
    <a href="http://www.alloyteam.com/wp-content/uploads/2015/03/y.png"><img class="alignnone size-full wp-image-6439" alt="y" src="http://www.alloyteam.com/wp-content/uploads/2015/03/y.png" width="865" height="597" /></a>
    还有一种做法是对压缩包进行缓存,若发现缓存中存在对应的压缩包,则不再新建。
     
    前者可以立即释放硬盘空间,后者则可以节省计算,各有利弊。
     
    <strong>浏览器端下载文件</strong>
     
    浏览器端下载文件主要有几步:
     
    1. 使用 AJAX 去下载所需要的文件
     
    2. 使用 Blob 对象对文件内容进行存储
     
    3. 使用 JSZip.js 或其他 zip 类库进行压缩
     
    4. 使用 FileSaver.js 或其他 file 接口保存文件
     
    <a href="http://www.alloyteam.com/wp-content/uploads/2015/03/z.png"><img class="alignnone size-full wp-image-6440" alt="z" src="http://www.alloyteam.com/wp-content/uploads/2015/03/z.png" width="865" height="229" /></a>
     
    ajax 异步下载文件,如何得知所有文件下载完成呢? 可以自己维护一个计数器,或者使用 Promise 吧!
    <a href="http://www.alloyteam.com/wp-content/uploads/2015/03/z-a.png"><img class="alignnone size-full wp-image-6441" alt="z-a" src="http://www.alloyteam.com/wp-content/uploads/2015/03/z-a.png" width="790" height="590" /></a>
     
    我这里使用了以下优秀类库
     
    - [jsZip](https://stuk.github.io/jszip/) 用于在浏览器端压缩文件
     
    - [FileSaver](https://github.com/eligrey/FileSaver.js) 用于将文件保存到本地
     
    - [bluebird](https://github.com/petkaantonov/bluebird) 用于控制异步 AJAX 获取文件
     
    <strong>服务器端与浏览器端下载文件的对比</strong>
     
    <a href="http://www.alloyteam.com/wp-content/uploads/2015/03/z-b.png"><img class="alignnone size-full wp-image-6442" alt="z-b" src="http://www.alloyteam.com/wp-content/uploads/2015/03/z-b.png" width="862" height="473" />
     
    </a>

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    1. 环境依赖:
     
      在服务器端需要部署环境,而对于一些简单的文件合并,
      我们可能就只是希望放在 gh-pages 上就能跑,
      那在浏览器端进行文件合并与压缩则比较轻量。
     
    2. 可靠程度:
     
      服务器是直接返回一个压缩包,
      而浏览器则需要自行下载多个文件后合并,
      而其中的文件传输可能会发生错误。
     
    3. 临时文件:
     
      服务器对于不同的定制化请求都会产生临时文件
    (是可以把临时文件放到内存里,但是内存比硬盘还贵还小,成本会比较高),
      若同一时间定制的请求过多(比如有人恶意 DDOS?),
      那么硬盘将会撑满,就只能把躺在硬盘里的苍老师删掉了吧 - -!
      而对于浏览器而言,我们可以把文件存放到不同的 CDN 中,加快文件的传输,
      并且临时文件存到了用户的硬盘里,就不用删掉服务器里的苍老师了。
     
    4. 缓存功能:
     
      服务器可以做缓存以减少计算,但带来了硬盘的开销。
     
    5. 传输文件:
     
      服务器响应一个请求返回一个压缩包,可以经过 gzip 压缩返回,
      而浏览器则要下载多个文件多个 HTTP 请求响应,
      但同时可以利用 cdn 进行下载,加快速度。
     
    6. 兼容性: 你懂的。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    <strong>示例代码 </strong>
    - <a href="http://laispace.github.io/downloadFilesInBrowserAndServer/public/">客户端下载文件</a>
    - <a href="http://laispace.github.io/downloadFilesInBrowserAndServer/createObjectURL.html">createObjectURL.html</a>
    - <a href="http://laispace.github.io/downloadFilesInBrowserAndServer/DataURL&ObjectURL.html">DataURL&ObjectURL.html</a>
    - <a style="font-weight: bold;" href="https://github.com/laispace/downloadFilesInBrowserAndServer">所有代码</a>
     
    <strong>参考链接</strong>
    - <a href="https://developer.mozilla.org/en-US/docs/Web/API/Blob">[Blob](https://developer.mozilla.org/en-US/docs/Web/API/Blob)</a>
    - <a href="http://en.wikipedia.org/wiki/MIME">[MIME](http://en.wikipedia.org/wiki/MIME)</a>
    - <a href="https://developer.mozilla.org/en-US/docs/Web/API/File">[File](https://developer.mozilla.org/en-US/docs/Web/API/File)</a>
    - <a href="https://developer.mozilla.org/en-US/docs/Web/API/FileList">[FileList](https://developer.mozilla.org/en-US/docs/Web/API/FileList)</a>
    - <a href="https://developer.mozilla.org/en-US/docs/Web/API/FileReader">[FileReader](https://developer.mozilla.org/en-US/docs/Web/API/FileReader)</a>
    - <a href="https://developer.mozilla.org/en-US/docs/Web/API/URL/createObjectURL">[createObjectURL](https://developer.mozilla.org/en-US/docs/Web/API/URL/createObjectURL)</a>
    - <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer">[ArrayBuffer](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer)</a>
    - <a href="https://stuk.github.io/jszip/">[jsZip](https://stuk.github.io/jszip/)</a>
    - <a href="https://github.com/eligrey/FileSaver.js">[FileSaver](https://github.com/eligrey/FileSaver.js)</a>
    - <a href="https://github.com/petkaantonov/bluebird">[bluebird](https://github.com/petkaantonov/bluebird) </a>

    继续阅读

上页 1 ...32 33 34 35 36 37 38 39 40 41 ...61 下页
公众号:AlloyTeam
扫码关注
公众号:AlloyTeam
合作伙伴
HTML5梦工场
腾讯云
Coding
兄弟团队
  • 腾讯 ISUX
  • 腾讯 CDC
  • 腾讯游戏 TGideas
  • 百度前端 EFE
  • 百度前端 FEX
  • 淘宝前端团队 FED
友情链接
  • 印记中文
  • W3CTech
  • 前端观察
  • W3C Plus
  • Web 前端开发
  • V2EX
  • 蓝色理想
  • 云开发 CloudBase
  • HTML5中文学习网
  • 爱思资源网
  • 牛大拿_前端设计导航
  • 吕小鸣前端博客
  • 腾讯大学


Copyright ©  2011-2025 AlloyTeam. All Rights Reserved. Powered By WordPress
粤ICP备15071938号-2