你不知道的js技巧
JS進階
說起這個應該算是老生常談了吧。所謂的高級,其實就是講了一些我們平常用不到(或許用了不知道),但是非常實在的東西。算是熟練掌握js的一個必經road吧。
檢測函數類型
其實檢測函數的類型應該算是js的一個痛點,因為js是一門弱類型的語言,對類型的檢測不是那么看重。但隨著JS的發展,類型變得更加豐富。而檢測類型的復雜度,也變得復雜了~ (MD). 大致梳理一下吧。如果你想檢測值類型(Number,String,Boolean,undefined,null,Symbol). 使用typeof就可以了
typeof 23; //"number" typeof "webpack"; //"string" typeof true; //"boolean" typeof undefined; //"undefined" typeof symbol(); //"symbol" //但是有個呆毛null. typeof null; //"object" 如果理解原型鏈的話,那就無可厚非了
而檢測自定義類型,或者原生的應用類型,則需要使用到instanceof
let obj = new Object(); obj instanceof Object; //true ...
但是有時候情況往往不是這么簡單。 比如如果你想檢測iframe里面的屬性值的話,基本上是不可能的。因為檢測的前提要求是在同一個全局作用于下。所以為了能夠正常檢測一些值的類型(排除在iframe的情況).那有沒有什么萬能的方法呢? 確實有,你可以使用調用toString()的方法,得到相關的類型.
let value = new FormData(); console.log(Object.prototype.toString.call(value)); //"[object FormData]"
是不是感覺特別情切呢。 你也可以更進一步的提取。要知道,我們是有情懷的淫。
function getType(value){ //基本上可以返回所有的類型,不論你是自定義還是原生 return Object.prototype.toString.call(value).match(/\s{1}(\w+)/)[1]; } let obj = new Object(); console.log(getType(obj)); //"Object"
作用域安全的構造函數
關于函數的坑應該是無處不在(誰叫他是js里面最難懂的一個類型)。關于函數里面的this得說明一下。只有函數在運行的時候,函數里面的this才會真正的綁定.這就造成了一個問題,即,如果你在全局不小心運行了一個函數,那結果就呵呵了。 因為此時,你的this代表的window.這樣你會污染到全局的相關屬性,造成一個蜜汁bug.所以,為了安全需要在創建時,對this指針做一個判斷.
function Father(name){ this.name = name; } var jimmy = Father("jimmy"); //這樣會污染全局變量window.name的屬性。造成重寫 console.log(window.name); //"jimmy" console.log(jimmy.name); //"jimmy" //修改過后 function Father(name){ if(this instanceof Father){ this.name = name; }else{ return new Father(name); } } var jimmy = Father("jimmy"); //保證了作用域的安全性 console.log(window.name); //"xxx" console.log(jimmy.name); //"jimmy"
惰性載入函數
這個應用最多的場景應該是兼容性判斷吧。比如你寫了一個判斷綁定事件方法的檢測函數
function bind(ele,fn,type){ if(document.addEventListener){ //檢測現代瀏覽器 ele.addEventListener(type,fn,false); }else if(document.attachEvent){ //檢測低版本的IE ele.attachEvent(type,fn); } } let ele = document.querySelector("#first"); bind(ele,function(){console.log("hehe");},'click'); //執行一次判斷 bind(ele,function(){console.log("hehe");},'dbclick'); //第二次執行判斷 bind(ele,function(){console.log("hehe");},'mouseover'); //第三次執行判斷 ...
如果你綁定的事件越多,那么他每次綁定時都會執行一次判斷. 為了減少判斷次數,可以使用惰性載入函數,即,先判斷再返回函數.
function bind(){ if(document.addEventListener){ bind = function(ele,fn,type){ ele.addEventListener(type,fn,false); } }else if(document.attachEvent){ //檢測低版本的IE bind = function(ele,fn,type){ ele.attachEvent(type,fn); } }else{ throw "u browser is from outer space"; } } bind(); //首先檢測一遍,然后返回對應的檢測版本 console.log(bind); //可以檢測一下現在bind里面的內容 //當然如果不爽的話可以直接使用匿名函數,直接執行 var bind = (function(){ if(document.addEventListener){ return function(ele,fn,type){ ele.addEventListener(type,fn,false); } }else if(document.attachEvent){ //檢測低版本的IE return function(ele,fn,type){ ele.attachEvent(type,fn); } }else{ throw "u browser is from outer space"; } })(); console.log(bind);
本人推薦下面哪種寫法,因為言簡意賅,不用顯示調用~.
函數的綁定
這個坑應該大多數人都踩過.比如我使用單例,創建了一系列的函數和內容.然后再執行綁定.
let sendMsg = { ele: document.querySelector('#element'), change:function(){ this.ele.classList.toggle(".active"); //改變狀態 } } document.querySelector('#button').addEventListener('click',sendMsg.change,false);
意淫的效果是,點擊#button元素,#element會改變狀態。但實際是會報錯。找不到你的ele.
原因出現在,綁定事件的回調函數是在全局作用域中執行的。 上面那種寫法,就像當對于把change函數的代碼給拷貝到第二個參數.
即
document.querySelector('#button').addEventListener('click',function(){ this.ele.classList.toggle(".active"); //改變狀態 },false);
而執行的時候,是在window的全局環境里執行的。所以會拋出錯誤。解決辦法就是創建一個閉包,來保存這個調用方法的作用域.
document.querySelector('#button').addEventListener('click',function(){ sendMsg.change(); },false);
這樣就不會出錯了。但這樣寫有悖我們作為一名代碼藝術家的風格。 通常是不提倡使用閉包的(即不要讓別人看出來你在使用閉包). 這時候可以自己創建一個綁定函數(I call it as 代理)
function bind(fn,context){ return function(){ fn.apply(context,arguments); //arguments是作為參數傳入的 } } //上面的閉包可以改為 document.querySelector('#button').addEventListener('click',bind(sendMsg.change,sendMsg),false);
在es5中,每個函數都自帶了自已bind的方法,這樣就更容易,讓別人看不出,你在使用閉包了。
document.querySelector('#button').addEventListener('click',sendMsg.change.bind(sendMsg),false);
由于這個方法只兼容到IE9+,所以遇到IE8的時候你就呵呵了.
函數的Curry
函數的柯里化應該算是函數綁定的一個升級版。但他們兩個有個共同點就是: 都是用了閉包并且返回了一個函數. 但是Curry 可以額外的傳入參數,這是函數綁定所不具備的.關于Curry還有一個好處就是,實現自定義參數函數的重用性.
//這是JS高程上面的例子 function curry(fn){ var args = Array.prototype.slice.call(arguments,1); return function(){ var innerArgs = Array.prototype.slice.call(arguments); var final = args.concat(innerArgs); } } function add(num1,num2){ return num1+num2; } var Cadd = curry(add,5); console.log(Cadd(3)); //8 console.log(Cadd(5)); //10
可以重寫上面的bind
function bind(fn,context){ var args = Array.prototype.slice.call(arguments,2); //獲取上面兩個參數以外的其余參數 return function(){ //獲取你第二次傳入的參數,并轉化為數組 var innerArgs = Array.prototype.slice.call(arguments); var final = args.concat(innerArgs); fn.apply(context,final); //使用apply解析參數并調用. } }
這樣我們就可以傳入多個參數,而且還可以自定義參數. 當然也可以使用原來的調用方式。差不多了,覺得上面的如果你用到了,說明你的js水平應該有一些,如果沒有用到的話,可以當做學習,萬一以后踩坑了,應該知道自己是怎么屎的~