资讯专栏INFORMATION COLUMN

[面试专题]JS设计模式

李义 / 1678人阅读

摘要:设计模式发布订阅模式这种设计模式可以大大降低程序模块之间的耦合度,便于更加灵活的扩展和维护。代理模式使得代理对象控制具体对象的引用。该模式使一个类的实例化延迟到了子类。

JS设计模式 发布订阅模式:

这种设计模式可以大大降低程序模块之间的耦合度,便于更加灵活的扩展和维护。

// 一个播放器类
class Player {

  constructor() {
    // 初始化观察者列表
    this.watchers = {}

    // 模拟2秒后发布一个"play"事件
    setTimeout(() => {
      this._publish("play", true)
    }, 2000)

    // 模拟4秒后发布一个"pause"事件
    setTimeout(() => {
      this._publish("pause", true)
    }, 4000)
  }

  // 发布事件
  _publish(event, data) {
    if (this.watchers[event] && this.watchers[event].length) {
      this.watchers[event].forEach(callback => callback.bind(this)(data))
    }
  }

  // 订阅事件
  subscribe(event, callback) {
    this.watchers[event] = this.watchers[event] || []
    this.watchers[event].push(callback)
  }

  // 退订事件
  unsubscribe(event = null, callback = null) {
    // 如果传入指定事件函数,则仅退订此事件函数
    if (callback&&event) {
      if (this.watchers[event] && this.watchers[event].length) {
        this.watchers[event].splice(this.watchers[event].findIndex(cb => Object.is(cb, callback)), 1)
      }

    // 如果仅传入事件名称,则退订此事件对应的所有的事件函数
    } else if (event) {
      this.watchers[event] = []

    // 如果未传入任何参数,则退订所有事件
    } else {
      this.watchers = {}
    }
  }
}

// 实例化播放器
const player = new Player()
console.log(player)

// 播放事件回调函数1
const onPlayerPlay1 = function(data) {
  console.log("1: Player is play, the `this` context is current player", this, data)
}

// 播放事件回调函数2
const onPlayerPlay2 = data => {
  console.log("2: Player is play", data)
}

// 暂停事件回调函数
const onPlayerPause = data => {
  console.log("Player is pause", data)
}

// 加载事件回调函数
const onPlayerLoaded = data => {
  console.log("Player is loaded", data)
}

// 可订阅多个不同事件
player.subscribe("play", onPlayerPlay1)
player.subscribe("play", onPlayerPlay2)
player.subscribe("pause", onPlayerPause)
player.subscribe("loaded", onPlayerLoaded)

// 可以退订指定订阅事件
player.unsubscribe("play", onPlayerPlay2)
// 退订指定事件名称下的所有订阅事件
player.unsubscribe("play")
// 退订所有订阅事件
player.unsubscribe()

// 可以在外部手动发出事件(真实生产场景中,发布特性一般为类内部私有方法)
player._publish("loaded", true)
中介者模式 Mediator Pattern:

观察者模式通过维护一堆列表来管理对象间的多对多关系,中介者模式通过统一接口来维护一对多关系,且通信者之间不需要知道彼此之间的关系,只需要约定好API即可。

// 汽车
class Bus {

  constructor() {

    // 初始化所有乘客
    this.passengers = {}
  }

  // 发布广播
  broadcast(passenger, message = passenger) {
    // 如果车上有乘客
    if (Object.keys(this.passengers).length) {

      // 如果是针对某个乘客发的,就多带带给他听
      if (passenger.id && passenger.listen) {

        // 乘客他爱听不听
        if (this.passengers[passenger.id]) {
          this.passengers[passenger.id].listen(message)
        }

      // 不然就广播给所有乘客
      } else {
        Object.keys(this.passengers).forEach(passenger => {
          if (this.passengers[passenger].listen) {
            this.passengers[passenger].listen(message)
          }
        })
      }
    }
  }

  // 乘客上车
  aboard(passenger) {
    this.passengers[passenger.id] = passenger
  }

  // 乘客下车
  debus(passenger) {
    this.passengers[passenger.id] = null
    delete this.passengers[passenger.id]
    console.log(`乘客${passenger.id}下车`)
  }

  // 开车
  start() {
    this.broadcast({ type: 1, content: "前方无障碍,开车!Over"})
  }

  // 停车
  end() {
    this.broadcast({ type: 2, content: "老司机翻车,停车!Over"})
  }
}

// 乘客
class Passenger {

  constructor(id) {
    this.id = id
  }

  // 听广播
  listen(message) {
    console.log(`乘客${this.id}收到消息`, message)
    // 乘客发现停车了,于是自己下车
    if (Object.is(message.type, 2)) {
      this.debus()
    }
  }

