原生JS技巧(2)浅谈回调函数(拦截器、注入和控制反转原理)
现在很多框架都在说拦截器、依赖注入、控制反转,尤其是java,很多的js框架也引入这种设计思想,包括angular、vue等等,在网上一查也有好多关于这方面的文章,但技术性有很深,但读过源码的人应该就明白它核心的原理,由大到小来解释就是“回调函数”。
1、回调函数
什么是“回调函数”,看下面例子
1 var list = [{a:1,b:2,c:3},{a:3,b:4,c:5},{a:4,b:3,c:2},{a:7,b:1,c:1}]; 2 list.filter(function(item){ 3 return item.a > 3; 4 });
filter是Array的一个函数,正常我们会list.filter()这么调用,来筛选数组,
如果所有的逻辑都写在filter()内部,我们需要大量的判断条件,在不清楚list数组内容的情况,就无法去写filter的条件,
这样我们就需要把控制权交给外面,里面只提供基础功能,用于返回新数组。
那我们自己写一个回调,如下:
1 var filter = function(array,callback){ 2 var _array = []; 3 for(var i in array){ 4 if(callback.call(this,array[i])) _array.push(array[i]); 5 } 6 }
由此能看出,回调函数就是参数只传入一个函数体,而函数内部执行作为参数的这个函数。
2、拦截器
很多语言都有拦截器,而拦截器的原理和回调函数很像,也是把部分的控制权交给了外部,而内部只是调用包含外部控制权的函数
var c = function(callback){ var b = 0; setInterval(function(){ b += 1; if(b % 3 === 0){ callback.apply(this,[b*1000]); } },1000) } var a = []; c(function(val){ a.push(val); });
a的值: [3000, 6000, 9000, ...] ,会按照每3秒的时间拦截一次,并向a数组中添加
我们在外部可以任意定义callback中的逻辑,如果c函数内部是一个ajax请求或其他异步,我们需要捕捉请求回来时变量被赋值,或者捕捉模块加载完成时,都可以在需要的节点下调用一个回调函数,并把内部获取的数据通过参数返回给外部,像:callback.apply(this指向的对象,[参数]),外部可以任意控制处理,这就是拦截器的基本原理。
3、控制反转和依赖注入
我们也可以用回调来实现,先简单理解下控制反转和依赖注入,首先依赖注入是控制反转的实现,
我们每次实例化对象的时候,都会现new一个新的对象,像var a = new b()
然后用a.xxx的方式来使用b中的属性和方法,但有一个地方调b的对象,要用到b里y1 : function(),另一个地方也调b对象,却不能有y1 : function(),
这样我们就不方便把控制权交给b对象,而是放在外面,而里面只需要让它自动new一个对象,这就是注入
简单来说就是外部只需要传入类的引用,外部就可以直接调用它的对象。
1 export class SummaryComponent implements OnInit, AfterViewInit { 2 constructor(public http: Http){ 3 4 ngOnInit() { 5 //XHR异步请求:发送get请求 6 this.http.get('./app/contact360/summary.sub.component.html').subscribe(data => { 7 alert(data.text()); //如果请求的是文本: data.text() , 如果请求的是json文件: data.json() 8 }); 9 } 10 }
这是angular2注入的例子,在构造方法中传入Http这个类,正常我需要 this.http = new Http()
但现在我把new的控制权交给了angular,使它在实例化SummaryComponent的时候,顺便把构造方法中传入的类都实例化了,并反给自身,
这样,我们就可以不用new而直接可以调用this.http的对象,这么做也有个好处就是,new Http()的时候,我不知道传什么参数给它,而现在我也不用考虑这个了。
问:这和回调函数有什么关系?
答:都是把控制权交给外面,里面只做基本操作
1 define(["demo"],function(_demo){ return new _demo(); });
知道模块化开发的同学都知道上面这个用于引入新外来模块用的,但如果define我这么写
1 _define = function(mods,func){ 2 var _objs = []; 3 define(mods,function(){ 4 for(var i=0;i<arguments.length;i++){ 5 _objs.push(new arguments[i]); 6 } 7 }); 8 func.apply(this,_objs); 9 }
用的时候我就不用new _demo()了,并可以直接用_demo.test()对象方法了
1 _define(["demo"],function(_demo){ return _demo.test(); });
这就是通过回调函数来注入,
当然注入对象的主要还是通过调用接口和继承的方式,但原理依然脱离不了这种方式,
虽然原理很简单,但结合各种设计模式后,就会奥妙无穷。