AngularJS深入(5)——provider

太精彩,不得不全文引用。

到这个层次,可能才敢说自己懂了吧。。。

http://syaning.com/2015/07/21/dive-into-angular-5/

在使用AngularJS的时候,可能需要创建各种各样的服务,这个时候,需要告诉AngularJS如何创建这些服务,这便是Provider。在实际使用的时候,会有providerfactoryservicevalueconstant,事实上,它们都是Provider,从factoryconstant,只不过是对provider的一步步封装。相关源码都在函数createInjector中。


1. provider

源码如下:

function provider(name, provider_) {
	assertNotHasOwnProperty(name, 'service');
	if (isFunction(provider_) || isArray(provider_)) {
		provider_ = providerInjector.instantiate(provider_);
	}
	if (!provider_.$get) {
		throw $injectorMinErr('pget', "Provider '{0}' must define $get factory method.", name);
	}
	return providerCache[name + providerSuffix] = provider_;
}

首先,一个Provider的名字不可以是hasOwnProperty,然后,如果provider_是函数或者数组的话,调用providerInjector.instantiate将其实例化。之后进行判断,如果provider_没有$get属性,则报错。然后将provider_缓存在providerCache中。

下面来分析对参数provider_的约束。

(1) 如果provider_是函数,那么会调用provider_ = providerInjector.instantiate(provider_)对其再次赋值,相当于以provider_作为构造函数来创建实例。因此在构造函数中,必须要有对$get的操作,因此如下形式都是可以的:

app.provider('greeting', function() {
	this.$get = function() {
		return {
			sayHello: function() {
				console.log('hello world');
			}
		};
	};
});

// or

app.provider('greeting', function() {
	return {
		$get: function() {
			return {
				sayHello: function() {
					console.log('hello world');
				}
			};
		}
	};
});

(2) 如果provider_是数组,同样的接下来会调用provider_ = providerInjector.instantiate(provider_),所以数组必须是[deps, fn]的形式,其中deps为依赖,fn与(1)中函数的限制相同。例如:

app.provider('greeting', ['$injector', function($injector) {
	this.$get = function() {
		return {
			sayHello: function() {
				console.log('hello world', $injector);
			}
		};
	};
}]);

(3)如果provider_是一个对象字面量,则它必须要有$get属性,例如:

app.provider('greeting', {
	$get: function() {
		return {
			sayHello: function() {
				console.log('hello world');
			}
		};
	}
});

接下来讨论对$get的约束。在定义instanceInjector的时候,代码如下:

instanceInjector = (instanceCache.$injector =
	createInternalInjector(instanceCache, function(serviceName, caller) {
		var provider = providerInjector.get(serviceName + providerSuffix, caller);
		return instanceInjector.invoke(provider.$get, provider, undefined, serviceName);
	}));

由于会被invoke调用,因此$get的值必须为一个函数或者[deps, fn]形式的数组。而函数的返回值,则可以为原始数据类型、对象、函数等等,并无限制。

总之,在调用provider(name, provider_)的时候,会将(name + 'Provider', {$get:function(){/* ... */}})键值对缓存在providerCache中,在注入的时候,则会调用$get函数,将其返回值进行注入,并缓存在instanceCache中。

2. factory

源码如下:

function factory(name, factoryFn, enforce) {
	return provider(name, {
		$get: enforce !== false ? enforceReturnValue(name, factoryFn) : factoryFn
	});
}

可以看到,factory事实上是调用了provider方法,而第二个参数factoryFn,事实上也就是上面的$get的值,因此应当符合对$get的约束。

因此上面的例子可以这样写:

app.factory('greeting', function() {
	return {
		sayHello: function() {
			console.log('hello world');
		}
	};
});

注意到factory的源码中函数有第三个参数enforce,也就是说是否强制返回值,如果函数没有返回值(包括显式返回值为undefined)且第三个参数不为false的时候,就回报错,相关源码如下:

function enforceReturnValue(name, factory) {
	return function enforcedReturnValue() {
		var result = instanceInjector.invoke(factory, this);
		if (isUndefined(result)) {
			throw $injectorMinErr('undef', "Provider '{0}' must return a value from $get factory method.", name);
		}
		return result;
	};
}

然而在使用的过程中,会发现,即使显示声明了enforcefalse,还是会报错,例如:

app.factory('greeting', function() {
	console.log('hello world');
}, false);

// Error: [$injector:undef] Provider 'greeting' must return a value from $get factory method.

这主要是由supportObject引起的,因为providerCache.$provide.factory的值实际上是supportObject(factory),而supportObject源码如下:

