2022年8月31日 星期三

JS Callback function - function 的執行順序

 目的 :  認識 callback function 的定義及用法

處理說明:  JavaScript 是一個「非同步」的語言,
當我們執行程式時, for 迴圈並不會等待處理作業(statement)結束後才繼續
而是在執行階段就一口氣將statement執行完

一. callback function 的定義:  call function , 只有滿足某個條件(event 發生時)才會被執行

    Callback function 跟一般的函式沒什麼不同, 差別在於被呼叫執行的時機

     Office.addEventListener( '電話響', function(){ /* 接電話 */ }, false);

Callback function就是「把函式當作另一個函式的參數,透過另一個函式來呼叫它

Ex1>   window.setTimeout( function(){ ... }, 1000);
Ex2>
  var handler = function() { /* 接電話 */ }; 
  Office.addEventListener( '電話響', handler, false);
Ex3:
var funcA = function(){
  console.log('function A');
};

var funcB = function(){
  console.log('function B');
};
funcA();
funcB();

         -->  先印  function A  , 再印 function B

Ex4:

         var funcA=function {
                   window.setTimeout(function(){  console.log('function A'); }, 
                    Math.random()*1000);
         }

         var funcB=function {
                   window.setTimeout(function(){console.log('function B'); }, 
                    Math.random()*1000);
         }
         funcA();
         funcB();
        --> 不確定 function A / function B 先印

Ex5: 確保 function A 先印, 再印 function B
         var funcA=function(par_callback) {
                   window.setTimeout(function(){
                    console.log('function A'); 
                    if (typeof par_callback="function"){
                        par_callback();  
                    }
                    }, 
                    Math.random()*1000);
         };
         var funcB=function(){
               window.setTimeout(function(){
                    console.log('function B'); 
                    }, 
                    Math.random()*1000);
         };
       funcA(funcB);
  
       --> 無論 funcA 在執行的時候要等多久, 
     funcB 都會等到 console.log('function A'); 之後才執行。


Ex6 : 在五秒鐘之內,每秒鐘依序透過 console.log 印出: 0 1 2 3 4

          for (var i=0;i<5;i++){
               window.setTimeout(function(){   console.log(i);  }
                    ,1000*i);
           };
           //執行結果 : 5 5 5 5 5  

--> JavaScript 是一個「非同步」的語言,執行程式時, 
   for迴圈並不會等待處理作業(statement , window.setTimeout )結束後才繼續
      而是在執行階段就一口氣將statement執行完              
--> 切分變數(i)有效範圍的最小單位是 "function"
--> 因為函式未宣告變數 i , 所以每次執行 console.log(i); 的 i 變數是去函式「外層」取得
      因 function() 內未宣告 var i, 所以會在函式外層取得 (此時for迴圈已跑完 i=5) , 所以印出 5
      for(var i =0; i<5;i++)   --> 宣告 i 為全域變數


二.IIFE(Immediately Invoked Function Expression, IIFE)

    一般宣告方式:  doit(x);
    IIFE宣告方式:  函式宣告當下即呼叫 , 傳入參數 i , 即傳入當時的 i 值(0,1,2,3,4,5)
           (function doit(x){ console.log(x);} ) (i)

--> for (var i=0;i<5;i++) 
          (function doit(x){ console.log(x);} ) (i);

JS Array 陣列宣告 - push,pop - unshift,shift

目的: 說明 陣列 (Array) 的宣告及使用

 陣列可以看作是一種特別的「物件」,同樣是零至多個元素的集合

陣列內可以是原始的資料類型、其他陣列、函式等等

陣列是個有順序性的集合,且只能透過 [] 來存取。

陣列的索引是由 0 開始計算的

一.陣列宣告的3個方法:

1>方法1:  new Array();

var a = new Array(); 
a[0] = "apple"; 
a[1] = "boy"; 
a[2] = "cat"; 
a.length; // 3

