手写js中的call和apply

bind、call和apply的区别与联系

1、联系:

作用上:它们三个都可以绑定this指向,因为函数内部的this由运行的方式决定,this的指向常常会发生变化,这是就会产生绑定的需要。

使用上,三个函数的第一个参数都是this将要指向的对象,它们后面的参数(即除第一个参数外的其他参数位置)可以用来放被调用函数的参数

2、区别:

使用上,bind 是返回对应函数,便于稍后调用;apply 、call 则是立即调用执行 。基于此原因,在底层实现上,apply和call较为相似,所以我们今天就将它们的底层实现放在一起来探讨

实现上,apply和call的主要区别在传参方式上:call 传参是散开的,apply 传参则是数组形式的 [ ],

call和apply在传参方式上的区别是我们在底层实现上要关注的!!!

○ 代码展示call和apply传参方式上的区别:

func.call(this, arg1, arg2); // call的传参是散的
func.apply(this, [arg1, arg2]) // apply的传参则是数组形式的

手写一个call

js原生的call函数调用

let name = null,
	age = null; // 定义变量
	
let person = {	// 定义对象
	name: 'Youth',
	age: 18
};

const fn = function(name, age) {}; // 定义call要调用的函数

fn.call(person, name, age); // call调用时第一个参数是作用对象,被指定的对象

手写之前的背景知识

1、任何方法function都可以通过 fn.call() 来调用,
其中fn为某个函数,所以在实现上要具有普适性
2、 具有原型链思维:Function.prototype.call2 (call2是为了不与原生js的call发生冲突),有了原型链,我们可以在顶层设计完成call的实现

实现的思想

让方法为指定的对象打工。
call的出现,让即使一个没有自身定义方法的对象也能够调用他人实现的方法,非常灵活;
在实现call的过程中,更形象的,我们把指定的对象可以当做老板,函数则作为一个散工,老板可以选择指定使用任何一个散工为其工作,并给其一些生产资料(相当于传的参数)去做事,实现某种功能。

正式实现
1、定义测试的指定对象:老板

// 定义对象
let foo1 = { // 老板
    value: 1
}

2、定义call函数,这是在建立老板与员工的联系,它们之间的联系在js中位于原型链上,主要体现在下面的Function.prototype.call2上(Function是一个构造函数,其实每一个函数都是它的实例,包括它自己)

// 函数本身也是一个对象,所以语法上允许Function.prototype
Function.prototype.call2 = function(context=window) { // 给参数赋默认值,这是es6的语法
    // ○ 核心代码
    context.fn = this;
     // 将老板与员工联系起来,把员工(函数)的指向交给老板(指定对象)的一个属性
    // .fn  是什么? 可以动态加属性,fn是老板content的一个属性
    //这个this指向的是调用了该call2方法的对象(这里是函数,函数本身也是对象)
    
    const args = [...arguments].slice(1); // 获取传入的其他参数
    // 伪数组arguments,想要获取函数运行的参数:可以利用伪数组!
    // ... 是将数组展开,属于es6语法
    // slice(1) 是指从第二个元素开始截取后面的部分,因为call函数的第一个参数是指定对象,不需要
    
    const result = context.fn(args); 
    // fn() 较难理解,它是属性,但其内容却是this,而this指向了员工(函数),不是全局window,所以它是可以执行的!
        
    delete context.fn; // 在一个对象上删除某个属性,call完美结束!
    // 以上的语句是为了去除我们的call在实现时对象绑定函数方式的副作用,让对象不受到污染!!!
    
    return result; // 这是为了模仿函数具有返回值的特性,也可以不写
}

小结
1. 作为call函数来调用。要改变this的指向,逆向思维,改变函数的运行方式,让其利用f.call()简洁运行
2. 从打工者和老板的角度考虑call的绑定问题,打工者为函数,老板为指定对象
3. 要获取某个函数的this,可以将其作为对象,调用其他函数,其他函数中可以用到它的this
4. 伪数组获取个数未知参数。arguments是每个函数都带有的,同时我们可以通过 […arguements]将其展开
5. 消除call在 context.fn 实现时的副作用,它将传进来的对象context与函数fn绑定起来,污染了context对象,我们要通过 delete context.fn 删除该属性保证对象的纯洁性

3、定义员工:函数

function bar(name, age) { // 打工者
    console.log(name);
    console.log(age);
    // this 指向全局? js语言的bug  对象的方法被调用时
    // foo 调用方法bar,this指向由函数的调用方式来决定
    console.log(this.value); // this不指向函数自己,而是其运行环境,
}

4、调用自定义的call方法:

let name = 'Youth',
	age = 18;
bar.call2(bar, name, age) // call 调用方式

手写一个apply(与call基本类似)

apply在与call的实现上基本类似,区别在于传参方式上,这里我们仅看apply的实现方式和最后的调用方式:

实现方式:

Function.prototype.apply2 = function(context=window, args=[]) { // 给参数赋默认值,第二个参数传参是一个数组
    // ○ 核心代码
    context.fn = this; // 将老板与员工联系起来
    
    const result = context.fn(...args); // fn ,函数本身也是对象,体现在1.prototype设置上,etc
    // 将数组展开 :...args
    delete context.fn; // 在一个对象上删除某个属性,call完就结束!
    return result;
}

调用方式:

bar.apply2(bar, ['Luck', 18]) // call 调用方式,

小结

在手写call和apply时,要注意联系指定对象与函数的思想,即通过动态的给指定对象添加函数属性,以达到联系双方的目标,context.fn = this ,理解这一语句至关重要;

同时,还要理解 delete context.fn的意义,它是为了取消 对象.函数 的副作用,防止变量在使用call/apply后受到污染。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值