  // 下车
  debus() {
    console.log(`我是乘客${this.id},我现在要下车`, bus)
    bus.debus(this)
  }
}

// 创建一辆汽车
const bus = new Bus()

// 创建两个乘客
const passenger1 = new Passenger(1)
const passenger2 = new Passenger(2)

// 俩乘客分别上车
bus.aboard(passenger1)
bus.aboard(passenger2)

// 2秒后开车
setTimeout(bus.start.bind(bus), 2000)

// 3秒时司机发现2号乘客没买票,2号乘客被驱逐下车
setTimeout(() => {
  bus.broadcast(passenger2, { type: 3, content: "同志你好,你没买票,请下车!" })
  bus.debus(passenger2)
}, 3000)

// 4秒后到站停车
setTimeout(bus.end.bind(bus), 3600)

// 6秒后再开车,车上已经没乘客了
setTimeout(bus.start.bind(bus), 6666)
代理模式 Proxy Pattern:

为其他对象提供一种代理以控制对这个对象的访问。
代理模式使得代理对象控制具体对象的引用。代理几乎可以是任何对象:文件,资源,内存中的对象,或者是一些难以复制的东西。

ES6中的Proxy对象

const target = {}
const handler = {
    get(target, property) {
        if (property in target) {
            return target[property]
        } else {
            throw new ReferenceError("Property "" + property + "" does not exist.")
        }
    }
}
const p = new Proxy(target, {})
p.a = 3  // 被转发到代理的操作
console.log(p.c) //
单例模式 Singleton Pattern:

保证一个类只有一个实例,并提供一个访问它的全局访问点(调用一个类,任何时候返回的都是同一个实例)。

实现方法:使用一个变量来标志当前是否已经为某个类创建过对象,如果创建了,则在下一次获取该类的实例时,直接返回之前创建的对象,否则就创建一个对象。

// 类数实例:
class Singleton {
  constructor(name) {
    this.name = name
    this.instance = null   // 
  }
  getName() {
    alert(this.name)
  }
  static getInstance(name) {
    if (!this.instance) {
      this.instance = new Singleton(name)
    }
    return this.instance
  }
}
const ins = new Singleton("hhhh")
const instanceA = Singleton.getInstance("seven1")
const instanceB = Singleton.getInstance("seven2")
//闭包包装实例:
const SingletonP = (function() {
  let instance
  return class Singleton {

    constructor(name) {
      if (instance) {
        return instance
      } else {
        this.init(name)
        instance = this
        return this
      }
    }

    init(name) {
      this.name = name
      console.log("已初始化")
    }
  }
})()

const instanceA = new SingletonP("seven1")
const instanceB = new SingletonP("seven2")
// ES5 iife
var SingletonTester = (function () {
    function Singleton(args) {
        var args = args || {};
        //设置name参数
        this.name = "SingletonTester";
    }
    //实例容器
    var instance;
    return {
        name: "SingletonTester",
        getInstance: function (args) {
            if (instance === undefined) {
                instance = new Singleton(args);
            }
            return instance;
        }
    };
})();

var singletonTest = SingletonTester.getInstance({ pointX: 5 });
console.log(singletonTest.pointX); // 输出 5 
// 构造函数的属性
function Universe() {
    if (typeof Universe.instance === "object") {
        return Universe.instance;
    }
    this.start_time = 0;
    this.bang = "Big";
    Universe.instance = this;
}
// 测试
var uni = new Universe();
var uni2 = new Universe();
console.log(uni === uni2); // true
// 重写构造函数
function Universe() {
    var instance = this;
    // 其它内容
    this.start_time = 0;
    this.bang = "Big";
    // 重写构造函数
    Universe = function () {
        return instance;
    };
}
// 测试
var uni = new Universe();
var uni2 = new Universe();
uni.bang = "123";
console.log(uni === uni2); // true
console.log(uni2.bang); // 123
工厂模式 Factory Pattern:

工厂模式定义一个用于创建对象的接口,这个接口由子类决定实例化哪一个类。该模式使一个类的实例化延迟到了子类。而子类可以重写接口方法以便创建的时候指定自己的对象类型。

简单说:假如我们想在网页面里插入一些元素,而这些元素类型不固定,可能是图片、链接、文本,根据工厂模式的定义,在工厂模式下,工厂函数只需接受我们要创建的元素的类型,其他的工厂函数帮我们处理。

// 文本工厂
class Text {
    constructor(text) {
        this.text = text
    }
    insert(where) {
        const txt = document.createTextNode(this.text)
        where.appendChild(txt)
    }
}

// 链接工厂
class Link {
    constructor(url) {
        this.url = url
    }
    insert(where) {
        const link = document.createElement("a")
        link.href = this.url
        link.appendChild(document.createTextNode(this.url))
        where.appendChild(link)
    }
}

