工厂模式
工厂模式是 js 中最常用的一种用于创建对象的设计模式,其核心就是将逻辑封装在一个函数中不暴露创建对象的具体逻辑.
es5写法
const Person = function () {
const [name, age, sex] = [...arguments];
this.name = name;
this.age = age;
this.sex = sex;
this.sayName = () => {
console.log(`我叫 ${this.name},性别 ${this.sex},今年 ${this.age}`);
}
};
const p1 = new Person('帅哥', 25, '男');
const p2 = new Person('靓女', 25, '女');
p1.sayName(); // 我叫 帅哥,性别 男,今年 25
p2.sayName(); // 我叫 靓女,性别 女,今年 25
es6写法
class Person {
constructor(name, age, sex) {
this.name = name;
this.age = age;
this.sex = sex;
}
sayName() {
console.log(`我叫 ${this.name},性别 ${this.sex},今年 ${this.age}`);
}
}
const p1 = new Person('帅哥', 25, '男');
const p2 = new Person('靓女', 25, '女');
p1.sayName(); // 我叫 帅哥,性别 男,今年 25
p2.sayName(); // 我叫 靓女,性别 女,今年 25
工厂模式的优缺点如下:
优点:能解决多个相似的问题。
缺点:不能知道对象识别的问题(对象的类型不知道)
单例模式
单例模式即一个类只能构造出唯一实例,单例模式的意义在于共享、唯一, Redux/Vuex 中的 Store、 或者业务场景中的购物车、登录框都是单例模式的应用。
ES5 写法:
const Single = function (name, password) {
this.name = name;
this.password = password;
};
Single.prototype.login = (name, password) => {
if (!this.only) {
this.only = new Single(name, password);
}
return this.only;
};
let user1 = new Single().login('xiaoming', '123456');
let user2 = new Single().login('xiaohua', '654321');
console.log(user1 === user2); // true
console.log(user1); // Single { name: 'xiaoming', password: '123456' }
console.log(user2); // Single { name: 'xiaoming', password: '123456' }
es6写法:
class SingletonLogin {
constructor(name, password) {
this.name = name;
this.password = password;
}
static getInstance(name, password) {
// 判断对象是否已经被创建,若创建则返回旧对象
if (!this.instance) {
this.instance = new SingletonLogin(name, password);
}
return this.instance;
}
}
let obj1 = SingletonLogin.getInstance('xiaoming', '123456')
let obj2 = SingletonLogin.getInstance('xiaohua', '654321')
console.log(obj1 === obj2) // true
console.log(obj1) // SingletonLogin { name: 'xiaoming', password: '123456' }
console.log(obj2) // SingletonLogin { name: 'xiaoming', password: '123456' }
代理模式和中介者模式
代理模式
代理模式强调的是个体。将原类(原方法体)进行封装,客户端只需要与代理进行交流,代理就是原类的一个替身。简而言之就是用一个对象代表另外一个对象。
比如:xiaoming 要给心动女孩送花,然后 xiaoming 不好意思,所以他摆脱他的好哥们帮忙送花,这个代理人就是好哥们。
// 心动女孩
const Girl = function () {
this.name = '心动女孩';
this.get = (person, gift) => {
console.log(`${person} 送给 ${this.name} ${gift}`);
};
}
// xiaoming
const XiaoMing = function () {
this.name = 'xiaoming';
this.send = () => {
return '99 朵玫瑰';
}
}
// 好哥们
const Friend = function (getUser) {
this.getUser = new getUser();
this.xiaoming = new XiaoMing();
this.run = () => {
this.getUser.get(this.xiaoming.name, this.xiaoming.send());
}
};
// 好哥们帮忙给不同妹子送礼物
const friend = new Friend(Girl);
friend.run(); // 最终打印:xiaoming 送给 心动女孩 99 朵玫瑰
中介者模式
那么什么是中介者模式呢?
就是当好哥们想赚外快的时候,想起他曾经帮 xiaoming 跑过腿,那么他也可以帮其他人跑腿啊,于是好哥们变成了中间商,成立了快递公司。
const Girl = function () {
this.name = '心动女孩';
this.get = (person, gift) => {
console.log(`${person} 送给 ${this.name} ${gift}`);
};
}
// xiaoming
const XiaoMing = function () {
this.name = 'xiaoming';
this.send = () => {
return '99 朵玫瑰';
}
}
// 快递公司
const Express = function (postUser, getUser) {
this.postUser = new postUser();
this.getUser = new getUser();
this.run = () => {
this.getUser.get(this.postUser.name, this.postUser.send());
}
};
// 快递员
const courier = new Express(XiaoMing, Girl);
courier.run(); // 最终打印:xiaoming 送给 心动女孩 99 朵玫瑰
假设xiaoming 喜欢的女孩不在广州,那么就需要别人 “帮忙” 将礼物送给异地 girl。
而这种模式,就是中介者模式,作为一个中间人传递信息。
从中可以看出代理模式的好处:
代理对象可以代替本体被实例化,并使其可以被远程访问
可以把本体实例化推迟到真正需要的时候。
当然,有好就有坏,在代码中使用代理模式,这个中介会承担较多的责任,一旦中介(快递员)病了,那么你的花就送达不了了。
中介者使用场景:
var goods = {
// 手机库存
"red|32G": 3,
"red|64G": 1,
"blue|32G": 7,
"blue|32G": 6,
};
// 中介者
var mediator = (function () {
var colorSelect = document.getElementById("colorSelect");
var memorySelect = document.getElementById("memorySelect");
var numSelect = document.getElementById("numSelect");
return {
changed: function (obj) {
switch (obj) {
case colorSelect:
// TODO
break;
case memorySelect:
// TODO
break;
case numSelect:
// TODO
break;
}
},
};
})();
colorSelect.onchange = function () {
mediator.changed(this);
};
memorySelect.onchange = function () {
mediator.changed(this);
};
numSelect.onchange = function () {
mediator.changed(this);
};
发布订阅模式
发布-订阅模式又叫观察者模式,它定义了对象间的一种一对多的关系,让多个观察者对象同时监听某一个主题对象,当一个对象发生改变时,所有依赖于它的对象都将得到通知。
看完定义有点懵?
现实生活中的发布-订阅模式,就好比 你 在网上看中一双鞋子,但是没货了,于是点击(订阅)了卖家的发货通知。等卖家有货之后,所有点击(订阅)了发货通知的买家,都会看到通知。
发布-订阅模式的优缺点:
- 优点
1.支持简单的广播通信。当对象状态发生改变时,会自动通知已经订阅过的对象,例如上面的举例。
2.发布者与订阅者的耦合性降低。我是卖家我并不关心有多少人订阅了,我只要到货的时候,将货物数量更改下就行了。 - 缺点
1.耗时耗内存。创建订阅者需要消耗一定的时间和内存
2.过度使用不好维护。
介绍Vue 中的 Object.defineProperty 和 Proxy 使用:
// 定义发布者
const seller = {};
// 缓存列表:存放订阅者的回调函数
seller.buyerList = [];
// 订阅方法
seller.listen = (user, fn) => {
// 如果有人订阅了,那就将它存放到缓存列表中
seller.buyerList.push(fn);
console.log(`亲爱的 ${user},你已经订阅了新鞋发布`);
}
// 发布信息
seller.trigger = () => {
const buyerList = seller.buyerList;
for (let i in buyerList) {
if (buyerList.hasOwnProperty(i)) {
buyerList[i].call(this);
}
}
}
const buyer1 = seller.listen('xiaoming', () => console.log('颜色是黑色,尺码是 43 码')); // 亲爱的 xiaoming,你已经订阅了新鞋发布
const buyer2 = seller.listen('xiaozhang', () => console.log('颜色是红色,尺码是 44 码')); // 亲爱的 xiaozhang,你已经订阅了新鞋发布
// 假设 2 秒后到货
setTimeout(() => {
seller.trigger();
}, 2000);
// 颜色是黑色,尺码是 43 码
// 颜色是红色,尺码是 44 码
这样就实现了简单的发布-订阅模式,如果卖家有不同商品,两个用户分别关注两个不同商品要怎么实现呢?小伙伴们可以思考思考。
Object.defineProperty 和 Proxy区别:
在 Vue 2.0 版本,我们通过 Object.defineProperty 来观察数据:
const person = {
name: 'xiaoming',
age: 25,
};
Object.keys(person).forEach((key) => {
Object.defineProperty(person, key, {
enumerable: true,
configurable: true,
get: () => {
console.log('get');
},
set: (newName) => {
console.log(newName); //打印xiaozhang
},
});
});
person.name = 'xiaozhang'; // xiaozhang
console.log(person); // { name: [Getter/Setter], age: [Getter/Setter] }
解释:
- enumerable:对象属性是否可通过 for-in 循环,false 为不可循环,默认值为 true。
- configurable:能否使用 ·、能否需改属性特性、或能否修改访问器属性,false 为不可重新定义,默认值为 true。
我们再来看看 Proxy:
const queuedObserver = new Set();
const observe = fn => queuedObserver.add(fn);
const observeable = obj => new Proxy(obj, {
set
});
const set = function (target, key, value, receiver) {
const result = Reflect.set(target, key, value, receiver);
queuedObserver.forEach(observer => observer());
return result;
};
const person = observeable({
name: 'xiaoming',
age: 25,
});
const print = () => {
console.log(`姓名:${person.name},年龄:${person.age}`);
}
observe(print);
person.name = 'xiaozhang'; // 姓名:zhazhaliang,年龄:25
那么,为什么 Vue 3.0 要换成 Proxy 呢?(面试常考)
Object.defineProperty 缺点:
因为:
1.不能监听数组变化
2.必须遍历对象每个属性,而 Proxy 的目标是对象,这样就省去了属性的遍历。
最后,需要指正一点的是,ES6 Proxy 在模式上属于代理模式。
日常工作中,常见的观察者模式有:
Promise
Vue 的 Watch 声明周期钩子