JavaScript的原型机制

关于this
this 的指向大致可以分为:
<div id="div1">我是一个 div</div>
<script>
document.getElementById = (function (func) {
return function () {
console.log(arguments);
return func.apply(document, arguments);
}
})(document.getElementById);
var getId = document.getElementById;
var div = getId('div1');
console.log(div.id); // 输出: div1
</script>
单例模式
策略模式
<script>
//不使用策略模式:
var calculateBonus = function (performanceLevel, salary) {
if (performanceLevel === 'S') {
return salary * 4;
}
if (performanceLevel === 'A') {
return salary * 3;
}
if (performanceLevel === 'B') {
return salary * 2;
}
};
calculateBonus('B', 20000); // 输出:40000
calculateBonus('S', 6000); // 输出:24000
//使用策略模式:消除了原程序中大片的条件分支语句。
//策略类:封装了具体的算法,并负责具体的计算过程。
var strategies = {
"S": function (salary) {
return salary * 4;
},
"A": function (salary) {
return salary * 3;
},
"B": function (salary) {
return salary * 2;
}
};
//环境类Context,Context 接受客户的请求,随后把请求委托给策略类。
var calculateBonus = function (level, salary) {
return strategies[level](salary);
};
console.log(calculateBonus('S', 20000)); // 输出:80000
console.log(calculateBonus('A', 10000)); // 输出:30000
</script>
代理模式
<input type="checkbox" id="1"></input>1
<input type="checkbox" id="2"></input>2
<input type="checkbox" id="3"></input>3
<input type="checkbox" id="4"></input>4
<input type="checkbox" id="5"></input>5
<input type="checkbox" id="6"></input>6
<input type="checkbox" id="7"></input>7
<input type="checkbox" id="8"></input>8
<input type="checkbox" id="9"></input>9
<script>
var synchronousFile = function (id) {
console.log('开始同步文件,id 为: ' + id);
};
//代理函数:收集一段时间之内发的请求,再一次性发给服务器
var proxySynchronousFile = (function () {
var cache = [], // 保存一段时间内需要同步的 ID
timer; // 定时器
return function (id) {
cache.push(id);
if (timer) { // 保证不会覆盖已经启动的定时器
return;
}
timer = setTimeout(function () {
synchronousFile(cache.join(',')); // 2 秒后向本体发送需要同步的 ID 集合
clearTimeout(timer); // 清空定时器
timer = null;
cache.length = 0; // 清空 ID 集合
}, 2000);
}
})();
var checkbox = document.getElementsByTagName('input');
for (var i = 0, c; c = checkbox[i++];) {
c.onclick = function () {
if (this.checked === true) {
proxySynchronousFile(this.id);
}
}
};
</script>
用于ajax异步请求数据:在项目中遇到分页的需求,同一页的数据理论上只需要去后台拉取一次,这些已经拉取到的数据在某个地方被缓存之后,下次再请求同一页的时候,便可以直接使用之前的数据。
/**************** 计算乘积 *****************/
var mult = function () {
var a = 1;
for (var i = 0, l = arguments.length; i < l; i++) {
a = a * arguments[i];
}
return a;
};
/**************** 计算加和 *****************/
var plus = function () {
var a = 0;
for (var i = 0, l = arguments.length; i < l; i++) {
a = a + arguments[i];
}
return a;
};
/**************** 创建缓存代理的工厂 *****************/
var createProxyFactory = function (fn) {
var cache = {};
return function () {
var args = Array.prototype.join.call(arguments, ',');
if (args in cache) {
return cache[args];
}
return cache[args] = fn.apply(this, arguments);
}
};
var proxyMult = createProxyFactory(mult),
proxyPlus = createProxyFactory(plus);
alert(proxyMult(1, 2, 3, 4)); // 输出:24
alert(proxyMult(1, 2, 3, 4)); // 输出:24
alert(proxyPlus(1, 2, 3, 4)); // 输出:10
alert(proxyPlus(1, 2, 3, 4)); // 输出:10
迭代器模式
发布-订阅模式(观察者模式)
DOM事件
自定义事件:可以往回调函数里填入一些参数,订阅者可以接收这些参数。
/*11自定义事件基础版*/
var salesOffices = {}; // 定义售楼处
salesOffices.clientList = []; // 缓存列表,存放订阅者的回调函数
salesOffices.listen = function (fn) { // 增加订阅者
this.clientList.push(fn); // 订阅的消息添加进缓存列表
};
salesOffices.trigger = function () { // 发布消息
for (var i = 0, fn; fn = this.clientList[i++];) {
fn.apply(this, arguments); // (2) // arguments 是发布消息时带上的参数
}
};
// 下面我们来进行一些简单的测试:
salesOffices.listen(function (price, squareMeter) { // 小明订阅消息
console.log('价格= ' + price);
console.log('squareMeter= ' + squareMeter);
});
salesOffices.listen(function (price, squareMeter) { // 小红订阅消息
console.log('价格= ' + price);
console.log('squareMeter= ' + squareMeter);
});
salesOffices.trigger(2000000, 88); // 输出:200 万,88 平方米
salesOffices.trigger(3000000, 110); // 输出:300 万,110 平方米
/*22增加一个标示 key,让订阅者只订阅自己感兴趣的消息*/
var salesOffices = {}; // 定义售楼处
salesOffices.clientList = {}; // 缓存列表,存放订阅者的回调函数
salesOffices.listen = function (key, fn) {
if (!this.clientList[key]) { // 如果还没有订阅过此类消息,给该类消息创建一个缓存列表
this.clientList[key] = [];
}
this.clientList[key].push(fn); // 订阅的消息添加进消息缓存列表
};
salesOffices.trigger = function () { // 发布消息
var key = Array.prototype.shift.call(arguments), // 取出消息类型
fns = this.clientList[key]; // 取出该消息对应的回调函数集合
if (!fns || fns.length === 0) { // 如果没有订阅该消息,则返回
return false;
}
for (var i = 0, fn; fn = fns[i++];) {
fn.apply(this, arguments); // (2) // arguments 是发布消息时附送的参数
}
};
salesOffices.listen('squareMeter88', function (price) { // 小明订阅 88 平方米房子的消息
console.log('价格= ' + price); // 输出: 2000000
});
salesOffices.listen('squareMeter110', function (price) { // 小红订阅 110 平方米房子的消息
console.log('价格= ' + price); // 输出: 3000000
});
salesOffices.trigger('squareMeter88', 2000000); // 发布 88 平方米房子的价格
salesOffices.trigger('squareMeter110', 3000000); // 发布 110 平方米房子的价格
/*33把发布—订阅的功能提取出来,放在一个单独的对象内*/
var event = {
clientList: [],
//订阅者
listen: function (key, fn) {
if (!this.clientList[key]) {
this.clientList[key] = [];
}
this.clientList[key].push(fn); // 订阅的消息添加进缓存列表
},
//发布者
trigger: function () {
var key = Array.prototype.shift.call(arguments), // (1);
fns = this.clientList[key];
if (!fns || fns.length === 0) { // 如果没有绑定对应的消息
return false;
}
for (var i = 0, fn; fn = fns[i++];) {
fn.apply(this, arguments); // (2) // arguments 是 trigger 时带上的参数
}
}
};
//取消订阅事件
event.remove = function (key, fn) {
var fns = this.clientList[key];
if (!fns) { // 如果 key 对应的消息没有被人订阅,则直接返回
return false;
}
if (!fn) { // 如果没有传入具体的回调函数,表示需要取消 key 对应消息的所有订阅
fns && (fns.length = 0);
} else {
for (var l = fns.length - 1; l >= 0; l--) { // 反向遍历订阅的回调函数列表
var _fn = fns[l];
if (_fn === fn) {
fns.splice(l, 1); // 删除订阅者的回调函数
}
}
}
};
var installEvent = function (obj) {
for (var i in event) {
obj[i] = event[i];
}
};
var salesOffices = {};
installEvent(salesOffices);//这样可以一直赋值给多个对象(让多个售楼处都拥有该功能)
salesOffices.listen('squareMeter88', fn1 = function (price) { // 小明订阅消息
console.log('价格= ' + price);
});
salesOffices.listen('squareMeter100', function (price) { // 小红订阅消息
console.log('价格= ' + price);
});
salesOffices.remove( 'squareMeter88', fn1 ); // 删除小明的订阅
salesOffices.trigger('squareMeter88', 2000000); // 输出:2000000 (删除之后无法输出)
salesOffices.trigger('squareMeter100', 3000000); // 输出:3000000
/*44全局的发布-订阅对象(不需要一直创建多个对象,如salesOffices对象,而是使用中介的方式(全局)处理不同楼盘和买房者)*/
var Event = (function () {
var clientList = {},
listen,
trigger,
remove;
listen = function (key, fn) {
if (!clientList[key]) {
clientList[key] = [];
}
clientList[key].push(fn);
};
trigger = function () {
var key = Array.prototype.shift.call(arguments),
fns = clientList[key];
if (!fns || fns.length === 0) {
return false;
}
for (var i = 0, fn; fn = fns[i++];) {
fn.apply(this, arguments);
}
};
remove = function (key, fn) {
var fns = clientList[key];
if (!fns) {
return false;
}
if (!fn) {
fns && (fns.length = 0);
} else {
for (var l = fns.length - 1; l >= 0; l--) {
var _fn = fns[l];
if (_fn === fn) {
fns.splice(l, 1);
}
}
}
};
return {
listen: listen,
trigger: trigger,
remove: remove
}
})();
Event.listen('squareMeter88', function (price) { // 小红订阅消息
console.log('价格= ' + price); // 输出:'价格=2000000'
});
Event.trigger('squareMeter88', 2000000); // 售楼处发布消息
**使用该模式的案例:
网站登录:每次登录需要ajax异步请求获取用户的登录信息,但其他模块中需要用到登录后返回的信息,可以直接用发布-订阅模式,在登录成功时发布(trigger)一个消息,其他模块中订阅(listen)监听该消息并操作。
**未订阅先发布的情况:
有时候还没有人订阅,但是已经发布了消息,可以先建立一个存放离线事件的堆栈,暂时把发布事件的动作包裹在一个函数里,等有对象来订阅事件时再遍历堆栈并依次执行该包装函数,即重新发布事件。当然离线事件的生命周期只有一次,就像 QQ 的未读消息只会被重 新阅读一次,所以刚才的操作我们只能进行一次。
**推模型和拉模型:
- 一为时间上的解耦,二为对象之间的解耦。(楼盘有新动向时再发消息给买房者,而不需要买房者存储销售的电话,每天都询问楼盘动向【时间】。并且销售辞职或买房者是谁等对象信息也不会影响该消息传送【对象】)
- 三可以用在异步编程中,也可帮助实现一些别的设计模式,比如中介者模式。
- 四架构上来看,无论是 MVC 还是 MVVM, 都少不了发布—订阅模式的参与。
命令模式
<button id="button1">点击按钮 1</button>
<button id="button2">点击按钮 2</button>
<button id="button3">点击按钮 3</button>
<script>
var button1 = document.getElementById('button1');
var button2 = document.getElementById('button2')
var button3 = document.getElementById('button3');
//使用闭包的方式:
/*关键点:只需要预留好安装命令的接口(setCommand),不需要关心其他,于是把请求发送者和请求接收者解耦开。*/
var setCommand = function (button, func) {
button.onclick = function () {
func();
}
};
var MenuBar = {
refresh: function () {
console.log('刷新菜单目录');
}
};
var SubMenu = {
add: function () {
console.log('增加子菜单');
},
del: function () {
console.log('删除子菜单');
}
};
var RefreshMenuBarCommand = function (receiver) {
return function () {
receiver.refresh();
}
};
var refreshMenuBarCommand = RefreshMenuBarCommand(MenuBar);
setCommand(button1, refreshMenuBarCommand);
组合模式
模板方法模式
原型链上设置好对象父类,后面需要用到的时候再继承并重写,和其他语言的抽象类相似。
钩子方法:
享元模式
(减少创建的对象数量)
职责链模式
/*无使用职责链*/
var order = function (orderType, pay, stock) {
if (orderType === 1) { // 500 元定金购买模式
if (pay === true) { // 已支付定金
console.log('500 元定金预购, 得到 100 优惠券');
} else { // 未支付定金,降级到普通购买模式
if (stock > 0) { // 用于普通购买的手机还有库存
console.log('普通购买, 无优惠券');
} else {
console.log('手机库存不足');
}
}
}
else if (orderType === 2) { // 200 元定金购买模式
if (pay === true) {
console.log('200 元定金预购, 得到 50 优惠券');
} else {
if (stock > 0) {
console.log('普通购买, 无优惠券');
} else {
console.log('手机库存不足');
}
}
}
else if (orderType === 3) {
if (stock > 0) {
console.log('普通购买, 无优惠券');
} else {
console.log('手机库存不足');
}
}
};
order(1, true, 500); // 输出: 500 元定金预购, 得到 100 优惠券
/*用职责链模式重构代码*/
// 500 元订单
//缺点:请求在链条传递中的顺序非常僵硬,传递请求的代码被耦合在了业务函数之中,违反开放-封闭原则
var order500 = function (orderType, pay, stock) {
if (orderType === 1 && pay === true) {
console.log('500 元定金预购, 得到 100 优惠券');
} else {
order200(orderType, pay, stock); // 将请求传递给 200 元订单
}
};
// 200 元订单
var order200 = function (orderType, pay, stock) {
if (orderType === 2 && pay === true) {
console.log('200 元定金预购, 得到 50 优惠券');
} else {
orderNormal(orderType, pay, stock); // 将请求传递给普通订单
}
};
// 普通购买订单
var orderNormal = function (orderType, pay, stock) {
if (stock > 0) {
console.log('普通购买, 无优惠券');
} else {
console.log('手机库存不足');
}
};
// 测试结果:
order500(1, true, 500); // 输出:500 元定金预购, 得到 100 优惠券
order500(1, false, 500); // 输出:普通购买, 无优惠券
order500(2, true, 500); // 输出:200 元定金预购, 得到 500 优惠券
order500(3, false, 500); // 输出:普通购买, 无优惠券
order500(3, false, 0); // 输出:手机库存不足
/*灵活可拆分的职责链节点*/
var order500 = function (orderType, pay, stock) {
if (orderType === 1 && pay === true) {
console.log('500 元定金预购,得到 100 优惠券');
} else {
return 'nextSuccessor'; // 我不知道下一个节点是谁,反正把请求往后面传递
}
};
var order200 = function (orderType, pay, stock) {
if (orderType === 2 && pay === true) {
console.log('200 元定金预购,得到 50 优惠券');
} else {
return 'nextSuccessor'; // 我不知道下一个节点是谁,反正把请求往后面传递
}
};
var orderNormal = function (orderType, pay, stock) {
if (stock > 0) {
console.log('普通购买,无优惠券');
} else {
console.log('手机库存不足');
}
};
var Chain = function (fn) {
this.fn = fn;
this.successor = null;
};
//异步的职责链p185:数同步返回"nextSuccessor"已经没有意义了。需要添加一个原型方法,在异步触发时可以用
// Chain.prototype.next = function () {
// return this.successor && this.successor.passRequest.apply(this.successor, arguments);
// };
Chain.prototype.setNextSuccessor = function (successor) {
return this.successor = successor;
};
Chain.prototype.passRequest = function () {
var ret = this.fn.apply(this, arguments);
if (ret === 'nextSuccessor') {
return this.successor && this.successor.passRequest.apply(this.successor, arguments);
}
return ret;
};
var chainOrder500 = new Chain(order500);
var chainOrder200 = new Chain(order200);
var chainOrderNormal = new Chain(orderNormal);
chainOrder500.setNextSuccessor(chainOrder200);//职责链的连接:函数是封闭的,暴露出接口是开放的(符合封闭-开放原则)
chainOrder200.setNextSuccessor(chainOrderNormal);
// chainOrder500.passRequest(1, true, 500); // 输出:500 元定金预购,得到 100 优惠券
chainOrder500.passRequest(2, true, 500); // 输出:200 元定金预购,得到 50 优惠券
// chainOrder500.passRequest(3, true, 500); // 输出:普通购买,无优惠券
// chainOrder500.passRequest(1, false, 0); // 输出:手机库存不足
装饰者模式
/*给对象动态增加职责的方式,并没有真正地改动对象自身,而是将对象放入另一个对象
之中,这些对象以一条链的方式进行引用,形成一个聚合对象。这些对象都拥有相同的接口(fire
方法),当请求达到链中的某个对象时,这个对象会执行自身的操作,随后把请求转发给链中的
下一个对象。*/
let Plane = function() {}
Plane.prototype.fire = function() {
console.log('plane');
}
let PlaneOne = function(plane) {
this.plane=plane
}
PlaneOne.prototype.fire=function() {
this.plane.fire()
console.log('planeOne');
}
let plane = new Plane()
let planeOne = new PlaneOne(plane)
planeOne.fire()
/*但可以直接改写对象或者对象的某个方法,并不需要使用“类”来实现装饰者模式*/
var plane = {
fire: function () {
console.log('发射普通子弹');
}
}
var missileDecorator = function () {
console.log('发射导弹');
}
var atomDecorator = function () {
console.log('发射原子弹');
}
var fire1 = plane.fire;
plane.fire = function () {
fire1();
missileDecorator();
}
var fire2 = plane.fire;
plane.fire = function () {
fire2();
atomDecorator();
}
plane.fire();
fire1()
// 分别输出: 发射普通子弹、发射导弹、发射原子弹
/*11*/
//报错,this指向不对(被劫持)
var _getElementById = document.getElementById;
document.getElementById = function (id) {
alert(1);
return _getElementById(id); // (1)
}
var button = document.getElementById('button');
//改变this指向,但该做法不方便
var _getElementById = document.getElementById;
document.getElementById = function () {
alert(1);
return _getElementById.apply(document,arguments); // (1)
}
var button = document.getElementById('button');
/*22*/
//不使用AOP时新增新的onload事件,必须维护中间变量_onload(可能不止一个)
window.onload = function () {
alert(1);
}
var _onload = window.onload || function () { };
window.onload = function () {
_onload();
alert(2);
}
/*上面两者的解决方式:AOP*/
Function.prototype.before = function (beforefn) {
var __self = this;
return function () {
beforefn.apply(this, arguments);//先执行参数
return __self.apply(this, arguments);
}
}
Function.prototype.after = function (afterfn) {
var __self = this;
return function () {
var ret = __self.apply(this, arguments);
afterfn.apply(this, arguments);//后执行参数
return ret;
}
};
// var button = document.getElementById('button');
// document.getElementById = document.getElementById.before(function () {
// console.log(3);
// }).after(function () {
// console.log(1);
// });
// console.log(button);
window.onload = function () {
alert(1);
}
window.onload = (window.onload || function () { }).after(function () {
alert(2);
}).after(function () {
alert(3);
}).after(function () {
alert(4);
});
/*担心上面的方法会污染原型的话:*/
var before = function (fn, beforefn) {
return function () {
beforefn.apply(this, arguments);
return fn.apply(this, arguments);
}
}
var a = before(
function () { alert(3) },
function () { alert(2) }
);
a = before(a, function () { alert(1); });
a();
装饰者模式和代理模式的区别:
代理模式 强调一种一开始就可以被确定的关系,而装饰者模式用于一开始并不能确定对象的全部功能,在后期时通过参数再传入另一个功能函数,它可以形成一条长长的装饰链。
本文深入探讨了JavaScript中的设计模式,包括原型机制、this的指向、单例模式、策略模式、代理模式、迭代器模式、发布-订阅模式、命令模式、组合模式、模板方法模式、享元模式、职责链模式和装饰者模式。特别讨论了发布-订阅模式在异步编程和架构中的重要角色,以及装饰者模式与代理模式的区别。
2088

被折叠的 条评论
为什么被折叠?



