多多色-多人伦交性欧美在线观看-多人伦精品一区二区三区视频-多色视频-免费黄色视屏网站-免费黄色在线

國內最全IT社區平臺 聯系我們 | 收藏本站
阿里云優惠2
您當前位置:首頁 > web前端 > htmlcss > JS學習20(高級技巧)

JS學習20(高級技巧)

來源:程序員人生   發布時間:2016-06-24 13:56:52 閱讀次數:3680次

高級函數

函數本質上是很簡單且進程化的,但是由于JS天生的動態的特性,從使用方式上可以很復雜。

安全的類型檢測

雖然JS中是有類型檢測的,但是由于閱讀器實現等它們其實不完全可靠。比如typeof在Safari中對正則表達式也返回function。
instanceof在存在多個全局作用域時也會把同種卻不同作用域中構造函數的實例辨認為不同的實例:

var isArray = value instanceof Array;

這個表達式要是想返回true,value必須是個數組,且必須與Array構造函數在同1個全局作用域中,如果value是另外一個全局作用域中定義的數組,那這個表達式返回false。
檢測某個對象是原生的還是開發人員自定義的對象時也會有問題。由于閱讀器開始原生支持JSON了,而有些開發人員還是在用第3方庫來實現JSON,這個庫里會有全局的JSON對象,這樣想肯定JSON對象是否是原生的就麻煩了。
解決這些問題的辦法就是使用Object的toString方法,這個方法會返回1個[object NativeConstructorName]格式的字符串。

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]"; }

不過要注意的是,對在IE中任何以COM情勢實現的函數,isFunction()都會返回false。
對JSON是不是為原生的問題可以這樣:

var isNativeJSON = window.JSON && Object.prototype.toString.call(JSON) == "[object JSON]";

作用域安全的構造函數

之前我們說的構造函數是這么使用的:

function Person(name, age, job){ this.name = name; this.age = age; this.job = job; } var person = new Person("Nicholas", 29, "Software Engineer");

在這里,由于使用了new操作符,this被綁定在了新創建的Person對象上,如果不用new操作符直接調用Person(),this就會被綁定到window上,這明顯是不行的。

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){ //這里調用時,傳進去的this是Rectangle類型的,沒辦法拓展side屬性 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

解決方法就是使Rectangle也是Polygon的1個實例就好啦

Rectangle.prototype = new Polygon(); var rect = new Rectangle(5, 10); alert(rect.sides); //2

惰性載入函數

由于閱讀器差異,大量的判斷閱讀器能力的函數需要被使用(通常是大量的if),但是這些判斷1般其實沒必要每次都履行,在履行1次后,閱讀器的能力就肯定了,以后就應當不用在判斷了。比如:

function createXHR(){ if (typeof XMLHttpRequest != "undefined"){ return new XMLHttpRequest(); } else if (typeof ActiveXObject != "undefined"){ 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){ } } } return new ActiveXObject(arguments.callee.activeXString); } else { throw new Error("No XHR object available."); } }

這里的創建XHR對象的函數,每次創建對象時都會判斷1次閱讀器能力,這是沒必要要的。
惰性載入有兩種方式,第1種就是在函數第1次被調用時,根據不同情況,用不同的新函數把這個函數覆蓋掉,以后調用就不需要再判斷而是直接履行該履行的操作。

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(); } createXHR(); alert(createXHR);

第2種思路1樣,只不過是在聲明函數時就指定新函數,不在第1次調用時再指定。兩種辦法其實本質上是1樣的,看你想怎樣用了。

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."); }; } })(); alert(createXHR);

函數綁定

函數綁定解決的問題是調用函數時函數的this對象被改成我們不想要的對象的問題。這類情況常常出現在指定事件處理函數時。看個例子:

var handler = { message: "Event handled", handleClick: function(event){ alert(this); alert(this.message); } }; var btn = document.getElementById("myButton"); EventUtil.addHandler(btn, "click", handler.handleClick); //[object HTMLButtonElement] undefined handler.handleClick(); // [object Object] Event handled

這里的this被改成了按鈕元素。
解決辦法就是將真實的函數套在1個閉包中,以此來保存著個函數的環境

