Archive for the 'Web开发' Category

阻止浏览器滚动条事件

标题党。

在开发天猫迷你购物袋的过程当中,交互设计师要求当购物袋元素内的滚动条滚动到底(或顶部)并继续滚动,应不会影响到窗口滚动条的滚动,就像facebook的聊天窗口那样:

facebook聊天窗口

一开始以为在元素的onscroll事件里阻止默认事件就可以了,后来查阅资料发现原来要阻止的应该是onmousewheel事件(Firefox下的DOMMouseScroll)。

代码挺简单的

//mousewheel兼容处理
function mw_handler(ele, ev){
    var height = DOM.height(ele),
        scrollHeight = ele.scrollHeight,
        delta;
    //firefox除外的浏览器
    if(ev.wheelDelta) delta = ev.wheelDelta / 120;
    //firefox
    if(ev.detail) delta = -ev.detail / 3;
    //当存在滚动条时 一旦滚动到最顶或者最底 则阻止默认事件
    if(DOM.css(ele, 'overflowY') == 'scroll' && ( (ele.scrollTop == (scrollHeight - height) && delta < 0) || (ele.scrollTop ==0 && delta > 0) )){
        ev.preventDefault();
    }
}
//firefox
if(S.UA.gecko){
    Event.on(conEl, 'DOMMouseScroll', function(e){
        mw_handler(this, e);
    });
}else{
    Event.on(conEl, 'mousewheel', function(e){
        mw_handler(this, e);
    });
}

天猫购物袋

对IE6|7浏览器提供localStorage支持

localStorge是IE8以上版本和其他高级浏览器才具有的功能,本文通过在Window环境下对UserData进行封装,对IE6和IE7浏览器提供了与其他浏览器localStorage原生API兼容的方法,包括:setItem、getItem、removeItem以及clear。(tips:请在原生IE中测试,在IEtester中会引起IEtester崩溃)

源码:

/**
 * 对IE6|7浏览器提供localStorage支持
 * fanyu.xhy@tmall.com 2012-2-20
 */
;(function(){
    var S = KISSY, DOM = S.DOM,
        isIE67 = (S.UA.ie < 8) ? true: false,
        _userDataEl,
        _saveFileName,
        _expires;
    if(isIE67 && !window['localStorage']){
        var IELocalStorage = function(config){

            var _config = S.mix({
                'saveFileName': 'IELocalStorage',
                'expires': 365
            }, config);

            _saveFileName = _config.saveFileName;
            var _expires = new Date();

            _expires.setDate(_expires.getDate() + _config.expires);

            try{
                var _userDataEl = document.createElement('input');
                _userDataEl.type = 'hidden';
                _userDataEl.addBehavior('#default#userData');
                document.body.appendChild(_userDataEl);
                _userDataEl.expires = _expires.toUTCString();
            }catch(e){
                return false;
            }

            return {
                'setItem': function(key, value){
                    _userDataEl.load(_saveFileName);
                    _userDataEl.setAttribute(key, value);
                    _userDataEl.save(_saveFileName);
                },
                'getItem': function(key){
                    _userDataEl.load(_saveFileName);
                    return _userDataEl.getAttribute(key);
                },
                'removeItem': function(key){
                    _userDataEl.load(_saveFileName);
                    _userDataEl.removeAttribute(key);
                    _userDataEl.save(_saveFileName);
                },
                'clear': function(){
                    _userDataEl.load(_saveFileName);
                    var d = new Date();
                    d.setDate(d.getDate() - 1);
                    _userDataEl.expires = d.toUTCString();
                    _userDataEl.save(_saveFileName);
                }
            }

        }
        window['localStorage'] = IELocalStorage();
    }
})(KISSY);

Uncaught TypeError: Property ‘submit’ of object # is not a function

今天在项目中遇到,如果一个表单元素内部含有name属性为submit的表单元素,如

<input type="hidden" name="submit" />

那么当你用js提交该表单,即:
form1.submit();
会出现报错:
Uncaught TypeError: Property ‘submit’ of object # is not a function
原因是form.xx 是会优先取得同名input元素,即那个元素,而DOM元素是不能被执行的,所以报错了。
在国外网站上找到了吊爆的解决方案:

document.createElement('form').submit.call(form1);

PHP模拟BigPipe

