Qida's Blog

纸上得来终觉浅,绝知此事要躬行。

除了依赖关系,继承和聚合关系也是 Maven 项目中很常用的两种关系。

继承关系

  • 在子项目中,使用 <parent> 指定父项目;
  • 项目能继承父项目的一些配置(属性,依赖等);
  • 一般用于多项目共享父配置。
  • 特别注意 relativePath 元素。虽非必填,但可以用作 Maven 的一个指示符,以在搜索本地和远程仓库之前首先搜索此路径,以达到优先使用本地父项目的目的(本地开发过程中有时我们会修改本地父项目的配置,例如修改依赖版本号)。

父项目配置如下:

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
<project>
<modelVersion>4.0.0</modelVersion>

<groupId>test</groupId>
<artifactId>parent</artifactId>
<version>1.0</version>
<packaging>pom</packaging> <!-- 必须是 pom -->

<!-- 以下配置都可以被子项目继承 -->

<!-- 属性配置 -->
<properties...>
<!-- 依赖管理配置,如 spring-cloud、spring-boot、reactor -->
<dependencyManagement...>
<!-- 依赖配置 -->
<dependencies...>
<!-- 构建配置 -->
<build...>
<!-- Nexus 分发包地址 -->
<distributionManagement...>
<!-- Nexus 仓库地址 -->
<repositories...>
<!-- Nexus 插件地址 -->
<pluginRepositories...>
</project>

子项目配置如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<project>
<modelVersion>4.0.0</modelVersion>

<parent>
<groupId>test</groupId>
<artifactId>parent</artifactId>
<version>1.0</version>
<relativePath>../parent</relativePath>
</parent>

<groupId>test</groupId> <!-- 可继承自父项目 -->
<artifactId>child-a</artifactId>
<version>1.0</version> <!-- 可继承自父项目 -->
</project>

聚合关系

  • 在父项目中,使用 <modules> 指定子项目;
  • 在父项目中进行构建,会同时构建全部子项目;
  • 一般用于批量管理一个项目的多个模块;

父项目配置如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<project>
<modelVersion>4.0.0</modelVersion>

<groupId>test</groupId>
<artifactId>parent</artifactId>
<version>1.0</version>
<packaging>pom</packaging> <!-- 必须是 pom -->

<modules>
<!-- 子模块相对于父项目的路径,一般为 artifactId -->
<module>child-a</module>
<module>child-a</module>
</modules>
</project>

依赖关系

依赖关系是 Maven 项目之间最常见的关系。Maven 会自动解析所有项目的直接依赖传递性依赖,并且根据规则正确判断每个依赖范围(Dependency Scope)。对于一些依赖冲突,自动进行依赖调解(Dependency Mediation),或开发者手动排除依赖(exclusions),以确保任何一个构件只有唯一的版本在依赖中存在。经过这些工作之后,得到的那些依赖被称为已解析依赖(Resolved Dependency),从而产生一个 Effective POM。

依赖配置

下面是一段依赖配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<project>

<dependencies>
<dependency>
<groupId></groupId> <!-- 依赖的构件属组 -->
<artifactId></artifactId> <!-- 依赖的构件ID -->
<version></version> <!-- 依赖的构件版本 -->
<type></type> <!-- 依赖类型,默认值为 jar -->
<scope></scope> <!-- 依赖范围 -->
<optional></optional> <!-- 可选依赖,默认情况下不会被继承,只有声明了该依赖才会继承 -->
<exclusions> <!-- 排除(传递性)依赖 -->
<exclusion></exclusion>

</exclusions>
</dependency>

</dependencies>

</project>

依赖传递

Maven 会解析各个直接依赖的 POM,将那些必要的间接依赖,以传递性依赖的方式引入到当前项目中,而开发者不用再考虑该直接依赖还依赖了什么,或手动引入导致多余依赖:

传递性依赖

依赖调解

