函数劫持能作啥?前端黑科技揭秘

啥是函数劫持?其实就是给调用的函数加一层包装函数,在包装函数里调用一下原函数,有点像代理服务器的做用, 拦截一些全局对象和方法, 能实现一些很是便利的全局功能,今天我就抛砖引玉跟你们分享一下。前端

人生苦短,苦中作乐的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/…

相关文章
相关标签/搜索