安全的类型检测
在任何值上调用Object原生的toString()方法,都会返回一个[object NativeConstructorName]格式字符串。每个类在内部都有一个[[Class]]属性,这个属性中就指定了各个类型的构造函数名。
使用这种方法判断值属于什么类型最可靠;比如:
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]";
}
判断是不是原生的JSON对象
function isNativeJSON(value){
return window.JSON&&Object.prototype.toString.call(value)=="[object JSON]";
}
在javascript中,自定义的构造函数上述方法检测都会返回[object object]。
作用域安全构造函数
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);
}
}
继承作用域安全的构造函数
function LittleBoy(classNum,name,age,job){
Person.call(this,name,age,job);//Person的作用域是安全的,this并不指向Person实例,所以必须让LittleBoy的原型链指向Person的实例
this.classNum=classNum;
this.getClassNum=function(){
return this.classNum;
}
}
LittleBoy.prototype=new Person();
惰性载入函数
在if判断后,将原函数重新定义,再次执行原函数时,就不用if判断了。也就是在一定环境下,只需第一次有if判断,后面就不需要if判断,从而提高性能。
防篡改对象
var person={name:"ha"};
Object.preventExtensions(person);//禁止再添加新属性和方法,但可以修改和删除现有的属性和方法
调用Object.isExtensible()方法可以判断对象是否可拓展
console.log(Object.isExtensible(person));//false
密封对象
Object.seal(person);//不能添加和删除属性和方法
判断是否是密封对象
Object.isSealed(person);
冻结对象
Object.freeze(person);//不能修改,不能添加,不能删除
判断是否被冻结
console.log(Object.isFrozen(person));//判断是否被冻结
Yielding Processes(屈服/投降 进程)
运行在浏览器中的javascript代码都被分配了一个确定数量的资源。如果脚本运行时间过长,浏览器会弹出一个错误对话框。这对用户体验非常不好。
脚本运行过长的原因通常由两种:
1、过长的或过深的嵌套的函数的调用。
2、进行大量处理的循环。
这两者中后者是比较容易解决的。
例如:
for(var i=0,len=data.length;i<len;i++){
process(data[i]);
}
假如一个process程序需要执行100ms才能完成,如果data数组有10个项目,那么循环处理完这个数组需要花费1s,这花费的时间就有些长了,会严重阻塞后续代码的执行,对用户体验非常不好。我们可以用setTimeout定时函数来对数组分块执行,因为setTimeout可以为代码创建一个队列,在合适的时候插入队列,就不会阻塞后续代码的执行了。不过使用这个数组分块的技术,你需要保证在两个条件下使用:
1、该处理是否应该同步完成。
2、数据是否需要按顺序完成。
如果上述两个问题你都回答否,那么你就可以安心的使用数组分块技术了,例子:
function chunk(array,process,context){
setTimeout(function(){
var item=array.shift();//截取出原数组的第一项,原数组会改变
process.call(context,item);//将运行环境抱定在context上,可选参数
if(array.length>0){
setTimeout(arguments.callee,100);
}
},100);//100毫秒效果很好 }
chunk()方法接收三个参数:处理的数组,用于处理项目的函数,以及可选的运行该函数的环境(不传就为null);
使用事例:
var data=[12,123,1234,453,436,23,23,5,4123,45,346,5634,345,342];
function printValue(item){
var div=document.getElementById("myDiv");
div.innerHTML+=item+"<br>"
}
chunk(data,printValue);
如果想保持原数组不变,可以将原数组拷贝一下再传入使用,用数组的concat()方法就可以拷贝数组:
chunk(data.concat(),printValue);
函数节流
函数节流用于使高频触发的事件,变为指定事件段执行一次,比如说onresize,等等,我们可以将onresize里执行的代码变成低频触发,而不是让onresize变成低频触发,这个是改变不了的。
同样,是使用定时器函数来实现的。
var processor={
timeoutId:null,
performProcessing:function(){
//实际执行的代码
console.log(11);//例子
},
process:function(){
clearTimeout(this.timeoutId);
var that=this;
this.timeoutId=setTimeout(function(){
that.performProcessing();
},100);
}
}
//使用
window.onresize=function(){
processor.process();
}
以上模式可以用throttle()函数来简化 throttle:节流阀
这个函数可以自动进行定时器的设置和清除:
function throttle(method,context){
clearTimeout(method.tId);//清除计时器
method.tId=setTimeout(function(){
method.call(context);//将函数绑定在一定环境上执行
},100)
}
throttle()方法接收两个参数,要执行的函数和在哪个作用域中执行。
使用:
//使用
var resizeDiv=function(){
var div=document.getElementById("myDiv");
div.style.height=div.offsetWidth+"px";
};
window.onresize=function(){
throttle(resizeDiv,window);//window可省略
}
自定义事件
事件是javascript与浏览器交互的主要途径。事件是一种叫作观观察者的实际模式,这是一种创建松散耦合代码的技术。对象可以发布事件,用来表示在该对象声明周期中某个有趣的时刻到了。然后其他对象可以观察该对象,等待这些有趣的时刻到来并通过运行代码来响应。
观察者模式有两类对象组成:主体和观察者。主体负责发布事件,同时观察者通过订阅这些事件来观察该主体。该模式的一个关键概念是主体并不知道观察者的任何事情,也就是说他可以独自存在并正常运作即观察者不存在。从另一方面来说,观察者知道主体并能注册事件的回调函数(事件处理程序)。涉及DOM上时,DOM元素便是主体,你的事件处理代码便是观察者。
事件是与DOM交互的最常见的方式,但他们也可以用于非DOM代码中----通过定义自定义事件。自定义事件背后的概念是创建一个管理事件的对象,让其他对象监听那些事件。实现此功能的基本模式如下定义:
function EventTarget(){
this.handlers={};//存放事件处理程序
} EventTarget.prototype={
constructor:EventTarget,
//addHandler:用来注册给定类型的事件处理程序
addHandler:function(type,handler){//接收两个参数,一个事件类型,一个该类型的事件处理程序
if(typeof this.handlers[type]=="undefined"){//如果handlers对象中没有这个类型的属性,则添加一个这个属性,为数组类型
this.handlers[type]=[];
}
this.handlers[type].push(handler);
},
// fire 用来触发某个事件
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++){
handlers[i](event);
}
}
},
// removeHandler;用于注销某个事件类型的事件处理程序
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);
}
}
}
使用
// 声明一个事件处理程序
function handleMessage(event){
console.log("message receive:"+event.message);
}
// 创建一个新对象
var target=new EventTarget();
// 添加一个事件处理程序(传入事件类型和事件处理程序)
target.addHandler("message",handleMessage);
// 触发事件(传入自定义的event对象)
target.fire({type:"message",message:"hello"});
//删除事件处理程序
target.removeHandler("message",handleMessage);
// 再次,就没有处理程序
target.fire({type:"message",message:"hello"});
因为这种功能封装在一种自定义类型中,其他对象可以继承EventTarget并获得这个行为:
function inheritPrototype(subType,superType){//寄生组合继承方法
var prototype=Object(superType.prototype);
prototype.constructor=subType;
subType.prototype=prototype;
} function Person(name,age){//建一个类
EventTarget.call(this);//改变EventTarget的执行环境
this.name=name;
this.age=age;
}
inheritPrototype(Person,EventTarget);//Person继承EventTarget
Person.prototype.say=function(message){
this.fire({type:"message",message:message});
}
上面采用的是寄生组合继承
然后在Person类的实例上调用自定义方法:
function handleMessage(event){
console.log(event.target.name+"say"+event.message);
}
// 创建新person
var person = new Person("Nicholas",29);
// 添加一个事件处理程序
person.addHandler("message",handleMessage);
// 在该对象上调用一个方法,它触发消息事件
person.say("Hi there");
拖放
简单的拖放需要为文档指定一个onmousemove事件处理程序,指定一个绝对定位的元素,然后让元素移动到鼠标指针的位置:
document.onmousemove=function(event){
var myDiv=document.getElementById("myDiv");
myDiv.style.left=event.clientX+"px";
myDiv.style.top=event.clientY+"px";
}
下面实现一个简单的拖放功能,用的是跨浏览器添加事件的方法,所以需要将跨浏览器事件的对象先写在前面,然后再单独写拖放功能:
var EventUtil={
addHandler:function(element,type,handler){
if(element.addEventListener){
element.addEventListener(type,handler,false);
}else if(element.attachEvent){
element.attachEvent("on"+type,handler);
}else{
element["on"+type]=handler;
}
},
removeHandler:function(element,type,handler){
if(element.removeEventListener){
element.removeEventListener(type,handler,false);
}else if(element.detachEvent){
element.detachEvent("on"+type,handler);
}else{
element["on"+type]=null;
}
},
getEvent:function(event){//获取事件对象
return event?event:window.event;
},
getTarget:function(event){//获取事件目标对象
return event.target||event.srcElement;
},
preventDefault:function(event){//阻止事件默认行为
if(event.preventDefault){
event.preventDefault();
}else{
event.returnValue=false;//IE
}
},
stopPropagation:function(event){//阻止事件冒泡
if(event.stopPropagation){
event.stopPropagation();
}else{
event.cancelBubble=true;//IE
}
}
}
下面来用跨浏览器的方法实现一个拖动案例:
var DragDrop=function(){
var dragging=null,
diffX=0,//鼠标在元素上的水平位置
diffY=0;//鼠标在元素上的垂直位置
function handlerEvent(event){
//获取事件和目标对象
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",handlerEvent);
EventUtil.addHandler(document,"mousemove",handlerEvent);
EventUtil.addHandler(document,"mouseup",handlerEvent);
},
disable:function(){
EventUtil.removeHandler(document,"mousedown",handlerEvent);
EventUtil.removeHandler(document,"mousemove",handlerEvent);
EventUtil.removeHandler(document,"mouseup",handlerEvent);
}
} }
使用
主要在页面上包含这些代码并调用enable()。拖放会自动针对所包含“draggable”类的元素启用:
<div class="box draggable"></div>//元素必须是绝对定位
DragDrop().enable();//调用
为拖放添加自定义事件
拖放功能还不能真正的应用起来,除非能知道什么时候拖动开始了。从这点上看,当前的方法没有提供任何的拖动开始、正在拖动、拖动结束。这时可以使用自定义事件来指定这几个事件的发生,让应用的其它部分与拖动功能进行交互。
由于DragDrop对象是一个使用了模块模式的单例,所以需要一些更改来使用EventTarget类型。首先,创建一个新的EventTarget对象,然后添加enable()和disable()方法,最后返回这个对象。修改如下:
var DragDrop=function(){
var dragdrop=new EventTarget(),//创建一个自定义事件对象
dragging=null,
diffX=0,//鼠标在元素上的水平位置
diffY=0;//鼠标在元素上的垂直位置
function handlerEvent(event){
//获取事件和目标对象
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;
//触发dragstart事件
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",handlerEvent);
EventUtil.addHandler(document,"mousemove",handlerEvent);
EventUtil.addHandler(document,"mouseup",handlerEvent);
};
dragdrop.disable=function(){
EventUtil.removeHandler(document,"mousedown",handlerEvent);
EventUtil.removeHandler(document,"mousemove",handlerEvent);
EventUtil.removeHandler(document,"mouseup",handlerEvent);
};
return dragdrop; }();//自执行
这段代码新增了三个事件,dragstart、drag、dragend。他们都将被拖动的元素设置为了target,并给出了x和y属性来表示当前的位置。他们触发于dragdrop对象上,之后在返回对象前给对象增加了enable()和disable()方法。这些模块模式中的细小更改令DragDrop对象支持了事件,如:
DragDrop.enable();//执行拖放效果
DragDrop.addHandler("dragstart",function(event){
var status=document.getElementById("status");
status.innerHTML="Started dragging"+event.target.id;
});
DragDrop.addHandler("drag",function(event){
var status=document.getElementById("status");
status.innerHTML+="<br/> Dragged"+event.target.id+"to("+event.x+","+event.y+")";
});
DragDrop.addHandler("dragend",function(event){
var status=document.getElementById("status");
status.innerHTML+="<br/> Dragged"+event.target.id+"at("+event.x+","+event.y+")";
});