JavaScript 之 call 和 apply 的模拟实现

1 call和apply的区别

call()方法接受的是参数列表
apply()方法接受的是一个参数数组

它们的共同之处:都可以用来代替另一个对象调用一个方法,将一个函数的对象上下文从初始的上下文改变为由this指定的新对象。

call()方法使用一个指定的 this 值和单独给出的一个或多个参数来调用一个函数。

fun.call(thisArg, arg1, arg2, ...)
thisArg 表示在 fun 函数运行时指定的 this 值。
arg1, arg2, ...  表示指定的参数列表。
apply()方法调用一个具有给定this值的函数,
以及作为一个数组(或类似数组对象)提供的参数。

func.apply(thisArg, [argsArray])
thisArg 在 func 函数运行时使用的 this 值
argsArray 一个数组或者类数组对象,其中的数组元素将作为单独的参数传给func 函数

实际上,apply和call的功能是一样的,只是传入的参数列表形式不同

2 call 方法的实现

call() 方法在使用一个指定的 this 值和若干个指定的参数值的前提下调用某个函数或方法。

2.1 设计思路

2.1.1 第一步改变this指向

var foo = {
    value: 1
};
function bar() {
    console.log(this.value);
}
bar.call(foo); // 1

上述代码实现了  call改变this指向 和 执行bar函数

调用call函数需要改变this指向,如下代码this指向foo的value属性,但是给foo添加了一个value属性。

var foo = {
    value: 1,
    bar: function() {
        console.log(this.value)
    }
};

foo.bar(); // 1

给对象添加的属性用完及时删除就可以了

所以模拟步骤:(fn为对象的属性名,可以随便起)

  1. 将函数设置为对象属性 foo.fn = bar
  2. 执行该函数 foo.fn()
  3. 删除该函数 delete foo.fn

所以我们可以实现代码,改变this指向

Function.prototype.newcall = function (context) {
    // 首先要获取调用call的函数,使用this获取
    context.fn = this;
    context.fn();
    delete context.fn;
}
var foo ={
    value:1
};
function bar() {
    console.log(this.value);
}
bar.newcall(foo);

2.1.2 第二步参数个数不确定

var foo = {
    value: 1
};
function bar(name, age) {
    console.log(name)
    console.log(age)
    console.log(this.value);
}
bar.call(foo, 'aaaa', 18);
// aaaa
// 18
// 1


// 我们可以从arguments对象中提取值为:
// arguments = {
//      0: foo,
//      1: 'kevin',
//      2: 18,
//      length: 3
// }
// 因为arguments是类数组对象,所以可以用for循环
var args = [];
for(var i = 1, len = arguments.length; i < len; i++) {
    args.push('arguments[' + i + ']');
}
// 执行后 args为 
//["arguments[1]", "arguments[2]", "arguments[3]"]


//不定长的参数问题解决了,我们接着要把这个参数数组
//放到要执行的函数的参数里面去。
eval('context.fn(' + args +')')

以下代码解决参数个数不确定的问题

Function.prototype.newcall = function (context) {
    context.fn = this; // 将函数设为对象的属性

    var args = [];
    for(var i=1,len=arguments.length;i<len;i++){
        args.push('arguments['+i+']');
    }
    eval('context.fn('+args+')') // 执行该函数

    delete context.fn;  // 删除该函数
}
var foo ={
    value:1
};
function bar(name,age) {
    console.log(name)
    console.log(age)
    console.log(this.value);
}
bar.newcall(foo,'aaaa',18);
// aaaa
// 18
// 1

2.1.3 传入参数为空或有返回值

 if (typeof context === 'object' || typeof context === 'function') {
        context = context || window
    } else {
     context = Object.create(null)
 }

2.2 最终代码

2.2.1 传入参数只有context 时

Function.prototype.call1 = function (context) {
    console.log(context)
    if (typeof context === 'object' || typeof context === 'function') {
        context = context || window
    } else {
        context = Object.create(null)
    }
    context.fn = this;
    // console.log(this)  // [Function: ClassA]
    var args = [];
    for(var i = 1, len = arguments.length; i < len; i++) {
        args.push('arguments[' + i + ']');
        // console.log(arguments[i]+'---------------------')
        // string---------------------
        //     function(){
        //         var fnStr = 'this is a string in function';
        //         console.log(fnStr)
        //     }---------------------
        //     [object Object]---------------------
        //     1,2,3---------------------

    }
    console.log(args) // [ 'arguments[1]', 'arguments[2]', 'arguments[3]', 'arguments[4]' ]
    var result = eval('context.fn(' + args +')');
    console.log(result)
    delete context.fn
    return result;
}

function ClassA(str, fn, obj, arr) {
    console.log(this.name);
    console.log(str);
    fn();
    console.log(obj);
    console.log(arr);
}

var obj = {
    name: 'aaaaaaaaaa'
};

ClassA.call1(obj, 'string', function(){
    var fnStr = 'this is a string in function';
    console.log(fnStr)
    }, {
    color: 'red'
}, [1, 2, 3]);

// name
// string
// this is a string in function
// { color: 'red' }
//  [ 1, 2, 3 ]

2.2.2 参数为2的时候

Function.prototype.newCall = function(context, ...arr) {
    if (typeof context === 'object' || typeof context === 'function') {
        context = context || window
    } else {
        context = Object.create(null)
    }
    context.fn = this
    const res =context.fn(...arr)
    delete context.fn;
    return res
}

let person = {
    name: 'Abiel'
}
function sayHi(age,sex) {
    console.log(this.name, age, sex);
}
sayHi.newCall (person, 25, '男'); // Abiel 25 男

3 apply 的实现

apply的实现跟call类似,只是处理参数的方式不同。

Function.prototype.apply = function (context, arr) {
    var context = Object(context) || window;
    // console.log(context) // { name: 'Abiel' }
    context.fn = this;
    // console.log(context.fn) // [Function: sayHi]
    var result;
    if (!arr) {
        result = context.fn();
    } else {
        var args = [];
        for (var i = 0, len = arr.length; i < len; i++) {
            // console.log(arr[i])
            args.push('arr[' + i + ']');
        }
        result = eval('context.fn(' + args + ')')
        // console.log(args) // [ 'arr[0]', 'arr[1]' ]
    }
    delete context.fn

    return result;
}
let person = {
    name: "Abiel"
};
function sayHi(age, sex) {
    console.log(this.name, age, sex);
}
sayHi.apply (person,[ 25, '男']) //Abiel 25 男
// sayHi.apply(person,[]) // Abiel undefined undefined

  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 6
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值