深刻Javascript原理

call、apply、bind、new、instanceof 實現原理javascript

call 模擬實現

更改函數的 this 指向並執行函數。java

Function.prototype.myCall = function(context) {
    var context = context || window
    context.fn = this
    var args = []
    for(var i = 1; i < arguments.length; i++) {
       args.push('arguments[' + i + ']') 
    }
    var result = eval('context.fn(' + args + ')')
    delete context.fn
    return result
}
複製代碼

分析:git

  • 首先 context 做爲可選參數,若是不傳或傳 null ,默認上下文爲 window
  • 而後給上下文 context 添加方法 fnfn 就是要執行的函數,誰調用了 call() , fn 就是誰。 因此 context.fn = this
  • call 還能夠傳入多個參數做爲調用的函數的參數,能夠經過 arguments 對象獲取函數的參數。
  • 最後調用函數保存返回值,刪除給對象添加的 fn ,將結果返回。

核心就是,經過將函數添加到對象上,而後經過對象調用這個函數,從而使函數中的 this 指向這個對象。github

更簡潔的寫法:數組

Function.prototype.myCall = function(context, ...rest) {
    var context = context || window 
    context.fn = this
    var result  = context.fn(...rest)
    delete context.fn
    return result
}
複製代碼

apply 模擬實現

apply 實現與 call 相似,區別在於對參數的處理app

Function.prototype.myApply = function(context, arr) {
    var context = context || window
    context.fn = this
    var result
    if(!arr) {
        result = context.fn()
    } else {
        var args = []
        for(var i = 0; i < arr.length; i++) {
            args.push('arr[' + i + ']')
        }
        result = eval('context.fn(' + args + ')')
    }
    delete context.fn
    return result
}
複製代碼

簡潔:函數

Function.prototype.myApply = function(context, arr) {
    var context = context || window 
    context.fn = this
    var result 
    if(arr) {
        result = context.fn(...arr)
    }else {
        result = context.fn()
    }
    delete context.fn
    return result 
}
複製代碼

bind 模擬實現

bind 方法返回一個改變 this 指向的函數,bind 第一個參數做爲這個函數的 this,後續參數會在函數調用時,做爲函數的參數傳入,而後纔會傳入函數的實參。ui

舉個例子:this

function foo(name,age) {
    console.log('name:' + name + 'age:' + age)
}
var f =  foo.bind(null,'張三')
f(20)  // name:張三age:20
複製代碼

實現:spa

Function.prototype.myBind = function(context) {
    var self = this
    var args = [].slice.call(arguments, 1)
    function F() {
        var args2 = [].slice.call(arguments)
        var arglist = args.concat(args2)
        return self.apply(this instanceof F ? this: context, arglist)
    }
    F.prototype = Object.create(self.prototype)
    return F
}
複製代碼

簡潔版:

Function.prototype.myBind = function(context) {
    var self = this
    var args = [...arguments].slice(1)
    function F() {
        return self.apply(this instanceof F ? this : context,args.concat(...arguments))
    }
    F.prototype = Object.create(self.prototype)
    return F
}
複製代碼
  • bind 返回的是一個函數, 並在這個函數裏面返回 原函數.apply() 實現改變 this 。
  • 需注意的是:若是 bind 返回的函數做爲構造函數執行時(new 函數),this 不能改爲傳入的 context ,this 應該是聲明的變量。
  • bind() 傳入的除 context 的後續參數,先存起來,在調用由 bind 返回的新函數時 ,向 apply() 傳入由原來存的參數和新傳入的參數組成的數組(注意順序)。

new 模擬實現

new 運算符建立了一個用戶自定義類型的實例

在執行 new 構造函數() 時,發生了:

  1. 生成一個新對象
  2. 將對象的原型引用(__proto__)指向構造函數的原型
  3. 綁定 this 爲該對象,執行構造函數中的內容
  4. 若是函數執行的返回值是對象,就返回這個對象,不然返回以前建立的新對象
function myNew() {
    var constructor = [].shift.call(arguments)
    var obj = Object.create(constructor.prototype)
    var res = constructor.apply(obj, arguments)
    return res instanceof Object ? res: obj
}
複製代碼

ES6 簡潔版:

function myNew(constructor, ...rest) {
    let obj = Object.create(constructor.prototype)
    let res = constructor.apply(obj, rest)
    return res instanceof Object ? res : obj
}
複製代碼

instanceof

能正確判斷對象的類型,原理是判斷構造函數的原型對象是否能在對象的原型鏈上

function myInstanceof(obj, fn) {
    let prototype = fn.prototype
    let objProto = obj.__proto__
    while(true) {
    if(objProto == null)
        return false
    if(objProto === prototype)
        return true
    objProto = objProto.__proto__
    }
}
複製代碼
  1. 得到構造函數的原型
  2. 得到實例對象的原型
  3. 判斷構造函數的原型是否就是實例對象的原型,若是是就返回 true ,不是就讓實例對象的原型指向自身的原型繼續判斷,直到實例對象的原型爲 null,返回 false

閱讀原文

參考資料:

github.com/mqyqingfeng…

juejin.im/book/5bdc71…

相關文章
相關標籤/搜索