工作生活中碰到了太多不友好的代码和行为,实在是使人不能承受之重。有些点真的非常简单,大部分人其实也都知道,但是在工作中写代码就是那么写。

阅读全文 »

笔记的存在,用来下一次记忆知识点时,可以快速地找到内容,而不至于再去翻整本书;以及加深记忆(避免看了等于白看)。

这些都是在书上画线标记了的。
这些都是在书上画线标记了的。
这些都是在书上画线标记了的。

重要的事情要说三遍。

此外注意:此系列中出现的术语都是《JavaScript高级程序设计》中出现的术语,例如此书中出现的一些不符的名词(例如将类型、甚至对象称为类)都将被更准确的类型、对象代替。

第二章 接口

阅读全文 »

笔记的存在,用来下一次记忆知识点时,可以快速地找到内容,而不至于再去翻整本书;以及加深记忆(避免看了等于白看)。

这些都是在书上画线标记了的。
这些都是在书上画线标记了的。
这些都是在书上画线标记了的。

重要的事情要说三遍。

此外注意:此系列中出现的术语都是《JavaScript高级程序设计》中出现的术语,例如此书中出现的一些不符的名词(例如将类型、甚至对象称为类)都将被更准确的类型、对象代替。

第一章 富有表现力的JavaScript

阅读全文 »

上一篇提到一种劫持形式是在一个js文件中追加了一段脚本,当然这段脚本并未对代码的执行造成多少影响,只是右下角出现一个框框广告罢了(虽然也很烦人,但相比下面的情况至少好多了)。

这一篇谈到的场景则是,运营商直接劫持了一整个文件,并替换了整个文件的内容。场景重现是这样的:页面引入了a.jsb.jsc.js三个文件,a.js为全局的通用依赖,比如说jQuery,b.js为几个页面业务间通用的一些方法等等的合集,c.js为当前页面业务代码,三个脚本的执行顺序为a.jsb.jsc.js

然后运营商来劫持了,他有可能会把a.js里的文件内容替换成这样:

阅读全文 »

DNS劫持又称域名劫持,是指在劫持的网络范围内拦截域名解析的请求,分析请求的域名,把审查范围以外的请求放行,否则返回假的IP地址或者什么都不做使请求失去响应,其效果就是对特定的网络不能反应或访问的是假网址。

阅读全文 »

缓存问题可以算是至今为止遇到最多维持很久的一个问题。这个问题的发生,导致了脚本报错页面不可访问、样式错乱的情况,而且持续时间长。

缓存可以归为两部分,一部分是页面的缓存,一部分是js、css引用的文件缓存。于是具体缓存问题的发生,可以归为两类:

  1. html已经更新了,但是js、css文件取到的还是缓存的文件。
  2. js、css文件已经更新了,但是html缓存还没有更新。

因为两部分缓存的设置大部分不同,所以缓存更新不同步,导致了文件的不对应,从而脚本报错页面不可访问、样式错乱。

以某一现状为例,线上js、css文件的缓存控制方案是这样的:
发布的过程会跑一个本地node的一个命令,遍历存放js、css生产文件,通过node crypto模块来获取文件唯一的版本信息,然后将version对象写入到一个单独的json文件version.json中。

1
2
3
4
5
6
7
8
var md5 = crypto.createHash('md5'),
file = folder + '/production/' + name + '.' + type;

md5.update(grunt.file.read(file));
md5 = md5.digest('hex');
version[type][name] = {
md5: md5
};

js、css文件的引入,是通过一个后端的方法,读取version.json文件配置,将文件名拼上部分md5信息放入到页面中。类似http://st.bbeeii.com/script/production/a.js 这个文件的引入,线上实际引入的链接则是http://st.bbeeii.com/script/production/a-rfgt45jfsg.js ,其中rfgt45jfsg是32位md5信息做了10位截取的。

http://st.bbeeii.com/script/production/a-rfgt45jfsg.js 这个文件请求的发起,会先经过cdn。cdn如果发现有这个文件立即返回。否则cdn向我们的服务器回源,请求/production/a-rfgt45jfsg.js 文件,再返回回去。

自己的服务器这边是没有/production/a-rfgt45jfsg.js 这个实体文件的,这个请求,通过nginx rewrite返回了/production/a.js 。

因为js、css代码文件与后端动态脚本文件是在一个代码库内,通过svn提交到trunk,然后所有的机器svn up更新代码。代码发布svn up的时间会有时间间隔,如果version.json文件先更新,后端脚本读取到这个文件,前台用户发起请求,cdn回源,一气呵成。然后/production/a.js文件实际还没有更新,反而返回回去了。于是cdn就拿到了老的代码,并且是一段比较长的缓存。除非手动更新缓存,缓存期间页面始终都是错误的。

另外,因为用户访问js、css文件并不是定向到cdn服务器(不稳定),有可能请求是到自己的服务器上,从而也会有html文件(缓存较久)还没更新,而js、css文件已经更新的情况。

如果请求的文件/production/a-rfgt45jfsg.js以及上一个版本/production/a-gthdtyh65.js在我们的服务器上实际存在会怎么样呢(多版本共存)?

