读书笔记 - javascript设计模式和开发实践

基础知识

动态类型语言和鸭子类型:

编程语言按照数据类型大体可以分为两类,一类是静态类型语言,另一类是动态类型语言。静态类型语言在编译时便已确定变量的类型,而动态类型语言的变量类型要到程序运行的时候,待变量被赋予某个值之后,才会具有某种类型。

静态类型语言的优点: 1. 在编译时就能发现类型不匹配的错误 2. 在程序中明确地规定了数据类型

静态类型语言的缺点: 1.必须依照强契约来编写程序 2. 类型的声明也会增加更多的代码

动态类型语言的优点: 1. 代码数量更少,2.简洁

动态类型语言的缺点: 1;无法保证变量的类型

鸭子类型: 如果它走起路来像鸭子,叫起来也是鸭子,那么它就是鸭子。

多态

多态:给不同的对象发送同一个消息的时候,这些对象会根据这个消息分别给出不同的反馈。
一段多肽的代码:

案例:例如
主人家里养了两只动物,分别是一只鸭和一只鸡,当主人向它们发出“叫”的命令时,鸭会“嘎嘎嘎”地叫,而鸡会“咯咯咯”地叫。这两只动物都会以自己的方式来发出叫声。它们同样“都是动物,并且可以发出叫声”,但根据主人的指令,它们会各自发出不同的叫声。

var makeSound = function (animal) {
   
   
	if (animal instanceof Duck) {
   
   
		console.log( '嘎嘎嘎' );
	} else if (animal instanceof Chicken) {
   
   
		console.log( '咯咯' );
	}
}
对象的多态性
function makeSound = function (animal) {
   
   
	animal.sound()
}

var Duck = function () {
   
   }
Duck.prototype.sound = function () {
   
   
	console.log( '嘎嘎嘎' );
}
var Chilken = function () {
   
   }
Chilken.prototype.sound = function () {
   
   
	console.log( '咯咯' );
}

makeSound(new Duck())
makeSound(new Chilken())
javascript的多态
var gooleMap = function () {
   
   
	show: function () {
   
   
		console.log( 'google地图开始渲染' );
	}
}
var baiduMap = function () {
   
   
	show: function () {
   
   
		console.log('百度地图开始渲染')
	}
}

var renderMap = function (map) {
   
   
	if (map.show instanceof Function) {
   
   
		map.show()
	}
}
renderMap(gooleMap)
renderMap(baiduMap)
封装

封装: 封装的目的是将信息隐藏。封装可以是:封装数据,封装实现,封装类型,封装变化。

  • 封装数据
var myObject = (function () {
   
   
	var _name = 'liz' //  私有(private)变量
	return {
   
    
		getName: function () {
   
   return _name} // 公开(public)方法
	}
})() 

console.log( myObject.getName() ); // 输出:sven 
console.log( myObject.__name ) // 输出:undefined
  • 封装实现
    封装的目的是将信息隐藏,封装应该被视为“任何形式的封装”,也就是说,封装不仅仅是隐藏数据,还包括隐藏实现细节、设计细节以及隐藏对象的类型等。

例如:迭代器的封装

  • 封装类型
    例如:如工厂方法模式、组合模式

  • 封装变化
    封装变化:找到变化并封装之。

继承(设计模式 - 原型模式)
  • 使用克隆的原型模式
    从设计的角度讲,原型模式是用于创建对象的一种模式,如果我们想要创建一个对象,一种方式是先指定它的类型,然后通过类来创建这个对象。原型模式选择了另外一种方式,我们不再关心对象的具体类型,而是找到一个对象,然后通过克隆来创建一个一模一样的对象。

如果使用原型模式,我们只需要调用负责克隆的方法,便能完成同样的功能。原型模式的实现关键,是语言本身是否提供了clone方法。ECMAScript 5提供了Object.create方法,可以用来克隆对象。

案例:

var Plane = function() {
   
   
	this.blood = 100
	this.attackLevel = 1
	this.defenseLevel = 1
}
var plane = new Plane()
plane.blood = 500
plane.attackLevel = 10
plane.defenseLevel = 7

// 克隆对象
var clonePlane = Object.create(plane)
console.log( clonePlane ); // 输出:Object {blood: 500, attackLevel: 10, defenseLevel: 7}

在不支持 Object.create 方法的浏览器中,则可以使用以下代码:

Object.create = Object.create || function( obj ){
   
   
	var F = function () {
   
   }
	F.prototype = obj
	return new F()
}
  • 克隆是创建对象的手段
    原型模式的真正目的并非在于需要得到一个一模一样的对象,而是提供了一种便捷的方式去创建某个类型的对象,克隆只是创建这个对象的过程和手段。

  • javascript中的原型继承

事实上,JavaScript 中的根对象是 Object.prototype 对象。Object.prototype 对象是一个空的对象。我们在 JavaScript 遇到的每个对象,实际上都是从 Object.prototype 对象克隆而来的,Object.prototype 对象就是它们的原型。

在 JavaScript 语言里,我们并不需要关心克隆的细节,因为这是引擎内部负责实现的。我们所需要做的只是显式地调用 var obj1 = new Object()或者 var obj2 = {}。此时,引擎内部会从Object.prototype 上面克隆一个对象出来,我们最终得到的就是这个对象。

继承

var A = function() {
   
   }
A.prototype = {
   
   name: 'seven'}

var B = function () {
   
   }
B.prototype = new A()

