Chrome 下图片缓存设置导致合图失效问题

开发百度首页的音乐卡片模块时,发现合并之后的图片并没有真正生效,具体表现为:hover展示同一张大图内的图标时,该大图被重复加载一次, 其行为表现类似于图片未合并时候的情况,如下图所示:

大图重复加载图

一开始因为看到了 Initiator 里面显示的 jquery 还一度以为是 jquery 触发的背景图片重新 load,伟哥还怀疑有 js 隐藏/显示了一个元素导致background 里面的图片重新加载。反复调试没啥结果,有同事反馈在 firefox 下是没有这个问题的。而在我的Chrome Version 38.0.2125.111和伟哥的 chrome 上都复现了这个问题。

观察了下图片的每次请求都是200,猜测可能是缓存策略导致的。遂看了下主站cdn 上图片的响应头:

Cache-Control:max-age=31104000
Connection:Keep-Alive
Date:Wed, 05 Nov 2014 13:57:02 GMT
ETag:"1798851251"
Expires:Sat, 31 Oct 2015 13:57:02 GMT
Last-Modified:Tue, 04 Nov 2014 10:25:19 GMT

对比了出问题的大图的响应头如下所示:

Cache-Control:no-store, no-cache, must-revalidate, post-check=0, pre-check=0
Connection:keep-alive
Content-Type:image/png;charset=utf-8
Date:Wed, 05 Nov 2014 13:48:06 GMT
Expires:Thu, 19 Nov 1981 08:52:00 GMT
Pragma:no-cache

果然返回的图片没有设置缓存,于是把图片地址替换成本地的 server 进行测试:

Cache-Control:public, max-age=0
Connection:keep-alive
Content-Length:15702
Content-Type:image/png
Date:Wed, 05 Nov 2014 14:59:58 GMT
ETag:W/"3d56-394850958"
Last-Modified:Wed, 05 Nov 2014 14:59:56 GMT

果然问题解决了,chrome下不再有多余的请求发出。

总结:对于没有设置缓存的大背景图,当原来没有展示的图片需要展示时(如 :hover, show()):

  • chrome第一次会发起请求重新加载图片,导致原来大图失效,出现图标闪烁问题。当二次展示时,不会再重新加载同一张大图
  • ie6, firefox则不会受图片缓存策略影响,正常显示。
[2014-11-05]

编码风格最实践

使用 CoffeeLint

参考github的JS编码规范, 使用coffeelint来进行代码风格校验。

新版的coffeelint会自动选择配置文件(自动遍历需要lint的文件所在的目录树,查找coffeelint.json文件, 或者带有coffeelintConfig属性的package.json文件, 如果没有找到,或者是从stdin进行lint,则从用户home目录查找coffeelint.json文件)

与Sublime Text 3 配合使用

依赖一款叫做SublimeLinter的插件,其有一个 SublimeLinter-coffeelint 插件。可能需要做如操作:

  • 同时使用
[2014-08-18]

php基础知识

常量

  • 定义一个常量 define("VAR", "hello world!")
  • 使用 constant(VAR) 来取一个常量的值
[2014-06-24]

nginx实现浅析

参考 The Architecture of Open Source Applications 中对ngnix的一篇专业文献。

ngnix 代码结构

nginx会启动多个进程,主要有一个主进程和多个worker进程,还有一些特殊用途的进程:cache loader进程和cache manager进程。在1.x版本中所有上述进程都是单线程的。进程间使用共享内存的方式进行通信。

master 进程负责

  • 读取,验证配置
  • 创建、绑定、关闭socket连接
  • 启动、终止、维护配置数量的worker进程
  • 平滑重配置
  • 平滑升级或者回滚nginx程序
  • 将日志写入新的文件
  • 编译嵌入的perl脚本

worker

  • 负责处理客户端连接,提供反向代理和过滤功能,及ngnix几乎所有的其他功能,workers是实际上服务器处理日常操作的进程。
  • run-loop是nginx worker 中最复杂的部分,通过模块化、事件通知、大量的回调函数和精妙设计的计时器来实现异步操作,当不再有存储空间支持worker进程的时候,nginx才会阻塞
  • 不需要创建销毁进程的开销,节省了CPU资源。nginx只监测网络和存储的状态,将新连接加入到run-loop中,然后进行异步处理,待该请求处理完成之后释放该连接并将其从run-loop中移除。
  • 通过创建多个worker来支持多核CPU,创建和CPU核心个数相等的worker来最大化的利用CPU,如果负载类型是CPU密集型(如处理大量的TCP/IP, SSL, 压缩等)的,创建和CPU核心个数相等的worker;如果是磁盘IO密集(如提供存储的不同的内容,代理为主等)的,则可以将worker数提高到CPU核心数的1.5~2倍之间

