成人午夜激情影院,小视频免费在线观看,国产精品夜夜嗨,欧美日韩精品一区二区在线播放

JavaScript開(kāi)發(fā)中this、call、apply、bind詳解

2017-09-20 10:33:35來(lái)源:Cherry's Blog作者:

這又是一個(gè)面試經(jīng)典問(wèn)題~/(ㄒoㄒ)/~~也是 ES5中眾多坑中的一個(gè),在 ES6 中可能會(huì)極大避免 this 產(chǎn)生的錯(cuò)誤,但是為了一些老代碼的維護(hù),最好還是了解一下 this 的指向和 call、apply、bind 三者的區(qū)別。

這又是一個(gè)面試經(jīng)典問(wèn)題~/(ㄒoㄒ)/~~也是 ES5中眾多坑中的一個(gè),在 ES6 中可能會(huì)極大避免 this 產(chǎn)生的錯(cuò)誤,但是為了一些老代碼的維護(hù),最好還是了解一下 this 的指向和 call、apply、bind 三者的區(qū)別。

this 的指向

在 ES5 中,其實(shí) this 的指向,始終堅(jiān)持一個(gè)原理:this 永遠(yuǎn)指向最后調(diào)用它的那個(gè)對(duì)象,來(lái),跟著我朗讀三遍:this 永遠(yuǎn)指向最后調(diào)用它的那個(gè)對(duì)象,this 永遠(yuǎn)指向最后調(diào)用它的那個(gè)對(duì)象,this 永遠(yuǎn)指向最后調(diào)用它的那個(gè)對(duì)象。記住這句話,this 你已經(jīng)了解一半了。

下面我們來(lái)看一個(gè)最簡(jiǎn)單的例子:
例 1:
 

1
2
3
4
5
6
7
8
9
10
var name = "windowsName";
function a() {
var name = "Cherry";
 
console.log(this.name); // windowsName
 
console.log("inner:" + this); // inner: Window
}
a();
console.log("outer:" + this) // outer: Window

 

這個(gè)相信大家都知道為什么 log 的是 windowsName,因?yàn)楦鶕?jù)剛剛的那句話“this 永遠(yuǎn)指向最后調(diào)用它的那個(gè)對(duì)象”,我們看最后調(diào)用 a 的地方 a();,前面沒(méi)有調(diào)用的對(duì)象那么就是全局對(duì)象 window,這就相當(dāng)于是 window.a();注意,這里我們沒(méi)有使用嚴(yán)格模式,如果使用嚴(yán)格模式的話,全局對(duì)象就是 undefined,那么就會(huì)報(bào)錯(cuò) Uncaught TypeError: Cannot read property 'name' of undefined。

再看下這個(gè)例子:
例 2:
 

1
2
3
4
5
6
7
8
var name = "windowsName";
var a = {
name: "Cherry",
fn : function () {
console.log(this.name); // Cherry
}
}
a.fn();

 

在這個(gè)例子中,函數(shù) fn 是對(duì)象 a 調(diào)用的,所以打印的值就是 a 中的 name 的值。是不是有一點(diǎn)清晰了呢~

我們做一個(gè)小小的改動(dòng):
例 3:
 

1
2
3
4
5
6
7
8
var name = "windowsName";
var a = {
name: "Cherry",
fn : function () {
console.log(this.name); // Cherry
}
}
window.a.fn();

 

這里打印 Cherry 的原因也是因?yàn)閯倓偰蔷湓?ldquo;this 永遠(yuǎn)指向最后調(diào)用它的那個(gè)對(duì)象”,最后調(diào)用它的對(duì)象仍然是對(duì)象 a。

我們?cè)賮?lái)看一下這個(gè)例子:
例 4:
 

1
2
3
4
5
6
7
8
var name = "windowsName";
var a = {
// name: "Cherry",
fn : function () {
console.log(this.name); // undefined
}
}
window.a.fn();

 

