前些时候有使用过AngularJS一些时间,最大的感受就是Angular完全颠覆了我们开发Web应用的方式,自己被其许多耳目一新的设计思想所折服。

首先想说的就是依赖注入(DI),这也意味着,你在使用某一个模块时,不需要去手动require()一下,你需要的模块会自己注入到的函数所在的作用域中,如:

JavaScript
1
2
3
4
5
app.service('PersonService', function ($http) {
    this.addPerson = function () {
        return $http.post('url/addPersonAction', {name:'name'});
    }
});

上面的代码中,直接使用了$http的post()方法。那么问题来了:为什么可以这样?我们知道JS函数在调用时,其形参如果没有赋值就会是undefined。能直接使用$http的post()方法,就说明$http是有对应的实参与之对应的。这是怎么发生的呢?下面,就让我们一起来揭开其中的秘密。

一: 如何在JS中实现DI

在这之前,我们先回顾一下toString方法。在JS中,除了null和undefined,其它所有的一切值都是有toString()方法的。函数也不例外,而且函数的toString()方法,是可以拿到函数定义的全部代码,甚至是注释。有了这一前提,我们可以实现一个获取函数形参的方法。

JavaScript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
 * 以数组的形式返回函数的参数名字字符串。没有参数时返回空数组
 * @param {Function} fn - 要获取参数的函数
 * @returns {Array}
 */
function argumentNames(fn) {
    var ret,
        methodCode = fn.toString();
        methodCode.replace(/\((.*?)\)/, function(match, g1) {
            var argStr = g1.replace(/\s/g, '');
            ret = argStr.length ? argStr.split(',') : [];
        });
    return ret;
}
 
// 使用:
argumentNames(function(arg1, arg2) {}); // ["arg1", "arg2"];

有了这个方法,我们要实现参数的DI,还需要两步:
1. 函数运行拦截它
2. 把对应的模块传给函数的上下文

对于第一步,JS原生没有提供对应方法。但我们可以参照defined(), require()的做法。你定义模块时,必须使用我给的方式去定义。类比Angular是

JavaScript
1
2
3
angularModule.service('serviceID', function (dependencyModuleA [, dependencyModuleB...]) {
    // do something
});

呵呵,既然你使用了我的方法去定义模块,那么我就可以对你传入的函数为所欲为了…。可能你已经想到了,我们要对其传入的函数所做的第一件事就是获取其参数列表,然后再把这个模块保存下来。有了这个参数列表,就知道要注入多少个依赖。再接着,把对应的依赖传作为实参传过去。那么,如何拿到对应的依赖?在我们给的定义方法中,已经让传入了一个ID,这个ID就是获取对应模块的关键。

要注入的模块 === 模块仓库[模块ID];

JavaScript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
// DI的完整实现:
(function (widnow) {
    window.DI = {
        serviceCache:{},
        _argumentNames: function (fn) {
            var ret, methodCode = fn.toString();
            methodCode.replace(/\((.*?)\)/, function(match, g1) {
                var argStr = g1.replace(/\s/g, '');
                ret = argStr.length ? argStr.split(',') : [];
            });
            return ret;
        },
 
        service: function (serviceID, serviceFactory) {
            this.serviceCache[serviceID] = new serviceFactory();
            return this;
        },
 
        controller: function (controllerID, controllerCb) {
            var controllerCbArgs = this._argumentNames(controllerCb);
            var dependencies = [], i = 0;
 
            // 根据controllerCbArgs有序填充依赖
            while (controllerCbArgs[i]) {
                dependencies.push(this.serviceCache[controllerCbArgs[i]]);
                i++;
            }
 
            controllerCb.apply({}, dependencies);
            return this;
        }
    };
 
})(this);
 
// 使用方法:
DI
    .service('AT', function () {
        this.name = 'Alloy Team';
 
        this.concatUs = function () {
            document.body.innerHTML = 'Email: <a onclick="javascript:pageTracker._trackPageview('/mailto/AlloyTeam@tencent.com');" href="mailto:AlloyTeam@tencent.com">AlloyTeam@tencent.com</a>';
        };
    })
 
    .controller('c', function (AT) {
        AT.concatUs();
    });

到此,我们已经简单实现了依赖注入。当然,这个实现是有很多问题的,比如JS混淆后不能正常工作,定义一个模块就立即new也是不恰当的。有兴趣的话可以尝试去完善这个DI,这里就不继续下去了。

二: 如何在JS中实现AOP

提到DI,我就想到了AOP。有Java基础的同学都知道Ioc和AOP是Spring的两大特性。在JS中,要实现AOP也很简单,但方式却显得唯一:重写原来的函数定义。如下是AOP一个实现:

JavaScript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
(function (window) {
    window.AOP = {
        before: function (ns, fnName, beforefn) {
            var ori = ns[fnName];
 
            ns[fnName] = function () {
                beforefn();
                ori();
            }
        }
    };
})(this);
 
// 使用
var ns = {
    foo: function () {
        console.log('foo...');
    }
};
 
var bar = function () {
    console.log('bar...');
};
 
// 现在使用AOP在ns.foo函数执行前,切入新逻辑bar()
AOP.before(ns, 'foo', bar);
 
// 执行ns.foo
ns.foo(); 
/*
打印:
    bar...
    foo...
*/

虽然上面AOP的实现比较丑陋,但目前要想在JS中实现AOP,核心原理都是重写函数定义。期望有一天能像操作XMLHttpRequest对象那样,在每个函数对象上,也有一个类似readyState的属性,这时,再结合Object.observe,相信那时JS中的AOP实现将会非常优雅,AOP也会在JS中得到更好的使用。更多AOP的使用场景可以参考文章:js实现aop

