设计模式开篇

301 阅读7分钟

js

灵活的js

函数声明

// 劣势:创建了很多全局变量
function checkname() {
  // 验证姓名
}
function checkage() {
  // 验证年龄
}
function checkpassword() {
  // 验证密码
}

函数表达式

// 函数表达式在使用前需要提前声明。
var checkname = function() {
  // 验证姓名
}
var checkage = function() {
  // 验证年龄
}
var checkpassword = function() {
  // 验证密码
}

用对象收编变量

var checkObj = {
  checkname() {
    // 验证姓名
  },
  checkage() {
    // 验证年龄
  },
  checkpassword() {
    // 验证密码
  },
}

对象的另一种形式

// 劣势:使用 new 关键字创建新对象时,不能继承这些方法
var checkObj = function() {}
checkObj.checkname = function() {
  // 验证姓名
}
checkObj.checkage = function() {
  // 验证年龄
}
checkObj.checkpassword = function() {
  // 验证密码
}

真假对象

// 劣势:返回的新对象跟 checkObj 没有任何关系,做不到真正意义上的复制
var checkObj = function() {
  return {
    checkname() {
      // 验证姓名
    },
    checkage() {
      // 验证年龄
    },
    checkpassword() {
      // 验证密码
    },
  }
}

var a = checkObj()
a.checkname()

// 劣势:每次 new 一个类的时候,都需要对类的 this 上的属性进性复制,造成资源消耗
var CheckObj = function() {
  this.checkname = function() {
    // 验证姓名
  }
  this.checkage = function() {
    // 验证年龄
  }
  this.checkpassword = function() {
    // 验证密码
  }
}

var a = new CheckObj()
a.checkname()

检测类

// 劣势:prototype 属性需要写多遍
var CheckObj = function() {}
CheckObj.prototype.checkname = function() {
  // 验证姓名
}
CheckObj.prototype.checkage = function() {
  // 验证年龄
}
CheckObj.prototype.checkpassword = function() {
  // 验证密码
}

// or 

// 劣势:覆盖 prototype 对象
var CheckObj = function() {}
CheckObj.prototype = {
  checkname() {
    // 验证姓名
  },
  checkage() {
    // 验证年龄
  },
  checkpassword() {
    // 验证密码
  },
}

var a = new CheckObj()
a.checkname()
a.checkage()
a.checkpassword()

链式调用

var CheckObj = {
  checkname() {
    // 验证姓名
    return this
  },
  checkage() {
    // 验证年龄
    return this
  },
  checkpassword() {
    // 验证密码
    return this
  },
}

// or 

var CheckObj = function() {}
CheckObj.prototype = {
  checkname() {
    // 验证姓名
    return this
  },
  checkage() {
    // 验证年龄
    return this
  },
  checkpassword() {
    // 验证密码
    return this
  },
}

var a = new CheckObj()
a.checkname().checkage().checkpassword()

函数的祖先

Function.prototype.checkemail = function() {
  // 验证邮箱
}

var f = function() {}
f.checkemail()

// or

var f = new Function()
f.checkemail()

// 缺点:污染原生对象 Function,所以别人创建的函数也会被你创建的函数所污染,造成不必要的开销

// 解决途径:抽象出一个统一添加方法的功能方法

Function.prototype.addMethods = function(name, fn) {
  this[name] = fn
  return this
}

// 链式添加和使用统一添加方法的功能方法

var methods = function() {}
methods
  .addMethods('checkname', function() {
    //  验证姓名
    return this
  })
  .addMethods('checkage', function() {
    // 验证年龄
    return this
  })
  .addMethods('checkpassword', function() {
    // 验证密码
    return this
  })

methods
  .checkname()
  .checkage()
  .checkpassword()
  
// 当然你也可以使用类
Function.prototype.addMethod = function(name, fn) {
    this.prototype[name] = fn
    return this
}

var Methods = function() {}
Methods.addMethod('chencname', function() {}).addMethod('checkemail', function() {})

var m = new Methods()
m.checkname()

编程风格:面向对象编程(另外还有面向过程编程,我在初期写项目的时候一直用的是面向过程编程)

封装

类可以分为三个部分:第一部分是构造函数内部的,这是供实例化对象复制用的,第二部分是通过点语法添加的,这是供类使用的,实例化对象是访问不到的,第三部分是类的原型中的,实例化对象可以通过原型链间接地访问到,也是供所有实例化对象所共用的。