var handler = { message: "Event handled", handleClick: function(event){ alert(this); // [object Object] alert(this.message); // Event handled } }; var btn = document.getElementById("myButton"); EventUtil.addHandler(btn, "click", function(event){ alert(this); //[object HTMLButtonElement] handler.handleClick(event); });

可以看到,閉包的this被改成了button,而由于這個閉包的保護,我們的處理函數的環境保存住了。
為了不每次都手動創建1個閉包,我們可以創建1個工具函數bind:

function bind(fn, context){ alert(arguments[0]); //fn本身 return function(){ alert(arguments[0]); //event return fn.apply(context, arguments); }; }

這里就是把1個函數使用apply在特定的環境下調用,注意1下arguments對象,最后應當使用的是return過去的匿名函數(閉包)的arguments才對,這樣事件給事件處理函數傳遞的參數才能穿到我們的函數里。

var handler = { message: "Event handled", handleClick: function(event){ alert(this); // [object Object] alert(this.message); // Event handled } }; var btn = document.getElementById("myButton"); EventUtil.addHandler(btn, "click", bind(handler.handleClick, handler));

ES5中為所有函數都定義了1個原生的bind()方法。直接使用就行。

EventUtil.addHandler(btn, "click", handler.handleClick.bind(handler));

只要是將某個函數指針以值的情勢進行傳遞,同時該函數必須在特定環境中履行,被綁定函數的效果就顯現了

函數柯里化

這個是用來創建已設置好1個或多個參數的函數,用1個例子看看基本思想:

function add(num1, num2){ return num1 + num2; } function curriedAdd(num2){ return add(5, num2); } alert(add(2, 3)); //5 alert(curriedAdd(3)); //8

創建柯里化函數的通用方式:

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(a,b) { alert(a); alert(b); alert(a+b); } var curriedAdd = curry(add, 5); curriedAdd(3); //8 curriedAdd = curry(add, 3); curriedAdd(5); //8 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){ alert(this.message + ":"+ name + ":"+ event.type); } }; var btn = document.getElementById("myButton"); EventUtil.addHandler(btn, "click", bind(handler.handleClick, handler, "myButton"));

這里要注意的是參數的順序,比如這里name和event反了可就不對了呦。
ES5中的bind也實現了柯里化,直接使用就能夠。

EventUtil.addHandler(btn, "click", handler.handleClick.bind(handler, "myButton"));

防篡改對象

JS同享的本質使任意對象都可被隨便修改。這樣有時很不方便。ES5增加了幾個方法來設置對象的行動。1旦將對象設置為防篡改就不能撤消了。

不可拓展對象

不可以添加新的屬性和方法

var person = { name: "Nicholas" }; Object.preventExtensions(person); person.age = 29; alert(person.age); //undefined alert(Object.isExtensible(person)); //false person.name = "hahah"; alert(person.name); //hahah

密封的對象

不可以添加或刪除屬性,已有成員的[[Configurable]]被設置為false。

var person = { name: "Nicholas" }; Object.seal(person); person.age = 29; alert(person.age); //undefined delete person.name; alert(person.name); //"Nicholas" alert(Object.isExtensible(person)); //false alert(Object.isSealed(person)); //true

凍結的對象

不可拓展,密封,且對象數據屬性的[[Writable]]將被設為false。如果定義了[[Set]],訪問器屬性仍然可寫。

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" alert(Object.isExtensible(person));//false alert(Object.isSealed(person));//true alert(Object.isFrozen(person));//true

高級定時器

setTimeout()和setInterval()是很實用的功能,不過有些事情是要注意的。
JS是單線程的,這就意味著定時器實際上是很有可能被阻塞的。我們在這兩個函數中所設置的定時,實際上是代表將代碼加入到履行隊列的事件,如果在加入時恰巧JS是空閑的,那末這段代碼會立即被履行,也就是說這個定時被準時的履行了。相反,如果這時候JS其實不空閑或隊列中還有別的優先級更高的代碼,那就意味著你的定時器會被延時履行。

重復的定時器

使用setInterval創建定時器的目的是使代碼規則的插入到隊列中。這個方式的問題在于,存在這樣1種可能,在上次代碼還沒履行完的時候代碼再次被添加到隊列。JS引擎會解決這個問題,在將代碼添加到隊列時會檢查隊列中有無代碼實例,如果有就不添加,這確保了定時器代碼被加入隊列中的最小間隔是規定間隔。但是在某些特殊情況下還是會出現兩個問題,某些間隔由于JS的處理被跳過,代碼之間的間隔比預期的小。
所以盡可能使用setTimeout()摹擬間隔調用。

setTimeout(function(){ setTimeout(arguments.callee, interval); }, interval);

Yielding Processes

如果你的頁面中要進行大量的循環處理,每次循環會消耗大量的時間,那就會阻塞用戶的操作。這時候分塊處理數據就是個好辦法。
這個例子每100ms取1個數組元素并添加到頁面。

function chunk(array, process, context){ setTimeout(function(){ var item = array.shift(); process.call(context, item); if (array.length > 0){ setTimeout(arguments.callee, 100); } }, 100); } 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);

函數節流

這個是為了不某個操作連續不停的觸發,比如觸及到DOM操作,連續大量的DOM操作非常耗資源。
函數節流的基本思想是,每次調用實際上是設置1個真正調用的setTimeout操作,每次調用都會先清除當前的setTimeout再設置1個新的。如果短時間內大量調用,就回1直設置新的setTimeout而不履行setTimeout內的操作。只有停止調用足夠長的時間,直到setTimeout時間到了,內部的真正操作才會履行1次。這個對onresize事件特別有用。

function throttle(method, context) { clearTimeout(method.tId); method.tId= setTimeout(function(){ method.call(context); }, 100); } function reDiv(){ var div = document.getElementById("myDiv"); div.innerHTML += "qqqqqq" + "<br>"; } window.onresize = function(){ throttle(reDiv); };

這樣只有當你停下調劑窗口大小100ms后才會履行reDiv操作。

自定義事件

事件這樣的交互其實就是視察者模式,這類模式由兩類對象組成:主體和視察者。主體負責發布事件,視察者通過定閱這些事件來視察該主體。
創建自定義事件實際上就是創建1個管理事件的對象,并在里面存入各種事件類型的處理函數,觸發事件時,只要你給失事件類型,這個對象就會找到相應的事件處理程序并履行。
下面是1個事件管理對象的大體情勢:

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會依照事件的類型將處理函數存入handlers屬性中對應的數組里(如果還沒有則新建)。
觸發事件時使用fire,傳入1個最少有type屬性的對象。
使用時就像這樣:

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!"});

自定義事件常常用來解耦對象之間的交互,使用事件就不需要有對象與對象之間的援用,使事件處理和事件觸發保持隔離。

拖放

使用原始的鼠標事件

創建1個單例,使用模塊模式來創建1個拖動的插件,返回兩個方法,分別用來添加和移除所有的事件處理程序。

var DragDrop = function(){ var dragging = null; var diffX = 0; var 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); } } }(); DragDrop.enable();

這樣看來拖動的功能是實現了,不過有個問題。比如說這是我寫的1個插件,使用的人想在拖動開始的時候做1些事情,那末他就不能不在起的源碼里做出修改。他需要把所有要履行的代碼和函數加到case “mousedown”里。如果這個插件我加密了呢,那想在這個時間點做些事情就更麻煩了。這樣的做法明顯其實不科學。
這時候如果使用了自定義事件,就能夠很好的解決這個問題。

添加自定義事件

我們在這里新定義1個dragdrop變量,它是EventTarget類型的對象,在它上面我們可以添加事件處理函數或觸發事件。在拖動開始時,進程中,結束時,都觸發了自定義事件,這樣有人想在這幾個節點做甚么就直接添加事件處理函數就能夠了。

var DragDrop = function(){ //這里的dragdrop是之前的EventTarget類型,可以用來保存和觸發事件 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; }(); DragDrop.addHandler("dragstart", function(event){ var status = document.getElementById("myDiv"); status.innerHTML = "Started dragging " + event.target.id; }); DragDrop.addHandler("drag", function(event){ var status = document.getElementById("myDiv"); status.innerHTML += "<br/> Dragged " + event.target.id + " to (" + event.x + "," + event.y + ")"; }); DragDrop.addHandler("dragend", function(event){ var status = document.getElementById("myDiv"); status.innerHTML += "<br/> Dropped " + event.target.id + " at (" + event.x + "," + event.y + ")"; }); DragDrop.enable();
生活不易,碼農辛苦
如果您覺得本網站對您的學習有所幫助,可以手機掃描二維碼進行捐贈
程序員人生
------分隔線----------------------------
分享到:
------分隔線----------------------------
關閉
程序員人生
主站蜘蛛池模板: 性欧美与印度人xxx 性欧美孕妇xxxx | 国产女人体一区二区三区 | 亚洲资源最新版在线观看 | 九九干| 久久只有精品 | 老司机午夜免费福利 | 国产区久久 | 韩国jizz | 欧美三级午夜理伦三级小说 | 国产91嫩草精品 | 国产精品2023| 国产成人香蕉在线视频fuz | 欧美黑人巨大videos极品 | 欧美性猛交xxxx黑人猛交 | 亚洲伊人精品 | 日本乱码一卡二卡三卡永久 | 激情一区二区三区 | 波多野结衣在线中文字幕 | 欧美xxxx极品流血 | 欧美最爽乱淫视频播放黑人 | 69视频国产 | 国产精品久久国产三级国不卡顿 | 久久综合欧美成人 | 日本系列 1页 亚洲系列 | 一区二区三区鲁丝不卡麻豆 | 精品久久久久久亚洲 | 欧美天天性 | 午夜老司机永久免费看片 | 欧美我不卡 | 久久99精品久久久久久三级 | 午夜视频在线观看免费视频 | 日本一区二区三区四区不卡 | 国产三级午夜理伦三级 | 久久在精品线影院精品国产 | 91亚洲综合 | 秋霞一级成人欧美理论 | 久久久亚洲精品视频 | 欧美操片在线观看 | 国产精品成人观看视频国产 | 国产a国产片色老头 | 视频国产一区 |