前不久新浪微博改版,前端引入了BigPipe(最早是Facebook提出的),页面载入速度嗖嗖嗖地上去了。。那末为神马BigPipe能这么快呢?
与AJAX相似,BigPipe使页面可以按区块渲染。但与AJAX不同的是,BigPipe不需要在页面载入后再通过XMLHttpRequest向服务器发起异步请求,而是(通过使用缓冲区)在页面载入过程中先输出页面布局,并在HTML中预留各个页面区块(Facebook称为Pagelet),在页面底部才输出Javascript代码,DOM操作,从而对区块进行内容填充,也就是把内容赋给innerHTML。
这么做的好处是,在页面加载的过程中,把一些响应时间相对较长的输出放到了页面底部,而页面整体轮廓先显示出来,减少用户的等待时间;此外,与Ajax相比,BigPipe不需要进行多次请求就能完全显示内容,降低了服务器的负担。
下面用PHP模拟一下BigPipe,代码里用sleep(1)模拟一些运算的时间损耗(要先关闭Apache的GZip):

<!doctype>
<html>
<head>
	<meta charset="utf-8" />
	<title>PHP模拟BigPipe</title>
</head>
<style>
.section{height:200px;}
#con1{background:#ef8090;}
#con2{background:#fef000;}
#con3{background:#19c000;}
</style>
<body>
	<div class="wrapper">
		<div class="section" id="con1">页头内容,正在加载……</div>
		<div class="section" id="con2">正文内容,正在加载……</div>
		<div class="section" id="con3">页尾内容,正在加载……</div>
	</div>
	<?php
	/*
	 * 输出缓存区
	 */
	function flush_now(){
		ob_flush();
		flush();
	}
	flush_now();
	?>

	<?php sleep(1);?>
	<script>
		document.getElementById("con1").innerHTML = "<?php echo str_repeat(\'内容1\', 200) ?>";
	</script>
	<?php flush_now()?>

	<?php sleep(1);?>
	<script>
		document.getElementById("con2").innerHTML = "<?php echo str_repeat(\'内容2\', 200) ?>";
	</script>
	<?php flush_now()?>

	<?php sleep(1);?>
	<script>
		document.getElementById("con3").innerHTML = "<?php echo str_repeat(\'内容3\', 200) ?>";
	</script>
	<?php flush_now()?>
</body>
</html>

这样,打开页面后,页头、正文和页尾的内容一开始显示的是“正在加载……”,但随后内容会逐一显示出来,肿么样,很神奇吧。
但话说回来,这只是模拟的BigPipe,因为每个区块的内容输出之间是阻塞的,需要等待前一个区块处理完了,才能接着处理后一个。而要真正实现BigPipe就得使用多线程,可以通过CURL的异步模式或者pctnl。

关于一些编程语言的运算符“优先级”

Tips:这个问题是铁军师兄发现并提出的,我写篇博客梳理一下。
当我们在学习一门编程语言的时候,我们经常会看到类似这样的一句话:运算符优先级是一套规则。该规则在计算表达式时控制运算符执行的顺序。具有较高优先级的运算符先于较低优先级的运算符执行。
那么事实是不是果真如此呢,下面让我们用Javascript等几门语言为例来验证一下。
首先,在官方的ECMA-262/Core JavaScript手册以及权威的MDN Docs中,分别是这样介绍Javascript运算符优先级的:

All operators have a pre-defined execution priority or precedence. Within a given complex expression, operations will be performed in order of priority.

Operator precedence determines the order in which operators are evaluated. Operators with higher precedence are evaluated first.

两段介绍的意思大致是一样的,都是说运算符优先级决定了运算符执行的先后顺序。运算符优先级高的运算符会被先执行(计算)。我们以往的编程经验似乎也在告诉我们这是真理,然而请先看下面一段代码:

var a=1;
function fn(){
    console.log(a);
}
var b = a++ + fn();

