【前端】基于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;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章