// 图片工厂
class Image {
    constructor(url) {
        this.url = url
    }
    insert(where) {
        const img = document.createElement("img")
        img.src = this.url
        where.appendChild(img)
    }
}

// DOM工厂
class DomFactory {

  constructor(type) {
    return new (this[type]())
  }

  // 各流水线
  link() { return Link }
  text() { return Text }
  image() { return Image }
}

// 创建工厂
const linkFactory = new DomFactory("link")
const textFactory = new DomFactory("text")

linkFactory.url = "https://surmon.me"
linkFactory.insert(document.body)

textFactory.text = "HI! I am surmon."
textFactory.insert(document.body)
装饰者模式 Decorative Pattern:

装饰者(decorator)模式能够在不改变对象自身的基础上,在程序运行期间给对像动态的添加职责(方法或属性)。与继承相比,装饰者是一种更轻便灵活的做法。

简单说:可以动态的给某个对象添加额外的职责,而不会影响从这个类中派生的其它对象。

ES7装饰器
function isAnimal(target) {
    target.isAnimal = true
    return target
}

// 装饰器
@isAnimal
class Cat {
    // ...
}
console.log(Cat.isAnimal)    // true



作用于类属性的装饰器:

function readonly(target, name, descriptor) {
    discriptor.writable = false
    return discriptor
}

class Cat {
    @readonly
    say() {
        console.log("meow ~")
    }
}

var kitty = new Cat()
kitty.say = function() {
    console.log("woof !")
}
kitty.say()    // meow ~

参考:
输入理解js系列
来自ES6入门实践

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/112481.html

相关文章

  • [面试专题]一线互联网大厂面试总结

    摘要:道阻且长啊前端面试总结前端面试笔试面试腾讯一面浏览器工作原理浏览器的主要组件包括用户界面包括地址栏后退前进按钮书签目录浏览器引擎用来查询及操作渲染引擎的接口渲染引擎渲染界面和是基于两种渲染引擎构建的,使用自主研发的渲染引擎,和都使用网络用来 道阻且长啊TAT(前端面试总结) 前端 面试 笔试 面试 腾讯一面 1.浏览器工作原理 浏览器的主要组件包括: 用户界面- 包括地址栏、后退/前...

    lemanli 评论0 收藏0
  • [面试专题]一线互联网大厂面试总结

    摘要:道阻且长啊前端面试总结前端面试笔试面试腾讯一面浏览器工作原理浏览器的主要组件包括用户界面包括地址栏后退前进按钮书签目录浏览器引擎用来查询及操作渲染引擎的接口渲染引擎渲染界面和是基于两种渲染引擎构建的,使用自主研发的渲染引擎,和都使用网络用来 道阻且长啊TAT(前端面试总结) 前端 面试 笔试 面试 腾讯一面 1.浏览器工作原理 浏览器的主要组件包括: 用户界面- 包括地址栏、后退/前...

    xfee 评论0 收藏0
  • [面试专题]一线互联网大厂面试总结

    摘要:道阻且长啊前端面试总结前端面试笔试面试腾讯一面浏览器工作原理浏览器的主要组件包括用户界面包括地址栏后退前进按钮书签目录浏览器引擎用来查询及操作渲染引擎的接口渲染引擎渲染界面和是基于两种渲染引擎构建的,使用自主研发的渲染引擎,和都使用网络用来 道阻且长啊TAT(前端面试总结) 前端 面试 笔试 面试 腾讯一面 1.浏览器工作原理 浏览器的主要组件包括: 用户界面- 包括地址栏、后退/前...

    leap_frog 评论0 收藏0
  • [面试专题]js之面向对象(OOP)

    摘要:之面向对象对象类型数据类型分六类简单类型五种复杂类型其中也属于基本类型。 js之面向对象(OOP) js对象类型(Object) js数据类型分六类,简单类型:Undefined,Null,Bollean,Number,String五种,复杂类型:Object.其中Undefined、Null、Boolean、Number也属于基本类型。Object、Array和Function则属...

    andycall 评论0 收藏0
  • [面试专题]Vue.js 2.0 独立构建和运行时构建的区别

    摘要:独立构建和运行时构建的区别标签空格分隔未分类在使用时,有独立构建和运行时构建两种版本可供选择。运行时构建不包括模板编译,不支持选项。这就形成了独立构建编译器运行时和运行时构建仅运行时。 Vue.js 2.0 独立构建和运行时构建的区别 标签(空格分隔): 未分类 在使用 Vue.js 2.0 时,有独立构建(standalone)和运行时构建(runtime-only)两种版本可供选...

    李义 评论0 收藏0

发表评论

0条评论

李义

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<