本文會以詳細講解一道 字節面試題 的方式,按部就班徹底搞定 js 中 this 指向優先級的問題。 ⛹️♂️⛹️♂️
js 中的 this 指向問題應該是一個討論了好久的話題了,關於這個話題的文章,在掘金也有不少。可是,可能以前看到的文章不怎麼適合本身,每次看完都仍是似懂非懂、沒有多少頭緒。前幾天幸得個人老學長—— 猛哥 的交流以後,好像對這個問題理解的更深了些,寫篇文章總結一下。🌈前端
只要你仔細認真看完這篇文章,無論你是 js 新手、仍是大佬,我保證你必定會有收穫的!若是一丟丟收穫都沒有,你能夠揍我!️👀
複製代碼
Function
原型的 bind
(即手寫下面代碼塊中的myBind) 方法,使得如下程序最後能輸出 'success'
。function Animal(name, color) {
this.name = name;
this.color = color;
}
Animal.prototype.say = function () {
return `I'm a ${this.color} ${this.name}`;
};
const Cat = Animal.myBind(null, 'cat');
const cat = new Cat('white');
if (cat.say() === 'I\'m a white cat' &&
cat instanceof Cat && cat instanceof Animal) {
console.log('success');
}
複製代碼
先來看看解題須要瞭解的一些問題 慢慢來 不要慌張🌈
複製代碼
oneweb
var name = "globalName";
function a() {
var name = "jingjing";
console.log(this.name)
}
a(); //globalName
複製代碼
根據剛剛上面那個原則: this 永遠指向 最後調用它的那個對象 能夠獲得答案。咱們看最後調用 a 的地方是在哪裏?在最後一行代碼a()
; 它前面沒有調用的對象,那麼就是默認的全局對象 window
,因此console.log(this.name)
就變成了console.log(window.name)
,結果輸出的是 globalName
(👉非嚴格模式下👈)。面試
var name = "globalName";
var a = {
name: "jingjing",
jing: function () {
console.log(this.name); // jingjing
}
}
window.a.jing();
複製代碼
我又要重複上面那句話了😁。 this 永遠指向 最後調用它的那個對象 能夠獲得答案。咱們看最後調用 jing()
函數 的地方是在哪裏?或者說函數 jing()
左邊這個.
的左邊的對象是哪一個?顯然是對象 a,因此console.log(this.name)
就變成了console.log(a.name)
,結果輸出的是jingjing
。微信
var name = "globalName";
var a = {
name: "jingjing",
jing: function () {
console.log(this.name); // globalName
}
}
var hao = a.jing
hao();
複製代碼
🎈 這裏咱們雖然將 a 對象的 jing 方法賦值給變量 hao 了,可是注意!!!🔍這一步沒有調用執行 jing 方法!。代碼最後一行 hao()
才被 window
調用執行了 jing()
方法。 因此console.log(this.name)
就變成了console.log(window.name)
,結果輸出的是 globalName
。再拿出這個原則:this 永遠指向最後調用它的那個對象。🌈🌈🌈markdown
在 JavaScript 中,this 指向的綁定規則有如下四種:app
window
, 嚴格模式下,this指向 undefined
。)call()
、apply()
、bind()
調用,this 指向被綁定的對象。)new
調用,this
指向由 new
新構造出來的這個對象。)這種綁定方式就是使用 Function.prototype
中的三個方法 call()
, apply()
,和 bind()
了。這三個函數,均可以改變函數的 this 指向到指定的對象,不一樣之處在於:svg
call()
和 apply()
都是 當即執行函數 ,可是它們接受的參數的形式不一樣,具體以下:call(this, arg1, arg2, ...)
apply(this, [arg1, arg2, ...])
複製代碼
bind()
則是 返回一個新的包裝函數,而不是馬上執行。bind()
會建立一個新函數。當這個新函數被調用時,bind()
的第一個參數將做爲它運行時的 this
,以後的一序列參數將會在傳遞的實參前傳入做爲它的參數。bind(this, arg1, arg2, ...)
複製代碼
使用new來調用函數,或者說發生構造函數調用時,會自動執行下面的操做。函數
在 JavaScript 中,new 操做符並不像其餘面向對象的語言同樣,而是一種模擬出來的機制。在 JavaScript 中,全部的函數均可以被 new 調用,這時候這個函數通常會被稱爲 「構造函數」,實際上並不存在所謂「構造函數」,更確切的理解應該是對於函數的 「構造器調用模式」。oop
new 綁定 > 顯式綁定 > 隱式綁定 > 默認綁定學習
結論如上,下面給一些例子證實這個結論。
毫無疑問,默認綁定的優先級是四條規則中最低的,因此咱們能夠先不考慮它。
複製代碼
function jing() {
console.log(this.a);
}
var obj1 = {
a: 10,
foo: jing
};
var obj2 = {
a: 20,
foo: jing
};
obj1.foo(); //10
obj2.foo(); //20
obj1.foo.call(obj2); //20
obj2.foo.call(obj1); //10
複製代碼
由這個運行結果可知,上面代碼塊倒數兩行經過 call() 方法改變了 this 的指向。因此能夠獲得 顯式綁定 > 隱式綁定 這個結論。
function jing() {
this.a = 'hao';
}
let obj = {
a: 'jing'
};
// 一、bind
const Bar = jing.bind(obj);
// 二、new
const bar = new Bar();
console.log(obj.a, '--', bar.a) //jing -- hao
複製代碼
上面代碼塊倒數第三行經過 bind()
方法改變了 this
的指向obj
,由上面這個 bar.a
打印輸出結果爲 hao
可知,倒數第二行代碼改變了this
指向 jing()
,因此能夠獲得 new綁定 > 顯式綁定。
因此最後能夠有此結論: new 綁定 > 顯式綁定 > 隱式綁定 > 默認綁定
Function.prototype.myBind = function (thisObj, ...arg1) {
// 一、這裏 ...arg1 是第一次傳的參數
let fn = this;
// 這裏用 fn 記下調用對象
function jingbind(...arg2) {
// 二、這裏 ...arg2 是下一次傳的參數
const args = arg1.concat(arg2);
// 誰調用 bind,最終拼好的參數就傳給誰
return fn.apply(thisObj, args);
}
return jingbind;
// bind() 返回一個未執行的函數
}
複製代碼
myBind()
,咱們再寫一個測試代碼來試試效果:function sum(a, b, c) {
return a + b + c;
}
const sum10 = sum.myBind(null, 10);
let jing = sum10(20, 30);
console.log(jing) //60
複製代碼
🙌🙌通過測試,成功輸出正確結果。那咱們如今能夠試試咱們剛剛實現的 myBind()
方法能不能解決文章頂部拋出來的面試題!想一想都讓人很興奮!!🙋♂️🙋♂️
把上面那個方法和給出的題目放在一塊兒,執行一下試試。
Function.prototype.myBind = function (thisObj, ...arg1) {
if (typeof this !== "function") {
throw new TypeError("not a function");
}
// 一、這裏 ...arg1 是第一次傳的參數
let fn = this;
// 這裏用 fn 記下調用對象
function jingbind(...arg2) {
// 二、這裏 ...arg2 是下一次傳的參數
const args = arg1.concat(arg2);
// 誰調用 bind,最終拼好的參數就傳給誰
return fn.apply(thisObj, args);
}
return jingbind;
// bind() 返回一個未執行的函數
}
function Animal(name, color) {
this.name = name;
this.color = color;
}
Animal.prototype.say = function () {
return `I'm a ${this.color} ${this.name}`;
};
const Cat = Animal.myBind(null, 'cat');
const cat = new Cat('white');
console.log(cat.say()) //這句代碼是做者解題調試加上去的
if (cat.say() === 'I\'m a white cat' &&
cat instanceof Cat && cat instanceof Animal) {
console.log('success');
}
複製代碼
很遺憾、也很正常,報錯了。錯誤信息截圖以下:
cat.say() is not a function
,那它爲何不是一個方法呢?cat.say()
,一步一步往上追。cat
是哪裏來的?是從 Cat
上 new
出來的實例,那這個 Cat
又是從哪裏來的呢?是由 Animal.myBind
生成的,你調用了 myBind()
我給你返回的。那 myBind()
返回了什麼呢?它返回的是 jingbind()
。因此最後咱們能獲得Cat === jingbind()
,便可以獲得 cat === new jingbind()
。jingbind()
函數裏面沒有定義一個 say()
方法啊, 這個 say()
方法定義在 Animal.prototype
上面。Animal.prototype
上面有一個 say()
方法,可是通過咱們寫的 myBind()
方法處理後,把Animal.prototype
搞丟了。myBind()
代碼以下:Function.prototype.myBind = function (thisObj, ...arg1) {
if (typeof this !== "function") {
throw new TypeError("not a function");
}
// 一、這裏 ...arg1 是第一次傳的參數
let fn = this;
// 這裏用 fn 記下調用對象
function jingbind(...arg2) {
// 二、這裏 ...arg2 是下一次傳的參數
const args = arg1.concat(arg2);
// 誰調用 bind,最終拼好的參數就傳給誰
return fn.apply(thisObj, args);
}
jingbind.prototype = fn.prototype; //只添加了這一行代碼
return jingbind;
// bind() 返回一個未執行的函數
}
複製代碼
再執行一下試試
😎😎😎
myBind()
函數裏面沒有作優先級的判斷 (換句話說就是沒有對不一樣的 this 綁定規則作出相應的 this 綁定)。咱們對 myBind()
方法再作出一些改變以下:
Function.prototype.myBind = function (thisObj, ...arg1) {
if (typeof this !== "function") {
throw new TypeError("not a function");
}
// 一、這裏 ...arg1 是第一次傳的參數
let fn = this;
// 這裏用 fn 記下調用對象
function jingbind(...arg2) {
// 二、這裏 ...arg2 是下一次傳的參數
const args = arg1.concat(arg2);
// 誰調用 bind,最終拼好的參數就傳給誰
let isjing = this instanceof jingbind; //判斷是不是 new 調用
return fn.apply(isjing ? this : thisObj, args);
}
jingbind.prototype = fn.prototype;
return jingbind;
// bind() 返回一個未執行的函數
}
複製代碼
instanceof
的做用沒必要多說instanceof
判斷是否是經過 new 調用的,若是是 new 調用的 咱們就要把 this 綁定到實例上去。myBind()
函數的第一個參數——thisObj
,這樣處理一下咱們應該能拿到想要的結果吧🤐🤐再來測試一下🥱
複製代碼
最後的最後,咱們把這個方法搞出來了。💦💦💦
做者搭建了一個博客網站,準備從基礎開始,不斷更新,搭建完備的知識體系,爲之後的面試作準備。若是是剛開始入門的小夥伴,有須要能夠來看看哦🙋♂️🙋♂️
有任何問題歡迎加做者微信交流學習(大佬忽略👀)