Maven 调解依赖有两个基本原则:

  1. 第一原则:路径最短者优先
    假设 X 是 A 的传递性依赖:A->B->C->X(1.0) 依赖路径长度为 3,A->D->X(2.0) 长度为 2,则 X(2.0) 会先被解析使用。
  2. 第二原则:第一声明者优先
    在依赖路径长度相等的前提下,在 POM 中,依赖声明的顺序决定了谁会先被解析使用。

依赖范围 scope

依赖范围 scope 用于控制依赖与这三种 classpath(编译、测试、运行)的关系,即是否有效:

依赖范围(Scope) 编译classpath 测试classpath 运行classpath 例子
compile spring-core
test × × spring-test、junit、mockito
provided × servlet-api(运行时,由于容器如tomcat已经提供,无须重复引入)
runtime × JDBC 驱动实现(编译时,只需 JDK 提供的 JDBC 接口;运行时,才需要接口的具体实现)
system × 本地的,Maven 仓库之外的类库文件
import / / / Maven 2.0.9 版本后出的属性,import 只能在 dependencyManagement 的中使用,能解决 Maven 单继承问题,import 依赖关系实际上并不参与限制依赖关系的传递性。

可选依赖 optional

可选依赖 optionaltrue 时,该依赖不会被传递进来。

依赖排除 exclusions

当传递性依赖的结果不是自己预期的构件版本,可以排除依赖重新自定义。下例展示了使用 <exclusions> 排除 A 的传递性依赖 C,并显式声明依赖 C(1.10):

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
<project>

<dependencies>
<dependency>
<groupId></groupId>
<artifactId>A</artifactId>
<version>1.0</version>
<exclusions> <!-- 排除 A 的传递性依赖 C -->
<exclusion>
<groupId></groupId>
<artifactId>C</artifactId>
</exclusion>

</exclusions>
</dependency>

<!-- 显式声明依赖 C(1.10) -->
<dependency>
<groupId></groupId>
<artifactId>C</artifactId>
<version>1.10</version>
</dependency>

</dependencies>

</project>

结果如图:

依赖排除

依赖归类

当多个构件使用同一版本号时,为了便于后期统一升级维护,可以使用 <properties> 属性统一定义:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<project>

<properties>
<spring.version>5.0.0.RELEASE</spring.version>
</properties>

<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
...
</dependencies>

</project>

常见问题

解决依赖冲突

例如在使用 Spring Boot 时,其依赖引入了一个有缺陷的 Jackson 框架版本 1.9.X,此时需要覆盖起步依赖引入的传递依赖:

方式一,如果项目未使用 Jackson 相关功能,可以使用 <exclusions> 排除依赖:

1
2
3
4
5
6
7
8
9
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>com.fasterxml.jackson.core</groupId>
</exclusion>
</exclusions>
</dependency>

方式二,如果项目使用了,可以利用“就近原则”,覆盖传递依赖的版本号,修复缺陷:

1
2
3
4
5
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.4.3</version>
</dependency>

依赖下载问题

有时候某个依赖下载有问题时,会导致本地环境找不到相关类。此时可以找到本地仓库下的相关 jar 包目录,排查下是否只有 *.lastupdated 文件而没有相应 jar 包。如果是,则删掉该目录重新下载依赖即可。

参考

http://maven.apache.org/guides/introduction/introduction-to-optional-and-excludes-dependencies.html

Maven optional关键字透彻图解

Maven 坐标

Maven 定义了这样一组规则:世界上任何一个构件都可以使用 Maven 坐标唯一标识,Maven 坐标的元素包括:

坐标元素 描述
groupId 必选。定义当前 Maven 项目隶属的实际项目。
artifactId 必选。定义实际项目中的一个 Maven 项目(模块),推荐使用实际项目名称作为前缀。
version 必选。定义当前 Maven 项目所处版本。
packaging 可选。定义 Maven 项目的打包方式,默认为 jar
classifier 不能直接定义。用来帮助定义构建输出的一些附属构件。如 javadoc、sources。

POM 关系类型

