【面試需要】掌握JavaScript中的this,call,apply的原理
- 2020 年 2 月 13 日
- 筆記

作者 | Jeskson
掘金 | https://juejin.im/user/5a16e1f3f265da43128096cb
2020年01月10日
前言,為什麼要學習在掌握JavaScript中的this,call,apply,因為面試官會問啊!所以我們 必須掌握才能答啊!那麼this是什麼,Function.prototype.call和
Function.prototype.apply這兩個方法又是如何使用在JavaScript中的呢。
學習掌握this是必須的,我們常常在編寫JavaScript中的程式碼時,會常用到它。
this的指針作用域,在全局環境中執行this,表示Global對象,在瀏覽器中表示window對象;當通過new運算符來調用函數時,函數被當做為一個構造函數,this的指向構造函數創建出來的對象;當在函數的執行環境中使用this時,情況有些不同,如函數沒有作為一個非window對象的屬性,那麼只是定義了在這個函數,不管這個函數是不是定義在另一個函數中,其函數中的this仍表示為window對象;如果函數表示作為一個非window對象的屬性,則表示函數中的this就代表為 這個對象。

如截圖情況下,在全局執行環境中使用this,表示Global對象,在瀏覽器中表示為window對象。

functionA(){//在A函數中定義一個B函數functionB(){console.log(this);//Windowconsole.log(typeofthis);//objectconsole.log(this===window);//true}//在A函數內部調用B函數B();}//調用A函數A();
在函數執行環境中使用this時,如果函數沒有明顯的作為非window對象的屬性,而只是定義了函數,不管這個函數是不是定義在另一個函數中,這個函數中的this仍然表示window對象。

//定義一個對象obj,添加屬性name,添加方法objFunvarobj = {name:'dada',objFun:function(){console.log(this);// Object {name: "dada"}console.log(typeofthis);//objectconsole.log(this===window);//falseconsole.log(this.name);//dada}};//調用obj對象的方法obj.objFun();//this 綁定到當前對象,也就是obj對象

//定義一個對象obj,添加屬性name,添加方法objFunvarobj = {name:'dada',objFun:function(){console.log(this);//windowconsole.log(typeofthis);//objectconsole.log(this===window);//trueconsole.log('dada的,名字'+this.name+'大帥哥'); }};vartest = obj.objFun;test();
可以看出函數內部中this值不是靜態的,是動態的,可以改變的,每次調用一個函數時,它總是在重新求值。函數內部中的this值,實際上是由函數被調用的父作用域提供,依賴實際函數的語法。
// 第一種情況//調用obj對象的方法obj.objFun();//this 綁定到當前對象,也就是obj對象// 第二種情況vartest = obj.objFun;test();
vartest = obj.objFun// 這裡沒有調用函數test()// 這裡調用了函數// test不是一個對象的引用,所以this值代表全局對象。
當通過new運算符來調用函數時,函數被當做一個構造函數,this指向構造函數創建出來的對象。

new創建出來了一個構造函數,這個時候this的值,指向構造函數創建出來的對象。
varname ='dada';functionA(){console.log(this.name);} A();// dadavarB =newA();//undefined (因為B並沒有name屬性)VM162:3dadaVM162:3undefinedundefined

varname ='windowdada';varobj = {name:'objdada',objB: {name:'objBdada',bFun:function(){console.log(this.name); } }};vartest = obj.objB.bFun();vartest2 = obj.objB.bFun;test2();vartest3 = obj.objB;vartest4 = test3.bFun;test4();


注意()的近鄰的左邊,如果這個的左邊是一個引用,那麼傳遞給調用函數的this值為引用所屬的這個對象,否則this指向為全局對象。
vartest = obj.objB.bFun();// ()左邊是bFun引用,它指向objB這個對象,所有列印出objBdada
vartest2 = obj.objB.bFun;test2();// ()的左邊為test2,它不是某個對象的引用,所以是全局對象// 列印出 objBdada
vartest4 = test3.bFun;test4();// 同理這個也是
JavaScript中this的原理


varname ='windowDada';varobj = {name:'dada',foo:function(){console.log(this.name); }};varfoo = obj.foo;// 寫法一obj.foo()// 寫法二foo()VM593:5dadaVM593:5windowDada
這個時候我相信你已經看懂了。this指向的是函數運行時所在的環境,對於obj.foo()來說,foo運行在obj環境中,所以這個時候的this指向為obj這個對象,對於foo()來說,foo運行是全局環境,這個this的指向為全局環境。(你會問為什麼呢?一個指向obj這個對象,一個運行環境為全局環境,這裡可以運用()左邊方法)
對呀為什麼呢?函數的運行環境是怎麼決定在哪種情況的?
為什麼obj.foo()的環境就在obj這個環境中,而作為
var foo = obj.foo,foo()的運行環境就變成了全局的執行環境呢?
this的指向設計,跟記憶體的數據結構有關。
varobj = {name:'dada'};
當一個對象賦值給一個變數obj的時候,JavaScript引擎會在記憶體里,先生成一個對象為 { name: 'dada' },然後才把這個對象的記憶體地址賦值給這個變數 obj。
我們說過了很多很多遍了,都知道這個變數obj就是一個地址,這個時候如果要讀 obj.foo,那麼引擎就會從這個變數 obj中拿記憶體地址,然後再從這個地址 讀取原始對象,返回它的foo屬性。
注意:原始的對象(開始創建的對象 { name: 'dada' })以字典結構保存的,每個屬性名都對應一個屬性描述對象。foo屬性的值保存在屬性描述對象的value屬性裡面。
this指包含它的函數作為方法被調用時所屬的對象。
this,第一包,含它的函數,第二,作為方法被調用時,第三,所屬的對象。

functionda(){console.log(this);//window}da();
會調用我,我就是誰的,誰最後調用我,我就是誰的。

testFunda()函數是在全局中被window對象所調用的,this的指向為window對象,而nameda變數在testFunda函數中,window對象中沒有這個變數,所以列印不出來。
注意()的左邊為testFunda
而testFunda()函數是在全局中被window對象所調用的哦!
因此this的指向就是window對象哦!

varnamedada ='dada'functiontestFundada(){varnamedada="hello dada!";console.log(this.namedada);}testFundada();VM717:4dada
看這個程式碼當然列印出的是dada啦,因為從全局調用,全局中有這個屬性,就列印這個屬性。
this被包含中一個函數中,但是這個函數被另個函數包含的情況下,這個this的指向為頂部的函數。

varobj={a:"da",b:function(){vara="dada";console.log(this.a); }};obj.b();VM726:5da
this被包含在函數b()中,因為是被obj對象所調用的,所以這個this屬於這個obj對象,列印出來的就是da這個字元串了。
誰最後調用我,我就屬於誰!

varobj = {a:1,b:{fn:function(){console.log(this.a);//undefined} }};obj.b.fn();VM730:5undefined
對象obj是在window上定義的,所以如下顯示:
obj.b.fn()=window.obj.b.fn()
誰先調用我不算,誰最後調用我才算,window,那麼this不是指向全局的對象了嗎,但是最後的是被fn()調用,()左邊為b對象,所以this就指向這個b對象了,因為函數中沒有這個變數,所以為undefined。
出一道考題

結果是啥?我知道為2,你知道嗎?那看看執行結果吧!
varobj = {name:1,b:{name:2,fn:function(){varname =3console.log(this.name); } }};obj.b.fn();

函數情況,屬性的值為一個函數

varobj = {foo:function(){} };
在JavaScript引擎中會將函數單獨保存在記憶體中,再將函數的地址賦值給foo屬性的value屬性。

{foo: { [[value]]: 函數的地址 … }}

varf =function(){};varobj = {f: f };// 單獨執行f()// obj 環境執行obj.f()

varfda =function(){console.log('da');};varobjDada = {f: fda };// 單獨執行fda()// objDada 環境執行objDada.fda()VM858:2da
環境的考慮,在JavaScript中運行在函數體內部,引用當前環境的其他變數。在JavaScript中,由於函數可以在不同的運行環境執行,就要一種機制,使能夠在函數體內部獲取當前的運行環境。
this的出現,目的在於就是指代函數當前的運行環境。
this 指代全局對象
functiontest(){this.x =1; alert(this.x);}test();// 1
this 指代上級對象
functiontest(){ alert(this.x);}varo = {};o.x =1;o.m = test;o.m();// 1
this 指代 new 出的對象
varx =3;functiontest(){this.x =1;}varo =newtest();alert(x);// 3alert(o.x);// 1
函數的不同使用場合,this 有不同的值,this是函數運行時所在的環境對象。

call的用法
call(thisObj,arg1,arg2,arg…)
調用一個對象的方法,以另一個對象替換當前對象,call方法用來代替另一個對象調用一個方法,該方法可以將一個函數對象的上下文改變為由this obj指定的新對象。
call方法的參數,如果是不傳,或是null,undefined的情況下,函數中的this指向就是指window對象,如果傳遞的是另一個函數的函數名,函數中的this指向就是這個函數的引用,如果參數傳遞的是基本類型數據時,函數中的this指向就是其對應的 包裝對象了,如果參數傳遞的是一個對象時,函數中的this就指向這個對象。
一個函數的函數名,函數名是引用,所以指向是指這個函數的引用
一個對象,所以this就指向這個對象
基本類型數據,就指向這個包裝對象
Function.prototype.call(thisArg[,arg1[,arg2, …]])
當以thisArg和可選擇的arg1,arg2等等作為參數在一個func對象上調用call方法。


varda = {name:"da",sayHello:function(age){console.log("hello, i am ",this.name +" "+ age +" years old"); }};varjeskson = {name:"jeskson",};da.sayHello(12);VM891:4hello, i am da12years oldundefinedda.sayHello.call(jeskson,13);VM891:4hello, i am jeskson13years oldundefined



在JavaScript中,call和apply作用是一樣的
為了改變某個函數運行時的上下文(context)而存在的,就是為了改變函數體內部this的指向。
每個函數都包含兩個非繼承而來的方法:
call()和apply()
apply的用法

apply(thisObj,argArray)
apply方法應用於某一個對象的一個方法
用另一個對象替換當前對象。
區別:參數書寫方式不同
call() 方法分別接受參數。
apply() 方法接受數組形式的參數。
Math.max(1,2,3);// 會返回 3Math.max.apply(null, [1,2,3]);// 也會返回 3Math.max.apply(Math, [1,2,3]);// 也會返回 3Math.max.apply(" ", [1,2,3]);// 也會返回 3Math.max.apply(0, [1,2,3]);// 也會返回 3
call(thisObj, arg1, arg2, arg3, arg4);apply(thisObj, [args]);thisObj:call和apply第一個參數是一樣的該參數將替代Function類裡面的this對象arg1,arg2….是一個個的參數args一個數組或類數組,是一個參數列表

JavaScript 嚴格模式
如果 apply() 方法的第一個參數不是對象,它將成為被調用函數的所有者(對象)。
在「非嚴格」模式下,它成為全局對象。
參考:
http://www.ruanyifeng.com/blog/2018/06/javascript-this.html