2>方法2:   []
var a = []; 
a[0] = "apple"; 
a[1] = "boy"; 
a[2] = "cat"; 
a.length; // 3

3>方法3:   ["apple","boy","cat"];
var a = ["apple", "boy", "cat"]; 
a.length; //3


二.陣列的新增/刪除

1>Array.legth 可以修改
var a = ["apple", "boy", "cat"]; 
a.length; // 3 
a.length = 1; 
console.log(a); // ["apple"] 
a.length = 3; 
console.log(a); // ["apple", undefined, undefined]

2>Array元素可以任意指定
var array = ['a', 'b', 'c']; 
array.length; // 3 
array[7] = 'z'; 
console.log(array); // ["a", "b", "c", undefined, undefined, undefined, undefined, "z"]

3>在陣列末端新增/移除元素時,可以透過 ARRAY.push() ; Array.pop()
var array = ['a', 'b', 'c']; 
array.length; // 3 
array.push('d'); 
console.log(array); // ['a', 'b', 'c', 'd']
array.pop();            //傳回 "d"
console.log(array); // ['a', 'b', 'c']

4>在陣列前端新增/移除元素時,可以透過 ARRAY.unshift() ; Array.shift()
var array=['a', 'b', 'c'];
var Tmp_el=array.shift();
console.log(array); // ['b','c']
Tmp_el=array.shift();
console.log(array); // ['c']
Tmp_el=array.unshift(4,"e");
console.log(array); // [4,"e",'c']

JS - - Object,Array的宣告 , Object 基本型別 及 物件型別 - Object 的建立 - 屬性的存取 - typeof 型別的判斷 - == ===


JavaScript 內建的型別主要可以分成基本型別 (Primitives) 與物件型別 (Object) 兩大類。 
基本型別又分成 string、number、boolean、null、undefined 幾種,
除了以上幾種之外,其他都可以歸類至物件型別 (Object)

一.物件的定義 - Object :
「An object is a collection of properties and has a single prototype object.」
一個物件可以是個零至多種屬性的集合,而屬性是鍵 (key) 與值 (value) 之間的關聯。 
一個屬性的「值」可以是某個基本型別,也可以是另一個物件,甚至可以是一個函數。
物件可以是瀏覽器預先定義好的,當然也可以是由自己定義物件的屬性與內容

1>Object 的宣告方式:
var person={}                                 //或是  var person=new Object();
person["NAME"]="蔡聰進";
person["GENDER"]="male";

2>Array的宣告方式:
var tempData=[];
tempData.push(rec);


1>建立 Object 的2種方法如下:
方法1> new Object() 方式,建立物件 , 再設定物件屬性
var person = new Object(); 
person.name = 'Kuro'; 
person.job = 'Front-end developer'; 
person.sayName = function() { alert( this.name ); };

方法2>  JSON 方式 , 建立物件
var person = { 
       name: 'Kuro', 
       job: 'Front-end developer', 
       sayName: function() { alert( this.name ); } 
};

 -->
直接用大括號 { },即可建立起一個新的物件,並且在建立物件的同時直接指定屬性至該物件內。
這種建立物件的方式稱為「物件實字 (Object literal)」,同時也是 JSON 格式的核心語法。



二.屬性的存取 , 建議用   Object["Name"]
存取屬性 的2種方法如下:
var person = { 
      name: 'Kuro', 
      job: 'Front-end developer', 
      sayName: function() { alert( this.name ); } ,
      "001" : "Hello",
}; 
1>由.Name存取
person.name;                   // 'Kuro' 
person.sayName();         // ƒ sayName()
person.001;                   // SyntaxError: Unexpected number 

2>由 [Name]存取
person["name"];             // 'Kuro' 
person["sayName"];      // ƒ sayName()
person["001"]              // Hello 