// 创建一个类
var Book = function(id, bookname, price) {
    this.id = id
    this.bookname = bookname
    this.proce = price
}

// 通过类的原型附加属性
Book.prototype.display = function() {
    // 展示这本书
}

// or 

Book.prototype = {
    display() {
        // 展示这本书
    }
}

// 使用以上两种方法之前需要知道各自方法的利弊,对于实例、原型、构造函数之间的关系可以自行度娘了解

// 通过 this 添加的属性和方法是该对象自身拥有的,所以我们每次通过类创建一个新对象时,this 指向的属性和方法都会得到相应的创建,而通过 prototype 继承的属性和方法是每个对象通过 prototype 访问到,所以我们每次通过类创建一个对象时这些属性和方法不会再次创建。
var Book = function(id, name, price) {
    // 私有属性
    var num = 1
    // 私有方法
    function checkid() {}
    // 特权方法
    this.getname = function() {}
    this.getprice = function() {}
    this.setname = function() {}
    this.setprice = function() {}
    // 公有属性
    this.id = id
    // 公有方法
    this.copy = function() {}
    // 构造器
    this.setname(name)
    this.setprice(price)
}

// 类静态公有属性(对象不能访问,只能通过类本身调用)
Book.isChinese = true
// 类静态公有方法(对象不能访问,只能通过类本身调用)
Book.resetTime = function() {
    console.log('new Time')
}
Book.prototype = {
    // 公有属性
    isJSBook: false,
    // 公有方法
    display() {}
}

// 通过 new 关键字创建新对象时,由于类外面通过点语法添加的属性和方法没有执行到,所以新创建的对象中无法获取它们,但是可以通过类来使用。而类通过 prototype 创建的属性或者方法在类实例的对象中是可以通过 this 访问到的。

闭包

// 利用闭包实现类的静态变量
var Book = function() {
    // 静态私有变量
    var bookNum = 0
    // 静态私有方法
    function checkbook(name) {}
    // 返回构造函数
    return function(newid, newname, newprice) {
        // 私有变量
        var name, price
        // 私有方法
        function checkid(id) {}
        // 特权方法
        this.getname = function() {}
        this.getprice = function() {}
        this.setname = function() {}
        this.setprice = function() {}
        // 公有属性
        this.id = newid
        // 公有方法
        this.copy = function() {}
        bookNum++
        if (bookNum > 100) {
            throw new Error('我们仅出版100本书')
        }
        // 构造器
        this.setname(name)
        this.setprice(price)
    }
}

Book.prototype = {
    // 静态公有属性
    isJSBook: false,
    // 静态公有方法
    display() {}
}

 // or
 
 var Book = function() {
    // 静态私有变量
    var bookNum = 0
    // 静态私有方法
    function checkbook(name) {}
    // 返回构造函数
    function _book(newid, newname, newprice) {
        // 私有变量
        var name, price
        // 私有方法
        function checkid(id) {}
        // 特权方法
        this.getname = function() {}
        this.getprice = function() {}
        this.setname = function() {}
        this.setprice = function() {}
        // 公有属性
        this.id = newid
        // 公有方法
        this.copy = function() {}
        bookNum++
        if (bookNum > 100) {
            throw new Error('我们仅出版100本书')
        }
        // 构造器
        this.setname(name)
        this.setprice(price)
    }
    _book.prototype = {
        // 静态公有属性
        isJSBook: false,
        // 静态公有方法
        display() {}
    }
    return _book
}
// 避免忘记使用 new 关键字实例化对象而造成意想不到的错误
var Book = function(title, time, type) {
    if (this instanceof Book) {
        this.title = title
        this.time = time
        this.type = type
    } else {
        return new Book(title, time, type)
    }
}

继承

  • 原型链继承

优势:

  1. 利用原型让一个引用类型继承另一个引用类型的属性和方法

劣势:

  1. 原型中包含引用类型值的属性时,该属性会被所有实例共享,那么该属性在其中一个实例中被修改后会影响所有实例。
  2. 在创建子类型实例时,不能向超类型的实例中传递参数。
function SuperType() {
    this.property = true
}

SuperType.prototype.getSuperValue = function() {
    return this.property
}

function SubType() {
    this.subproperty = false
}

SubType.prototype = new SuperType()

SubType.prototype.getSubValue = function() {
    return this.subproperty
}

var instance = new SubType()

instance.getSuperValue() // true

