js中的call

java中對象包含屬性和方法, 方法即函數, 只有對象可以調用方法
但是js中一切皆對象, 函數是對象, 函數也可以調用方法
Function.prototype.call
Function.prototype.apply
Function.prototype.bind
Function.prototype.toString
為什么js語言會在function原型上掛上這四個函數?
因為好處太多了, 現在我就說一說call的應用

// A對象
let a = {
    name: "aaa",
    toName(age) {
        console.log(age, "我的名字:", this.name)
    }
}
// B對象
let b = {
    name: "bbb",
}
// A對象調用自己的方法
a.toName('A對象')
// A對象具有某種能力, 能打印自己的名字,但是如何與別人共享這種能力呢?
// 編程思想促進了語法的形成 ==>  于是js語法就設計了這種通用能力
a.toName.call(b, 'B對象')

再深入思考一下, call語法是基于什么形成的呢 ?
了解底層語法有助于我們理解高級的語法
因為在底層的語法眼中, 高級語法只不過是工具, 根本不算語法, 不過是由我定義構建處理來的

理解call函數, 核心必要知識

  1. 接受函數也可以調用函數這個觀念(違反直覺, 需要編程思維, 原型 原型鏈 prototype)
  2. 詞法作用域 作用域鏈(不同瀏覽器內核不同, 谷歌瀏覽器是v8設計出來的js執(zhí)行機制, 底層整個系統(tǒng)基于算法 + 數據結構, 總之, 這就是js執(zhí)行規(guī)則的制定者)
  3. 誰是this, 即誰在調用函數
    函數調用, 需要知道函數當前的上下文信息,
    因為函數內部的作用域可能會引用函數擁有者的相關信息, 函數擁有者就是函數的上下文,
    找不到需要的信息, 通過作用域鏈就會在全局作用域尋找
Object.prototype.mycall = function(obj){
    // a.toName.mycall(b, 'B對象')
    // 拆開分析 , 上面是我們的目標
    // 1. 可以看出, a.toName就是我們要寫的這個函數的調用者, 'B對象',就是傳入的第二個參數
    let fn = this 
    // 此時fn就是我們要借用的函數 a.toName 用來打印名字, 但是此時它打印的名字依然是A對象的
    // 2. 如何讓它打印傳入對象obj, 也就是b的名字呢?
    // 作用域分析, 方法一: 將A對象與B對象交換作用域 (v8也許有,但我不知道)
    // 方法二: 打不過就加入, A對象抄襲B對象, 將函數復制一份, 變成自己的函數, 然后調用自己的函數
    obj.copyFn = fn
    obj.copyFn()
}

call的本質并不是調用別人的函數, 而是copy了別人的函數

哈哈哈, v8內部的不知道, 反正js基于現有語法就有這個能力 (還有解法, 基于proto,原型鏈的方式, var temp = Object.create(null)), 利用干凈的地方, 防止覆蓋原對象的函數

Function.prototype.call = function(ctx, ...args) {
    var proto = null
    if ('__proto__' in ctx) {
        proto = ctx.__proto__
    }
    var temp = Object.create(proto)
    temp.$f = this
    ctx.__proto__ = temp
    var result = ctx.$f(...args)
    ctx.__proto__ = proto
    return result
}

顯然, 我們自己的上面的mycall還要很多問題

  1. 將傳入的對象添加了copyFn函數, 可能重置掉對象原有的copyFn, 修改了對象, 需要delete刪除
  2. 傳入對象需要進行類型判斷
  3. 傳入參數需要處理
  4. 返回值
Object.prototype.mycall = function (obj) {
    obj = obj || window   //node環(huán)境下無法使用window 
    // 這里如果害怕屬性覆蓋可以設置唯一值, 這里就不展開了
   // Object.getOwnPropertyNames()  // 獲得對象的自己的屬性[]
    let fn = this
    //給context添加一個方法 指向this
    obj[fn] = this
    // 處理參數 去除第一個參數this 其它傳入fn函數
    let arg = [...arguments].slice(1)
    // 或者使用rest運算符獲取剩余參數
    // function f(a, ...arg) {
    //     console.log(arg, Array.isArray(arg))
    // }
    // f()         //[] true
    // f(1)       //[] true
    // f(1, 2)   //[2] true
    // f(1, 2, 3)  //[2, 3] true
    let res = context[fn](...arg) //執(zhí)行fn
    delete context[fn] //刪除屬性
    return res
}

這里我們就實現了自己的call, 以后看見call就知道都不過是些雕蟲小技了(當然真實的實現方式肯定需要考慮更多因素和性能, 而且在v8更大的環(huán)境下, 可能還不止一種實現方式,保持謙虛)

call的雕蟲小技

  1. Array, String等對象函數的借調
    Array.prototype.slice.call(arguments)
    [].slice.call(arguments)
    Object.prototype.toString.call(arr).slice(8, -1) // Array

  2. 實現借用構造函數繼承

    function  Person(name,age,love){ 
        this.name=name; 
        this.age=age; 
        this.love=love; 
        this.say=function say(){ 
            alert("姓名:"+name); 
        } 
    } 
    //call方式 
    function student(name,age){ 
        Person.call(this,name,age); 
    } 
  1. vue源碼中也有相關應用先寫這么多了哈.
?著作權歸作者所有,轉載或內容合作請聯系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容