ES5和ES6中this指向問題

2019-09-17 11:12:59 85

首先對this的下個定義:this是在執行上下文創建時確定的一個在執行過程中不可更改的變量。this只在函數調用階段確定,也就是執行上下文創建的階段進行賦值,保存在變量對象中。這個特性也導致了this的多變性:即當函數在不同的調用方式下都可能會導致this的值不同:

var a =1;
function fun(){  
    'use strict';
     var a =2;
     return this.a;
}
fun();//報錯 Cannot read property 'a' of undefined

嚴格模式下,this指向undefined;

var a =1;
function fun(){
var a =2;
return this.a;
}
fun();//1

即在不同模式下之所以有不同表現,就是因為this在嚴格模式,非嚴格模式下的不同。

結論:當函數獨立調用的時候,在嚴格模式下它的this指向undefined,在非嚴格模式下,當this指向undefined的時候,自動指向全局對象(瀏覽器中就是window)

在全局環境下,this就是指向自己:

this.a =1;
var b =1;
c =1;
console.log(this===window)//true//這三種都能得到想要的結果,全局上下文的變量對象中存在這三個變量

當this不在函數中用的時候,this還是指向了window:

var a =100;
var obj = {a:1,b:this.a +1}
function fun(){
var obj = {a:1,c:this.a +2//嚴格模式下這塊報錯 Cannot read property 'a' of undefined}
return obj.c;
}
console.log(fun());//102 
console.log(obj.b);//101

結論:當obj在全局聲明的時候,obj內部屬性中的this指向全局對象,當obj在一個函數中聲明的時候,嚴格模式下this會指向undefined,非嚴格模式自動轉為指向全局對象。

現在知道了嚴格模式和非嚴格模式下this的區別,然而我們日常應用最多的還是在函數中用this,上面也說過了this在函數的不同調用方式還有區別,那么函數的調用方式都有哪些呢?四種:

在全局環境或是普通函數中直接調用

作為對象的方法

使用apply和call

作為構造函數

下面分別就四種情況展開:

直接調用

fun函數雖然在obj.b方法中定義,但它還是一個普通函數,直接調用在非嚴格模式下指向undefined,又自動指向了全局對象,正如預料,嚴格模式會報錯undefined.a不成立,a未定義。

var a =1;
var obj  =  {
                a:2,
                b:function(){function fun(){return this.a} console.log(fun());}
            } 
obj.b();//1

作為對象的方法

b所引用的匿名函數作為obj的一個方法調用,這時候this指向調用它的對象。這里也就是obj:

var a =1;
var obj = {a:2,b:function(){return this.a;}}
console.log(obj.b())//2

那么如果b方法不作為對象方法調用:

var a =1;
var obj = {a:2,b:function(){return this.a;}}
var t = obj.b;
console.log(t());//1

如上,t函數執行結果竟然是全局變量1,為啥呢?這就涉及Javascript的內存空間了,就是說,obj對象的b屬性存儲的是對該匿名函數的一個引用,可以理解為一個指針。當賦值給t的時候,并沒有單獨開辟內存空間存儲新的函數,而是讓t存儲了一個指針,該指針指向這個函數。相當于執行了這么一段偽代碼:

var a = 1;
function fun() {//此函數存儲在堆中
return this.a;
}
var obj = {
a: 2,
b: fun //b指向fun函數
}
var t = fun;//變量t指向fun函數
console.log(t());//1

此時的t就是一個指向fun函數的指針,調用t,相當于直接調用fun,套用以上規則,打印出來1自然很好理解了。

使用apply,call

這是個萬能公式,實際上上面直接調用的代碼,我們可以看成這樣的:

function fun() {
return this.a;
}
fun();//1//嚴格模式
fun.call(undefined)//非嚴格模式
fun.call(window)

這時候我們就可以解釋下,為啥說在非嚴格模式下,當函數this指向undefined的時候,會自動指向全局對象,如上,在非嚴格模式下,當調用fun.call(undefined)的時候打印出來的依舊是1,就是最好的證據。

為啥說是萬能公式呢?再看函數作為對象的方法調用:

var a = 1;
var obj = {
a: 2,
b: function() {
return this.a;
}
}
obj.b()
obj.b.call(obj)

如上,是不是很強大,可以理解為其它兩種都是這個方法的語法糖罷了,那么apply和call是不是真的萬能的呢?并不是,ES6的箭頭函數就是特例,因為箭頭函數的this不是在調用時候確定的,這也就是為啥說箭頭函數好用的原因之一,因為它的this固定不會變來變去的了。關于箭頭函數的this我們稍后再說。



作為構造函數


何為構造函數?所謂構造函數就是用來new對象的函數,像Function、Object、Array、Date等都是全局定義的構造函數。其實每一個函數都可以new對象,那些批量生產我們需要的對象的函數就叫它構造函數罷了。注意,構造函數首字母記得大寫。

function Fun() {
this.name = 'Lili';
this.age = 21;
this.sex = 'woman';
this.run = function () {
return this.name + '正在跑步';
}}Fun.prototype = {
contructor: Fun,
say: function () {
return this.name + '正在說話';
}}var f = new Fun();f.run();//Lili正在跑步f.say();//Lili正在說話

如上,如果函數作為構造函數用,那么其中的this就代表它即將new出來的對象。為啥呢?new做了啥呢?
偽代碼如下:

function Fun() {
//new做的事情
var obj = {};
obj.__proto__ = Fun.prototype;//Base為構造函數
obj.name = 'Lili';
...//一系列賦值以及更多的事
return obj}

也就是說new做了下面這些事:

  • 創建一個臨時對象

  • 給臨時對象綁定原型

  • 給臨時對象對應屬性賦值

  • 將臨時對象return
    也就是說new其實就是個語法糖,this之所以指向臨時對象還是沒逃脫上面說的幾種情況。
    當然如果直接調用Fun(),如下:

function Fun() {
this.name = 'Damonre';
this.age = 21;
this.sex = 'man';
this.run = function () {
return this.name + '正在跑步';
}}Fun();console.log(window)

其實就是直接調用一個函數,this在非嚴格模式下指向window,你可以在window對象找到所有的變量。
另外還有一點,prototype對象的方法的this指向實例對象,因為實例對象的proto已經指向了原型函數的prototype。這就涉及原型鏈的知識了,即方法會沿著對象的原型鏈進行查找。

箭頭函數


剛剛提到了箭頭函數是一個不可以用call和apply改變this的典型。

我們看下面這個例子:

var a = 1;var obj = {
a: 2};var fun = () => console.log(this.a);fun();//1fun.call(obj)//1

以上,兩次調用都是1。

那么箭頭函數的this是怎么確定的呢?箭頭函數會捕獲其所在上下文的 this 值,作為自己的 this 值,也就是說箭頭函數的this在詞法層面就完成了綁定。apply,call方法只是傳入參數,卻改不了this。

var a = 1;var obj = {
a: 2};function fun() {
var a = 3;
let f = () => console.log(this.a);
f();};fun();//1fun.call(obj);//2

如上,fun直接調用,fun的上下文中的this值為window,注意,這個地方有點繞。fun的上下文就是此箭頭函數所在的上下文,因此此時f的this為fun的this也就是window。當fun.call(obj)再次調用的時候,新的上下文創建,fun此時的this為obj,也就是箭頭函數的this值。

再來一個例子:

function Fun() {
this.name = 'Damonare';}Fun.prototype.say = () => {
console.log(this);}var f = new Fun();f.say();//window

有的同學看到這個例子會很懵,感覺上this應該指向f這個實例對象啊。不是的,此時的箭頭函數所在的上下文是proto所在的上下文也就是Object函數的上下文,而Object的this值就是全局對象。

那么再來一個例子:

function Fun() {
this.name = 'Damonare';
this.say = () => {
console.log(this);
}}var f = new Fun();f.say();//Fun的實例對象

如上,this.say所在的上下文,此時箭頭函數所在的上下文就變成了Fun的上下文環境,而因為上面說過當函數作為構造函數調用的時候(也就是new的作用)上下文環境的this指向實例對象。

原文鏈接:https://www.jianshu.com/p/77cff23936b4



快乐12胆拖玩法图片 福建体彩31选7推荐号 山东11选五5开奖走势图 体彩飞鱼中了多少钱 北京快3计划 福彩幸运快3开奖结果查询 有哪些投资理财的方式 澳门好运彩现场开奖直播 华泽期货配资 河北福彩排列七 广西11选5开奖结果今天 福建体彩31选7推荐号 山东11选五5开奖走势图 体彩飞鱼中了多少钱 北京快3计划 福彩幸运快3开奖结果查询 有哪些投资理财的方式 澳门好运彩现场开奖直播 华泽期货配资 河北福彩排列七 广西11选5开奖结果今天