JS 紀錄3 - event、iterator

JavaScript event、iterator 紀錄

事件 event

DOM 語句

1
document.querySelector(‘input’).textContent=‘hello’
  • document
    • 取的html文件
  • querySelector
    • 找到我要的元素
  • (‘input’)
    • 找到 input 元素
  • textContent
    • 取得這個元素的文字內容
  • =‘hello’
    • 把內容設成 ’hello’
  • = morningAlarm;
    • 把內容設成變數
  • oText.innerHTML=index // 標籤中的內容:元素.innerHTML。(網頁tag)
  • oTeat.value=index // 屬性的寫入操作 (網頁表單類)

  • on-event 對應的 function 指的是「事件的處理器」

    • btn.onclick = function(){}
    • 只能綁定一個事件
  • addEventListener() 這個「事件的監聽器」基本上有三個參數,
    • 「事件名稱」
    • 「事件的處理器」(事件觸發時執行的 function)
    • Boolean」值,由這個 Boolean 決定事件是以「捕獲 true」或「冒泡 false」機制執行,若不指定則預設為「冒泡 false
    • 可綁定多個事件

事件流程可以分成兩種機制

  • 事件冒泡 (Event Bubbling)
  • 事件捕獲 (Event Capture)
  • Capture 或 Bubbling 誰先誰後呢?
    • 要看程式碼的順序而定

情境

  • 點擊 <td>

title
圖片來源: W3C, DOM event flow

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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
HTML
<div>
<div id="parent">
父元素
<div id="child">子元素</div>
</div>
</div>

JS
// 父元素
var parent = document.querySelector('#parent');
// 子元素
var child = document.querySelector('#child');


// 第三個參數 true / false 分別代表 捕獲/ 冒泡 機制

parent.addEventListener('click', function () {
console.log('Parent Capturing');
}, true);
// "Parent Capturing” 捕獲 上~下

parent.addEventListener('click', function () {
console.log('Parent Bubbling');
}, false);
// "Parent Bubbling” 冒泡 下~上


child.addEventListener('click', function () {
console.log('Child Capturing');
}, true);
// "Parent Capturing” 捕獲 上~下
// "Child Capturing"
// "Parent Bubbling"


child.addEventListener('click', function () {
console.log('Child Bubbling');
}, false);
// "Parent Capturing” 冒泡 下~上
// "Child Capturing"
// "Child Bubbling"
// "Parent Bubbling"

阻擋事件冒泡傳遞 event.stopPropagation()

  • 阻擋事件向上冒泡傳遞
  • 要在元素與元素重疊的情況下使用其中一個元素事件
  • 在 jQuery 的 event handler 最後加上 return false 來得到 preventDefault() 與 stopPropagation() 的效果
  • 但在 JavaScript 的 addEventListener() 裡,最後面加上 return false 只會有 preventDefault() 的效果,不會有 stopPropagation() 的作用
  • IE 兼容性寫法
1
e.stopPropagation ? e.stopPropagation() : e.cancelBubble = true;
1
2
3
4
5
6
7
8
9
// 阻止冒泡 不會在回到父元素
child.addEventListener('click', function () {
e.stopPropagation()
console.log('Child Capturing');
}, false);

// "Parent Capturing” 冒泡 下~上
// "Child Capturing"
// "Child Bubbling"

事件指派 (Event Delegation)

  • 用父元素偵測子元素的作法,這樣才不會綁那麼多監聽導致效能低落
  • 判斷 e.target 是不是我們要的目標節點
  • e 是紀錄當執行該事件的資訊
  • target 永遠指向觸發事件的 DOM 物件
  • 處理多個事件目標
1
2
3
4
5
6
7
8
9
10
HTML
<ul class = "parent">父
<li class="child">子</li>
</ul>

JS
parent.addEventListener('click',function(e){
if(e.target.nodeName!=="LI"){return} // 不是 LI 就回傳空白
console.log(e.target.textContent)
}) // 點擊 parent 不會有反應 點擊 child 顯示 ‘子'

阻擋預設行為 event.preventDefault()

  • HTML 部分元素會有預設行為,像是 <a> 的連結,或是表單的 submit 等等…
  • 如果要在這些元素上綁定事件,那麼適當地取消它們的預設行為就是很重要的一件事
1
2
3
4
5
6
7
8
9
HTML
<a id="link" href="https://www.google.com">Google</a>

JS
let a = document.querySelector('#link')
a.addEventListener('click',function(e){
e.preventDefault() // 執行的是 console 而不是直接帶去 google 的網站
console.log('Google!');
})

事件迴圈 even.loop

  • 通過決定何時調用函數以及如何處理異步事件來使用事件循環來創建更流暢的瀏覽體驗
  • 執行方式
    • 簡單
      • 也就是當同步與異步一起執行的時候,同步不用理會異步,異步等同步執行完之後才會執行
    • 詳細
      • 運行程序時,會進行函數調用並將其添加到堆疊(Stack)
      • 需要等待服務器響應請求的函數回傳它的值與狀態回來然後被發送到單獨的佇列(Queue)堆疊(Stack)清除後,將執行佇列(Queue)中請求的函數功能拉到堆疊(stack)中去執行
    • 原則
      • 當只有在堆疊(Stack)空空如也時,才會把佇列(Queue)中任務移回堆疊(Stack)