這里為什么會(huì)打印 undefined 呢?這是因?yàn)檎鐒倓偹枋龅哪菢樱{(diào)用 fn 的是 a 對(duì)象,也就是說(shuō) fn 的內(nèi)部的 this 是對(duì)象 a,而對(duì)象 a 中并沒(méi)有對(duì) name 進(jìn)行定義,所以 log 的 this.name 的值是 undefined。

這個(gè)例子還是說(shuō)明了:this 永遠(yuǎn)指向最后調(diào)用它的那個(gè)對(duì)象,因?yàn)樽詈笳{(diào)用 fn 的對(duì)象是 a,所以就算 a 中沒(méi)有 name 這個(gè)屬性,也不會(huì)繼續(xù)向上一個(gè)對(duì)象尋找 this.name,而是直接輸出 undefined。

再來(lái)看一個(gè)比較坑的例子:
例 5:
 

1
2
3
4
5
6
7
8
9
10
11
var name = "windowsName";
var a = {
name : null,
// name: "Cherry",
fn : function () {
console.log(this.name); // windowsName
}
}
 
var f = a.fn;
f();

 

這里你可能會(huì)有疑問(wèn),為什么不是 Cherry,這是因?yàn)殡m然將 a 對(duì)象的 fn 方法賦值給變量 f 了,但是沒(méi)有調(diào)用,再接著跟我念這一句話:“this 永遠(yuǎn)指向最后調(diào)用它的那個(gè)對(duì)象”,由于剛剛的 f 并沒(méi)有調(diào)用,所以 fn() 最后仍然是被 window 調(diào)用的。所以 this 指向的也就是 window。

由以上五個(gè)例子我們可以看出,this 的指向并不是在創(chuàng)建的時(shí)候就可以確定的,在 es5 中,永遠(yuǎn)是this 永遠(yuǎn)指向最后調(diào)用它的那個(gè)對(duì)象。

再來(lái)看一個(gè)例子:
例 6:
 

1
2
3
4
5
6
7
8
9
10
11
var name = "windowsName";
 
function fn() {
var name = 'Cherry';
innerFunction();
function innerFunction() {
console.log(this.name); // windowsName
}
}
 
fn()

 

讀到現(xiàn)在了應(yīng)該能夠理解這是為什么了吧(o゚▽゚)o。

怎么改變 this 的指向

改變 this 的指向我總結(jié)有以下幾種方法:

  • 使用 ES6 的箭頭函數(shù)
  • 在函數(shù)內(nèi)部使用 _this = this
  • 使用 apply、call、bind
  • new 實(shí)例化一個(gè)對(duì)象

例 7:
 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
var name = "windowsName";
 
var a = {
name : "Cherry",
 
func1: function () {
console.log(this.name)
},
 
func2: function () {
setTimeout( function () {
this.func1()
},100);
}
 
};
 
a.func2() // this.func1 is not a function

 

在不使用箭頭函數(shù)的情況下,是會(huì)報(bào)錯(cuò)的,因?yàn)樽詈笳{(diào)用 setTimeout 的對(duì)象是 window,但是在 window 中并沒(méi)有 func1 函數(shù)。

我們?cè)诟淖?this 指向這一節(jié)將把這個(gè)例子作為 demo 進(jìn)行改造。

箭頭函數(shù)

眾所周知,ES6 的箭頭函數(shù)是可以避免 ES5 中使用 this 的坑的。箭頭函數(shù)的 this 始終指向函數(shù)定義時(shí)的 this,而非執(zhí)行時(shí)。,箭頭函數(shù)需要記著這句話:“箭頭函數(shù)中沒(méi)有 this 綁定,必須通過(guò)查找作用域鏈來(lái)決定其值,如果箭頭函數(shù)被非箭頭函數(shù)包含,則 this 綁定的是最近一層非箭頭函數(shù)的 this,否則,this 為 undefined”。

例 8 :
 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
var name = "windowsName";
 
var a = {
name : "Cherry",
 
func1: function () {
console.log(this.name)
},
 
func2: function () {
setTimeout( () => {
this.func1()
},100);
}
 
};
 
a.func2() // Cherry

 

在函數(shù)內(nèi)部使用 _this = this

如果不使用 ES6,那么這種方式應(yīng)該是最簡(jiǎn)單的不會(huì)出錯(cuò)的方式了,我們是先將調(diào)用這個(gè)函數(shù)的對(duì)象保存在變量 _this 中,然后在函數(shù)中都使用這個(gè) _this,這樣 _this 就不會(huì)改變了。
例 9:
 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
var name = "windowsName";
 
var a = {
 
name : "Cherry",
 
func1: function () {
console.log(this.name)
},
 
func2: function () {
var _this = this;
setTimeout( function() {
_this.func1()
},100);
}
 
};
 
a.func2() // Cherry

 

