高級函數
因爲在 JS 中,全部的函數都是對象,因此使用函數指針十分簡單,也是這些東西使 JS 函數有趣且強
大
安全的類型檢測
JS 內置的類型檢測機制並非徹底可靠的
typeof
操做符返回一個字符串,表示未經計算的操做數的類型,在大多數狀況下很靠譜,可是固然還有例外
正則表達式
javascript
typeof /s/ === 'function'; // Chrome 1‐12 , 不符合 ECMAScript 5.1
typeof /s/ === 'object'; // Firefox 5+ , 符合 ECMAScript 5.1
NULL
javascript
typeof null === 'object'; // 從一開始出現JavaScript就是這樣的
在 JavaScript 最初的實現中, JavaScript 中的值是由一個表示類型的標籤和實際數據值表
示的。對象的類型標籤是 0。因爲 null 表明的是空指針(大多數平臺下值爲 0x00 ),因
此, null 的類型標籤也成爲了 0, typeof null 就錯誤的返回了 object
instanceof運算符用來測試一個對象在其原型鏈中是否存在一個構造函數的 prototype 屬性
語法
object instanceof constructor(要檢測的對象 instanceof 構造函數)
可是在瀏覽器中,咱們的腳本可能須要在多個窗口之間進行交互。多個窗口意味着多個全局環境,不
同的全局環境擁有不一樣的全局對象,從而擁有不一樣的內置類型構造函數。這可能會引起一些問題。
javascript
[] instanceof window.frames[0].Array //false
由於 Array.prototype !== window.frames[0].Array.prototype ,所以你必須使用
Array.isArray(myObj) 或者 Object.prototype.toString.call(myObj) === "[object Array]" 來判
斷 myObj 是不是數組
解決以上兩個問題的方案就是 Object.prototype.toString
Object.prototype.toString
方法返回一個表示該對象的字符串
能夠經過 toString() 來獲取每一個對象的類型。爲了每一個對象都能經過
Object.prototype.toString() 來檢測,須要以 Function.prototype.call() 或者
Function.prototype.apply() 的形式來調用,傳遞要檢查的對象做爲第一個參數,稱爲 thisArg
javascript
var toString = Object.prototype.toString;
toString.call(new Date); // [object Date]
toString.call(new String); // [object String]
toString.call(Math); // [object Math]
toString.call(/s/); // [object RegExp]
toString.call([]); // [object Array]
//Since JavaScript 1.8.5
toString.call(undefined); // [object Undefined]
toString.call(null); // [object Null]
做用域安全的構造函數構造函數其實就是一個使用 new 操做符調用的函數。當使用 new 調用時,構造函數內用到的 this 對
象會指向新建立的對象實例
javascript
function Person(name, age){
this.name = name;
this.age = age;
}
let person = new Person("addone", 20);
person.name // addone
當你使用 new 操做符的時候,就會建立一個新的 Person 對象,同時分配這些屬性,可是若是你沒有
使用 new
javascript
let person = Person("addone", 20);
person1.name // Cannot read property 'name' of undefined
window.name // addone
這是由於 this 是在執行時確認的,當你沒有使用 new ,那麼 this 在當前狀況下就被解析成了
window ,屬性就被分配到 window 上了
做用域安全的構造函數在進行更改前,首先確認 this 對象是正確類型的實例,若是不是,就建立新
的對象而且返回
javascript
function Person(name, age){
if(this instanceof Person){
this.name = name;
this.age = age;
}else{
return new Person(name, age);
}
}
let person1 = new Person("addone", 20);
person1.name // addone
let person2 = Person("addone", 20);
person2.name // addone
this instanceof Person 檢查了 this 對象是否是 Person 的實例,若是是則繼續,不是則調用 new惰性載入函數
假如你要寫一個函數,裏面有一些判斷語句
javascript
function foo(){
if(a != b){
console.log('aaa')
}else{
console.log('bbb')
}
}
若是你的 a 和 b 是不變的,那麼這個函數不論執行多少次,結果都是不變的,可是每次執行還要進
行 if 判斷,這就形成了沒必要要的浪費。
惰性載入表示函數執行的分支只會發生一次,這裏有兩種解決方式。
在函數被調用時再處理函數
javascript
function foo(){
if(a != b){
foo = function(){
console.log('aaa')
}
}else{
foo = function(){
console.log('bbb')
}
}
return foo();
}
這樣進入每一個分支後都會對 foo 進行賦值,覆蓋了以前的函數,以後每次調用 foo 就不會再執行 if
判斷
在聲明函數時就指定適當的函數
javascript
var foo = (function foo(){
if(a != b){
return function(){
console.log('aaa')
}}else{
return function(){
console.log('bbb')
}
}
})();
這裏建立一個匿名,自執行的函數,用來肯定應該使用哪個函數來實現。
惰性函數的優勢就是隻在第一次執行分支時犧牲一點點性能
函數綁定
請使用 fun.bind(thisArg[, arg1[, arg2[, ...]]])
thisArg
當綁定函數被調用時,該參數會做爲原函數運行時的 this 指向。當使用 new 操做符調用綁定函數
時,該參數無效
arg1,arg2,...
當綁定函數被調用時,這些參數將置於實參以前傳遞給被綁定的方法
返回
由指定的this值和初始化參數改造的原函數拷貝
一個例子
javascript
let person = {
name: 'addone',
click: function(e){
console.log(this.name)
}
}
let btn = document.getElementById('btn');
EventUtil.addHandle(btn, 'click', person.click);這裏建立了一個 person 對象,而後將 person.click 方法分配給 DOM 按鈕的事件處理程序,當你點擊
按按鈕時,會打印出 undefiend ,緣由是執行時 this 指向了 DOM 按鈕而不是 person
解決方案: 將 this 強行指向 person
javascript
EventUtil.addHandle(btn, 'click', person.click.bind(person));
函數柯里化
函數柯里化是把接受多個參數的函數轉變成接受單一參數的函數
javascript
function add(num1, num2){
return num1 + num2;
}
function curryAdd(num2){
return add(1, num2);
}
add(2, 3) // 5
curryAdd(2) // 3
這個例子用來方便理解柯里化的概念
下面是建立函數柯里化的通用方式
javascript
function curry(fn){
var args = Array.prototype.slice.call(arguments, 1);
return function(){
let innerArgs = Array.prototype.slice.call(arguments);
let finalArgs = args.concat(innerArgs);
return fn.apply(null, finalArgs);
}
}
第一個參數是要進行柯里化的函數,其餘參數是要傳入的值。這裏使
用 Array.prototype.slice.call(arguments, 1) 來獲取第一個參數後的全部參數(外部)。在返回的函數
中,一樣調用 Array.prototype.slice.call(arguments) 讓 innerArgs 來存放全部的參數(內部),而後
用 concat 將內部外部參數組合,用 apply 傳遞給函數javascript
function add(num1, num2){
return num1 + num2;
}
let curryAdd1 = curry(add, 1);
curryAdd1(2); // 3
let curryAdd2 = curry(add, 1, 2);
curryAdd2(); // 3
防篡改對象
Javascript 中任何對象均可以被同一環境中運行的代碼修改,因此開發人員有時候須要定義防篡改對
象(tamper-proof object) 來保護本身
不可擴展對象
默認狀況下全部對象都是能夠擴展的(添加屬性和方法)
javascript
let person = { name: 'addone' };
person.age = 20;
第二行爲 person 對象擴展了 age 屬性,固然你能夠阻止這一行爲,使用 Object.preventExtensions()
javascript
let person = { name: 'addone' };
Object.preventExtensions(person);
person.age = 20;
person.age // undefined
你還能夠用 Object.isExtensible() 來判斷對象是否是可擴展的
javascript
let person = { name: 'addone' };
Object.isExtensible(person); // true
Object.preventExtensions(person);
Object.isExtensible(person); // false請記住這是不可擴展!!,即不能添加屬性或方法
密封的對象
密封對象不可擴展,且不能刪除屬性和方法
javascript
let person = { name: 'addone' };
Object.seal(person);
person.age = 20;
delete person.name;
person.age // undefined
person.name // addone
相對的也有 Object.isSealed() 來判斷是否密封
javascript
let person = { name: 'addone' };
Object.isExtensible(person); // true
Object.isSealed(person); // false
Object.seal(person);
Object.isExtensible(person); // false
Object.isSealed(person); // true
凍結的對象
這是最嚴格的防篡改級別,凍結的對象即不可擴展,又密封,且不能修改
javascript
let person = { name: 'addone' };
Object.freeze(person);
person.age = 20;
delete person.name;
person.name = 'addtwo'
person.age // undefined
person.name // addone一樣也有 Object.isFrozen 來檢測
javascript
let person = { name: 'addone' };
Object.isExtensible(person); // true
Object.isSealed(person); // false
Object.isFrozen(person); // false
Object.freeze(person);
Object.isExtensible(person); // false
Object.isSealed(person); // true
Object.isFrozen(person); // true
以上三種方法在嚴格模式下進行錯誤操做均會致使拋出錯誤
高級定時器
閱讀前提
大概理解 setTimeout 的基本執行機制和 js 事件機制
重複的定時器
當你使用 setInterval 重複定義多個定時器的時候,可能會出現某個定時器代碼在代碼再次被添加到
執行隊列以前尚未完成執行,致使定時器代碼連續執行屢次。
機智 Javascript 引擎解決了這個問題,使用 setInterval() 的時候,僅當沒有該定時器的其餘代碼實
例時,纔會將定時器代碼添加到隊列中。但這還會致使一些問題:
某些間隔被跳過
間隔可能比預期的小
爲了不這個兩個問題,你可使用鏈式 setTimeout() 調用
javascript
setTimeout(function(){
TODO();
setTimeout(arguments.callee, interval);
}, interval)arguments.callee 獲取了當前執行函數的引用,而後爲其設置另一個定時器,這樣就確保在下一次
定時器代碼執行前,必須等待指定的間隔。
Yielding Processes
瀏覽器對長時間運行的腳本進行了制約,若是代碼運行超過特定的時間或者特定語句數量就不會繼續
執行。
若是你發現某個循環佔用了大量的時間,那麼對於下面這兩個問題
該處理是否必須同步完成?
數據是否必須按順序完成?
若是你的兩個答案都是"否",那麼你可使用一種叫作數組分塊(array chunking) 的技術。基本思路
是爲要處理的項目建立一個隊列,而後使用定時器取出下一個要出處理的項目進行處理,而後再設置
另外一個定時器。
javascript
function chunk(array, process, context){
setTimeout(function(){
// 取出下一個項目進行處理
let item = array.shift();
process.call(item);
if(array.length > 0){
setTimeout(arguments.callee, 100);
}
}, 100)
}
這裏接受三個參數,要處理的數組,處理的函數,運行該函數的環境(可選),這裏設置間隔 100ms 是
個效果不錯的選擇
若是你一個函數須要50ms以上時間完成,那麼最好看看可否將任務分割成一系列可使用定
時器的小任務
函數節流(Throttle)
節流的目的是防止某些操做執行的太快。好比在調整瀏覽器大小的時候會出發 onresize 事件,若是在
其內部進行一些 DOM 操做,這種高頻率的更愛可能會使瀏覽器崩潰。爲了不這種狀況,能夠採起函數節流的方式。
javascript
function throttle(method, context){
clearTimeout(method.tId);
method.tId = setTimeout(function(){
method.call(context);
}, 100)
}
這裏接受兩個參數,要執行的函數,執行的環境。執行時先清除以前的定時器,而後將當前定時器賦
值給方法的 tId ,以後調用 call 來肯定函數的執行環境。
一個應用的例子
javascript
function resizeDiv(){
let div = document.getElementById('div');
div.style.height = div.offsetWidth + "px";
}
window.onresize = function(){
throttle(resizeDiv);
}