摘要:如果構(gòu)造函數(shù)竊取結(jié)合使用原型鏈或者寄生組合則可以解決這個問題惰性載入函數(shù)惰性載入表示函數(shù)執(zhí)行的分支僅會發(fā)生一次。當?shù)诙握{(diào)用該函數(shù)時,它會清除前一次的定時器并設(shè)置另一個。,用于注銷某個事件類型的事件處理程序。
高級技巧 高級函數(shù) 安全的類型檢測
typeof操作符在檢測數(shù)據(jù)類型時,可能會得到不靠譜的結(jié)果
instanceof操作符在存在多個全局作用域,也就是頁面包含多個iframe的情況下,也會出現(xiàn)問題
在任何值上調(diào)用Object原生的toString()方法,都會返回一個[object NativeConstructorName]格式的字符串
原生數(shù)組的構(gòu)造函數(shù)名與全局作用域無關(guān),因此使用toString()就能保證返回一致的值
function isArray(value){ return Object.prototype.toString.call(value)=="[object Array]"; }
基于這一思路來測試某個值是不是原生函數(shù)或正則表達式
function isFunction(value){ return Object.prototype.toString.call(value)=="[object Function]"; } function isRegExp(value){ return Object.prototype.toString.call(value)=="[object RegExp]"; }作用域安全的構(gòu)造函數(shù)
作用域安全的構(gòu)造函數(shù)在進行任何更改之前,首先確認this對象是正確類型的實例,如果不是,那么會創(chuàng)建新的實例并返回
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
使用構(gòu)造函數(shù)竊取模式的繼承且不適用原型鏈,這個繼承可能被破壞
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構(gòu)造函數(shù)是作用域安全的,然而Rectangle構(gòu)造函數(shù)則不是。如果構(gòu)造函數(shù)竊取結(jié)合使用原型鏈或者寄生組合則可以解決這個問題
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惰性載入函數(shù)
惰性載入表示函數(shù)執(zhí)行的分支僅會發(fā)生一次。
第一種實現(xiàn)惰性載入的方法,在函數(shù)被調(diào)用時再處理函數(shù)。在第一次調(diào)用的過程中,該函數(shù)會覆蓋為另一個按何時方式執(zhí)行的函數(shù),這樣任何對原函數(shù)的調(diào)用都不用再經(jīng)過執(zhí)行的分支了
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(); }
第二種實現(xiàn)惰性載入的方式是在聲明函數(shù)時就指定適當?shù)暮瘮?shù),這樣,第一次調(diào)用函數(shù)時就不會喪失性能了,而在代碼首次加載的時候回損失一點性能
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."); }; } })();函數(shù)綁定
一個簡單的bind()函數(shù)接受一個函數(shù)和一個環(huán)境,并返回一個在給定環(huán)境中調(diào)用給定函數(shù)的函數(shù),并且將所有參數(shù)原封不動傳遞過去
function bind(fn, context){ return function(){ return fn.apply(context, arguments); }; }
當調(diào)用返回的函數(shù)時,它會在給定環(huán)境中執(zhí)行被傳入的函數(shù)并給出所有參數(shù)
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為所有函數(shù)定義了一個原生的bind()方法,進一步簡單了操作,不用再自己定義bind()函數(shù)了,而是可以直接在函數(shù)上調(diào)用這個方法
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));函數(shù)的柯里化
用于創(chuàng)建已經(jīng)設(shè)置好了一個或者多個參數(shù)的函數(shù)。函數(shù)柯里化的基本方法和函數(shù)綁定是一樣的。使用一個閉包返回一個函數(shù),兩者的區(qū)別在于,當函數(shù)被調(diào)用時,返回的函數(shù)還需要設(shè)置一些傳入的參數(shù)
柯里化函數(shù)通常由一下步驟動態(tài)創(chuàng)建:調(diào)用另一個函數(shù)并為它傳入要柯里化的函數(shù)和必要參數(shù)
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()方法也實現(xiàn)函數(shù)柯里化,只要在this的值之后再傳入另一個參數(shù)即可
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"));防篡改對象 不可擴展對象
默認情況下,所有對象都是可以擴展的,也就是說,任何時候都可以向?qū)ο笾刑砑訉傩院头椒?/p>
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]特性將被設(shè)置為false,這就意味著不能刪除屬性和方法,因為不能使用Object.defineProperty()把數(shù)據(jù)屬性修改為訪問器屬性
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凍結(jié)的對象
凍結(jié)的對象既不可擴展又是密封的,而且對象數(shù)據(jù)屬性的[Writable]特性會被設(shè)置為false,如果定義[Set]函數(shù),訪問器屬性仍然是可寫的。ECMAScript5丁意思的Object.freeze()方法可以用來凍結(jié)對象
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()方法用于檢測凍結(jié)對象,因為凍結(jié)對象既是密封的又是不可擴展的,所以O(shè)bject.isExtensible()和Object.isSealed()檢測凍結(jié)對象將分別返回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高級定時器 重復(fù)的定時器
使用setInterval()創(chuàng)建的定時器確保了定時器代碼規(guī)則地插入隊列中
使用setInterval()時,僅當沒有該定時器的任何其他代碼實現(xiàn)時,才將定時器代碼添加到隊列中,這確保了定時器加入到隊列中的最小時間間隔為指定間隔
重復(fù)定時器有兩個問題:某些間隔會被跳過;多個定時器的代碼執(zhí)行之間的間隔可能會比預(yù)期小
為了避免setInterval()的重復(fù)定時器的兩個問題,使用鏈式setTimeout()調(diào)用
setTimeout(function(){ //處理中 setTimeout(arguments.callee, interval); }, interval);
這個模式主要用于重復(fù)定時器
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)Yielding Processes
腳本長時間運行的問題通常是由兩個原因之一造成的:過長的、過深嵌套的函數(shù)調(diào)用或者是進行大量處理的循環(huán)
兩個重要問題:該處理是否必須同步完成;數(shù)據(jù)是否必須按順序完成
如果兩個問題回答都是否,可以使用定時器分隔這個循環(huán),即數(shù)組分塊技術(shù)
小塊小塊地處理數(shù)組,通常每次一小塊,基本的思路是為要處理的項目創(chuàng)建一個隊列,然后使用定時器取出下一個要處理的項目進行處理,接著再設(shè)置另一個定時器
setTimeout(function(){ //取出下一個條目并處理 var item = array.shift(); process(item); //若還有條目,再設(shè)置另一個定時器 if(array.length > 0){ setTimeout(arguments.callee, 100); } }, 100);
數(shù)組分塊模式中,array變量本質(zhì)上就是一個列表,包含了要處理的項目。使用shift()方法獲取隊列中要處理的項目,然后將其傳遞給某個函數(shù)。如果在隊列中還有其他項目,則設(shè)置另一個定時器,并通過arguments.callee調(diào)用同一個匿名函數(shù),要實現(xiàn)數(shù)組分塊非常簡單,可以使用下面的函數(shù)
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()方法接收三個參數(shù):要處理的項目的數(shù)組,用于處理項目的函數(shù),以及可選的運行該函數(shù)的環(huán)境
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 + "函數(shù)節(jié)流
"; } chunk(data, printValue);
函數(shù)節(jié)流背后的基本思想是指,某些代碼不可以在沒有間斷的情況連續(xù)重復(fù)執(zhí)行。第一次調(diào)用函數(shù),創(chuàng)建一個定時器,在指定的時間間隔之后運行代碼。當?shù)诙握{(diào)用該函數(shù)時,它會清除前一次的定時器并設(shè)置另一個。如果前一個定時器已經(jīng)執(zhí)行過了,這個操作就沒有任何意義。然而,如果前一個定時器尚未執(zhí)行,其實就是將其替換為一個新的定時器。目的是只有在執(zhí)行函數(shù)的請求停止了一段時間之后才執(zhí)行。
var processor = { timeoutId: null, //實際進行處理的方法 performProcessing: function(){ //實際執(zhí)行的代碼 }, //初始處理調(diào)用的方法 process: function(){ clearTimeout(this.timeoutId); var that = this; this.timeoutId = setTimeout(function(){ that.performProcessing(); }, 100); } }; //嘗試開始執(zhí)行 processor.process();自定義事件
觀察者模式由兩類對象組成:主體和觀察者。主體負責(zé)發(fā)布事件,同時觀察者通過訂閱這些事件來觀察該主體
自定義事件背后的概念是創(chuàng)建一個管理事件的對象,讓其他對象監(jiān)聽事件
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 ,用于儲存事件處理程序。
還有三個方法:
addHandler() ,用于注冊給定類型事件的事件處理程序
addHandler() 方法接受兩個參數(shù):事件類型和用于處理該事件的函數(shù)。當調(diào)用該方法時,會進行一次檢查,看看 handlers 屬性中是否已經(jīng)存在一個針對該事件類型的數(shù)組;如果沒有,則創(chuàng)建一個新的。然后使用 push() 將該處理程序添加到數(shù)組的末尾。
fire() ,用于觸發(fā)一個事件
如果要觸發(fā)一個事件,要調(diào)用 fire() 函數(shù)。該方法接受一個多帶帶的參數(shù),是一個至少包含 type屬性的對象。 fire() 方法先給 event 對象設(shè)置一個 target 屬性,如果它尚未被指定的話。然后它就查找對應(yīng)該事件類型的一組處理程序,調(diào)用各個函數(shù),并給出 event 對象。
removeHandler() ,用于注銷某個事件類型的事件處理程序。
removeHandler() 方法是 addHandler() 的輔助,它們接受的參數(shù)一樣:事件的類型和事件處理程序。這個方法搜索事件處理程序的數(shù)組找到要刪除的處理程序的位置。如果找到了,則使用 break操作符退出 for 循環(huán)。然后使用 splice() 方法將該項目從數(shù)組中刪除。
function handleMessage(event){ alert("Message received: " + event.message); } //創(chuàng)建一個新對象 var target = new EventTarget(); //添加一個事件處理程序 target.addHandler("message", handleMessage); //觸發(fā)事件 target.fire({ type: "message", message: "Hello world!"}); //刪除事件處理程序 target.removeHandler("message", handleMessage); //再次,應(yīng)沒有處理程序 target.fire({ type: "message", message: "Hello world!"});拖放
基本實現(xiàn)過程
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); } } }();修繕拖動功能
為防止出現(xiàn)上圖情況。計算元素左上角和指針位置之間的差值。這個差值應(yīng)該在 mousedown 事件發(fā)生的時候確定,并且一直保持,直到 mouseup 事件發(fā)生。通過將 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"; // 觸發(fā)自定義事件 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 。它們都將被拖動的元素設(shè)置為了 target ,并給出了 x 和 y 屬性來表示當前的位置。它們觸發(fā)于 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 += "
Dragged " + event.target.id + " to (" + event.x + "," + event.y + ")"; }); DragDrop.addHandler("dragend", function(event){ var status = document.getElementById("status"); status.innerHTML += "
Dropped " + event.target.id + " at (" + event.x + "," + event.y + ")"; });
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/98189.html
摘要:基本概念語法區(qū)分大小寫,中的一切變量函數(shù)名和操作符都區(qū)分大小寫。要將一個值轉(zhuǎn)換成對應(yīng)的值,可以調(diào)用類型包括整數(shù)和浮點數(shù)值,基本數(shù)值字面量格式是十進制整數(shù),除了十進制外還有八進制十六進制。八進制第一位必須是,十六進制第一位必須是。 基本概念 語法 區(qū)分大小寫,ECMAScript中的一切(變量、函數(shù)名和操作符)都區(qū)分大小寫。函數(shù)名不能使用typeof,因為它是一個關(guān)鍵字,但typeOf...
摘要:當代碼在一個環(huán)境中執(zhí)行時,會創(chuàng)建變量對象的一個作用域鏈。作用域鏈的用途,是保證對執(zhí)行環(huán)境有權(quán)訪問的所有變量和函數(shù)的有序訪問。這樣,一直延續(xù)到全局執(zhí)行環(huán)境全局執(zhí)行環(huán)境的變量對象始終都是作用域鏈中的最后一個對象。 變量、作用域和內(nèi)存問題 基本類型和引用類型的值 基本類型值指的是簡單的數(shù)據(jù)段,而引用類型值值那些可能由多個值構(gòu)成的對象。 定義基本類型值的引用和引用類型值的方法是類似的,創(chuàng)建...
摘要:能力檢測無法精確地檢測特定的瀏覽器和版本。用戶代理檢測通過檢測用戶代理字符串來識別瀏覽器。用戶代理檢測需要特殊的技巧,特別是要注意會隱瞞其用戶代理字符串的情況。 客戶端檢測 能力檢測 能力檢測的目的不是識別特定的瀏覽器,而是識別瀏覽器的能力,采用這種方式不必顧忌特定的瀏覽器,只要確定瀏覽器支持的特定的能力,就能給出解決方案,檢測基本模式 if(object.propertyInQu...
摘要:對象的核心對象是,它表示瀏覽器的一個實例。而和則表示該容器中頁面視圖區(qū)的大小。在中,與返回相同的值,即視口大小而非瀏覽器窗口大小。第三個參數(shù)是一個逗號分隔的設(shè)置字符串,表示在新窗口中都顯示哪些特性。這應(yīng)該是用戶打開窗口后的第一個頁面 BOM window對象 BOM的核心對象是window,它表示瀏覽器的一個實例。在瀏覽器中,window對象有雙重角色,它既是通過JavaScript訪...
摘要:匿名函數(shù)可以用來模仿塊級作用域來避免這個問題這里是塊級作用域代碼定義并立即調(diào)用了一個匿名函數(shù),將函數(shù)聲明包含在一對圓括號中,表示它實際上是一個函數(shù)表達式,而緊隨其后的另一對圓括號會立即調(diào)用這個函數(shù)。 函數(shù)表達式 遞歸 遞歸函數(shù)是在一個函數(shù)通過名字調(diào)用自身的情況下構(gòu)成的 function factrial(num){ if(num
閱讀 1014·2021-11-22 13:52
閱讀 929·2019-08-30 15:44
閱讀 575·2019-08-30 15:43
閱讀 2428·2019-08-30 12:52
閱讀 3478·2019-08-29 16:16
閱讀 641·2019-08-29 13:05
閱讀 2946·2019-08-26 18:36
閱讀 1994·2019-08-26 13:46