同步(Synchronous, sync)與異步(Asynchronous, async)

  • 同步(Synchronous, sync)

    • 指程式碼的執行順序,都是由上往下依順序執行,一個執行程序完成後才會再接著下一個
    • 但對於 JavaScript 只有單執行緒的程式語言,容易造成阻塞(blocking)
    • 也就是說當這個資料庫查詢的執行程序,需要很長的一段時間才能結束時,在這期間其他的操作都會停擺,像是滑鼠要點按按鈕之類的功能,就完全沒有作用
  • 異步(Asynchronous, async)

    • 先往佇列(task queue)丟,在之後的某個時間再回傳它的值與狀態回來
    • 不是所有的 callback(回調)函式都是會丟到任務佇列(task queue)之中執行,只有經過特殊設計過的異步callback(回調)才會這樣作
    • 含有 callback(回調) function 由上到下執行
      • 會先將 setTimeout 中的 callback function(簡稱 cb)放到 WebAPIs 的計時器中,當時間到時,把該 cb 放到佇列(task queue)內,在「等到」所有堆疊(stack)的內容都被清空(堆疊當中沒有執行項目的時候,event loop 便把佇列(task queue)中的內容拉到堆疊(stack)中去執行),在「立即」執行這個 cb
      • event loop 的作用是去監控堆疊(stack)佇列(task queue),當堆疊當中沒有執行項目的時候,便把佇列中的內容拉到堆疊中去執行
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 1 同步執行項目輸出 hi,結束
console.log('hi’)

/* 異步
cb -> callback function
1 setTimeout(cb,5000) -> 2 cb 在 WebAPIs 等待 5000ms
-> 3 cb加入到 task queue -> 1 stack 無執行項目 cb 輸出 there,結束
*/
setTimeout(function () {
console.log('there’)
}, 5000)

// 1 同步執行項目輸出 JSConfEU,結束
console.log('JSConfEU’)

title

事件

  • 可能會有一個以上的任務佇列(task queue) 以下是幾種會包含的任務:
    • Events(事件): EventTarget 物件異步分派到對應的 Events 物件
    • Parsing(解析): HTML parser
    • Callbacks(回調): 呼叫異步回調函式
    • 使用外部資源: 資料庫、檔案、Web I/O
    • DOM處理的反應: 回應DOM處理時的元素對應事件
  • 少數幾個內建的 API 與相關物件有類似的異步機制:
    • setTimeout
    • setInterval
    • XMLHttpRequest
    • requestAnimationFrame
    • WebSocket
    • Worker
    • 某些 HTML5 API,例如 File API、Web Database API
    • 有使用 onload 的 API
  • 異步的程式流程的組織方式,現在也有好幾種作法:
    • Promise 語法結構(ES6)
    • Generators (ES6)
    • Async 函式(ES7)

遍歷器 iterator

title

  • iterator 是一種接口,為不同的數據結構提供統一的訪問機制
  • iterator 主要供 for…of 使用
  • ES6 中提供了一些具備原生 iterator 的數據結構( 包括 Array、Map、Set、String、TypedArray、函數的 arguments 、NodeList,不包括 Object),部署在數據結構的 Symbol.iterator 屬性中
  • 所以也可以說,一種數據結構只要有 Symbol.iterator 屬性,就稱之為可遍歷

iterator 的遍歷過程

  • 創建一個對象,指向當前數據結構的起始位置
  • 第一次調用對象的 next(),可以指向數據結構的第一個成員
  • 第二次調用對象的 next(),就指向數據結構的第二個成員
  • 不斷調用對象的 next(),直到它指向數據結構的結束位置

next()

  • next() 必須總是回傳一個包含符合 done 及 value 屬性的物件
    • 假如回傳了一個非物件值(如 false 或 undefined),則將會拋出一個 TypeError 錯誤
    • done(布林值)
      • 若迭代完整個序列,則值為 true。在這個情況下 value 可以是代表迭代器的回傳值
      • 若迭代器能夠產出序列中的下一個值,則值為 false。相當於完全不指定 done 屬性
    • value - 任何由迭代器所回傳的 JavaScript 值
      • 可於 done 為 true 時省略
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
const arr = [1,4,2];
const iter = arr[Symbol.iterator]();
console.log(iter.next()); // {value: 1, done: false}
console.log(iter.next()); // {value: 4, done: false}
console.log(iter.next()); // {value: 2, done: false}
console.log(iter.next()); // {value: undefined, done: true}

var it = makeIterator(['a', 'b']);
function makeIterator(array) {
var nextIndex = 0;
return {
next: function() {
return nextIndex < array.length ?
{value: array[nextIndex++], done: false} :
{value: undefined, done: true};
}
};
}
console.log(it.next()) // { value: "a", done: false }
console.log(it.next()) // { value: "b", done: false }
console.log(it.next()) // { value: undefined, done: true }

for

1
2
3
4
let iterable = "boo";
for (let i=0;i<iterable.length;i++){
console.log(i+iterable[i]) // '0b','1o','2o'
}

for..in

  • 使用範圍
    • 把該物件中的所有屬性名稱屬性值都呼叫出
    • 透過建構函式(constructor function )來建立物件時,這個物件可能會繼承該函式建構式的一些屬性或方法,這時候直接使用 for…in 時,這些繼承而來的屬性和方法也會被一併輸出
  • object
1
2
3
4
5
6
7
8
var obj = {
a: 1,
b: [],
c: function () {}
};
for (var key in obj) {
console.log(key); // 'a' 'b' 'c'
}
  • array
    • 得到 index
    • 得到 value
    • 得到 index , value
    • 得到 index , object prototype 無法取值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
var arr = [3, 5, 7];
for (var i in arr) {
console.log(i); // 0 1 2
}

var arr = [3, 5, 7];
for (var i in arr) {
console.log(arr[i]); // 3 5 7
}

var arr = [3, 5, 7];
for (var i in arr) {
console.log(i, arr[i]); // '0' 3 , '1' 5 , '2' 7
}

Object.prototype.objCustom = function(){};
Array.prototype.arrCustom = function(){};
var arr = [3, 5, 7];
arr.foo = 'hello';
for (var i in arr) {
console.log(i); // “0" “1" “2" “foo" “arrCustom" "objCustom"
}
  • hasOwnProperty
    • 不會往上檢查物件的原型鏈 (prototype chain),只會檢查物件本身是否存在這個屬性
    • Array index 依然存在無法取值
1
2
3
4
5
6
7
8
9
Object.prototype.objCustom = function(){}; 
Array.prototype.arrCustom = function(){};
var arr = [3, 5, 7];
arr.foo = 'hello';
for (var i in arr) {
if (arr. hasOwnProperty (i)) {
console.log(i); // 0 1 2 foo
}
}

forEach

  • forEach(function(item(參數項目), index(參數索引),array(陣列本身)){})
  • forEach 的時候是無法 break 或者 return false 中斷
1
2
3
4
5
6
7
var arr = [3, 5, 7];
arr.forEach(function (value) {
console.log(value); // 3 5 7
if (value == 5) {
return false;
}
});

for…of

  • 目的通常是為了逼近所需目標或結果(魔術方塊的概念)
  • 每一次對過程的重複稱為一次“迭代”,而每一次迭代得到的結果會作為下一次迭代的初始值。重複執行一系列運算步驟
  • 使用範圍
    • Array、Set 和 Map 結構、類Array(比如 arguments、NodeList)、Generator 對象,以及 String
    • 中斷 for…of,可以使用 break、continue、throw 或 return
  • Array
    • 得到 value
1
2
3
4
5
6
7
8
9
10
11
12
let obj = [10,20,30]
for (var value of obj) {
console.log(value); // 10 20 30
}

var arr = [3, 5, 7];
for (let value of arr) {
console.log(value); // 3 5
if (value == 5) {
break;
}
}
  • String
1
2
3
4
let iterable = "boo";
for (let value of iterable) {
console.log(value); // 'b' 'o' 'o'
}
  • Map
    • 會 return 回處理好的值,為新的 array
    • people.map(function( item, index, array ){})
1
2
3
4
5
6
7
8
let iterable = new Map([["a", 1], ["b", 2], ["c", 3]]);
for (let [key, value] of iterable) {
console.log(value); // 1 2 3
}

for (let entry of iterable) {
console.log(entry); // ["a", 1] ["b", 2] ["c", 3]
}
  • Set
    • new Set([iterable])
    • 類陣列,但是成員的值都是唯一的,沒有重複的值
    • 如果要轉為陣列,用 Array.form
1
2
3
4
let iterable = new Set([1, 1, 2, 2, 3, 3]);
for (let value of iterable) {
console.log(value); // 1 2 3
}
  • arguments
1
2
3
4
5
(function() {
for (let argument of arguments) {
console.log(argument); // 1 2 3
}
})(1, 2, 3);
  • NodeList
    • 原先用 Array.from(querySelectorAll) 轉成 array 在用 forEach 取出值
1
2
3
4
let elements = document.querySelectorAll('body');
for (let element of elements) {
console.log(element.tagName); // body
}
  • enumerable 屬性
    • for–of 並不能直接使用在普通的對像上,但如果我們按對象所擁有的屬性進行循環,可使用內置的 Object.keys() ,取得物件的 key
1
2
3
for (var key of Object.keys(someObject)) {
console.log(key + ": " + someObject[key]);
}
  • TypedArray
1
2
3
4
5
let typeArr = new Uint8Array([0x00, 0xff]);

for (let value of typeArr) {
console.log(value); // 0 255
}
  • 循環一個生成器(generators)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
function* fibonacci() {        // a generator function
let [prev, curr] = [0, 1]; // 解構
while (true) {
[prev, curr] = [curr, prev + curr];
yield curr;
}
}

for (let n of fibonacci()) {
console.log(n); // truncate the sequence at 1000
if (n >= 1000) {
break;
}
}
0%