您好,登錄后才能下訂單哦!
安全的類型檢測
JS內置的類型檢測機制并不是完全可靠的
typeof
操作符返回一個字符串,表示未經計算的操作數的類型,在大多數情況下很靠譜,但是當然還有例外
正則表達式
typeof /s/ === 'function'; // Chrome 1-12 , 不符合 ECMAScript 5.1 typeof /s/ === 'object'; // Firefox 5+ , 符合 ECMAScript 5.1
NULL
typeof null === 'object'; // 從一開始出現JavaScript就是這樣的
在 JavaScript 最初的實現中,JavaScript 中的值是由一個表示類型的標簽和實際數據值表示的。對象的類型標簽是 0。由于 null
代表的是空指針(大多數平臺下值為 0x00),因此,null
的類型標簽也成為了 0,typeof null
就錯誤的返回了object
instanceof
運算符用來測試一個對象在其原型鏈中是否存在一個構造函數的 prototype 屬性
語法
object instanceof constructor(要檢測的對象 instanceof 構造函數)
但是在瀏覽器中,我們的腳本可能需要在多個窗口之間進行交互。多個窗口意味著多個全局環境,不同的全局環境擁有不同的全局對象,從而擁有不同的內置類型構造函數。這可能會引發一些問題。
[] 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
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
對象會指向新創建的對象實例
function Person(name, age){ this.name = name; this.age = age; } let person = new Person("addone", 20); person.name // addone
當你使用new
操作符的時候,就會創建一個新的Person
對象,同時分配這些屬性,但是如果你沒有使用new
let person = Person("addone", 20); person1.name // Cannot read property 'name' of undefined window.name // addone
這是因為this
是在執行時確認的,當你沒有使用new
,那么this
在當前情況下就被解析成了window
,屬性就被分配到window
上了
作用域安全的構造函數在進行更改前,首先確認this
對象是正確類型的實例,如果不是,就創建新的對象并且返回
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
惰性載入函數
假如你要寫一個函數,里面有一些判斷語句
function foo(){ if(a != b){ console.log('aaa') }else{ console.log('bbb') } }
如果你的a
和b
是不變的,那么這個函數不論執行多少次,結果都是不變的,但是每次執行還要進行if判斷,這就造成了不必要的浪費。
惰性載入表示函數執行的分支只會發生一次,這里有兩種解決方式。
在函數被調用時再處理函數
function foo(){ if(a != b){ foo = function(){ console.log('aaa') } }else{ foo = function(){ console.log('bbb') } } return foo(); }
這樣進入每個分支后都會對foo
進行賦值,覆蓋了之前的函數,之后每次調用foo
就不會再執行if
判斷
在聲明函數時就指定適當的函數
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值和初始化參數改造的原函數拷貝
一個例子
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
EventUtil.addHandle(btn, 'click', person.click.bind(person));
函數柯里化
函數柯里化是把接受多個參數的函數轉變成接受單一參數的函數
function add(num1, num2){ return num1 + num2; } function curryAdd(num2){ return add(1, num2); } add(2, 3) // 5 curryAdd(2) // 3
這個例子用來方便理解柯里化的概念
下面是創建函數柯里化的通用方式
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
傳遞給函數
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) 來保護自己
不可擴展對象
默認情況下所有對象都是可以擴展的(添加屬性和方法)
let person = { name: 'addone' }; person.age = 20;
第二行為person
對象擴展了age
屬性,當然你可以阻止這一行為,使用Object.preventExtensions()
let person = { name: 'addone' }; Object.preventExtensions(person); person.age = 20; person.age // undefined
你還可以用Object.isExtensible()
來判斷對象是不是可擴展的
let person = { name: 'addone' }; Object.isExtensible(person); // true Object.preventExtensions(person); Object.isExtensible(person); // false
請記住這是不可擴展!!,即不能添加屬性或方法
密封的對象
密封對象不可擴展,且不能刪除屬性和方法
let person = { name: 'addone' }; Object.seal(person); person.age = 20; delete person.name; person.age // undefined person.name // addone
相對的也有Object.isSealed()
來判斷是否密封
let person = { name: 'addone' }; Object.isExtensible(person); // true Object.isSealed(person); // false Object.seal(person); Object.isExtensible(person); // false Object.isSealed(person); // true
凍結的對象
這是最嚴格的防篡改級別,凍結的對象即不可擴展,又密封,且不能修改
let person = { name: 'addone' }; Object.freeze(person); person.age = 20; delete person.name; person.name = 'addtwo' person.age // undefined person.name // addone
同樣也有Object.isFrozen
來檢測
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事件機制
重復的定時器
當你使用setInterva
l重復定義多個定時器的時候,可能會出現某個定時器代碼在代碼再次被添加到執行隊列之前還沒有完成執行,導致定時器代碼連續執行多次。
機智Javascript引擎解決了這個問題,使用setInterval()
的時候,僅當沒有該定時器的其他代碼實例時,才會將定時器代碼添加到隊列中。但這還會導致一些問題:
為了避免這個兩個問題,你可以使用鏈式setTimeout()
調用
setTimeout(function(){ TODO(); setTimeout(arguments.callee, interval); }, interval)
arguments.callee
獲取了當前執行函數的引用,然后為其設置另外一個定時器,這樣就確保在下一次定時器代碼執行前,必須等待指定的間隔。
Yielding Processes
瀏覽器對長時間運行的腳本進行了制約,如果代碼運行超過特定的時間或者特定語句數量就不會繼續執行。
如果你發現某個循環占用了大量的時間,那么對于下面這兩個問題
如果你的兩個答案都是"否",那么你可以使用一種叫做數組分塊(array chunking) 的技術。基本思路是為要處理的項目創建一個隊列,然后使用定時器取出下一個要出處理的項目進行處理,然后再設置另一個定時器。
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
操作,這種高頻率的更愛可能會使瀏覽器崩潰。為了避免這種情況,可以采取函數節流的方式。
function throttle(method, context){ clearTimeout(method.tId); method.tId = setTimeout(function(){ method.call(context); }, 100) }
這里接受兩個參數,要執行的函數,執行的環境。執行時先清除之前的定時器,然后將當前定時器賦值給方法的tId
,之后調用call
來確定函數的執行環境。
一個應用的例子
function resizeDiv(){ let div = document.getElementById('div'); div.style.height = div.offsetWidth + "px"; } window.onresize = function(){ throttle(resizeDiv); }
這個就不用講了吧2333
文章參考于《JavaScript高級程序設計(第三版)》
如果你覺得我的理解有問題或者整理的太簡略,那么我強烈安利你自己去讀一下這本書~
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。