基于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;
}