安全的類型檢測
檢測對象到底是原生對象還是開發者自定義的對象。
使用object的toString方法
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]";
}
爲什麼不用instanceOf
?
instanceOf要求實例和構造函數必須在同一個作用域下。
例:
var isArray = value instanceOf Array
以上代碼要返回true,value必須是一個數組,而且還必須與Array構造函數在同一個全局作用域中。(別忘了,Array是window的屬性)如果value是在另一個框架中定義的數組,那麼以上代碼返回false
作用域安全的構造函數
function Person(name, age, job) {
this.name = name;
this.age = age;
this.job = job;
}
var person = new Person("Nicoloas", 29, "SE");
假如以上代碼,沒有使用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);
}
}
但是,實現這個模式後,你就鎖定了可以調用構造函數的環境。如果你使用構造函數竊取模式的繼承且不使用原型鏈,那麼這個繼承可能被破壞。
例:
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 rec = new Rectangle(5, 10);
alert(rec.sides); // undefined
在這段代碼中,新創建一個Rectangle實例後,這個實例應該通過Polygon.call()
來繼承Polygon的屬性,但是由於Polygon的構造函數是作用域安全的,this對象並非Polygon的實例,所以會創建並返回一個新的Polygon對象。但是Rectangle並沒有接受到Polygon.call
的返回值,因此,this對象並沒有得到增長,所以Rectangle實例中不會有sides屬性。
解決方案
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 rec = new Rectangle(5, 10);
alert(rec.sides); // 2
通過將Rectangle的原型設爲Polygon,那麼在調用Polygon.call()
時,一個Rectangle實例也同時是一個Polygon實例,所以會設置this的各種屬性。
函數綁定
函數綁定要創建一個函數,可以在特定的this環境中以指定參數調用另一個函數。該函數常常與回調函數和事件處理程序一起使用,以便將函數作爲變量傳遞的同時保留代碼執行環境。
例:
var handler = {
message: "Event handled",
handleClick: function(event) {
alert(this.message);
}
};
var btn = document.getElementById("my-btn");
EventUtil.addHandler(btn, "click", handler.handleClick);
貌似警告框應該顯示Event handled,但實際顯示的是undefined。這個問題在於沒有保存handler.handleClick()
的環境,所以this對象最後指向了DOM按鈕而非handler。(在IE8中,this指向window)
回顧:
EventUtil是封裝過的跨瀏覽器版本的addEventLister和removeEventListener
var EventUtil = {
addHandler: function(element, type, handler) {
if (element.addEventListener) {
element.addEventListener(type, handler, false);
}
else if (element.attachEvent) {
element.attachEvent("on" + type, handler);
}
else {
element["on" + type] = handler;
}
},
removeHandler: function(element, type, handler) {
if (element.removeEventListener) {
element.removeEventListener(type, handler, false);
}
else if (element.detachEvent) {
element.detachEvent("on" + type, handler);
}
else {
element["on" + type] = null;
}
}
}
解決方案1
var handler = {
message: "Event handled",
handleClick: function(event) {
alert(this.message);
}
};
var btn = document.getElementById("my-btn");
EventUtil.addHandler(btn, "click", function(event){
handler.handleClick(event);
});
閉包本身不是解決方案,而是通過閉包避免DOM直接調用handleClick導致喪失環境
解決方案2
方案1會創建太多閉包導致代碼閱讀性變差,因此需要創造一個將函數與環境綁定的函數。
function bind(fn, context) {
return function() { // 通過閉包保存context,返回的函數在調用時將會傳入event參數
return fn.apply(context, arguments); //event參數被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));
上面的代碼相當於:
var handler = {
message: "Event handled",
handleClick: function(event) {
alert(this.message);
}
};
var btn = document.getElementById("my-btn");
var newfunc = bind(handler.handleClick, handler);
btn.onclick = newfunc;
解決方案3
ECMAScript5爲所有函數定義了一個原生的bind()
方法,進一步簡單了操作
可以直接在函數上調用
var handler = {
message: "Event handled",
handleClick: function(event) {
alert(this.message);
}
};
var btn = document.getElementById("my-btn");
EventUtil.addHandler(btn, "click", handler.handleClick.bind(handler));
// function.bind(context)
函數科裏化
它用於創建已經設置好了一個或多個參數的函數。
函數科裏化和函數綁定的基本方法是一樣的。
構建步驟:調用另一個函數併爲他傳入要科裏化的函數和必要參數。
通用方式
function curry(fn) {
var args = Array.prototype.slice.call(arguments, 1); // 獲取fn之後的參數
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); //5傳給args
alert(curriedAdd(3)); //8 //3傳給innerArgs
綁定特定環境
function bind(fn, context) {
var args = Array.prototype.slice.call(arguments, 2); // 獲取context之後的參數
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) { //event作爲最後的參數
alert(this.message + ":" + name + ":" + event.type);
}
};
var btn = document.getElementById("my-btn");
EventUtil.addHandler(btn, "click", bind(handler.handleClick, handler, "my-btn"));
JS5的bind函數
var handler = {
message: "Event handled",
handleClick: function(name, event) { //event作爲最後的參數
alert(this.message + ":" + name + ":" + event.type);
}
};
var btn = document.getElementById("my-btn");
EventUtil.addHandler(btn, "click", handler.handleClick.bind(handler, "my-btn"));
// curry實參直接作爲第二,三,。。。個參數傳給bind函數即可
// curry函數則需要將寫好對應的形參,event作爲最後的參數
高級定時器
JavaScript是運行於單線程環境的,除了主進程外,還需要一個在進程下一次空閒時執行的代碼隊列。隨着頁面在其生命週期的推移,代碼會按照執行順序添加入隊列。
因此,在Js中沒有任何代碼是立即執行的,但一旦進程空閒則儘快執行。
定時器的工作方式是,當特定的時間過去後,將代碼插入到隊列中。注意,給隊列添加任何代碼並不意味着他立即執行,而只能表示他儘快執行。
指定的時間間隔表示何時將定時器的代碼添加到隊列中,而不是何時實際執行代碼。
重複的定時器問題
使用setInterval()創建的定時器確保了定時器代碼規則地插入隊列中。這個方式的問題在於,定時器代碼可能在代碼再次被添加到隊列之前還沒有完成執行,結果導致定時器代碼連續執行好幾次,而之間沒有任何停頓。
但Js引擎夠聰明,能夠避免這個問題。當使用setInterval()時,僅當隊列中沒有該定時器的任何代碼時,纔將定時器代碼添加到隊列中。