不知道大家最近有沒有發現下面多了一條東東

然後按了左邊的icon會跳出一個小視窗

如果沒有的話現在可以開起來玩看看

那是一個可以在裡面打javascript然後執行的一個小工具

有鑑於我們這個地方滿常分享一些js的東西

可能有時候讀者覺得很酷想要試試看

但是用瀏覽器的console又有點難打一些比較長的code

所以我就做了一個小小的玩具放在我們的blog

大家可以沒試開起來玩玩js

稍微筆記一下他是用了哪些很酷的東西來完成

Architecture

主要分為兩個部分

  • 編輯程式的打字框
  • 如何執行跟中止程式碼

乍看之下好像滿容易的

只要放一個<textarea>

然後用一個eval()

兩件事就輕鬆處理了

錯!事情其實沒有這麼單純的!

事情不是一個textarea這麼簡單就可以處理的

如果想在網頁裡放一個簡單可以打字的框框

那就直接一個<textarea>

但是如果想要讓這個打字框更猛一點

像是可以打出 粗體 斜體 不同顏色的字

或是像fb那像可以打@來tag人

Imgur

Imgur

有tag的文字背景還會藍藍的

這種效果用<textarea>是不到的

像是下面這個例子

1
2
3
<textarea>
<span style="background: blue">QQ</span>
</textarea>

完惹<textarea>完全看不懂你的<span>想對于子軒做什麼壞壞的事

另一種製做可編輯文字的方法

如果你用瀏覽器的開發者元件看的話

你會發現fb不是用<textarea>做的

Imgur

其實在html5裡面除了用<textarea>以外

還有用一個叫做contenteditable的屬性來製做可編輯的文字

只要在你的標籤多加一個contenteditable="true"

就可以讓他的內容變成可編輯的了

1
2
3
<div contenteditable="true">
<span style="background: blue">于子軒</span>超胖
</div>

看起來就像下面這樣

于子軒超胖 (按一下可以編輯)

馬上弄出一個div元件的文字框

這樣一來什麼css跟js

都可以很輕易的加到你的文字框裡

難以駕馭的魔鬼

Contenteditable能做到很多textarea做不到的事

乍看之下很猛

但是他有一些難以駕馭的地方

常常會發生

你看到的東西一樣

可是實際上的dom不一樣

例如今天有一個Hello world

Hello world

如果我的游標在兩個字中間然後按下enter

chrome會變成兩個<div>,而firefox則是在兩個字中間加<br>

兩個看起來都會像是

Hello
world

可是dom長得不太一樣

有的時候會造成debug上的困難

面對這種狀況

我們需要的是一個好的library來幫我們處理一些瑣事

拿我最常用的react來說

Draft.js就是react裡面在處理這種問題的Library

關於draft.js的介紹又是另一個複雜的故事了

以後有空再打一篇

這個project也是用了draft.js

一些像是code highlight

或是按tab會縮排的功能

做起來就是輕輕鬆鬆

執行打好的程式

大家可能會知道

在Javascript裡面要把一個字串當成程式來執行

可以使用eval()這個東西來做到

像是

1
2
var str = "console.log('yaya')";
eval(str);

但是如果有個人很惡劣

直接拿一個while(1){}塞給eval會發生什麼事呢

答案是 會 壞 掉

整個網頁會因為那個無限迴圈卡住

而且你還停不掉

如果有寫過android app

可能會知道一些繁重的工作像是http request

不能直接寫在main thread

要讓他在背景執行

才不會讓整個畫面卡住

而前端的javascript中也有類的東西可以使用

不要讓胖子卡住你的畫面

在前端的javascript中

有一個東西叫做Web worker

他可以讓一段javascript程式在背景執行

我在這裡的做法就是

先開一個thread在背景跑

要執行code的時候再丟給他去做eval()

這樣一來就算餵給他一坨屎 ex: while(1) {}

他也不會搞壞整個頁面

也可以直接kill掉整個thread

好像什麼事都沒發生過

Web worker的用法大概如下

1
2
3
4
5
var worker = new Worker('worker.js'); // 開一個新的worker
worker.postMessage('message'); // 利用postMessage來跟他溝通
worker.onmessage = function(event) { // 用onmessage來接收worker傳來的東西
console.log(event.data);
}

在worker.js中

1
2
3
4
onmessage = function(event) { // 設定onmessage來接收main傳來的東西
console.log(event.data);
postMessage('received'); // 也可以用postMessage回傳東西
}

更詳細的用法在這裡

至於後端的node.js

也有自己開thread的方法

這邊就不多做介紹了

是不是還少了什麼

沒錯,我們少了一個很重要的東西

就是要把剛剛執行的結果show出來

也就是要想辦法抓到console.log的東西

這裡用了一個叫做Monkey-patch的手段

就是一種自己對console.log加一個補丁

有一種攔截封包的感覺

那要怎麼用呢

比如說你有一個加法的function

1
2
3
function add(a, b) {
return a + b;
}

然後對他加點補丁

1
2
3
4
5
var oldAdd = add; // 把原本的add存起來
add = function(a, b) { // 宣告另一個function取代他
console.log(a, b); // 做壞壞的事
return oldAdd(a, b); // call原本的東西
};

這個時候再去呼叫這個add的時候

除了可以做原本的加法

他現在還會把你的參數給log出來

我在這裡對console.log做的也是類似的事

1
2
3
4
5
const log = console.log;
console.log = function (...args) {
postMessage(consoleLog(args.join(', '))); // worker把參數回傳給main thread
log.apply(console, args);
};

要注意的是這裡用了apply來呼叫函式

他有一點類似bind

是在指定你呼叫的函式裡面的this到底是什麼

大家可以試試

1
2
3
4
5
function a() {
console.log(this);
}
a();
a.apply(1);

就會比較清楚一點了

更詳細的說明在這裡

結語

本來只是想做一個小玩具而己

順便當做練習draft.js

結果就越寫發現越多東西

參考資料