分类目录归档:JavaScript

对大量子节点DOM操作的最佳实践方式

有时要处理一个DOM节点下面的大量子节点,比如对一个ul一次性插入几百个li,或者清空一个ul下面的所有li,或者是替换掉ul下面的所有内容。本文记录一下最佳的实践方式。

插入子节点

用innerHTML的方式要优于appendChild,但切记不要在循环里用innerHTML += “str”这样的方式,可以先把字符串拼接好,最后再赋值给innerHTML。

替换全部子节点

用replaceChild的方式要优于直接给innerHTML赋值。在firefox下,如果一个元素有大量子节点,给这个元素的innerHTML赋值将会十分的慢,目前最好的办法是Clone这个元素得到新元素,然后给新元素的innerHTML赋值,最后用新元素替换掉旧元素。代码如下:

var str = '', 
	test = document.getElementById('test'),
	newUL = test.cloneNode(false);

for(var i = 0; i < iteration; i++) {
	str += '<li>Test</li>';
}
newUL.innerHTML = str;
test.parentNode.replaceChild(newUL, test);

清除全部子节点

用replaceChild的方式要优于innerHTML = ”,道理同“替换全部子节点”。不要用removeChild循环清空一个元素下面的所有子节点,这种方式在IE 8下非常慢。代码如下:

var test = document.getElementById('test'),
	newUL = test.cloneNode(false);

test.parentNode.replaceChild(newUL, test);

使用minify合并YUI请求

介绍

minify是一个基于php的开源项目,用它可以合并多个JS与CSS的请求。

下载与安装

下载链接:http://code.google.com/p/minify/

将下载下来的压缩包解压缩,将里面的min目录放到网站的根目录下。打开min目录修改里面的config.php文件,将$min_serveOptions[‘minApp’][‘maxFiles’] = 10;的值改大一些,这个参数表示一次请求最多可以合并多少个文件,因为YUI的细粒度很高,所以一次请求的模块文件很多,默认值10显然不够。同时将$min_cachePath = ‘/tmp’;这行的注释去掉,可以提升性能。

完成上面的步骤可以在浏览器测试一下,我机器的测试地址是http://localhost:8086/min/?f=/build/oop/oop.js,/build/event-custom/event-custom.js

注意服务器要开启mod_rewrite模块

改造YUI loader

因为YUI loader构造的请求地址是类似http://yui.yahooapis.com/combo?3.3.0/build/classnamemanager/classnamemanager-min.js&3.3.0/build/oop/oop-min.js&3.3.0/build/event-custom/event-custom-min.js&3.3.0/build/event/event-base-min.js&3.3.0/build/pluginhost/pluginhost-min.js&3.3.0/build/dom/dom-min.js&3.3.0/build/node/node-min.js&3.3.0/build/event/event-delegate-min.js&3.3.0/build/attribute/attribute-base-min.js&3.3.0/build/base/base-base-min.js&3.3.0/build/plugin/plugin-min.js&这样的,URL没有参数名,每个JS文件用”&”号分隔。而minify的地址格式是http://localhost:8086/min/?f=/build/oop/oop.js,/build/event-custom/event-custom.js,可以看出minify的请求地址用’f’当作参数名,每个JS地址用”,”号分隔。所以要对YUI loader进行一下改造,让它可以构造出符合minify格式的请求地址。

进入到YUI的build/loader文件夹,将loader.js备份一下,之后编辑loade.js,找到如下行:

url += frag;
if (i < (len - 1)) {
    url += '&';
}

替换成下面的代码

//fixed YUI 3.1.0 combo bug
//http://yuilibrary.com/projects/yui3/ticket/2528680
if(url.slice(-3) ==='.js' || url.slice(-4) === '.css'){
    //for K2
    //使用Minfy 取代 Combo Handler
    //url += '&';
    url += ',';
}

url += frag;

// there is a bug that combo?a.css&b.css&
/*
if (i < (len - 1)) {
    url += '&';
}
*/

在YUI使用minify

做完上面的工作之后,就可以在YUI中使用自己的combine服务了。在实例化YUI的时候加上如下的配置项:

YUI({
    combine: true,
    comboBase: '/min/?b=build&f=',
    root: ''
}).use('placeholder', function(Y) {
	Y.all('.test').plug(Y.Plugin.Placeholder);
});

阶乘

// 定义:计算n的阶乘,并把结果传到con中
function fac(n, con) {
    if(n <= 1) {
        // 直接将1传到con中,并执行
        con(1);
    } else {
        // 调用:求解n-1的阶乘
        arguments.callee(n - 1, function(r) {
            // 将n-1阶乘的结果与n相乘
            con(n * r);
        });
    }
}

// 调用:计算5的阶乘
fac(5, function(r) {
    console.debug(r);
});