小结:
1. 在JS中实现DI:利用函数的toString方法
2. 在JS中实现AOP:重写原函数

js 实现 di的更多相关文章

  1. 原创:Javascript DI!Angular依赖注入的实现原理

    DI是Angular的特色功能,而在Angular 2.0的计划中,DI将成为一个独立的模块,参见 https://github.com/angular/di.js 这意味着它也有机会被用于nodej ...

  2. webpack优化之code splitting

    作为当前风头正盛的打包工具,webpack风靡前端界.确实作为引领了一个时代的打包工具,很多方面都带来了颠覆性的改进,让我们更加的感受到自动化的快感.不过最为大家诟病的一点就是用起来太难了. 要想愉快 ...

  3. Decorator - 利用装饰器武装前端代码

    历史 以前做后端时,接触过一点Spring,也是第一次了解DI.IOC等概念,面向切面编程,对于面向对象编程还不怎么熟练的情况下,整个人慌的一批,它的日志记录.数据库配置等都非常方便,不回侵入到业务代 ...

  4. Atitit js中的依赖注入di ioc的实现

    Atitit js中的依赖注入di ioc的实现 全类名(FQCN)为标识符1 混合请求模式1 使用类内  builder  即可..2 Service locator method走ok拦2 Jav ...

  5. MVC、MVP、MVVM、Angular.js、Knockout.js、Backbone.js、React.js、Ember.js、Avalon.js、Vue.js 概念摘录

    注:文章内容都是摘录性文字,自己阅读的一些笔记,方便日后查看. MVC MVC(Model-View-Controller),M 是指业务模型,V 是指用户界面,C 则是控制器,使用 MVC 的目的是 ...

  6. 分享一个html+js+ashx+easyui+ado.net权限管理系统

    EasyUI.权限管理 这是个都快被搞烂了的组合,但是easyui的确好用,权限管理在项目中的确实用.一直以来博客园里也不少朋友分享过,但是感觉好的要不没源码,要不就是过度设计写的太复杂看不懂,也懒得 ...

  7. 【干货】JS版汉字与拼音互转终极方案,附简单的JS拼音输入法

    前言 网上关于JS实现汉字和拼音互转的文章很多,但是比较杂乱,都是互相抄来抄去,而且有的不支持多音字,有的不支持声调,有的字典文件太大,还比如有时候我仅仅是需要获取汉字拼音首字母却要引入200kb的字 ...

  8. angular.js:13920 Error: [$injector:unpr] Unknown provider: $scopeProvider <- $scope <- testServe

    angular.js:13920 Error: [$injector:unpr] Unknown provider: $scopeProvider <- $scope <- testSer ...

  9. Vue.js学习笔记(8)拖放

    小颖在目前负责的项目中,负责给同事提供所需组件,在这期间,我们家大颖姐姐让我 写个拖拽组件,一开始我是用click实现,先将你要拖拽的dom点一下,然后再点你要放的位置,这个dom再通过小颖写的方法, ...

随机推荐

  1. TOJ3660家庭关系(并查集+hash+图的连通性)

    家庭关系   时间限制(普通/Java):1000MS/3000MS     运行内存限制:65536KByte 总提交: 38            测试通过: 9 描述 给定若干家庭成员之间的关系 ...

  2. [React + Mobx] Mobx and React intro: syncing the UI with the app state using observable and observer

    Applications are driven by state. Many things, like the user interface, should always be consistent ...

  3. HTML5游戏开发技术基础整理

    随着HTML5标准终于敲定.HTML5将有望成为游戏开发领域的的热门平台. HTML5游戏能够执行于包含iPhone系列和iPad系列在内的计算机.智能手机以及平板电脑上,是眼下跨平台应用开发的最佳实 ...

  4. ios--绘图介绍

    iOS–绘图介绍 绘制图像的三种方式 一. 子类化UIView,在drawRect:方法画图 执行方法时,系统会自行创建画布(CGContext),并且讲画布推到堆栈的栈顶位置 执行完毕后,系统会执行 ...

  5. iOS流布局UICollectionView使用FlowLayout进行更灵活布局

    一.引言 前面的博客介绍了UICollectionView的相关方法和其协议中的方法,但对布局的管理类UICollectionViewFlowLayout没有着重探讨,这篇博客介绍关于布局的相关设置和 ...

  6. c#中跨线程调用windows窗体控件

    c#中跨线程调用windows窗体控件解决. 我们在做winform应用的时候,大部分情况下都会碰到使用多线程控制界面上控件信息的问题.然而我们并不能用传统方法来做这个问题,下面我将详细的介绍.首先来 ...

  7. xslt语法之---position()函数

    最近在学习使用XSLT,很好很强大的样式表语言.使用到了position()函数特此记录一下. position()函数--返回节点位置 语法:position() 参数:无 返回值:整数 用途:该函 ...

  8. eclipse4.3 kepler中安装maven

    1.软件准备 a:Eclipse 4.3 http://www.eclipse.org/downloads/ b:maven http://maven.apache.org/download.cgi ...

  9. java异常类的使用

    1.异常的概念 什么是异常?程序出错分为两部分,编译时出粗和运行时出错.编译时出错是编译器在编译源码时发生的错误: 运行时出错是在编译通过,在运行时出现的错误.这种情况叫异常. 例如:数组越界,除数为 ...

  10. Asp.net Mvc4 基于Authorize实现的模块访问权限

    在MVC中,我们可以通过在action或者controller上设置Authorize[Role="xxx"] 的方式来设置用户对action的访问权限.显然,这样并不能满足我们的 ...