var b = new B()
// 测试
console.log(b.name) // seven

我们来看看执行这段代码的时候,引擎做了哪些事情。
1.首先,尝试遍历对象 b 中的所有属性,但没有找到 name 这个属性。
2. 查找 name 属性的请求被委托给对象 b 的构造器的原型,它被 b.proto 记录着并且指向B.prototype,而 B.prototype 被设置为一个通过 new A()创建出来的对象。
3. 在该对象中依然没有找到 name 属性,于是请求被继续委托给这个对象构造器的原型A.prototype。
4. 在 A.prototype 中找到了 name 属性,并返回它的值。

原型继承的未来
class Animal{
   
   
	constructor(name) {
   
    this.name = name }
	getName() {
   
    return this.name }
}

class Dog extends Animal{
   
   
	constructor(props) {
   
   
		super(props)
	}
	speak() {
   
   
		return 'woof'
	}
}

var dog = new Dog('Scamp')
console.log(dog.getName() + ' says ' + dog.speak());

单例模式:

单例模式的定义是:保证一个类仅有一个实例,并提供一个访问它的全局访问点。

单例模式是一种常用的模式,有一些对象我们往往只需要一个,比如线程池、全局缓存、浏 览器中的 window 对象等。在 JavaScript 开发中,单例模式的用途同样非常广泛。试想一下,当我 们单击登录按钮的时候,页面中会出现一个登录浮窗,而这个登录浮窗是唯一的,无论单击多少 次登录按钮,这个浮窗都只会被创建一次,那么这个登录浮窗就适合用单例模式来创建。

单例模式实现:

要实现一个标准的单例模式并不复杂,无非是用一个变量来标志当前是否已经为某个类创建过对象,如果是,则在下一次获取该类的实例时,直接返回之前创建的对象。

方法1:

var Singleton = function(name) {
   
   
      this.name = name
      this.instance = null
    }
    Singleton.prototype.getName = function() {
   
   
      return this.name
    }
    Singleton.getInstance = function (name) {
   
   
      if (!this.instance) {
   
   
        this.instance = new Singleton(name)
      }
      return this.instance
    }
    // 测试:
    var a = Singleton.getInstance('sven1')
    var b = Singleton.getInstance('sven2')
    alert(a === b) // true

等同于:
但是下面的方法用到了匿名函数和自执行

 var Singleton = function (name) {
   
   
      this.name = name
    }
    Singleton.getInstance = (function() {
   
   
      var instance = null
      return function(name) {
   
   
        if (!instance) {
   
   
          instance = new Singleton(name)
        }
        return instance
      }
    })()
// 测试:
var a = Singleton.getInstance( 'sven1' ); 
var b = Singleton.getInstance( 'sven2' ); 
alert ( a === b ); // true

以上方式创建的单例有一个缺点:不透明性。因为:使用者:不一定知道这是个单例。跟以往通过 new XXX 的方式来获取对象不同,这里偏要使用 Singleton.getInstance 来获取对象。

透明的单例模式
var CreateDiv = (function() {
   
   
      var instance;
      var CreateDiv = function(html) {
   
   
        if (instance) {
   
   
          return instance
        }
        this.html = html
        this.init()
        return instance = this
      }
      CreateDiv.prototype.init = function() {
   
   
        var div = document.createElement('div')
        div.innerHTML = this.html
        document.body.appendChild(div)
      }
      return CreateDiv
    })()
    var a = new CreateDiv( 'sven1' ); 
    var b = new CreateDiv( 'sven2' ); 
    alert ( a === b ); // true

虽然现在完成了一个透明的单例类的编写,但它同样有一些缺点。
为了把 instance 封装起来,我们使用了自执行的匿名函数和闭包,并且让这个匿名函数返回真正的 Singleton 构造方法,这增加了一些程序的复杂度,阅读起来也不是很舒服。

var CreateDiv = function( html ){
   
    
	 if ( instance ){
   
    
	 	return instance; 
	 } 
	 this.html = html; 
	 this.init(); 
	 return instance = this; 
}; 

在这段代码中,CreateDiv 的构造函数实际上负责了两件事情。第一是创建对象和执行初始化 init 方法,第二是保证只有一个对象。虽然我们目前还没有接触过“单一职责原则”的概念,但可以明确的是,这是一种不好的做法,至少这个构造函数看起来很奇怪。

使用代理模式实现单例模式(重点):
// 创建 div 的类
 var CreateDiv = function(html) {
   
   
      this.html = html
      this.init()
}
CreateDiv.prototype.init = function() {
   
   
  var div = document.createElement( 'div' ); 
  div.innerHTML = this.html; 
  document.body.appendChild( div );
}

// 接下来引入代理类 proxySingletonCreateDiv:
var proxySingletonCreateDiv = function () {
   
   
  var instance;
  return function (html) {
   
   
    if (!instance) {
   
   
      instance = new CreateDiv(html)
    }
    return instance
  }
}
var a = new ProxySingletonCreateDiv( 'sven1' ); 
var b = new ProxySingletonCreateDiv( 'sven2' );
alert ( a === b );

通过引入代理类的方式,我们同样完成了一个单例模式的编写,跟之前不同的是,现在我们把负责管理单例的逻辑移到了代理类 proxySingletonCreateDiv 中。这样一来,CreateDiv 就变成了一个普通的类,它跟 proxySingletonCreateDiv 组合起来可以达到单例模式的效果。

单例模式的核心是确保只有

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值