Try   HackMD

JS 核心 this:DOM、課後練習

this:DOM

DOM : 針對網頁上元素的操作
來看看若在 DOM 元素上操作的話,this 有什麼不同 ?

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →
範例 : 直接把方法寫在元素上

this 直接綁定這個 DOM 元素,此種綁定方式讓開發會更為輕鬆

<ul> <li>1</li> <li>2</li> <li>3</li> <li>4</li> <li>5</li> </ul> <button onclick="console.log(this)">這是一個按鈕</button>

使用 console.log 的方式會連 HTML 標籤一起呈現,就看不到原始內容

  • 按下按鈕後,會直接把元素取出來

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

使用 console.dir 的方式查看原始內容

  • 按下按鈕後,直接把單純物件取出來
    • 可得知此物件有哪些屬性可運用
<button onclick="console.dir(this)">這是一個按鈕</button>

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →
範例 : 針對監聽器來綁定 this 的運用

DOM 的事件監聽會直接指向觸發事件的元素

<ul>
  <li>1</li>
  <li>2</li>
  <li>3</li>
  <li>4</li>
  <li>5</li>
</ul>
<button>這是一個按鈕</button>
var fn = function () {
  console.dir(this);  // this 受到監聽器影響 (this 指向所點擊的物件),把 this 綁在監聽器上面
  this.style.backgroundColor = 'orange';  // 點擊元素的背景色會變色
}
var els = document.querySelectorAll('li'); // 把所有的 li 取出

for (var i = 0; i < els.length; i++) {
  els[i].addEventListener('click', fn);    
  // 為所有的 li 補上監聽器,每點擊一次 li 都會觸發事件
}

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →


this 課後練習

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →
範例

  • 這裡的 this 是指 obj 物件,此 obj 物件下並沒有包含 name 的屬性,所以才會是 undefined。
  • this 的觀念在這邊重點在於「他是怎麼在函式呼叫的」,而該題目他是在 obj 物件底下呼叫的,所以 this 就只會查找 obj。
var name = '小明';
var obj = {
  x : function(){
    name = '小王';
    console.log(this.name);
  },
  y : '2',
}
obj.x();  // undefined

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →
範例

使用簡易呼叫,在非嚴格模式下,他的 this 會指向 window。
由於 (第 9 行) 第一個參數值是 undefined,故 name 為 undefined。
由於 (第 10 行) 使用 call 方法,所以第一個參數值是 帶入物件 auntie; 後續依續帶入參數,故 name 值為 '小明'。

function callName(name){ console.log(this.name, name); } var name = '全域阿婆'; var auntie = { name : '漂亮阿姨', } callName(undefined, '小明'); // (使用簡易呼叫) 全域阿婆 undefined callName.call(auntie, '小明'); // 漂亮阿姨 小明

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →
範例

(第 15 行) 在物件底下呼叫函式,this 指向就是前面的物件 x。
(第 7 行) 「雖然 setTimeout 是在 callName 裡面,但 setTimeout 裡面的 function 依然是屬於 callback function,我們不用看這個函式是在那裡執行,重點是他是怎麼去執行他。所以這段他依然是屬於 simple call 所以他的 this 指向 window。

