代码改变世界

模拟AngularJS之依赖注入

2016-08-19 08:44  猴子猿  阅读(1429)  评论(2编辑  收藏  举报
一、概述

AngularJS有一经典之处就是依赖注入,对于什么是依赖注入,熟悉spring的同学应该都非常了解了,但,对于前端而言,还是比较新颖的。

依赖注入,简而言之,就是解除硬编码,达到解偶的目的。

下面,我们看看AngularJS中常用的实现方式。

方法一:推断式注入声明,假定参数名称就是依赖的名称。因此,它会在内部调用函数对象的toString()方法,分析并提取出函数参数列表,然后通过$injector将这些参数注入进对象实例。

如下:

//方法一:推断式注入声明,假定参数名称就是依赖的名称。
//因此,它会在内部调用函数对象的toString()方法,分析并提取出函数参数列表,
//然后通过$injector将这些参数注入进对象实例
injector.invoke(function($http, $timeout){
    //TODO
});

方法二:行内注入声明,允许我们在函数定义时,直接传入一个参数数组,数组包含了字符串和函数,其中,字符串代表依赖名,函数代表目标函数对象。

如下:

//方法二:行内注入声明,允许我们在函数定义时,直接传入一个参数数组,
//数组包含了字符串和函数,其中,字符串代表依赖名,函数代表目标函数对象。
module.controller('name', ['$http', '$timeout', function($http, $timeout){
    //TODO
}]);

看了上述代码,心中有一疑问,这些是怎么实现的呢?

哈哈,下面,我们就来一起模拟一下这些依赖注入方式,从而了解它们。

该篇博客原文地址:http://www.cnblogs.com/giggle/p/5769169.html

二、搭建基本骨架

依赖注入的获取过程就是,通过字段映射,从而获取到相应的方法。

故而,要实现一个基本的依赖注入,我们需要一个存储空间(dependencies),存储所需键值(key/value);一个注册方法(register),用于新增键值对到存储空间中;还有一个就是核心实现方法(resolve),通过相关参数,到存储空间中获得对应的映射结果。

So,基本骨架如下:

var injector = {
    dependencies: {},
    register: function(key, value){
        this.dependencies[key] = value;
        return this;
    },
    resolve: function(){
                            
    }
};
三、完善核心方法resolve

从我们搭建的基本骨架中,可以发现,重点其实resolve方法,用于实现我们具体形式的依赖注入需求。

首先,我们来实现,如下形式的依赖注入:推断式注入声明。

如下:

injector.resolve(function(Monkey, Dorie){
    Monkey();
    Dorie();
});

要实现上述效果,我们可以利用函数的toString()方法,我们可以将函数转换成字符串,从而通过正则表达式获得参数名,即key值。 然后通过key值,在存储空间dependencies找value值,没找到对应值,则报错。

实现如下:

var injector = {
    dependencies: {},
    register: function(key, value){
        this.dependencies[key] = value;
        return this;
    },
    resolve: function(){
        var func, deps, args = [], scope = null;
        func = arguments[0];
        //获取函数的参数名
        deps = func.toString().match(/^function\s*[^\(]*\(\s*([^\)]*)\)/m)[1].replace(/ /g, '').split(',');
        scope = arguments[1] || {};
        for(var i = 0, len = deps.length; i < len, d = deps[i]; i++){
            if(this.dependencies[d]){
                args.push(this.dependencies[d]);
            }else{
                throw new Error('Can\'t find ' + d);
            }
        }
        func.apply(scope, args);                    
    }
};

测试代码,如下:

<!DOCTYPE html>
    <head>
        <meta charset="utf-8"/>
    </head>
    <body>
        <script>
            var injector = {
                dependencies: {},
                register: function(key, value){
                    this.dependencies[key] = value;
                    return this;
                },
                resolve: function(){
                    var func, deps, args = [], scope = null;
                    func = arguments[0];
                    //获取函数的参数名
                    deps = func.toString().match(/^function\s*[^\(]*\(\s*([^\)]*)\)/m)[1].replace(/ /g, '').split(',');
                    scope = arguments[1] || {};
                    for(var i = 0, len = deps.length; i < len, d = deps[i]; i++){
                        if(this.dependencies[d]){
                            args.push(this.dependencies[d]);
                        }else{
                            throw new Error('Can\'t find ' + d);
                        }
                    }
                    func.apply(scope, args);                    
                }
            };
            //测试代码
            injector.register('Monkey', function(){
                console.log('Monkey');
            }).register('Dorie', function(){
                console.log('Dorie');
            });
            injector.resolve(function(Monkey, Dorie){
                Monkey();
                Dorie();
                console.log('-.-');
            });        
        </script>
    </body>
</html>
代码稍长,请自行打开

推断式注入声明,有个缺点,就是不能利用压缩工具压缩,因为我们是通过函数的参数作为依赖的,当我们压缩时,会将参数名改掉,参数名都变了,那肯定扑街咯。

那么下面,我们就看看行内注入声明,它可以弥补这缺点。

实现行内注入声明,如下:

injector.resolve(['Monkey', 'Dorie', function(M, D){
    M();
    D();
}]);

利用typeof判断arguments[0]的类型可以辨别并获得依赖参数和函数。

实现如下:

var injector = {
    dependencies: {},
    register: function(key, value){
        this.dependencies[key] = value;
        return this;
    },
    resolve: function(){
        var firstParams, func, deps = [], scope = null, args = [];
        firstParams = arguments[0];
        scope = arguments[1] || {};
        //获得依赖参数
        for(var i = 0, len = firstParams.length; i < len; i++){
            var val = firstParams[i],
                type = typeof val;
            if(type === 'string'){
                deps.push(val);
            }else if(type === 'function'){
                func = val;
            }
        }
        //通过依赖参数,找到关联值
        for(i = 0, len = deps.length; i < len, d = deps[i]; i++){
            if(this.dependencies[d]){
                args.push(this.dependencies[d]);
            }else{
                throw new Error('Can\'t find ' + d);
            }
        }
        func.apply(scope || {}, args);                    
    }
};
代码稍长,请自行打开

测试代码,如下:

<!DOCTYPE html>
    <head>
        <meta charset="utf-8"/>
    </head>
    <body>
        <script>
            var injector = {
                dependencies: {},
                register: function(key, value){
                    this.dependencies[key] = value;
                    return this;
                },
                resolve: function(){
                    var firstParams, func, deps = [], scope = null, args = [];
                    firstParams = arguments[0];
                    scope = arguments[1] || {};
                    //获得依赖参数
                    for(var i = 0, len = firstParams.length; i < len; i++){
                        var val = firstParams[i],
                            type = typeof val;
                        if(type === 'string'){
                            deps.push(val);
                        }else if(type === 'function'){
                            func = val;
                        }
                    }
                    //通过依赖参数,找到关联值
                    for(i = 0, len = deps.length; i < len, d = deps[i]; i++){
                        if(this.dependencies[d]){
                            args.push(this.dependencies[d]);
                        }else{
                            throw new Error('Can\'t find ' + d);
                        }
                    }
                    func.apply(scope || {}, args);                    
                }
            };
            //测试代码
            injector.register('Monkey', function(){
                console.log('Monkey');
            }).register('Dorie', function(){
                console.log('Dorie');
            });
            injector.resolve(['Monkey','Dorie',function(M, D){
                M();
                D();
                console.log('-.-');
            }]);    
        </script>
    </body>
</html>
代码稍长,请自行打开

因为行内注入声明,是通过字符串的形式作为依赖参数,so,压缩也不怕咯。

最后,我们将上面实现的两种方法,整合到一起,就可以为所欲为啦。

那,就合并下吧,如下:

var injector = {
    dependencies: {},
    register: function(key, value){
        this.dependencies[key] = value;
        return this;
    },
    resolve: function(){
        var firstParams, func, deps = [], scope = null, args = [];
        firstParams = arguments[0];
        scope = arguments[1] || {};
        //判断哪种形式的注入
        if(typeof firstParams === 'function'){
            func = firstParams;
            deps = func.toString().match(/^function\s*[^\(]*\(\s*([^\)]*)\)/m)[1].replace(/ /g, '').split(',');
        }else{
            for(var i = 0, len = firstParams.length; i < len; i++){
                var val = firstParams[i],
                    type = typeof val;
                if(type === 'string'){
                    deps.push(val);
                }else if(type === 'function'){
                    func = val;
                }
            }    
        }
        //通过依赖参数,找到关联值
        for(i = 0, len = deps.length; i < len, d = deps[i]; i++){
            if(this.dependencies[d]){
                args.push(this.dependencies[d]);
            }else{
                throw new Error('Can\'t find ' + d);
            }
        }
        func.apply(scope || {}, args);                    
    }
};
代码稍长,请自行打开
四、花絮—RequireJS之依赖注入

依赖注入并非在AngularJS中有,倘若你使用过RequireJS,那么下面这种形式,不会陌生吧:

require(['Monkey', 'Dorie'], function(M, D){
    //TODO    
});

通过,上面我们一步步的模拟AngularJS依赖注入的实现,想必,看到这,你自己也会豁然开朗,换汤不换药嘛。

模拟实现如下:

var injector = {
    dependencies: {},
    register: function(key, value){
        this.dependencies[key] = value;
        return this;
    },
    resolve: function(deps, func, scope){
        var args = [];
        for(var i = 0, len = deps.length; i < len, d = deps[i]; i++){
            if(this.dependencies[d]){
                args.push(this.dependencies[d]);
            }else{
                throw new Error('Can\'t resolve ' + d);
            }
        }
        func.apply(scope || {}, args);
    }
};

测试代码如下:

<!DOCTYPE html>
    <head>
        <meta charset="utf-8"/>
    </head>
    <body>
        <script>
            var injector = {
                dependencies: {},
                register: function(key, value){
                    this.dependencies[key] = value;
                    return this;
                },
                resolve: function(deps, func, scope){
                    var args = [];
                    for(var i = 0, len = deps.length; i < len, d = deps[i]; i++){
                        if(this.dependencies[d]){
                            args.push(this.dependencies[d]);
                        }else{
                            throw new Error('Can\'t resolve ' + d);
                        }
                    }
                    func.apply(scope || {}, args);
                }
            };
            //测试代码            
            injector.register('Monkey', function(){
                console.log('Monkey');
            }).register('Dorie', function(){
                console.log('Dorie');
            });
            injector.resolve(['Monkey', 'Dorie'], function(M, D){
                M();
                D();
                console.log('-.-');
            });        
        </script>
    </body>
</html>
代码稍长,请自行打开
五、参考

1、AngularJS应用开发思维之3:依赖注入

2、Dependency injection in JavaScript