Maven 一个强大的地方在于处理项目之间的关系。其中包括依赖关系(包括传递性依赖关系)、继承关系聚合关系(多模块项目)。

详见:《Maven 依赖关系》、《Maven 继承关系与聚合关系

属性

Maven 属性是类似于 Ant 属性一样的值占位符。可以通过符号 ${X}(其中 X 是属性)在 POM 中的任意位置访问它们的值。它们也可以被插件用作默认值使用,例如:

1
2
3
4
5
6
7
8
9
10
<project>
...
<properties>
<maven.compiler.source>1.7</maven.compiler.source>
<maven.compiler.target>1.7</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
</properties>
...
</project>

共有五种风格的占位符:

${env.X} 返回 shell 的环境变量。例如 ${env.PATH} 返回 PATH 环境变量。为了可靠性,Maven 2.1.0 开始将环境变量的名称归一化为全部大写。
${project.x} 返回 pom.xml 文件中对应元素的值。 例如可通过 ${project.version} 访问 <project><version>1.0</version></project>
${settings.x} 返回 settings.xml 文件中对应元素的值。例如 <settings><offline>false</offline></settings> 可通过 ${settings.offline} 访问。
${java.x} 返回 Java 系统属性。 通过 java.lang.System.getProperties() 可访问的所有属性都可用作 POM 属性,例如${java.home}
${x} 返回 pom.xml 文件中 <properties/> 元素内的值。例如 <spring.version>1.0.1-SNAPSHOT</spring.version> 可通过 ${spring.version} 访问。

参考

http://maven.apache.org/pom.html

学习 Git 快一年了,感受良多,总结如下。

如何开始?

早年的时候,为了获取优质的项目和资源,注册了 GitHub 的账号,但仅仅简单用了 Star 和 Clone 功能。后来开始想在 GitHub 上面托管一些资源,陆续做了一些功课:

  • Git 官方文档,第一手官方材料,由浅入深,涉及 Git 各方面的内容,建议有实操经验的同学深入阅读。
  • GitHub 帮助文档,偏向实操,建议初学者阅读。而且里面涉及到一些 GitHub 特性(图形化操作、Social、Pages)可以与 Git 互补。
  • 廖雪峰的博客

其实网上相关教程、博客、书籍很多,但建议初学者重心先放在:

  • 基础命令的实操(推荐 Git cheatsheet,按功能分组命令,便于记忆)
  • 简单概念的理解(例如重点关注 Git 文件流转的三个工作区域及远程仓库,至于配置、分支(我知道分支是 Git 的杀手锏功能,但事实上很多人只用到一个 master 分支)、工作流等先统统忽略)

然后安装好 Git,选择好一个代码托管商(如国外 GitHub,国内 GitCafe),赶紧先跑起来,再好起来!如果你曾大致了解过 Git 这一门技术,你会发现这是属于“记忆型”的技术,需要多用才熟能生巧。

我的学习轨迹

第一轮

第一轮学习从零开始,大致如下:

  • 简单阅读了一些文档,用思维导图做成笔记。
  • 安装客户端,实操命令。
  • 把所做所得整理成 PPT,用自己的理解在团队内部做了一次技术分享,反响不错,还加深了自己的理解。

第二轮

第二轮的起因是因为想用 GitHub Pages 服务搭建一个静态博客写写文章。由于已经有了第一轮的沉淀,这回学习速度就很快了。还顺便学会了 Markdown 语法和 Hexo 博客搭建。

第三轮

第三轮是因为跳槽后新公司正好使用的是 Git,但由于团队成员用得都不熟练,因此利用空余时间进一步研究了 Git 的进阶内容:

  • 分支管理与团队工作流程
  • 冲突解决方案
  • pullmergelogresetcheckout 等实用命令
  • rebasecherry-pick 等高级命令

