【前端】基於JavaScript修改this指向的方法的實現與探索(以call、apply、bind爲例)

基於JavaScript修改this指向的方法的實現與探索(以call、apply、bind爲例)

改變函數內this的指向

基本介紹

語法介紹

apply() 方法調用一個具有給定this值的函數,以及作爲一個數組(或類似數組對象)提供的參數。

**bind()方法創建一個新的函數,在bind()**被調用時,這個新函數的this被bind的第一個參數指定,其餘的參數將作爲新函數的參數供調用時使用。

  • [Function.prototype.call( thisArg, arg1, arg2, … )][call_mozilla]

call() 方法使用一個指定的 this 值和單獨給出的一個或多個參數來調用一個函數。

執行結果

函數 執行結果
apply 調用有指定this值和參數的函數的結果。
call 調用有指定this值和參數的函數的結果。
若該方法沒有返回值,則返回undefined
bind function的拷貝(指定this),如果帶有參數可以指定參數

注意事項

【apply參數長度限制】

調用apply,會有超出JavaScript引擎的參數長度限制的風險。當你對一個方法傳入非常多的參數(比如一萬個)時,就非常有可能會導致越界問題, 這個臨界值是根據不同的 JavaScript 引擎而定的(JavaScript 核心中已經做了硬編碼 參數個數限制在65536),因爲這個限制(實際上也是任何用到超大棧空間的行爲的自然表現)是未指定的. 有些引擎會拋出異常。

【thisArg】

1、可選值

2、非嚴格模式下:thisArg指定爲null,undefined時,this會自動替換爲window對象。

3、非嚴格模式下:原始值會被包裝(new Number()|new Boolean()|new String())。

三個函數的區別

  • applycall的區別

第一個參數都是thisArg,但是傳入的參數不同。

apply傳入的是數組對象,call傳入的是每一個參數。

  • apple/callbind的區別

apple/call 在執行後馬上執行該函數,並返回函數的執行結果

bind在執行後返回拷貝函數並改變函數上下文,不執行該函數

例子

apply/call/bind不帶參數的例子

var student = {
	age: null,
	sex: null,
	getInfo( ){
		return  { 
			age: this.age, 
			sex: this.sex
		};
	}
}
return1 = student.getInfo();
student.age = 30;
return2 = student.getInfo();
let mike = { sex:'male' };
return3 = student.getInfo.apply( mike );
return4 = student.getInfo.call( mike );
bindFunc = student.getInfo.bind( mike );
return5 = bindFunc();
console.log( return1 )	// {age: null, sex: null}
console.log( return2 )	// {age: 30, sex: null}
console.log( return3 )	// {age: undefined, sex: "male"}
console.log( return4 )	// {age: undefined, sex: "male"}
console.log( return5 )	// {age: undefined, sex: "male"}

演示地址

apply/call/bind帶參數的例子

var student = {
	age: null,
	sex: null,
	getInfo( name, isRegistered ){
		return  { 
			age: this.age, 
			sex: this.sex, 
			name,
			isRegistered
		};
	}
}
return1 = student.getInfo()
student.age = 30;
return2 = student.getInfo()
let mike = { sex:'male' }
return3 = student.getInfo.apply( mike )
let parma = [ "mike",true ];
return3 = student.getInfo.apply( mike, parma )
return4 = student.getInfo.call( mike, ...parma )
bindFunc = student.getInfo.bind( mike, ...parma )
return5 = bindFunc();
console.log( return1 )	// {age: null, sex: null, name: undefined, isRegistered: undefined}
console.log( return2 )	// {age: 30	, sex: null, name: undefined, isRegistered: undefined}
console.log( return3 )	// {age: undefined, sex: "male", name: "mike", isRegistered: true}
console.log( return4 )	// {age: undefined, sex: "male", name: "mike", isRegistered: true}
console.log( return5 )	// {age: undefined, sex: "male", name: "mike", isRegistered: true}

演示地址

類數組對象轉換爲標準數組

而對於一個普通的對象來說,如果它的所有property名均爲正整數,同時也有相應的length屬性,那麼雖然該對象並不是由Array構造函數所創建的,它依然呈現出數組的行爲,在這種情況下,這些對象被稱爲“類數組對象”。總而言之,具有以下兩點的對象:

  • 擁有length屬性,其它屬性(索引)爲非負整數
  • 不具有數組所具有的方法

eg:

var arrayLike = {0:42, 1:52, 2:63, length:3}
var newArray = Array.prototype.slice.call(arrayLike);
console.log( newArray )

類數組對象轉換爲標準數組對象的方法有很多,如`

Array.prototype.slice.call()
Array.prototype.splice.call()
Array.from()
Array.prototype.concat.apply()
ES6 拓展運算符(……)

使用場景

apply / call

  • 參數不確定個數或者適合用數組儲存用apply
  • 參數確定個數用call

bind

不馬上執行的函數,只修改this對象的函數

手寫call/apply、bind

手寫call

要手寫call,我們先實現一個最簡單的、不帶參數的call。

var student = {
	age: null,
	sex: null,
	getInfo( name, isRegistered ){
		return  { 
			age: this.age, 
			sex: this.sex, 
			name,
			isRegistered
		};
	}
}

let mike = { sex:'male' }
let caller = student.getInfo.call( mike )
console.log( caller )
//{age: undefined, sex: "male", name: undefined, isRegistered: undefined}

現在我們嘗試一下自己寫一個myCall()方法:

Function.prototype.myCall = function(context) {
    if (context === null || context === undefined) {
        context = window 
    } else {
        context = Object(context);
    }
    let fn = Symbol('myCall');
    let arg = [...arguments].slice(1);
    context[fn] = this;
    let result = context[fn]( );
    delete context[fn]; 
    return result;
}
  • 嚴格模式下,函數的this值就是call和apply的第一個參數thisArg,
  • 非嚴格模式下,thisArg值被指定爲 null 或 undefined 時this值會自動替換爲指向全局對象,原始值則會被自動包裝,也就是Object()。
  • 使用Symbol是爲了創建一個獨一無二的唯一值參數,使其與context自帶的屬性不衝突

手寫apply

Function.prototype.myApply = function( context, arg ){
    if( context === null || context == undefined ){
        context == window;
    } else {
        context = Object(context);
    }

    function isArrayLike(o){
        if( typeof o == "object" &&
            isFinite(o.length) &&
            o.length >= 0 &&
            o.length === Math.floor( o.length ) &&
            o.length < Math.pow(2,32)
        )
            return true;
        else
            return false;
    }

    let fn = Symbol('myApply');
    context[fn] = this;
    let result;
    if( arg ){
        if( !Array.isArray(arg) && !isArrayLike(arg) ){
            throw new TypeError("第二個參數應該傳入數組");
        } else {
            arg = Array.from(arg);
            result = context[fn](...arg);
        }
    } else {
         result = context[fn]();
    }
    delete context[fn];
    return result;
}

手寫bind

Function.prototype.myBind = function( context, ...param ){
    let thisFn = this;
    let fn = function( ...otherParam ){
        if( this instanceof fn ){
            context = this;
            if( thisFn.prototype )
                fn.prototype = Object.create(thisFn.prototype);
            let result = thisFn.call( context, ...param, ...otherParam );
            let isObject = typeof result === 'object' && result !== null;
            let isFunction = typeof result === 'function';
            if(isObject || isFunction){
                return result;
            }
            return this;
        } else {
            context = Object(context);
        }
        return thisFn.call( context, ...param, ...otherParam );
    }
    return fn;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章