写点什么

手写 JavaScript 常见 5 种设计模式

  • 2022-12-20
    浙江
  • 本文字数:4163 字

    阅读完需:约 14 分钟

想分享的几种设计模式

目前模式:工厂模式,单例模式,适配器模式,装饰者模式,建造者模式

建造者模式


简介:建造者模式(builder pattern)比较简单,它属于创建型模式的一种。


白话:4 个部分:有个产品,有个工厂可以造产品,有个设计师指挥造多少,有个人想买产品。


买产品的用户不介意产品制造流程,只需要产品!


function Cola() {    this.sugar = '50g',    this.water = '100g'}function Packing() { // 第一种打包方式    this.createPkg = function(){        console.log('创建可乐外皮')    }    this.pushCola = function() {        console.log('可乐倒进瓶子')    }    this.complete = function() {        var cola = new Cola()        cola.complete = true        return cola    }    this.init = function() {        this.createPkg() // 创建外皮        this.pushCola() // 倒进瓶子        //还可以增加其他步骤        return this.complete() // 制作完成    }}function greenPacking() { //绿皮可乐打包方式    this.createPkg = function(){        console.log('创建green可乐外皮')    }    this.pushCola = function() {        console.log('可乐倒进green瓶子')    }    this.complete = function() {        var cola = new Cola()        cola.complete = true        return cola    }    this.init = function() {        this.createPkg() // 创建外皮        this.pushCola() // 倒进瓶子        //还可以增加其他步骤        return this.complete() // 制作完成    }}function Boss() {    this.createCola = function(packType) {        const pack = new window[packType]        this.product = pack.init() //完整产品产出    }    this.getCola = function(packType) {        this.createCola(packType);        return this.product    }}const boss = new Boss()var UserCola = boss.getCola('greenPacking') // UserCola.complete === true
复制代码


其他东西都不要,只要最后生产好的 Cola,有 sugar,有 water。


关键在于 Boss 函数中,担任一个整合的职责


同样的 Boss 函数,我可以通过更换 Packing 函数,打包方式,获得不同样式的 Cola。


通过给 getCola 函数传入不同想要的参数,获得不同的最终产品。实现了可插拔的函数结构。

装饰者模式


装饰者提供比继承更有弹性的替代方案。 装饰者用用于包装同接口的对象,不仅允许你向方法添加行为,而且还可以将方法设置成原始对象调用(例如装饰者的构造函数)。


装饰者用于通过重载方法的形式添加新功能,该模式可以在被装饰者前面或者后面加上自己的行为以达到特定的目的。


好处:


装饰者是一种实现继承的替代方案。当脚本运行时,在子类中增加行为会影响原有类所有的实例,而装饰者却不然。取而代之的是它能给不同对象各自添加新行为


参考 前端进阶面试题详细解答


function iwatch () {    this.battery = 100;    this.getBattery = function() {        console.log(this.battery)    }}
iwatch.prototype.getNewPart = function(part) { this[part].prototype = this; //把this对象上的属性 指向 新对象的prototype return new this[part]; //返回一个新对象,不修改原对象,新增了新对象的属性}
iwatch.prototype.addNetwork = function() { this.network = function() { console.log('network') }}
iwatch.prototype.addSwim = function() { this.swim = function() { console.log('swim') }}
var watch = new iwatch();watch.getBattery(); // 100
watch = watch.getNewPart('addNetwork'); // 添加新行为,network()watch = watch.getNewPart('addSwim'); // 既有network方法,也有swim方法
复制代码


在 ES7 中引入了 @decorator 修饰器的提案,参考阮一峰的文章。


@testableclass MyTestableClass {  // ...}
function testable(target) { target.isTestable = true;}
MyTestableClass.isTestable // true
复制代码


直接可以使用,装饰器行为


@decoratorclass A {}
// 等同于
class A {}A = decorator(A) || A;
复制代码

工厂模式


一个工厂能生产好多不同的产品,最常见的工厂函数就是 jQ 的 $()函数,每一个函数的结果都是一个需要的产品。


function Product(name) {  this.name = name;}Product.prototype.init = function () {  console.log('init');}Product.prototype.go = function () {  console.log('go');}
function Factory () {}Factory.prototype.add = function(name) { return new Product(name);}
//uselet f = new Factory();let a = f.add('a');
console.log(a.name);a.init();a.go();
复制代码

适配器模式


Adapter,将一个类(对象)的接口(方法或者属性)转化为另一个接口,以满足用户需求,使类(对象)之间接口的不兼容问题通过适配器得以解决


function Person () {}Person.prototype.Say = function() {  throw new Error("该方法必须被重写!")}Person.prototype.Walk = function() {  throw new Error("该方法必须被重写!")}
function Dog () {}Dog.prototype.Walk = function() { throw new Error("该方法必须被重写!")}Dog.prototype.shout = function() { throw new Error("该方法必须被重写!")}
function PersonA () { Person.apply(this)}PersonA.prototype = new Person()PersonA.prototype.Say = function() { console.log('Person say')}PersonA.prototype.Walk = function() { console.log('Person Walk')}
function DogBlack () { Dog.apply(this)}DogBlack.prototype = new Dog()DogBlack.prototype.Walk = function() { console.log('Dog Walk')}DogBlack.prototype.shout = function() { console.log('Dog Shout')}
//现在希望Dog类也可以学会Say,并且多走几步
function DogSayAdapter (DogClass) { Dog.apply(this) this.DogClass = DogClass}DogSayAdapter.prototype = new Dog()DogSayAdapter.prototype.Say = function() { this.DogClass.shout()}DogSayAdapter.prototype.Walk = function() { this.DogClass.Walk() this.DogClass.Walk()}
var personA = new PersonA()var dogBlack = new DogBlack()var dogSay = new DogSayAdapter(dogBlack)
personA.Say()personA.Walk()dogBlack.Walk()dogBlack.shout()
dogSay.Say()dogSay.Walk()//walk * 2
复制代码


适配器不只是函数接口,还有数据格式的适配


在前后端数据传递时,常用到适配器模式,也就是通俗易懂的格式化数据,format 函数等等


vue 的 computed 计算属性也是适配器模式的一种实现


const originData = [    {        title: 'title',        age: 18,        content: ['123',321],        callback: function(){            console.log(this)        }    },  {        title: 'title2',        age: 1,        content: ['1',3],        callback: function(){            console.log('title2')        }    }]
function dataAdapter(data) { return data.map(item => { return { title: item.title, content: item.content.join(','), init: item.callback } })}
var formatData = dataAdapter(originData)
复制代码


e.g:原始 data 的数据不满足当前的要求,通过适配器,把数据格式化成想要的格式,对原始数据没有改变

单例模式


function Simple (name) {  this.name = name}Simple.prototype.go = function() {  this.name = 'go'  console.log(this.name)}
//static静态方法Simple.getInstance = (function() { var ins return function(name){ if (!ins) { ins = new Simple(name) } return ins }})()
let a = Simple.getInstance('a') // name: alet b = Simple.getInstance('b') // name: a
b===a//true
复制代码


非单例模式下,相同的 new Simple()构造函数,不相等。


通过闭包只创建一次 Simple 实例,大家公用一个。

惰性单例模式

惰性和懒加载 lazyload 相似,延迟加载,或者说需要时再加载,不然一次加载过多,频繁进行操作 dom 影响性能


尽管上述代码有 Simple.getInstance 方法,可以在需要时再进行实例化,但仍然不是一个好的实现方式。


可以将惰性加载的部分提取出来。


e.g:


var simple = function(fn) {    var instance;    return function() {        return instance || (instance = fn.apply(this, arguments));    }};// 创建遮罩层var createMask = function(){    // 创建div元素    var mask = document.createElement('div');    // 设置样式    mask.style.position = 'fixed';    mask.style.top = '0';      ...    ...    document.body.appendChild(mask);    // 单击隐藏遮罩层    mask.onclick = function(){        this.style.display = 'none';    }    return mask;};
// 创建登陆窗口var createLogin = function() { // 创建div元素 var login = document.createElement('div'); // 设置样式 login.style.position = 'fixed'; login.style.top = '50%'; ... ... login.innerHTML = 'login it'; document.body.appendChild(login);
return login;};
document.getElementById('btn').onclick = function() { var oMask = simple(createMask)(); oMask.style.display = 'block'; var oLogin = simple(createLogin)(); oLogin.style.display = 'block';}
复制代码

总结

对五种常见常用的设计模式进行了学习,这几种很多时候都会用到,接下来还会继续学习其他的 18 种设计模式,可能有的设计模式不一定在实际敲码中使用,学了没坏处,总能用得上嗷!


网上对于设计模式的文章,书籍层出不尽,但看得再多,不如自己理解,并且实际使用。很多时候是几种设计模式融合在一起使用,如果不是自己去写一遍,理解一遍,可能常见的设计模式都理解不了。这样就太可惜了,发现干净整洁的代码,都说不出哪里好,就是看着舒服,顺眼,运行速度快...


用户头像

还未添加个人签名 2022-07-31 加入

还未添加个人简介

评论

发布
暂无评论
手写JavaScript常见5种设计模式_JavaScript_helloworld1024fd_InfoQ写作社区