整个过程使用的是“INK 学习法”,并再次将理解的内容整理成 PPT 与团队分享。这轮学习的不同之处在于:

  • 以往都是个人使用 Git,使用的命令都很简单。当与团队一起使用时,问题规模不同,对工具的理解也会进一步加深。
  • 与第一次纯粹分享不同,这次侧重于推广我的方案,规范团队的工作流程,将知识转化为生产力。

总结

学习的轨迹应当是螺旋向上,难易度逐轮递增。每一轮的学习主题还应有所侧重,意图一口一个大胖子的行为会噎死自己 :)

如今 Git 对我来说不止是门技术,更是一种生活方式。除了工作中每天都要用到,通过它还“连接”了我与开源世界。

参考

https://learngitbranching.js.org/

告别 SVN,Git 成 “独苗”:GitHub 在 13 年后宣布淘汰 Subversion 支持

1.获取权限

首先获取相关 Group 的 Owner 权限。

2.在线迁移

Git 迁移项目

3.更新本地仓库

更新本地仓库,首先查看当前 remote url:

1
2
3
$ git remote -v
origin git@git.kd.ssj:finance-ssjmarket/finance-market.git (fetch)
origin git@git.kd.ssj:finance-ssjmarket/finance-market.git (push)

使用 git remote set-url 重置 remote url:

1
$ git remote set-url origin git@git.kd.ssj:finance-web/finance-market.git

检查重置是否成功:

1
2
3
$ git remote -v
origin git@git.kd.ssj:finance-web/finance-market.git (fetch)
origin git@git.kd.ssj:finance-web/finance-market.git (push)

4 批量更新本地仓库

适用于一堆 git 仓库放在同一个目录下,可以用这个方法进行批量替换:

  1. 检查一下现在的 url:
1
cat ./finance-*/.git/config | grep 'git@'
  1. 批量替换:
1
ls -1 ./仓库名-*/.git/config | xargs  sed -i 's/git@.*\:/git@github.com:/g'
  1. 再次检查一下结果:
1
cat ./finance-*/.git/config | grep 'git@'

工作中常用到 mysql 自带的命令行工具,但实在难用。推荐一款 MySQL 命令行工具——MyCli,支持自动补全语法高亮。也可用于 MariaDB 和 Percona。

功能如下:

MyCLI

MyCLI 的兼容性爆表,支持 Windows、MacOS、Linux,运行在 Python 2.7, 3.3, 3.4, 3.5, 3.6。安装简易,例如 Windows 只要安装了 Python 环境及其包管理工具 pip ,就能一键安装:

1
2
3
4
5
$ pip install mycli

or

$ easy_install mycli

其它系统的安装方式,请参考:http://mycli.net/install

Windows 下使用 cmd 连接数据库,如下:

1
mycli -hlocalhost -P3306 -uroot -p123456

我们知道如何在电脑上通过 Chrome、Firefox 调试页面请求,但在手机端呢?我们可以使用 fiddler 来调试webapp。fiddler 是一个很好的调试、抓包工具。

问题:在客户端打开页面有问题,但浏览器正常;(由于客户端特定的环境,我们无法在电脑端浏览器调试定位一些问题,线上环境,更不能修改代码来调试。怎么办? 用Fiddler 代理本地文件来调试)

解决办法: 使用Fiddler 代理调试APP页面;

PC 端设置

Fiddler 安装

https://www.telerik.com/download/fiddler

Fiddler 设置

首先打开Fiddler->Tools->Fiddler Options 进行配置,配置完成后重启 Fiddler,如下图:

Fiddler 设置

关闭防火墙

关闭本机防火墙,避免手机无法 ping 通。

iPhone 手机端调试

参考:http://blog.csdn.net/asmcvc/article/details/51566569

安装 Fiddler 证书

手机浏览器访问 172.22.31.43:8888 ,点击安装证书:

安装 Fiddler 证书

提示警告,继续安装:

安装 Fiddler 证书

安装完毕:

安装 Fiddler 证书

证书信任设置

iOS 10 需要设置:通用 - 关于本机 - 证书信任设置 - 针对根证书启用完全信任:

