JS開發者應懂的33個概念系列6--this,bind,call,apply
話說,只有掌握了JS中this用法才算真正的跨過了JS的門檻
。
this是什麼?
指向物件的this
想必大家都記得那句話“this 永遠指向最後呼叫它的那個物件”
。這也就是隱式繫結的規則,函式被呼叫時先看一看點號左側。
var cat = {
name: "kitty",
getName: function() {
console.log(this.name);
}
}
cat.getName(); // "kitty"
複製程式碼
為了避免不必要的麻煩,這也是最常用的一種方式。但是樹林大了,什麼鳥都有
,我們也要去了解更多的用法,才能以不變應萬變。
指向window的this
什麼樣子的this是會指向window呢?
我們對上面的例子進行一個小小的改動
var cat = {
name: "kitty",
getName: function() {
console.log(this.name);
}
}
var realName = cat.getName;
realName(); // undefined
複製程式碼
當然,我們按照前面的口訣,查詢呼叫這個函式的物件的時候,即這個函式被呼叫時點號的左側,並未發現沒有任何物件,對於此口訣已經不實用,我們已然找不到this是誰了?那這個時候this
指向那裡呢?那就是window
,而在全域性中並沒有定義 name
屬性,所以我們會得到:
undefined
。
如果我們規定使用了ES5
中的"use strict"
的嚴格模式,那麼我們就會收到報錯:
Uncaught TypeError: Cannot read property 'name' of undefined
複製程式碼
使用ES6箭頭函式時的this
為了避免ES5
中的this
的問題,ES6
引入了箭頭函式,箭頭函式的this指向的是定義時的物件,而非執行時。
看個例子:
var name = "miao";
var cat = {
name: "kitty",
getName: function() {
console.log(this.name);
},
getArrowName: () => {
console.log(this.name);
}
}
cat.getName(); // "kitty"
cat.getArrowName(); // "miao"
複製程式碼
ES6
中的this
規則:箭頭函式中沒有 this 繫結,必須通過查詢作用域鏈來決定其值,如果箭頭函式被非箭頭函式包含,則 this 繫結的是最近一層非箭頭函式的 this,否則,this 為 undefined
使用new時的this
function getName(name) {
this.name = name;
}
var newName = new getName("kitty");
console.log(newName.name); // "kitty"
複製程式碼
new的實現原理
- 新建一個物件
obj
obj
物件中的__proto__
屬性指向建構函式的原型,這樣形成一個原型鏈。具體的建構函式和原型鏈內容可連結我的另一篇JS開發者應懂的33個概念系列5(下)--typeof 與 instanceof && 23--原型繼承與原型鏈。- 利用
call
改變this
指向,顯示繫結到obj
上(call
的實現原理將在下面講述) - 如果
result
是物件型別並且不是null
,則返回result
new getName("kitty")
其實就做了這樣的工作:
(function(){
var obj = {};
obj.__proto__ = getName.prototype;
var result = getName.call(obj,"kitty");
return (typeof result === 'object' && result) ? result : obj;
})()
複製程式碼
使用call時的this
上面提到call
,它是可以改變this
的指向。使用和表現方式如下:
var cat = {
name: "kitty",
getAge: function(age, voice) {
console.log(this.name + " is " + age + " years old. " + voice);
},
}
cat.getAge(5, "miao"); // "kitty is 5 years old. miao"
var dog = {
name: "blank"
}
cat.getAge.call(dog, 2, "wangwang") // "blank is 2 years old. wangwang"
複製程式碼
call的實現原理
劃重點:面試題常考型別,小本本記一下:
Function.prototype.newCall = function(content) {
// 當call傳入的物件是null的時候,或者其他一些型別的時候,函式會報錯。
if(typeof content === 'object') {
content = content || window;
} else {
content = Object.create(null);
}
content.fn = this;
var keys = Array.from(arguments);
var arg = keys.slice(1);
content.fn(...arg);
delete content.fn;
}
var cat = {
name: "Kitty",
getAge: function(age, voice) {
console.log(this.name + " is " + age + " years old. " + voice);
},
}
var dog = {
name: "Blank"
}
cat.getAge.newCall(dog, 2, "wangwang");
複製程式碼
使用apply時的this
和call
相比較,其實就是傳參的型別不同而已:
var cat = {
name: "kitty",
getAge: function(age, voice) {
console.log(this.name + " is " + age + " years old. " + voice);
},
}
var dog = {
name: "blank"
}
cat.getAge.apply(dog, [2, "wangwang"]) // "blank is 2 years old. wangwang"
複製程式碼
apply的實現原理
Function.prototype.newApply = function(content, params) {
// 當call傳入的物件是null的時候,或者其他一些型別的時候,函式會報錯。
if(typeof content === 'object') {
content = content || window;
} else {
content = Object.create(null);
}
content.fn = this;
content.fn(...params);
delete content.fn;
}
var cat = {
name: "Kitty",
getAge: function(age, voice) {
console.log(this.name + " is " + age + " years old. " + voice);
},
}
var dog = {
name: "Blank"
}
cat.getAge.newApply(dog, [5, "wangwangwang"]);
複製程式碼
使用bind時的this
bind
同樣可以改變this
的指向,只不過其返回的是個函式:
var cat = {
name: "kitty",
getAge: function(age, voice) {
console.log(this.name + " is " + age + " years old. " + voice);
},
}
var dog = {
name: "blank"
}
cat.getAge.bind(dog, 2, "wangwang")(); // "blank is 2 years old. wangwang"
複製程式碼
bind的實現原理
// 使用了ES6的擴充套件運算子
Function.prototype.newBind = function(content, ...params) {
var obj = this;
return function(...otherParams) {
obj.call(content, ...params, ...otherParams);
}
}
var cat = {
name: "Kitty",
getAge: function(age, voice) {
console.log(this.name + " is " + age + " years old. " + voice);
},
}
var dog = {
name: "Blank"
}
var newGetAge = cat.getAge.newBind(dog, 10, "wuwuwu")
newGetAge("wuwuwu");
複製程式碼