js手写系列-call&apply模拟实现

11 篇文章 0 订阅

手写js-call&apply模拟实现

call的用途:

总结

  1. 改变this中的this指向
  2. 执行this

举个🌰

let obj = {
    value: 1
};

let fn1 = function() {
    console.log(this.value);
};

fn1.call(obj); // 1

1.改变this中的this指向
call的this指向fn1,因此改变fn1的this指向obj

2.执行this

执行fn1,此时fn1的this指向obj

为了更好理解,来一道奇奇怪怪的题

let fn1 = function () {
    console.log(1);
};
let fn2 = function () {
    console.log(2);
};

fn1.call.call.call(fn2);

按照运算符执行顺序,从左往右执行。

前面这一堆

fn1.call.call.call

根据原型链机制,相当于从函数中不断找Function.prototype.call这个方法

let fn1 = function () {
    console.log(1);
};
let fn2 = function () {
    console.log(2);
};

fn1.call.call.call(fn2);
//=> call.call(fn2);

1.改变this中的this指向
第二个call的this指向第一个call,因此改变第一个call的this指向fn2

2.执行this

执行第一个call,此时第一个call的this指向fn2

打印结果为2

模拟实现

1.改变this中的this指向

2.执行改变指向后的this

Function.prototype.myCall = function (context) {
    // 此时this:fn1
    // 目的是修改this中的this 即fn1中的this
    // context.fn1 => context.this
    // 这样写是会报错的,可以这样
    context.fn = this;
    
    // 注意,这里是给context添加了一个属性存放fn1(this)
    // 后面是需要清除的
    // 2.执行改变指向后的this
    context.fn();
    delete context.fn;
}

var obj = {
    value: 1
};

var fn1 = function() {
    console.log(this.value);
};

fn1.myCall(obj); //=> 1

完善

增加对原始数据类型的包装,如果传入null作为上下文,this中的this指向window

var fn1 = function () {
    console.log(this);
}
Function.prototype.myCall = function (context) {
    context.fn = this;
    context.fn();
    delete context.fn;
}

fn1.myCall('abc') //=> Uncaught TypeError:context.fn is not a function
// 还有一种情况,在非严格模式下传入null,this中的this指向window
var fn1 = function () {
    console.log(this);
}
Function.prototype.myCall = function (context) {
    // 存在就包装成对象,不存在就指向window
    context = context ? Object(context): window;
    
    context.fn = this;
    context.fn();
    delete context.fn;
}
fn1.myCall('abc'); //=> String {"abc", fn: ƒ}

call方法是可以传入参数的,并且还应该有返回值

var obj = {
    value: 1
};
var fn1 = function(x, y) {
    console.log(this.value, x, y);
    return x + y;
};

var result = fn1.call(obj, 1, 2); // 1 1 2
console.log(result); //=> 3

继续完善

// 使用ES6的展开运算符非常方便
Function.prototype.myCall = function (context, ...arg) {
    context = context ? Object(context): window;
    
    context.fn = this;
    //保存返回值,因为要删去 context.fn所以不能直接return
    var result = context.fn(...arg);
    delete context.fn;
    return result;
}

var obj = {
    value: 1
};
var fn1 = function(x, y) {
    console.log(this.value, x, y);
    return x + y;
};

var result = fn1.myCall(obj, 1, 2); // 1 1 2
console.log(result); //=> 3

但是call方法是ES3的方法,如果不使用ES6如何解决传参问题?

// 需要实现的目的
var result = context.fn(argments[0], arguments[1],...);
                        
Function.prototype.myCall = function (context) {
    context = context ? Object(context): window;
    
    context.fn = this;
    var args = [];
    
    // 注意arguments应该从1开始
    for(var i = 1; i < arguments.length; i++) {
        // 存储字符串['arguments[0]',...]
        args.push('arguments[' + i +']');
    }
    
    // 字符串拼接的时候会自动调用Array.toString方法
    // ['arguments[0]',...] => 'arguments[0], ...'
    // 拼接以后使用eval方法执行
    var result = eval('context.fn(' + args + ')');
    delete context.fn;
    return result;
}

核心思想就是拼凑出’context.fn(arguments[0], …)’,然后eval执行

apply实现

apply其实是类似的,与call的区别就在于传参形式,传入的应该是一个数组

var result = fn1.apply(obj, [1, 2]); 效果与 fn1.call(obj,1,2); 是等价的

Function.prototype.myApply = function (context, arr) {
    context = context ? Object(context): window;

    context.fn = this;

    var result = null;
    if (!arr) {
        result = context.fn();
    } else {
        var args = [];
        for(var i = 0; i < arr.length; i++) {
            args.push('arr[' + i +']');
        }
        result = eval('context.fn(' + args +')');
    }
    delete context.fn;
    return result;
}

var obj = {
    value: 1
};
var fn1 = function(x, y) {
    console.log(this.value, x, y);
    return x + y;
};

var result = fn1.myApply(obj, [1, 2]); // 1 1 2
console.log(result); //=> 3

核心思想就是拼凑出 ‘context.fn(arr[0], arr[1], …)’

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值