教你優(yōu)雅的使用迭代器模式以及案例復(fù)盤
眼看新的一年又來(lái)了,為了提高程序員的幸福指數(shù), 我覺(jué)得設(shè)計(jì)模式還是非常有必要好好復(fù)盤一下的. 筆者基于工作中的總結(jié)和提煉,為了提高團(tuán)隊(duì)代碼質(zhì)量和可維護(hù)性,特意寫了幾篇設(shè)計(jì)模式的文章,供大家參考和學(xué)習(xí)。
你將學(xué)到
- 迭代器模式的含義
- 實(shí)現(xiàn)一個(gè)數(shù)組迭代器
- 實(shí)現(xiàn)一個(gè)對(duì)象迭代器
- 實(shí)現(xiàn)路徑查找/賦值迭代器
- 如何用迭代器的思想解決分支循環(huán)嵌套問(wèn)題
- 實(shí)現(xiàn)一個(gè)圖片播放器

正文
1.迭代器的含義
- 迭代器模式主要的思想就是在不暴露對(duì)象內(nèi)部結(jié)構(gòu)的同時(shí)可以按照一定順序訪問(wèn)對(duì)象內(nèi)部的元素。
其實(shí)javascript中的很多方法都運(yùn)用了迭代器的思想,比如數(shù)組的forEach,every,find,some,map,entries等等,這些操作極大的簡(jiǎn)化了我們的邏輯操作,接下來(lái)我們就來(lái)看看它的具體應(yīng)用吧。
2.實(shí)現(xiàn)一個(gè)數(shù)組迭代器
我們都知道javascript中數(shù)組的forEach方法,那么不用這個(gè)方法,我們能自己實(shí)現(xiàn)一個(gè)嗎?
- // 數(shù)組迭代器
- let eachArr = function(arr, fn) {
- let i = 0,
- len = arr.length;
- for(; i < len; i++) {
- if(fn.call(arr[i], i, arr[i]) === false) {
- break;
- }
- }
- }
- // 使用
- eachArr([1,2,3,4], (index, value) => { console.log(index, value) })
3.實(shí)現(xiàn)一個(gè)對(duì)象迭代器
對(duì)象迭代器和數(shù)組迭代器類似, 只是傳參不同,如下:
- // 對(duì)象迭代器
- let eachObj = function(obj, fn) {
- for(let key in obj) {
- if(fn.call(obj[key], key, obj[key]) === false) {
- break;
- }
- }
- }
- // 使用
- eachObj({a: 11, b: 12}, (key, value) => { console.log(key, value) })
4.實(shí)現(xiàn)路徑查找/賦值迭代器
有時(shí)候我們操作對(duì)象的某些屬性時(shí),我們不知道服務(wù)器端是否將該屬性或者該屬性的上級(jí)屬性正確的返回給我們,這個(gè)時(shí)候我們直接通過(guò)點(diǎn)語(yǔ)法或者[]語(yǔ)法直接訪問(wèn)會(huì)導(dǎo)致代碼報(bào)錯(cuò),因此需要我們每一層操作都要做安全校驗(yàn),這樣會(huì)產(chǎn)生大量臃腫代碼,比如:
- let obj = {};
- // 獲取 obj.num.titNum
- let titNum = obj.num.titNum; // 報(bào)錯(cuò)
- let titNum = obj && obj.num && obj.num.titNum; // 正確
我們通過(guò)迭代器可以極大的減少這種校驗(yàn),實(shí)現(xiàn)更健壯的代碼模式:
- let findObjAttr = function(obj, key){
- if(!obj || !key) {
- return undefined
- }
- let result = obj;
- key = key.split('.');
- for(let i =0; len = key.length; i< len; i++) {
- if(result[key[i]] !== undefined) {
- result = result[key[i]]
- }else {
- return undefined
- }
- }
- return result
- }
- // 使用
- let a = { b: { c: { d: 1 } } };
- findObjAttr(a, 'a.b.c.d') // 1
這種方式是不是有點(diǎn)類似于lodash的對(duì)象/數(shù)組查找器呢?同理,我們也可以實(shí)現(xiàn)路徑賦值器,如下所示:
- let setObjAttr = function(obj, key, value){
- if(!obj) {
- return false
- }
- let result = obj,
- key = key.split('.');
- for(let i =0, len = key.length; i< len - 1; i++){
- if(result[key[i]] === undefined) {
- result[key[i]] = {};
- }
- if(!(result[key[i]] instanceof Object)){
- // 如果第i層對(duì)應(yīng)的不是一個(gè)對(duì)象,則剖出錯(cuò)誤
- throw new Error('is not Object')
- return false
- }
- result = result[key[i]]
- }
- return result[key[i]] = val
- }
- // 使用
- setObjAttr(obj, 'a.b.c.d', 'xuxi')
5.如何用迭代器的思想解決分支循環(huán)嵌套問(wèn)題
分支循環(huán)嵌套的問(wèn)題主要是指在循環(huán)體中還需要進(jìn)行額外的判斷,如果判斷條件變多,將會(huì)造成嚴(yán)重的性能開銷問(wèn)題,如下面的例子:
- // 數(shù)據(jù)分組
- function group(name, num) {
- let data = [];
- for(let i = 0; i < num; i++){
- switch(name) {
- case 'header':
- data[i][0] = 0;
- data[i][1] = 1;
- break;
- case 'content':
- data[i][0] = 2;
- data[i][1] = 3;
- break;
- case 'footer':
- data[i][0] = 4;
- data[i][1] = 532;
- break;
- default:
- break;
- }
- }
- return data
- }
由以上分析可知,上面的代碼還有很多優(yōu)化空間,因?yàn)槊恳淮伪闅v都要進(jìn)行一次分支判斷,那么如果num變成100000,且name的種類有100種,那么我們就要做100000*100種無(wú)用的分支判斷,這樣無(wú)疑會(huì)讓你的代碼在大數(shù)據(jù)下卡死。不過(guò)我們可以通過(guò)以下這種方式優(yōu)化它:
- // 數(shù)據(jù)分組
- function group(name, num) {
- let data = [];
- let strategy = function() {
- let deal = {
- 'default': function(i){
- return
- },
- 'header': function(i){
- data[i][0] = 0;
- data[i][1] = 1;
- },
- 'content': function(i){
- data[i][0] = 2;
- data[i][1] = 3;
- },
- //...
- }
- return function(name) {
- return deal[name] || deal['default']
- }
- }();
- // 迭代器處理數(shù)據(jù)
- function _each(fn) {
- for(let i = 0; i < num; i++){
- fn(i)
- }
- }
- _each(strategy(name))
- return data
- }
這樣我們就能避免分支判斷,極大的提高了代碼效率和性能。
6.實(shí)現(xiàn)一個(gè)圖片播放器

