您好,登錄后才能下訂單哦!
JavaScript中this的錯誤認(rèn)識和綁定規(guī)則以及常見問題是怎樣的,很多新手對此不是很清楚,為了幫助大家解決這個難題,下面小編將為大家詳細(xì)講解,有這方面需求的人可以來學(xué)習(xí)下,希望你能有所收獲。
相信 Javascript 中的 this 會使很多同學(xué)在工作學(xué)習(xí)中產(chǎn)生困惑,筆者也同樣是,經(jīng)過閱讀各種資料及實際工作中的應(yīng)用,做了以下梳理,主要內(nèi)容包括長期以來大家對 this 的錯誤認(rèn)識及 this 的綁定規(guī)則,箭頭函數(shù)、實際工作場景中遇到的問題,希望對于有此困惑的你能有所幫助。
一、兩種錯誤認(rèn)識
1. 指向自身
this 的第一個錯誤認(rèn)識是,很容易把 this 理解成指向函數(shù)自身,其實 this 的指向在函數(shù)定義階段是無法確定的,只有函數(shù)執(zhí)行時才能確定 this 到底指向誰,實際 this 的最終指向是調(diào)用它的那個對象。
下面示例,聲明函數(shù) foo(),執(zhí)行 foo.count=0 時,像函數(shù)對象 foo 添加一個屬性 count。但是函數(shù) foo 內(nèi)部代碼 this.count 中的 this 并不是指向那個函數(shù)對象,for 循環(huán)中的 foo(i) 掉用它的對象是 window,等價于 window.foo(i),因此函數(shù) foo 里面的 this 指向的是 window。
function foo(num){ this.count++; // 記錄 foo 被調(diào)用次數(shù) } foo.count = 0; window.count = 0; for(let i=0; i<10; i++){ if(i > 5){ foo(i); } } console.log(foo.count, window.count); // 0 4
2. 指向函數(shù)的作用域
對 this 的第二種誤解就是 this 指向函數(shù)的作用域
以下這段代碼,在 foo 中試圖調(diào)用 bar 函數(shù),是否成功調(diào)用,取決于環(huán)境。
瀏覽器:在瀏覽器環(huán)境里是沒有問題的,全局聲明的函數(shù)放在了 window 對象下,foo 函數(shù)里面的 this 代指的是 window 對象,在全局環(huán)境中并沒有聲明變量 a,因此在 bar 函數(shù)中的 this.a 自然沒有定義,輸出 undefined。
Node.js:在 Node.js 環(huán)境下,聲明的 function 不會放在 global 全局對象下,因此在 foo 函數(shù)里調(diào)用 this.bar 函數(shù)會報 TypeError: this.bar is not a function 錯誤。要想運行不報錯,調(diào)用 bar 函數(shù)時省去前面的 this。
function foo(){ var a = 2; this.bar(); } function bar(){ console.log(this.a); } foo();
二、This 四種綁定規(guī)則
1. 默認(rèn)綁定
當(dāng)函數(shù)調(diào)用屬于獨立調(diào)用(不帶函數(shù)引用的調(diào)用),無法調(diào)用其他的綁定規(guī)則,我們給它一個稱呼 “默認(rèn)綁定”,在非嚴(yán)格模式下綁定到全局對象,在使用了嚴(yán)格模式 (use strict) 下綁定到 undefined。
嚴(yán)格模式下調(diào)用:
'use strict' function demo(){ // TypeError: Cannot read property 'a' of undefined console.log(this.a); } const a = 1; demo();
非嚴(yán)格模式下調(diào)用:
在瀏覽器環(huán)境下會將 a 綁定到 window.a,以下代碼使用 var 聲明的變量 a 會輸出 1。
function demo(){ console.log(this.a); // 1 } var a = 1; demo();
以下代碼使用 let 或 const 聲明變量 a 結(jié)果會輸出 undefined
function demo(){ console.log(this.a); // undefined } let a = 1; demo();
在舉例子的時候其實想要重點說明 this 的默認(rèn)綁定關(guān)系的,但是你會發(fā)現(xiàn)上面兩種代碼因為分別使用了 var、let 進(jìn)行聲明導(dǎo)致的結(jié)果也是不一樣的,歸其原因涉及到 頂層對象的概念
在 Issue: Nodejs-Roadmap/issues/11 里有童鞋提到這個疑問,也是之前的疏忽,再簡單聊下頂層對象的概念,頂層對象(瀏覽器環(huán)境指 window、Node.js 環(huán)境指 Global)的屬性和全局變量屬性的賦值是相等價的,使用 var 和 function 聲明的是頂層對象的屬性,而 let 就屬于 ES6 規(guī)范了,但是 ES6 規(guī)范中 let、const、class 這些聲明的全局變量,不再屬于頂層對象的屬性。
2. 隱式綁定
在函數(shù)的調(diào)用位置處被某個對象包含,擁有上下文,看以下示例:
function child() { console.log(this.name); } let parent = { name: 'zhangsan', child, } parent.child(); // zhangsan
函數(shù)在調(diào)用時會使用 parent 對象上下文來引用函數(shù) child,可以理解為child 函數(shù)被調(diào)用時 parent 對象擁有或包含它。
隱式綁定的隱患:
被隱式綁定的函數(shù),因為一些不小心的操作會丟失綁定對象,此時就會應(yīng)用最開始講的綁定規(guī)則中的默認(rèn)綁定,看下面代碼:
function child() { console.log(this.name); } let parent = { name: 'zhangsan', child, } let parentparent2 = parent.child; var name = 'lisi'; parent2();
將 parent.child 函數(shù)本身賦給 parent2,調(diào)用 parent2() 其實是一個不帶任何修飾的函數(shù)調(diào)用,因此會應(yīng)用默認(rèn)綁定。
3. 顯示綁定
顯示綁定和隱式綁定從字面意思理解,有一個相反的對比,一個表現(xiàn)的更直接,一個表現(xiàn)的更委婉,下面在看下兩個規(guī)則各自的含義:
隱式綁定:在一個對象的內(nèi)部通過屬性間接引用函數(shù),從而把 this 隱式綁定到對象內(nèi)部屬性所指向的函數(shù)(例如上例中的對象 parent 的 child 屬性引用函數(shù) function child(){})。
顯示綁定:需要引用一個對象時進(jìn)行強制綁定調(diào)用,js 有提供 call()、apply() 方法,ES5 中也提供了內(nèi)置的方法 Function.prototype.bind。
call()、apply() 這兩個函數(shù)的第一個參數(shù)都是設(shè)置 this 對象,區(qū)別是 apply 傳遞參數(shù)是按照數(shù)組傳遞,call 是一個一個傳遞。
function fruit(...args){ console.log(this.name, args); } var apple = { name: '蘋果' } var banana = { name: '香蕉' } fruit.call(banana, 'a', 'b') // [ 'a', 'b' ] fruit.apply(apple, ['a', 'b']) // [ 'a', 'b' ]
下面是 bind 綁定的示例,只是將一個值綁定到函數(shù)的 this 上,并將綁定好的函數(shù)返回,只有在執(zhí)行 fruit 函數(shù)時才會輸出信息,例:
function fruit(){ console.log(this.name); } var apple = { name: '蘋果' } fruitfruit = fruit.bind(apple); fruit(); // 蘋果
除了以上 call、apply、bind 還可以通過上下文 context,例:
function fruit(name){ console.log(`${this.name}: ${name}`); } const obj = { name: '這是水果', } const arr = ['蘋果', '香蕉']; arr.forEach(fruit, obj); // 這是水果: 蘋果 // 這是水果: 香蕉
4. new 綁定
new 綁定也可以影響 this 調(diào)用,它是一個構(gòu)造函數(shù),每一次 new 綁定都會創(chuàng)建一個新對象。
function Fruit(name){ this.name = name; } const f1 = new Fruit('apple'); const f2 = new Fruit('banana'); console.log(f1.name, f2.name); // apple banana
三、優(yōu)先級
如果 this 的調(diào)用位置同時應(yīng)用了多種綁定規(guī)則,它是有優(yōu)先級的:new 綁定 -> 顯示綁定 -> 隱式綁定 -> 默認(rèn)綁定。
四、箭頭函數(shù)
箭頭函數(shù)并非使用 function 關(guān)鍵字進(jìn)行定義,也不會使用上面所講解的 this 四種標(biāo)準(zhǔn)規(guī)范,箭頭函數(shù)會繼承自外層函數(shù)調(diào)用的 this 綁定。
執(zhí)行 fruit.call(apple) 時,箭頭函數(shù) this 已被綁定,無法再次被修改。
function fruit(){ return () => { console.log(this.name); } } var apple = { name: '蘋果' } var banana = { name: '香蕉' } var fruitfruitCall = fruit.call(apple); fruitCall.call(banana); // 蘋果
五、This 使用中的幾個常見問題
1. 通過函數(shù)和原型鏈模擬類
以下示例,定義函數(shù) Fruit,之后在原型鏈上定義 info 方法,實例化對象 f1 和定義對象 f2 分別調(diào)用 info 方法。
function Fruit(name) { this.name = name; } Fruit.prototype.info = function() { console.log(this.name); } const f1 = new Fruit('Apple'); f1.info(); const f2 = { name: 'Banana' }; f2.info = f1.info; f2.info()
輸出之后,兩次結(jié)果是不一樣的,原因是 info 方法里的 this 對應(yīng)的不是定義時的上下文,而是調(diào)用時的上下文,根據(jù)我們上面講的幾種綁定規(guī)則,對應(yīng)的是隱式綁定規(guī)則。
Apple Banana
2. 原型鏈上使用箭頭函數(shù)
如果使用構(gòu)造函數(shù)和原型鏈模擬類,不能在原型鏈上定義箭頭函數(shù),因為箭頭函數(shù)的里的 this 會繼承外層函數(shù)調(diào)用的 this 綁定。
function Fruit(name) { this.name = name; } Fruit.prototype.info = () => { console.log(this.name); } var name = 'Banana' const f1 = new Fruit('Apple'); f1.info();
3. 在事件中的使用
舉一個 Node.js 示例,在事件中使用時,當(dāng)我們的監(jiān)聽器被調(diào)用時,如果聲明的是普通函數(shù),this 會被指向監(jiān)聽器所綁定的 EventEmitter 實例,如果使用的箭頭函數(shù)方式 this 不會指向 EventEmitter 實例。
const EventEmitter = require('events'); class MyEmitter extends EventEmitter { constructor() { super(); this.name = 'myEmitter'; } } const func1 = () => console.log(this.name); const func2 = function () { console.log(this.name); }; const myEmitter = new MyEmitter(); myEmitter.on('event', func1); // undefined myEmitter.on('event', func2); // myEmitter myEmitter.emit('event');
看完上述內(nèi)容是否對您有幫助呢?如果還想對相關(guān)知識有進(jìn)一步的了解或閱讀更多相關(guān)文章,請關(guān)注億速云行業(yè)資訊頻道,感謝您對億速云的支持。
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。