JavaScript 五种常见设计模式手写实现与解析

作者:半夏时间:2026-10-26 07:52:12浏览:541

本文详细讲解JavaScript中五种常见设计模式的手写实现,包括工厂模式、单例模式、适配器模式、装饰者模式和建造者模式。通过具体代码示例,解析各模式的适用场景、核心逻辑及优缺点,帮助开发者理解并灵活运用这些设计模式优化代码结构。

本文旨在分享几种常见的设计模式。

目前涉及的模式包括:工厂模式、单例模式、适配器模式、装饰者模式和建造者模式。

建造者模式

简介:建造者模式(builder pattern)属于创建型模式的一种,其概念相对简单。

通俗理解:该模式包含四个部分:一个产品、一个负责制造产品的工厂、一个指挥生产数量的设计师,以及一个购买产品的用户。

购买产品的用户并不关心产品的制造流程,他们只关心最终的产品。

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函数中,它承担着整合的职责。

通过更换Packing函数(即打包方式),相同的Boss函数可以获得不同样式的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修饰器的提案,参考阮一峰的文章。

@testable
class MyTestableClass {
  // ...
}
function testable(target) {
  target.isTestable = true;
}
MyTestableClass.isTestable // true

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

@decorator
class A {}
// 等同于
class A {}
A = decorator(A) || A;

工厂模式

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

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);
}
//use
let 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)

例如:原始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: a
let b = Simple.getInstance('b') // name: a
b===a//true

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

通过闭包只创建一次Simple实例,供所有人共用。

惰性单例模式

惰性与懒加载(lazyload)相似,即延迟加载,或者说需要时再加载,否则一次加载过多,频繁操作DOM会影响性能。

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

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

例如:

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种设计模式,可能有的设计模式不一定在实际敲码中使用,学了没坏处,总能用得上!

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

到此这篇关于JavaScript设计模式手写示例讲解的文章就介绍到这了,更多相关JS设计模式内容请搜索本站以前的文章或继续浏览下面的相关文章,希望大家以后多多支持本站!

作者最新文章