cache loader

+ 负责检查磁盘缓存,将缓存相关数据写入nginx的内存数据库中。 + 为nginx实例提供存储在磁盘上的文件的特殊分配的目录结构,它遍历这些目录,检查缓存文件的信息,更新共享内存中的缓存内容,确定内容是最新、可用的之后退出 + 

cache manager

是负责缓存的过期和失效,随一般的nginx操作一起常驻在内存中,当出现问题时会被master进程重启

nginx缓存简介

  • 缓存的数据分层存放,分层和命名细节在通过配置文件指定
  • 缓存数据的key是可配置的, 通过不同的请求参数控制被缓存的内容
  • 缓存keys和metadata存放在共享内存中,可被cache loader, cache manager, worker访问
  • 目前不是将内容缓存在内存中,而是通过操作系统的虚拟文件系统的机制优化,将每个缓存的响应存放在不同的文件中(虚拟文件系统),分层和命名通过配置文件指定,路径和文件名和代理URLMD5后的结果相关
  • 存放缓存内容的规则如下:1). nginx从上游服务器读到的响应内容首先会被写入缓存目录结构外的一个临时文件 2). ngnix完成请求处理后,重命名该临时文件并移动到缓存目录
  • 可以通过第三方扩展远程管理缓存
[2014-06-23]

HTTP服务器(Apache, Lighttpd, nginx)浅析

引言:骆老师说服务器看nginx就够了,代码精简,质量高,其他的Apache,Lighttpd都是浮云

Apache篇

管理命令

  • httpd -l 查看加载了哪些模块
  • httpd -V 查看编译Apache时的配置(可以看到默认读取的配置文件的路径等)

nginx 篇

安装过程

configure, sudo make install就能自动安装到/usr/local/nginx目录下面

