基於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()
)。
三個函數的區別
apply
與call
的區別
第一個參數都是thisArg,但是傳入的參數不同。
apply傳入的是數組對象,call傳入的是每一個參數。
apple/call
與bind
的區別
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;
}