函數劫持能作啥?前端黑科技揭祕

啥是函數劫持?其實就是給調用的函數加一層包裝函數,在包裝函數裏調用一下原函數,有點像代理服務器的做用, 攔截一些全局對象和方法, 能實現一些很是便利的全局功能,今天我就拋磚引玉跟你們分享一下。前端

人生苦短,苦中作樂的console.log 美化

前段時間看到emoji圖標在地址欄的動畫應用,挺有趣的,很娛樂,我就試着能不能把emoji應用於到我正在研究的函數劫持的實例中,實際作了一下效果還不錯,自娛自樂一下,也分享給你們使用,給平淡的開發生活添加一抹亮色。node

console.log自己支持樣表式,第一個參數中加入 %c格式化輸出,第二個參數就能夠傳入樣式表了,能夠實現漸變色,顯示圖片等很是漂亮的效果。 能不能讓console.log 默認就改變顏色呢,咱們試着劫持 console.log 給它添加默認功能 ,讓它每次都隨機更換一種顏色和圖標進行輸出,爲了保護視力,增大一號字體,效果以下:git

var _consoleLog=window.console.log;
window.console.log=function (){
    if(arguments.length==1 && typeof arguments[0]!="object"){
        var iconList=["🌏","🌛","🌟","🔥","🐦","👿","🎃","🎊","🎋","🎁","🍀","🌷","🌱","🍁","🌸","🍃","🌿","🍒","🍎","🍊","🍓","🍍","🍇"]
        var colorList=["background-color:#8BC34A;color:white",
                        "background-color:#42b983;color:white",
                        "background-color:#33A5FF;color:white",
                        "background-color:orange;color:white",
                        "background-color:#2EAFB0;color:white",
                        "background-color:#6EC1C2;color:white",
                        "background-color:#FFDD4D;color:black",
                        "background-color:#7F2B82;color:white",
                        "background-color:#4b4b4b;color:white",
                        "background-color:#E41A6A;color:white"]
        var idx1=Math.floor(Math.random()*iconList.length);
        var idx2=Math.floor(Math.random()*colorList.length);
        var msg=arguments[0];
        msg="%c "+iconList[idx1]+" "+msg;
        
        _consoleLog(msg,"font-size:20pt;"+colorList[idx2])
    }else{
        _consoleLog.apply(this,arguments)
    }
}

console.log("hello,world")
複製代碼

在公共代碼裏引入一下,天天就能夠看到五彩繽紛的console輸出了。注:win 7及如下操做系統不支持emoji表情的顏色顯示github

防眼花的方法:json輸出自動格式化

JSON.stringify會把JSON輸出成一行,很是難看,有沒有方法美化一下呢,咱們來看下mdn文檔: JSON.stringify有3個參數,第3個參數能夠指定縮進用的空白字符串,用於美化輸出(pretty-print);若是參數是個數字,它表明有多少的空格;上限爲10。該值若小於1,則意味着沒有空格;若是該參數爲字符串(字符串的前十個字母),該字符串將被做爲空格;那麼第二個參數用來幹嗎呢,原來是一個回調函數能夠用於過濾每個屬性值,通常用不到傳null就行了,web

JSON.stringify({result:{a:1,b:2}},null,4)
複製代碼

輸出結果已是格式化好的json了, 完美:ajax

{
    "result": {
        "a": 1,
        "b": 2
    }
}
複製代碼

可是每次調用都傳3個參數太煩了,況且還有不少同窗不知道這種用法,咱們嘗試用函數劫持封裝一下,這樣就默認帶格式化了:json

var _stringify=JSON.stringify
JSON.stringify=function (){
    if(arguments.length==1){
    	return _stringify(arguments[0],null,4)
    }else{
    	return _stringify.apply(this,arguments)
    }
}
複製代碼

再來試試JSON.stringify吧,不用傳3個參數也能格式化輸出了。須要注意的是不要重複執行函數劫持,不然會產生遞歸死循環。那麼怎麼判斷已經劫持過了呢,調用一下函數的toString方法,若是是原生的函數,會返回 [native code] ,以下後端

"function stringify() { [native code] }"
複製代碼

作一下字符檢測就能夠了。瀏覽器

未雨綢繆,防手賤執行alert

有些新手喜歡用alert 調試,這種代碼若是帶到線上簡直是災難,咱們寫個劫持,永遠杜絕這個問題:bash

window.alert=function (){
console.log(arguments[0])
}
複製代碼

再也彈不出的alert,你還好嗎........

真正的黑科技1:mock.js 瞞天過海攔截ajax請求

壓軸戲來了,在先後端分離的開發流程中,不可避免要用到mock數據,常見的有兩種方案。

  1. 客戶端mock,好處是不用搭建服務器,在前端就把請求攔截下來。
  2. 服務端mock,就是作一個空接口,返回死數據。比較接近於真實環境的http交互流程。

我本身在多年前就實現了mockjs相似的功能,後來才發現有這樣一個開源項目,也算白白作了一個輪子吧。它的原理仍是函數劫持,劫持window.XMLHttpRequest對象,主要是對send方法和onreadystatechange事件的劫持,劫持send簡單,和前面的console.log劫持一毛同樣,嵌套個高階函數封裝一下就完事了,但是事件要怎麼代理,謝天謝地的是XHR它沒有用addEventListener去監聽事件,而是用了Dom level 0的方式直接用on事件名添加回調,否則咱們就須要去劫持addEventListener了,那但是個大麻煩,一不當心就會捅大簍子的。

