原生數組的構造函數名與全局做用域無關,所以使用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 + ")"; });