常用命令:

  • 直接 nginx 启动服务
  • nginx -s stop|quit|reload|reopen 分别执行相应操作
  • 发送HUP命令进行平滑重启 kill -HUPcat /usr/local/nginx/logs/nginx.pid`

基本理念

  • nginx的首要功能是一个反向代理,其次才是服务器,因此更关注URLs而不是文件。
  • nginx的配置是层级继承的形式,主要由三个嵌套的块构成:HTTP-block,server-block,location block,继承关系为http -> server -> location,还有两个特殊的location:event-block和设定http-block和event-block归属的root。需要配置的主要是前面的三个块。server-block对应Apache中的虚拟主机,location-block主要对应URI
  • 代理一切请求。过滤掉资源类型的请求,只有当nginx无法处理请求的uri时,才将请求分发给后端程序来处理。例如location / {try_files $uri $uri/ /index.php;}就能将静态文件的请求直接返回,然后再加上/index.php交给适配php的规则去处理。这里注意有proxy_passfastcgi_pass两种(暂时不知道是否和include过来的配置有关)。

配置示例

变量

http://nginx.org/en/docs/http/ngxhttpcore_module.html#variables 定义了可以在配置文件中使用的nginx变量。

  • $schema, 自动适配http或者https
  • $requesr_uri,请求的uri ### 语句
  • try_files $uri =404;, 当请求的uri不存在时,自动返回404,此时定义在后面的语句不会再被执行
  • return 301 http://domain.com$request_uri; 重写url
  • 完整例子

反向代理

  • proxy_pass, 可以指向一台机器(http://127.0.0.1:8088)或者一个集群(http://tomcats).
  • nginx默认将代理的返回数据缓存起来,知道全部接受完毕再返回给客户端,可以使用proxy_buffering off关闭该功能,实时返回数据给客户端。proxy_buffers 16 4k;设置缓冲区个数和每个缓冲区的大小。无论proxy_buffering是否开启,proxy_buffer_size(main buffer)都是工作的,设置的缓冲区大小用来存储upstream端response的header

    集群中的所有后台服务器的配置信息

    upstream tomcats { server 192.168.0.11:8080 weight=50; server 192.168.0.11:8081 weight=50; }

使用 Tips

  • 配置虚拟主机一直报 403, 原来为指向了自己的home目录,却没有开放home目录的711权限,只开放了home目录下www目录的755权限,没有x权限就导致nginx无法进入home目录,于是就报403了
  • 对于未匹配到的server_name, nginx默认会使用配置文件中的第一条 server {} 配置
  • nginx在选择location块来分发请求时,首先会检查严格定义的前缀,以适配到最长前缀的规则为准,然后再看这条规则是否满足其他的 正则匹配规则,如果有正则适配则采用适配到的正则规则,否则使用之前匹配到的前缀规则(When nginx selects a location block to serve a request it first checks location directives that specify prefixes, remembering location with the longest prefix, and then checks regular expressions. If there is a match with a regular expression, nginx picks this location or, otherwise, it picks the one remembered earlier.)

参考链接

Linux 管理相关

查看Linux发行版信息的方法

  • uname -a 查看是否有明确字眼
  • cat /etc/issue 例如CentOS
  • lsb_release -a命令,这个命令我试了一下在redhat和SUSE上好用,在aix和solaris上不能用
  • cat /etc/*release*

尽量不要设置服务器语言为中文

  • locale -a能列出所有支持的语系
  • locale查看当前语系设置
  • 尽量保留LANG和LCALL为"enUS.UTF-8/en_US.utf8"

查看开机自启动项

  • chkconfig 命令
  • chkconfig --add/del xxx 添加新的服务进入chkconfig列表
  • chkconfig xxx on/off 是否开机自启动
  • chkconfig --list xxx 只显示xxx的开机自启动状态

自启动项的0~6分别代表下述等级 + 0:开机(请不要切换到此等级) + 1:单人使用者模式的文字界面 + 2:多人使用者模式的文字界面,不具有网络档案系统(NFS)功能 + 3:多人使用者模式的文字界面,具有网络档案系统(NFS)功能 + 4:某些发行版的linux使用此等级进入x windows system + 5:某些发行版的linux使用此等级进入x windows system + 6:重新启动

查看某个进程的具体启动时间

使用 ps -p PID -o lstart, 其中,PID为某个进程的进程ID号。

for pid in $(pgrep httpd); do 
    echo -n "${pid} " ; 
    ps -p ${pid} -o lstart | grep -v "START" ; 
done
[2014-06-22]

Nodejs异常处理最佳实践

程序错误 VS 可控错误

严格定义函数处理的类型,不匹配的输入抛出异常

  • 对用户输入的类型判断越多,越容易出错
  • 随时可以使函数接受更多的参数,但是如果一开始十分宽泛,后面要严格的话就会导致兼容性问题
  • 不鼓励使用domainsuncaughtException,因为任何可控错误都可以通过callback或EventEmitter进行处理。

书写函数的建议

1. 明确函数功能

  1. 需要的参数
  2. 每个参数的类型
  3. 关于参数的额外要求(比如必须是合法的IP地址)
  4. 可能会产生的错误
  5. 产生错误的方式
  6. 返回值

2. 使用Error(子)对象定义所有错误

通过name和message和stack属性提供准确的错误信息

3. 使用Error的name属性区分错误

[2014-06-04]

监测DOM内容变化

开发jquery异步加载插件——asyncModule.js的调试功能的时候,发现append到父元素下的调试dom因为模块内部js调用了父元素的html()方法而被移除,因此需要在调用html()方法之后再进行调试dom append的操作。

解决方法:

修改jQuery的html方法

(function($) {
    var oldHtml = $.fn.html;
    $.fn.html = function()
    {
        var ret = oldHtml.apply(this, arguments);

        //trigger your event.
        this.trigger("change");

        return ret;
    };
})(jQuery);

通过在html()方法完成时,派发一个事件

DOMCharacterDataModified DOMSubtreeModified event

当插入的元素是字符时,会触发该事件,但是当插入dom例如 e.innerHTML = "<span>hello world!</span>" 时,则不会触发该事件

参考 JavaScript DOM Events, 用DOMSubtreeModified更好

DOM MutationObserver

// select the target node
var target = document.querySelector('#some-id');

// create an observer instance
var observer = new MutationObserver(function(mutations) {
    mutations.forEach(function(mutation) {
        console.log(mutation.type);
    });    
});

// configuration of the observer:
var config = { attributes: true, childList: true, characterData: true }

// pass in the target node, as well as the observer options
observer.observe(target, config);

// later, you can stop observing
observer.disconnect();

当DOM完成变化,会触发回调并传入发生的变化列表(属性arrtibutes、子元素childList、?characterData)

参考链接

[2014-05-13]

JavaScript框架设计 读书笔记

CH01 种子模块

1.2 对象扩展

Object.prototype.keys 方法的简易实现

var a = [];
for (a[a.length] in Obj);
return a;

直接在for...in的过程中就把对象的key赋值给了数组

1.4 类型的判定

1.5 domReady

  1. 支持DOMContentLoaded事件的直接使用该事件
  2. IE则采用调用doScroll方法是否报错来检测DOM是否构建完成/给script标签添加defer属性(仅仅ie6支持),会在dom构建完后触发readystatechange属性

1.6 无冲突处理

执行jQuery库前,将jQuery, $变量缓存到_jQuery, _$变量中,调用$.noConflict()时还原,并真正的jQuery返回便于重命名。

CH14 动画引擎

更多动画相关知识

  • css动画的原理——给元素创建自己layer,而非与页面上大部分的元素共用layer。
[2014-05-06]


基于触摸事件实现移动端浏览器效果增强

原文参考 Variation of e.touches, e.targetTouches and e.changedTouches

We have the following lists:

  • touches: A list of information for every finger currently touching the screen
  • targetTouches: Like touches, but is filtered to only the information for finger touches that started out within the same node
  • changedTouches: A list of information for every finger involved in the event (see below) To better understand what might be in these lists, let’s go over some examples quickly They vary in the following pattern:
  1. When I put a finger down, all three lists will have the same information. It will be in changedTouches because putting the finger down is what caused the event
  2. When I put a second finger down, touches will have two items, one for each finger. targetTouches will have two items only if the finger was placed in the same node as the first finger. changedTouches will have the information related to the second finger, because it’s what caused the event
  3. If I put two fingers down at exactly the same time, it’s possible to have two items in changedTouches, one for each finger
  4. If I move my fingers, the only list that will change is changedTouches and will contain information related to as many fingers as have moved (at least one).
  5. When I lift a finger, it will be removed from touches, targetTouches and will appear in changedTouches since it’s what caused the event
  6. Removing my last finger will leave touches and targetTouches empty, and changedTouches will contain information for the last finger

Touch对象的方法参考: Touch.clientX/Touch.clientY/Touch.force /Touch.identifier/Touch.pageX/Touch.pageY/Touch.radiusX/Touch.radiusY/Touch.rotationAngle/Touch.screenX/Touch.screenY

[2014-03-19]

一些Android自带浏览器上touch事件跨页面触发click事件的坑

在开发百度票务移动端的时候, 发现一个很诡异的问题:

首页列表图

如上图所示, 点击下拉菜单选项在选中选项并进行页面跳转的同时点击了下方的列表项,导致先重载列表,然后直接跳转到演出的详情页了。产生了貌似是点击穿透的效果。

  1. 首先尝试在下拉框出来的时候,给整个页面加个遮罩,人为屏蔽掉穿透的效果,经测试无效!
  2. 给整个列表设置"pointer-events: none"的属性,屏蔽掉点击事件,经测试无效!
  3. 通过对比出问题的浏览器的浏览记录,发现整个流程是先跳到新的列表页,然后再跳到详情页,因此出问题的不是在原先的页面,而是在最新的页面触发了链接的点击事件。经过测试,发现这些浏览器的事件和页面不是分开的!。即我在A页面的touch事件,如果导致了页面跳转到B,并不会取消click事件的触发。当B页面在相应位置有可以点击的元素时,就会因为A页面上的touch事件而触发在B页面上的click事件。

总结: AppleWebKit/534.xx 系列的Android浏览器根据点击的位置先后触发touch和click,不会因为页面的跳转而中止click事件的触发,由于使用了Fastclick库,将touch事件模拟成click事件,当我在列表页选择一个筛选条件之后,首先由fastclick根据touch事件触发了虚拟的click事件,导致页面跳转,当新页面快速加载完成的时候(300ms之内?),继续在那个位置触发click事件,如果此时新页面下方有可以点击的元素的时候,就会触发那些元素的点击事件。 复现要点: 1. 跳转的页面加载速度要快, 要在click事件触发之前完成加载 2. 原先点击的位置对应到新页面中有可点击的元素才能看到明显效果。

[2014-03-17]