原生数组的构造函数名与全局做用域无关,所以使用toString()就能保证返回一致的值javascript
function isArray(value){ return Object.prototype.toString.call(value)=="[object Array]"; }
基于这一思路来测试某个值是否是原生函数或正则表达式java
function isFunction(value){ return Object.prototype.toString.call(value)=="[object Function]"; } function isRegExp(value){ return Object.prototype.toString.call(value)=="[object RegExp]"; }
做用域安全的构造函数在进行任何更改以前,首先确认this对象是正确类型的实例,若是不是,那么会建立新的实例并返回正则表达式
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); } } var person1 = Person("Nicholas", 29, "Software Engineer"); alert(window.name); //"" alert(person1.name); //"Nicholas" var person2 = new Person("Shelby", 34, "Ergonomist"); alert(person2.name); //"Shelby
使用构造函数窃取模式的继承且不适用原型链,这个继承可能被破坏数组
function Polygon(sides){ if (this instanceof Polygon) { this.sides = sides; this.getArea = function(){ return 0; }; } else { return new Polygon(sides); } } function Rectangle(width, height){ Polygon.call(this, 2); this.width = width; this.height = height; this.getArea = function(){ return this.width * this.height; }; } var rect = new Rectangle(5, 10); alert(rect.sides); //undefined
上面的代码中,Polygon构造函数是做用域安全的,然而Rectangle构造函数则不是。若是构造函数窃取结合使用原型链或者寄生组合则能够解决这个问题安全
function Polygon(sides){ if (this instanceof Polygon) { this.sides = sides; this.getArea = function(){ return 0; }; } else { return new Polygon(sides); } } function Rectangle(width, height){ Polygon.call(this, 2); this.width = width; this.height = height; this.getArea = function(){ return this.width * this.height; }; } Rectangle.prototype = new Polygon(); var rect = new Rectangle(5, 10); alert(rect.sides); //2
惰性载入表示函数执行的分支仅会发生一次。闭包
第一种实现惰性载入的方法,在函数被调用时再处理函数。在第一次调用的过程当中,该函数会覆盖为另外一个按什么时候方式执行的函数,这样任何对原函数的调用都不用再通过执行的分支了app
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(); }
第二种实现惰性载入的方式是在声明函数时就指定适当的函数,这样,第一次调用函数时就不会丧失性能了,而在代码首次加载的时候回损失一点性能ide
var createXHR = (function(){ if (typeof XMLHttpRequest != "undefined"){ return function(){ return new XMLHttpRequest(); }; } else if (typeof ActiveXObject != "undefined"){ return 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 { return function(){ throw new Error("No XHR object available."); }; } })();
一个简单的bind()函数接受一个函数和一个环境,并返回一个在给定环境中调用给定函数的函数,而且将全部参数原封不动传递过去函数
function bind(fn, context){ return function(){ return fn.apply(context, arguments); }; }
当调用返回的函数时,它会在给定环境中执行被传入的函数并给出全部参数性能
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()方法,进一步简单了操做,不用再本身定义bind()函数了,而是能够直接在函数上调用这个方法
var handler = { message: "Event handled", handleClick: function(event){ alert(this.message + ":" + event.type); } }; var btn = document.getElementById("my-btn"); EventUtil.addHandler(btn, "click", handler.handleClick.bind(handler));
柯里化函数一般由一下步骤动态建立:调用另外一个函数并为它传入要柯里化的函数和必要参数
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); }; }
ECMAScript5的bind()方法也实现函数柯里化,只要在this的值以后再传入另外一个参数便可
var handler = { message: "Event handled", handleClick: function(name, event){ alert(this.message + ":" + name + ":" + event.type); } }; var btn = document.getElementById("my-btn"); EventUtil.addHandler(btn, "click", handler.handleClick.bind(handler, "my-btn"));
Object.preventExtensions()方法能够改变这个行为,不能再给对象添加属性和方法
var person={name:"Nicholas"}; Object.preventExtensions(person); person.age=29; alert(person.age);//undefined
Object.isExtensible()方法还能够肯定对象是否能够扩展
var person={name:"Nicholas"}; alert(Object.isExtensible(person));//true Object.preventExtensions(person); alert(Object.isExtensible(person));//true
封闭对象不可扩展,并且已有成员[Configurable]特性将被设置为false,这就意味着不能删除属性和方法,由于不能使用Object.defineProperty()把数据属性修改成访问器属性
var person = { name: "Nicholas" }; Object.seal(person); person.age = 29; alert(person.age); //undefined delete person.name; alert(person.name); //"Nicholas"
Object.isSealed()方法能够肯定对象是否被密封了,由于被密封的对象不可扩展,因此用Object.isExtensible()检测密封的对象也会返回false
var person = { name: "Nicholas" }; alert(Object.isExtensible(person)); //true alert(Object.isSealed(person)); //false Object.seal(person); alert(Object.isExtensible(person)); //false alert(Object.isSealed(person)); //true
冻结的对象既不可扩展又是密封的,并且对象数据属性的[Writable]特性会被设置为false,若是定义[Set]函数,访问器属性仍然是可写的。ECMAScript5丁意思的Object.freeze()方法能够用来冻结对象
var person = { name: "Nicholas" }; Object.freeze(person); person.age = 29; alert(person.age); //undefined delete person.name; alert(person.name); //"Nicholas" person.name = "Greg"; alert(person.name); //"Nicholas"
Object.isFrozen()方法用于检测冻结对象,由于冻结对象既是密封的又是不可扩展的,因此Object.isExtensible()和Object.isSealed()检测冻结对象将分别返回false和true
var person = { name: "Nicholas" }; alert(Object.isExtensible(person)); //true alert(Object.isSealed(person)); //false alert(Object.isFrozen(person)); //false Object.freeze(person); alert(Object.isExtensible(person)); //false alert(Object.isSealed(person)); //true alert(Object.isFrozen(person)); //true
为了不setInterval()的重复定时器的两个问题,使用链式setTimeout()调用
setTimeout(function(){ //处理中 setTimeout(arguments.callee, interval); }, interval);
这个模式主要用于重复定时器
setTimeout(function(){ var div = document.getElementById("myDiv"); left = parseInt(div.style.left) + 5; div.style.left = left + "px"; if (left < 200){ setTimeout(arguments.callee, 50); } }, 50)
小块小块地处理数组,一般每次一小块,基本的思路是为要处理的项目建立一个队列,而后使用定时器取出下一个要处理的项目进行处理,接着再设置另外一个定时器
setTimeout(function(){ //取出下一个条目并处理 var item = array.shift(); process(item); //若还有条目,再设置另外一个定时器 if(array.length > 0){ setTimeout(arguments.callee, 100); } }, 100);
数组分块模式中,array变量本质上就是一个列表,包含了要处理的项目。使用shift()方法获取队列中要处理的项目,而后将其传递给某个函数。若是在队列中还有其余项目,则设置另外一个定时器,并经过arguments.callee调用同一个匿名函数,要实现数组分块很是简单,可使用下面的函数
function chunk(array, process, context){ setTimeout(function(){ var item = array.shift(); process.call(context, item); if (array.length > 0){ setTimeout(arguments.callee, 100); } }, 100); }
chunk()方法接收三个参数:要处理的项目的数组,用于处理项目的函数,以及可选的运行该函数的环境
var data = [12,123,1234,453,436,23,23,5,4123,45,346,5634,2234,345,342]; function printValue(item){ var div = document.getElementById("myDiv"); div.innerHTML += item + "<br>"; } chunk(data, printValue);
函数节流背后的基本思想是指,某些代码不能够在没有间断的状况连续重复执行。第一次调用函数,建立一个定时器,在指定的时间间隔以后运行代码。当第二次调用该函数时,它会清除前一次的定时器并设置另外一个。若是前一个定时器已经执行过了,这个操做就没有任何意义。然而,若是前一个定时器还没有执行,其实就是将其替换为一个新的定时器。目的是只有在执行函数的请求中止了一段时间以后才执行。
var processor = { timeoutId: null, //实际进行处理的方法 performProcessing: function(){ //实际执行的代码 }, //初始处理调用的方法 process: function(){ clearTimeout(this.timeoutId); var that = this; this.timeoutId = setTimeout(function(){ that.performProcessing(); }, 100); } }; //尝试开始执行 processor.process();
自定义事件背后的概念是建立一个管理事件的对象,让其余对象监听事件
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); }, 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: 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); } } };
还有三个方法:
addHandler() ,用于注册给定类型事件的事件处理程序
fire() ,用于触发一个事件
removeHandler() ,用于注销某个事件类型的事件处理程序。
function handleMessage(event){ alert("Message received: " + event.message); } //建立一个新对象 var target = new EventTarget(); //添加一个事件处理程序 target.addHandler("message", handleMessage); //触发事件 target.fire({ type: "message", message: "Hello world!"}); //删除事件处理程序 target.removeHandler("message", handleMessage); //再次,应没有处理程序 target.fire({ type: "message", message: "Hello world!"});
基本实现过程
var DragDrop = function(){ var dragging = null; function handleEvent(event){ //获取事件和目标 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); } } }();
为防止出现上图状况。计算元素左上角和指针位置之间的差值。这个差值应该在 mousedown 事件发生的时候肯定,而且一直保持,直到 mouseup 事件发生。经过将 event的 clientX 和 clientY 属性与该元素的 offsetLeft 和 offsetTop 属性进行比较,就能够算出水平方向和垂直方向上须要多少空间
var DragDrop = function(){ var dragging = null; diffX = 0; diffY = 0; function handleEvent(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", 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){ //获取事件和对象 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; }();
这段代码定义了三个事件: dragstart 、 drag 和 dragend 。它们都将被拖动的元素设置为了 target ,并给出了 x 和 y 属性来表示当前的位置。它们触发于 dragdrop 对象上,以后在返回对象前给对象增长 enable() 和 disable() 方法。这些模块模式中的细小更改令 DragDrop 对象支持了事件
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/> Dropped " + event.target.id + " at (" + event.x + "," + event.y + ")"; });