证书信任设置

WIFI 代理设置

WIFI 代理设置

开始抓包调试

嗅探所有请求、响应

打开APP页面,可以嗅探被过滤的请求:

嗅探器

解码 URL 请求参数

普通表单的显示如下:

解码 URL 请求参数

可以使用 Send to TextWizard 进一步解码:

解码 URL 请求参数

设置断点,修改请求、响应

Fiddler 最强大的功能莫过于设置断点了,设置好断点后,你可以修改请求头(如 cookie)、请求体、响应头、响应体。

断点原理

设置断点有两种方法:

第一种:设置全局断点

打开 Fiddler 点击 Rules-> Automatic Breakpoint -> Before Requests,可修改请求;After Responses,可修改响应。

如何消除全局断点呢? 点击Rules-> Automatic Breakpoint ->Disabled

设置全局断点

第二种:设置指定断点

在命令行中输入命令: bpu www.baidu.com (这种方法只会中断 www.baidu.com)

如何消除命令呢? 在命令行中输入命令 bpu

断点效果如下:

设置指定断点

设置指定断点

自动响应

有时候,线上客户端环境下打开的页面有bug无法用浏览器调试,则可以用fiddler代理本地文件来进行调试,非常方便。例如:

设置自动响应

我们将线上JS文件代理为本地文件,我们可以修改本地文件,就能用客户端打开看到修改结果,非常方便,当然我们可以同时代理 引入 vconsole 来在手机端打印错误日志。

也可以将一个接口代理下来,然后新建一个json文件,和代理js文件是同样的方法,就可以修改接口的请求了。

PS:代理接口时可能会出现跨域问题,解决方法点这里

显示 IP

如何显示 IP?只需配置一行代码:

1
FiddlerObject.UI.lvSessions.AddBoundColumn("IP", 120, "X-HostIP");

显示 IP

常见问题

  1. 多级代理相互影响(数据库连接vpn_ssl、蓝灯、xx-net等)
  2. 代理缓存
  3. 左下角的“Captuing”仅用于控制电脑端抓包,不影响手机端,可以关掉,避免影响电脑端的正常上网(例如网易云音乐听歌,印象笔记同步)。
  4. 开启代理后,响应体不会被 gzip,也没有响应头:Content-Encoding: gzip。可以自行用 Chrome 和 IE 对比测试。

移动 Web 开发要点总结

总结下其中几个要点:

iOS 300ms 点击延时问题

为什么存在这个问题?

这要追溯至 2007 年初。苹果公司在发布首款 iPhone 前夕,遇到一个问题 —— 当时的网站都是为大屏幕设备所设计的。于是苹果的工程师们做了一些约定,应对 iPhone 这种小屏幕浏览 PC 端站点的问题。这当中最出名的,当属双击缩放(double tap to zoom)。

双击缩放,顾名思义,即用手指在屏幕上快速点击两次,iOS 自带的 Safari 浏览器会将网页缩放至原始比例。

那么问题来了,假设用户在 iOS Safari 里边点击了一个链接,当用户一次点击屏幕之后,浏览器并不能立刻判断用户是确实要打开这个链接,还是想要进行双击操作。

因此,iOS Safari 就等待 300 毫秒,以判断用户是否再次点击了屏幕。如果没有,就触发 click 点击事件。

According to Google:

… mobile browsers will wait approximately 300ms from the time that you tap the button to fire the click event. The reason for this is that the browser is waiting to see if you are actually performing a double tap.

验证问题

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
<html>
<body>
<button>Click Me</button>
<script type="text/javascript">
var button = document.querySelector("button"),
record;

button.addEventListener("touchstart", function(){
record = Date.now();
console.log("button touchstart");
});
button.addEventListener("touchend", function(){
var delay = Date.now() - record;
console.log("button touchend delay: " + delay + "ms");
});
button.addEventListener("click", function(){
var delay = Date.now() - record;
console.log("button click delay: " + delay + "ms");
});
document.addEventListener("click", function(){
var delay = Date.now() - record;
console.log("document click delay: " + delay + "ms");
});
</script>
</body>
</html>

