javascript 高级技巧详解

函数的使用技巧

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里面的事件处理程序添加更多变化的事件处理程序,这样自定义拖放事件就做好了

时间: 2024-10-25 23:05:34

javascript 高级技巧详解的相关文章

javascript运动详解

javascript运动详解 本文给大家详细介绍下如何使用javascript来实现运动效果,总结的十分全面,附上各种效果的详细示例和演示图,有需要的小伙伴可以参考下. 物体运动原理:通过改变物体的位置,而发生移动变化. 方法: 1.运动的物体使用绝对定位 2.通过改变定位物体的属性(left.right.top.bottom)值来使物体移动.例如向右或左移动可以使用offsetLeft(offsetRight)来控制左右移动. 步骤: 1.开始运动前,先清除已有定时器 (因为:是连续点击按钮,

javascript事件详解笔记

javascript事件详解笔记: 一.事件流 1.事件流: 描述的是页面中接受事件的顺序,有事件冒泡.事件捕获两种. 2.事件冒泡: 由最具体的元素接收,然后逐级向上传播到最不具体的元素的节点(文档). 3.事件捕获: 最不具体的节点先接收事件,而最具体的节点应该是最后接收事件. <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>事件</title&

JavaScript prototype 详解(对prototype 使用的一些讲解)

对JavaScript有一定了解的你,对jquery不陌生吧,那你看jQuery源代码的时候对prototype 也一定有见过,如果对prototype有疑问或者想更深入的去了解与使用它,欢迎你继续往下阅读. 最初的用法是, 为了避免方法在构造器里随机数据被实例化时而产生重复的副本  后来被用在"继承"上面了, 注意, JS语义上是没有继承的, 这里说的是人为的实现.对于下面对JavaScript中类型名称叫做"对象"."函数"."类型

Javascript学习--------详解window窗口对象

对话框: 警告对话框:alert(): 语法:window.alert(src)或者alert(src); 询问回答对话框:confirm(): 语法:window.confrim(question)或者confrim(question); 单击确认,返回true: 单击取消,返回false 提示对话框:prompt(): 语法:window.prompt([showtxt],[defaultTxt])或者prompt([showtxt],[defaultTxt]); 单击确认,返回输入的文本:

JavaScript事件详解-zepto的事件实现

zepto的event 可以结合上一篇JavaScript事件详解-原生事件基础(一)综合考虑源码暂且不表,github里还有中文网站都能下到最新版的zepto.整个event模块不长,274行,我们可以看到,整个event模块,事件绑定核心就是on和off,还有一个trigger用来触发,类观察者模式,可以先看看汤姆大叔的深入理解JavaScript系列(32):设计模式之观察者模式,其余皆为实现的处理函数.首先来个demo: $("#btn").on("click&quo

javascript 函数详解2 -- arguments

今天我们接着上篇文章来继续javascript函数这个主题.今天要讲的是函数对像中一个很重要的属性--arguments. 相关阅读: javascript 函数详解1 -- 概述 javascript 函数详解2 -- arguments Javascript 函数详解3 -- this对象 Javascript 函数详解4 -- 函数的其他属性 Javascript 函数详解5 -- 函数对象的内部函数 arguments对象参数数组引用 arguments是函数对象内部一个比较特殊的类数组

JavaScript正则表达式详解(一)正则表达式入门

JavaScript正则表达式是很多JavaScript开发人员比较头疼的事情,也很多人不愿意学习,只是必要的时候上网查一下就可以啦~本文中详细的把JavaScript正则表达式的用法进行了列表,希望对于大家学习JavaScript正则表达式有一定的帮助. 建立正则表达式对象语法 re = new RegExp(/pattern/[flags]) flags 参数说明: g (全文查找出现的所有 pattern) i (忽略大小写) m (多行查找) 普通字符 描述 \ 将下一个字符标记为一个特

JavaScript正则表达式详解(二)JavaScript中正则表达式函数详解

二.JavaScript中正则表达式函数详解(exec, test, match, replace, search, split) 1.使用正则表达式的方法去匹配查找字符串 1.1. exec方法详解 exec方法的返回值 exec方法返回的其实并不是匹配结果字符串,而是一个对象,简单地修改一下execReg函数,来做一个实验就可以印证这一点: function execReg(reg, str) { var result = reg.exec(str); alert(typeof result

JavaScript 对象详解

1 创建对象的方法 最常见的创建对象方法是通过new调用构造函数,此外还有一个方法就是使用Object.create(),将一个原型对象作为参数传递给Object.create也可以创建一个继承了其属性的新对象.但是和使用new创建对象还有一点差别,那就是构造函数不会被调用.所以如果使用这种方法创建一个foo新对象,其foo.f是undefined的: function Foo(z) { this.f = z; } Foo.prototype.add = function(x, y) { ret