function supportObject(delegate) {
	return function(key, value) {
		if (isObject(key)) {
			forEach(key, reverseParams(delegate));
		} else {
			return delegate(key, value);
		}
	};
}

可以发现,经过supportObject处理后,函数的有效参数值最多只有两个了,因此第三个参数false也就被忽略掉了。

3. service

源码如下:

function service(name, constructor) {
	return factory(name, ['$injector', function($injector) {
		return $injector.instantiate(constructor);
	}]);
}

可以看到,实质上是调用了factory,第二个参数constructor是作为构造函数,最终返回的是该构造函数表示的类的一个实例。由于会调用$injector.instantiate(constructor),因此constructor必须是一个函数或者[deps, fn]的形式。

因此上面的例子可以写成:

app.service('greeting', function() {
	this.sayHello = function() {
		console.log('hello world');
	};
});

// or

app.service('greeting', function() {
	return {
		sayHello: function() {
			console.log('hello world');
		}
	};
});

4. value

源码如下:

function value(name, val) {
	return factory(name, valueFn(val), false);
}

可以看到,也是调用了factory。其中valueFn的作用是将一个值包装为函数,源码如下:

function valueFn(value) {
	return function() {
		return value;
	};
}

5. constant

源码如下:

function constant(name, value) {
	assertNotHasOwnProperty(name, 'constant');
	providerCache[name] = value;
	instanceCache[name] = value;
}

可以看到,该方法只是对providerCacheinstanceCache进行了属性设定。

6. value & constant

(1)value设定的值是可以改变的,而constant设定的值是不可变的,例如:

app.value('greeting', 'hello value')
	.value('greeting', 'hello world ')
	.controller('ctrl', ['greeting', function(greeting) {
		console.log(greeting); // hello world
	}]);

////////////////////

app.constant('greeting', 'hello constant')
	.constant('greeting', 'hello world ')
	.controller('ctrl', ['greeting', function(greeting) {
		console.log(greeting); // hello constant
	}]);

实现机制在函数setUpModuleLoader中。部分相关源码如下:

var moduleInstance = {
    _invokeQueue: invokeQueue,
    value: invokeLater('$provide', 'value'),
    constant: invokeLater('$provide', 'constant', 'unshift'),
    // ... ...
};

function invokeLater(provider, method, insertMethod, queue) {
    if (!queue) queue = invokeQueue;
    return function() {
        queue[insertMethod || 'push']([provider, method, arguments]);
        return moduleInstance;
    };
}

在调用value的时候,用的是数组的push方法;而调用constant的时候,用的是数组的unshift方法。在加载模块的时候,会依次执行_invokeQueue中的内容。对于通过value添加的值,会按照声明的顺序进行设定,因此后面的值会覆盖掉前面的值;而对于通过constant添加的值,会按照声明的逆序进行设定,因此最后得到的值为第一次设定的值,从而就实现了常量的效果。

(2)config中,value不可以被注入,而constant可以被注入,例如:

app.constant('greeting', 'hello world')
	.config(function(greeting) {
		console.log(greeting); // hello world
	});

////////////////////

app.value('greeting', 'hello world')
	.config(function(greeting) {
		console.log(greeting); // Error: [$injector:unpr] Unknown provider: greeting
	});

这是因为在使用value的时候,实际上是将greetingProvider换存在了providerCache中;而使用constant的时候,则是将greeting换存在了providerCache中。然后在执行模块的_configBlocks的时候,会在providerCache中查找注入的依赖greeting,因此对于value来说,自然是找不到了。

7. provider, factory & service

这三者的关系非常密切,看如下例子:

function Greeting() {
	this.sayHello = function() {
		console.log('hello world');
	};
}

app.provider('greetingFromProvider', function() {
		this.$get = function() {
			return new Greeting();
		}
	})
	.factory('greetingFromFactory', function() {
		return new Greeting();
	})
	.service('greetingFromService', Greeting)
	.controller('ctrl', ['greetingFromProvider', 'greetingFromFactory', 'greetingFromService',
		function(gp, gf, gs) {
			gp.sayHello();
			gf.sayHello();
			gs.sayHello();
		}
	]);

可以简单理解为service接收的参数是构造函数,factory接收的参数是工厂函数,而该工厂函数是作为provider中的$get属性而存在的。但是providerfactory更加灵活可配置,因此可以理解为可配置的工厂函数。例如:

app.provider('greeting', function() {
		var name = 'world';

		this.$get = function() {
			return {
				sayHello: function() {
					console.log('hello ' + name);
				}
			};
		};

		this.setName = function(newName) {
			name = newName;
		};
	})
	.config(function(greetingProvider) {
		greetingProvider.setName('alex');
	})
	.controller('ctrl', function(greeting) {
		greeting.sayHello(); // hello alex
	});