iOS Safari 上点击后,显示结果如下:

1
2
3
4
button touchstart
button touchend delay: 59ms
button click delay: 310ms
document click delay: 312ms

可见,click 事件延迟了 300ms 左右。

找出原因

当一个用户点击屏幕的时候,会产生两个事件:touchclicktouch 事件会首先触发,完成捕获、冒泡的事件流。同时在点击的 300ms 延时后,触发 click 事件。

解决方案

如今移动端 webapp 性能都追求与原生应用匹配,上述 iOS 单击事件 300ms 延迟,显然是不可接受的。有三个解决方案:

方案一

只使用 touch 事件,然后使用 e.preventDefault() 来阻止默认行为 click

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
<html>
<body>
<button>Click Me</button>
<script type="text/javascript">
var button = document.querySelector("button"),
record;

button.addEventListener("touchstart", function(e){
record = Date.now();
console.log("button touchstart");
e.preventDefault();
});
button.addEventListener("touchend", function(){
var delay = Date.now() - record;
console.log("button touchend delay: " + delay + "ms");
});
button.addEventListener("click", function(){
var delay = Date.now() - record;
console.log("button click delay: " + delay + "ms");
});
document.addEventListener("click", function(){
var delay = Date.now() - record;
console.log("document click delay: " + delay + "ms");
});
</script>
</body>
</html>

效果如下:

1
2
button touchstart
button touchend delay: 60ms

这种方案看起来简单易行,然而功能复杂的时候容易出问题。比如滑动加选择,会因为滑动触发 touchend,从而触发选择行为。所以如果本该绑定在 click 上的事件全部绑定到 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
<html>
<body>
<button>Click Me</button>
<script type="text/javascript">
var button = document.querySelector("button"),
record;

button.addEventListener("touchstart", function(){
record = Date.now();
console.log("button touchstart");
});
document.addEventListener("touchmove", function(){
console.log("document move");
});
button.addEventListener("touchend", function(){
var delay = Date.now() - record;
console.log("button touchend delay: " + delay + "ms");
});
button.addEventListener("click", function(){
var delay = Date.now() - record;
console.log("button click delay: " + delay + "ms");
});
document.addEventListener("click", function(){
var delay = Date.now() - record;
console.log("document click delay: " + delay + "ms");
});
</script>
</body>
</html>

当点击按钮并拖动时,显示结果如下:

1
2
3
4
5
6
7
button touchstart
document move
document move
document move
document move
document move
button touchend delay: 943ms

可见:

  • 该例中用户可能只想拖动页面,但却被迫触发了 touchend 事件。所以如果本该绑定在 click 上的事件全部绑定到 touchend 事件上,就会出现问题,违背用户意图。

  • 拖动行为会导致 click 事件不会执行,可以理解为 touchmoveclick 是相斥的。

因此,建议用回 click

方案二

使用 zepto.js 的 tap 事件,底层是 click 事件并去掉 300ms 延时,然而会有点击穿透的问题。

方案三

使用 fastclick,兼容性好,用法简单,没有点透问题,如下:

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
<html>
<body>
<button>Click Me</button>
<script type="text/javascript" src="fastclick.js"></script>
<script type="text/javascript">
var button = document.querySelector("button"),
record;

button.addEventListener("touchstart", function(){
record = Date.now();
console.log("button touchstart");
});
button.addEventListener("touchend", function(){
var delay = Date.now() - record;
console.log("button touchend delay: " + delay + "ms");
});
button.addEventListener("click", function(){
var delay = Date.now() - record;
console.log("button click delay: " + delay + "ms");
});
document.addEventListener("click", function(){
var delay = Date.now() - record;
console.log("document click delay: " + delay + "ms");
});
document.addEventListener('DOMContentLoaded', function() {
FastClick.attach(document.body);
}, false);
</script>
</body>
</html>

