【面試需要】掌握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