函数的使用技巧
javascript内置的类型检测机制并非完全可靠。在Object.prototype.toString()方法没有被修改的前提下,可以使用下面的安全检测方法检测是不是原生的:
function isArray(value){
return Object.prototype.toString.call(value)=="[object Array]";
}
function isFunction(value){
return Object.prototype.toString.call(value)=="[object Function]";
}
function isRegExp(value){
return Object.prototype.toString.call(value)=="[object RegExp]";
}
这个技巧可以对任何对象给出正确结论
作用域安全的构造函数
一般的构造函数都要使用new操作符来调用,但是有时可能会忘记这个函数是不是构造函数,这样会产生错误。为了避免这种错误,要像下面这样进行设计构造函数:
function Person(name,age,job){
if(this instanceof Person){
this.name=name;
this.age=age;
this.job=job;
}else{
return new Person(name,age,job);
}
}
这样做的好处是调用它无论是否使用new操作符,都会返回一个Person的新实例,避免了在全局对象上意外设置属性。
除非你单纯基于构造函数窃取来实现继承,推荐作用域安全的构造函数作为最佳实践。
惰性载入函数
下面看一个更改的示例:
function createXHR(){
if(typeof XMLHttpRequest!="undefined"){
createXHR=function(){ return new XMLHttpRequest(); };
}else if(typeof ActiveXObject!="undefined"){
createXHR=function(){
if(typeof arguments.callee.activeXString!="string"){
var versions=["MSXML2.XMLHttp.6.0","MSXML2.XMLHttp.3.0","MSXML2.XMLHttp"],i,len;
for(i=0,len=versions.length;i<len;i++){
try{
new ActiveXObject(versions[i]);
arguments.callee.activeXString=versions[i];
break;
}catch(ex){
//skip;
}
}
}
return new ActiveXObject(arguments.callee.activeXString);
};
}else{
createXHR=function(){
throw new Error("No XHR object available.");
};
}
return createXHR();
}
这样的话只会在函数的第一次执行时损失性能,以后执行就不会了。考虑到这个函数是基于浏览器的,这样修改会变得棒极了!
另一种方式就是把每一部分都变成一个函数,作为返回值返回。这两种之间的区别非常的微妙。
函数的绑定
函数绑定要创建一个函数,可以在特定的this环境中以指定参数调用另一个函数。该技巧常常和回调函数与事件处理程序一起使用,以便在将函数作为变量传递的同时保留代码的执行环境。由于代码之中存在着this变量,而this在当前环境下指向确定的对象,但是当更改代码的执行环境时,就会出现问题了。为了解决这个问题,javascript函数库中实现了一个bind()函数来解决这个问题。
一个简单的bind()函数接收一个函数和一个环境,并返回一个在给定环境中调用给定函数的函数,并且将所有参数原封不动传递过去。语法如下:
function bind(fn,context){
return function(){
return fn.apply(context,arguments);
}
}
注意这里使用的arguments并不是bind()的,是内部函数的。
var handler={
message:"Event handled",
handleClick:function(event){
alert(this.message);
}
};
var btn=document.getElementById("my-btn");
EventUtil.addHandler(btn,"click",bind(handler.handleClick,handler));
ECMAScript5为所有函数定义了一个原生的bind()方法,进一步简化了操作。
var handler={
message:"Event handled",
handleClick:function(event){
alert(this.message);
}
};
var btn=document.getElementById("my-btn");
EventUtil.addHandler(btn,"click",handler.handleClick.bind(handler));
它们主要用于事件处理程序以及setTimeout()和setInterval()。然而被绑定函数与普通函数相比有更多的开销,它们需要更多内存,同时也因为多重函数调用稍微慢一些,所以最好只在必要时使用。
函数柯里化
它用于创建已经设置好了一个或多个参数的函数。函数柯里化的基本方法是:使用一个闭包返回一个函数。当函数被调用时,返回的函数还需要设置一些传入的参数。
柯里化函数通常由以下步骤动态的创建:调用另一个函数并为它传入要柯里化的函数和必要参数。下面是创建柯里化函数的通用方式:
function curry(fn){
var args=Array.prototype.slice.call(arguments,1);
return function(){
var innerArgs=Array.prototype.slice.call(arguments);
var finalArgs=args.concat(innerArgs);
return fn.apply(null,finalArgs);
}
}
这种变化也需要额外的开销
防篡改对象
javascript共享的本质一直是一件很头疼的事。但是ECMAScript5解决了这个问题。增加了几个方法,可以指定对象的行为。但是一旦把对象定义为防篡改就无法撤销了。
不可扩展可以使用这个方法:Object.preventExtensions(obj);
一旦设置防扩展,对象就无法添加新的属性和方法。已有的属性方法不受影响,这些属性方法仍然可以修改和删除。
密封对象可以使用方法:Object.seal(obj);
一旦设置密封对象,不可以进行扩展,已有属性和方法不可以删除,但是属性值是可以修改的。
冻结对象可以使用方法:Object.freeze(obj);
一旦设置冻结对象,既不可以扩展,又是密封的,只有通过访问器的set函数才可以修改属性值,其他情况下不允许修改。
Object.isExtensible(obj);//检测是否可扩展
Object.isSealed(obj);//检测是否封闭的
ObjectisFrozen(obj);//检测是否是冻结的
高级定时器
为了解决setInterval的一些执行问题,下面是采用链式setTimeout的方式来规避:
setTimeout(function(){
// add code here
setTimeout(arguments.callee,interval);
},interval);
Yielding Processes
脚本长时间运行的原因:过长的、过深嵌套的函数调用;进行大量的处理循环。
在展开循环之前,你需要考虑两个重要的问题:
1:该出事是否必须同步完成?
2:数据是否必须按顺序完成?
当你发现有些循环占用大量的事件,同时对上述两个问题答案都是否,那么可以使用定时器来分隔这个循环。
setTimeout(function(){
//取出下一个条目处理
var item=array.shift();
process(item);
//还有条目,再设置一个定时器
if(array.length>0){
setTimeout(arguments.callee,interval);
}
},interval);
函数节流
对一些持续不断触发的事件,如果建立的事件处理程序不够好的话,会导致浏览器崩溃或者其他的事故。为了规避这个问题,可以使用定时器对事件处理程序进行节流。
函数节流背后的基本思想是:一些代码不可以在没有间断的情况下连续重复执行。具体做法是:第一次调用函数,创建一个定时器,在指定的时间间隔后运行代码。当第二次调用该函数时,它会清除前一次的定时器,并设置另一个。如果前一个定时器已经执行过了,这个操作就没有任何意义。然而如果前一个定时器尚未执行,其实就是将其替换为一个新的定时器。目的就是执行函数的请求停止了一段时间之后才执行。以下是该模式的基本形式:
var processor={
timeoutId:null,
performProcessing:function(){
//实际执行的处理程序
},
process:function(){
clearTimeout(this.timeoutId);
var that=this;
this.timeoutId=setTimeout(function(){
that.performProcessing();
},1000);
}
};
processor.process();//尝试开始执行
只要代码是周期性执行的,都应该用节流。处理的速率根据需求设置,上面的例子是1000毫秒。
自定义事件
事件的模式是采用观察者模式建立的。下面是一段自定义事件的代码示例,注意观察和体会:
function EventTarget(){
this.handlers={};
}
EventTarget.prototype={
constructor:EventTarget,
addHandler:function(type,handler){
if(typeof this.handlers[type]=="undefined"){
this.handlers[type]=[];
}
this.handlers[type].push(handler);
},
removeHandler:function(type,handler){
if(this.handlers[type] instanceof Array){
var handlers=this.handlers[type];
for(var i=0,len=handlers.length;i<len;i++){
if(handlers[i]===handler)break;
}
handlers.splice(i,1);
}
},
fire:function(event){
if(!event.target)event.target=this;
if(this.handlers[event.type] instanceof Array){
var handlers=this.handlers[event.type];
for(var i=0,len=handlers.length;i<len;i++){
if(handlers[i]===handler)break;
}
}
}
}
当其他的节点继承EventTarget的时候,它也会有上面的事件行为了。
拖放
这是一个非常流行的自定义事件。非常不客气的说,如果你不会,你就不要说自己学过自定义事件。
var DragDrop=function(){
var dragging=null;
function handleEvent(event){
var event=EventUtil.getEvent(event);
var target=EventUtil.getTarget(event);
switch(event.type){
case "mousedown": if(target.className.indexOf("draggable")>-1){
dragging=target;
}
break;
case "mousemove": if(dragging!=null){
dragging.style.left=event.clientX+"px";
dragging.style.top=event.clientY+"px";
}
break;
case "mouseup": dragging=null;
break;
}
};
return {
enable:function(){
EventUtil.addHandler(document,"mousedown",handleEvent);
EventUtil.addHandler(document,"mousemove",handleEvent);
EventUtil.addHandler(document,"mouseup",handleEvent);
},
disable:function(){
EventUtil.removeHandler(document,"mousedown",handleEvent);
EventUtil.removeHandler(document,"mousemove",handleEvent);
EventUtil.removeHandler(document,"mouseup",handleEvent);
}
}
}
注意元素被拖放,他必须是绝对定位。
<div class="draggable" style="position:absolute;background:red"/>
但是使用之后会发现一些瑕疵,我们可以做一些进一步的修改
var DragDrop=function(){
var dragging=null,diffX=0,diffY=0;
function handleEvent(event){
var event=EventUtil.getEvent(event);
var target=EventUtil.getTarget(event);
switch(event.type){
case "mousedown": if(target.className.indexOf("draggable")>-1){
dragging=target;
diffX=event.clientX-target.offsetLeft;
diffY=event.clientY-target.offsetTop;
}
break;
case "mousemove": if(dragging!=null){
dragging.style.left=(event.clientX-diffX)+"px";
dragging.style.top=(event.clientY-diffY)+"px";
}
break;
case "mouseup": dragging=null;
break;
}
};
return {
enable:function(){
EventUtil.addHandler(document,"mousedown",handleEvent);
EventUtil.addHandler(document,"mousemove",handleEvent);
EventUtil.addHandler(document,"mouseup",handleEvent);
},
disable:function(){
EventUtil.removeHandler(document,"mousedown",handleEvent);
EventUtil.removeHandler(document,"mousemove",handleEvent);
EventUtil.removeHandler(document,"mouseup",handleEvent);
}
}
}
这时需要提供一些触发的自定义事件了
var DragDrop=function(){
var dragdrop=new EventTarget(),dragging=null,diffX=0,diffY=0;
function handleEvent(event){
var event=EventUtil.getEvent(event);
var target=EventUtil.getTarget(event);
switch(event.type){
case "mousedown": if(target.className.indexOf("draggable")>-1){
dragging=target;
diffX=event.clientX-target.offsetLeft;
diffY=event.clientY-target.offsetTop;
dragdrop.fire({type:"dragstart",target:dragging,x:event.clientX,y:event.clientY});
}
break;
case "mousemove": if(dragging!=null){
dragging.style.left=(event.clientX-diffX)+"px";
dragging.style.top=(event.clientY-diffY)+"px";
dragdrop.fire({type:"drag",target:dragging,x:event.clientX,y:event.clientY});
}
break;
case "mouseup": dragdrop.fire({type:"dragend",target:dragging,x:event.clientX,y:event.clientY});
dragging=null;
break;
}
};
dragdrop.enable:function(){
EventUtil.addHandler(document,"mousedown",handleEvent);
EventUtil.addHandler(document,"mousemove",handleEvent);
EventUtil.addHandler(document,"mouseup",handleEvent);
};
dragdrop.disable:function(){
EventUtil.removeHandler(document,"mousedown",handleEvent);
EventUtil.removeHandler(document,"mousemove",handleEvent);
EventUtil.removeHandler(document,"mouseup",handleEvent);
};
return dragdrop;
}
还可以为switch里面的事件处理程序添加更多变化的事件处理程序,这样自定义拖放事件就做好了