四个你应该知道的Javascript设计模式
Posted 码上打卡
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了四个你应该知道的Javascript设计模式相关的知识,希望对你有一定的参考价值。
写出具有可维护性、可读性、可复用的代码是每一位开发者的追求。随着应用越来越大,代码架构变得越来越重要。面对这种挑战,设计模式慢慢的凸显出其重要性。
其实javascript开发者经常与开发模式打交道,只是有时候你可能没有察觉到。
尽管在某些情况,设计模式也不一而足,多种多样,但Javascript开发者倾向于使用一些通常的模式。
在这篇博文中,我想讨论一些通常用的模式和方法来提高你的编程技巧,以及潜入到Javascript更深的内部探索一些知识。
要讨论的设计模式包含如下:
模块
原型
观察者
单例
每一个模式有很多属性组成,我会重点放在以下重要的点上:
正文:在何种情况下使用何种模式?
问题:我们解决什么问题
解决方案:怎么用该模式解决我们提出的问题
实施:怎么实施?
模块设计模式
Javascript模块化,把代码分成各个单独的模块,保持每个组件的代码独立,是目前最流行的设计模式。这保证代码解耦,可以搭出结构化的代码。
和一些面向对象的语言相似,模块相当于Javascript的类。类的一个好处就是封闭性,保护状态及行为不被其它类干扰。模块模式可以有公开和私有的访问权限,以及一些少用的保护性和优先性访问权限。
模块化应该是面对私有域的立即函数表达式(IIFE)形式,私有域是指封闭的可以保护变量和方法,它将返回的是一个对象。看起来像下面的代码:
(function() { // declare private variables and/or functions
return { // declare public variables and/or functions } })();
在返回我们想要的对象之前,这里我们例示了私有变量和函数。我们封装之外 的代码是访问不到我们的私有变量的,因为它们不会在一个域中。我们举一个更具体的例子:
var htmlChanger = (function() {
var contents = 'contents' var changeHTML = function() {
var element = document.getElementById('attribute-to-change'); element.innerHTML = contents; }
return {
callChangeHTML: function() { changeHTML();
console.log(contents); } }; })(); HTMLChanger.callChangeHTML(); // Outputs: 'contents'
console.log(HTMLChanger.contents); // undefined
注意,callChangeHmtl函数绑到了返回对象上,它可以被有HTMLChanger命名空间的对象引用。不过,在模块之外,内容就没法被引用了。
显示模块模式
模块模式的变体称为显示模块模式。它的目的是维护封装,揭示某些变量和方法,以一个对象的方式返回。例示如下:
var Exposer = (function() {
var privateVariable = 10;
var privateMethod = function() {
console.log('Inside a private method!'); privateVariable++; }
var methodToExpose = function() {
console.log('This is a method I want to expose!'); }
var otherMethodIWantToExpose = function() { privateMethod(); }
return {
first: methodToExpose,
second: otherMethodIWantToExpose }; })(); Exposer.first(); // Output: This is a method I want to expose!
Exposer.second(); // Output: Inside a private method!
Exposer.methodToExpose; // undefined
尽管代码看起来更干净,一个明显的缺点是没办法引用私有方法。这对单元测试是个困难。相似的,公共的部分不能被重写。
原型设计模式
Javascript开发者估计没有人说没有见过关键字prototype,但很多人一边对原型继承困惑着,一边在代码里应用着。原型设计模式依赖Javascript原型继承理论。原型模型主要是在集中度较强的情况下创建对象使用。
原型继承是对象从源对象上复制(浅克隆)。一个使用原型模式的情况就是用在扩展数据库操作上,创建一个可以用在应用的其它部分的对象。如果另一个过程需要使用该对象,不用执行大量的数据操作,它将得益于从上一个创建的对象上克隆。
这个仿真图展示了一个原型接口被继承的具体执行过程。
为了克隆一个对象,构造器必须存在云实例化一个对象。下一步,用关键字prototype变量和方法绑到对象的结构上。我们看一个基础的例子:
var TeslaModelS = function() {
this.numWheels = 4;
this.manufacturer = 'Tesla';
this.make = 'Model S'; } TeslaModelS.prototype.go = function() { // Rotate wheels} TeslaModelS.prototype.stop = function() { // Apply brake pads}
构造函数允许创建单个TeslaModelS对象。当创建时,它会保持状态的初始化。另外,维护函数go和stop很容易,因为我们把它们声明在原型上。另一种在原型上扩展函数的方法如下:
var TeslaModelS = function() {
this.numWheels = 4;
this.manufacturer = 'Tesla';
this.make = 'Model S'; } TeslaModelS.prototype = { go: function() { // Rotate wheels }, stop: function() { // Apply brake pads } }
显示原型模式
和模块模式相似,原型模式也有一个显示变化。在被返回对象之前,显示原型模式为私有和公有成员提供封装。
在我们返回对象的时候,我将在函数前面加上原型对象。就以上面的例子做扩展,在当前原型中,我们可以选择我们想要暴露出去的东西以保留它们的访问等级:
var TeslaModelS = function() {
this.numWheels = 4;
this.manufacturer = 'Tesla';
this.make = 'Model S'; } TeslaModelS.prototype = function() {
var go = function() { // Rotate wheels };
var stop = function() { // Apply brake pads };
return { pressBrakePedal: stop, pressGasPedal: go } }();
注意函数stop和go是和返回的对象隔离的,由于返回对象的域在外面。因为Javscript本身支持原型继承,所以没有必要重写相关的特性。
观察者设计模式
我们在应用中总是遇到这样的情况,应用的一个部分改变,其它部分也要跟着被更新。在AngularJS中,如果$scope对象更新,则一个事件将被触发,通知到另一个相关的组件。观察者模式就是这样监控,如果一个对象被改变,就广播给相关的对象,这样就触发了变化。
另一个主流的例子就是model-view-controller(MVC)架构;模型改变时视图将跟着更新。一个好处是视图和模型的解耦以减少依赖。
上面仿真图表中所展示的,必需的对象是subject,observer和concrete对象。目标对象包含对具体观察者的引用以通知任何可能的变化。观察者对象是一个抽象的类,这个类允许具体的观察者完成通知方法。
我们看一个通过观察者模式来完成事件管理的例子,是angularJs的:
// Controller 1
$scope.$on('nameChanged', function(event, args) { $scope.name = args.name; }); ...// Controller 2
$scope.userNameChanged = function(name) { $scope.$emit('nameChanged', {name: name}); };
通过观察者模式,区分独立的对象或者目标对象很重要。
另一个注意意的重要的点是虽然观察者模式有很多优点,但随着观察者的增多,性能的下降是一个大的弊端。一个最臭名昭著的观察者就是watchers。在AngularJS中。我们能监视变量,函数和对象。$$digest循环运行着,无论何时一个域的对象被修改,它都会把新的值通知到每个监视者。
我们可以创建自己的目标对象和观察者。下面看一下是怎么完成的:
var Subject = function() {
this.observers = [];
return {
subscribeObserver: function(observer) {
this.observers.push(observer); },
unsubscribeObserver: function(observer) {
var index = this.observers.indexOf(observer);
if(index > -1) {
this.observers.splice(index, 1); } },
notifyObserver: function(observer) {
var index = this.observers.indexOf(observer);
if(index > -1) {
this.observers[index].notify(index); } },
notifyAllObservers: function() {
for(var i = 0; i < this.observers.length; i++){
this.observers[i].notify(i); }; } }; };
var Observer = function() {
return {
notify: function(index) {
console.log("Observer " + index + " is notified!"); } } }
var subject = new Subject();
var observer1 = new Observer();
var observer2 = new Observer();
var observer3 = new Observer();
var observer4 = new Observer(); subject.subscribeObserver(observer1); subject.subscribeObserver(observer2); subject.subscribeObserver(observer3); subject.subscribeObserver(observer4); subject.notifyObserver(observer2); // Observer 2 is notified!
subject.notifyAllObservers();// Observer 1 is notified!
// Observer 2 is notified!
// Observer 3 is notified!
// Observer 4 is notified!
发布/订阅
发布/订阅模式,用一个在对象之间的话题/事件通道以寄于能收到订阅者的通知,然后对象发射事件。这种事件系统代码定义具体应用的事件,能传自定义的订阅者需要的参数。这个办法是避免订阅者和发布者之间的依赖。
这和观察者模式不同,因为任何订阅者完成一个注册的事件是通过接收话题通知,这通知是来自发布者的广播。
很多开发者会选择集合使用发布/订阅设计模式和观察者设计模式,,尽管它们不同。订阅者是通过消息中间件来接收消息的,而观察者是通过实现类似于主题的处理程序接到通知的。
在AngularJS中,订单者通过$on('event',callback)方法订阅事件的,一个发布者是通过$emit('event',args)或$broadcast('event',args)方法来发布事件的。
单例
单例仅允许单实例化,不过相同的对象可以有很多实例。单例限制用户创建多对象,在第一个对象创建后,它就将返回自己的实例。
原先没有使用的它的同学,很难找出使用单例的案例。一个例子是用在了办公打印机上。如果办公室有十个人,他们共用一台打印机,就是十台电脑共享一个打印机,这里打印机就可以看作是一个实例,他们共享资源。
var printer = (function () {
var printerInstance;
function create () {
function print() { // underlying printer mechanics }
function turnOn() { // warm up // check for paper }
return { // public + private states and behaviors print: print, turnOn: turnOn }; }
return { getInstance: function() {
if(!printerInstance) { printerInstance = create(); }
return printerInstance; } };
function Singleton () {
if(!printerInstance) { printerInstance = intialize(); } }; })();
创建方法是私有的,因为我们不想让用户访问,不过,getinstance方法是公共的。每个办公室成员都可以能过getinstance方法构建一个打印机的实例,如下:
var officePrinter = printer.getInstance();
AngularJS中,单例很流行,最值得注意的是services,factories和providers。因为他们维护状态和提供资源访问权限,创建两个实例的话违背service/factory/provider的共享要点。
紊乱情况发生在多线程应用中,这时会不止一个线程访问同一个资源。在这种情况下,单例易受影响,如果初始化时没有实例,两个线程然后就会创建两个对象,而不是返回和实例化。这违背单例的设计目的。因此,在多线程应用中实现单例模式,开发者要注意同步问题。
结论
在大型应用中,设计模式很常用。在不同的实践中,也各有有缺点。
在构建大型应用前,你应该通盘考虑各个组件模块之间的交互关系。在复习了模块,原型,观察者及单例设计模式之后,你就应该有能力分辨这些模式,然后灵活的应用它们。
以上是关于四个你应该知道的Javascript设计模式的主要内容,如果未能解决你的问题,请参考以下文章