這個(gè)例子中,在 func2 中,首先設(shè)置 var _this = this;,這里的 this 是調(diào)用 func2 的對(duì)象 a,為了防止在 func2 中的 setTimeout 被 window 調(diào)用而導(dǎo)致的在 setTimeout 中的 this 為 window。我們將 this(指向變量 a) 賦值給一個(gè)變量 _this,這樣,在 func2 中我們使用 _this 就是指向?qū)ο?a 了。

使用 apply、call、bind

使用 apply、call、bind 函數(shù)也是可以改變 this 的指向的,原理稍后再講,我們先來(lái)看一下是怎么實(shí)現(xiàn)的:

使用 apply

例 10:
 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var a = {
name : "Cherry",
 
func1: function () {
console.log(this.name)
},
 
func2: function () {
setTimeout( function () {
this.func1()
}.apply(a),100);
}
 
};
 
a.func2() // Cherry

 

使用 call

例 11:
 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var a = {
name : "Cherry",
 
func1: function () {
console.log(this.name)
},
 
func2: function () {
setTimeout( function () {
this.func1()
}.call(a),100);
}
 
};
 
a.func2() // Cherry

 

使用 bind

例 12:
 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var a = {
name : "Cherry",
 
func1: function () {
console.log(this.name)
},
 
func2: function () {
setTimeout( function () {
this.func1()
}.bind(a)(),100);
}
 
};
 
a.func2() // Cherry

 

apply、call、bind 區(qū)別

剛剛我們已經(jīng)介紹了 apply、call、bind 都是可以改變 this 的指向的,但是這三個(gè)函數(shù)稍有不同。

在 MDN 中定義 apply 如下;

apply() 方法調(diào)用一個(gè)函數(shù), 其具有一個(gè)指定的this值,以及作為一個(gè)數(shù)組(或類似數(shù)組的對(duì)象)提供的參數(shù)

語(yǔ)法:

fun.apply(thisArg, [argsArray])

  • thisArg:在 fun 函數(shù)運(yùn)行時(shí)指定的 this 值。需要注意的是,指定的 this 值并不一定是該函數(shù)執(zhí)行時(shí)真正的 this 值,如果這個(gè)函數(shù)處于非嚴(yán)格模式下,則指定為 null 或 undefined 時(shí)會(huì)自動(dòng)指向全局對(duì)象(瀏覽器中就是window對(duì)象),同時(shí)值為原始值(數(shù)字,字符串,布爾值)的 this 會(huì)指向該原始值的自動(dòng)包裝對(duì)象。
  • argsArray:一個(gè)數(shù)組或者類數(shù)組對(duì)象,其中的數(shù)組元素將作為單獨(dú)的參數(shù)傳給 fun 函數(shù)。如果該參數(shù)的值為null 或 undefined,則表示不需要傳入任何參數(shù)。從ECMAScript 5 開(kāi)始可以使用類數(shù)組對(duì)象。瀏覽器兼容性請(qǐng)參閱本文底部?jī)?nèi)容。

apply 和 call 的區(qū)別

其實(shí) apply 和 call 基本類似,他們的區(qū)別只是傳入的參數(shù)不同。

call 的語(yǔ)法為:
 

1
fun.call(thisArg[, arg1[, arg2[, ...]]])

 

所以 apply 和 call 的區(qū)別是 call 方法接受的是若干個(gè)參數(shù)列表,而 apply 接收的是一個(gè)包含多個(gè)參數(shù)的數(shù)組。

例 13:
 

1
2
3
4
5
6
7
8
9
var a ={
name : "Cherry",
fn : function (a,b) {
console.log( a + b)
}
}
 
var b = a.fn;
b.apply(a,[1,2]) // 3

 

例 14:
 

1
2
3
4
5
6
7
8
9
var a ={
name : "Cherry",
fn : function (a,b) {
console.log( a + b)
}
}
 
var b = a.fn;
b.call(a,1,2) // 3

 

bind 和 apply、call 區(qū)別

我們先來(lái)將剛剛的例子使用 bind 試一下
 

1
2
3
4
5
6
7
8
9
var a ={
name : "Cherry",
fn : function (a,b) {
console.log( a + b)
}
}
 
var b = a.fn;
b.bind(a,1,2)

 

我們會(huì)發(fā)現(xiàn)并沒(méi)有輸出,這是為什么呢,我們來(lái)看一下 MDN 上的文檔說(shuō)明:

