JavaScript的call/apply/bind方法/函數原生實現

call/apply/bind方法簡介

在JavaScript中,函數中this的指向往往在調用時纔可確定,而JavaScript提供了call/apply/bind方法讓我們得以顯示綁定函數的this指向。
它們的第一個參數是一個對象,它們會把這個對象綁定到調用他們的函數內的this。因爲你可以直接指定 this 的綁定對象,因此我們稱之爲顯式綁定。

//用例
var a = { q: 1 };
var b = { q: 2 };
var c = { q: 3 };
function cs(s) {
    console.log(this.q)
}
cs.bind(a)();//1
cs.call(b);//2
cs.apply(c);//3

tips

var s = new fn.myBind({ a: 2333 })();//報錯!!,運算優先級:屬性訪問>帶參new>函數調用>無參new
var s = new (fn.myBind({ a: 2333 }))();//正確姿勢

自定義Call方法實現

參數從arguments[1]開始,func.myCall(obj:Object[,agr1:any[,agr2:any[...]]])

if (!Function.prototype.myCall) {
    Function.prototype.myCall = function (targetThis) {
        //targetThis默認爲windows(嚴格模式不允許)
        targetThis = targetThis || window;
        //利用對象調用指定this
        targetThis.fn = this;
        //收集參數
        var agrs = [];
        //因爲arguments[0]===targetThis,故從下標1開始
        for (var ge = 1, len = arguments.length; ge < len; ge++) {
            agrs.push('arguments[' + ge + ']');
        }
        //利用eval展開參數 並執行函數
        var result = eval('targetThis.fn(' + agrs + ')');
        //刪除附加對象的屬性以消除副作用
        delete targetThis.fn;
        //返回結果
        return result;
    }
}

自定義apply方法實現

參數放在數組裏func.call(obj:Object[,agr:Array])

if (!Function.prototype.myApply) {
    Function.prototype.myApply = function (targetThis, arrAgrs) {
        //targetThis默認爲windows(嚴格模式不允許)
        targetThis = targetThis || window;
        //利用對象調用指定this
        targetThis.fn = this;
        var agrs = [];
        //收集參數數組
        for (var ge = 0, len = arrAgrs.length; ge < len; ge++) {
            agrs.push('arrAgrs[' + ge + ']');
        }
        //利用eval展開參數 並執行函數
        var result = eval(' targetThis.fn(' + agrs + ')');
        //刪除附加對象的屬性以消除副作用
        delete targetThis.fn;
        //返回結果
        return result;
    }
}

自定義bind方法實現

參數從arguments[1]開始,func.myCall(obj:Object[,agr1:any[,agr2:any[...]]])

//考慮參數合併以及new優先級和原型繼承
if (!Function.prototype.myBind) {
    Function.prototype.myBind = function (targetThis) {
        //若非函數對象來調用本方法,報異常
        if (typeof this !== "function") {
            throw new TypeError(
                "Function.prototype.bind error"
            );
        }
        //收集參數
        var bindArgs = Array.prototype.slice.call(arguments, 1),
            originFunction = this,//保存原始this(原始函數)
            fnProto = function () { },//利用空函數間接鏈接prototype以應對new時的原型繼承
            fnBounding = function () {
                //考覈new操作this綁定優先
                return originFunction.apply(
                    (
                        this instanceof fnProto ? this : targetThis
                    ),
                    bindArgs.concat(
                        Array.prototype.slice.call(arguments)
                    )
                )
            };
        fnProto.prototype = this.prototype;//鏈接原型
        //new一個fnProto以實現簡潔繼承原型,防止對fnBounding.prototype的操作污染originFunction原型prototype
        fnBounding.prototype = new fnProto();
        return fnBounding;
    };
}


軟綁定

bind之後可以再bind或call/apply

if (!Function.prototype.softBind) {
    Function.prototype.softBind = function (obj) {
        var fn = this;
        // 捕獲所有 curried 參數
        var curried = [].slice.call(arguments, 1);
        var bound = function () {
            return fn.apply(
                (!this || this === (window || global)) ?
                    obj : this,
                curried.concat.apply(curried, arguments)
            );
        };
        bound.prototype = Object.create(fn.prototype);//鏈接原型
        return bound;
    };
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章