22.1 高級函數
22.1.1 安全的類型檢測
對于之前,我們要去判斷一個變量的類型,對于基本類型的,可以使用typeof,對于引用類型的,我們可以使用 instanceof ,不過,這兩種方法在某些情況下,是不可靠的,會存在一些兼容性問題。
接下來,我們介紹一種更佳可靠的檢測方法:
我們知道,在任何值上調用 Object 原生的 toString() 方法,都會返回一個 [object NativeConstrectorName] 格式的字符串,每個類在內部都有一個 [[Class]] 屬性,這個屬性中就指定了上述字符串中的構造函數名。
比如,我們可以使用下面方法來檢測各種對象:
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]";
}
22.1.2 作用域安全的構造函數
構造函數其實就是一個使用 new 操作符調用的函數。當使用 new 調用時,構造函數內用到的this對象會指向新創建的對象實例。
我們來看一個使用構造函數的例子:
function Person(name, age, job){
this.name = name;
this.age = age;
this.job = job;
}
var person = new Person('jack', 29, 'teacher');
person.name ==> 'jack'
person.age ==> 29
person.job ==> 'teacher'
可是?。?!
這個模式會出現一個問題,在你有意或者無意漏掉 new 操作符時,會發生以下事情
function Person(name, age, job){
this.name = name;
this.age = age;
this.job = job;
}
var person = Person('jack', 29, 'teacher');
person.name ==> 報錯
window.name ==> 'jack'
window.age ==> 29
window.job ==> 'teacher'
這是什么原因呢???
因為漏掉了 new 操作符后,Person()被作為普通函數調用,大家知道,普通函數內部的this指向了window,所以name,age,job
這三個屬性會被掛載到window對象上,造成全局變量污染。
這一點,對于構造函數來說,是一個極大的問題,怎么樣避免這個問題,可以更安全的使用自己的構造函數呢????
那就是:創建一個作用域安全的構造函數!!??!
function Person(myName, age, job){
if( this instanceof Person ){
this.myName = myName;
this.age = age;
this.job = job;
} else {
return new Person(myName, age, job);
}
}
var person = Person('jack', 29, 'teacher');
window.myName ==> undefined
person.myName ==> 'jack'
22.1.3 惰性載入函數
22.1.4 函數綁定
22.1.5 函數柯里化
22.2 防篡改對象
22.2.1 不可擴展對象
默認情況下,所有對象都是可擴展的。
比如:
var person = { name: 'jack' };
person.age = 20;
如何禁止給對象添加擴展呢??可以使用對象的 Object.preventExtensions()方法
var person = { name: 'jack' };
Object.preventExtensions(person);
person.age = 20;
console.log(person.age) ==》 undefined
還有,使用 Object.isExtensible() 可以檢測對象是否可擴展
22.2.2 密封的對象
可以使用 Object.seal() 方法使一個對象成為密封對象。成為密封對象后,此對象將不可擴展,而且其屬性不可刪除,但是可以修改
var person = { name: 'jack' };
Object.seal(person);
delete person.name;
alert(person.name); ==> 'jack'
person.name = 'pony';
alert(person.name) ==> 'pony';
可以使用 Object.isSealed() 來檢測對象是否被密封了。
22.2.3 凍結的對象
最嚴格的防篡改級別是凍結對象。凍結的對象,既不可擴展,又是密封,又不能修改其屬性。
即 Object.freeze();
對于一個JS庫而言,凍結對象是很有用的。因為可以極大的防止庫中核心對象被修改。
22.3 高級定時器
定時器對隊列的工作方式是:當特定事件過去后,將代碼插入。注意,給隊列添加代碼并不意味著代碼會立即執行,只能表示它會盡快執行。
等待瀏覽器線程空閑會執行。
使用 setInterval() 會存在以下問題:
(1) 某些間隔會被跳過
(2) 多個定時器的代碼執行之間的間隔可能會比預期的小。
所以,推薦使用 setTimeout() 來代替 setInterval()
22.3.2 Yielding Processes
不同于桌面應用往往能夠隨意控制他們要的內存大小和處理器時間,JS被嚴格限制了,以防止惡意的web程序員把用戶電腦搞掛了。
22.3.3 函數節流(重要概念)
在瀏覽器中,某些計算和處理要比其他的昂貴很多,其高頻率的更改可能會讓瀏覽器崩潰。為了繞開這個問題,你可以使用定時器對該函數進行節流。
函數節流背后的基本思想是指,某些代碼不可以在沒有間斷的情況連續重復執行。
該模式的基本形式:
var processor = {
timeoutId: null,
//實際進行處理的方法
performProcessing: function(){
//實際執行的代碼
},
//初始處理調用的方法
process: function(){
clearTimeout( this.timeoutId );
var that = this;
this.timeoutId = setTimeout(function(){
that.performProcessing();
},100);
}
}
//嘗試開始執行
processor.process();
當調用了 process(),第一步是清除存好的 timeoutId,來阻止之前的調用被執行。然后創建一個新的定時器調用 performProcessing()。
可以將以上函數用下面這個函數來簡化
function throttle(method, context){
clearTimeout( method.tId );
method.tId = setTimeout(function(){
method.call(context)
},100)
}
接下來,我們再來看一個例子:
window.onresize = function(){
var div = document.getElementById('myDiv');
div.style.height = div.offsetWidth + 'px';
}
不知道大家有沒有意識到,上面這段代碼可能會帶來兩個問題:
(1):首先,要計算 offsetWidth 屬性,如果該元素或頁面上其他元素有非常復雜的CSS樣式,那么這個過程將會很復雜。
(2):其次,設置某個元素的高度需要對頁面進行回流來令改動生效。如果頁面有很多元素同時應用了相當數量的css,那么又會有很多計算。
節流優化后的代碼:
function resizeDiv(){
var div = document.getElementById('myDiv');
div.style.height = div.offsetWidth + 'px';
}
window.onresize = function(){
throttle(resizeDiv);
}
總結,只要代碼是周期性執行的,都應該使用截流,比如以下js事件:input、scroll、resize等。
如果大家有人用過 underscore.js,里面有關于函數節流(throttle)與函數去抖(debounce)兩個方法,大家可以比較學習下