bind()方法創(chuàng)建一個(gè)新的函數(shù), 當(dāng)被調(diào)用時(shí),將其this關(guān)鍵字設(shè)置為提供的值,在調(diào)用新函數(shù)時(shí),在任何提供之前提供一個(gè)給定的參數(shù)序列。

所以我們可以看出,bind 是創(chuàng)建一個(gè)新的函數(shù),我們必須要手動(dòng)去調(diào)用:
 

1
2
3
4
5
6
7
8
9
var a ={
name : "Cherry",
fn : function (a,b) {
console.log( a + b)
}
}
 
var b = a.fn;
b.bind(a,1,2)() // 3

 

==================================== 更新==============================

JS 中的函數(shù)調(diào)用

看到留言說(shuō),很多童靴不理解為什么 例 6 的 innerFunction 和 例 7 的 this 是指向 window 的,所以我就來(lái)補(bǔ)充一下 JS 中的函數(shù)調(diào)用。
例 6:
 

1
2
3
4
5
6
7
8
9
10
11
var name = "windowsName";
 
function fn() {
var name = 'Cherry';
innerFunction();
function innerFunction() {
console.log(this.name); // windowsName
}
}
 
fn()

 

例 7:
 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
var name = "windowsName";
 
var a = {
name : "Cherry",
 
func1: function () {
console.log(this.name)
},
 
func2: function () {
setTimeout( function () {
this.func1()
},100);
}
 
};
 
a.func2() // this.func1 is not a function

 

函數(shù)調(diào)用的方法一共有 4 種

  1. 作為一個(gè)函數(shù)調(diào)用
  2. 函數(shù)作為方法調(diào)用
  3. 使用構(gòu)造函數(shù)調(diào)用函數(shù)
  4. 作為函數(shù)方法調(diào)用函數(shù)(call、apply)

作為一個(gè)函數(shù)調(diào)用

比如上面的 例 1:
例 1:
 

1
2
3
4
5
6
7
8
9
10
var name = "windowsName";
function a() {
var name = "Cherry";
 
console.log(this.name); // windowsName
 
console.log("inner:" + this); // inner: Window
}
a();
console.log("outer:" + this) // outer: Window

 

這樣一個(gè)最簡(jiǎn)單的函數(shù),不屬于任何一個(gè)對(duì)象,就是一個(gè)函數(shù),這樣的情況在 JavaScript 的在瀏覽器中的非嚴(yán)格模式默認(rèn)是屬于全局對(duì)象 window 的,在嚴(yán)格模式,就是 undefined。

但這是一個(gè)全局的函數(shù),很容易產(chǎn)生命名沖突,所以不建議這樣使用。

函數(shù)作為方法調(diào)用

所以說(shuō)更多的情況是將函數(shù)作為對(duì)象的方法使用。比如例 2:
例 2:
 

1
2
3
4
5
6
7
8
var name = "windowsName";
var a = {
name: "Cherry",
fn : function () {
console.log(this.name); // Cherry
}
}
a.fn();

 

這里定義一個(gè)對(duì)象 a,對(duì)象 a 有一個(gè)屬性(name)和一個(gè)方法(fn)。

然后對(duì)象 a 通過(guò) . 方法調(diào)用了其中的 fn 方法。

然后我們一直記住的那句話“this 永遠(yuǎn)指向最后調(diào)用它的那個(gè)對(duì)象”,所以在 fn 中的 this 就是指向 a 的。

使用構(gòu)造函數(shù)調(diào)用函數(shù)

如果函數(shù)調(diào)用前使用了 new 關(guān)鍵字, 則是調(diào)用了構(gòu)造函數(shù)。
這看起來(lái)就像創(chuàng)建了新的函數(shù),但實(shí)際上 JavaScript 函數(shù)是重新創(chuàng)建的對(duì)象:

1
2
3
4
5
6
7
8
9
// 構(gòu)造函數(shù):
function myFunction(arg1, arg2) {
this.firstName = arg1;
this.lastName = arg2;
}
 
// This creates a new object
var a = new myFunction("Li","Cherry");
a.lastName; // 返回 "Cherry"

這就有要說(shuō)另一個(gè)面試經(jīng)典問(wèn)題:new 的過(guò)程了,(ಥ_ಥ)
這里就簡(jiǎn)單的來(lái)看一下 new 的過(guò)程吧:
偽代碼表示:
 

1
2
3
4
5
6
7
8
var a = new myFunction("Li","Cherry");
 