效果如下:

1
2
3
4
button touchstart
button touchend delay: 60ms
button click delay: 60ms
document click delay: 60ms

参考

http://developer.telerik.com/featured/300-ms-click-delay-ios-8/

https://www.sitepoint.com/5-ways-prevent-300ms-click-delay-mobile-devices/

http://www.linovo.me/front/webapp-300ms.html

https://github.com/ftlabs/fastclick

https://github.com/filamentgroup/tappy/

http://labs.ft.com/2011/08/fastclick-native-like-tapping-for-touch-apps/

http://www.mamicode.com/info-detail-666685.html

https://www.jianshu.com/p/dc3bceb10dbb

h5 存储

Cookie 指令在 HTTP 头的形式如下:

  • HTTP 请求头 Cookie 指令:

    1
    Cookie: code=23365f1409b; __auth=eda2ebe49a4a91d3546435c3
  • HTTP 响应头 Set-Cookie 指令:

    1
    Set-Cookie: code=23365f1409b; Expires=Thu, 31-Dec-2016 07:23:59 GMT; Domain=x.y.z.com; Path=/ws; Secure; HttpOnly

其中 Cookie 的 domain 属性比较特殊,存在一些读写限制:可读写本身或上一级 domain 的 cookie,但无法读写同级或下一级 domain 的 cookie。

z.com y.z.com
z.com
y.z.com ×
x.z.com × ×
x.y.z.com × ×

如果不设置 Cookie 的 Expires 或者 Max-Age 属性,其默认值是 Session,也就是关闭浏览器后该 Cookie 就消失了。

参考

https://www.ibm.com/developerworks/cn/java/books/javaweb_xlb/10/index.html

http://www.cnblogs.com/xiaowei0705/archive/2011/04/19/2021372.html

某段时期前端技术选型上使用过 Gulp.js 解决前端工程化及自动化构建问题,下表整理了其在实践项目中常用的插件:

插件 功能 备注
gulp-jshint 检查 JavaScript 语法 http://jshint.com/docs/options/
gulp-uglify 压缩 JavaScript
gulp-sourcemaps 输出 sourcemaps 部署前端之前,开发者通常会对代码进行打包压缩,这样可以减少代码大小,从而有效提高访问速度。然而,压缩代码的报错信息是很难Debug的,因为它的行号和列号已经失真。这时就需要Source Map来还原真实的出错位置了。
gulp-imagemin 压缩图片 progressive JPEG 图像渐进式扫描;interlaced GIF 图像隔行扫描
gulp-rev 静态资源 hash 在实际生产环境中,我们页面引用的静态资源的文件名都是带版本号的(非覆盖式升级),这样方便版本管理(如更新与回滚)和防止缓存。通常我们使用文件的md5编码作为版本号,生成文件指纹。
gulp-less Less 文件编译 用于引入 Less 扩展 CSS 语言,提升前端样式的开发效率。
gulp-autoprefixer 根据所需兼容的浏览器版本,自动补全厂商前缀
gulp-css-base64 将CSS 样式表中引用的图片和字体通过 base64 编码压缩合并到一起,减少文件请求数 maxWeightResource 资源最大阈值,默认为 32K
gulp-ejs 编译 HTML 中的 ejs 模板,可用于页面布局拆分,提升代码复用性 http://ejs.co/
gulp-htmlmin 压缩 HTML
gulp-inline-source 将 HTML 外部引用的样式和脚本以内联的方式嵌到 HTML 文件中,减少文件请求数
gulp-if 编译时动态判断
gulp-replace 编译时动态替换字符串 可用于根据不同环境构建代码,解决各环境间的差异。例如正则匹配并全局替换 HTML 中的 ${web} 变量。
gulp-tar 打包静态资源 tar-stream
gulp-mock-server API Mock Server,前后端分离后的 API 模拟利器
browser-sync 监听本地文件变化并同步刷新浏览器,提升开发效率的利器 https://www.browsersync.io/