按照Javascript官方手册上对运算符优先级的定义以及的运算符优先级表(如图所示或参考http://rx4ajax-jscore.com/ecmacore/operator/predence.html),我们可以推算运行结果:在最后一行代码中,由于函数调用的括号运算符“()”优先级比自增运算符“++”和算术运算符“+”都要高,所以会先被执行。所以在控制台中被log的a的值应该是1。而事实上,结果是2。这说明了什么?——虽然“()”运算符的优先级比“++”要来得高,但a++却先于fn()被执行。
javascript优先级
图节选自Javascript运算符优先级表(http://rx4ajax-jscore.com/ecmacore/operator/predence.html)
那么在Javascript中是这种状况,在其他语言中又会是什么情况呢?随后,我们分别测试了PHP、C/C++、Java以及C#版本的代码(Python和Ruby中没有自增运算符)。猜猜结果各是什么?
PHP版:

$a=1;
function fn($a1){
    echo $a1;
}
$b = $a++ + fn($a);

C++版:

#include "stdafx.h"
#include <iostream>
using namespace std;
int fn(int a1)
{
	cout<<a1;
	return 0;
}
int _tmain(int argc, _TCHAR* argv[])
{
	int a = 1;
	int b = a++ + fn(a);
	int i;
	cin>>i;
	return 0;
}

揭晓答案:
只有C/C++输出了1,其他语言都输出了2。
这样看来似乎最传统的编译型语言C/C++能得出与其运算符优先级定义相符合的结果。而事实上关于运算符优先级,PHP手册是这样定义的:

The precedence of an operator specifies how “tightly” it binds two expressions together.

意思是运算符优先级决定了两个表达式之间“紧密程度”——也就是说代码的组合方式。这么一来PHP为什么输出2就能解释了。而C#和Java的代码,如果这么解释也能解释得清楚。不过在Java的运算符中,并没有函数调用的“()”运算符。C#当中却是有的。更有趣的是,在C/C++中,前自增/减运算符“++/–”的优先级居然排在了函数调用运算符“()”的前面,仅次于“::”运算符。
结论:混乱啊。。

一道Javascript面试题

var n = 2;
var showNumber = function(){
    n = 1;
    alert(n);
}
(function(){
    n=3;
    alert(n);
})()

你能说出alert出的内容并解释其原因吗?

事件委托在jQuery和KISSY内核中的应用

一直觉得jQuery中的 .live() 方法很神奇,居然可以对动态加入的元素进行绑定。今天终于弄明白了,原来这是通过使用事件委托实现的。

   $('a').live('click', function(){
      //code block1
   })
   

上面的这段代码可以约等于(但不完全等于,总结中说明了为什么):

   $(document).bind('click', function(e){
      if(e.srcElement.tagName == 'a'){
         //code block1
      }
   })
   

此外,jQuery还有一个 .delegate() 方法,可以实现接近 .live() 的效果:

   $('#container').delegate('a','click',function(){
      //code block2
   })
   

jQuery在文档中查找#container,并使用click事件和’a'这一CSS选择器作为参数,把函数绑定到$(‘#container’)上。任何时候只要有事件冒泡到$(‘#container’)上,它就查看该事件是否是click事件,以及该事件的目标元素是否与CSS选择器相匹配。如果两种检查的结果都为真的话,它就执行绑定的函数。
在KISSY 1.2版本中也引入了 .delegate() 方法。
这两种方法中,个人觉得还是 .delegate() 方法效率高。因为 .live() 方法需要把选择器匹配到的元素都包装成jQuery对象;而 .delegate() 方法只在委托的节点上监听冒泡事件。感觉用 .live() 方法进行事件委托比较鸡肋啊,可能这也是为什么其他js框架没有类似方法的原因吧。

javascript元编程创建DOM节点

在使用javascript创建DOM节点的过程中,难免要使用document.createElement、setAttribute、document.createTextNode等冗长、还有浏览器兼容性问题的节点操作方法,虽然有人提议使用字符串拼接后,再使用.innerHTML=可以降低创建节点的成本,而且还有更好的性能,但在我印象中innerHTML并不能适应所有需求(具体神马需求我也忘了),所以下面给大家介绍一种使用javascript元编程技巧来动态创建节点的方法。
那么什么是元编程呢?了解Ruby的同学知道,Ruby的风靡离不开Ruby on Rails(RoR)框架的推动。RoR框架通过魔幻实现质朴的方式,把很多需要用其他语言实现的功能用Ruby来实现,最经典当属RoR框架的ActiveRecord。让我们看一下下面这段代码:

person = Person.find_by_email('xxx@x1989.com')
person.age = 23
person.save

上述代码可以用SQL语句替换,如下所示:

SELECT * FROM person WHERE email = 'xxx@x1989.com'
UPDATE person SET age = 23 WHERE email = 'xxx@x1989.com'

即使不会Ruby和SQL的人也可以看出,采用Ruby语法方式更自然。而正是Rails通过元编程技巧,让我们无须在一个程序中混用两种不同的语言。维基百科上元编程的定义:

编写一些程序来提前生成一些数据或代码供运行时使用,用来生成这些数据信息或代码的程序称为元程序(MetaProgram),编写这种程序就称为元编程(MetaProgramming)。

另一种说法:

元编程是指某类计算机程序的编写,这类计算机程序编写或者操纵其它程序(或者自身)作为它们的数据,或者在运行时完成部分本应在编译时完成的工作。

那么,我希望用javascript实现”create_”+任意HTML5标签名的动态节点生成函数,比如用create_form、create_div、create_input来生成对应名称的节点,使用下面的api:

//①创建只含有文本的节点
var span = create_span('hello world');
或不含文本的节点
var br = create_br();

//②可以在参数1中以对象字面量来定义属性
var input = create_input({
    'type': 'text',
    'size': 20,
    'value': '123'
})

//③用参数2定义文本节点
var a = create_a({
    'href': 'http://www.google.com.hk',
    'title': '谷歌搜索',
    'target': '_blank',
    'rel': 'xxxxx',
    'costom_attr': '自定义属性',
}, '搜索');

//④参数2可以是节点 参数3(可选)是文本节点
var div = create_div({}, create_img({'src': 'http://www.google.com.hk/intl/zh-CN/images/logo_cn.png'}), '这是logo')

//⑤参数2也可以是节点构成的数组
var ul = create_ul({
	'class': 'list1'
}, [create_li('项目1'), create_li('项目2'), create_li('项目3')]);

实现方式:

void function() {
    //支持以"create_"+任意HTML5标签名的动态节点生成函数
    var doc = document, win = window,
    //HTML5标签:
    tags = ["a", "abbr", "acronym", "address", "applet", "area", "article", "aside", "audio", "b", "base", "basefont", "bdo", "big", "blockquote", "body", "br", "button", "canvas", "caption", "center", "cite", "code", "col", "colgroup", "command", "datagrid", "datalist", "datatemplate", "dd", "del", "details", "dialog", "dir", "div", "dfn", "dl", "dt", "em", "embed", "event-source", "fieldset", "figure", "font", "footer", "form", "frame", "frameset", "h1", "h2", "h3", "h4", "h5", "h6", "head", "header", "hr", "html", "i", "iframe", "img", "input", "ins", "isindex", "kbd", "label", "legend", "li", "link", "m", "map", "menu", "meta", "meter", "nav", "nest", "noframes", "noscript", "object", "ol", "optgroup", "option", "output", "p", "param", "pre", "progress", "q", "rule", "s", "samp", "script", "section", "select", "small", "source", "span", "strike", "strong", "style", "sub", "sup", "table", "tbody", "td", "textarea", "tfoot", "th", "thead", "time", "title", "tr", "tt", "u", "ul", "var", "video", "xmp"];
    for (var i = 0; tags[i]; i++) {
        //闭包是个好东东
        win['create_' + tags[i]] = function(tag) {
            return function(attrs, childNode, textNode) {
                return createNode(tag, attrs, childNode, textNode)
            }
        } (tags[i]);
    }
    var createNode = function (tagName, attrs, childNode, textNode) {
        //创建以tagName命名的节点
        var node = doc.createElement(tagName);

        //处理attrs参数 设置节点属性
        typeof attrs === 'string' ? createTextNode(node, attrs) : createAttributeNode(node, attrs); //创建并设置属性节点
        //处理childNode参数 添加子节点
        if (typeof childNode === 'string') {
            createTextNode(node, childNode);
        } else if (childNode && childNode.nodeName) {
            node.appendChild(childNode)
        } else if (childNode instanceof Array) {
            for (var i = 0; childNode[i]; i++) {
                node.appendChild(childNode[i])
            }
        }

        //处理文本节点
        if (textNode && typeof textNode === 'string') {
            createTextNode(node, textNode);
        }
        return node;
    }
    var createAttributeNode = function(node, attrs) {
        for (var i in attrs) {
            //下面这种方式适用于原生的节点属性
            //node[i] = attrs[i];
            //在IE下setAttribute设置某些原生属性会有兼容性问题
            //node.setAttribute(i, attrs[i]);
            //document.createAttribute在IE下设置原生属性会不带引号
            var a = doc.createAttribute(i);
            a.value = attrs[i];
            node.setAttributeNode(a);
        }
    }
    var createTextNode = function (node, text) {
        node.appendChild(doc.createTextNode(text))
    }
} ();

Demo
文档?足够的注释不需要文档!

Javascript惰性函数函数的应用

function addEvent(element, type, func) {
	if (document.addEventListener)
		element.addEventListener(type, func);
	else
		element.attachEvent(type, func);
}

上面的函数在每次调用的时候都会检测document.addEventListener是否存在,并用它绑定一个事件,如果不存在,就使用ie的方式绑定事件。
这个函数工作得很好,尽管它在浏览器的兼容性检测上还不够完善。但从性能的角度出发上,我们可以做得更好:只执行一次检测,就是在函数第一次被调用的时候。
为了达到这个目的,我们使用一个javascript的惰性加载特性(http://peter.michaux.ca/articles/lazy-function-definition-pattern)

function addEvent(element, type, func) {
	if (document.addEventListener) {
		addEvent = function(element, type, func) {
			element.addEventListener(type, func)
		}
	}else{
		addEvent = function(element, type, func) {
			element.attachEvent(type, func)
		}
	}
	return addEvent(element, type, func)
}

Kissy中jsonp api的bug

在相邻的代码里多次调用Kissy的jsonp方法,调用十次大概会出现六次的:Uncaught ReferenceError: jsonp1302852733609 is not defined(anonymous function).同事仙羽查看源码发现,是kissy的jsonp api是调用太快了,产生了同一个时间戳。。两次请求返回的都是同个函数调用,但是kissy里面的jsonp有个垃圾收集的处理,结果把这个函数给删了,第二次再调用的时候就调用不到了。算是kissy的bug吧,应该用guid的。出错代码如下

for(var i=0;i<10;i++){
    KISSY.jsonp('http://110.75.14.39/home/recommendTmallSpu.htm?appID=87&num=4&key=110202',function(data){
        console.log(data);
    })
}

问题就出现在ajax模块下的这行代码:

jsonp = c['jsonpCallback'] || JSONP + S.now();

贴一段jQuery源码中的实现:

// 默认的发起jsonp请求的设置 即url中callback=?的情况 回调函数也是"jsonp_"+时间错
jQuery.ajaxSetup({
	jsonp: "callback",
	jsonpCallback: function() {
		return jQuery.expando + "_" + ( jsc++ );
	}
});

// 装载jsonp的callback函数
jQuery.ajaxPrefilter( "json jsonp", function( s, originalSettings, jqXHR ) {

	var dataIsString = ( typeof s.data === "string" );

	if ( s.dataTypes[ 0 ] === "jsonp" ||
		originalSettings.jsonpCallback ||
		originalSettings.jsonp != null ||
		s.jsonp !== false && ( jsre.test( s.url ) ||
				dataIsString && jsre.test( s.data ) ) ) {
                //响应容器
		var responseContainer,
			jsonpCallback = s.jsonpCallback =
				jQuery.isFunction( s.jsonpCallback ) ? s.jsonpCallback() : s.jsonpCallback,
			previous = window[ jsonpCallback ],
			url = s.url,
			data = s.data,
			replace = "$1" + jsonpCallback + "$2",
			cleanUp = function() {
				window[ jsonpCallback ] = previous;
				// 当有请求时调用callback
				if ( responseContainer && jQuery.isFunction( previous ) ) {
					window[ jsonpCallback ]( responseContainer[ 0 ] );
				}
			};

		if ( s.jsonp !== false ) {
			url = url.replace( jsre, replace );
			if ( s.url === url ) {
				if ( dataIsString ) {
					data = data.replace( jsre, replace );
				}
				if ( s.data === data ) {
					// Add callback manually
					url += (/\?/.test( url ) ? "&" : "?") + s.jsonp + "=" + jsonpCallback;
				}
			}
		}

		s.url = url;
		s.data = data;

		// 装载callback
		window[ jsonpCallback ] = function( response ) {
			responseContainer = [ response ];
		};

		// Install cleanUp function
		jqXHR.then( cleanUp, cleanUp );