设计模式
面向对象
传统面向对象的三个特征:封装,继承,多态
封装
创建两个相同类型的对象
let catA = {
name:'catA',
eat(){
console.log('catA eat something')
}
}
let catB = {
name:'catB',
eat(){
console.log('catB eat something')
}
}
catA.eat()
catB.eat()
工厂模式封装
function createCat(name){
let obj = {}
obj.name = name
obj.eat = function(){
console.log(name+' eat something')
}
return obj
}
let catAA = createCat('catAA')
let catBB = createCat('catBB')
catAA.eat()
catBB.eat()
面向对象的方式封装
CreateCat叫做构造函数,为区分一般函数首字母大写,new出来的对象叫做实例
在构造函数中return基本类型不会影响构造函数的值,而return对象类型则会替代构造函数返回该对象
function CreateCat(name){
this.name = name
this.eat = function(){
console.log(this.name + ' eat something')
}
}
let catAAA = new CreateCat('catAAA')
let catBBB = new CreateCat('catBBB')
catAAA.eat()
catBBB.eat()
this
在函数执行的时候会在函数内部创建两个变量,arguments,this
- arguments存储着实参的一个类数组对象
- this指向函数的执行上下文(谁掉用这个函数this就指向谁)
function a(a,b){
console.log(a,b)//1,2
console.log(arguments)//{0:1,1:2,2:3,3:4,length:4}
}
a(1,2,3,4)
// 数组:[1,2,3,4]
// 类数组:{0:1,1:2,2:3,3:4,length:4}
function doit(){
console.log(this)
}
obj = {
b:doit,
c:{
d:doit
}
}
doit()//window
obj.b()//obj
obj.c.d()//obj.c
改变this指向
自定义方法
Function.prototype.myapply = function(thisobj,arg){
if(thisobj ==null || thisobj == undefined){
thisobj = window
}
let special = Symbol()
thisobj[special] = this
let result = thisobj[special](...arg)
delete thisobj[special]
return result
}
Function.prototype.mybind = function(thisobj,...arg){
let thisFn = this
const bindFn = function(...secondarg){
let isNew = this instanceof bindFn
let thisArg = isNew?Object.getPrototypeOf(this):thisobj
return thisFn.call(thisArg,...arg,...secondarg)
}
bindFn.prototype = Object.create(thisFn.prototype)
return bindFn
}
测试自定义的方法
let obj = {
name:'张三',
abc:function(){
console.log(this.name)
console.log(arguments)
console.log(this)
}
}
let obj2 = {name:'李四'}
obj.abc(7,3,52,2);//this指向obj
obj.abc.myapply(obj2,[1,2,3,4]);//this指向obj2
let d = obj.abc.mybind(obj2,[1,2,3]);//this指向obj2
d(5,6)
new运算符
**new在执行时会做四件事情:**见原型链
- 创建一个空的简单 JavaScript 对象(即
{}
); - 为步骤 1 新创建的对象添加属性
__proto__
,将该属性链接至构造函数的原型对象 ; - 将步骤 1 新创建的对象作为
this
的上下文 ; - 如果该函数没有返回对象,则返回
this
(有返回值,返回基本类型则返回this,否则返回该对象)。
多态
表示不同对象调用相同方法会产生不同结果
function Base(){}
Base.prototype.initial = function(){
this.init()
}
function SubA(){
this.init = function(){
console.log('subA init')
}
}
function SubB(){
this.init = function(){
console.log('subB init')
}
}
SubA.prototype = new Base()
SubB.prototype = new Base()
let subA = new SubA()
let subB = new SubB()
subA.initial()
subB.initial()
工厂模式
用于创建重复的对象
function createPerson(name){
let o={}
o.name = name
o.getName = function(){
console.log(this.name)
}
return o
}
let person1 = createPerson('zhangsan')
console.log(person1.name)
person1.getName()
进一步封装工厂模式
function Person(name){
this.name = name
}
Person.prototype.getName = function(){
console.log(this.name)
}
function Car(model){
this.model = model
}
Car.prototype.getModel = function(){
console.log(this.model)
}
function create(type,param){
// 判断函数调用是否使用了new运算符
if(this instanceof create){
// instanceof 判断后面的构造函数的原型是不是存在前面这个对象的原型链里
return new this[type](param)
}else{
return new create(type,param)
}
}
create.prototype = {
person:Person,
car:Car
}
let preson1 = new create('person','zhangsan')
let car1 = create('car','Benz')
preson1.getName()
car1.getModel()
建造者模式
建造者模式注重创建对象的细节,使用这种模式创建出的复杂对象或者复合对象结构非常清晰
// 如数据的处理
var data = [
{
name:'zhang san',
age:30,
work:'engineer'
},
{
name:'li si',
age:23,
work:'teacher'
},
{
name:'wang wu',
age:16,
work:'other'
}
]
function Candidata(param){
_candidata = {}
_candidata.name = param.name
_candidata.age = param.age
_candidata.firstname = param.name.split(' ')[0]
_candidata.secondname = param.name.split(' ')[1]
_candidata.work = {}
switch(param.work){
case 'engineer':
_candidata.work.name = '工程师'
_candidata.work.description = '热爱编程'
break
case 'tercher':
_candidata.work.name = '老师'
_candidata.work.description = '乐于分享'
break
default:
_candidata.work.name = '其他'
_candidata.work.description = '无'
}
_candidata.work.changeWork = function(work){
this.name = work
}
_candidata.work.changeDes = function(des){
this.description = des
}
return _candidata
}
// 将所有内容写在一个构造函数里不利于维护
let candidataArr = []
for(let i=0;i<data.length;i++){
candidataArr[i] = Candidata(data[i])
}
candidataArr[0].work.changeWork('singer')
candidataArr[0].work.changeDes('酷爱唱歌')
console.log(candidataArr[0])
优化建造者
function Candidata(param){
let _candidata = new Person(param)
_candidata.work = new CreateWork(param.work)
return _candidata
}
function Person(param){
this.name = param.name
this.age = param.age
this.firstname = this.name.split(' ')[0]
this.secondname = this.name.split(' ')[1]
}
function CreateWork(work){
switch(work){
case 'engineer':
this.name = '工程师'
this.description = '热爱编程'
break
case 'tercher':
this.name = '老师'
this.description = '乐于分享'
break
default:
this.name = '其他'
this.description = '无'
}
}
CreateWork.prototype.changeWork = function(work){
this.name = work
}
CreateWork.prototype.changeDes = function(des){
this.description = des
}
单例模式
单例模式希望在使用方法创建对象的时候,无论创建多少次对象都指向同一个
function notSingle(){
return {
a:123
}
}
let a = notSingle()
let b = notSingle()
console.log(a===b)//false
function NotSingle(){
this.a=123
}
let c = new NotSingle()
let d = new NotSingle()
console.log(c===d)//false
方式一:使用变量缓存创建的对象
// _unique是一个全局变量,被修改后会破坏单例模式,解决方法,把变量变成私有(闭包)
let _unique = null
function createSingle() {
let obj = {
a: 123
}
if (_unique === null) {
_unique = obj
}
return _unique
}
let a = createSingle()
// _unique = null; // 修改后将创建新的对象
let b = createSingle()
console.log(a === b)//true
方式二:安全的单例模式
let createSingle2 = (function () {
let _unique = null
function single() {
// 创建对象或者构造函数,都可以写在这里
return {
a: 1
}
}
return function () {
if (_unique === null) {
_unique = single()
}
return _unique
}
})()
let c = createSingle2()
// console.log(_unique)//undefind
let d = createSingle2()
console.log(c === d)//true
装饰器模式
装饰器模式希望在不改变原对象的基础上,通过对其拓展功能和属性来实现更复杂的逻辑
function Car(){
this.price = 10
}
Car.prototype = {
addHeatSeat:function(){
this.hasHeatSeat = true
this.price +=2
},
addAutoMirror:function(){
this.hasAutoMirror = true
this.price +=0.8
}
}
let car1 = new Car()
console.log(car1)//{price:10}
car1.addHeatSeat()
car1.addAutoMirror()
console.log(car1)//{price: 12.8, hasHeatSeat: true, hasAutoMirror: true}
装饰器模式写法:不会修改和破坏原来的构造函数,有新的功能和配置也不需要写在构造函数里面,而是单独声明一个函数去完成,减少了对构造函数的修改和接入
function Car() {
this.price = 10
}
function carWithHeatSeat(CarClass) {
CarClass.hasHeatSeat = true
CarClass.price += 2
}
function carWithAutoMirror(CarClass) {
CarClass.hasAutoMirror = true
CarClass.price +=0.8
}
let car2 = new Car()
console.log(car2)//{price:10}
carWithHeatSeat(car2)
carWithAutoMirror(car2)
console.log(car2)
观察者模式
观察者模式又叫发布订阅模式或者消息模式。是设计模式中非常著名也是非常重要的一种模式,这种模式一般会定义一个主体和众多的个体,这里主体可以想象为一个消息中心,里面有各种各样的消息,众多的个体可以订阅不同的消息,当未来消息中心发布某条消息的时候,订阅过他的个体就会得到通知。
// 主体
let msgCenter = (function () {
let _msg = {}//存储消息
/*
_msg = {
'carInfo':[person1.alreadyRegister.carInfo,person2.alreadyRegister.carInfo,person3.alreadyRegister.carInfo],
'newsInfo':[person1.alreadyRegister.newsInfo,person2.alreadyRegister.newsInfo]
}
*/
return {
// 订阅消息
register: function (type, fn) {
if (_msg[type]) {
_msg[type].push(fn)
} else {
_msg[type] = [fn]
}
},
// 发布消息
fire: function (type, args) {
if (!_msg[type]) {
return
}
let event = {
type,
args: args || {}
}
for (let i = 0; i < _msg[type].length; i++) {
_msg[type][i](event)
}
},
// 取消消息
cancel: function (type, fn) {
if (!_msg[type]) {
return
}
for (let i = 0; i < _msg[type].length; i++) {
if (_msg[type][i] === fn) {
_msg[type].splice(i, 1)
break
}
}
}
}
})()
// 个体
function Person() {
this.alreadyRegister = {}
}
Person.prototype.register = function (type, fn) {
if (this.alreadyRegister[type]) {
console.log('已订阅该消息')
} else {
msgCenter.register(type, fn)
this.alreadyRegister[type] = fn
}
}
Person.prototype.cancel = function (type) {
msgCenter.cancel(type, this.alreadyRegister[type])
delete this.alreadyRegister[type]
}
let person1 = new Person()
let person2 = new Person()
let person3 = new Person()
// 订阅
person1.register('carInfo', function (e) {
console.log('person1-' + e.type + '-' + e.args.info)
})
person2.register('carInfo', function (e) {
console.log('person2-' + e.type + '-' + e.args.info)
})
person3.register('carInfo', function (e) {
console.log('person3-' + e.type + '-' + e.args.info)
})
person1.register('newsInfo', function (e) {
console.log('person1-' + e.type + '-' + e.args.info)
})
person2.register('newsInfo', function (e) {
console.log('person2-' + e.type + '-' + e.args.info)
})
//消息中心发布消息
msgCenter.fire('carInfo', { info: '新汽车上市' })
msgCenter.fire('newsInfo', { info: '上天' })
msgCenter.fire('newsInfo', { info: '下海' })
console.log('----')
person1.cancel('carInfo')//取消订阅
msgCenter.fire('carInfo', { info: '新汽车消息' })//发布消息后person1没有该消息的订阅
策略模式
策略模式主要用在有多种状态或者策略需要进行,选择的时候,将所有选择封装在一起,只给外部暴露出必要的接口
比如有一个表单,需要验证表单项,如果验证不通过就返回一个提示文案。这里我们就可以使用策略模式的思路进行封装。
//如果有继承的情况就需要使用构造函数
let formStrategy = (function () {
let strategy = {
notEmpty: function (value) {
return value.length ? '' : '请填写内容'
},
isNumber: function (value) {
let reg = /^[0-9]+(\.[0-9]+)?$/
return reg.test(value) ? '' : '请填写一个数字'
},
isPhone: function (value) {
//010-12345678 0022-1234567
let reg = /^\d{3}-\d{8}$|^\d{4}-\d{7}$/
}
}
return {
validate: function (type, value) {
value = value.replace(/^\s+|\s+$/, '')
return strategy[type] ? strategy[type](value) : '无此方法,使用addStrategy手动添加'
},
addStrategy: function (type, fn) {
if (strategy[type]) {
return '该方法已存在'
} else {
strategy[type] = fn;
}
}
}
})()
formStrategy.addStrategy('isEmail', function (value) {
let reg = /^[a-zA-Z0-9_-]+@[a-zA-Z0-9_-]+(\.[a-zA-Z0-9_-]+)+$/
return reg.test(value) ? '' : '输入正确邮箱地址'
})
let oInput = document.querySelector('input')
oInput.onchange = function () {
let result = formStrategy.validate('notEmpty', this.value) ||
formStrategy.validate('isEmail', this.value) ||
'通过检测'
console.log(result)
}
链模式
链模式是实现链式调用的主要方法,通过在自身方法中返回自身的方式,在一个对象连续多次调用自身方法时可以简化写法,这种链式调用在开很多库和框架如jquery/zepto中频繁的被使用。
let obj = {
a:function(){
console.log('aaa')
return this
},
b:function(){
console.log('bbb')
return this
}
}
obj.a().b().a().b().b()
委托模式
当多个对象需要处理同一请求时,可以将这些请求交由另一个对象统一处理
<ul>
<li>1</li>
<li>2</li>
<li>3</li>
<li>4</li>
</ul>
<script>
// 需要给每个li绑定事件,造成性能损失内存占用,动态增加了li后,是没有绑定事件的
let lis = document.querySelectorAll('li')
for(let i=0;i<lis.length;i++){
lis[i].onclick = function(e){
console.log(e.target.innerText)
}
}
</script>
参考事件冒泡
<ul id="ul">
<li>1</li>
<li>2</li>
<li>3</li>
<LI>4</LI>
</ul>
<script>
window.onload = function(){
let oUl = document.querySelector('#ul')
oUl.onclick = function(e){
let target = e.target || e.srcElement;
if(target.nodeName.toLowerCase() ==='li'){
console.log(target.innerText)
}
}
// 新增的元素同样冒泡到ul
let oLi = document.createElement('li')
oLi.innerHTML = 'eddd'
oUl.appendChild(oLi)
}
</script>
MVC模式
MVC是由三个单词的首字符组成的:分别是模型model-视图view-控制器controller,他是一种是使用业务逻辑,数据,视图进行分离的方式来组织架构代码的一种模式。
简单实例
let MVC = {}
MVC.model = (function () {
let data = {
sidebar: [
{ title: 'sidebar1', href: './1.html'},
{ title: 'sidebar2', href: './2.html'},
{ title: 'sidebar3', href: './3.html'},
]
}
return {
getData:function(key){
return data[key]
},
setData:function(key,value){
data[key] = value
MVC.view('createSidebar')
}
}
})()
MVC.view = (function () {
let m = MVC.model
let view = {
createSidebar:function(){
let data = m.getData('sidebar')
let html = ''
html += '<div id="sidebar">'
for(let i=0;i<data.length;i++){
html+='<div><a href="'+data[i].href+'">'+data[i].title+'</a></div>'
}
html+='</div>'
document.body.innerHTML = html
}
}
return function(v){
view[v]()
}
})()
MVC.ctrl = (function () {
let m = MVC.model
let v = MVC.view
let c = {
initSideBar:function(){
v('createSidebar')
},
updateSideBar:function(){
m.setData('sidebar',[{title:'new sidebar',href:'./abc.html'}])
}
}
return c
})()
可使用如下命令在控制台手动初始化和更新
MVC.ctrl.initSideBar()
MVC.ctrl.updateSideBar()
此外还有一种MVVM模式是Model-View-ViewModel的简写。它本质上就是MVC 的改进版,MVVM 就是将其中的View 的状态和行为抽象化,让我们将视图 UI 和业务逻辑分开,重在数据驱动视图的一种设计模式。