DOM : 針對網頁上元素的操作
來看看若在 DOM 元素上操作的話,this 有什麼不同 ?
<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 標籤一起呈現,就看不到原始內容
使用 console.dir 的方式查看原始內容
<button onclick="console.dir(this)">這是一個按鈕</button>
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 都會觸發事件
}
var name = '小明';
var obj = {
x : function(){
name = '小王';
console.log(this.name);
},
y : '2',
}
obj.x(); // undefined
使用簡易呼叫,在非嚴格模式下,他的 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, '小明'); // 漂亮阿姨 小明
(第 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();
(第 9 行) 得知此為簡易呼叫,所以 this 指向全域 window。
this.myName 答案是 '全域'。
var myName = '全域'; // 全域變數
var person = {
myName: '小明',
getName: function () {
return this.myName;
}
}
var getName = person.getName; // 把函式取出,但未執行他。把它賦予在另一個變數上
console.log(getName()); // 執行 getName 函式
思考使用 bind 方法時,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
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 函式的結果) (如下圖)
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() 方法 : 把傳入的字串轉為整數
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
(function () {
console.log('六角學院 A'); // 六角學院 A
}());
(function () {
console.log('六角學院 B');
});
把 4-6 行程式放進「開發人員工具」 console 看看,結果如下。
若函式的參數是一個函式,當函式被傳入之前被呼叫的話就會優先被執行。
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));