Nodejs和MongoDB初体验

学习了一下Nodejs和MongoDB,写了个示例程序,读取数据库中产品的列表。

var http = require("http"),
	mongo = require("mongodb"),
	events = require("events");

http.createServer(function(req, res) {

	var products_emitter = new events.EventEmitter(),
	    // 创建到northwind数据库的链接。相当于use northwind
	    db = new mongo.Db("northwind", new mongo.Server('localhost', 27017, {}), {});
	
	var listener = function(products) {
	    var html = [], len = products.length;
	    html.push('<!DOCTYPE html>');
	    html.push('<html>');
	    html.push('<head>');
	    html.push('<title>Nodejs</title>');
	    html.push('</head>');
	    html.push('<body>');	 
	    if(len > 0) {
	    	html.push('<ul>');					
	    	for(var i = 0; i < len; i++) {
    			html.push('<li>' + products[i].name + '</li>');
	    	}
	    	html.push('</ul>');
	    }
	    html.push('</body>');
	    html.push('</html>');
	    
	    res.writeHead(200, "Content-Type: text/html");
	    res.write(html.join(''));
	    res.end();
	    
	    clearTimeout(timeout);
	}
	products_emitter.on('products', listener);
	
	var timeout = setTimeout(function() {
	    products_emitter.emit('products', []);
	    products_emitter.removeListener('products', listener);
	}, 10000);
	        
	db.open(function() {
	    // 打开名为products的表
		db.collection("products", function(err, collection) {
		    // select * from products 相当于db.products.find()
			collection.find(function(err, cursor) {
				cursor.toArray(function(err, items) {
					products_emitter.emit('products', items);
				});
			});
		});
	});
	
}).listen(8000);

console.log("Started");

新浪操作textarea的工具函数

从新浪的库上copy下来的操作textarea的工具函数,用做学习研究目的。

App.TextareaUtils = (function () {
    var it = {},
        ds = document.selection;
    it.selectionStart = function (oElement) {
        if (!ds) {
            return oElement.selectionStart
        }
        var er = ds.createRange(),
            value, len, s = 0;
        var er1 = document.body.createTextRange();
        er1.moveToElementText(oElement);
        for (s; er1.compareEndPoints("StartToStart", er) < 0; s++) {
            er1.moveStart("character", 1)
        }
        return s
    };
    it.selectionBefore = function (oElement) {
        return oElement.value.slice(0, it.selectionStart(oElement))
    };
    it.selectText = function (oElement, nStart, nEnd) {
        oElement.focus();
        if (!ds) {
            oElement.setSelectionRange(nStart, nEnd);
            return
        }
        var c = oElement.createTextRange();
        c.collapse(1);
        c.moveStart("character", nStart);
        c.moveEnd("character", nEnd - nStart);
        c.select()
    };
    it.insertText = function (oElement, sInsertText, nStart, nLen) {
        oElement.focus();
        nLen = nLen || 0;
        if (!ds) {
            var text = oElement.value,
                start = nStart - nLen,
                end = start + sInsertText.length;
            oElement.value = text.slice(0, start) + sInsertText + text.slice(nStart, text.length);
            it.selectText(oElement, end, end);
            return
        }
        var c = ds.createRange();
        c.moveStart("character", -nLen);
        c.text = sInsertText
    };
    it.getCursorPos = function (obj) {
        var CaretPos = 0;
        if ($IE) {
            obj.focus();
            var range = null;
            range = ds.createRange();
            var stored_range = range.duplicate();
            stored_range.moveToElementText(obj);
            stored_range.setEndPoint("EndToEnd", range);
            obj.selectionStart = stored_range.text.length - range.text.length;
            obj.selectionEnd = obj.selectionStart + range.text.length;
            CaretPos = obj.selectionStart
        } else {
            if (obj.selectionStart || obj.selectionStart == "0") {
                CaretPos = obj.selectionStart
            }
        }
        return CaretPos
    };
    it.getSelectedText = function (obj) {
        var selectedText = "";
        var getSelection = function (e) {
            if (e.selectionStart != undefined && e.selectionEnd != undefined) {
                return e.value.substring(e.selectionStart, e.selectionEnd)
            } else {
                return ""
            }
        };
        if (window.getSelection) {
            selectedText = getSelection(obj)
        } else {
            selectedText = ds.createRange().text
        }
        return selectedText
    };
    it.setCursor = function (obj, pos, coverlen) {
        pos = pos == null ? obj.value.length : pos;
        coverlen = coverlen == null ? 0 : coverlen;
        obj.focus();
        if (obj.createTextRange) {
            var range = obj.createTextRange();
            range.move("character", pos);
            range.moveEnd("character", coverlen);
            range.select()
        } else {
            obj.setSelectionRange(pos, pos + coverlen)
        }
    };
    it.unCoverInsertText = function (obj, str, pars) {
        pars = (pars == null) ? {} : pars;
        pars.rcs = pars.rcs == null ? obj.value.length : pars.rcs * 1;
        pars.rccl = pars.rccl == null ? 0 : pars.rccl * 1;
        var text = obj.value,
            fstr = text.slice(0, pars.rcs),
            lstr = text.slice(pars.rcs + pars.rccl, text == "" ? 0 : text.length);
        obj.value = fstr + str + lstr;
        this.setCursor(obj, pars.rcs + (str == null ? 0 : str.length))
    };
    return it
})();