--> 屬性的新增及刪除
person["age"]=50;
console.log("person[age]]",person["age"]);  //50
delete person["age"];
console.log("person[age]]",person["age"]);  //undefined

--> 判斷屬性是否存在
方法1:  ==undefined
if (person["age"]!=undefined){
    console.log("property age exist");
}

方法2:  (age in person)  , true/false
if (age in person){
    console.log("property age exist");
}

方法3: person.hasOwnProperty('age')
if (person.hasOwnProperty('age')) {
    console.log("property age exist");
}

-->
if (person["name"]!="undefined"){
    console.log("1 property name exist");
};
if ("name" in person){
    console.log("2 property name exist");
};
     if (person.hasOwnProperty('name')) {
    console.log("3 property name exist");
};


三.型別的判斷 - typeof  -  typeof 運算子回傳的東西都是「字串」

typeof true; // 'boolean' 
typeof 'Kuro'; // 'string' 
typeof 123; // 'number' 
typeof NaN; // 'number' 
typeof { }; // 'object' 
typeof [ ]; // 'object' Array
typeof undefined; // 'undefined' 
typeof window.alert; // 'function' 
typeof null; // 'object'


--> typeof   Array   --> 得到 "object" , 非 array
      由 Array.isarray([]) 判斷  //true
       Array.isarray( [4,"a","b","c"] );  //true
       Array.isarray("abc");   //false


四.==   ===  比較
var a = 10; 
var b = "10"; 
console.log( a == b ); // true 
console.log( a === b ); // false

console.log(null == undefined) ; //true
console.log(null === undefined) ; //false

-->
在 JavaScript 這門程式語言中,大家會提倡盡量使用 === 來取代 == 的原因。
-->
!= 的版本會做自動轉型,而 !== 則不會做自動轉型。

JS function - var 變數宣告, 及scope變數的範圍 - 全域變數/區域變數

function 的宣告 , 如下兩種均可
       1>function doSomeThing(y){}
       2>var doSomeThing = function (y)  {}

Ex1> function 宣告, [函式宣告]方式定義的函式可以在宣告前使用
square(2); // 4 
function square(number) { 
     return number * number; 
}

Ex2: function 宣告,  [變數定義的函式] 不可以在宣告前使用
square(2); // TypeError: square is not a function 
var square = function (number) { 
      return number * number; 
};

Ex1: 函式內的變數宣告
var x = 1; 
var doSomeThing = function(y) { 
      var x = 100; 
      return x + y; }; 


console.log( doSomeThing(50) ); //   150
console.log( x ); // 1 

-->  同 function doSomeThing(y)  { var x=100; return x+y;}  
-->
因為切分變數有效範圍的最小單位是 "function",所以
在函式區塊內透過 var 定義的 x 實際上只屬於這個函式。
換句話說,外面的 x 跟 function 內的 x 其實是兩個不同的變數。

-->
自己的 function 內如果找不到,就會一層層往外找,直到全域變數為止:
function 可以讀取外層已經宣告的變數,
但外層拿不到裡面宣告的變數。

Ex2: 函式的變數宣告
var x = 1; 
var doSomeThing = function(y) { 
       x = 100; 
       return x + y; }; 

console.log( doSomeThing(50) ); // 150
console.log( x ); // 100


Ex3: 函式的變數宣告
var x = 1; 
var doSomeThing = function(y) { 
console.log(x); // 會出現什麼? 
var x = 100; 
return x + y; }; 
console.log( doSomeThing(50) ); // 150 
console.log( x ); // undefined
--> 
要是不小心在 var 宣告前就使用了這個變數,這時候 JavaScript
就會開始尋找變數 x 了,在自己的 scope 找... 啊,找到了!
雖然是在下面,但可以確認的是自己的 scope 裡面有宣告,
於是就 很貼心地 「只會把宣告的語法」拉到這個 scope 的「最上面」...
-->如上寫法同下:
var doSomeThing = function(y) { 
var x; 
console.log(x); // undefined 
x = 100; return x + y; 
};

