ES5和ES6中this指向問題

2019-09-17 11:12:59 37

首先對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



北京时时彩pk10 怎么用免费来赚钱 3d所有组三有几个 重庆时时开奖结果官方骗局 新疆时时彩微信群 最好玩的捕鱼平台 十分快3稳赚公式 北京pk赛车预测软件app 分分彩计划软件安卓版 重庆时时开奖结果手机版 广东快乐十分app 12生肖复式5肖多少组 白小姐四肖八码论坛 广东时时彩计划 菁优网教师做题赚钱 电瓶车锂电池维修赚钱吗 天天捕鱼电玩版辅助 怎么用免费来赚钱 3d所有组三有几个 重庆时时开奖结果官方骗局 新疆时时彩微信群 最好玩的捕鱼平台 十分快3稳赚公式 北京pk赛车预测软件app 分分彩计划软件安卓版 重庆时时开奖结果手机版 广东快乐十分app 12生肖复式5肖多少组 白小姐四肖八码论坛 广东时时彩计划 菁优网教师做题赚钱 电瓶车锂电池维修赚钱吗 天天捕鱼电玩版辅助