函數本質上是很簡單且進程化的,但是由于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);
如果你的頁面中要進行大量的循環處理,每次循環會消耗大量的時間,那就會阻塞用戶的操作。這時候分塊處理數據就是個好辦法。
這個例子每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();
上一篇 SQL優化之索引
下一篇 MySQL查詢高速緩沖詳解