第一次發技術文,這篇文章原文寫在我的Blog上,因為這邊不能直接寫Demo所以我改給JS Fiddle
的Demo連結。
以下正文開始
前天有人問了一題有趣的題目,內容是問要怎麼實作編輯器得上一步下一步動作,也是就是說要做一個步驟紀錄器,覺得沒做過這種東西覺得好像蠻有趣的,就來試試看。
原理很簡單,先建立一個Array
當作容器,每次輸入時使用就把資料塞進Array
中,在使用一個變數紀錄當前的步驟,上一步就是把資料還原上一個Array元素
的內容而已,是不是很簡單呢?XD
HTML的DOM事件只能監控你每次輸入的瞬間,假如說你輸入123
,這樣會產生3步,這樣感覺紀錄的太細膩,所以我們要做一個延遲,需要輸入後停止多久的時間,紀錄器才會把資料記錄下來。
當然紀錄器的空間要有限制,不是一直Array.push()
就好,這樣你會害使用者記憶體爆掉的....
所以我們一定要限制Array
的大小,對使用者來說就是可以紀錄的幾個步驟。
實作使用TextArea
來當示範,首先先建立2個Button
和TextArray
<button id="prev" type="button">上一步</button>
<button id="next" type="button">下一步</button>
<textarea id="text" cols="5"></textarea>
建立Array
和使用的變數
var step = 0; // 步驟變數
var textList = new Array(); // array容器
var prev = document.getElementById('prev');
var next = document.getElementById('next');
var text = document.getElementById('text');
寫一個事件,每個步驟都會把TextArea
的內容寫入textList
,並寫步驟+1
function textWrite(){
step++;
textList.push(text.value);
}
當然只有事件是不會自動執行的,此時我們需要監控TextArea
的輸入,並執行上面的事件
text.addEventListener('input', textWrite);
這邊開始就簡單了,上一步只要讀取上個Array
元素的資料並塞進TextArea
,下一步則是倒過來。
prev.onClick = function(){
// 判斷是否還有上一步
if(step < 2){
alert('沒有上一步');
}else{
step--;// 步驟往回一步
text.value = textList[step - 1]; // 因為Array從0開始所以要-1
}
}
next.onClick = function(){
if(step => textList.length){
alert('沒有下一步');
}else{
step++;
text.value = textList[step - 1];
}
}
這個就要使用我的上一篇文章,使用Lodash的_.debounce
就能實現了。
var debounce = _.debounce(textWrite, 500);// 延遲500毫秒紀錄
text.addEventListener('input', debounce);// 改成監聽延遲函數
要限制很簡單,只要儲存容器本身要限制寬度就行,這邊主要的另一個問題應該是超出範圍是該怎麼辦?
答案很簡單就是把現有資料向前移動就行,然後在最後一項資料再塞進新資料。
var textList = new Array(3); // 限制Array只能4個元素
function textWrite(){
// 判斷是否已到最大紀錄步驟
if(step === textList.length){
for(var i = 0;i < textList.length - 1; i++){
textList[i] = textList[i + 1];
}
textList[step - 1] = text.value;
} else {
step++;
textList[step] = text.value;
}
}
整體來說不難,最後一項步驟器的限制
難度有比較高,我在想資料位移時還一直想到腦筋打結,原本是for
遞減,後來想一想好像遞增比較好寫,
害我卡了很久,不過很多IT邦很多大大說編輯器很難寫是真的,雖然基本的觀念不是很難,但是細部的微調真的很難寫,例如我其時還少寫了一個步驟不在最後一步時,
要覆寫後面的步驟,實作起來應該是更難,我這邊就不做了(暈)。
原來 Lodash 有 _.debounce
這麼好用的東西!!
接續文章結尾,完成了 覆寫後面
的部分,並稍做修改將陣列做成環狀佇列,這樣滿了以後就不需要每次位移。
var prev = document.getElementById('prev');
var next = document.getElementById('next');
var text = document.getElementById('text');
var size = 6; //佇列大小,需空一個位置給佇列結尾
var front = 0; //佇列開頭
var real = 1; //佇列結尾的下一個位置
var curr = 0; //步驟當前位置
var queue = new Array(size); //佇列容器
queue[0] = ""; //設定第一個元素為空白
prev.onclick = function() {
//判斷是不是在開頭位置
if (curr === front){
alert('沒有上一步');
return;
}
//將當前位置移動到上一步
curr = (curr + size - 1) % size;
text.value = queue[curr];
}
next.onclick = function() {
//判斷是不是在結尾的位置
var next = (curr + 1) % size;
if (next === real){
alert('沒有下一步');
return;
}
//將當前位置移動到下一步
curr = next;
text.value = queue[curr];
}
text.addEventListener('input', function textWrite() {
//判斷當前位置是不是在結尾
var next = (curr + 1) % size;
if (next === real) {
//判斷佇列是否已滿
if ((real + 1) % size === front) {
//已滿則覆蓋開頭的紀錄
front = (front + 1) % size;
}
}
//設到當前位置到下個位置上
curr = next;
//將文字加入佇列
queue[curr] = text.value;
//將結尾設到當前位置的下個位置上(覆寫後面)
real = (curr + 1) % size;
});
結構圖:
補充:
後來想想其實並不能叫環狀佇列,因為沒有 先進先出
的概念,但循環的方式非常像,讓我很直覺的聯想到環狀佇列。
喔喔~~
太棒了明天早上來看看
看懂了
真的省略了覆蓋步驟耶
謝謝分享
還有queue[0] = "";
改成queue[0] = text.value;
比較好
哈對,[0]
用來放 textbox 的預設值。
感謝樓主
因為Array
是從0開始
其實也能寫成
text.value = textList[step];
step++;
只是我原本的寫法感覺比較直覺