设计模式(Design pattern)代表了最佳的实践,通常被有经验的面向对象的软件开发人员所采用。设计模式是软件开发人员在软件开发过程中面临的一般问题的解决方案。这些解决方案是众多软件开发人员经过相当长的一段时间的试验和错误总结出来的。
而虽然js前端使用设计模式不像后端那么频繁,但是依然是一名程序员必备的本领,本文通过使用实例方便大家理解记忆设计模式,可用于面试前速记。
一. 设计模式介绍
在js设计模式中,核心:封装变化。
将变与不变分离,确保变化灵活,不变稳定。
var employee1 = {
name:"zs",
age:18
}
var employee2 = {
name:"ls",
age:19
}
在构造对象时,如果数据量变多,对象的代码会变得冗余臃肿
使用设计模式后:
function Employee(name,age){
this.name = name;
this.age =age;
this.say = function(){
console.log(this.name+"-",this.age)
}
}
new Employee("zs",18)
new Employee("ls",19)
function UserFactory(role){
function User(role,pages){
this.role = role;
this.pages = pages;
}
switch(role){
case "superadmin":
return new User("superadmin",["home","user-manage","right-manage","news-manage"])
break;
case "admin":
return new User("admin",["home","user-manage","news-manage"])
break;
case "editor":
return new User("editor",["home","news-manage"])
break;
default:
throw new Error('参数错误')
}
}
class User {
constructor(name) {
this.name = name
}
welcome() {
console.log("欢迎回来", this.name)
}
dataShow() {
throw new Error("抽象方法不允许直接调用")
}
}
class Editor extends User {
constructor(name) {
super(name)
this.role = "editor"
this.pages = ["home", "news-manage"]
}
dataShow() {
console.log("editor的可视化逻辑")
}
}
class Admin extends User {
constructor(name) {
super(name)
this.role = "admin"
this.pages = ["home", "user-manage", "news-manage"]
}
dataShow() {
console.log("admin的可视化逻辑")
}
AddUser() {
console.log("adduser方法")
}
}
class SuperAdmin extends User {
constructor(name) {
super(name)
this.role = "superadmin"
this.pages = ["home", "user-manage", "right-manage", "news-manage"]
}
dataShow() {
console.log("superadmin的可视化逻辑")
}
AddUser() {
console.log("adduser方法")
}
AddRight() {
console.log("addright方法")
}
}
function getAbstractUserFactory(role) {
switch (role) {
case 'superadmin':
return SuperAdmin;
break;
case 'admin':
return Admin;
break;
case 'editor':
return Editor;
break;
default:
throw new Error('参数错误')
}
}
class Navbar {//没一个类的细节都不同
init() {
console.log("navbar-init")
}
getData() {
return new Promise((resolve) => {
setTimeout(() => {
resolve()
console.log("navbar-getData")
}, 1000)
})
}
render() {
console.log("navbar-render")
}
}
class List {
init() {
console.log("List-init")
}
getData() {
return new Promise((resolve) => {
setTimeout(() => {
resolve()
console.log("List-getData")
}, 1000)
})
}
render() {
console.log("List-render")
}
}
class Operator {
async startBuild(builder) {//设计师只关心这个函数中代码的运行逻辑
await builder.init()
await builder.getData()
await builder.render()
}
}
const op = new Operator();
const navbar = new Navbar();
const list = new List();
op.startBuild(navbar);
op.startBuild(list);
const Modal = (function () {
let modal = null
return function () {//返回一个类,但是由于modal变量访问了外层作用域所以缓存不会立即释放
if (!modal) {
modal = document.createElement('div')
modal.innerHTML = '登录对话框'
modal.className = 'kerwin-modal'
modal.style.display = 'none'
document.body.appendChild(modal)
}
return modal
}
})()
document.querySelector('#open').addEventListener('click', function () {
const modal = new Modal()
modal.style.display = 'block'
})
document.querySelector('#close').addEventListener('click', function () {
const modal = new Modal()
modal.style.display = 'none'
})
Function.prototype.before = function (beforeFn) {
var _this = this;
return function () {
beforeFn.apply(this, arguments);
return _this.apply(this, arguments);
};
};
Function.prototype.after = function (afterFn) {
var _this = this;//函数体本省
return function () {//返回一个新的函数
var ret = _this.apply(this, arguments);
afterFn.apply(this, arguments);
return ret;
};
};
function test() {
console.log("11111")
}
var test1 = test.before(() => {
console.log("00000")
}).after(()=>{
console.log("22222")
})
test1()
//按照官网代码复制
class TencentMap {
show() {
console.log('开始渲染腾讯地图');
}
}
//按照官网代码复制
class BaiduMap {
display() {
console.log('开始渲染百度地图');
}
}
class BaiduMapAdapter extends BaiduMap {
constructor() {
super();
}
render() {
this.display();
}
}
class TencentMapAdapter extends TencentMap {
constructor() {
super();
}
render() {
this.show();
}
}
// 外部调用者
function renderMap(map) {
map.render(); // 统一接口调用
}
renderMap(new TencentMapAdapter());
renderMap(new BaiduMapAdapter());
var list = [{
title: "男人看了沉默",
type: 1
},
{
title: "震惊",
type: 2
},
{
title: "kerwin来了",
type: 3
},
{
title: "tiechui离开了",
type: 2
}
]
let obj = {
1: {
content: "审核中",
className: "yellowitem"
},
2: {
content: "已通过",
className: "greenitem"
},
3: {
content: "被驳回",
className: "reditem"
}
}
mylist.innerHTML = list.map(item =>
`
${item.title}
${obj[item.type].className}">${obj[item.type].content}
`).join("")
let obj = {}
let proxy = new Proxy(obj,{
get(target,key){
console.log("get",target[key])
return target[key]
},
set(target,key,value){
console.log("set",target,key,value)
if(key==="data"){
box.innerHTML = value
}
target[key] = value
}
})
class Sub {
constructor() {
this.observers = []
}
add(observer) {
this.observers.push(observer)
}
remove(observer) {
this.observers = this.observers.filter(item => item !== observer)
}
notify() {
this.observers.forEach(item => item.update())
}
}
class Observer {
constructor(name) {
this.name = name
}
update() {
console.log("通知了", this.name)
}
}
const observer1 = new Observer("kerwin")
const observer2 = new Observer("tiechui")
const sub = new Sub()
sub.add(observer1)
sub.add(observer2)
setTimeout(() => {
sub.notify()
}, 2000)
class SubPub {
constructor() {
this.message = {}//记录本
}
subscribe(type, fn) {
if (!this.message[type]) {
this.message[type] = [fn]
} else {
this.message[type].push(fn)
}
}
publish(type, ...arg) {
if (!this.message[type]) return
const event = {
type: type,
arg: arg || {}
}
// 循环执行为当前事件类型订阅的所有事件处理函数
this.message[type].forEach(item => {
item.call(this, event.arg)
})
}
}
与观者不同的是,订阅的是事件而非对象,而且有不同的记录本,记录不同的事
14. 模块化模式
模块化模式最初被定义为在传统软件工程中为类提供私有和公共封装的一种方法。
能够使一个单独的对象拥有公共/私有的方法和变量,从而屏蔽来自全局作用域的特殊部分。这可以减少我们的函数名与在页面中其他脚本区域内定义的函数名冲突的可能性。
早前模块化是由闭包完成
var testModule = (function () {
var count = 0;
return {
increment () {
return ++count;
},
reset: function () {
count = 0;
},
decrement(){
return --count;
}
};
})();
模块化
export default {
name:"moduleA",
test(){
return "test"
}
}
<script type="module">
import moduleA from './1.js'
console.log(moduleA)
</script>
// 统一遍历接口实现
var kerwinEach = function (arr, callBack) {
for (let i = 0; i < arr.length; i++) {
callBack(i, arr[i])
}
}
// 外部调用
kerwinEach([11, 22, 33, 44, 55], function (index, value) {
console.log([index, value]);
var oli =document.createElement("li")
oli.innerHTML = value
list.appendChild(oli)
})
btn.addEventListener("click", function (event) {
checks.check()
});
function checkEmpty() {
if (input.value.length == 0) {
console.log("这里不能为空");
return
}
return "next"
}
function checkNumber() {
if (Number.isNaN(+input.value)) {
console.log("这里必须是数字");
return
}
return "next"
}
function checkLength() {
if (input.value.length < 6) {
console.log("这里要大于6个数字");
return
}
return "next"
}
class Chain {
constructor(fn) {
this.checkRule = fn || (() => "next");
this.nextRule = null;
}
addRule(nextRule) {
this.nextRule = new Chain(nextRule);
return this.nextRule;//不断返回实例,实现链式调用
}
end() {
this.nextRule = {
check: () => "end"
};
}
check() {
this.checkRule() == "next" ? this.nextRule.check() : null;
}
}
const checks = new Chain();
checks.addRule(checkEmpty).addRule(checkNumber).addRule(checkLength).end();
最后还有一种不算设计模式但是非常有用的模式,名为函数柯里化
试想一下你要打印一个日志,它的内容是时间,类型,以及内容
很明显时间在一天中日期是不会变的,类型也就那么几种,唯独内容每次都不一样
但是前面两个参数,只是不会频繁变化,但却不能完全写死。
这时需要一个容器,记住前面两个参数。大家可能会想到使用Object对象,但是不要忘了函数也是对象,合理利用函数闭包,完全可以优雅的解决这个棘手的问题。
function logs(x){
return function(y){
return function(z){
console.log(`时间:${x},类型:${y},内容:${z}`)
}
}
}
let logDate=logs(new Date().toLocaleDateString())//这个容器保存当天时间
let logDateError=logDate('error')//定义不同类型
let logDateSuccess=logDate('success')
let message1=logDateError('错误信息1')//填写完全不同的内容
let message1=logDateError('错误信息2')
let message2=logDateSuccess('成功信息1')
这样写logs函数大家可能担心陷入回调地狱,凡是关于回调地狱都可以使用递归解决!
自动封装函数:
function currying(fn) {
let curryFn = function (...args) {
if (args.length >= fn.length) {
return fn(...args)
} else {
return function (...newArgs) {
return curryFn(...args, ...newArgs)
}
}
}
return curryFn
}
function test(a, b, c) {
console.log(a, b, c)
}
var testCurrying = currying(test)//放入该函数即可自动生成
testCurrying(1)(2)(3)