--> 
變數都盡量在 scope 的最上面先宣告完成後再使用

二.全域變數/區域變數
1>全域變數
 JavaScript 這門語言中,沒有所謂「全域變數」這種東西。
更準確地說,
我們所說的「全域變數」其實指的是「全域物件」(或者叫「頂層物件」) 的屬性。
以瀏覽器來說,「全域物件」指的就是 window,在 node 環境中則叫做 global。
Ex:
var a = 10; 
console.log( window.a ); // 10


  • 變數有效範圍 (scope) 的最小切分單位是 function (ES6 的 let 與 const 例外)
    即使是寫在函式內,沒有 var 的變數會變成「全域變數」
    全域變數指的是全域物件 (頂層物件) 的「屬性」

敘述句 (Statement):敘述句就是執行某個動作  , 
                                   Ex: var foo;
運算式 (Expression): 運算式最大的特性,就是它會產生一個「值」。 
                                            Ex: var a = 10 * 10;
在運算式中,會透過提供一些數值給「運算子」(Operator) 進行運算,進而得到一個運算的結果。

DOM - 2 Event 的特殊處理 - 停止event預設動作 - 停止往上冒泡 -bubble,catch

目的: 停止event預設動作 - 停止往上冒泡處理說明:1> Label 的event flow 為 bubble 方式時

            2> 觸發 lbl.onclick event後 , console 顯示 "lbl click"

                 瀏覽器將 click event  自動往下傳, 即傳給 checkbox.click event

            3> 因 checkbox event 為 bubble 方式,所以又往上傳給 lbl 

                即觸發 lbl.onclick event , concole 又顯示 "lbl click"

--> 即 lbl.onclick event 被觸發兩次


一.要阻擋事件向上冒泡傳遞:

--> event.stopPropagation()



<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <link rel="stylesheet" href="src/style.css" />
  </head>
  <body>
    <h1>Hi friend, try edit me!</h1>
    <label class="lbl">
      Label <input type="checkbox" name="chkbox" id="chkbox" />
    </label>
    // label

    <script>
      var lbl = document.querySelector('.lbl');
      var chkbox = document.querySelector('#chkbox');

      lbl.addEventListener('click', function (e) {
        console.log('lbl click');
      }, false);

      chkbox.addEventListener('click', function (e) {
        console.log('chkbox click');
      }, false);

    </script>

    <script src="src/script.js"></script>
  </body>
</html>

-->

     chkbox.addEventListener('click', function (e) {
        console.log('chkbox click');
         e.stopPropagation();
      }, false);





二.不執行預設動作, 只執行目前動作( console.log("google") )



<a id="link" href="https://www.google.com">Google</a>

var link = document.querySelector('#link'); 

// 在 evend handler 加上 e.preventDefault(); 
link.addEventListener('click', function (e) { 
e.preventDefault(); 
console.log('Google!'); 
}, false);

Browser - 三大元素 HTML / CSS / JavaScript

目的: 認識 Browser 的三大要素 

處理說明:「HTML、CSS 與 JavaScript 是網頁前端三大要素」:
  1. HTML 負責資料與結構
  2. CSS 負責樣式與呈現
  3. JavaScript 負責行為與互動
原始參考網址 : https://ithelp.ithome.com.tw/articles/10191666

  • JavaScript 核心 (以 ECMAScript 標準為基礎)
  • BOM (Browser Object Model,瀏覽器物件模型)
  • DOM (Document Object Model,文件物件模型)
由於「BOM」與「DOM」是由瀏覽器執行環境所提供。
換句話說,在 node 環境下的 JavaScript 就不會有這兩個部分

1>BOM (Browser Object Model,瀏覽器物件模型),是瀏覽器所有功能的核心,與網頁的內容無關。
 W3C 把各家瀏覽器都有實作的部分,以及確定已經(或未來會) 加入的功能,統一集合起來納入了 HTML5 的標準中,這也就是我們現在看到的 BOM API 的實作。