對象屬性的劫持固然是用defineProperty大法,把onreadystatechange回調改寫,若是mock匹配到了url,就用假數據返回,若是沒匹配到固然不能影響其它正常的ajax接口了,觸發原始xhr的流程。下面是完整的可運行的代碼,爲了方便演示,我把幾個不經常使用的屬性和正則匹配url規則庫的地方省略了用固定地址。

var __XMLHttpRequest=XMLHttpRequest;
window.XMLHttpRequest=function (){
       var resultObj={
           xhr:new __XMLHttpRequest(),
           onreadystatechange:null,
           open:function (method,url,async){
               this.url=url;
               this.method=method;
           },
           send:function (data){
               //用異步改下時序,讓send 在 onreadystatechange 以後再執行
               setTimeout(()=> {
                   if(this.url.indexOf("http://www.baidu.com/post")>=0){    //匹配url規則,這個是業務代碼省略掉了,先用固定地址
                       this.readyState=4;
                       this.status=200;
                       this.responseText="{result:'hello,baidu'}"; //爲了演示先寫死報文了
                       this.onreadystatechange({mock:true});
                   }else{
                       this.xhr.open(this.method,this.url);
                       this.xhr.send(data);
                   }
               }, 0);
           }
       }
       Object.defineProperty(resultObj,"onreadystatechange", {
           get:function (){
               return this.xhr.onreadystatechange;
           },
           set:function (func){
               this.xhr.onreadystatechange= (arg)=>{
                   if(arg.mock){ //mock接口,直接觸發回調 
                       func();
                   }else{ //沒匹配到,把原始xhr獲得的數據複製回來
                       this.readyState=this.xhr.readyState;
                       this.status=this.xhr.status;
                       this.responseText=this.xhr.responseText;
                       func();
                   }
               };
           }
       })
       return resultObj;
}
複製代碼

咱們寫個用例跑一下,注:爲了簡化代碼,上面匹配url的地方寫死了,只能用http://www.baidu.com/post這個地址

var xhr=new XMLHttpRequest()
xhr.open("post","http://www.baidu.com/post");
xhr.send();
xhr.onreadystatechange=function (){
    console.log(xhr.responseText)
}
複製代碼

完美運行,輸出了 {result:'hello,baidu'} ,這在之前是不敢想象的,ajax請求一個不存在的地址也能返回報文 。。。

真正的黑科技2:無侵入式接口層異常監控,把問題優雅的甩鍋給後臺的方法

多年之前,用戶常常出現一些問題,開發難以重現,後臺同窗神經比較大,不怕不怕啦,常常不記日誌,或是稱查不到日誌,前端又沒日誌,最後基本都是前端背責任,後來咱們前端團隊就實現了在xhr 層包裝了一層監控後臺接口的異常和超時現象,優雅的甩鍋給後臺,節省好多時間(其實不少是服務器問題或是網絡緣由,後臺真的查不出來)。多年以後,我實現了mock 以後,纔想到其實直接劫持xhr纔是更好的方案,能夠通用於一切項目,去年看到有公司作出來了:www.fundebug.com,感到很欣慰,不少想法和我不謀而合。其實這樣一個解決方案要產品化仍是很難的,一項技術到產品化之間有巨大的鴻溝。

來說講它的核心原理吧

  • 首先腳本異常的全局捕獲,有window.onerror全局事件,在node.js端,有uncaughtException事件,能夠捕獲到異常的類型,錯誤堆棧信息,技術真的是突飛猛進了,想當年IE瀏覽器時代,onerror事件根本沒啥參數,想得到異常堆棧只能經過arguments.callee.caller一級級往上回溯堆棧函數再toString。

  • 而後是接口層異常捕獲,有了上面的mock.js代碼以後,要實現無侵入式的接口異常檢測很容易了,mock若是沒匹配到會正常發ajax請求的,直接在 readystatechange裏檢測狀態碼和報文,若是發現狀態碼異常就上報,固然,若是你的後臺報錯仍然返回200狀態碼的話,那就只能制定報文規範,約定成功的返回值,若是檢測不到成功的關鍵字,一概按失敗處理,上報日誌。

  • 接口的超時檢測能夠在send時加一個setTimeout計時,若是指定的時候尚未觸發onreadystatechange,就上報超時日誌。

其實就是在上面mock.js的基礎上,加幾行代碼就行實現,不詳細貼代碼了,大體代碼以下:

this.xhr.onreadystatechange= (arg)=>{
    if(arg.mock){ //mock接口,直接觸發回調 
        func();
    }else{ //沒匹配到,把原始xhr獲得的數據複製回來
        if(this.readyState==4 && this.status!=200){
            reportError() //上報日誌
        }
      //.......省略
    }
};
複製代碼

總結

掌握了函數劫持,咱們彈藥庫裏又多了一種武器, 技術永遠不是冰冷的,它能實現不少應用,要充分發揮你的想象力, 作爲一名研發,保持好奇心很重要,若是發現了某個黑科技功能,你還不知道他是怎麼實現的,必定要去研究他的原理,而後想一想還能應用在哪些領域,你纔算真正掌握了這項技術。

最後,平常推薦一下個人開源項目:

webcontext,最簡潔的node.js web開發框架,github地址:github.com/windyfancy/…

相關文章
相關標籤/搜索