JavaScript解析QueryString

function PageQuery(q) {
    if (q.length > 1) this.q = q.substring(1, q.length);
    else this.q = null;
    this.keyValuePairs = new Array();
    if (q) {
        for (var i = 0; i < this.q.split("&").length; i++) {
            this.keyValuePairs[i] = this.q.split("&")[i];
        }
    }
    this.getKeyValuePairs = function () { return this.keyValuePairs; }
    this.getValue = function (s) {
        for (var j = 0; j < this.keyValuePairs.length; j++) {
            if (this.keyValuePairs[j].split("=")[0] == s)
                return this.keyValuePairs[j].split("=")[1];
        }
        return false;
    }
    this.getParameters = function () {
        var a = new Array(this.getLength());
        for (var j = 0; j < this.keyValuePairs.length; j++) {
            a[j] = this.keyValuePairs[j].split("=")[0];
        }
        return a;
    }
    this.getLength = function () { return this.keyValuePairs.length; }
}
function queryString(key) {
    var page = new PageQuery(window.location.search);
    return unescape(page.getValue(key));
}

弹出窗口的兼容方案

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
	<meta http-equiv="Content-Type" content="text/html;charset=UTF-8" />
	<title>主页面</title>
	<style type="text/css">
	html, body {
		margin: 0;
		padding: 0;
	}
	body {
	}
	#div1 {
		height: 2000px;
	}
	#floatedLayer {
		position: fixed;
		_position: absolute;
	}
	</style>
</head>
<body>
	<div id="floatedLayer">
		<iframe src="demo.html" width="580" height="542" frameborder="no"></iframe>
	</div>
	<div id="div1"></div>
	<script type="text/javascript">
		var floatedLayer = document.getElementById("floatedLayer");
		function adjustPopupWin() {
			var height = document.documentElement.clientHeight,
				width = document.documentElement.clientWidth;				
			floatedLayer.style.left = Math.max((width - floatedLayer.offsetWidth) / 2, 0) + "px";
			floatedLayer.style.top = Math.max((height - floatedLayer.offsetHeight) / 2, 0) + "px";
		}
	</script>
	<!--[if IE 6]>
	<script type="text/javascript">
		function adjustPopupWin() {
			var height = document.documentElement.clientHeight,
				width = document.documentElement.clientWidth;
			floatedLayer.style.left = Math.max((width - floatedLayer.offsetWidth) / 2, 0) + document.documentElement.scrollLeft + "px";
			floatedLayer.style.top = Math.max((height - floatedLayer.offsetHeight) / 2, 0) + document.documentElement.scrollTop + "px";
		}
		window.onscroll = adjustPopupWin;
	</script>
	<![endif]-->
	<script type="text/javascript">
		window.onresize = adjustPopupWin;
		adjustPopupWin();
	</script>
</body>
</html>

判断元素位置关系的一些方法

在很多场景下,要判断一个元素是不是包含另一个元素,总结了一下,有下列的方法:

一,遍历节点树,这个方法是最容易的,也没有什么兼容性问题。在“mouseenter和mouseleave”这篇日志中有演示,可以看一下。

二,对于IE浏览器,DOM元素有个contains的方法可以判断包含关系。比如div1.contains(div2),如果返回的结果为true就说明div1包含了div2,反之亦然。

三,DOM3的Node.compareDocumentPosition方法。
(div1.compareDocumentPosition(div2) & 0x10) === 0x10如果为true的话,说明div1包含div2。
详细的文档见:https://developer.mozilla.org/en/DOM/Node.compareDocumentPosition

四,强大的位置关系函数。

 // Compare Position - MIT Licensed, John Resig
function comparePosition(a, b){
 return a.compareDocumentPosition ?
 a.compareDocumentPosition(b) :
 a.contains ?
  ( a != b && a.contains(b) && 16 ) +
  ( a != b && b.contains(a) && 8 ) +
  ( a.sourceIndex >= 0 && b.sourceIndex >= 0 ?
   (a.sourceIndex < b.sourceIndex && 4 ) +
   (a.sourceIndex > b.sourceIndex && 2 ) :
   1 ) :
  0;
}

