面向?qū)ο?/h2>
鏈?zhǔn)秸{(diào)用
-
在原型上添加方法
//類式定義
//**區(qū)別**
Function.prototype.addMethods = function (name, fn) {
this.prototype[name]=fn;
return this;
}
//添加方法
var Methods = function(){};
Methods.addMethods('checkName', function () {})
.addMethods('checkEmail', function () {});
//使用
//**區(qū)別**
var md = new Methods();
md.checkName();
-
在對(duì)象本身添加方法
//函數(shù)式定義
Function.prototype.addMethods = function (name, fn) {
//**區(qū)別**
this[name]=fn;
return this;
}
//添加方法
var methods = function(){};
Methods.addMethods('checkName', function () {})
.addMethods('checkEmail', function () {});
//使用
//**區(qū)別**
methods.checkName();
在原型上添加方法
//類式定義
//**區(qū)別**
Function.prototype.addMethods = function (name, fn) {
this.prototype[name]=fn;
return this;
}
//添加方法
var Methods = function(){};
Methods.addMethods('checkName', function () {})
.addMethods('checkEmail', function () {});
//使用
//**區(qū)別**
var md = new Methods();
md.checkName();
在對(duì)象本身添加方法
//函數(shù)式定義
Function.prototype.addMethods = function (name, fn) {
//**區(qū)別**
this[name]=fn;
return this;
}
//添加方法
var methods = function(){};
Methods.addMethods('checkName', function () {})
.addMethods('checkEmail', function () {});
//使用
//**區(qū)別**
methods.checkName();
區(qū)別:在原型上定義,多個(gè)實(shí)例共享一個(gè)方法;在對(duì)象上定義,每個(gè)實(shí)例復(fù)制一份方法,易造成資源浪費(fèi),產(chǎn)生多個(gè)副本。調(diào)用方式也不一樣
封裝
-
靜態(tài)方法/私有方法/公有方法
var Book = function (id, name, price) { //私有變量 var num = 1; //私有方法 function checkId(){}; //公有變量和方法 this.id = id; this.getName = function (){}; this.copy = function (){}; } //靜態(tài)方法,通過類訪問 `Book.belonged` `Book.resetTime()` //只能訪問靜態(tài)屬性,不能訪問私有變量和對(duì)象變量 Book.belonged = 'Black Shop'; Book.resetTime = function (){}; //原型方法,通過實(shí)例化訪問 //var b = new Book(1, 'You don't know JS', '$100'); //b.isChineseBook; b.display() Book.prototype.isChineseBook = true; Book.prototype.display = function (){};
-
閉包
有權(quán)訪問另一個(gè)作用域的變量的函數(shù),函數(shù)內(nèi)返回函數(shù)。
var Book = (function () { var num = 0; return function (id, name, price) { num++; } })();
繼承
-
類式繼承
缺點(diǎn):不能向父類傳遞參數(shù),并且父類屬性共用,改一發(fā)而動(dòng)所有實(shí)例
var SuperClass = function () {} var SubClass = function () {} SubClass.prototype = new SuperClass();
-
構(gòu)造函數(shù)繼承
缺點(diǎn):父類的原型方法不能被子類繼承,如果在此方法下要被繼承,就要放在構(gòu)造函數(shù)里,但每次創(chuàng)建實(shí)例都會(huì)拷貝一份,違背代碼復(fù)用原則。
var SuperClass = function (id) { this.id = id; } var SubClass = function (id){ SuperClass.call(this, id); }
-
組合繼承(方法和屬性分別繼承)
var SuperClass = function (id) { this.id = id; } SuperClass.prototype.getId = function () { return this.id; } var SubClass = function (id){ SuperClass.call(this, id); } SubClass.prototype = new SuperClass();
寄生式繼承
...
多態(tài)
一個(gè)函數(shù)多種調(diào)用方法,自適應(yīng)參數(shù)
創(chuàng)建型設(shè)計(jì)模式
工廠系列方法 [簡(jiǎn)單工廠/工廠方法/抽象工廠]
區(qū)別:對(duì)于分類的復(fù)雜度不同。簡(jiǎn)單工廠把各分類拆分到各類中去;工廠方法則將各分類集成到工廠方法本身里;抽象工廠可用于進(jìn)一步分類,在工廠中創(chuàng)建產(chǎn)品類簇,再使產(chǎn)品子類去繼承產(chǎn)品類簇。
// 抽象工廠方法例子
建造者模式
相比工廠模式,關(guān)注創(chuàng)建過程。如:人要關(guān)注穿的衣服,性別和愛好等,分別處理。
var Named = function (name) {
this.wholeName = name;
var that = this;
(function (name) {
const [firstName, lastName] = name.split(' ');
if (lastName) {
that.firstName = firstName;
that.lastName = lastName;
}
})(name)
}
var Person = function (name, skill) {
//存儲(chǔ)基類信息
var _person = new Human();
//關(guān)注過程處理
_person.name = new Named(name);
_person.skill = new Skills(skill);
return _person;
}
原型模式
將可復(fù)用,可共享,耗時(shí)大的從基類中提取出來,放到原型中,通過組合/寄生繼承復(fù)用,對(duì)需要重寫的進(jìn)行重寫。
//例子
單例模式
用于管理命名空間和私有變量的私密性
var A = {
Tool: {
tool_1: function (){},
},
Util: {
util_1: function (){},
util_2: function (){},
}
}
A.Tool.tool_1();
A.Util.util_2();
結(jié)構(gòu)型設(shè)計(jì)模式
外觀模式
用途:兼容底層接口
//瀏覽器兼容設(shè)計(jì)案例
function addEvent(dom, type, fn) {
// 對(duì)于支持DOM2級(jí)事件處理程序的瀏覽器
if(dom.addEventListener) {
dom.addEventListener(type, fn, false);
// 對(duì)于不支持addEventListener但支持attachEvent的瀏覽器
} else if(dom.attachEvent) {
dom.attachEvent('on' + type, fn);
} else {
dom['on' + type] = fn;
}
}
適配器
用途:框架之間、方法參數(shù)之間、數(shù)據(jù)適配、服務(wù)器端 數(shù)據(jù)適配
框架之間:在 A 框架基礎(chǔ)上引入 B 框架,則把 B 框架的方法適配到 A 上;
方法參數(shù):例子為眾插件
//ES6 參數(shù)賦值寫法
function objAdaptor({name = '', title = '設(shè)計(jì)模式', author = 'Xiao Ming'}) {}
數(shù)據(jù)適配: 例如數(shù)組轉(zhuǎn)對(duì)象
function arrAdaptor(arr) {
// ES6 賦值解構(gòu)寫法
const [name, type, title, data] = arr;
const obj = {name, type, title, data};
return obj;
}
服務(wù)器端數(shù)據(jù)適配:盡早 對(duì)接口返回格式化成前端組件需要的格式
代理模式
用途:跨域,站長(zhǎng)統(tǒng)計(jì),jsonP。解決耦合度和資源開銷的問題,統(tǒng)一處理非同域網(wǎng)絡(luò)訪問。在兩個(gè)不能互通的對(duì)象之間起到 中介 作用。
//動(dòng)態(tài)加載 script
window.onload = function () {
var url = 'XXX';
var scp = document.createElement('script');
scp.src = url;
document.body.appendChild(scp);
}
//圖片預(yù)覽例子
裝飾者模式
包裝原有實(shí)現(xiàn),與適配器的 區(qū)別 是不需要了解原有實(shí)現(xiàn),只需要原封不動(dòng)的調(diào)用。即 封裝拓展
// 裝飾者
var decorator = function(input, fn) {
// 獲取事件源
var input = document.getElementById(input);
// 若事件源已經(jīng)綁定事件
if(typeof input.onclick === 'function') {
// 緩存事件源原有回調(diào)函數(shù)
var oldClickFn = input.onclick;
// 為事件源定義新的事件
input.onclick = function() {
// 事件源原有回調(diào)函數(shù)
oldClickFn();
// 執(zhí)行事件源新增回調(diào)函數(shù)
fn();
}
} else {
input.onclick = fn;
}
}
橋接模式
提取變化的單元,如果是橋接方法,則把變化主體通過參數(shù)傳遞到單元內(nèi);如果是把變化的單元集合到橋接類中,則把變化主體直接設(shè)置為 this
。抽象實(shí)現(xiàn)層(元素綁定事件)和抽象層(修飾頁(yè)面邏輯 UI)。
//橋接方法
var changeColor = function (dom, color) {
dom.style.color = color;
}
XXX.onmouseover = function () {
changeColor(this.getElementsByTagName('strong')[0], 'red');
}
//橋接類
class Person {
constructor(x,y,f) {
// Speed 和 Speak 為多維變量
this.speed = new Speed(x, y);
this.font = new Speak(f);
}
move() {
this.speed.run();
this.font.say();
}
}
組合模式
拆分各模塊,通過 容器+成員類 的形式進(jìn)行組合。組合要有容器類。容器類和成員類均繼承同一基類,但成員類不能擁有子成員。
//表單組合 缺點(diǎn):代碼好多呀 w(?Д?)w
//所有表單 容器和成員類 均繼承基類
class Base {
constructor() {
this.element = null;
this.children = [];
}
add() {throw Error('Should rewrite this function.');}
init() {throw Error('Should rewrite this function.');}
getElement() {throw Error('Should rewrite this function.');}
}
/*容器類*/
class FormItem extends Base{
constructor(id, parent) {
super();
this.id = id;
this.parent =parent;
this.init();
}
init() {
this.element =document.createElement('form');
this.element.id = this.id;
this.element.className = 'form-item';
}
add(child){
this.children.push(child);
this.element.appendChild(child.getElement());
return this;
}
getElement() { return this.element; }
show() { this.parent.appendChild(this.element); }
}
class Group extends Base{
constructor() {
super();
this.init();
}
init() {
this.element = document.createElement('ul');
this.element.className = "group-item";
}
add(child){
this.children.push(child);
var liItem =document.createElement('li');
var childItem = liItem.appendChild(child.getElement());
this.element.appendChild(childItem);
return this;
}
getElement() { return this.element; }
}
class FieldsetItem extends Base{
constructor(id, name) {
super();
this.id = id;
this.name = name;
this.init();
}
init() {
var fieldsetDom = document.createElement('fieldset');
fieldsetDom.id = this.id;
fieldsetDom.className = 'fieldset-item';
var legendDom =document.createElement('legend');
legendDom.innerHTML = this.name;
fieldsetDom.appendChild(legendDom);
this.element = fieldsetDom;
}
add(child){
this.children.push(child);
this.element.appendChild(child.getElement());
return this;
}
getElement() { return this.element; }
}
/*成員類*/
class LabelItem extends Base {
constructor(id, name) {
super();
this.id = id;
this.name = name;
this.init();
}
init() {
this.element = document.createElement('label');
// 不能使用 for 關(guān)鍵字
//this.element.for = this.id;
//this.element.setAttribute("for",this.id);
this.element.htmlFor = this.id;
this.element.innerHTML = this.name;
}
getElement() { return this.element; }
}
class InputItem extends Base {
constructor(id) {
super();
this.id = id;
this.init();
}
init() {
this.element = document.createElement('input');
this.element.name = this.id;
this.element.id = this.id;
this.element.type = 'text';
}
getElement() { return this.element; }
}
class SpanItem extends Base{
constructor(content) {
super();
this.content = content;
this.init();
}
init() {
this.element = document.createElement('span');
this.element.innerHTML = this.content;
}
getElement() { return this.element; }
}
var form = new FormItem('FormItem', document.body);
form.add(
new FieldsetItem('account', '賬號(hào)').add(
new Group().add(
new LabelItem('user_name', '用戶名')
).add(
new InputItem('user_name')
).add(
new SpanItem('4-6位數(shù)字或字母')
)
).add(
new Group().add(
new LabelItem('user_password','密 碼')
).add(
new InputItem('user_password')
).add(
new SpanItem('6-12位數(shù)字或字母')
)
)
).add(
new FieldsetItem('information', '信息').add(
new Group().add(
new LabelItem('nickname', ' 昵稱')
).add(
new InputItem('nickname')
)
).add(
new Group().add(
new LabelItem('status','狀態(tài)')
).add(
new InputItem('status')
)
)
)
.show();
享元模式(共享模式)
共享數(shù)據(jù)或行為。把數(shù)據(jù)分為內(nèi)部數(shù)據(jù)、內(nèi)部方法和外部數(shù)據(jù)、外部方法。共享開銷大的元素,例如 dom 元素等,數(shù)據(jù)是獨(dú)立于 dom 的。相同的行為也可以提取出來作為公共元素,通過繼承等方式,減少冗余代碼。再例如彈窗。
行為型設(shè)計(jì)模式
模板方法模式
先實(shí)現(xiàn)基礎(chǔ)模板,再在模板上添加別的功能,類似 裝飾者模式,但不同是裝飾者是作為第三方存在,模板方法直接產(chǎn)出一個(gè)對(duì)象或行為,在實(shí)例上做改動(dòng),并且裝飾者不會(huì)修改原有對(duì)象或行為,模板方法會(huì)。案例就是彈層統(tǒng)一。父類中定義一組操作算法骨架,而將一些實(shí)現(xiàn)步驟延遲到子類,使得子類可以不改變父類算法結(jié)構(gòu)的同時(shí)可重新定義算法中某些實(shí)現(xiàn)步驟。
// 彈層統(tǒng)一,繼承后修改基類方法和元素, ES6
觀察者模式
發(fā)布 - 訂閱模式,和 dom 事件的異同。通過中轉(zhuǎn)站傳遞消息。
//發(fā)布-訂閱庫(kù)
var Observer = (function() {
// 防止消息隊(duì)列暴露而被篡改,故將消息容器作為靜態(tài)私有變量保存
var __messages = {};
return {
// 注冊(cè)信息接口
regist: function() {},
// 發(fā)布信息接口
fire: function() {},
// 移除信息接口
remove: function() {}
}
})();
狀態(tài)模式
對(duì)條件狀態(tài)判斷的每一種情況獨(dú)立管理,解決條件分支耦合問題。案例:超級(jí)瑪麗。和 橋接模式 類似,拆分多維變量,對(duì)每一種變量進(jìn)行獨(dú)立管理。
//超級(jí)瑪麗案例
class MarryState {
constructor() {
this._currentState = [];
this.state = {
jump: function (){},
move: function (){},,
shoot: function (){},
squat: function (){},
}
}
//??ES6 私有變量
_changeState(...args) {
this._currentState = [];
args.forEach(action => {
this._currentState.push(action);
});
return this;
}
goes() {
this._currentState.forEach(actionName => {
const action = this.state[actionName];
if (action) {action();}
});
return this;
}
change() { return this._changeState;}
}
//調(diào)用
var marry = new MarryState();
marry.change('jump', 'shoot').goes().goes().change('shoot').goes().goes();
//盒子移動(dòng)
class BoxState {
constructor(dom) {
this.dom = dom;
//this._currentState = [];
this.step = 50;
this.left = 0;
this.top = 0;
this.state = {
37: this.moveLeft.bind(this),
38: this.moveUp.bind(this),
39: this.moveRight.bind(this),
40: this.moveDown.bind(this),
}
}
moveLeft() {
this.left -= this.step;
this.dom.style.left = this.left+'px';
}
moveRight() {
this.left += this.step;
this.dom.style.left = this.left+'px';
}
moveUp() {
this.top -= this.step;
this.dom.style.top = this.top+'px';
}
moveDown() {
this.top += this.step;
this.dom.style.top = this.top+'px';
}
goes(keyCode) {
let action = this.state[keyCode];
if (action) { action(); }
}
}
let boxDom = document.getElementById('box');
let bx = new BoxState(boxDom);
window.addEventListener('keydown', function (e) {
//改變狀態(tài)
let keyCode = e.which;
bx.goes(keyCode);
});
策略模式
類似 狀態(tài)模式,不同的是,狀態(tài)模式 核心是對(duì)狀態(tài)的控制來決定行為,策略模式 關(guān)注算法封裝,通過策略名稱調(diào)用來獲得結(jié)果。另:對(duì)于各算法共用的部分,可以通過 享元模式 解決資源浪費(fèi)問題。拓展:支持算法延伸。
//??表單驗(yàn)證策略統(tǒng)一
職責(zé)鏈模式
分解復(fù)雜模塊,簡(jiǎn)化需求。例如:服務(wù)端拉取數(shù)據(jù) -> 適配并解析新聞 -> 創(chuàng)建新聞模塊 -> 為新聞添加交互 -> 展示新聞頁(yè)。每個(gè)步驟都是一個(gè)模塊,互相調(diào)用,且可測(cè)。
命令模式
解耦請(qǐng)求與實(shí)現(xiàn)并封裝成獨(dú)立對(duì)象。
//例如繪圖調(diào)用方法
//但這樣簡(jiǎn)單的使用其實(shí)用命令模式?jīng)]什么意義
CanvasCommand.excute([
{command: 'fillStyle', param: 'red'},
{command: 'fillRect', param: [20, 20, 100, 100]},
]);
//??時(shí)鐘案例
訪問者模式
對(duì)原聲對(duì)象構(gòu)造器進(jìn)行封裝,對(duì)多變的操作類型進(jìn)行原生行為封裝。例如:對(duì)象也可以使用 push,splice 等方法,通過Array.prototype.splice.apply(arguments[0], args)
。
中介者模式
類似 觀察者模式,區(qū)別是 觀察者 是雙向訂閱模式,一個(gè)模塊既可以是消息發(fā)送者也可以是接收者,而 中介者 是消息統(tǒng)一由中介發(fā)布,所有訂閱者間接被管理。模塊與模塊之間的交互在中介者內(nèi)部完成,保持了模塊之間的獨(dú)立性。
//鍵盤方向鍵控制盒子移動(dòng)
//單例模式 中介者
class Mediator {
static getInstance() {
if (!Mediator.instance) {
Mediator.instance = new Mediator();
}
return Mediator.instance;
}
constructor() {
this._msg = {};
}
registry(type, cb) {
this._msg[type] = this._msg[type] || [];
if (cb) {this._msg[type].push(cb);}
}
send(type) {
let funcs = this._msg[type];
if (funcs) {
funcs.forEach(func => {
if (func) {func();}
});
}
}
}
let md = Mediator.getInstance();
//盒子模型
class Box {
constructor() {
this.dom = document.getElementById('box');
this.left = 0;
this.top = 0;
this.step = 50;
}
moveLeft() {
this.left -= this.step;
this.dom.style.left = this.left+'px';
}
moveRight() {
this.left += this.step;
this.dom.style.left = this.left+'px';
}
moveUp() {
this.top -= this.step;
this.dom.style.top = this.top+'px';
}
moveDown() {
this.top += this.step;
this.dom.style.top = this.top+'px';
}
}
var b = new Box();
md.registry('moveLeft', b.moveLeft.bind(b));
md.registry('moveUp', b.moveUp.bind(b));
md.registry('moveRight', b.moveRight.bind(b));
md.registry('moveDown', b.moveDown.bind(b));
//發(fā)送層
window.addEventListener('keydown', function (e) {
let keyCode = e.which;
switch (keyCode) {
case 37: md.send('moveLeft');break;
case 38: md.send('moveUp');break;
case 39: md.send('moveRight');break;
case 40: md.send('moveDown');break;
}
});
備忘錄模式
緩存網(wǎng)絡(luò)請(qǐng)求等其他耗時(shí)耗力的數(shù)據(jù)。配合 適配器模式 在早期格式化網(wǎng)絡(luò)請(qǐng)求數(shù)據(jù),便于今后的重復(fù)使用。
// Page備忘錄類
var Page = function() {
// 信息緩存對(duì)象
var cache = {};
return function(page, fn) {
// 判斷該頁(yè)數(shù)據(jù)是否在緩存中
if(cache[page]) {
// 顯示該頁(yè)內(nèi)容
showPage(page, cache[page]);
// 執(zhí)行成功回調(diào)函數(shù)
fn && fn();
} else {
// 否則異步請(qǐng)求
$.post('./data/getNewsData.php', {
page: page
}, function(res) {
// 成功返回
if(res.errNo == 0) {
showPage(page, res.data);
cache[page] = res.data;
fn && fn();
} else {
// 處理異常
}
})
}
}
}
迭代器模式
對(duì)數(shù)據(jù)自定義迭代器
解釋器
技巧型設(shè)計(jì)模式
鏈模式
委托模式
冒泡 & 捕獲的區(qū)別:捕獲減少事件綁定,內(nèi)存消耗,子元素委托給父元素處理。通過委托者將請(qǐng)求委托給被委托者去處理實(shí)現(xiàn)。通過被委托者對(duì)接受到的請(qǐng)求進(jìn)行處理后,分發(fā)給相應(yīng)委托者處理。應(yīng)用有:事件處理,狀態(tài)模式 中的狀態(tài)對(duì)象,策略模式 中策略對(duì)象對(duì)接收到的算法處理,命令模式對(duì)接受到的命令處理。
數(shù)據(jù)訪問對(duì)象模式
節(jié)流模式
用戶在頁(yè)面的操作是沒有限制的,但是如果相應(yīng)用戶的頻繁操作,比如:scroll、resize、move 等,會(huì)造成動(dòng)作函數(shù)不停執(zhí)行,性能上會(huì)產(chǎn)生很大的問題。所以要延遲執(zhí)行,等用戶操作穩(wěn)定之后再處理??捎糜?浮層 show & hide 、可視范圍優(yōu)先加載,其他圖片延遲加載、統(tǒng)計(jì)打包
//簡(jiǎn)略版
function throttle(fn) {
return function(...args) {
let context = this
clearTimeout(timer)
timer = setTimeout(function() {
fn.apply(context, args)
}, 1000)
}
}
//詳細(xì)版
var throttle = function (...args) {
let isCLear = args[0];
let fn = null;
if (typeof isCLear === 'boolean') {
fn = args[1];
fn.__throttleId && clearTimeout(fn.__throttleId);
} else {
fn = isClear;
param = args[1];
let p = Object.assign({
context: null,
options = [],
time: 300
}, param);
args.callee(true, fn);
fn.__throttleId = setTimeout(function () {
fn.apply(p.context, p.options);
}, p.time);
}
}
//??搜索框 change 事件節(jié)流
簡(jiǎn)單模板模式
架構(gòu)型設(shè)計(jì)模式
同步模塊
即 CMD、AMD 方法的實(shí)現(xiàn)。對(duì)其他模塊通過引用方式引入到自己的模塊中,互不干擾。
//??模塊化管理工具的實(shí)現(xiàn)
異步模塊
對(duì)未加載模塊的文件引用(前端限制)。
//async 方法
//實(shí)現(xiàn)對(duì)樣式模塊的依賴化加載
Widget 模式
即模板引擎。
MVC 模式
拆分?jǐn)?shù)據(jù)、視圖、業(yè)務(wù)邏輯三個(gè)層次,顯性區(qū)分三個(gè)層次,這樣還有一個(gè)好處是可以實(shí)現(xiàn)數(shù)據(jù)共享。