时间: 2025-01-23 22:25:22

AngularJS深入(5)——provider的相关文章

AngularJS 依赖注入详解和简单实例_AngularJS

AngularJS 依赖注入 什么是依赖注入 wiki 上的解释是:依赖注入(Dependency Injection,简称DI)是一种软件设计模式,在这种模式下,一个或更多的依赖(或服务)被注入(或者通过引用传递)到一个独立的对象(或客户端)中,然后成为了该客户端状态的一部分. 该模式分离了客户端依赖本身行为的创建,这使得程序设计变得松耦合,并遵循了依赖反转和单一职责原则.与服务定位器模式形成直接对比的是,它允许客户端了解客户端如何使用该系统找到依赖 一句话 --- 没事你不要来找我,有事我会

angularJS Provider、factory、service详解及实例代码_AngularJS

factory 用 Factory 就是创建一个对象,为它添加属性,然后把这个对象返回出来.你把 service 传进 controller 之后,在 controller 里这个对象里的属性就可以通过 factory 使用了. app.controller('myFactoryCtrl', function($scope, myFactory){ $scope.artist = myFactory.getArtis(); }); app.factory('myFactory', functio

angularjs中factory,service,provider 自定义服务的不同

一,factory,service,provider自定义服务,services.js  代码如下 复制代码 'use strict'; /* Services */ var phonecatServices = angular.module('phonecatServices', []); phonecatServices.factory('facetorytest', ['$window',         //factory方式     function($window){        

AngularJS Provider,Service,Factory

provider是干啥的? provider可以为应用提供通用的服务,形式可以是常量,也可以是对象. 比如我们在controller里常用的$http就是AngularJS框架提供的provider Java代码   myApp.controller('MainController', function($scope, $http) {       $http.get(-)   }    在上面的代码里,就可以直接使用$http包好的各种功能了 provider 那我们自己想定制一个provid

详解AngularJS中的依赖注入机制

  这篇文章主要介绍了详解AngularJS中的依赖注入机制,对JavaScript各组件的使用起到非常重要的作用,需要的朋友可以参考下 依赖注入是一个在组件中给出的替代了硬的组件内的编码它们的依赖关系的软件设计模式.这减轻一个组成部分,从定位的依赖,依赖配置.这有助于使组件可重用,维护和测试. AngularJS提供了一个至高无上的依赖注入机制.它提供了一个可注入彼此依赖下列核心组件. 值 工厂 服务 提供者 常值 值 值是简单的JavaScript对象,它是用来将值传递过程中的配置相位控制器

angularjs源码分析之:angularjs执行流程

angularjs用了快一个月了,最难的不是代码本身,而是学会怎么用angular的思路思考问题.其中涉及到很多概念,比如:directive,controller,service,compile,link,scope,isolate scope,双向绑定,mvvm等.最近准备把这些都慢慢搞懂,分析源码并贴到博客园,如有分析不对的地方,还望各位包容并指正. 先上个大图,有个大概印象,注:angularjs的版本为:1.2.1,通过bower install angularjs安装的. 几个重要方

AngularJS 单元测试(二)详解_AngularJS

使用对象模拟注入 我们可以非常容易的使用angularjs的$provider服务用一个对象模拟一个依赖并且注入. 例子如下 angular.module('artists',[]). factory('Artists',['imageStore',function(imageStore){ return { thumb:function(){ return imageStore.thumbnailUrl(id) } } }]) 如何实现 如何确定了服务 1.创建一个URL的引用,稍后会被moc

AngularJS 单元测试(一)详解_AngularJS

AngularJS单元测试 网上有很多单元测试的教程,比如如何安装jasmine和ngMock,大家可以搜一下.这里就不在说了.下面重点介绍一个单元测试的过程. 加载一个模块 AngularJS用module来包括应用不同的部分比如controllers,services, filters.为了测试不同的部分,我们需要一个不同模块的引用,并且加载它.Angularjs模块注入使用ngMock模块.ngMock模块能够注入服务service进入单元测试. ngMock暴露出angular.mock

Angularjs 自定义服务的三种方式(推荐)_AngularJS

AngularJS简介: AngularJS 通过新的属性和表达式扩展了 HTML. AngularJS 可以构建一个单一页面应用程序(SPAs:Single Page Applications). AngularJS 学习起来非常简单. angularjs 中可通过三种($provider,$factory,$service)方式自定义服务,以下是不同的实现形式: // 定义module , module中注入$provide var starterApp = angular.module('