工厂模式
工厂模式是我们最常用的实例化对象模式了,是用工厂方法代替new操作的一种模式。
因为工厂模式就相当于创建实例对象的new,我们经常要根据类Class生成实例对象,如A a=new A() 工厂模式也是用来创建实例对象的,所以以后new时就要多个心眼,是否可以考虑使用工厂模式,虽然这样做,可能多做一些工作,但会给你系统带来更大的可扩展性和尽量少的修改量。
var lev=function(){
return "嘿哈";
};
function Parent(){
var Child=new Object();
Child.name="李小龙";
Child.age=20;
Child.lev=lev;
return Child;
};
var x=Parent();
alert(x.name);//李小龙
alert(x.lev());//嘿哈
说明:
1.建议将属性为方法的属性定义到函数之外,这样可以避免重复创建该方法
2.在引用该对象的时候,使用var x=Parent(),不建议使用var x=new object();因为用了new会出现很多问题。
3.在函数的最后返回该对象
.js构造函数模式
var lev=function(){
return "嘿哈";
};
function Parent(){
this.name ="李小龙";
this.age =20;
this.lev=lev;
};
var x=Parent();
alert(x.name);//李小龙
alert(x.lev());//嘿哈
说明:
1.与工厂方式相比,使用构造函数方式创建对象无需在函数内部创建对象,而使用this指代,并而函数无需明确return
2.同工厂模式一样,虽然属性的值可以为方法,仍建议该方法定义在函数之外
js原型模式
var lev=function(){
return "嘿哈";
};
function Parent(){
Parent.prototype.name ="李小龙";
Parent.prototype.age =20;
Parent.prototype.lev=lev;
};
var x=Parent();
alert(x.name);//李小龙
alert(x.lev());//嘿哈
说明:
1.函数中不对属性进行定义
2.利用prototype属性对属性进行定义
构造函数+原型的js混合模式(推荐)
function Parent(){
this.name ="李小龙";
this.age =20;
};
Parent.prototype.lev= function(){
return this.name;
}
var x = Parent();
alert(x.name);
alert(x.lev());
说明:
1.该模式是指混合搭配使用构造函数和原型方式。
2.将所有的属性,不是方法的定义在函数中,将所有属性值为方法的利用prototype在函数之外定义
构造函数+原型的动态原型模式(推荐)
function Parent(){
this.name ="李小龙";
this.age =20;
if(typeof Parent.lev=="undefined"){
Parent.prototype.lev= function(){
return this.name;
}
Parent.lev=true;
}
};
var x = Parent();
alert(x.lev());
说明:
1.动态原型方式可以理解为混合构造函数,原型方式的一个特例
2.该模式中,属性为方法的属性直接在函数中进行了定义,但是因为
if(typeof Parent.lev=="undefined"){
Parent.prototype.lev= function(){
return this.name;
}
Parent.lev=true;
}
从而保证创建该对象的实例时,属性的方法不会被重复的创建
订阅/发布模式(subscribe & publish)
text属性变化了,set方法触发了,但是文本节点的内容没有变化。 如何才能让同样绑定到text的文本节点也同步变化呢? 这里又有一个知识点: 订阅发布模式。
订阅发布模式又称为观察者模式,定义了一种一对多的关系,让多个观察者同时监听某一个主题对象,这个主题对象的状态发生改变时就会通知所有的观察者对象。
发布者发出通知 =>主题对象收到通知并推送给订阅者 => 订阅者执行相应的操作。
// 一个发布者 publisher,功能就是负责发布消息 - publish
var pub = {
publish: function () {
dep.notify();
}
}
// 多个订阅者 subscribers, 在发布者发布消息之后执行函数
var sub1 = {
update: function () {
console.log(1);
}
}
var sub2 = {
update: function () {
console.log(2);
}
}
var sub3 = {
update: function () {
console.log(3);
}
}
// 一个主题对象
function Dep() {
this.subs = [sub1, sub2, sub3];
}
Dep.prototype.notify = function () {
this.subs.forEach(function (sub) {
sub.update();
});
}
// 发布者发布消息, 主题对象执行notify方法,进而触发订阅者执行Update方法
var dep = new Dep();
pub.publish();
思路: 发布者负责发布消息、 订阅者负责接收接收消息,而最重要的是主题对象,他需要记录所有的订阅这特消息的人,然后负责吧发布的消息通知给哪些订阅了消息的人。
所以,当set方法触发后做的第二件事情就是作为发布者发出通知: “我是属性text,我变了”。 文本节点作为订阅者,在接收到消息之后执行相应的更新动作。
策略模式
策略模式是指将策略(算法)封装起来,策略的目的是将算法和使用分离开。
我们假设有这样一个应用:
年终绩效评定,有S,A,B,C四种绩效,对应年终奖为4,3,2,1个月工资,设计函数,计算绩效。函数如下:
function calBonus(grade, salary) {
if (grade === 'S') {
return salary*4;
} else if (grade === 'A') {
return salary*3;
} else if (grade === 'B') {
return salary*2;
} else {
return salary;
}
}
函数里各种判断条件,如果算法实现比较复杂,这个函数会异常庞大。我们用策略模式改造函数可如下:
//计算绩效为S的年终
function S(salary) {
return salary * 4;
}
//策略使用
function calBonus(fn, salary) {
return fn(salary);
}
//计算绩效为S,工资为20000的年终
calBonus(S, 20000);
这种方法改写代码后,代码的扩展性就好了一些,实际的算法由函数封装起来,代码的扩展性强了,也方便复用。
这就是策略模式的应用之一,在代码中,将具体的算法或策略封装起来,和使用的场景分离,可以提高代码扩展性和复用率。
代理模式
代理模式很好理解,我们不能直接使用目标函数,而是通过调用代理函数来实现对目标函数的使用。
对于网络代理这个词语,每个同学应该都了解,就是无法直接上网,把上网的请求都发送到代理服务器,由代理服务器请求数据,然后转发给相应人员。
现在有一个场景,某个网页有很多请求,有部分请求是不被允许的,我们使用代理模式实现这一功能。代码如下:
//允许的请求地址
let urlArr = ['/aaa', '/bbb'];
//正常的ajax请求函数,url请求地址,method请求方法, data请求数据,callback回调函数
function commonAjax(url, method, data, callback) {
//具体实现略过,这个大家应该都知道
}
//代理请求函数
function proxyAjax(url, method, data, callback) {
if (urlArr.indexOf(url) > -1) {
commonAjax(url, method, data, callback);
} else {
let data = {
status: false,
message: '该请求不被允许',
code: 403
}
callback(data);
}
}
代码解析:urlArr是我们允许的请求地址,其它请求不允许发送,commonAjax是正常的请求函数,proxyAjax是代理请求函数。
注意:代理接口和实际函数接口要保持一致。
后续需求发生变化,不需要限制任何请求,直接调用请求函数即可。现在大部分公司联网都是有限制的,过滤一部分网站,只有公司白名单里的网站才能访问,其功能和上述代码逻辑是一致的。
单例模式
单例模式就是一个实例在整个网页的生命周期里只创建一次,后续再调用实例创建函数的时候,返回的仍是之前创建的实例。在实际开发中应用十分广泛,例如页面中的登录框,显示消息的提示窗,都只需要一个实例即可。
我们以消息提示框为例,展示如何实现单例模式。代码如下:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>singleTon</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
/* 提示框动画和样式 */
@keyframes appear{
from {
left: 100%;
}
to {
left: 50%;
}
}
.message-tip{
animation: appear .5s linear;
position: fixed;
width: 200px;
background: red;
color: #fff;
line-height: 1.5;
padding-left: 5px;
top: 20px;
left: 50%;
border: 1px solid #d3d3d3;
transform: translateX(-50%);
}
</style>
</head>
<body>
<button id="tipsBtn" onclick="showMsg()">提示消息</button>
<script>
//创建提示框单例
let createTips = (function(){
let tipDom = null;
function createDom() {
let div = document.createElement('div');
div.setAttribute('class', 'message-tip');
return div;
}
return function(msg) {
if (!tipDom) {
tipDom = createDom();
}
tipDom.innerHTML = msg;
return tipDom;
}
})();
//验证是够是单例,打印true,则是单例模式
console.log(createTips('1') === createTips('2'));
function showMsg() {
let tips = createTips('这是消息提示');
document.body.appendChild(tips);
setTimeout(() => {
document.body.removeChild(tips);
}, 5000);
}
</script>
</body>
</html>
代码讲解:使用createTips函数创建的div时,如果原先已经创建了div,则直接调用原来的div,不再重新创建。除了提示框,网页中一些登录框等等也可以用单例模式实现,网页的整个生命周期里,只有一个dom被创建。或者js的代码中的某个实例如果共享的,只需要一次,就使用单例模式创建。window对象也是一个单例,整个生命周期里只有一个window对象。