1.不屬於常見23種設計模式javascript
1. 定義css
保證一個類僅有一個實例,並提供一個訪問它的全局訪問點html
2. 核心前端
確保只有一個實例,並提供全局訪問java
3. 實現python
假設要設置一個管理員,屢次調用也僅設置一次,咱們可使用閉包緩存一個內部變量來實現這個單例jquery
function SetManager(name) { this.manager = name; } SetManager.prototype.getName = function() { console.log(this.manager); }; var SingletonSetManager = (function() { var manager = null; return function(name) { if (!manager) { manager = new SetManager(name); } return manager; } })(); SingletonSetManager('a').getName(); // a SingletonSetManager('b').getName(); // a SingletonSetManager('c').getName(); // a
這是比較簡單的作法,可是假如咱們還要設置一個HR呢?就得複製一遍代碼了web
因此,能夠改寫單例內部,實現地更通用一些面試
// 提取出通用的單例 function getSingleton(fn) { var instance = null; return function() { if (!instance) { instance = fn.apply(this, arguments); } return instance; } }
再進行調用,結果仍是同樣算法
// 獲取單例 var managerSingleton = getSingleton(function(name) { var manager = new SetManager(name); return manager; }); managerSingleton('a').getName(); // a managerSingleton('b').getName(); // a managerSingleton('c').getName(); // a
這時,咱們添加HR時,就不須要更改獲取單例內部的實現了,僅須要實現添加HR所須要作的,再調用便可
function SetHr(name) { this.hr = name; } SetHr.prototype.getName = function() { console.log(this.hr); }; var hrSingleton = getSingleton(function(name) { var hr = new SetHr(name); return hr; }); hrSingleton('aa').getName(); // aa hrSingleton('bb').getName(); // aa hrSingleton('cc').getName(); // aa
或者,僅想要建立一個div層,不須要將對象實例化,直接調用函數
結果爲頁面中僅有第一個建立的div
function createPopup(html) { var div = document.createElement('div'); div.innerHTML = html; document.body.append(div); return div; } var popupSingleton = getSingleton(function() { var div = createPopup.apply(this, arguments); return div; }); console.log( popupSingleton('aaa').innerHTML, popupSingleton('bbb').innerHTML, popupSingleton('bbb').innerHTML ); // aaa aaa aaa
1. 定義
定義一系列的算法,把它們一個個封裝起來,而且使它們能夠相互替換。
2. 核心
將算法的使用和算法的實現分離開來。
一個基於策略模式的程序至少由兩部分組成:
第一個部分是一組策略類,策略類封裝了具體的算法,並負責具體的計算過程。
第二個部分是環境類Context,Context接受客戶的請求,隨後把請求委託給某一個策略類。要作到這點,說明Context 中要維持對某個策略對象的引用
3. 實現
策略模式能夠用於組合一系列算法,也可用於組合一系列業務規則
假設須要經過成績等級來計算學生的最終得分,每一個成績等級有對應的加權值。咱們能夠利用對象字面量的形式直接定義這個組策略
// 加權映射關係 var levelMap = { S: 10, A: 8, B: 6, C: 4 }; // 組策略 var scoreLevel = { basicScore: 80, S: function() { return this.basicScore + levelMap['S']; }, A: function() { return this.basicScore + levelMap['A']; }, B: function() { return this.basicScore + levelMap['B']; }, C: function() { return this.basicScore + levelMap['C']; } } // 調用 function getScore(level) { return scoreLevel[level] ? scoreLevel[level]() : 0; } console.log( getScore('S'), getScore('A'), getScore('B'), getScore('C'), getScore('D') ); // 90 88 86 84 0
在組合業務規則方面,比較經典的是表單的驗證方法。這裏列出比較關鍵的部分
// 錯誤提示 var errorMsgs = { default: '輸入數據格式不正確', minLength: '輸入數據長度不足', isNumber: '請輸入數字', required: '內容不爲空' }; // 規則集 var rules = { minLength: function(value, length, errorMsg) { if (value.length < length) { return errorMsg || errorMsgs['minLength'] } }, isNumber: function(value, errorMsg) { if (!/\d+/.test(value)) { return errorMsg || errorMsgs['isNumber']; } }, required: function(value, errorMsg) { if (value === '') { return errorMsg || errorMsgs['required']; } } }; // 校驗器 function Validator() { this.items = []; }; Validator.prototype = { constructor: Validator, // 添加校驗規則 add: function(value, rule, errorMsg) { var arg = [value]; if (rule.indexOf('minLength') !== -1) { var temp = rule.split(':'); arg.push(temp[1]); rule = temp[0]; } arg.push(errorMsg); this.items.push(function() { // 進行校驗 return rules[rule].apply(this, arg); }); }, // 開始校驗 start: function() { for (var i = 0; i < this.items.length; ++i) { var ret = this.items[i](); if (ret) { console.log(ret); // return ret; } } } }; // 測試數據 function testTel(val) { return val; } var validate = new Validator(); validate.add(testTel('ccc'), 'isNumber', '只能爲數字'); // 只能爲數字 validate.add(testTel(''), 'required'); // 內容不爲空 validate.add(testTel('123'), 'minLength:5', '最少5位'); // 最少5位 validate.add(testTel('12345'), 'minLength:5', '最少5位'); var ret = validate.start(); console.log(ret);
4. 優缺點
優勢
能夠有效地避免多重條件語句,將一系列方法封裝起來也更直觀,利於維護
缺點
每每策略集會比較多,咱們須要事先就瞭解定義好全部的狀況
1. 定義
爲一個對象提供一個代用品或佔位符,以便控制對它的訪問
2. 核心
當客戶不方便直接訪問一個 對象或者不知足須要的時候,提供一個替身對象 來控制對這個對象的訪問,客戶實際上訪問的是 替身對象。
替身對象對請求作出一些處理以後, 再把請求轉交給本體對象
代理和本體的接口具備一致性,本體定義了關鍵功能,而代理是提供或拒絕對它的訪問,或者在訪問本體以前作一 些額外的事情
3. 實現
代理模式主要有三種:保護代理、虛擬代理、緩存代理
保護代理主要實現了訪問主體的限制行爲,以過濾字符做爲簡單的例子
// 主體,發送消息 function sendMsg(msg) { console.log(msg); } // 代理,對消息進行過濾 function proxySendMsg(msg) { // 無消息則直接返回 if (typeof msg === 'undefined') { console.log('deny'); return; } // 有消息則進行過濾 msg = ('' + msg).replace(/泥\s*煤/g, ''); sendMsg(msg); } sendMsg('泥煤呀泥 煤呀'); // 泥煤呀泥 煤呀 proxySendMsg('泥煤呀泥 煤'); // 呀 proxySendMsg(); // deny
它的意圖很明顯,在訪問主體以前進行控制,沒有消息的時候直接在代理中返回了,拒絕訪問主體,這數據保護代理的形式
有消息的時候對敏感字符進行了處理,這屬於虛擬代理的模式
虛擬代理在控制對主體的訪問時,加入了一些額外的操做
在滾動事件觸發的時候,也許不須要頻繁觸發,咱們能夠引入函數節流,這是一種虛擬代理的實現
// 函數防抖,頻繁操做中不處理,直到操做完成以後(再過 delay 的時間)才一次性處理 function debounce(fn, delay) { delay = delay || 200; var timer = null; return function() { var arg = arguments; // 每次操做時,清除上次的定時器 clearTimeout(timer); timer = null; // 定義新的定時器,一段時間後進行操做 timer = setTimeout(function() { fn.apply(this, arg); }, delay); } }; var count = 0; // 主體 function scrollHandle(e) { console.log(e.type, ++count); // scroll } // 代理 var proxyScrollHandle = (function() { return debounce(scrollHandle, 500); })(); window.onscroll = proxyScrollHandle;
緩存代理能夠爲一些開銷大的運算結果提供暫時的緩存,提高效率
來個栗子,緩存加法操做
// 主體 function add() { var arg = [].slice.call(arguments); return arg.reduce(function(a, b) { return a + b; }); } // 代理 var proxyAdd = (function() { var cache = []; return function() { var arg = [].slice.call(arguments).join(','); // 若是有,則直接從緩存返回 if (cache[arg]) { return cache[arg]; } else { var ret = add.apply(this, arguments); return ret; } }; })(); console.log( add(1, 2, 3, 4), add(1, 2, 3, 4), proxyAdd(10, 20, 30, 40), proxyAdd(10, 20, 30, 40) ); // 10 10 100 100
1. 定義
迭代器模式是指提供一種方法順序訪問一個聚合對象中的各個元素,而又不須要暴露該對象的內部表示。
2. 核心
在使用迭代器模式以後,即便不關心對象的內部構造,也能夠按順序訪問其中的每一個元素
3. 實現
JS中數組的map forEach 已經內置了迭代器
[1, 2, 3].forEach(function(item, index, arr) { console.log(item, index, arr); });
不過對於對象的遍歷,每每不能與數組同樣使用同一的遍歷代碼
咱們能夠封裝一下
function each(obj, cb) { var value; if (Array.isArray(obj)) { for (var i = 0; i < obj.length; ++i) { value = cb.call(obj[i], i, obj[i]); if (value === false) { break; } } } else { for (var i in obj) { value = cb.call(obj[i], i, obj[i]); if (value === false) { break; } } } } each([1, 2, 3], function(index, value) { console.log(index, value); }); each({a: 1, b: 2}, function(index, value) { console.log(index, value); }); // 0 1 // 1 2 // 2 3 // a 1 // b 2
再來看一個例子,強行地使用迭代器,來了解一下迭代器也能夠替換頻繁的條件語句
雖然例子不太好,但在其餘負責的分支判斷狀況下,也是值得考慮的
function getManager() { var year = new Date().getFullYear(); if (year <= 2000) { console.log('A'); } else if (year >= 2100) { console.log('C'); } else { console.log('B'); } } getManager(); // B
將每一個條件語句拆分出邏輯函數,放入迭代器中迭代
function year2000() { var year = new Date().getFullYear(); if (year <= 2000) { console.log('A'); } return false; } function year2100() { var year = new Date().getFullYear(); if (year >= 2100) { console.log('C'); } return false; } function year() { var year = new Date().getFullYear(); if (year > 2000 && year < 2100) { console.log('B'); } return false; } function iteratorYear() { for (var i = 0; i < arguments.length; ++i) { var ret = arguments[i](); if (ret !== false) { return ret; } } } var manager = iteratorYear(year2000, year2100, year); // B
1. 定義
也稱做觀察者模式,定義了對象間的一種一對多的依賴關係,當一個對象的狀態發 生改變時,全部依賴於它的對象都將獲得通知
2. 核心
取代對象之間硬編碼的通知機制,一個對象不用再顯式地調用另一個對象的某個接口。
與傳統的發佈-訂閱模式實現方式(將訂閱者自身當成引用傳入發佈者)不一樣,在JS中一般使用註冊回調函數的形式來訂閱
3. 實現
JS中的事件就是經典的發佈-訂閱模式的實現
// 訂閱 document.body.addEventListener('click', function() { console.log('click1'); }, false); document.body.addEventListener('click', function() { console.log('click2'); }, false); // 發佈 document.body.click(); // click1 click2
本身實現一下
小A在公司C完成了筆試及面試,小B也在公司C完成了筆試。他們焦急地等待結果,每隔半天就電話詢問公司C,致使公司C很不耐煩。
一種解決辦法是 AB直接把聯繫方式留給C,有結果的話C天然會通知AB
這裏的「詢問」屬於顯示調用,「留給」屬於訂閱,「通知」屬於發佈
// 觀察者 var observer = { // 訂閱集合 subscribes: [], // 訂閱 subscribe: function(type, fn) { if (!this.subscribes[type]) { this.subscribes[type] = []; } // 收集訂閱者的處理 typeof fn === 'function' && this.subscribes[type].push(fn); }, // 發佈 可能會攜帶一些信息發佈出去 publish: function() { var type = [].shift.call(arguments), fns = this.subscribes[type]; // 不存在的訂閱類型,以及訂閱時未傳入處理回調的 if (!fns || !fns.length) { return; } // 挨個處理調用 for (var i = 0; i < fns.length; ++i) { fns[i].apply(this, arguments); } }, // 刪除訂閱 remove: function(type, fn) { // 刪除所有 if (typeof type === 'undefined') { this.subscribes = []; return; } var fns = this.subscribes[type]; // 不存在的訂閱類型,以及訂閱時未傳入處理回調的 if (!fns || !fns.length) { return; } if (typeof fn === 'undefined') { fns.length = 0; return; } // 挨個處理刪除 for (var i = 0; i < fns.length; ++i) { if (fns[i] === fn) { fns.splice(i, 1); } } } }; // 訂閱崗位列表 function jobListForA(jobs) { console.log('A', jobs); } function jobListForB(jobs) { console.log('B', jobs); } // A訂閱了筆試成績 observer.subscribe('job', jobListForA); // B訂閱了筆試成績 observer.subscribe('job', jobListForB); // A訂閱了筆試成績 observer.subscribe('examinationA', function(score) { console.log(score); }); // B訂閱了筆試成績 observer.subscribe('examinationB', function(score) { console.log(score); }); // A訂閱了面試結果 observer.subscribe('interviewA', function(result) { console.log(result); }); observer.publish('examinationA', 100); // 100 observer.publish('examinationB', 80); // 80 observer.publish('interviewA', '備用'); // 備用 observer.publish('job', ['前端', '後端', '測試']); // 輸出A和B的崗位 // B取消訂閱了筆試成績 observer.remove('examinationB'); // A都取消訂閱了崗位 observer.remove('job', jobListForA); observer.publish('examinationB', 80); // 沒有可匹配的訂閱,無輸出 observer.publish('job', ['前端', '後端', '測試']); // 輸出B的崗位
4. 優缺點
優勢
一爲時間上的解耦,二爲對象之間的解耦。能夠用在異步編程中與MV*框架中
缺點
建立訂閱者自己要消耗必定的時間和內存,訂閱的處理函數不必定會被執行,駐留內存有性能開銷
弱化了對象之間的聯繫,複雜的狀況下可能會致使程序難以跟蹤維護和理解
1. 定義
用一種鬆耦合的方式來設計程序,使得請求發送者和請求接收者可以消除彼此之間的耦合關係
命令(command)指的是一個執行某些特定事情的指令
2. 核心
命令中帶有execute執行、undo撤銷、redo重作等相關命令方法,建議顯示地指示這些方法名
3. 實現
簡單的命令模式實現能夠直接使用對象字面量的形式定義一個命令
var incrementCommand = { execute: function() { // something } };
不過接下來的例子是一個自增命令,提供執行、撤銷、重作功能
採用對象建立處理的方式,定義這個自增
// 自增 function IncrementCommand() { // 當前值 this.val = 0; // 命令棧 this.stack = []; // 棧指針位置 this.stackPosition = -1; }; IncrementCommand.prototype = { constructor: IncrementCommand, // 執行 execute: function() { this._clearRedo(); // 定義執行的處理 var command = function() { this.val += 2; }.bind(this); // 執行並緩存起來 command(); this.stack.push(command); this.stackPosition++; this.getValue(); }, canUndo: function() { return this.stackPosition >= 0; }, canRedo: function() { return this.stackPosition < this.stack.length - 1; }, // 撤銷 undo: function() { if (!this.canUndo()) { return; } this.stackPosition--; // 命令的撤銷,與執行的處理相反 var command = function() { this.val -= 2; }.bind(this); // 撤銷後不須要緩存 command(); this.getValue(); }, // 重作 redo: function() { if (!this.canRedo()) { return; } // 執行棧頂的命令 this.stack[++this.stackPosition](); this.getValue(); }, // 在執行時,已經撤銷的部分不能再重作 _clearRedo: function() { this.stack = this.stack.slice(0, this.stackPosition + 1); }, // 獲取當前值 getValue: function() { console.log(this.val); } };
再實例化進行測試,模擬執行、撤銷、重作的操做
var incrementCommand = new IncrementCommand(); // 模擬事件觸發,執行命令 var eventTrigger = { // 某個事件的處理中,直接調用命令的處理方法 increment: function() { incrementCommand.execute(); }, incrementUndo: function() { incrementCommand.undo(); }, incrementRedo: function() { incrementCommand.redo(); } }; eventTrigger['increment'](); // 2 eventTrigger['increment'](); // 4 eventTrigger['incrementUndo'](); // 2 eventTrigger['increment'](); // 4 eventTrigger['incrementUndo'](); // 2 eventTrigger['incrementUndo'](); // 0 eventTrigger['incrementUndo'](); // 無輸出 eventTrigger['incrementRedo'](); // 2 eventTrigger['incrementRedo'](); // 4 eventTrigger['incrementRedo'](); // 無輸出 eventTrigger['increment'](); // 6
此外,還能夠實現簡單的宏命令(一系列命令的集合)
var MacroCommand = { commands: [], add: function(command) { this.commands.push(command); return this; }, remove: function(command) { if (!command) { this.commands = []; return; } for (var i = 0; i < this.commands.length; ++i) { if (this.commands[i] === command) { this.commands.splice(i, 1); } } }, execute: function() { for (var i = 0; i < this.commands.length; ++i) { this.commands[i].execute(); } } }; var showTime = { execute: function() { console.log('time'); } }; var showName = { execute: function() { console.log('name'); } }; var showAge = { execute: function() { console.log('age'); } }; MacroCommand.add(showTime).add(showName).add(showAge); MacroCommand.remove(showName); MacroCommand.execute(); // time age
1. 定義
是用小的子對象來構建更大的 對象,而這些小的子對象自己也許是由更小 的「孫對象」構成的。
2. 核心
能夠用樹形結構來表示這種「部分- 總體」的層次結構。
調用組合對象 的execute方法,程序會遞歸調用組合對象 下面的葉對象的execute方法
但要注意的是,組合模式不是父子關係,它是一種HAS-A(聚合)的關係,將請求委託給 它所包含的全部葉對象。基於這種委託,就須要保證組合對象和葉對象擁有相同的 接口
此外,也要保證用一致的方式對待 列表中的每一個葉對象,即葉對象屬於同一類,不須要過多特殊的額外操做
3. 實現
使用組合模式來實現掃描文件夾中的文件
// 文件夾 組合對象 function Folder(name) { this.name = name; this.parent = null; this.files = []; } Folder.prototype = { constructor: Folder, add: function(file) { file.parent = this; this.files.push(file); return this; }, scan: function() { // 委託給葉對象處理 for (var i = 0; i < this.files.length; ++i) { this.files[i].scan(); } }, remove: function(file) { if (typeof file === 'undefined') { this.files = []; return; } for (var i = 0; i < this.files.length; ++i) { if (this.files[i] === file) { this.files.splice(i, 1); } } } }; // 文件 葉對象 function File(name) { this.name = name; this.parent = null; } File.prototype = { constructor: File, add: function() { console.log('文件裏面不能添加文件'); }, scan: function() { var name = [this.name]; var parent = this.parent; while (parent) { name.unshift(parent.name); parent = parent.parent; } console.log(name.join(' / ')); } };
構造好組合對象與葉對象的關係後,實例化,在組合對象中插入組合或葉對象
var web = new Folder('Web'); var fe = new Folder('前端'); var css = new Folder('CSS'); var js = new Folder('js'); var rd = new Folder('後端'); web.add(fe).add(rd); var file1 = new File('HTML權威指南.pdf'); var file2 = new File('CSS權威指南.pdf'); var file3 = new File('JavaScript權威指南.pdf'); var file4 = new File('MySQL基礎.pdf'); var file5 = new File('Web安全.pdf'); var file6 = new File('Linux菜鳥.pdf'); css.add(file2); fe.add(file1).add(file3).add(css).add(js); rd.add(file4).add(file5); web.add(file6); rd.remove(file4); // 掃描 web.scan();
掃描結果爲
4. 優缺點
優勢
可 以方便地構造一棵樹來表示對象的部分-總體 結構。在樹的構造最終 完成以後,只須要經過請求樹的最頂層對 象,便能對整棵樹作統一一致的操做。
缺點
建立出來的對象長得都差很少,可能會使代碼很差理解,建立太多的對象對性能也會有一些影響
1. 定義
模板方法模式由兩部分結構組成,第一部分是抽象父類,第二部分是具體的實現子類。
2. 核心
在抽象父類中封裝子類的算法框架,它的 init方法可做爲一個算法的模板,指導子類以何種順序去執行哪些方法。
由父類分離出公共部分,要求子類重寫某些父類的(易變化的)抽象方法
3. 實現
模板方法模式通常的實現方式爲繼承
以運動做爲例子,運動有比較通用的一些處理,這部分能夠抽離開來,在父類中實現。具體某項運動的特殊性則有自類來重寫實現。
最終子類直接調用父類的模板函數來執行
// 體育運動 function Sport() { } Sport.prototype = { constructor: Sport, // 模板,按順序執行 init: function() { this.stretch(); this.jog(); this.deepBreath(); this.start(); var free = this.end(); // 運動後還有空的話,就拉伸一下 if (free !== false) { this.stretch(); } }, // 拉伸 stretch: function() { console.log('拉伸'); }, // 慢跑 jog: function() { console.log('慢跑'); }, // 深呼吸 deepBreath: function() { console.log('深呼吸'); }, // 開始運動 start: function() { throw new Error('子類必須重寫此方法'); }, // 結束運動 end: function() { console.log('運動結束'); } }; // 籃球 function Basketball() { } Basketball.prototype = new Sport(); // 重寫相關的方法 Basketball.prototype.start = function() { console.log('先投上幾個三分'); }; Basketball.prototype.end = function() { console.log('運動結束了,有事先走一步'); return false; }; // 馬拉松 function Marathon() { } Marathon.prototype = new Sport(); var basketball = new Basketball(); var marathon = new Marathon(); // 子類調用,最終會按照父類定義的順序執行 basketball.init(); marathon.init();
1. 定義
2. 核心
運用共享技術來有效支持大量細粒度的對象。
強調將對象的屬性劃分爲內部狀態(屬性)與外部狀態(屬性)。內部狀態用於對象的共享,一般不變;而外部狀態則剝離開來,由具體的場景決定。
3. 實現
在程序中使用了大量的類似對象時,能夠利用享元模式來優化,減小對象的數量
舉個栗子,要對某個班進行身體素質測量,僅測量身高體重來評判
// 健康測量 function Fitness(name, sex, age, height, weight) { this.name = name; this.sex = sex; this.age = age; this.height = height; this.weight = weight; } // 開始評判 Fitness.prototype.judge = function() { var ret = this.name + ': '; if (this.sex === 'male') { ret += this.judgeMale(); } else { ret += this.judgeFemale(); } console.log(ret); }; // 男性評判規則 Fitness.prototype.judgeMale = function() { var ratio = this.height / this.weight; return this.age > 20 ? (ratio > 3.5) : (ratio > 2.8); }; // 女性評判規則 Fitness.prototype.judgeFemale = function() { var ratio = this.height / this.weight; return this.age > 20 ? (ratio > 4) : (ratio > 3); }; var a = new Fitness('A', 'male', 18, 160, 80); var b = new Fitness('B', 'male', 21, 180, 70); var c = new Fitness('C', 'female', 28, 160, 80); var d = new Fitness('D', 'male', 18, 170, 60); var e = new Fitness('E', 'female', 18, 160, 40); // 開始評判 a.judge(); // A: false b.judge(); // B: false c.judge(); // C: false d.judge(); // D: true e.judge(); // E: true
評判五我的就須要建立五個對象,一個班就幾十個對象
能夠將對象的公共部分(內部狀態)抽離出來,與外部狀態獨立。將性別看作內部狀態便可,其餘屬性都屬於外部狀態。
這麼一來咱們只須要維護男和女兩個對象(使用factory對象),而其餘變化的部分則在外部維護(使用manager對象)
// 健康測量 function Fitness(sex) { this.sex = sex; } // 工廠,建立可共享的對象 var FitnessFactory = { objs: [], create: function(sex) { if (!this.objs[sex]) { this.objs[sex] = new Fitness(sex); } return this.objs[sex]; } }; // 管理器,管理非共享的部分 var FitnessManager = { fitnessData: {}, // 添加一項 add: function(name, sex, age, height, weight) { var fitness = FitnessFactory.create(sex); // 存儲變化的數據 this.fitnessData[name] = { age: age, height: height, weight: weight }; return fitness; }, // 從存儲的數據中獲取,更新至當前正在使用的對象 updateFitnessData: function(name, obj) { var fitnessData = this.fitnessData[name]; for (var item in fitnessData) { if (fitnessData.hasOwnProperty(item)) { obj[item] = fitnessData[item]; } } } }; // 開始評判 Fitness.prototype.judge = function(name) { // 操做前先更新當前狀態(從外部狀態管理器中獲取) FitnessManager.updateFitnessData(name, this); var ret = name + ': '; if (this.sex === 'male') { ret += this.judgeMale(); } else { ret += this.judgeFemale(); } console.log(ret); }; // 男性評判規則 Fitness.prototype.judgeMale = function() { var ratio = this.height / this.weight; return this.age > 20 ? (ratio > 3.5) : (ratio > 2.8); }; // 女性評判規則 Fitness.prototype.judgeFemale = function() { var ratio = this.height / this.weight; return this.age > 20 ? (ratio > 4) : (ratio > 3); }; var a = FitnessManager.add('A', 'male', 18, 160, 80); var b = FitnessManager.add('B', 'male', 21, 180, 70); var c = FitnessManager.add('C', 'female', 28, 160, 80); var d = FitnessManager.add('D', 'male', 18, 170, 60); var e = FitnessManager.add('E', 'female', 18, 160, 40); // 開始評判 a.judge('A'); // A: false b.judge('B'); // B: false c.judge('C'); // C: false d.judge('D'); // D: true e.judge('E'); // E: true
不過代碼可能更復雜了,這個例子可能還不夠充分,只是展現了享元模式如何實現,它節省了多個類似的對象,但多了一些操做。
factory對象有點像單例模式,只是多了一個sex的參數,若是沒有內部狀態,則沒有參數的factory對象就更接近單例模式了
1. 定義
2. 核心
請求發送者只須要知道鏈中的第一個節點,弱化發送者和一組接收者之間的強聯繫,能夠便捷地在職責鏈中增長或刪除一個節點,一樣地,指定誰是第一個節點也很便捷
3. 實現
以展現不一樣類型的變量爲例,設置一條職責鏈,能夠免去多重if條件分支
// 定義鏈的某一項 function ChainItem(fn) { this.fn = fn; this.next = null; } ChainItem.prototype = { constructor: ChainItem, // 設置下一項 setNext: function(next) { this.next = next; return next; }, // 開始執行 start: function() { this.fn.apply(this, arguments); }, // 轉到鏈的下一項執行 toNext: function() { if (this.next) { this.start.apply(this.next, arguments); } else { console.log('無匹配的執行項目'); } } }; // 展現數字 function showNumber(num) { if (typeof num === 'number') { console.log('number', num); } else { // 轉移到下一項 this.toNext(num); } } // 展現字符串 function showString(str) { if (typeof str === 'string') { console.log('string', str); } else { this.toNext(str); } } // 展現對象 function showObject(obj) { if (typeof obj === 'object') { console.log('object', obj); } else { this.toNext(obj); } } var chainNumber = new ChainItem(showNumber); var chainString = new ChainItem(showString); var chainObject = new ChainItem(showObject); // 設置鏈條 chainObject.setNext(chainNumber).setNext(chainString); chainString.start('12'); // string 12 chainNumber.start({}); // 無匹配的執行項目 chainObject.start({}); // object {} chainObject.start(123); // number 123
這時想判斷未定義的時候呢,直接加到鏈中便可
// 展現未定義 function showUndefined(obj) { if (typeof obj === 'undefined') { console.log('undefined'); } else { this.toNext(obj); } } var chainUndefined = new ChainItem(showUndefined); chainString.setNext(chainUndefined); chainNumber.start(); // undefined
由例子能夠看到,使用了職責鏈後,由本來的條件分支換成了不少對象,雖然結構更加清晰了,但在必定程度上可能會影響到性能,因此要注意避免過長的職責鏈。
1. 定義
2. 核心
使網狀的多對多關係變成了相對簡單的一對多關係(複雜的調度處理都交給中介者)
使用中介者後
3. 實現
多個對象,指的不必定得是實例化的對象,也能夠將其理解成互爲獨立的多個項。當這些項在處理時,須要知曉並經過其餘項的數據來處理。
若是每一個項都直接處理,程序會很是複雜,修改某個地方就得在多個項內部修改
咱們將這個處理過程抽離出來,封裝成中介者來處理,各項須要處理時,通知中介者便可。
var A = { score: 10, changeTo: function(score) { this.score = score; // 本身獲取 this.getRank(); }, // 直接獲取 getRank: function() { var scores = [this.score, B.score, C.score].sort(function(a, b) { return a < b; }); console.log(scores.indexOf(this.score) + 1); } }; var B = { score: 20, changeTo: function(score) { this.score = score; // 經過中介者獲取 rankMediator(B); } }; var C = { score: 30, changeTo: function(score) { this.score = score; rankMediator(C); } }; // 中介者,計算排名 function rankMediator(person) { var scores = [A.score, B.score, C.score].sort(function(a, b) { return a < b; }); console.log(scores.indexOf(person.score) + 1); } // A經過自身來處理 A.changeTo(100); // 1 // B和C交由中介者處理 B.changeTo(200); // 1 C.changeTo(50); // 3
ABC三我的分數改變後想要知道本身的排名,在A中本身處理,而B和C使用了中介者。B和C將更爲輕鬆,總體代碼也更簡潔
最後,雖然中介者作到了對模塊和對象的解耦,但有時對象之間的關係並不是必定要解耦,強行使用中介者來整合,可能會使代碼更爲繁瑣,須要注意。
1. 定義
2. 核心
是爲對象動態加入行爲,通過多重包裝,能夠造成一條裝飾鏈
3. 實現
最簡單的裝飾者,就是重寫對象的屬性
var A = { score: 10 }; A.score = '分數:' + A.score;
可使用傳統面向對象的方法來實現裝飾,添加技能
function Person() {} Person.prototype.skill = function() { console.log('數學'); }; // 裝飾器,還會音樂 function MusicDecorator(person) { this.person = person; } MusicDecorator.prototype.skill = function() { this.person.skill(); console.log('音樂'); }; // 裝飾器,還會跑步 function RunDecorator(person) { this.person = person; } RunDecorator.prototype.skill = function() { this.person.skill(); console.log('跑步'); }; var person = new Person(); // 裝飾一下 var person1 = new MusicDecorator(person); person1 = new RunDecorator(person1); person.skill(); // 數學 person1.skill(); // 數學 音樂 跑步
在JS中,函數爲一等對象,因此咱們也可使用更通用的裝飾函數
// 裝飾器,在當前函數執行前先執行另外一個函數 function decoratorBefore(fn, beforeFn) { return function() { var ret = beforeFn.apply(this, arguments); // 在前一個函數中判斷,不須要執行當前函數 if (ret !== false) { fn.apply(this, arguments); } }; } function skill() { console.log('數學'); } function skillMusic() { console.log('音樂'); } function skillRun() { console.log('跑步'); } var skillDecorator = decoratorBefore(skill, skillMusic); skillDecorator = decoratorBefore(skillDecorator, skillRun); skillDecorator(); // 跑步 音樂 數學
1. 定義
2. 核心
區分事物內部的狀態,把事物的每種狀態都封裝成單獨的類,跟此種狀態有關的行爲都被封裝在這個類的內部
3. 實現
以一我的的工做狀態做爲例子,在剛醒、精神、疲倦幾個狀態中切換着
// 工做狀態 function Work(name) { this.name = name; this.currentState = null; // 工做狀態,保存爲對應狀態對象 this.wakeUpState = new WakeUpState(this); // 精神飽滿 this.energeticState = new EnergeticState(this); // 疲倦 this.tiredState = new TiredState(this); this.init(); } Work.prototype.init = function() { this.currentState = this.wakeUpState; // 點擊事件,用於觸發更新狀態 document.body.onclick = () => { this.currentState.behaviour(); }; }; // 更新工做狀態 Work.prototype.setState = function(state) { this.currentState = state; } // 剛醒 function WakeUpState(work) { this.work = work; } // 剛醒的行爲 WakeUpState.prototype.behaviour = function() { console.log(this.work.name, ':', '剛醒呢,睡個懶覺先'); // 只睡了2秒鐘懶覺就精神了.. setTimeout(() => { this.work.setState(this.work.energeticState); }, 2 * 1000); } // 精神飽滿 function EnergeticState(work) { this.work = work; } EnergeticState.prototype.behaviour = function() { console.log(this.work.name, ':', '超級精神的'); // 才精神1秒鐘就發睏了 setTimeout(() => { this.work.setState(this.work.tiredState); }, 1000); }; // 疲倦 function TiredState(work) { this.work = work; } TiredState.prototype.behaviour = function() { console.log(this.work.name, ':', '怎麼肥事,好睏'); // 不知不覺,又變成了剛醒着的狀態... 不斷循環呀 setTimeout(() => { this.work.setState(this.work.wakeUpState); }, 1000); }; var work = new Work('曹操');
點擊一下頁面,觸發更新狀態的操做
4. 優缺點
優勢
狀態切換的邏輯分佈在狀態類中,易於維護
缺點
多個狀態類,對於性能來講,也是一個缺點,這個缺點可使用享元模式來作進一步優化
將邏輯分散在狀態類中,可能不會很輕易就能看出狀態機的變化邏輯
1. 定義
2. 核心
解決兩個已有接口之間不匹配的問題
3. 實現
好比一個簡單的數據格式轉換的適配器
// 渲染數據,格式限制爲數組了 function renderData(data) { data.forEach(function(item) { console.log(item); }); } // 對非數組的進行轉換適配 function arrayAdapter(data) { if (typeof data !== 'object') { return []; } if (Object.prototype.toString.call(data) === '[object Array]') { return data; } var temp = []; for (var item in data) { if (data.hasOwnProperty(item)) { temp.push(data[item]); } } return temp; } var data = { 0: 'A', 1: 'B', 2: 'C' }; renderData(arrayAdapter(data)); // A B C
1. 定義
2. 核心
能夠經過請求外觀接口來達到訪問子系統,也能夠選擇越過外觀來直接訪問子系統
3. 實現
外觀模式在JS中,能夠認爲是一組函數的集合
// 三個處理函數 function start() { console.log('start'); } function doing() { console.log('doing'); } function end() { console.log('end'); } // 外觀函數,將一些處理統一塊兒來,方便調用 function execute() { start(); doing(); end(); } // 調用init開始執行 function init() { // 此處直接調用了高層函數,也能夠選擇越過它直接調用相關的函數 execute(); } init(); // start doing end
咱們知道在js中有一個運算符能夠幫助咱們判斷一個值的類型,它就是typeof運算符。
1
2
3
4
5
6
7
8
|
console.log(
typeof
123);
//number
console.log(
typeof
'123'
);
//string
console.log(
typeof
true
);
//boolean
console.log(
typeof
undefined);
//undefined
console.log(
typeof
null
);
//object
console.log(
typeof
[]);
//object
console.log(
typeof
{});
//object
console.log(
typeof
function
() {});
//function
|
咱們從以上結果能夠看出typeof的不足之處,它對於數值、字符串、布爾值分別返回number、string、boolean,函數返回function,undefined返回undefined,除此之外,其餘狀況都返回object。
因此若是返回值爲object,咱們是沒法得知值的類型究竟是數組仍是對象或者其餘值。爲了準確獲得每一個值的類型,咱們必須使用js中另外一個運算符instanceof。下面簡單的說一下instanceof的用法。
instanceof運算符返回一個布爾值,表示指定對象是否爲某個構造函數的實例。
instanceof運算符的左邊是實例對象,右邊是構造函數。它會檢查右邊構造函數的ptototype屬性,是否在左邊對象的原型鏈上。
1
2
3
|
var
b = [];
b
instanceof
Array
//true
b
instanceof
Object
//true
|
注意,instanceof運算符只能用於對象,不適用原始類型的值。
因此咱們能夠結合typeof和instanceof運算符的特性,來對一個值的類型作出較爲準確的判斷。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
//獲得一個值的類型
function
getValueType(value) {
var
type =
''
;
if
(
typeof
value !=
'object'
) {
type =
typeof
value;
}
else
{
if
(value
instanceof
Array) {
type =
'array'
;
}
else
{
if
(value
instanceof
Object) {
type =
'object'
;
}
else
{
type =
'null'
;
}
}
}
return
type;
}
getValueType(123);
//number
getValueType(
'123'
);
//string
getValueType(
true
);
//boolean
getValueType(undefined);
//undefined
getValueType(
null
);
//null
getValueType([]);
//array
getValueType({});
//object
getValueType(
function
(){});
//function
|
3.關於原型對象如下說法錯誤的是
1、什麼是原型
原型是Javascript中的繼承的基礎,JavaScript的繼承就是基於原型的繼承。
1.1 函數的原型對象
在JavaScript中,咱們建立一個函數A(就是聲明一個函數), 那麼瀏覽器就會在內存中建立一個對象B,並且每一個函數都默認會有一個屬性 prototype 指向了這個對象( 即:prototype的屬性的值是這個對象 )。這個對象B就是函數A的原型對象,簡稱函數的原型。這個原型對象B 默認會有一個屬性 constructor 指向了這個函數A ( 意思就是說:constructor屬性的值是函數A )。
看下面的代碼:
<body>
<script type="text/javascript">
/*
聲明一個函數,則這個函數默認會有一個屬性叫 prototype 。並且瀏覽器會自動按照必定的規則
建立一個對象,這個對象就是這個函數的原型對象,prototype屬性指向這個原型對象。這個原型對象
有一個屬性叫constructor 執行了這個函數
注意:原型對象默認只有屬性:constructor。其餘都是從Object繼承而來,暫且不用考慮。
*/
function Person () {
}
</script>
</body>
下面的圖描述了聲明一個函數以後發生的事情:
1.2 使用構造函數建立對象
當把一個函數做爲構造函數 (理論上任何函數均可以做爲構造函數) 使用new建立對象的時候,那麼這個對象就會存在一個默認的不可見的屬性,來指向了構造函數的原型對象。 這個不可見的屬性咱們通常用 [[prototype]] 來表示,只是這個屬性沒有辦法直接訪問到。
看下面的代碼:
<body>
<script type="text/javascript">
function Person () {
}
/*
利用構造函數建立一個對象,則這個對象會自動添加一個不可見的屬性 [[prototype]], 並且這個屬性
指向了構造函數的原型對象。
*/
var p1 = new Person();
</script>
</body>
觀察下面的示意圖:
說明:
從上面的圖示中能夠看到,建立p1對象雖然使用的是Person構造函數,可是對象建立出來以後,這個p1對象其實已經與Person構造函數沒有任何關係了,p1對象的[[ prototype ]]屬性指向的是Person構造函數的原型對象。
若是使用new Person()建立多個對象,則多個對象都會同時指向Person構造函數的原型對象。
咱們能夠手動給這個原型對象添加屬性和方法,那麼p1,p2,p3…這些對象就會共享這些在原型中添加的屬性和方法。
若是咱們訪問p1中的一個屬性name,若是在p1對象中找到,則直接返回。若是p1對象中沒有找到,則直接去p1對象的[[prototype]]屬性指向的原型對象中查找,若是查找到則返回。(若是原型中也沒有找到,則繼續向上找原型的原型—原型鏈。 後面再講)。
若是經過p1對象添加了一個屬性name,則p1對象來講就屏蔽了原型中的屬性name。 換句話說:在p1中就沒有辦法訪問到原型的屬性name了。
經過p1對象只能讀取原型中的屬性name的值,而不能修改原型中的屬性name的值。 p1.name = 「李四」; 並非修改了原型中的值,而是在p1對象中給添加了一個屬性name。
看下面的代碼:
<body>
<script type="text/javascript">
function Person () {
}
// 可使用Person.prototype 直接訪問到原型對象
//給Person函數的原型對象中添加一個屬性 name而且值是 "張三"
Person.prototype.name = "張三";
Person.prototype.age = 20;
var p1 = new Person();
/*
訪問p1對象的屬性name,雖然在p1對象中咱們並無明確的添加屬性name,可是
p1的 [[prototype]] 屬性指向的原型中有name屬性,因此這個地方能夠訪問到屬性name
就值。
注意:這個時候不能經過p1對象刪除name屬性,由於只能刪除在p1中刪除的對象。
*/
alert(p1.name); // 張三
var p2 = new Person();
alert(p2.name); // 張三 都是從原型中找到的,因此同樣。
alert(p1.name === p2.name); // true
// 因爲不能修改原型中的值,則這種方法就直接在p1中添加了一個新的屬性name,而後在p1中沒法再訪問到
//原型中的屬性。
p1.name = "李四";
alert("p1:" + p1.name);
// 因爲p2中沒有name屬性,則對p2來講仍然是訪問的原型中的屬性。
alert("p2:" + p2.name); // 張三
</script>
</body>
2、與原型有關的幾個屬性和方法
2.1 prototype屬性
prototype 存在於構造函數中 (其實任意函數中都有,只是否是構造函數的時候prototype咱們不關注而已) ,他指向了這個構造函數的原型對象。
參考前面的示意圖。
2.2 constructor屬性
constructor屬性存在於原型對象中,他指向了構造函數
看下面的代碼:
<script type="text/javascript">
function Person () {
}
alert(Person.prototype.constructor === Person); // true
var p1 = new Person();
//使用instanceof 操做符能夠判斷一個對象的類型。
//typeof通常用來獲取簡單類型和函數。而引用類型通常使用instanceof,由於引用類型用typeof 老是返回object。
alert(p1 instanceof Person); // true
</script>
咱們根據須要,能夠Person.prototype 屬性指定新的對象,來做爲Person的原型對象。
可是這個時候有個問題,新的對象的constructor屬性則再也不指向Person構造函數了。
看下面的代碼:
<script type="text/javascript">
function Person () {
}
//直接給Person的原型指定對象字面量。則這個對象的constructor屬性再也不指向Person函數
Person.prototype = {
name:"志玲",
age:20
};
var p1 = new Person();
alert(p1.name); // 志玲
alert(p1 instanceof Person); // true
alert(Person.prototype.constructor === Person); //false
//若是constructor對你很重要,你應該在Person.prototype中添加一行這樣的代碼:
/*
Person.prototype = {
constructor : Person //讓constructor從新指向Person函數
}
*/
</script>
2.3 __proto__ 屬性(注意:左右各是2個下劃線)
用構造方法建立一個新的對象以後,這個對象中默認會有一個不可訪問的屬性 [[prototype]] , 這個屬性就指向了構造方法的原型對象。
可是在個別瀏覽器中,也提供了對這個屬性[[prototype]]的訪問(chrome瀏覽器和火狐瀏覽器。ie瀏覽器不支持)。訪問方式:p1.__proto__
可是開發者儘可能不要用這種方式去訪問,由於操做不慎會改變這個對象的繼承原型鏈。
<script type="text/javascript">
function Person () {
}
//直接給Person的原型指定對象字面量。則這個對象的constructor屬性再也不指向Person函數
Person.prototype = {
constructor : Person,
name:"志玲",
age:20
};
var p1 = new Person();
alert(p1.__proto__ === Person.prototype); //true
</script>
2.4 hasOwnProperty() 方法
你們知道,咱們用去訪問一個對象的屬性的時候,這個屬性既有可能來自對象自己,也有可能來自這個對象的[[prototype]]屬性指向的原型。
那麼如何判斷這個對象的來源呢?
hasOwnProperty方法,能夠判斷一個屬性是否來自對象自己。
<script type="text/javascript">
function Person () {
}
Person.prototype.name = "志玲";
var p1 = new Person();
p1.sex = "女";
//sex屬性是直接在p1屬性中添加,因此是true
alert("sex屬性是對象自己的:" + p1.hasOwnProperty("sex"));
// name屬性是在原型中添加的,因此是false
alert("name屬性是對象自己的:" + p1.hasOwnProperty("name"));
// age 屬性不存在,因此也是false
alert("age屬性是存在於對象自己:" + p1.hasOwnProperty("age"));
</script>
因此,經過hasOwnProperty這個方法能夠判斷一個對象是否在對象自己添加的,可是不能判斷是否存在於原型中,由於有可能這個屬性不存在。
也便是說,在原型中的屬性和不存在的屬性都會返回fasle。
如何判斷一個屬性是否存在於原型中呢?
2.5 in 操做符
in操做符用來判斷一個屬性是否存在於這個對象中。可是在查找這個屬性時候,如今對象自己中找,若是對象找不到再去原型中找。換句話說,只要對象和原型中有一個地方存在這個屬性,就返回true
<script type="text/javascript">
function Person () {
}
Person.prototype.name = "志玲";
var p1 = new Person();
p1.sex = "女";
alert("sex" in p1); // 對象自己添加的,因此true
alert("name" in p1); //原型中存在,因此true
alert("age" in p1); //對象和原型中都不存在,因此false
</script>
回到前面的問題,若是判斷一個屬性是否存在於原型中:
若是一個屬性存在,可是沒有在對象自己中,則必定存在於原型中。
<script type="text/javascript">
function Person () {
}
Person.prototype.name = "志玲";
var p1 = new Person();
p1.sex = "女";
//定義一個函數去判斷原型所在的位置
function propertyLocation(obj, prop){
if(!(prop in obj)){
alert(prop + "屬性不存在");
}else if(obj.hasOwnProperty(prop)){
alert(prop + "屬性存在於對象中");
}else {
alert(prop + "對象存在於原型中");
}
}
propertyLocation(p1, "age");
propertyLocation(p1, "name");
propertyLocation(p1, "sex");
</script
3、組合原型模型和構造函數模型建立對象
3.1 原型模型建立對象的缺陷
原型中的全部的屬性都是共享的。也就是說,用同一個構造函數建立的對象去訪問原型中的屬性的時候,你們都是訪問的同一個對象,若是一個對象對原型的屬性進行了修改,則會反映到全部的對象上面。
可是在實際使用中,每一個對象的屬性通常是不一樣的。張三的姓名是張三,李四的姓名是李四。
**可是,這個共享特性對 方法(屬性值是函數的屬性)又是很是合適的。**全部的對象共享方法是最佳狀態。這種特性在c#和Java中是天生存在的。
3.2 構造函數模型建立對象的缺陷
在構造函數中添加的屬性和方法,每一個對象都有本身獨有的一份,你們不會共享。這個特性對屬性比較合適,可是對方法又不太合適。由於對全部對象來講,他們的方法應該是一份就夠了,沒有必要每人一份,形成內存的浪費和性能的低下。
<script type="text/javascript">
function Person() {
this.name = "李四";
this.age = 20;
this.eat = function() {
alert("吃完東西");
}
}
var p1 = new Person();
var p2 = new Person();
//每一個對象都會有不一樣的方法
alert(p1.eat === p2.eat); //fasle
</script>
可使用下面的方法解決:
<script type="text/javascript">
function Person() {
this.name = "李四";
this.age = 20;
this.eat = eat;
}
function eat() {
alert("吃完東西");
}
var p1 = new Person();
var p2 = new Person();
//由於eat屬性都是賦值的同一個函數,因此是true
alert(p1.eat === p2.eat); //true
</script>
可是上面的這種解決方法具備致命的缺陷:封裝性太差。使用面向對象,目的之一就是封裝代碼,這個時候爲了性能又要把代碼抽出對象以外,這是反人類的設計。
3.3 使用組合模式解決上述兩種缺陷
原型模式適合封裝方法,構造函數模式適合封裝屬性,綜合兩種模式的優勢就有了組合模式。
<script type="text/javascript">
//在構造方法內部封裝屬性
function Person(name, age) {
this.name = name;
this.age = age;
}
//在原型對象內封裝方法
Person.prototype.eat = function (food) {
alert(this.name + "愛吃" + food);
}
Person.prototype.play = function (playName) {
alert(this.name + "愛玩" + playName);
}
var p1 = new Person("李四", 20);
var p2 = new Person("張三", 30);
p1.eat("蘋果");
p2.eat("香蕉");
p1.play("志玲");
p2.play("鳳姐");
</script>
4、動態原型模式建立對象
前面講到的組合模式,也並不是天衣無縫,有一點也是感受不是很完美。把構造方法和原型分開寫,總讓人感受不舒服,應該想辦法把構造方法和原型封裝在一塊兒,因此就有了動態原型模式。
動態原型模式把全部的屬性和方法都封裝在構造方法中,而僅僅在須要的時候纔去在構造方法中初始化原型,又保持了同時使用構造函數和原型的優勢。
看下面的代碼:
<script type="text/javascript">
//構造方法內部封裝屬性
function Person(name, age) {
//每一個對象都添加本身的屬性
this.name = name;
this.age = age;
/*
判斷this.eat這個屬性是否是function,若是不是function則證實是第一次建立對象,
則把這個funcion添加到原型中。
若是是function,則表明原型中已經有了這個方法,則不須要再添加。
perfect!完美解決了性能和代碼的封裝問題。
*/
if(typeof this.eat !== "function"){
Person.prototype.eat = function () {
alert(this.name + " 在吃");
}
}
}
var p1 = new Person("志玲", 40);
p1.eat();
</script>
說明:
組合模式和動態原型模式是JavaScript中使用比較多的兩種建立對象的方式。
建議之後使用動態原型模式。他解決了組合模式的封裝不完全的缺點。
原文連接:https://blog.csdn.net/u012468376/article/details/53121081
4.下面說法錯誤的是
引用http://blog.csdn.net/two_people/article/details/53374552
1、什麼是閉包?
官方」的解釋是:閉包是一個擁有許多變量和綁定了這些變量的環境的表達式(一般是一個函數),於是這些變量也是該表達式的一部分。
相信不多有人能直接看懂這句話,由於他描述的太學術。其實這句話通俗的來講就是:JavaScript中全部的function都是一個閉包。不過通常來講,嵌套的function所產生的閉包更爲強大,也是大部分時候咱們所謂的「閉包」。看下面這段代碼:
function a() {
var i = 0;
function b() { alert(++i); }
return b;
}
var c = a();
c();
這段代碼有兩個特色:
一、函數b嵌套在函數a內部;
二、函數a返回函數b。
引用關係如圖:
這樣在執行完var c=a()後,變量c其實是指向了函數b,再執行c()後就會彈出一個窗口顯示i的值(第一次爲1)。這段代碼其實就建立了一個閉包,爲何?由於函數a外的變量c引用了函數a內的函數b,就是說:
當函數a的內部函數b被函數a外的一個變量引用的時候,就建立了一個閉包。
讓咱們說的更透徹一些。所謂「閉包」,就是在構造函數體內定義另外的函數做爲目標對象的方法函數,而這個對象的方法函數反過來引用外層函數體中的臨時變量。這使得只要目標 對象在生存期內始終能保持其方法,就能間接保持原構造函數體當時用到的臨時變量值。儘管最開始的構造函數調用已經結束,臨時變量的名稱也都消失了,但在目 標對象的方法內卻始終能引用到該變量的值,並且該值只能通這種方法來訪問。即便再次調用相同的構造函數,但只會生成新對象和方法,新的臨時變量只是對應新 的值,和上次那次調用的是各自獨立的。
2、閉包有什麼做用?
簡而言之,閉包的做用就是在a執行完並返回後,閉包使得Javascript的垃圾回收機制GC不會收回a所佔用的資源,由於a的內部函數b的執行須要依賴a中的變量。這是對閉包做用的很是直白的描述,不專業也不嚴謹,但大概意思就是這樣,理解閉包須要按部就班的過程。
在上面的例子中,因爲閉包的存在使得函數a返回後,a中的i始終存在,這樣每次執行c(),i都是自加1後alert出i的值。
那 麼咱們來想象另外一種狀況,若是a返回的不是函數b,狀況就徹底不一樣了。由於a執行完後,b沒有被返回給a的外界,只是被a所引用,而此時a也只會被b引 用,所以函數a和b互相引用但又不被外界打擾(被外界引用),函數a和b就會被GC回收。(關於Javascript的垃圾回收機制將在後面詳細介紹)
3、閉包內的微觀世界
若是要更加深刻的瞭解閉包以及函數a和嵌套函數b的關係,咱們須要引入另外幾個概念:函數的執行環境(excution context)、活動對象(call object)、做用域(scope)、做用域鏈(scope chain)。以函數a從定義到執行的過程爲例闡述這幾個概念。
到此,整個函數a從定義到執行的步驟就完成了。此時a返回函數b的引用給c,又函數b的做用域鏈包含了對函數a的活動對象的引用,也就是說b能夠訪問到a中定義的全部變量和函數。函數b被c引用,函數b又依賴函數a,所以函數a在返回後不會被GC回收。
當函數b執行的時候亦會像以上步驟同樣。所以,執行時b的做用域鏈包含了3個對象:b的活動對象、a的活動對象和window對象,以下圖所示:
如圖所示,當在函數b中訪問一個變量的時候,搜索順序是:
小結,本段中提到了兩個重要的詞語:函數的定義與執行。文中提到函數的做用域是在定義函數時候就已經肯定,而不是在執行的時候肯定(參看步驟1和3)。用一段代碼來講明這個問題:
function f(x) {
var g = function () { return x; }
return g;
}
var h = f(1);
alert(h());
這段代碼中變量h指向了f中的那個匿名函數(由g返回)。
若是第一種假設成立,那輸出值就是undefined;若是第二種假設成立,輸出值則爲1。
運行結果證實了第2個假設是正確的,說明函數的做用域確實是在定義這個函數的時候就已經肯定了。
4、閉包的應用場景
保護函數內的變量安全。以最開始的例子爲例,函數a中i只有函數b才能訪問,而沒法經過其餘途徑訪問到,所以保護了i的安全性。
function Constructor(...){
var that = this;
var membername = value;
function membername(...){...}
}
以上3點是閉包最基本的應用場景,不少經典案例都源於此。
5、Javascript的垃圾回收機制
在Javascript中,若是一個對象再也不被引用,那麼這個對象就會被GC回收。若是兩個對象互相引用,而再也不被第3者所引用,那麼這兩個互相引用的對象也會被回收。由於函數a被b引用,b又被a外的c引用,這就是爲何函數a執行後不會被回收的緣由。
5.jQuery中判斷元素是否包含某個類名的方法是
在jquery中可使用2種方法來判斷一個元素是否包含一個肯定的類(class)。兩種方法有着相同的功能。2種方法以下:
1. is(‘.classname’)
2. hasClass(‘classname’)
如下是一個div元素是否包含一個redColor的例子:
$('div').is('.redColor')
$('div').hasClass('redColor')
$("#isTest").click(function () {
if($('div').is('.redColor')){
$('div').addClass('blueColor');
}
});
$("#hasClassTest").click(function () {
if($('div').hasClass('redColor')){
$('div').addClass('blueColor'); }
}
6.如下運行的結果是false的是 function Box(){this.name='zhang';} function Desk(){this.age=100;} function Table(){this.lever=1000} Desk.prototype=new Box();//經過原型鏈繼承 var desk=new Desk(); var table=new Table();
A. 一切事物皆對象
B. Dest 繼承了 Box, 因此正確
C. Desk 是 Function的實例,和Box無關
D. desk 是 Desk 的實例
7.如下關於jquery的說法正確的是
Dom原生對象和jQuery對象到底有什麼聯繫和區別呢
聯繫---二者之間可互相轉換
一、jQuery對象能夠經過jQuery包裝DOM對象後產生;
二、DOM對象也能夠經過jQuery按索引取得;
區別---兩個對象徹底不一樣
一、jQuery選擇器獲得的jQuery對象和標準的 javascript中的document.getElementById()取得的dom對象是兩種不一樣的對象類型,二者不等價;
二、jQuery沒法使用DOM對象的任何方法,同理DOM對象也不能使用jQuery裏的方法,上邊報錯就是這樣的。
______
A、DOM對象轉成jQuery對象:
對於DOM對象,只需用 $() 把DOM對象包裝起來,就可獲得jQuery對象
var dom =document.getElementById("id"); // DOM對象
var $dom = $(dom); // jQuery對象
B、jQuery對象轉成DOM對象:兩種轉換方式 [index] 和 .get(index)
1.jQuery對象是一個數據對象,經過 [index] 的方法
var $dom = $("#id") ; // jQuery對象
var dom = $dom [0]; // DOM對象
2.jQuery提供方法,經過 .get(index) 方法
var $dom = $("#id"); // jQuery對象
var dom = $dom.get(0); // DOM對象
轉載請註明,原文連接:http://zl378837964.iteye.com/blog/2327825
DOM對象和js對象以及jQuery對象的區別
文檔對象模型簡稱DOM,是W3C組織推薦的處理可擴展置標語言的標準編程接口。
jQuery對象實際上是一個JavaScript的數組,這個數組對象包含125個方法和4個屬性
4個屬性分別是
jquery對象就是經過jQuery包裝DOM對象後產生的對象。jQuery對象是jQuery獨有的,其可使用jQuery裏的方法,可是不能使用DOM的方法;反過來Dom對象也不能使用jquery的方法
jQuery對象和js對象區別:
1.jQuery對象屬於js的數組;
2.jQuery對象是經過jQuery包裝的DOM對象後產生的;
3.jQuery對象不能使用DOM對象的方法和屬性
4.DOM對象不能使用jQuery對象的方法和屬性
1) js轉jQuery對象:
$(js對象)
2)jQuery對象轉js對象
示例:
var doc2=$("#idDoc2")[0];
//轉換jQuery對象爲DOM對象
doc2.innerHTML="這是jQuery的第一個DOM對象"
//使用jQuery對象自己提供的get函數來返回指定集合位置的DOM對象
var doc2=$("#idDoc2").get(0);
doc2.innerHTML="這是jQuery的第二個DOM對象"
8. 如下說法正確的是
類 :對一羣具備相同特徵的對象的集合的描述;
對象:真實存在的對象個體;
所謂的面向對象,而不是面向類。
1.一切皆對象,繼承靠原型鏈,多態靠弱類型,封裝……雖然能夠靠閉包,但我我的更推崇和python同樣的,下劃線表明私有的風格
2.好比人類,指的是一個範圍; 對象:好比某我的,指的是這個範圍中具體的對象
3.Javascript中的function做爲構造函數時,就是一個類,搭配上new操做符,能夠返回一個對象。
固然,要生成一個對象,也能夠用字面量的形式,例如var obj = {x: 1, y: function(){} };
類能夠理解爲一個模板,而對象就是根據這個模板造出來的具體實例。
instanceof 判斷一個對象是否是屬於一個類
對象 instanceof 構造函數
本身的父級 父級 。。。。
constructor 判斷直接的父級
1. 前言
做爲一名前端工程師,必須搞懂JS中的prototype、__proto__與constructor屬性,相信不少初學者對這些屬性存在許多困惑,容易把它們混淆,本文旨在幫助你們理清它們之間的關係並完全搞懂它們。這裏說明一點,__proto__屬性的兩邊是各由兩個下劃線構成(這裏爲了方便你們看清,在兩下劃線之間加入了一個空格:_ _proto_ _,讀做「dunder proto」,「double underscore proto」的縮寫),實際上,該屬性在ES標準定義中的名字應該是[[Prototype]],具體實現是由瀏覽器代理本身實現,谷歌瀏覽器的實現就是將[[Prototype]]命名爲__proto__,你們清楚這個標準定義與具體實現的區別便可(名字有所差別,功能是同樣的),能夠經過該方式檢測引擎是否支持這個屬性:Object.getPrototypeOf({__proto__: null}) === null。本文基於谷歌瀏覽器(版本 72.0.3626.121)的實驗結果所得。
如今正式開始! 讓咱們從以下一個簡單的例子展開討論,並配以相關的圖幫助理解:
function Foo() {...};
let f1 = new Foo();
以上代碼表示建立一個構造函數Foo(),並用new關鍵字實例化該構造函數獲得一個實例化對象f1。這裏稍微補充一下new操做符將函數做爲構造器進行調用時的過程:函數被調用,而後新建立一個對象,而且成了函數的上下文(也就是此時函數內部的this是指向該新建立的對象,這意味着咱們能夠在構造器函數內部經過this參數初始化值),最後返回該新對象的引用。雖然是簡簡單單的兩行代碼,然而它們背後的關係倒是錯綜複雜的,以下圖所示:
看到這圖別怕,讓咱們一步步剖析,完全搞懂它們!
圖的說明:右下角爲圖例,紅色箭頭表示__proto__屬性指向、綠色箭頭表示prototype屬性的指向、棕色實線箭頭表示自己具備的constructor屬性的指向,棕色虛線箭頭表示繼承而來的constructor屬性的指向;藍色方塊表示對象,淺綠色方塊表示函數(這裏爲了更好看清,Foo()僅表明是函數,並非指執行函數Foo後獲得的結果,圖中的其餘函數同理)。圖的中間部分即爲它們之間的聯繫,圖的最左邊即爲例子代碼。
2. _ _ proto _ _ 屬性
首先,咱們須要牢記兩點:①__proto__和constructor屬性是對象所獨有的;② prototype屬性是函數所獨有的。可是因爲JS中函數也是一種對象,因此函數也擁有__proto__和constructor屬性,這點是導致咱們產生困惑的很大緣由之一。上圖有點複雜,咱們把它按照屬性分別拆開,而後進行分析:
第一,這裏咱們僅留下 __proto__ 屬性,它是對象所獨有的,能夠看到__proto__屬性都是由一個對象指向一個對象,即指向它們的原型對象(也能夠理解爲父對象),那麼這個屬性的做用是什麼呢?它的做用就是當訪問一個對象的屬性時,若是該對象內部不存在這個屬性,那麼就會去它的__proto__屬性所指向的那個對象(能夠理解爲父對象)裏找,若是父對象也不存在這個屬性,則繼續往父對象的__proto__屬性所指向的那個對象(能夠理解爲爺爺對象)裏找,若是還沒找到,則繼續往上找…直到原型鏈頂端null(能夠理解爲原始人。。。),再往上找就至關於在null上取值,會報錯(能夠理解爲,再往上就已經不是「人」的範疇了,找不到了,到此結束,null爲原型鏈的終點),由以上這種經過__proto__屬性來鏈接對象直到null的一條鏈即爲咱們所謂的原型鏈。
3. prototype屬性
第二,接下來咱們看 prototype 屬性:
prototype屬性,別忘了一點,就是咱們前面提到要牢記的兩點中的第二點,它是函數所獨有的,它是從一個函數指向一個對象。它的含義是函數的原型對象,也就是這個函數(其實全部函數均可以做爲構造函數)所建立的實例的原型對象,由此可知:f1.__proto__ === Foo.prototype,它們兩個徹底同樣。那prototype屬性的做用又是什麼呢?它的做用就是包含能夠由特定類型的全部實例共享的屬性和方法,也就是讓該函數所實例化的對象們均可以找到公用的屬性和方法。任何函數在建立的時候,其實會默認同時建立該函數的prototype對象。
4. constructor屬性
最後,咱們來看一下 constructor 屬性:
constructor屬性也是對象才擁有的,它是從一個對象指向一個函數,含義就是指向該對象的構造函數,每一個對象都有構造函數(自己擁有或繼承而來,繼承而來的要結合__proto__屬性查看會更清楚點,以下圖所示),從上圖中能夠看出Function這個對象比較特殊,它的構造函數就是它本身(由於Function能夠當作是一個函數,也能夠是一個對象),全部函數和對象最終都是由Function構造函數得來,因此constructor屬性的終點就是Function這個函數。
感謝網友的指出,這裏解釋一下上段中「每一個對象都有構造函數」這句話。這裏的意思是每一個對象均可以找到其對應的constructor,由於建立對象的前提是須要有constructor,而這個constructor多是對象本身自己顯式定義的或者經過__proto__在原型鏈中找到的。而單從constructor這個屬性來說,只有prototype對象纔有。每一個函數在建立的時候,JS會同時建立一個該函數對應的prototype對象,而函數建立的對象.__proto__ === 該函數.prototype,該函數.prototype.constructor===該函數自己,故經過函數建立的對象即便本身沒有constructor屬性,它也能經過__proto__找到對應的constructor,因此任何對象最終均可以找到其構造函數(null若是當成對象的話,將null除外)。以下:
5. 總結
總結一下:
咱們須要牢記兩點:①__proto__和constructor屬性是對象所獨有的;② prototype屬性是函數所獨有的,由於函數也是一種對象,因此函數也擁有__proto__和constructor屬性。
__proto__屬性的做用就是當訪問一個對象的屬性時,若是該對象內部不存在這個屬性,那麼就會去它的__proto__屬性所指向的那個對象(父對象)裏找,一直找,直到__proto__屬性的終點null,再往上找就至關於在null上取值,會報錯。經過__proto__屬性將對象鏈接起來的這條鏈路即咱們所謂的原型鏈。
prototype屬性的做用就是讓該函數所實例化的對象們均可以找到公用的屬性和方法,即f1.__proto__ === Foo.prototype。
constructor屬性的含義就是指向該對象的構造函數,全部函數(此時當作對象了)最終的構造函數都指向Function。
jQuery中提供了四種事件監聽方式,分別是bind、live、delegate、on,對應的解除監聽的函數分別是unbind、die、undelegate、off。
bind(type,[data],function(eventObject))
bind是使用頻率較高的一種,做用就是在選擇到的元素上綁定特定事件類型的監聽函數,參數的含義以下:
type:事件類型,如click、change、mouseover等; data:傳入監聽函數的參數,經過event.data取到。可選; function:監聽函數,可傳入event對象,這裏的event是jQuery封裝的event對象,與原生的event對象有區別,使用時須要注意
bind的源碼:
bind: function( types, data, fn ) { return this.on( types, null, data, fn ); } $('#myol li').bind('click',getHtml);
bind的特色就是會把監聽器綁定到目標元素上,有一個綁一個,在頁面上的元素不會動態添加的時候使用它沒什麼問題。但若是列表中動態增長一個「列表元素5」,點擊它是沒有反應的,必須再bind一次才行。要想不這麼麻煩,咱們可使用live。
live(type, [data], fn)
live的參數和bind同樣
live: function( types, data, fn ) { jQuery( this.context ).on( types, this.selector, data, fn ); return this; }
能夠看到live方法並無將監聽器綁定到本身(this)身上,而是綁定到了this.context上了。這個context是什麼東西呢?其實就是元素的限定範圍
$('#myol li').context; //document $('#myol li','#myol').context; //document $('#myol li',$('#myol')[0]); //ol
一般狀況下,咱們都不會像第三種方式那樣使用選擇器,因此也就認爲這個context一般就是document了,即live方法把監聽器綁定到了 document上了。不把監聽器直接綁定在元素上,你是否是想起事件委託機制來了呢?live正是利用了事件委託機制來 完成事件的監聽處理,把節點的處理委託給了document。在監聽函數中,咱們能夠用event.currentTarget來獲取到當前捕捉到事件的 節點。下面的例子來揭曉:
$('#myol li').live('click',getHtml);
live存在那樣的缺點,因此咱們就思考,既然老爺子負擔那麼重,可不能夠別把監聽器綁定在document上呢,綁定在就近的父級元素上不就行了。順應正常邏輯,delegate誕生了。
參數多了一個selector,用來指定觸發事件的目標元素,監聽器將被綁定在調用此方法的元素上。看看源碼:
delegate: function( selector, types, data, fn ) { return this.on( types, selector, data, fn ); }
又是調用了on,而且把selector傳給了on。看來這個on真的是舉足輕重的東西。照樣先無論它。看看示例先:
$('#myol').delegate('li','click',getHtml);
看了這麼多,你是否是火燒眉毛想看看這個on的真實面目了呢,這就來:
on(type,[selector],[data],fn)
參數與delegate差很少但仍是有細微的差異,首先type與selector換位置了,其次selector變爲了可選項。交換位置的緣由很差查證,應該是爲了讓視覺上更舒服一些吧。
咱們先不傳selector來看一個例子:
$('#myol li').on('click',getHtml);
能夠看到event.currentTarget是li本身,與bind的效果同樣。至於傳selector進去,就是跟delegate同樣的意義了,除了參數順序不一樣,其餘徹底同樣。