new, call, apply, bind模擬實現(xiàn)

一:模擬new的實現(xiàn)

我們首先看一下new的使用

function aninmal (name, actions) {
    this.name = name
    this.actions = actions
    this.sayName = function () {
        console.log('myname :>> ', this.name) 
    }
}
let cat = new aninmal('cat', ['eat', 'cat-walk'])

我們分析一下,new 一個對象之后,返回的是什么,以及它的內(nèi)部做了哪些操作?

  • 一個繼承aninmal的對象cat被創(chuàng)建
  • cat.__proto__ === aninmal.prototype
  • 需要執(zhí)行構(gòu)造函數(shù)aninmal,并將this 指向新創(chuàng)建的對象實例cat
  • 返回被一個新對象
    • 如果構(gòu)造函數(shù)沒有顯示返回,怎返回this
    • 如果構(gòu)造函數(shù)顯式的返回值,返回的是基本類型, 如 number,string,boolean, 則返回的還是this, 注意返回null,也是返回this
    • 如果構(gòu)造函數(shù)顯式返回的是對象,如 {name: 1}, 則返回這個對象{name: 1}

通過以上分析,我們來實現(xiàn)一個MNew來模擬以上實現(xiàn)

    function isObject (obj) {
        return (typeof obj === 'object' && typeof obj !== null) || typeof obj === 'function'
    }
    function MNew () {
        // this
        let o = new Object()
        // 獲取構(gòu)造函數(shù)
        let FunctionConstructor = Array.prototype.shift.call(arguments)
        // this對象指向構(gòu)造函數(shù)的對象
        o.__proto__ = FunctionConstructor.prototype
        // 需要執(zhí)行構(gòu)造函數(shù)aninmal,并將this 指向新創(chuàng)建的對象實例cat
        let obj = FunctionConstructor.apply(o, arguments)
        return isObject(obj) ? obj : o
    }

我們來一個簡單的例子測試一下MNew

function parent (name, gender) {
    this.name = name
    this.gender = gender
    // return null
    // function _dd () {
    //     console.log('hellow')
    // }
    // return _dd
    // 默認(rèn)return this
}

let aa = mNew(parent, 'rose', 'female')
console.log(aa)

let cc = new parent('jack', 'male')
console.log(cc) 

二: 模擬call實現(xiàn)

call() 方法使用一個指定的 this 值和單獨給出的一個或多個參數(shù)來調(diào)用一個函數(shù)。 -MDN

function Product(name, price) {
  this.name = name;
  this.price = price;
}

function Food(name, price) {
    // this是當(dāng)前 Food 的實例,繼承于Product
  Product.call(this, name, price);
  this.category = 'food';
}

console.log(new Food('cheese', 5).name);
// expected output: "cheese"

我們分析下,如何實現(xiàn)一個call

  • Product函數(shù)執(zhí)行了
  • Product函數(shù)里的this被綁定了call的第一個參數(shù)
  • Product的入?yún)⑹莄all第一個之后的參數(shù)
// 有一點值得我們思考: 在沒有依賴call,apply,bind的情況下,如果修改this指向
// 這里利用對象方法的形式: 把 Product 作為 call 第一個參數(shù)this的屬性,如果通過 this.Product的方法調(diào)用,那么 Product里this就被綁定了call的this
Function.prototype.call = function (context, ...args) {
    let context = context || window
    // context 是 被綁定的this
    // this 是被call 調(diào)用的方法 
    let context.fn = this
    // 被綁定的this
    let res = context.fn(...args)
    delete context.fn
    return res
}

我們寫一個簡單的例子測試下:

function bb(params) {
    this.bb = 123
    console.log('params==>', params) // 456
    return this
}
let obj = {aa: 456}
let cc = bb.call(obj, 456) // {aa: 345, bb: 123}

三:apply 模擬的實現(xiàn)

我們既然實現(xiàn)了call的實現(xiàn),那么 apply實現(xiàn)起來就簡單多了