// 别忘了默认原型哦,一句话概括:SubType 继承了 SuperType,而 SuperType 继承了 Object。当调用 instance.toString() 方法时,实际上调用的是保存在 Object.prototype 中的那个方法。
  • 构造函数继承

优势:

  1. 可以在子类型构造函数中向超类型构造函数传递参数

劣势:

  1. 方法都在构造函数中定义,因此函数很难复用
  2. 在超类型的原型中定义的方法,对子类型而言时不可见的,结果所有类型都只能使用构造函数模式
function SuperType(name) {
    this.name = name
}

function SubType() {
    SuperType.call(this, 'chen')
    this.age = 18
}

var instance = new SubType()

instance.name // 'chen'
instance.age // 18
  • 组合继承

优势:通过原型链实现对原型属性和方法的继承,通过构造函数实现对实例属性的继承。这样既通过原型上定义方法实现了函数的复用,又能够保证每个实例有自己的属性

劣势:

调用两次超类型构造函数:在创建子类型原型的时候,另一次在子类型构造函数内部。

function SuperType(name) {
    this.name = name
    this.colors = ['red']
}

SuperType.prototype.sayName = function() {
    alter(this.name)
}

function SubType(name, age) {
    SuperType.call(this, name)
    this.age = age
}

SubType.prototype = new SuperType()
SubType.prototype.constructor = SubType
SubType.prototype.sayAge = function() {
    alter(this.age)
}

var instance1 = new SubType('chen', 18)
instance1.colors.push('blue')
instance1.colors // ['red', 'blue']
instance1.sayName() // 'chen'
instance1.sayAge() // 18

var instance2 = new SubType('li', 16)
instance2.colors // ['red']
instance1.sayName() // 'li'
instance1.sayAge() // 20
  • 原型式继承
function object(o) {
    function F() {}
    F.prototype = o
    return new F()
}
var person = {
    name: 'chen'
    friends: ['li']
}

var person1 = object(person)
person1.name = 'chen1'
person1.friends.push('li1')

var person2 = object(person)
person2.name = 'chen2'
person2.friends.push('li2')

person1.name // 'chen1'
person1.friends // ['li', 'li1', 'li2']

person2.name // 'chen2'
person2.friends // ['li', 'li1', 'li2']

person.name // 'chen'
person.friends // ['li', 'li1', 'li2']
  • 寄生式继承
function object(o) {
    function F() {}
    F.prototype = o
    return new F()
}
function createAnother(original) {
    var clone = object(original)
    clone.sayHi = function() {
        alter('Hi')
    }
    return clone
}
var person = {
    name: 'chen'
    friends: ['li']
}

var person1 = createAnother(person)
person1.sayHi() // 'Hi'
  • 寄生组合式继承

优势:可以解决组合继承调用两次超类型构造函数的问题

function object(o) {
    function F() {}
    F.prototype = o
    return new F()
}
function inheritPrototype(SubType, SuperType) {
    var prototype = object(SuperType.prototype)
    prototype.constructor = SubType
    SubType.protoType = prototype
}
function SuperType(name) {
    this.name = name
    this.colors = ['red']
}

SuperType.prototype.sayName = function() {
    alter(this.name)
}

function SubType(name, age) {
    this.age = age
    SuperType.call(this, name)
}

inheritPrototype(SubType, SuperType)

SubType.prototype.sayAge = function() {
    alter(this.age)
}

多继承

function extend(target, source) {
    for (var property in source) {
        target[property] = source[property]
    }
    return target
}
function mix() {
    var i = 1,
        target = arguments[0],
        len = arguments.length,
        arg;
    for (; i < len; i++) {
        var arg = arguments[i]
        for (var property in arg) {
            targrt[property] = arg[property]
        }
    }
    return target
}

当然,你也可以将它绑定到原生对象 Object 上,这样所有的对象就都可以实现多继承了

Object.prototype.mix = function () {
    var i = 0,
        len = arguments.length,
        arg;
    for (; i < len; i++) {
        var arg = arguments[i]
        for (var property in arg) {
            this[property] = arg[property]
        }
    }
}

总结

这一篇文章主要讲述 JavaScript 面向对象编程的基础知识,也是后续五种设计模式的基础。接下来我会陆续总结五种设计模式,读者可以根据自己的兴趣选择性阅读。

  • 创建型设计模式

  • 结构型设计模式

  • 行为型设计模式

  • 技巧型设计模式

  • 架构型设计模式