在學習廖雪峰前輩的JavaScript教程中,遇到了一些需要註意的點,因此作為學習筆記列出來,提醒自己註意! 如果大家有需要,歡迎訪問前輩的博客https://www.liaoxuefeng.com/學習。 在一個對象中綁定函數,我們稱這個函數是這個對象的方法。 在前面的學習中,對象的定義如下: 如 ...
在學習廖雪峰前輩的JavaScript教程中,遇到了一些需要註意的點,因此作為學習筆記列出來,提醒自己註意!
如果大家有需要,歡迎訪問前輩的博客https://www.liaoxuefeng.com/學習。
在一個對象中綁定函數,我們稱這個函數是這個對象的方法。
在前面的學習中,對象的定義如下:
var xiaoming = { name: '小明', birth: 1990 };
如果我們給 xiaoming 這個對象綁定一個函數,就可以做更多的事情了。比如,寫個age()方法,返回xiaoming的年齡:
var xiaoming = { name: '小明', birth: 1990, age: function () { var y = new Date().getFullYear(); return y - this.birth; } }; xiaoming.age; // function xiaoming.age() xiaoming.age(); // 今年調用是25,明年調用就變成26了
綁定到對象上的函數稱為方法,和普通函數沒什麼區別,但是我們發現,它在內部使用了一個this關鍵字。
在一個方法內部,this是一個特殊的變數,它始終指向當前對象。在上面的例子中,也就是xiaoming這個變數。所以this.birth取到xiaoming的birth屬性。
如果把上面的代碼拆開寫:
function getAge() { var y = new Date().getFullYear(); return y - this.birth; } var xiaoming = { name: '小明', birth: 1990, age: getAge }; xiaoming.age(); // 25, 正常結果 getAge(); // NaN
我們發現,單獨調用函數 getAge() 返回了 NaN 。
註意,在JavaScript中,如果函數內部調用了 this , this 指向誰,有以下幾種情況:
- 如果以對象的方法形式調用,比如 xiaoming.age() ,該函數的this指向被調用的對象,也就是 xiaoming ,這是符合我們預期的。
- 如果單獨調用函數,比如 getAge() ,此時,該函數的 this 指向全局對象,也就是 window 。
如果採用下麵的方式,也是錯誤的:
var fn = xiaoming.age; // 先拿到xiaoming的age函數 fn(); // NaN
要保證this指向正確,必須用obj.xxx()的形式調用。
由於種種原因,ECMA決定,在strict模式下讓函數的this指向undefined,因此,在strict模式下,會得到一個錯誤提示,卻並沒有解決this應該指向的正確位置:
'use strict'; var xiaoming = { name: '小明', birth: 1990, age: function () { var y = new Date().getFullYear(); return y - this.birth; } }; var fn = xiaoming.age; fn(); // Uncaught TypeError: Cannot read property 'birth' of undefined
假如我們把方法重構一下:
'use strict'; var xiaoming = { name: '小明', birth: 1990, age: function () { function getAgeFromBirth() { var y = new Date().getFullYear(); return y - this.birth; } return getAgeFromBirth(); } }; xiaoming.age(); // Uncaught TypeError: Cannot read property 'birth' of undefined
結果又報錯了!原因是 this 指針只在 age 方法的函數內指向 xiaoming ,在函數內部定義的函數, this 又指向 undefined 了!(在非strict模式下,它重新指向全局對象 window !)
我們可以用一個 that 變數首先捕獲 this :
'use strict'; var xiaoming = { name: '小明', birth: 1990, age: function () { var that = this; // 在方法內部一開始就捕獲this function getAgeFromBirth() { var y = new Date().getFullYear(); return y - that.birth; // 用that而不是this } return getAgeFromBirth(); } }; xiaoming.age(); // 25
用 var that = this; ,你就可以放心地在方法內部定義其他函數,而不是把所有語句都堆到一個方法中。
apply
雖然在一個獨立的函數調用中,我們通過strict模式,讓 this 指向 undefined 或者 window ,不過,我們還是有其他辦法控制 this 的指向的:
要指定函數的 this 指向哪個對象,可以用函數本身的 apply 方法,它接收兩個參數,第一個參數就是需要綁定的 this 變數,第二個參數是 Array ,表示函數本身的參數。
用 apply 修複 getAge() 調用:
function getAge() { var y = new Date().getFullYear(); return y - this.birth; } var xiaoming = { name: '小明', birth: 1990, age: getAge }; xiaoming.age(); // 25 getAge.apply(xiaoming, []); // 25, this指向xiaoming, 參數為空
另一個與 apply() 類似的方法是 call() ,唯一區別是:
-
apply() 把參數打包成 Array 再傳入;
-
call() 把參數按順序傳入。
比如調用 Math.max(3, 5, 4) ,分別用 apply() 和 call() 實現如下:
Math.max.apply(null, [3, 5, 4]); // 5 Math.max.call(null, 3, 5, 4); // 5
對普通函數調用,我們通常把 this 綁定為 null 。
裝飾器
利用 apply() ,我們還可以動態改變函數的行為。
JavaScript的所有對象都是動態的,即使內置的函數,我們也可以重新指向新的函數。
現在假定我們想統計一下代碼一共調用了多少次 parseInt() ,可以把所有的調用都找出來,然後手動加上
count += 1 ,不過這樣做太傻了。最佳方案是用我們自己的函數替換掉預設的 parseInt() :
'use strict'; var count = 0; var oldParseInt = parseInt; // 保存原函數 window.parseInt = function () { count += 1; return oldParseInt.apply(null, arguments); // 調用原函數 // 測試: parseInt('10'); parseInt('20'); parseInt('30'); console.log('count = ' + count); // 3