底部浮动条的一种兼容方案

Demo

原理:在标准浏览器下用position: fixed的方式就可以了。IE6下面用overlay.className = overlay.className迫使浏览器重新布局,以达到position: fixed的效果。

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
<head>
	<meta http-equiv="Content-Type" content="text/html;charset=UTF-8" />
	<title>出师表</title>
	<style type='text/css'>
	html, body {
		margin: 0;
		padding: 0;
	}
	body {
		/* 这里不能加position: relative */
	}
	#content {
		font-family: 微软雅黑;
		font-size: 36px;
		line-height: 60px;
		width: 960px;
		margin: 0 auto;
	}
	#content p {
		text-indent: 2em;
	}
	#overlay {
		opacity: .5;
		filter: alpha(opacity=50);
		background: #ccc;
		width: 100%;
		height: 100px;
		position: fixed;
		_position: absolute;
		bottom: 0;
	}
	</style>
</head>
<body>
	<div id='content'><p>先帝创业未半,而中道崩殂;今天下三分,益州疲敝,此诚危急存亡之秋也。然侍卫之臣,不懈于内;忠志之士,忘身于外者:盖追先帝之殊遇,欲报之于陛下也。诚宜开张圣听,以光先帝遗德,恢弘志士之气;不宜妄自菲薄,引喻失义,以塞忠谏之路也。宫中府中,俱为一体;陟罚臧否,不宜异同:若有作奸犯科,及为忠善者,宜付有司,论其刑赏,以昭陛下平明之治;不宜偏私,使内外异法也。侍中、侍郎郭攸之、费依、董允等,此皆良实,志虑忠纯,是以先帝简拔以遗陛下:愚以为宫中之事,事无大小,悉以咨之,然后施行,必得裨补阙漏,有所广益。将军向宠,性行淑均,晓畅军事,试用之于昔日,先帝称之曰"能",是以众议举宠为督:愚以为营中之事,事无大小,悉以咨之,必能使行阵和穆,优劣得所也。亲贤臣,远小人,此先汉所以兴隆也;亲小人,远贤臣,此后汉所以倾颓也。先帝在时,每与臣论此事,未尝不叹息痛恨于桓、灵也!侍中、尚书、长史、参军,此悉贞亮死节之臣也,愿陛下亲之、信之,则汉室之隆,可计日而待也。</p><p>臣本布衣,躬耕南阳,苟全性命于乱世,不求闻达于诸侯。先帝不以臣卑鄙,猥自枉屈,三顾臣于草庐之中,谘臣以当世之事,由是感激,遂许先帝以驱驰。后值倾覆,受任于败军之际,奉命于危难之间:尔来二十有一年矣。先帝知臣谨慎,故临崩寄臣以大事也。授命以来,夙夜忧虑,恐付托不效,以伤先帝之明;故五月渡泸,深入不毛。今南方已定,甲兵已足,当奖帅三军,北定中原,庶竭驽钝,攘除奸凶,兴复汉室,还于旧都:此臣所以报先帝而忠陛下之职分也。至于斟酌损益,进尽忠言,则攸之、依、允等之任也。愿陛下托臣以讨贼兴复之效,不效则治臣之罪,以告先帝之灵;若无兴复之言,则责攸之、依、允等之咎,以彰其慢。陛下亦宜自谋,以谘诹善道,察纳雅言,深追先帝遗诏。臣不胜受恩感激!今当远离,临表涕泣,不知所云。</p>
	</div>
	<div id='overlay'></div>
<!--[if IE 6]>
<script type="text/javascript">
(function(){
	var overlay = document.getElementById('overlay'), t;
	window.onscroll = function() {
		t && clearTimeout(t);
		t = setTimeout(function() {
			// reflow
			overlay.className = overlay.className;
		}, 13);
	};
})();
</script>
<![endif]-->
</body>
</html>

几个版本的Y组合子

C#版

static Func<T, TResult> Fix<T, TResult>(Func<Func<T, TResult>, Func<T, TResult>> f)
{
    return x => f(Fix(f))(x);
}

static Func<T1, T2, TResult> Fix<T1, T2, TResult>(Func<Func<T1, T2, TResult>, Func<T1, T2, TResult>> f)
{
    return (x, y) => f(Fix(f))(x, y);
}

JavaScript版

var Y = function (F) {
 return (function (x) {
  return F(function (y) { return (x(x))(y);});
  })
		(function (x) {
  return F(function (y) { return (x(x))(y);});
  }) ;
} ;

应用

// var g = λf.λn.(n <= 1 ? 1 : n * f(n - 1))
var g = function(f) {
    return function(n) {
        if(n <= 1) {
            return 1
        } else {
            return n*f(n-1);
        }
    }
}

alert(Y(g)(5))