1>BOM 的核心是 window 物件。
而 window 物件提供的屬性主要為 documentlocationnavigatorscreenhistory 以及 frames
window 物件 - 用來與瀏覽器溝通的窗口」

alert("message");
=window.alert(message);
一行程式碼就可以生成一個對話框,很神奇吧?
而這就是瀏覽器環境的 BOM 提供給 JavaScript 控制的功能之一。
類似的對話框還有用來提供「確定/取消」的 window.confirm() window.confirm ,以及開放式問答的 window.prompt() window.prompt 對話框。
當然 BOM 提供的 API 很多,包含開啟/關閉視窗,改變視窗大小,計時器與取得網址等等。 這些在之後的文章當中還會再詳細解說。

2>DOM (Document Object Model,文件物件模型),是一個將 HTML 文件以樹狀的結構來表示的模型,而組合起來的樹狀圖,我們稱之為「DOM Tree」


DOM 的 document 其實也是 window 物件的子物件之一。

而「DOM」 與「BOM」最大的區別在於:

  • BOM: JavaScript 與「瀏覽器」溝通的窗口,不涉及網頁內容。
DOM: JavaScript 用來控制「網頁」的節點與內容的標

「BOM」完全依賴於瀏覽器廠商實作本身無標準規範,
「DOM」有著 W3C 所制定的標準來規範。


Ex:
<h1 id="greet"></h1>
<script> document.querySelector('#greet').textContent = 'Hello World!' </script>

--> 透過 document.querySelector() 方法來取得節點,
      然後修改 textContent 屬性

2022年8月30日 星期二

DOM -0 DOM解析 - How Browser Work

目的: DOM的解析及取得元件

處理說明:  1> Browser 如何解析 HTML 成 DOM


原始參考網址 : https://ithelp.ithome.com.tw/articles/10191765

透過 DOM API 查找節點







當一個網頁被載入到瀏覽器時,瀏覽器會先分析這個 HTML 檔案,然後會依照這份 HTML 的內容解析成「DOM」 (Document Object Model,文件物件模型)

DOM 是 W3C 制定的一個規範,它是獨立於平台與語言的標準。 換言之,只要遵守這樣的規範實作,不管是什麼平台或者是什麼語言開發,都可以透過 DOM 提供的 API 來操作 DOM 的內容、結構與樣式。

所以說,DOM 是網頁的根本,懂得 控制籃板球的人就能控制整場比賽 控制 DOM 就可以控制整個網頁,做出良好的互動體驗

那麼在今天的分享中,我們就繼續來介紹 DOM API 查找節點的方法吧。

1><script> </script>放在 </body>之前 , HELLO 正常顯示

-->
當我們把 <script> 標籤放在 </body> 結束之前,
由於 DOM 已經解析完成所以 document.querySelector 就可以順利取得 id="hello" 的節點,並且把 'HELLO' 的字串放在網頁裡囉!



2><script> </script>放在 </head>之前 , HELLO 無法正常顯示

-->

當瀏覽器在 <head> ... </head> 之間遇到 <script> 標籤時,就會暫停解析網頁,並且「立即」執行 <script> 裡的內容,直到 script 執行完畢後再繼續解析網頁。發在 <head> ... </head> 裡的 <script> 想要嘗試去尋找 <div id="hello"> 這個標籤,但因為還沒解析到網頁本體,所以也無從取得


document 物件是 「DOM tree」 的根節點,所以當我們要存取 HTML 時,都從 document 物件開始。 而 DOM 的節點類型除了 「HTML 元素節點」 (element nodes) 外,還有「文字節點」 (text nodes)、「註解節點」 (comment nodes) 等。

