在任何值上調用Object原生的toString()方法,都會返回一個[object NativeConstructorName]格式的字符串。每一個類在內部都有一個[[Class]]屬性,這個屬性就指定了上述字符串中的構造函數名。javascript
var arr = []; function fn(){ } var reg = /^\d/; var json = { "name":"Jack", "age":20 ,} console.log(Object.prototype.toString.call(arr) == "[object Array]"); //true console.log(Object.prototype.toString.call(fn) == "[object Function]"); //true console.log(Object.prototype.toString.call(reg) == "[object RegExp]"); //true console.log(window.JSON && Object.prototype.toString.call(json) == "[object Object]"); //true
做用域安全的構造函數在進行任何更改前,首先確認this對象是正確類型的實例。若是不是,會建立新的實例並返回,以下例子:css
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("jack",29,"IT"); console.log(window.name); //"" console.log(person1.name);//jack var person2 = Person("Tom",20,"teacher"); console.log(person2.name);//Tom
以下例子,Rectangle實例中沒有添加sides屬性:java
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); console.log(rect.sides); //undefined
修改後,Rectangle實例中添加了sides屬性:json
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); console.log(rect.sides); //2
function createXHR(){ if(typeof XMLHttpRequest != "undefined"){ return new XMLHttpRequest(); }else if(typeof ActiveXObject != "undefined"){ if(typeof arguments.callee.activeXString != 'string'){ var verisions = ["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 = version[i]; break; }catch(ex){ //跳過 } } } return new ActiveXObject(arguments.callee.activeXString); }else{ throw new Error("NO XHR object available."); } }
每次調用createXHR()時,他都要對瀏覽器所支持的能力仔細檢查。若是if語句沒必要每次執行,那麼代碼能夠運行的更快一些。解決方案稱之爲惰性載入的技巧。設計模式
惰性載入表示函數執行的分支僅會發生一次。有兩種實現惰性載入的方式,第一種就是函數在被調用時再處理函數。在第一次調用的過程當中,該函數會覆蓋爲另一個按合適方式執行的函數,這樣對原函數的調用都不用在通過執行的分支了。例如上面例子重寫爲:瀏覽器
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 verisions = ["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 = version[i]; break; }catch(ex){ //跳過 } } } return new ActiveXObject(arguments.callee.activeXString); } }else{ createXHR = function(){ throw new Error("NO XHR object available."); } } return createXHR(); }
第二種實現惰性載入的方式是在聲明函數時就指定適當的函數,以下代碼:安全
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 verisions = ["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 = version[i]; break; }catch(ex){ //跳過 } } } return new ActiveXObject(arguments.callee.activeXString); } }else{ return function(){ throw new Error("NO XHR object available."); } } })();
var handler = { message:"Event handler", handleClick:function(event){ console.log(this.meesage); //undefined } } var btn = document.getElementsByClassName("my-btn")[0]; btn.addEventListener("click",handler.handleClick,false);
上面的結果貌似會顯示「Event handler」的結果,可是結果是undefined,是由於沒有保存handler.handleClick()的環境。因此this對象最後是指向了DOM按鈕,而非handler對象(在IE8中,this指向window)。可使用一個閉包來修正這個問題,以下代碼:閉包
var handler = { message:"Event handler", handleClick:function(event){ console.log(this.message); //Event handler } } var btn = document.getElementsByClassName("my-btn")[0]; btn.addEventListener("click",function(event){ handler.handleClick(event); },false);
不少javascript庫實現了一個能夠將函數綁定到指定環境的函數。這個函數通常都叫作bind()。app
一個簡單的bind()函數接收一個函數和一個環境。並返回一個在給定函數中調用給定函數的函數,而且將全部參數原封不動的傳遞過去。語法以下:ide
function bind(fn,context){ return function(){ return fn.apply(context,arguments); } }
那麼咱們就能夠用上面的bind()方法來實現綁定,以下代碼:
var handler = { message:"Event handler", handleClick:function(event){ console.log(this.message); //Event handler } } var btn = document.getElementsByClassName("my-btn")[0]; btn.addEventListener("click",bind(handler.handleClick,handler),false);
ECMAScript5爲全部函數定義了一個原生的bind()方法,那麼上面代碼能夠以下:
var handler = { message:"Event handler", handleClick:function(event){ console.log(this.message); //Event handler } } var btn = document.getElementsByClassName("my-btn")[0]; btn.addEventListener("click",handler.handleClick.bind(handler),false);
原生的bind()方法和上面自定義的bind()方法很類似,都要傳入做爲this值的對象。支持原生bind()方法的瀏覽器有IE9+、Firefox4+和Chrome。
與函數綁定緊密相關的是主題是函數柯里化(function curring),它用於建立已經設置好了一個或多個參數的函數。函數柯里化的基本方法和函數綁定是同樣的:使用一個閉包返回一個函數。二者的區別在於,當函數被調用時,返回的函數還須要設置一些傳入的參數,以下例子:
function add(num1,num2){ return num1 + num2; } function curriedAdd(num2){ return add(5,num2); } console.log(add(2,3)); //5 console.log(curriedAdd(3)); //8
儘管從技術上來講curriedAdd()並不是柯里化的函數,但它很好的展現了其概念。
柯里化函數一般由如下步驟動態建立:調用另外一個函數併爲它傳入要柯里化的函數和必要參數。下面是建立柯里化函數的通用方式:
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); } }
調用方式:
function add(num1,num2){ return num1 + num2; } var curriedAdd = curry(add,5); curriedAdd(3); //8
也可像下面方式調用:
function add(num1,num2){ return num1 + num2; } var curriedAdd = curry(add,5,3); curriedAdd(); //8
函數柯里化還經常做爲函數綁定的一部分包含在其中,構造出更爲複雜的bind()函數,以下:
function bind(fn,context){ var args = Array.prototype.slice.call(arguments,2); return function(){ var innerArgs = Array.prototype.slice.call(arguments); var finalArgs = args.concat(innerArgs); return fn.apply(context,finalArgs); } }
實例:
var handler = { message:"Event handled", handleclick:function(name,event){ console.log(this.message + ":" + name + ":" + event.type); } } var btn = document.getElementById('my-btn'); btn.addEventListener('click',bind(handler.handleclick,handler,"my-btn"),false); //Event handled:my-btn:click
ECMAScript5的bind()方法也實現了函數的柯里化,以下代碼:
var handler = { message:"Event handled", handleclick:function(name,event){ console.log(this.message + ":" + name + ":" + event.type); } } var btn = document.getElementById('my-btn'); btn.addEventListener('click',handler.handleclick.bind(handler,"my_btn"),false); //Event handled:my-btn:click
注意:一旦把對象定義爲防篡改,就沒法撤銷。
使用Object.preventExtensions()就不能給原對象添加新的屬性和方法了,以下例子:
var person = { name:"jack" }; Object.preventExtensions(person); person.age = 20; console.log(person.age); //undefined 嚴格模式下拋出異常
使用Object.isExtensible()肯定對象是否能夠擴展,以下:
var person = { name:"jack" }; console.log(Object.isExtensible(person)); //true Object.preventExtensions(person); console.log(Object.isExtensible(person)); //false
密封對象不可擴展,並且已有成員的[[configurable]]特性將被設置爲false。這就意味着不能刪除屬性和方法,由於不能使用Object.defineProperty()把數據屬性修改成訪問器屬性,或者相反。屬性值是能夠修改的。
var person = { name:"jack" }; Object.seal(person); person.age = 20; console.log(person.age); //非嚴格模式下:undefined 嚴格模式下:拋出異常 delete person.name; console.log(person.name); //非嚴格模式下:jack 嚴格模式下:拋出異常
使用Object.isSealed()方法能夠肯定對象是否被密封了。由於被密封的對象不可擴展,因此調用Object.isExtensible()檢測密封的對象也會返回false。
var person = { name:"jack" }; console.log(Object.isExtensible(person)); //true console.log(Object.isSealed(person)); //false Object.seal(person); console.log(Object.isExtensible(person));//false console.log(Object.isSealed(person));//true
最嚴格的防篡改級別是凍結對象。凍結的對象既不可擴展,又是密封的,並且對象數據屬性[[writable]]特性會被設置爲false。若是定義[[Set]]函數,訪問器屬性仍然是可寫的。
ECMAScript5定義的Object.freeze()方法能夠用來凍結對象,以下代碼:
var person = { name:"jack" }; Object.freeze(person); person.age = 20; console.log(person.age); //非嚴格模式:undefined 嚴格模式下:拋出異常 delete person.name; console.log(person.name);//非嚴格模式:jack 嚴格模式下:拋出異常 person.name = "Tom"; console.log(person.name);//非嚴格模式:jack 嚴格模式下:拋出異常
使用Object.isFrozen()方法檢測凍結對象。由於凍結對象既是密封的又是不可擴展的,因此調用Object.isExtensible()和Object.isSealed()方法分別返回false和true。
var person = { name:"jack" }; console.log(Object.isExtensible(person)); //true console.log(Object.isSealed(person)); //false console.log(Object.isFrozen(person)) //false Object.freeze(person); console.log(Object.isExtensible(person));//false console.log(Object.isSealed(person));//true console.log(Object.isFrozen(person)) //true
DOM操做比起非DOM交互須要更多的內存和CPU。連續嘗試進行過多的DOM相關操做可能會致使瀏覽器掛起,有時候甚至會崩潰。好比在IE瀏覽器中使用onresize事件處理程序的時候容易發生,當調整瀏覽器窗口的時候,該事件會連續發生。在onresize事件處理程序內部若是嘗試進行DOM操做,其高頻率的更改可能會讓瀏覽器崩潰。爲了解決這個問題,可使用定時器對該函數進行節流。
基本形式代碼結構以下:
var processor = { timeoutId:null, //實際進行處理的方法 performProcessing:function(){ //實際執行的代碼 }, //初始處理調用的方法 process:function(){ clearTimeout(this.timeoutId); var that = this; this.timeoutId = setTimeout(function(){ that.performProcessing(); },100); } } //嘗試開始 執行 processor.process();
時間間隔設置爲了100ms,這表示最後一次調用process()以後,至少100ms後纔會調用performProcessing()。因此若是100ms以內調用了20次process(),也只會調用performProcessing()一次。
這個模式可使用throttle函數來簡化,這個函數能夠自動進行定時器的設置和清除,以下代碼:
function throttle(method,context){ clearTimeout(method.tId); method.tId = setTimeout(function(){ method.call(context); },100); }
throttle()函數接受兩個參數:要執行的函數以及在哪一個做用域中執行。
來看個例子,假若有一個div元素須要保持它的高度始終等於寬度。那麼實現這個JS代碼以下:
window.onresize = function(){ var div = document.getElementById("myDiv"); div.style.height = div.offsetWidth + "px"; }
上面代碼有兩個問題可能形成瀏覽器運行緩慢,一個是計算offsetWidth屬性,若是該元素或者頁面上的其它元素有很是複雜的css樣式,那麼這個過程將會很複雜。 另外一個設置某個元素的高度須要對頁面進行迴流來令改動生效。若是頁面有不少元素同時應用了至關數量的CSS的話,這有須要不少計算。這就能夠用到throttle()函數,以下代碼:
function resizeDiv(){ var div = document.getElementById("myDiv"); div.style.height = div.offsetWidth + "px"; } window.onresize = function(){ throttle(resizeDiv); }
只要代碼是週期性執行的,都應該使用節流。
事件是一種叫作觀察者的設計模式,這是一種建立鬆散耦合代碼的技術。對象能夠發佈事件,用來表示在該對象生命週期中某個有趣的時刻到了。而後其它對象能夠觀察該對象,等等這些有趣的時刻到來並經過運行代碼來響應。
觀察者模式由兩類對象組成:主體和觀察者。主體負責發佈事件,同時觀察者經過訂閱這些事件來觀察該主體。該模式的一個關鍵概念是主體並不知道觀察者的任何事情,也就是說它能夠獨自存在並正常運做即使觀察者不存在。從另外一個方面來講,觀察者知道主體並能註冊事件的回調函數(事件處理程序)。涉及DOM上時,DOM元素即是主體,你的事件處理代碼即是觀察者。
事件是與DOM交互的最多見的方式,但它們也能夠用於非DOM代碼中--經過實現自定義事件。
自定義事件背後的概念是建立一個管理事件的對象,讓其餘對象監聽那些事件。實現此功能的基本模式能夠以下定義:
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); } } };
EventTarget類型有一個單獨的屬性handlers,用於存儲事件處理程序。
定義的三個方法以下:
使用EventTarget類型的自定義事件能夠以下使用:
function handlerMessage(event){ console.log("Message received:" + event.message); } //建立一個新對象 var target = new EventTarget(); //添加一個事件處理程序 target.addHandler("message",handlerMessage); //觸發事件 target.fire({type:"message",message:"Hello world!"}); //移除事件處理程序 target.removeHandler("message",handlerMessage); //再次,應沒有處理程序 target.fire({type:"message",message:"Hello world!"});
以下實例:
function object(o){ function F(){}; F.prototype = o; return new F(); } function inheritPrototype(subType,superType){ var prototype = object(superType.prototype); prototype.constructor = subType; subType.prototype = prototype; } function Person(name,age){ EventTarget.call(this); this.name = name; this.age = age; } inheritPrototype(Person,EventTarget); Person.prototype.say = function(message){ this.fire({type:"message",message:message}); }
Person類型使用了寄生組合繼承方法來繼承EventTarget。怎樣使用:
function handlerMessage(event){ console.log(event.target.name + " says:" + event.message); } //建立新Person var person = new Person("Jack",29); //添加一個事件處理程序 person.addHandler("message",handlerMessage); //在該對象上調用一個方法,它觸發消息事件 person.say('Hi there');
下例代碼本身添加了一部分控制在屏幕區域的代碼,以下:
var DragDrop = function(){ var dragging = null, differX = 0, differY = 0, targetWidth = 0, targetHeight = 0, windowWidth = 0, windowHeight = 0, _isMove = false; //是否移動 function handleEvent(event){ //獲取事件和目標 event = event || window.event; var target = event.target || event.srcElement; //確認事件類型 switch(event.type){ case "mousedown": if(target.className.indexOf("drggable") != -1){ dragging = target; _isMove = true; differX = event.clientX - target.offsetLeft; differY = event.clientY - target.offsetTop; targetWidth = target.offsetWidth; targetHeight = target.offsetHeight; windowWidth = document.documentElement.clientWidth; windowHeight = document.documentElement.clientHeight; } break; case "mousemove": if(dragging !== null && _isMove){ var left = event.clientX - differX, top = event.clientY - differY; if(left < 0){ left = 0; } else if(left > windowWidth - targetWidth){ left = windowWidth - targetWidth; } if(top < 0){ top = 0; } else if(top > windowHeight - targetHeight){ top = windowHeight - targetHeight; } dragging.style.left = left + "px"; dragging.style.top = top + "px"; } break; case "mouseup": dragging = null; _isMove =false; break; } } //公共接口 return { enable:function(){ document.addEventListener("mousedown",handleEvent,false); document.addEventListener("mousemove",handleEvent,false); document.addEventListener("mouseup",handleEvent,false); }, disable:function(){ document.removeEventListener("mousedown",handleEvent,false); document.removeEventListener("mousemove",handleEvent,false); document.removeEventListener("mouseup",handleEvent,false); } } }(); DragDrop.enable();
上面寫的拖放功能還不能真正應用起來,除非能知道何時拖動開始了。從這點來看,前面的代碼沒有提供任何方法表示拖動開始、正在拖動或者拖動結束。這時,可使用自定義事件來指示這幾個事件的發生,讓應用的其它部分和拖動功能進行交互。以下代碼:
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); } } }; var DragDrop = function(){ var dragdrop = new EventTarget(), dragging = null, differX = 0, differY = 0; function handleEvent(event){ //獲取事件和目標 event = event || window.event; var target = event.target || event.srcElement; //確認事件類型 switch(event.type){ case "mousedown": if(target.className.indexOf("drggable") != -1){ dragging = target; _isMove = true; differX = event.clientX - target.offsetLeft; differY = 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 - differX) + "px"; dragging.style.top = (event.clientY - differY) + "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(){ document.addEventListener("mousedown",handleEvent,false); document.addEventListener("mousemove",handleEvent,false); document.addEventListener("mouseup",handleEvent,false); }; dragdrop.disable = function(){ document.removeEventListener("mousedown",handleEvent,false); document.removeEventListener("mousemove",handleEvent,false); document.removeEventListener("mouseup",handleEvent,false); }; return dragdrop; }();
這段代碼定義了三個自定義事件:dragstart、drag、dragend,它們都將被拖動的元素設置爲了target,並給出了x和y屬性來表示當前的位置。調用以下:
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/> Dropped" + event.target.id + " at(" + event.x + "," + event.y + ")"; });
貼出HTML代碼:
<div id="status"></div> <div id="myDiv" class="drggable"></div>
CSS代碼:
*{margin:0;padding:0;} #myDiv{width:200px;height:200px;background: blue;position: absolute;top:50px;left:400px;}