歡迎大家關(guān)注苍匆,接下來我會(huì)寫一個(gè)關(guān)于
JavaScirpt
系列文章漂佩,希望我們一起進(jìn)步祝钢。
前言
this
關(guān)鍵字是 JavaScript
中最復(fù)雜的機(jī)制之一冷冗。它是一個(gè)很特別的關(guān)鍵字,被自動(dòng)定義在所有函數(shù)的作用域中锁蠕。作為一名前端攻城獅對(duì)它再熟悉不過了夷野,然而正是因?yàn)槭煜に院苋菀缀雎运灾劣谟盟鼤r(shí)踩了不少的坑荣倾,甚至在面試時(shí)還因?yàn)樗鼟炝嗣跎ΑK詫W(xué)習(xí)和掌握 this
的用法和一些陷阱對(duì)于進(jìn)階成名一名合格前端攻城獅很有必要。
this 誤解
正所謂先破而后立舌仍,我們首先解除一下長(zhǎng)時(shí)間對(duì) this
的誤解妒貌,再開始 this
學(xué)習(xí)之旅。
一直以來我們可能以為 this
是指向函數(shù)自身或者函數(shù)的詞法作用域铸豁,這在某種情況下是可行的灌曙,但是還是不夠。this
在未執(zhí)行時(shí)我們誰也不知道它的指向到底是誰节芥,因?yàn)橹挥泻瘮?shù)被調(diào)用時(shí)才會(huì)對(duì) this
進(jìn)行賦值在刺,所以要知道 this
指向誰,首先知道它是在什么位置被調(diào)用的头镊。
調(diào)用位置
調(diào)用位置是函數(shù)在代碼中調(diào)用的位置(而不是聲明的位置)蚣驼。這句話好像看起來像是廢話,不過在它面前踩過坑的人覺得這句話說的太精辟了(這就是我想說的)相艇。
調(diào)用位置分為以下幾種情況:
- 普通函數(shù)調(diào)用:在全局環(huán)境中調(diào)用函數(shù)
- 對(duì)象方法調(diào)用:通過對(duì)象的方法調(diào)用
- 構(gòu)造器調(diào)用:使用
new
運(yùn)算符實(shí)例化調(diào)用 - 顯式調(diào)用:通過
call
隙姿、apply
、bind
調(diào)用厂捞,修正this
指向
普通函數(shù)調(diào)用
普通函數(shù)調(diào)用, this
指向全局對(duì)象队丝。在瀏覽器 JS
引擎中this
指向 window
, Nodejs
環(huán)境 this
指向 global
function f1(){
return this;
}
// 在瀏覽器中靡馁,全局對(duì)象是 window
f1() === window // true
//在Node中,全局對(duì)象是 global
f1() === global // true
// 示例代碼
var name = 'globalName'
var getName = function () {
return this.name
}
getName() // globalName
// or
var obj = function () {
name: 'John',
getName: function () {
return this.name
}
}
var getName = obj.getName
getName() // globalName
值得注意的是在最后兩行代碼机久,對(duì)象方法賦值給了 getName
變量臭墨,調(diào)用 getName()
相當(dāng)于 調(diào)用window.getName()
,此時(shí) this
是指向 window
陷阱
- 在嚴(yán)格模式下膘盖,
this
指向undefined
- 在全局環(huán)境中胧弛,使用
var
聲明的變量會(huì)掛載在window
上尤误,但let
、const
聲明的變量结缚,不會(huì)掛載在window
上
function f1(){
'use strict'
return this;
}
f1() // 嚴(yán)格模式下损晤,this 指向 undefined
var a = 111
window.a // 111
let b = 222
const c = 333
window.b // undefined let、const 聲明變量沒有掛載在 window 上
window.c // undefined
對(duì)象方法調(diào)用
當(dāng)函數(shù)作為對(duì)象的方法被調(diào)用時(shí)红竭, this 指向該對(duì)象:
var obj = {
name: '張三',
getName: function () {
return this.name
}
}
obj.getName() // 張三
陷阱
使用對(duì)象方法調(diào)用時(shí)尤勋,this
有可能會(huì)丟失,看下面這段代碼
var name = 'globalName'
var obj = {
name: '張三',
getName: function () {
function fn () {
return this.name
}
return fn() // globalName
}
}
obj.getName() // globalName
上面代碼輸出 globalName
而不是 張三
·茵宪,因?yàn)樵?getName
函數(shù)內(nèi)部調(diào)用 fn
, 此時(shí) fn
函數(shù)執(zhí)行上下文this
不是指向調(diào)用的對(duì)象 obj
最冰,而是指向 window
構(gòu)造器調(diào)用
除了宿主提供的一些內(nèi)置函數(shù),大部分 JavaScript
函數(shù)可以當(dāng)作構(gòu)造器使用稀火。構(gòu)造器表面和普通函數(shù)一模一樣暖哨,不同的地方在于被調(diào)用的方式。
使用 new
運(yùn)算符調(diào)用函數(shù)時(shí)凰狞,該函數(shù)總會(huì)返回一個(gè)對(duì)象篇裁,通常情況下,構(gòu)造器里的 this
就指向這個(gè)對(duì)象
var MyName = function () {
this.name = 'jeffery'
}
var obj = new MyName()
console.log(obj.name) // jeffery
使用 new
運(yùn)算符創(chuàng)建 MyName
構(gòu)造器服球,此時(shí)this
指向 obj
陷阱
使用 new
調(diào)用構(gòu)造器時(shí)茴恰,還要注意一個(gè)問題,如果構(gòu)造器顯式返回一個(gè) object
類型的對(duì)象斩熊,那么此次運(yùn)行結(jié)果最終是返回這個(gè)對(duì)象往枣,而不是我們之前期待的 this
:
var MyName = function () {
this.name = 'jeffery'
return { // 顯示返回一個(gè)對(duì)象
name: 'this is myName'
}
}
var obj = new MyName()
// 輸出 this is myName,而不是上面的 jeffery
console.log(obj.name) // this is myName
如果構(gòu)造器不顯式返回任何數(shù)據(jù)粉渠,或者返回一個(gè)非對(duì)象類型的數(shù)據(jù)分冈,就不會(huì)存在上面這個(gè)問題
var MyName = function () {
this.name = 'jeffery'
return 'this is myName'
}
var obj = new MyName()
console.log(obj.name) // jeffery
call、apply霸株、bind 顯式調(diào)用修正 this 指向
call雕沉、apply 修正 this 指向
call
和 apply
調(diào)用函數(shù)和其他函數(shù)調(diào)用相比,它會(huì)改變傳入函數(shù)的 this
去件, 指向第一個(gè)傳入的參數(shù)坡椒。call
和 apply
兩者實(shí)現(xiàn)功能相同, 不同的地方在于接收參數(shù)形式不一樣尤溜,前者接收的是參數(shù)個(gè)數(shù)倔叼,后者接收的是一個(gè)數(shù)組
var obj1 = {
name: 'obj1 name',
getName: function () {
return this.name
}
}
var obj2 = {
name: 'obj2 name'
}
// 對(duì)象方法調(diào)用,this 指向 obj1
obj1.getName() // obj1 name
// 使用 call 顯示調(diào)用宫莱,改變了原來 this 指向丈攒,指向了 obj2
obj1.getName.call(obj2) // obj2 name
陷阱
call
、apply
第一個(gè)參數(shù)除了可以是對(duì)象引用類型,也可以是基本類型:
null
或undefined
:this
指向window
巡验;不過际插,在嚴(yán)格模式下,this
還是指向undefined
显设。number
框弛、string
、boolean
:this
會(huì)指向其內(nèi)置構(gòu)造函數(shù)Number
敷硅、String
功咒、Boolean
var name = 'globalName'
var obj = {
name: 'obj name',
getName: function () {
// 'use strict' // 嚴(yán)格模式下,this 指向 undefined, null 會(huì)報(bào)錯(cuò)
return this.name
},
getThis: function () {
return this
}
}
obj.getName.call(null) // globalName
obj.getName.apply(undefined) // globalName
// number boolean string
obj.getThis.call(111) // Number {111}
obj.getThis.call(true) // Boolean {true}
obj.getThis.call('str') // String {"str"}
bind 修正 this 指向
bind
和 call
绞蹦、apply
不同的地方在于改變了this
指向同時(shí)會(huì)返回一個(gè)新的函數(shù)
function f(){
return this.a;
}
var g = f.bind({a:"azerty"});
console.log(g()); // azerty
var h = g.bind({a:'yoo'}); // bind只生效一次力奋!
console.log(h()); // azerty
var o = {a:37, f:f, g:g, h:h};
console.log(o.f(), o.g(), o.h()); // 37, azerty, azerty
bind
綁定改變 this
只能生效一次,如果鏈?zhǔn)桨l(fā)生多次綁定以第一次為準(zhǔn)
兩道經(jīng)典面試題
俗話說:“實(shí)踐是驗(yàn)證真理的唯一標(biāo)準(zhǔn)”幽七。很多時(shí)候我們以為學(xué)會(huì)了也只是自己以為學(xué)會(huì)了景殷,是騾子還是馬牽出來溜溜就知道了。所以澡屡,檢驗(yàn)自己的學(xué)習(xí)成果莫過于實(shí)踐猿挚。下面附上兩道面試題讓大家動(dòng)腦實(shí)踐一下
求解答為什么x.x調(diào)用結(jié)果會(huì)是undefined
function fn(xx){
this.x = xx;
return this;
}
var x = fn(5);
var y = fn(6);
console.log(x.x);
console.log(y.x);
下面的代碼輸出什么
let length = 10;
function fn() {
console.log(this.length);
}
var obj = {
length: 5,
method: function(fn) {
fn();
arguments[0]();
}
}
obj.method(fn, 1);
不知道答案的小伙伴可以戳這里:前端面試題(八)關(guān)于this指向的問題
引用鏈接
推薦閱讀