JavaScript:call和apply的ES6和ES5实现

JavaScript:call和apply的ES6和ES5实现

首先来看一下call和apply的定义和作用

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

apply() 方法调用一个具有给定this值的函数,以及以一个数组(或类数组对象)的形式提供的参数。

总的来说,call和apply的区别就是参数的不同。

call的实现

举个例子:

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

需要注意的两点是:

  • call 改变了 this 的指向,指向到 foo

  • bar 函数执行了

call实现的第一步:

当调用 call 的时候,可以把 foo 对象改造成如下:

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

但是这样会给foo对象多加了一个属性,不过可以使用之后可以删除。

所以这个可以拆分成三步:

  • 将函数设为对象的属性

  • 执行该函数

  • 删除该函数

// 第一步
foo.fn = bar
// 第二步
foo.fn()
// 第三步
delete foo.fn

所以call的v1可以这样写:

// callES6v1
Function.prototype.myCall = function(context) {
    // 首先要获取调用call的函数,用this可以获取
    context.fn = this;
    context.fn();
    delete context.fn;
}

接下来就是要注意的几个问题:

1.this 参数可以传 null,当为 null 的时候,视为指向 window

2.函数是可以有返回值的!

3.call可以传入多个参数

4.给对象添加的属性名不能冲突

5.调用call的必须是函数

这些都是小问题:

// callES6v2
Function.prototype.myCall = function(context, ...args) {
  	if(typeof this !== 'function') {
        throw new TypeError('error');
    }
  	// this 参数可以传 null,当为 null 的时候,视为指向 window
    context = context || window;
  	// 给对象添加的属性名不能冲突
    const fn = Symbol();
    context[fn] = this;
  	// call可以传入多个参数
    let result = context[fn](...args);
    delete context[fn];
  	// 函数是可以有返回值的
    return result;
}

以上就是call的es6实现,下面手写es5实现。es5实现逻辑和es6一样,前面都分析好了,就是语法的问题而已。

需要理解的语法:

  • eval() 函数会将传入的字符串当做 JavaScript 代码进行执行。可以理解为HTML5中的script标签。
// callES5
// 模拟symbol的函数
function mySymbol(obj) {
    var unique_proper = 'hcb' + Math.random();
    if(obj.hasOwnProperty(unique_proper)) {
        unique_proper += Math.random();
    } else {
        return unique_proper;
    }
}

Function.prototype.myCallES5 = function (context) {
    if(typeof this !== 'function') {
        throw new TypeError('error');
    }
    context = context || window;
    fn = mySymbol(context);
    context[fn] = this;
  
  	// 如果只有一个参数的话,那么直接返回对象执行绑定函数的结果。
    if(arguments.length < 2) {
        return context[fn]();
    }

    var args = [];
    for(var i = 1; i < arguments.length; i++) {
        args.push(arguments[i]);
    }

    var fnStr = 'context[fn](';
    for(var i = 0; i < args.length; i++) {
        fnStr += i == args.length - 1 ? args[i] : args[i] + ',';
    }
    fnStr += ')';

    var result = eval(fnStr);
    delete context[fn];
    return result;
}

同理,apply的es6实现和es5实现如下:

Function.prototype.myApplyES6v2 = function (context, args) {
  	if(typeof this !== 'function') {
        throw new TypeError('error');
    }
  
    context = context || window;
    fn = Symbol();
    context[fn] = this;
    let result = context[fn](...args);
    delete context.fn;
    // 这里为什么要return呢,因为可能绑定的函数会返回一个对象
    return result;
}
// es5的写法
function mySymbol(obj) {
    var unique_proper = 'hcb' + Math.random();
    if(obj.hasOwnProperty(unique_proper)) {
        unique_proper += Math.random();
    } else {
        return unique_proper;
    }
}

Function.prototype.myApply = function (context) {
    if(typeof this !== 'function') {
        throw new TypeError('error');
    }

    context = context || window;
    var args = arguments[1];
    var fn = mySymbol(context);
    context[fn] = this;

    if(arguments.length < 2) {
        return context[fn]();
    }

    var fnStr = 'context[fn](';
    for(var i = 0; i < args.length; i++) {
        fnStr += i == args.length - 1 ? args[i] : args[i] + ',';
    }
    fnStr += ')';
    var result = eval(fnStr);
    delete context[fn];
    return result;
}
------------- 本文结束 感谢阅读 -------------