优化后的发布流程为:

  1. 先发布静态部署文件例如/production/a.js 和版本文件version.json。
  2. 本地任务通过/production/a.js和version.json这两个文件,生成新的/production/a-rfgt45jfsg.js存放到单独的两台机器上(主、备)。
  3. 这两台机器上,跑一个任务,匹配文件前缀,比对last modified时间,保留最新的三到五个版本,其余清除。例如/production/a.js的版本多余限定,将最老的版本清除。

页面中引入的脚本、样式地址,例如:

  1. html缓存,引用的脚本地址也是老的: /production/a-gthdtyh65.js
  2. html已更新,引用的脚本地址也是新的:/production/a-rfgt45jfsg.js

因为/production/a-gthdtyh65.js和/production/a-rfgt45jfsg.js实际已经不是同一个文件(同一文件的不同版本),那么也就不会有缓存的问题了。

优化后的用户访问流程:
用户访问流程图

静态资源的访问,始终经过本地缓存、cdn。
此外,如果版本信息文件处理错误,为避免工具或者人工问题,导致/production/a-gthdtyh65.js文件的访问活着全站性的版本文件没有更新,得到的404,本地服务器在请求获得时将返回一个302 redirect,将文件重定向到/production/a.js,然后返回。

更深的优化项:

  1. 所有js、css部署文件可以全部设置长时间缓存,直接取用户本地缓存,节省带宽。因为存放版本信息和静态资源先发布,生成了对应新版本的实体文件,并且老版本文件也存在着。后端代码发布时,因版本信息更新,文件引入的名称也就更新了。此时html没有更新,引用的老版本文件在;html更新了,引用的新版本文件也在。
  2. 既然前端的发布和后端代码的发布分开,前端代码的访问只通过两台固定的服务器,那也是时候进行代码库分离和“动静分离”了。当然,这还涉及到另外的问题:代码库如何分离以及分离后前后端如何协同开发。

参考来源:

实际上这是去年7月份时碰到的问题,但是那会儿只是单纯认为这是浏览器纪录了刷新前的位置,并自发做了回到原来位置的处理导致的失效,并没有刻意去处理,而只是简单做了如下操作:

1
$(window).on('load', function () {
    setTimeout(function () {
        $('html,body').animate({
            'scrollTop' : top
        }, 0);
    }, 200);
});

上周时,因周年庆的活动,偶然间触动了这部分代码,偶然间触发了这个问题。于是今天花了几个小时做了算是精准的定位。

以下代码直接运行在html中,具体可以下载这个html代码做修改和查看这个页面

测试:chrome 41.0.2272.104 (64-bit) / ff 37.0.1

1
log('test 1 for window.scrollTo');
window.scrollTo(0, 2000);
log('now document.body.scrollTop is : ' + document.body.scrollTop); // chrome: 2000       FF: 0
log('now document.documentElement.scrollTop is : ' + document.documentElement.scrollTop); // chrome: 0   ff: 2000

…… 等等等等,总共做了六个测试,包括window.scorllTowindow.scrollwindow.scrollBywindow.onload then scrollTowindow.onload then setTimeout then scrollTo。最后log的结果都是类似的,而实际效果就是,chrome在刷新的情况下,怎么都到不了设定的2000px的位置,而firefox可以。重新打开倒是正常。

而实际业务逻辑中做的减法调试也耗费了不少的时间。包括以这样的代码找到所有的scroll事件依依去除。

1
var lookForBindHandler = function (dom, eventType) {
    $.each( $._data(dom).events[eventType], function (key, handle) {
        console.log(handle.handler.toString());
    });
}; 
lookForBindHandler(window, 'scroll')

到依次去除业务代码、依赖到的库文件lazyload、 infinitescroll、jQuery 到最后直接用原生的 window.scrollTo(jQuey内部用的应该也是window.scrollTo无误)。

1
if ( win ) {
    win.scrollTo(
        top ? val : jQuery( win ).scrollLeft(),
        top ? val : jQuery( win ).scrollTop()
    );
} else {
    elem[ method ] = val;
}

另见stackoverflow,链接A链接B,定位为Chrome 刷新当前页面的情况。

后做继续跟进,发现Safari、Opera(偶尔) 也有此现象,而IE、FF都是正常,那推测为Webkit的通病。

对应的UA为:

1
Chrome 	Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.104 Safari/537.36
Opera	Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/40.0.2214.111 Safari/537.36 OPR/27.0.1689.69
Safari	 Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_3) AppleWebKit/600.5.17 (KHTML, like Gecko) Version/8.0.5 Safari/600.5.17
FireFox	 Mozilla/5.0 (Macintosh; Intel Mac OS X 10.10; rv:37.0) Gecko/20100101 Firefox/37.0

因此,只需做webKit和是否刷新页面的判断,就可以提供比原来多200ms以上的用户反馈和体验。

1
if (window.navigator.userAgent.search(/AppleWebKit\/(\d+\.)+(\d+)? \(KHTML\, like Gecko\)/) !== -1) {
    if (window.name) { // 默认如果被设置过 就采用延时
        $(window).on('load', function () {
            setTimeout(next, 200);
        });
    } else {
        window.name = 'refresh_frag';
        next();
    }
} else {
    next();
}

网页是否当前页面刷新的判断 - 根据window.name

其实我们能做的可以做的还有很多…
The End.
2015.04.22 01:15