如何在 AngularJS 中创建任何类型的不可变提供程序

How to create any kind of immutable providers in AngularJS

考虑以下示例:

angular.module('demo')
    .service('MyService', function () {
        this.fn = function () {
            console.log('MyService:fn');
        };
    })
    .factory('MyFactory', function () {
        function fn() {
            console.log('MyFactory:fn');
        }
        return { fn: fn };
    })
    .value('MyValue', {
        fn: function () {
            console.log('MyValue:fn');
        }
    })
    .constant('MyConstant', {
        fn: function () {
            console.log('MyConstant:fn');
        }
    })
    .run(function (MyService, MyFactory, MyValue, MyConstant) {
        MyService.fn();
        MyFactory.fn();
        MyValue.fn();
        MyConstant.fn();

        MyService.fn = undefined;
        MyFactory.fn = undefined;
        MyValue.fn = undefined;
        MyConstant.fn = undefined;
    })
    .run(function (MyService, MyFactory, MyValue, MyConstant) {
        MyService.fn();
        MyFactory.fn();
        MyValue.fn();
        MyConstant.fn();
    });

当执行第一个run()时,将执行所有4个控制台日志并在控制台上打印一些内容。然后我将每个提供者 fn 函数设置为 undefined 为了简化目的,假设有人在某处重写了这个函数(这是我想防止的事情)。

在第二个 run() 块中,所有内容都是未定义的,并且会抛出错误。我对此感到困惑......不应该至少 其中一些constant 是第一个想到的)是不可变对象吗?

这是预期的行为还是我做错了什么?

为什么这是一个惊喜。 Angular 是 Javascript 之上的框架 运行,Javascript 是一种动态语言。你的问题实际上是关于语言结构的。

首先,认识到所有调用最终都是在注册一个提供程序,该提供程序将 return 一个要注入的对象。 .service.factory.value只是.provider的简写(.constant有点不同)。

确定一旦对象被注入后它们之间没有区别,接下来您需要关心的就是如何使该对象不可变。

Javascript 提供了 Object.freeze 功能,例如,你可以这样做:

var immutable = {
        fn: function () {
            console.log('MyConstant:fn');
        }
    };
Object.freeze(immutable);

app.constant("MyConstant", immutable);

函数 constantfactoryservice 等允许您创建 Javascript 个对象,这些对象被创建 一次 ,然后由 angular 缓存,并注入到 as/when 需要的组件中。因为这些是 Javascript 对象,那么(忽略使用 freeze 的可能性)任何代码都可以访问这些对象可以修改它们的属性,正如您所演示的那样。

虽然对象本身的属性可以修改,但对象本身不能更改为另一个,所以如果你真的想要一个不可变的提供者,它是安全的,不会受到任何篡改,这是我能想到的一种方法是使用 returns 不是对象,而是 getter 函数的工厂:

app.factory('MyFactory', function() {
  var functions = {
    myFunctionName: function() {
    }
  };

  return function(functionName) {
    return functions[functionName];
  };
});

可以在控制器中使用,例如

app.controller('MyController', function(MyFactory) {
  MyFactory('myFunctionName')();
});

与公开所有方法并允许修改对象的更传统方式相比,使用此方法的一个缺点是我怀疑单元测试可能会更复杂:您将无法轻松创建使用 createSpy(MyFactory, 'myFunctionName').

监视您的工厂方法