new myFunction{
var obj = {};
obj.__proto__ = myFunction.prototype;
var result = myFunction.call(obj,"Li","Cherry");
return typeof result === 'obj'? result : obj;
}

 

  1. 創(chuàng)建一個(gè)空對(duì)象 obj;
  2. 將新創(chuàng)建的空對(duì)象的隱式原型指向其構(gòu)造函數(shù)的顯示原型。
  3. 使用 call 改變 this 的指向
  4. 如果無(wú)返回值或者返回一個(gè)非對(duì)象值,則將 obj 返回作為新對(duì)象;如果返回值是一個(gè)新對(duì)象的話那么直接直接返回該對(duì)象。

所以我們可以看到,在 new 的過(guò)程中,我們是使用 call 改變了 this 的指向。

作為函數(shù)方法調(diào)用函數(shù)

在 JavaScript 中, 函數(shù)是對(duì)象。

JavaScript 函數(shù)有它的屬性和方法。
call() 和 apply() 是預(yù)定義的函數(shù)方法。 兩個(gè)方法可用于調(diào)用函數(shù),兩個(gè)方法的第一個(gè)參數(shù)必須是對(duì)象本身

在 JavaScript 嚴(yán)格模式(strict mode)下, 在調(diào)用函數(shù)時(shí)第一個(gè)參數(shù)會(huì)成為 this 的值, 即使該參數(shù)不是一個(gè)對(duì)象。
在 JavaScript 非嚴(yán)格模式(non-strict mode)下, 如果第一個(gè)參數(shù)的值是 null 或 undefined, 它將使用全局對(duì)象替代。

這個(gè)時(shí)候我們?cè)賮?lái)看例 6:
例 6:
 

1
2
3
4
5
6
7
8
9
10
11
var name = "windowsName";
 
function fn() {
var name = 'Cherry';
innerFunction();
function innerFunction() {
console.log(this.name); // windowsName
}
}
 
fn()

 

這里的 innerFunction() 的調(diào)用是不是屬于第一種調(diào)用方式:作為一個(gè)函數(shù)調(diào)用(它就是作為一個(gè)函數(shù)調(diào)用的,沒(méi)有掛載在任何對(duì)象上,所以對(duì)于沒(méi)有掛載在任何對(duì)象上的函數(shù),在非嚴(yán)格模式下就是 window 調(diào)用的)

然后再看一下 例 7:
例 7:
 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
var name = "windowsName";
 
var a = {
name : "Cherry",
 
func1: function () {
console.log(this.name)
},
 
func2: function () {
setTimeout( function () {
this.func1()
},100 );
}
 
};
 
a.func2() // this.func1 is not a function

 

這個(gè)簡(jiǎn)單一點(diǎn)的理解可以理解為“匿名函數(shù)的 this 永遠(yuǎn)指向 window”,你可以這樣想,還是那句話this 永遠(yuǎn)指向最后調(diào)用它的那個(gè)對(duì)象,那么我們就來(lái)找最后調(diào)用匿名函數(shù)的對(duì)象,這就很尷尬了,因?yàn)槟涿瘮?shù)名字啊,笑哭,所以我們是沒(méi)有辦法被其他對(duì)象調(diào)用匿名函數(shù)的。所以說(shuō) 匿名函數(shù)的 this 永遠(yuǎn)指向 window。

如果這個(gè)時(shí)候你要問(wèn),那匿名函數(shù)都是怎么定義的,首先,我們通常寫(xiě)的匿名函數(shù)都是自執(zhí)行的,就是在匿名函數(shù)后面加 () 讓其自執(zhí)行。其次就是雖然匿名函數(shù)不能被其他對(duì)象調(diào)用,但是可以被其他函數(shù)調(diào)用啊,比如例 7 中的 setTimeout。
 

關(guān)鍵詞:JavaScriptES6
主站蜘蛛池模板: 南华县| 焦作市| 利辛县| 道孚县| 安多县| 呼图壁县| 福建省| 松潘县| 富阳市| 桂平市| 凤庆县| 广丰县| 大庆市| 永吉县| 麻江县| 海盐县| 河北区| 嘉峪关市| 澄江县| 长白| 仙桃市| 黄梅县| 望奎县| 滨州市| 武胜县| 南木林县| 新竹市| 商城县| 安多县| 岳西县| 大石桥市| 青海省| 东丽区| 云南省| 长泰县| 满城县| 小金县| 西畴县| 桓台县| 海林市| 峡江县|