基础知识
动态类型语言和鸭子类型:
编程语言按照数据类型大体可以分为两类,一类是静态类型语言,另一类是动态类型语言。静态类型语言在编译时便已确定变量的类型,而动态类型语言的变量类型要到程序运行的时候,待变量被赋予某个值之后,才会具有某种类型。
静态类型语言的优点: 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 组合起来可以达到单例模式的效果。
单例模式的核心是确保只有