Js第22章-高級技巧

安全的類型檢測

檢測對象到底是原生對象還是開發者自定義的對象。
使用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()時,僅當隊列中沒有該定時器的任何代碼時,纔將定時器代碼添加到隊列中。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章