var name = '小明'; var obj = { x: { name: '小虎', myname: function() { console.log(this.name); // 小虎 setTimeout(function () { console.log(this.name); // 小明 (簡易呼叫的 this 就會是 window) },500) } }, y: '2', name: '小王', } var a = obj.x.myname();

總結:函式的常見陷阱題

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →
範例 : this 的指向和如何定義他沒有關係,關鍵在怎麼去執行

(第 9 行) 得知此為簡易呼叫,所以 this 指向全域 window。
this.myName 答案是 '全域'。

var myName = '全域'; // 全域變數 var person = { myName: '小明', getName: function () { return this.myName; } } var getName = person.getName; // 把函式取出,但未執行他。把它賦予在另一個變數上 console.log(getName()); // 執行 getName 函式

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →
範例

思考使用 bind 方法時,this 會指向誰、以及參數會怎麼帶 ?

  • fn.bind(第一個參數,後面參數)
    • 第一個參數會做為 this 的指向,若傳入為 null 或 undefined,this 預設會指向全域
    • 後面參數將依續帶入

以下答案為 全域 0 1 2

var myName = '全域'; // 全域變數 var obj = { myName: '奇怪的函式', fn: function (a, b, c) { // 函式帶上三個參數 return this.myName + ',' + a + ',' + b + ',' + c } } var fnA = obj.fn; // 宣告 fn 變數,把 obj 物件下的 fn 取出 var fnB = fnA.bind(null, 0); // 對 fnA 做綁定,思考若傳入 null,this 指向為何 ? console.log(fnB(1, 2)); // 執行 fnB ,再把其他參數帶入

[承上原始碼] 試著改變 (第 10 行) 程式碼,查看變化。 由於參數 c 沒有值帶入,故顯示為 undefined。
以下答案為 全域 0 1 undefined。

console.log(fnB(1));   // 改成只有傳入一個參數

[承上原始碼]
Q : 若要把 bind 方法的第一個參數傳入值為 null,null 傳入就作為 this 使用 (null 的 this 不會變成全域、也不會變成 obj 物件),請問該如何修正程式碼 ?
A : 直接把 fn 函式轉為嚴格模式。在嚴格模式下,null 和 undefined 就不會指向全域。

在 (第 4 行) fn 函式下插入嚴格模式 'use strict'。此時會跳錯,因為 null 裡面不會有 myName 的屬性。

'use strict';

把 (第 5 行) myName 屬性移除,即可得到答案為 null 0 1 2

return this + ',' + a + ',' + b + ',' + c

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →
範例 : 考 this 的觀念

var value = 'global';  // 全域變數
var foo = {
  value: 'local',
  bar: function () {
    return this.value;
  }
}

var b = {};   // 定義一個 b 物件
Object.defineProperty(b, 'a', {  // 在 b 物件下定義屬性 a
  value: undefined,   // 屬性 a 的值 undefined
  writable: false     // 屬性 a 的值無法被寫入、無法被覆蓋
});
b.a = 'a';            // 因為 b.a 無法寫入,就算賦予新值 'a',b.a 還是維持 undefined
console.log(b.a);     // undefined

console.log(foo.bar());             // local (在 foo 物件下執行 bar)
console.log((foo.bar = foo.bar)()); // global  賦值 (表達式)
console.log((false || foo.bar)());  // global  or (表達式)(第一個值若為 false,則回傳第二個值)
console.log((b.a = foo.bar)());     // global
// 以上三行為表達式,會回傳一個值,直接對值作執行 (由此段可看出是執行 bar 函式的結果) (如下圖)

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →
範例 : 考 call back function

  1. 宣告變數 arr,後面帶上 ['1', '2', '3'] 陣列,其中陣列值皆為字串,字串透過 parseInt 來轉為數值
  2. map 方法會把執行結果回傳至前方的 arr 陣列上
  3. arr 陣列值即依據字串 '1', '2', '3' 分別套用 parseInt 後回傳的結果
  4. 請問 arr 陣列值為何 ?

map 方法就是將傳入的陣列值一一取出,並且計算之後回傳結果

var arr = ['1', '2', '3'].map(parseInt);
console.log(arr);   // [1, NaN, NaN]

「map 方法」運作原理

var arr = ['1', '2', '3'].map(function(item) {   // 這段函式就是 call back function
  console.log(item);     // 分別回傳 1 2 3
  return ('a' + item); 
});
console.log(arr);        // (3) ["a1", "a2", "a3"]

parseInt() 方法 : 把傳入的字串轉為整數

  • map() 方法傳入三個參數 : arr.map(function(item, index, arr))
    • item : 當前的值
    • index : 索引位置
    • arr : 陣列本身
var arr = ['1', '2', '3'].map(function(item, i) {   // 這段函式就是 call back function
  console.log(item, i);        // 分別回傳 1 2 3
  return parseInt(item, 10);   // 分別把字串傳入,並使用 10 進位
});
console.log(arr);  // (結果如上右圖)

回頭看一下程式碼並改寫。 parseInt( ) 方法 MDN 參考,改寫如下段程式碼。

僅需要知道這個觀念就可以了,實際撰寫程式碼時,並不會使用進位。

(第 1 行) parseInt 所帶的值就是前面陣列的值以及索引位置。原始碼為(第 3 行)。

(第 3 行) 對該回呼函式來說 Array.prototype.map 帶了三個參數:元素、索引、陣列。第三個參數會被 parseInt 忽略,但它可不會忽略第二個。
(第 4 行) parseInt 通常只用上一個參數 argument,但他其實用了兩個:第一個是表達式,第二個則是進位數。
(第 5 行) 把 parseInt 回傳出來。parseInt 會帶上值、索引位置。分別把字串傳入,i 索引位置代表進位模式。
(第 8 行) 2 進位只會有 0 跟 1,此數值不存在回傳 NaN。

// var arr = ['1', '2', '3'].map(parseInt); // 帶入的 parseInt 就是 call back 函式 // call back 函式帶入 item 值和索引位置 var arr = ['1', '2', '3'].map(function(item, i) { return parseInt(item, i); // 當 item 為 1,i 索引位置為 0 (即代表進位模式為 0),並沒有 0 進位,直接回傳 1 // 當 item 為 2,i 索引位置為 1,代表數值為 1 就進位,此數值不存在回傳 NaN // 當 item 為 3,i 索引位置為 2,代表數值為 2 就進位 }); console.log(arr); // (3)[1, NaN, NaN]

parseInt("1",0); // radix 為10也就是10進制的1轉10進制,也就是 1
parseInt("2",1); // radix 介於2和36之間的整數,當 radix 不在這個範圍內的解析全都返回 NaN
parseInt("3",2); // 3不是2進制的數字,3前面也沒有其他數字,所以這裡返回 NaN

蛋老師影片 - JavaScript map()和parseInt()

參考文章 : 快速理解[“1″,”2″,”3”].map(parseInt) [1, 7, 11].map(parseInt)

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →
範例 : 陷阱題

(function () { console.log('六角學院 A'); // 六角學院 A }()); (function () { console.log('六角學院 B'); });

把 4-6 行程式放進「開發人員工具」 console 看看,結果如下。

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →
範例

若函式的參數是一個函式,當函式被傳入之前被呼叫的話就會優先被執行。

  • 關於 a is not a function,是因為 b() 沒有回傳東西所以結果是 undefined,流程如下:
    一開始是將 c() 傳入 b() 並執行,所以出現 'casper',但此時的 b() 並沒有回傳任何內容,而又將 b() 執行的結果傳入 a(),但是 b() 沒有回傳,所以結果為 undefined。undefined 自然無法被執行,所以結果會是 a is not a function。
  • 因此執行順序是 b() -> c() -> a() 這是因為 function 是預先執行 b(),且函式已經被執行釋放掉,所以順序並非為 a() -> b() -> c()。
function a(a) {
  a();
}
function b(b) {
  b();
}
function c(c) {
  console.log('casper');
}
a(b(c));  //  a is not a function

再試著了解函式的執行順序
依照執行順序來看是 b() - c() - a() 拆解後就會像這樣子,其原因在於函式我已經預先執行 b(),因此函式已經被執行釋放掉,因此這一題的函式執行順序並不是 a() - b() - c(),而會出現 a is not a function 函式呼叫了自己。

function a(a) {
 console.log('a:' + a);
 a() // undefined
}
function b(b) {
 console.log('b:' + b)
 b(); // c function (b接收參數(c 的 function),是因為 callback function 概念)
}
function c(c) {
 console.log('c')
 console.log('casper');
}
a(b(c));

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →
學習回顧

  • this 的指向和如何定義他沒有關係,關鍵在怎麼去執行