JS高級(jí)技巧(簡(jiǎn)潔版)
安全的類型檢測(cè)
JS內(nèi)置的類型檢測(cè)機(jī)制并不是完全可靠的
typeof
操作符返回一個(gè)字符串,表示未經(jīng)計(jì)算的操作數(shù)的類型,在大多數(shù)情況下很靠譜,但是當(dāng)然還有例外
正則表達(dá)式
typeof /s/ === 'function'; // Chrome 1-12 , 不符合 ECMAScript 5.1 typeof /s/ === 'object'; // Firefox 5+ , 符合 ECMAScript 5.1
NULL
typeof null === 'object'; // 從一開始出現(xiàn)JavaScript就是這樣的
在 JavaScript 最初的實(shí)現(xiàn)中,JavaScript 中的值是由一個(gè)表示類型的標(biāo)簽和實(shí)際數(shù)據(jù)值表示的。對(duì)象的類型標(biāo)簽是 0。由于 null
代表的是空指針(大多數(shù)平臺(tái)下值為 0x00),因此,null
的類型標(biāo)簽也成為了 0,typeof null
就錯(cuò)誤的返回了object
instanceof
運(yùn)算符用來(lái)測(cè)試一個(gè)對(duì)象在其原型鏈中是否存在一個(gè)構(gòu)造函數(shù)的 prototype 屬性
語(yǔ)法
object instanceof constructor(要檢測(cè)的對(duì)象 instanceof 構(gòu)造函數(shù))
但是在瀏覽器中,我們的腳本可能需要在多個(gè)窗口之間進(jìn)行交互。多個(gè)窗口意味著多個(gè)全局環(huán)境,不同的全局環(huán)境擁有不同的全局對(duì)象,從而擁有不同的內(nèi)置類型構(gòu)造函數(shù)。這可能會(huì)引發(fā)一些問(wèn)題。
[] instanceof window.frames[0].Array //false
因?yàn)?Array.prototype !== window.frames[0].Array.prototype
,因此你必須使用 Array.isArray(myObj)
或者 Object.prototype.toString.call(myObj) === "[object Array]"
來(lái)判斷myObj是否是數(shù)組
解決以上兩個(gè)問(wèn)題的方案就是Object.prototype.toString
Object.prototype.toString
方法返回一個(gè)表示該對(duì)象的字符串
可以通過(guò)toString()
來(lái)獲取每個(gè)對(duì)象的類型。為了每個(gè)對(duì)象都能通過(guò) Object.prototype.toString()
來(lái)檢測(cè),需要以 Function.prototype.call()
或者 Function.prototype.apply()
的形式來(lái)調(diào)用,傳遞要檢查的對(duì)象作為第一個(gè)參數(shù),稱為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]
作用域安全的構(gòu)造函數(shù)
構(gòu)造函數(shù)其實(shí)就是一個(gè)使用new
操作符調(diào)用的函數(shù)。當(dāng)使用new
調(diào)用時(shí),構(gòu)造函數(shù)內(nèi)用到的this
對(duì)象會(huì)指向新創(chuàng)建的對(duì)象實(shí)例
function Person(name, age){ this.name = name; this.age = age; } let person = new Person("addone", 20); person.name // addone
當(dāng)你使用new
操作符的時(shí)候,就會(huì)創(chuàng)建一個(gè)新的Person
對(duì)象,同時(shí)分配這些屬性,但是如果你沒(méi)有使用new
let person = Person("addone", 20); person1.name // Cannot read property 'name' of undefined window.name // addone
這是因?yàn)?code>this是在執(zhí)行時(shí)確認(rèn)的,當(dāng)你沒(méi)有使用new
,那么this
在當(dāng)前情況下就被解析成了window
,屬性就被分配到window
上了
作用域安全的構(gòu)造函數(shù)在進(jìn)行更改前,首先確認(rèn)this
對(duì)象是正確類型的實(shí)例,如果不是,就創(chuàng)建新的對(duì)象并且返回
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
對(duì)象是不是Person
的實(shí)例,如果是則繼續(xù),不是則調(diào)用new
惰性載入函數(shù)
假如你要寫一個(gè)函數(shù),里面有一些判斷語(yǔ)句
function foo(){ if(a != b){ console.log('aaa') }else{ console.log('bbb') } }
如果你的a
和b
是不變的,那么這個(gè)函數(shù)不論執(zhí)行多少次,結(jié)果都是不變的,但是每次執(zhí)行還要進(jìn)行if判斷,這就造成了不必要的浪費(fèi)。
惰性載入表示函數(shù)執(zhí)行的分支只會(huì)發(fā)生一次,這里有兩種解決方式。
在函數(shù)被調(diào)用時(shí)再處理函數(shù)
function foo(){ if(a != b){ foo = function(){ console.log('aaa') } }else{ foo = function(){ console.log('bbb') } } return foo(); }
這樣進(jìn)入每個(gè)分支后都會(huì)對(duì)foo
進(jìn)行賦值,覆蓋了之前的函數(shù),之后每次調(diào)用foo
就不會(huì)再執(zhí)行if
判斷
在聲明函數(shù)時(shí)就指定適當(dāng)?shù)暮瘮?shù)
var foo = (function foo(){ if(a != b){ return function(){ console.log('aaa') } }else{ return function(){ console.log('bbb') } } })();
這里創(chuàng)建一個(gè)匿名,自執(zhí)行的函數(shù),用來(lái)確定應(yīng)該使用哪一個(gè)函數(shù)來(lái)實(shí)現(xiàn)。
惰性函數(shù)的優(yōu)點(diǎn)就是只在第一次執(zhí)行分支時(shí)犧牲一點(diǎn)點(diǎn)性能
函數(shù)綁定
請(qǐng)使用fun.bind(thisArg[, arg1[, arg2[, ...]]])
thisArg
當(dāng)綁定函數(shù)被調(diào)用時(shí),該參數(shù)會(huì)作為原函數(shù)運(yùn)行時(shí)的 this 指向。當(dāng)使用new 操作符調(diào)用綁定函數(shù)時(shí),該參數(shù)無(wú)效
arg1,arg2,...
當(dāng)綁定函數(shù)被調(diào)用時(shí),這些參數(shù)將置于實(shí)參之前傳遞給被綁定的方法
返回
由指定的this值和初始化參數(shù)改造的原函數(shù)拷貝
一個(gè)例子
let person = { name: 'addone', click: function(e){ console.log(this.name) } } let btn = document.getElementById('btn'); EventUtil.addHandle(btn, 'click', person.click);
這里創(chuàng)建了一個(gè)person
對(duì)象,然后將person.click
方法分配給DOM
按鈕的事件處理程序,當(dāng)你點(diǎn)擊按按鈕時(shí),會(huì)打印出undefiend
,原因是執(zhí)行時(shí)this
指向了DOM
按鈕而不是person
解決方案: 將this
強(qiáng)行指向person
EventUtil.addHandle(btn, 'click', person.click.bind(person));
函數(shù)柯里化
函數(shù)柯里化是把接受多個(gè)參數(shù)的函數(shù)轉(zhuǎn)變成接受單一參數(shù)的函數(shù)
function add(num1, num2){ return num1 + num2; } function curryAdd(num2){ return add(1, num2); } add(2, 3) // 5 curryAdd(2) // 3
這個(gè)例子用來(lái)方便理解柯里化的概念
下面是創(chuàng)建函數(shù)柯里化的通用方式
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); } }
第一個(gè)參數(shù)是要進(jìn)行柯里化的函數(shù),其他參數(shù)是要傳入的值。這里使用Array.prototype.slice.call(arguments, 1)
來(lái)獲取第一個(gè)參數(shù)后的所有參數(shù)(外部)。在返回的函數(shù)中,同樣調(diào)用Array.prototype.slice.call(arguments)
讓innerArgs
來(lái)存放所有的參數(shù)(內(nèi)部),然后用concat將內(nèi)部外部參數(shù)組合,用apply
傳遞給函數(shù)
function add(num1, num2){ return num1 + num2; } let curryAdd1 = curry(add, 1); curryAdd1(2); // 3 let curryAdd2 = curry(add, 1, 2); curryAdd2(); // 3
防篡改對(duì)象
Javascript中任何對(duì)象都可以被同一環(huán)境中運(yùn)行的代碼修改,所以開發(fā)人員有時(shí)候需要定義防篡改對(duì)象(tamper-proof object) 來(lái)保護(hù)自己
不可擴(kuò)展對(duì)象
默認(rèn)情況下所有對(duì)象都是可以擴(kuò)展的(添加屬性和方法)
let person = { name: 'addone' }; person.age = 20;
第二行為person
對(duì)象擴(kuò)展了age
屬性,當(dāng)然你可以阻止這一行為,使用Object.preventExtensions()
let person = { name: 'addone' }; Object.preventExtensions(person); person.age = 20; person.age // undefined
你還可以用Object.isExtensible()
來(lái)判斷對(duì)象是不是可擴(kuò)展的
let person = { name: 'addone' }; Object.isExtensible(person); // true Object.preventExtensions(person); Object.isExtensible(person); // false
請(qǐng)記住這是不可擴(kuò)展!!,即不能添加屬性或方法
密封的對(duì)象
密封對(duì)象不可擴(kuò)展,且不能刪除屬性和方法
let person = { name: 'addone' }; Object.seal(person); person.age = 20; delete person.name; person.age // undefined person.name // addone
相對(duì)的也有Object.isSealed()
來(lái)判斷是否密封
let person = { name: 'addone' }; Object.isExtensible(person); // true Object.isSealed(person); // false Object.seal(person); Object.isExtensible(person); // false Object.isSealed(person); // true
凍結(jié)的對(duì)象
這是最嚴(yán)格的防篡改級(jí)別,凍結(jié)的對(duì)象即不可擴(kuò)展,又密封,且不能修改
let person = { name: 'addone' }; Object.freeze(person); person.age = 20; delete person.name; person.name = 'addtwo' person.age // undefined person.name // addone
同樣也有Object.isFrozen
來(lái)檢測(cè)
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
以上三種方法在嚴(yán)格模式下進(jìn)行錯(cuò)誤操作均會(huì)導(dǎo)致拋出錯(cuò)誤
高級(jí)定時(shí)器
閱讀前提
大概理解setTimeout的基本執(zhí)行機(jī)制和js事件機(jī)制
重復(fù)的定時(shí)器
當(dāng)你使用setInterva
l重復(fù)定義多個(gè)定時(shí)器的時(shí)候,可能會(huì)出現(xiàn)某個(gè)定時(shí)器代碼在代碼再次被添加到執(zhí)行隊(duì)列之前還沒(méi)有完成執(zhí)行,導(dǎo)致定時(shí)器代碼連續(xù)執(zhí)行多次。
機(jī)智Javascript引擎解決了這個(gè)問(wèn)題,使用setInterval()
的時(shí)候,僅當(dāng)沒(méi)有該定時(shí)器的其他代碼實(shí)例時(shí),才會(huì)將定時(shí)器代碼添加到隊(duì)列中。但這還會(huì)導(dǎo)致一些問(wèn)題:
- 某些間隔被跳過(guò)
- 間隔可能比預(yù)期的小
為了避免這個(gè)兩個(gè)問(wèn)題,你可以使用鏈?zhǔn)?code>setTimeout()調(diào)用
setTimeout(function(){ TODO(); setTimeout(arguments.callee, interval); }, interval)
arguments.callee
獲取了當(dāng)前執(zhí)行函數(shù)的引用,然后為其設(shè)置另外一個(gè)定時(shí)器,這樣就確保在下一次定時(shí)器代碼執(zhí)行前,必須等待指定的間隔。
Yielding Processes
瀏覽器對(duì)長(zhǎng)時(shí)間運(yùn)行的腳本進(jìn)行了制約,如果代碼運(yùn)行超過(guò)特定的時(shí)間或者特定語(yǔ)句數(shù)量就不會(huì)繼續(xù)執(zhí)行。
如果你發(fā)現(xiàn)某個(gè)循環(huán)占用了大量的時(shí)間,那么對(duì)于下面這兩個(gè)問(wèn)題
- 該處理是否必須同步完成?
- 數(shù)據(jù)是否必須按順序完成?
如果你的兩個(gè)答案都是"否",那么你可以使用一種叫做數(shù)組分塊(array chunking) 的技術(shù)?;舅悸肥菫橐幚淼捻?xiàng)目創(chuàng)建一個(gè)隊(duì)列,然后使用定時(shí)器取出下一個(gè)要出處理的項(xiàng)目進(jìn)行處理,然后再設(shè)置另一個(gè)定時(shí)器。
function chunk(array, process, context){ setTimeout(function(){ // 取出下一個(gè)項(xiàng)目進(jìn)行處理 let item = array.shift(); process.call(item); if(array.length > 0){ setTimeout(arguments.callee, 100); } }, 100) }
這里接受三個(gè)參數(shù),要處理的數(shù)組,處理的函數(shù),運(yùn)行該函數(shù)的環(huán)境(可選),這里設(shè)置間隔100ms是個(gè)效果不錯(cuò)的選擇
如果你一個(gè)函數(shù)需要50ms以上時(shí)間完成,那么最好看看能否將任務(wù)分割成一系列可以使用定時(shí)器的小任務(wù)
函數(shù)節(jié)流(Throttle)
節(jié)流的目的是防止某些操作執(zhí)行的太快。比如在調(diào)整瀏覽器大小的時(shí)候會(huì)出發(fā)onresize
事件,如果在其內(nèi)部進(jìn)行一些DOM
操作,這種高頻率的更愛可能會(huì)使瀏覽器崩潰。為了避免這種情況,可以采取函數(shù)節(jié)流的方式。
function throttle(method, context){ clearTimeout(method.tId); method.tId = setTimeout(function(){ method.call(context); }, 100) }
這里接受兩個(gè)參數(shù),要執(zhí)行的函數(shù),執(zhí)行的環(huán)境。執(zhí)行時(shí)先清除之前的定時(shí)器,然后將當(dāng)前定時(shí)器賦值給方法的tId
,之后調(diào)用call
來(lái)確定函數(shù)的執(zhí)行環(huán)境。
一個(gè)應(yīng)用的例子
function resizeDiv(){ let div = document.getElementById('div'); div.style.height = div.offsetWidth + "px"; } window.onresize = function(){ throttle(resizeDiv); }
這個(gè)就不用講了吧2333
文章參考于《JavaScript高級(jí)程序設(shè)計(jì)(第三版)》
如果你覺(jué)得我的理解有問(wèn)題或者整理的太簡(jiǎn)略,那么我強(qiáng)烈安利你自己去讀一下這本書~
相關(guān)文章
JS模擬實(shí)現(xiàn)Excel條件格式中的色階效果
這篇文章主要為大家詳細(xì)介紹了如何利用JavaScript模擬實(shí)現(xiàn)Excel條件格式中的色階效果,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以嘗試一下2023-05-05easyui combobox開啟搜索自動(dòng)完成功能的實(shí)例代碼
下面小編就為大家?guī)?lái)一篇easyui combobox開啟搜索自動(dòng)完成功能的實(shí)例代碼。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2016-11-11JavaScript清空數(shù)組元素的兩種方法簡(jiǎn)單比較
這篇文章主要介紹了JavaScript清空數(shù)組元素的兩種方法簡(jiǎn)單比較,羅列了幾種常見的情況javascript數(shù)組的方法,并且比較了其中的兩種常見方法,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-07-07JS實(shí)現(xiàn)的郵箱提示補(bǔ)全效果示例
這篇文章主要介紹了JS實(shí)現(xiàn)的郵箱提示補(bǔ)全效果,涉及javascript正則匹配、事件響應(yīng)及頁(yè)面元素動(dòng)態(tài)操作相關(guān)技巧,需要的朋友可以參考下2018-01-01js 客戶端打印html 并且去掉頁(yè)眉、頁(yè)腳的實(shí)例
下面小編就為大家?guī)?lái)一篇js 客戶端打印html 并且去掉頁(yè)眉、頁(yè)腳的實(shí)例。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-11-11FireFox下XML對(duì)象轉(zhuǎn)化成字符串的解決方法
近,在客戶端解吸xml時(shí)候,需要將XML轉(zhuǎn)化成字符串,看了很多文章,介紹的最多的是將XML字符串轉(zhuǎn)化成對(duì)象2011-12-12PNGHandler-借助JS讓PNG圖在IE下實(shí)現(xiàn)透明(包括背景圖)
PNGHandler-借助JS讓PNG圖在IE下實(shí)現(xiàn)透明(包括背景圖)...2007-08-08如何使用ES6的class類繼承來(lái)實(shí)現(xiàn)絢麗小球效果
JS是由ES(ECMAScript)、DOM(瀏覽器文檔對(duì)象)、BOM(瀏覽器對(duì)象模型)組成,這篇文章主要給大家介紹了關(guān)于如何使用ES6的class類繼承來(lái)實(shí)現(xiàn)絢麗小球效果的相關(guān)資料,需要的朋友可以參考下2021-06-06