apply() 方法調(diào)用一個具有給定this值的函數(shù),以及以一個數(shù)組(或類數(shù)組對象)的形式提供的參數(shù)。- MDN

通過MDN的定義,我們發(fā)現(xiàn),call和 apply 唯一的區(qū)別就是,第二個參數(shù)的形式不同

fn.call(obj, p1, p2, p3)
fn.apply(obj, [p1, p2, p3])

同樣,我們分析,下如何實現(xiàn)一個apply,還是以Product這個例子來說明

  • Product函數(shù)執(zhí)行了
  • Product函數(shù)里的this被綁定了apply的第一個參數(shù)
  • Product的入?yún)⑹莂pply第一個之后的參數(shù)的數(shù)組,內(nèi)部會把數(shù)組拆開
Function.prototype.apply = function (this, args) {
    let context = this || window
    context.fn = this
    let res = context.fn(...args)
    delete context.fn
    return res
}

同樣,拿剛才的例子測試下

function bb(a, b, c) {
    this.bb = 123
    console.log('params==>', a, b, c) // 3,4,5
    return this
}
let obj = {aa: 456}
let cc = bb.apply(obj, [3,4,5]) // {aa: 345, bb: 123}

四:接下來,我們來實現(xiàn)bind

bind() 方法創(chuàng)建一個新的函數(shù),在 bind() 被調(diào)用時,這個新函數(shù)的 this 被指定為 bind() 的第一個參數(shù),而其余參數(shù)將作為新函數(shù)的參數(shù),供調(diào)用時使用。 -MDN

先來2個簡單的例子

function rose (gender, hobby) {
    this.gender = gender
    this.hobby = hobby
    console.log('name==>', this.name)
    return this
}
let jack = {
    name : 'jack'
}

let extendRose = rose.bind(jack, 'female',  ['swimming', 'football'])
console.log(extendRose())  
// { name: 'jack', gender: 'female', hobby: [ 'swimming', 'football' ] }


let extendRose = rose.bind(jack, 'female',  ['swimming', 'football'])
let person = new extendRose()  
console.log(person)
// 值得注意的是: 通過構(gòu)造函數(shù)返回,this仍然是rose的實例
// name==> undefined: rose 當(dāng)前 name沒有賦值
// rose { gender: 'female', hobby: [ 'swimming', 'football' ] }
let name = 'jack'
function getPerson (gender) {
    console.log(this.name)
    console.log('gender===>', gender)
}
let obj = {
    name: 'rose'
}
let person = getPerson.bind(obj, 'male')
person()
// 輸出: 參數(shù)被帶入了被bind的新函數(shù)
// rose 成功被綁定為obj
// gender===> male

通過以上的例子,我們來分析一下如何模擬一個bind,我們結(jié)合rose,jack例子來說明

  • rose.bind(jack, b, c) 返回一個新的函數(shù)extendRose,函數(shù)的this指向 jack, 內(nèi)置參數(shù)是 b, c
  • extendRose 如果是通過new來調(diào)用的,則實例對象仍是rose的實例, this指向rose的實例。

接下來,我們來實現(xiàn)一個bind

Function.prototype.bind = function (context, ...bindArgs) {
    // 不是函數(shù)綁定直接報錯
    if (typeof this !== 'function') {
        throw new Error('this must be a function')
    }
    // 調(diào)用函數(shù)
    let fn = this
    let fbound = function(...args) {
        args = [...bindArgs, ...args]
        // 看下面的關(guān)鍵點,來理解這一句代碼
        return fn.apply(this instanceof fn ? this : context, args)
    }
    // this 是當(dāng)前被綁定的函數(shù)
    // 關(guān)鍵點:this 有原型對象,返回的函數(shù)不能丟失this原型鏈對象上的屬性
    if (this.prototype) {
        fbound.prototype = Object.create(this.prototype)
    }
    return fbound
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容