天天看點

javascript中的call方法的另一種實作方式-更接近原方法

作者:Jsonk

上集我們說到對應的我們自己實作的call方法還是有一點纰漏,這裡我們就解決它

// 一、預備知識(簡單介紹)
// 1、Function.prototype.call()
// 文法:function.call(thisArg, arg1, arg2, ...)
// 參數:thisArg可選的。在function函數運作時使用的this值。請注意,this可能不是該方法看到的實際值:如果這個函數處于非嚴格模式下,則指定為null或undefined時會自動替換為指向全局對象,原始值會被包裝。arg1, arg2, ...指定的參數清單。
// 傳回值:使用調用者提供的this值和參數調用該函數的傳回值。若該方法沒有傳回值,則傳回undefined

// 用法:
// call() 允許為不同的對象配置設定和調用屬于一個對象的函數/方法。
// call() 提供新的 this 值給目前調用的函數/方法。你可以使用 call 來實作繼承:寫一個方法,然後讓另外一個新的對象來繼承它(而不是在新對象中再寫一次這個方法)
// 2、this指針
// 在絕大多數情況下,函數的調用方式決定了this的值(運作時綁定)。this不能在執行期間被指派,并且在每次函數被調用時this的值也可能會不同。ES5 引入了bind方法來設定函數的this值,而不用考慮函數如何被調用的。箭頭函數不提供自身的 this 綁定(this的值将保持為閉合詞法上下文的值)。目前執行上下文(global、function 或 eval)的一個屬性,在非嚴格模式下,總是指向一個對象,在嚴格模式下可以是任意值。無論是否在嚴格模式下,在全局執行環境中(在任何函數體外部)this都指向全局對象。在函數内部,this的值取決于函數被調用的方式。在嚴格模式下,如果進入執行環境時沒有設定this的值,this會保持為undefined 。可以使用globalThis擷取全局對象,無論你的代碼是否在目前上下文運作。

// 3、原型對象
// JavaScript 常被描述為一種基于原型的語言 (prototype-based language)——每個對象擁有一個原型對象,對象以其原型為模闆、從原型繼承方法和屬性。原型對象也可能擁有原型,并從中繼承方法和屬性,一層一層、以此類推。這種關系常被稱為原型鍊 (prototype chain),它解釋了為何一個對象會擁有定義在其他對象中的屬性和方法。這些屬性和方法定義在 Object 的構造器函數 (constructor functions) 之上的prototype屬性上,而非對象執行個體本身

// 4、Symbol
// symbol 是一種基本資料類型。Symbol() 函數會傳回 symbol 類型的值,該類型具有靜态屬性和靜态方法。它的靜态屬性會暴露幾個内建的成員對象;它的靜态方法會暴露全局的 symbol 注冊,且類似于内建對象類,但作為構造函數來說它并不完整,因為它不支援文法:"new Symbol()"。每個從 Symbol() 傳回的 symbol 值都是唯一的。一個 symbol 值能作為對象屬性的辨別符;這是該資料類型僅有的目的

// 5、Object.defineProperty()
// Object.defineProperty()方法會直接在一個對象上定義一個新屬性,或者修改一個對象的現有屬性,并傳回此對象,第一個參數是對象,第二個是對象裡面的屬性,後面的第三個是參數配置,當對象對這個屬性設定會觸發裡面的set方法,擷取會觸發get方法我們可以在其中做一些我們自己的操作,這兩個方法是預設就有的,當然我們可以顯式聲明出來再加添自己的邏輯


Function.prototype._call = function(context, ...args) {
    //globalThis是全局對象,在浏覽器中,全局對象是 Window
    //注意這裡我們将傳入的對象和參數分開了
    context = context === null || context === void 0 ? globalThis : Object(context);
    //使用Symbol的原因:防止與提供新的 this 值的屬性重複
    //這裡的symbol是為了防止屬性沖突
    const targetFn = Symbol('onlyKey');
    //下面這個Object.defineProperty常用于爬蟲的hook代碼,以及對應的vue2的響應式原理【有興趣可以去源碼看看】,在ES6還出現了Proxy的方式來實作且功能更強大
    Object.defineProperty(context, targetFn, {
        // enumerable:為true時,該屬性才能夠出現在對象的枚舉屬性中。
      enumerable: false,
      value: this,
    });
    //這裡是調用對象的方法【再對象中方法其實也是一種屬性】那麼就會走對應的上面的Object.defineProperty中的get方法【預設就有的,我們也可以自己定義】,它的value是this,那個this就是context因為我們給的對象就是context也就是我們調用傳入的obj
    const result = context[targetFn](...args);
    return result;
  };

  function add(a, b) {
    // 測試函數
    console.log(this)
    // { name: 'Alex-jsonk' }
    // 注意這裡和上一集的已經不一樣了和call方法一樣了
    return a + b
}
let obj = {
    name: "Alex-jsonk"
}

console.log(add._call(obj, 1, 2));

//3
           

以上就是另一種寫法,如果你面試前端工程師的時候隻要寫出一種已經超過大部分人了,當然我們隻是為了最求技術的進步,成為頂端的人,我們必須精益求精,深入底層,下集再見。