// 針對給定的 Selector 條件,回傳第一個 或 所有符合條件的 NodeList。
document.querySelector('xxx'); 
document.querySelectorAll('xxx');

document.querySelector 與 document.querySelectorAll 可以用 「CSS 選擇器」 (CSS Selectors) 來取得「第一個」或「所有」符合條件的元素集合 (NodeList)。



document.getElementById 以及 document.querySelector 
-->因爲取得的一定只有一個元素/節點,所以不會有 index 與 length 屬性。

而 document.getElementsBy** (注意,有個 s) 以及 document.querySelectorAll 
-->分別回傳 「HTMLCollection」 與 「NodeList」
「HTMLCollection」只收集 HTML element 節點
「NodeList」除了 HTML element 節點,也包含文字節點、屬性節點等



DOM -1 Event 的處理機制 - bubble -catch -eventflow

目的: 了解 Web  DOM的 event 處理 機制

處理說明:   1> catch 時, 由上而下 (Document --> html --> body --> table --> tr --> td --> div)

                    2> bubble時, 由下而上(div-->td--> tr--> table --> body --> html --> Document)

-->  DOM 兩種機制 (catch/bubble) 均有, 可分別設定 catch / bubble 的 event handler

參考原始網址: https://ithelp.ithome.com.tw/articles/10191970

- 重新認識 JavaScript: Day 14 事件機制的原理



而事件流程 (Event Flow) 指的就是「網頁元素接收事件的順序」。

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

  • 事件冒泡 (Event Bubbling)
  • 事件捕獲 (Event Capturing)

假設現在的事件是點擊上圖中藍色的 <td>

那麼當 td 的 click 事件發生時,會先走紅色的 「capture phase」:

  1. Document
  2. <html>
  3. <body>
  4. <table>
  5. <tbody>
  6. <tr>
  7. <td> (實際被點擊的元素)

由上而下依序觸發它們的 click 事件。

然後再繼續執行綠色的 「bubble phase」,反方向由 <td> 一路往上傳至 Document,整個事件流程到此結束。

Ex:  DOM的實作案例

<div>
    <div id="parent">父元素      
          <div id="child">子元素</div>
     </div>
</div>


// 父元素
var parent = document.getElementById('parent');
// 子元素
var child = document.getElementById('child');

// 透過 addEventListener 指定事件的綁定
// 第三個參數 true / false 分別代表捕獲/ 冒泡 機制

parent.addEventListener('click', function () {
  console.log('Parent Capturing');
}, true);  //capture 機制 event hadler

parent.addEventListener('click', function () {
  console.log('Parent Bubbling');
}, false); //bubble 機制 event hadler


child.addEventListener('click', function () {
  console.log('Child Capturing');
}, true);

child.addEventListener('click', function () {
  console.log('Child Bubbling');
}, false);

-->

當我點擊的是「子元素」的時候,透過 console.log 可以觀察到事件觸發的順序為:
       "Parent Capturing"
       "Child Capturing"
       "Child Bubbling"
       "Parent Bubbling"

由此可知,點擊子元素的時候,父層的 Capturing 會先被觸發,然後再到子層內部的 Capturing 或 Bubbling 事件。 最後才又回到父層的 Bubbling 結束。

子層的 Capturing 或 Bubbling 誰先誰後呢?
要看你程式碼的順序而定。

子層若是 Capturing 在 Bubbling 前面:

child.addEventListener('click', function () { console.log('Child Capturing'); }, true); child.addEventListener('click', function () { console.log('Child Bubbling'); }, false);

--> 子層觸發順序
"Child Capturing" "Child Bubbling"

-->
addEventListener() 基本上有三個參數,分別是「事件名稱」、「事件的處理器」(事件觸發時執行的 function),以及一個「Boolean」值,由這個 Boolean 決定事件是以「捕獲」或「冒泡」機制執行,若不指定則預設為「冒泡」。
true/false : catch/bubble


Bubble 及 Catch event flow