圖片播放器主要有以上幾個(gè)功能,上一頁(yè),下一頁(yè),首頁(yè),尾頁(yè),自動(dòng)播放按鈕,停止按鈕。具體組件的設(shè)計(jì)機(jī)構(gòu)可以參考我寫的demo:
- // 圖片播放器
- let imgPlayer = function(imgData, box) {
- let container = box && document.querySelector(box) || document,
- img = container.querySelector('img'),
- // 獲取圖片長(zhǎng)度
- len = imgData.length,
- // 當(dāng)前索引值
- index = 0;
- // 初始化圖片
- img.src = imgData[0];
- var timer = null;
- return {
- // 獲取第一個(gè)圖片
- first: function() {
- index = 0
- img.src = imgData[index]
- },
- // 獲取最后一個(gè)圖片
- last: function() {
- index = len - 1
- img.src = imgData[index]
- },
- // 切換到前一張圖片
- pre: function() {
- if(--index > 0) {
- img.src = imgData[index]
- }else {
- index = 0
- img.src = imgData[index]
- }
- },
- // 切換到后一張圖片
- next: function() {
- if(++index < len) {
- img.src = imgData[index]
- }else {
- index = len - 1
- img.src = imgData[index]
- }
- },
- // 自動(dòng)播放圖片
- play: function() {
- timer = setInterval(() => {
- if(index > len - 1) {
- index = 0
- }
- img.src = imgData[index]
- index++
- }, 5000)
- },
- // 停止播放圖片
- stop: function() {
- clearInterval(timer)
- }
- }
- }
- // 使用
- let player = new imgPlayer(imgData, '#box')
總之,迭代器思想和其他設(shè)計(jì)模式的組合,可以設(shè)計(jì)出各種各樣高度配置的組件,所以說(shuō)學(xué)好并理解 javascript 設(shè)計(jì)模式的精髓,決定了我們的高度和態(tài)度。