定义:保证一个类仅有一个实例,并提供一个访问它的全局访问点
const CreateDiv = function(html) {
this.html = html
this.init()
}
CreateDiv.prototype.init = function (){
let div = document.createElement('div')
div.innerHTML = this.html
document.body.appendChild(div)
}
var a = new CreateDiv('我是普通的div1')
var b = new CreateDiv('我是普通的div2')
// <div>我是普通的div1<div>
// <div>我是普通的div2<div>
// 代理
let ProxySingletonCreateDiv = (function(){
let instance
return function(html) {
if(!instance){
// 如果不存在instance实例,则创建一个
instance = new CreateDiv(html)
}else {
// 如果存在,则直接返回该实例
return instance
}
}
})
// 创建实例
var a = new CreateDiv('我是普通的div1')
var b = new CreateDiv('我是普通的div2')
// (a === b)
登录框案例:点击多次,只能创建一个登录框
const getSingle = function (fn) {
let result
return function() {
return result || (result = fn.apply(this,arguments))
}
}
const createLoginLayer = function () {
let div = document.createElement('div')
div.innerHTML = '我是登录框'
div.style.display = 'block'
document.body.appendChild('div')
return div
}
let createSingleLoginLayer = getSingle(createLoginLayer)
document.querySelect('#btn').onclick = function (){
let loginLayer = createSingleLoginLayer()
loginLayer.style.display = 'block'
}
定义:定义一系列的算法,把他们一个个封装起来,并且使他们可以互相替换
// 定义各个等级的计算函数,用一个总的对象包裹起来
const strategies = {
'S':function(salary) {
return salary * 4
},
'A':function(salary) {
return salary * 3
},
'B':function(salary) {
return salary * 2
}
}
// 暴露接口
let calculateBonus = function(level,salary){
return strategies[levels](salary)
}
// 计算等级S的工资
calculateBonus('S',20000) // 80000
// 计算等级A的工资
calculateBonus('A',10000) // 30000
const Animate = function (dom) {
this.dom = dom
this.startTime = 0 // 动画开始时间
this.startPos = 0 // dom初始位置
this.endPos = 0 // dom结束位置
this.propertyName = null // dom节点需要操控的属性名
this.easing = null // 缓动算法
this.duration = null // 动画持续时间
}
// 动画的启动方法
Animate.prototype.start = function (propertyName,endPos,duration,easing) {
this.startTime = Date.now() // 动画启动的时间
this.startPos = this.dom.getBoundingClientRect()[propertyName] // get方法是原生api,获取当前位置
this.propertyName = propertyName // 操控的css属性名
this.endPos = endPos // 目标位置
this.duration = duration // 动画持续时间
this.easing = tween[easing] // 缓动算法 tween方法未定义
const self = this
let timeId = setInterval(() => {
if (self.step() === false) {
// 如果动画已结束,则清楚定时器
clearInterval(timeId)
}
}, 1000)
}
Animate.prototype.step = function () {
let t = Date.now()
if (t >= this.startTime + this.duration) {
// 如果到了预定的时间(当前时间+每次运动的时间),更新css属性
this.update(this.endPos)
return false
}
let pos = this.easing(
t - this.startTime, // 总共运行了多久
this.startPos, // 开始位置
this.endPos - this.startPos, // 运动的距离
this.duration // 每帧的时间
)
this.update(pos)
}
Animate.prototype.update = function (pos) {
this.dom.style[this.propertyName] = pos + 'px'
}
let div = document.querySelector('.ball')
let animate = new Animate(div)
animate.start('right', 500, 1000, 'strongEaseIn')
实际开发中,通常会把使用策略模式来封装一系列的‘业务规则’,只要这些业务规则指向的目标一致,并且可以被替换使用,我们就可以用策略模式来封装它们
定义:代理模式是为一个对象提供一个代用品,以便控制对它的访问
// 某明星
let star = {
name: 'tongmian',
age: '20',
height: 175
}
// 经纪人
let proxy = new Proxy(star, {
get: function (target, key) {
if (key === 'height') {
// 有人获取明星的身高时,谎报身高
return target.height + 5
}
return target[key]
}
})
console.log(proxy.height) // 180
// 创建图片的方法
let myImage = (function(){
let imgNode = document.createElement('img')
document.body.appendChild(imgNode)
return function(src){
imgNode.src = src
}
})()
// 中间的代理
let proxyImage = (function(){
let img = new Image
img.onload = function(){
myImage.setSrc(this.src)
}
return function(src){
myImage.setSrc('占位用的图片地址')
img.src = src
}
})()
proxyImage('图片真正的地址')
// 文件上传的方法(主体)
let updateFile = function(id) {
console.log('开始上传文件')
}
// 代理
let proxyUpdateFile = (function(){
let cache = []
let timer
return function(id){
cache.push(id)
if(timer){
return
}
timer = setTimeout(function(){
updateFile(cache.join(','))
clearTimeout(timer)
timer = null
cache.length = 0
},2000)
}
})()
let checkbox = document.querySelect('.input')
checkbox.forEach(item=>{
item.onclick = ()=>{
if(this.checked === true) {
proxyUpdateFile(this.id) // 执行代理传入ID
}
}
})
let miniConsole = (function(){
let cache = []
let handler = function(e) {
if(e.keyCode === 113) {
let script = document.createElement('script')
script.onload = function(){
for(let i = 0,fn ; fn = cache[i++]) {
fn()
}
}
script.src = 'miniConsole.js'
document.getElementByTagName('head')[0].appendChild(script)
document.body.removeEventListener('keydown',handler) // 只加载一次
}
}
document.body.addEventListener('keydown',handler,false)
return {
log:function(){
let args = arguments
cache.push(function(){
return miniConsole.log.apply(miniConsole,args)
})
}
}
})()
miniConsole.log(1)
// miniConsole.js代码
miniConsole = {
log:function(){
console.log(Array.prototype.join.call(arguments))
}
}
let count = 0
let fbnqFn = n => {
count = count + 1
if (n === 1 || n === 2) {
return 1
}
return fbnqFn(n - 2) + fbnqFn(n - 1)
}
console.log(fbnqFn(41)) // 165580141
console.log(count+'次计算') // 331160281次计算
let count = 0 // 计数
// 本体 只用来计算斐波那契数列 只计算新式子
let fbnqFn = n => {
count = count + 1
if (n === 1 || n === 2) {
return 1
}
return proxyFbnq(n - 2) + proxyFbnq(n - 1) // 这里不再是调用自身
}
// 代理 保存已经计算的式子
let proxyFbnq = (() => {
let temp = {}
return function (n) {
if (n in temp) {
return temp[n]
}
return (temp[n] = fbnqFn(n)) // 新的式子丢回本体计算 并保存
}
})()
console.log(proxyFbnq(41)) // 165580141
console.log(count + '次计算') // 41次计算
定义:迭代器模式是指提供一种方法(顺序)访问一个对象中的各个元素,而又不需要暴露该对象的内部
小结:
定义:发布-订阅模式又叫观察者模式,它定义了对象间的一种一对多的依赖关系。
当一个对象的状态发生改变时,所有依赖于它的对象都会得到通知
DOM事件就是最好的发布-订阅模式的实例
// 订阅者 订阅了点击事件,持续监听它
document.querySelect('#btn').onclick = function () {
alert('我被点击了')
}
// 发布者
document.querySelect('#btn').click() // 模拟用户点击
// 拥有发布 和 订阅 功能的一个对象
let event = {
clientList : {}, // 缓存列表,存放给订阅者发送的信息
listen: (key,fn)=>{ // 设置key是保证订阅者只接收到它想要的消息
if(!this.clientList[key]){
// 如果这个key不存在则新建一个
this.clientList[key] = []
}
this.clientList[key].push( fn ) // 将消息添加到缓存
},
trigger: ()=>{
// 把参数的第一个取出来给key(关键字必须放在参数第一位)
let key = Array.prototype.shift.call( arguments )
let fns = this.clientList[ key ]
if( !fns || fns.length === 0 ) { // 如果没有绑定的消息
return false
}
for( let i = 0,fn; fn = fns[i++] ) {
fn.apply(this,arguments)
}
}
}
// 一个拷贝的函数 实际开发中这个可以去掉,直接使用event来实现
let installEvent = (obj)=>{
for(let i in event){
obj[i] = event[i]
}
}
let saleOffices = {} // 创建一个发布-订阅对象
installEvent(saleOffices)
saleOffices.listen('小户型',(price)=>{console.log('价格' + price)})//小明的订阅
saleOffices.listen('大户型',(price)=>{console.log('价格' + price)})//小明的订阅
// 售楼中心开始发布消息
saleOffices.trigger('小户型',10000)
saleOffices.trigger('大户型',12000)
小明如果突然不想买房,那么他就不愿意再接受售楼处发送的短信了,这时候应该取消订阅
event.remove = (key,fn) {
let fns = this.clientList[key]
if( !fns ){ // 如果对应的key没有被人订阅,那么直接返回
return false
}
if( !fn ){ // 如果没有传入具体的回调函数,表示需要取消key对应消息的所有订阅
fns && (fns.length = 0)
}else {
for(let i = fns.length -1 ; i >= 0 ; i--){
let _fn = fns[i]
if(_fn === fn){
fns.splice(i,1)
}
}
}
}
犹如插头转换器一样,适配器的作用就是将插座的插孔转换成手机充电器可用的插孔
// 插座类
class Power{
constructor(){
this.serveVoltage = 110 // 电压为110伏
this.serveShape = 'triangle' // 插头形状为三角形
}
}
// 适配器
class Adaptor{
constructor(){
// 插向插座的一面
this.consumeVoltage = 110
this.consumeShape = 'triangle'
}
// 面向手机充电器的一面
userPower(power){
if(!power){
throw new Error('请接入电源')
}
if(power.serveVoltage !== this.consumeVoltage || power.serveShage !== this.consumeShape){
throw new Error('电源规格不对')
}
// 修改面向手机充电器一面的接口参数
this.serveVoltage = 220
this.serveShage = 'double'
}
}
// 手机充电器
class User {
constructor(){
this.serveVlotage = 220
this.serveShage = 'double'
}
userPower(power){
if(!power){
throw new Error('请接入电源')
}
if(power.serveVoltage !== this.consumeVoltage || power.serveShage !== this.consumeShape){
throw new Error('电源规格不对')
}
}
}
// 使用
let power = new Power()
let user = new User()
let adaptor = new Adaptor()
adaptor.usePower(power)
user.usePower(adaptor)
向一个现有的对象添加新的功能,同时又不改变其结构。
使代码更加的优雅
手机壳就像是装饰器模式,不会改变手机原有的功能,但是增加了防摔,防窥,防水等效果
// 手机
function Phone(){}
Phone.prototype.mackcall = function(){
console.log('打电话')
}
// 装饰器
function decorate(target){
target.prototype.shell = function(){
console.log('手机壳')
}
return target
}
// 使用
Phone = decorate(Phone)
let phone = new Phone()
目前ES7已加入装饰器语法,但nodejs和浏览器都尚未支持,如果想使用,需要通过babel进行转译
// 装饰器
function writeCode(){
console.log('写代码')
}
@writeCode // 可以写多个装饰器
class Phone{
// 手机
}
var phone = new Phone()
phone.writeCode() // 写代码
function log(target,name,descriptor){
// target此时为Math.prototype
// name此时为方法名 add
// descriptor:
// value:函数本身
// enumerable:是否可遍历
// configurable:是否可配置
// writale:是否可重写
}
class Math{
@log
add(a,b){
return a+b
}
}
core-decorators
npm包将常用的装饰器工具进行了封装import {readonly,autobind,deprecate} from 'core-decorators'
// import为ES6语法,需要用webpack编译后引入
// readonly:只读
// autobind:自动绑定,把this强制绑定到实例上
// deprecate:弃用 警告你的用户这个方法即将弃用
把一堆复杂的接口逻辑放在一起(函数),对外提供一个更高级的统一接口,使外部对于子接口的访问和控制更加容易
function addEvent(dom,type,fn){
if(dom.addEventListener){
// 支持addEventListener方法的浏览器
dom.addEventListener(type,fn,false)
}else if(dom.attachEvent){
// IE浏览器
dom.attachEvent('on'+type,fn)
}else {
// 都不支持
dom['on'+type] = fn
}
}
// 不需要考虑兼容性的问题,直接使用即可
addEvent(myDom,'click',function(){ Do Something })
通常的封装,一般都是封装对象的行为,
在状态模式中是把对象的每种状态都进行独立封装
class Lamp{
constructor(){
this.offLightState = FSM.offLightState()
}
pressButton(){
this.state.trigger.call(this)
}
}
// FSM 有限状态机的缩写
// FSM定义:
// 1.状态总数是有限的
// 2.任一时刻,只处在一种状态之中
// 3.某种条件下,会从一种状态转变到另一种状态
const FSM = {
offLightState:{
// 关闭状态下触发trigger
trigger(){
console.log('弱光')
this.state = FMS.weakLightState
}
},
weakLightState:{
// 弱光状态下触发trigger
trigger(){
console.log('强光')
this.state = FMS.strongLightState
}
},
strongLightState:{
// 强光状态下触发trigger
trigger(){
console.log('关闭')
this.state = FMS.offLightState
}
}
}
本文地址:https://blog.csdn.net/weixin_45550048/article/details/107381861
如对本文有疑问, 点击进行留言回复!!
网友评论