手撕代码之手写JS的call、apply和bind方法

在JavaScript中,函数是一等公民,同时函数也是对象,可以像其他对象一样被传递、赋值、修改和调用。在函数调用时,可以通过调用函数对象的call、apply和bind方法来改变其执行上下文或生成一个新的函数,这些方法也是 JavaScript函数的重要部分。

有时候,我们需要在调用函数时动态地改变执行上下文,或者为函数生成一个绑定了特定上下文的新函数。这时,就需要用到call、apply和bind这三个专门用来改变函数执行上下文的方法。

在本文中,我们将手写JavaScript的call、apply和bind方法,让读者在深入了解其内部实现的同时,提升对这些方法的理解和运用。

一、基本概念

call、apply和bind方法是JavaScript的三个基本方法,它们都可以用来改变函数执行上下文中的this值。

其中,call和apply方法是直接对函数进行调用,并且允许我们在调用时手动传入this指向的对象和函数参数,两者的区别在于参数的传递方式不同:call方法的参数是一个一个地传入,而apply方法的参数是一个数组或类数组对象形式传入。

相比之下,bind方法则是返回一个新的函数,并且该新函数的执行上下文中的 this值会被永久绑定到bind方法的第一个参数所指向的对象,这也是bind方法与call和appl 方法最大的区别。

代码举例:

1、call方法的代码示例

function fn(greeting) {

console.log(greeting + ', ' + this.name + '!');

}

var user = { name: 'Alex' };

// 需要改变this指向的函数fn调用Function.prototype身上的call方法

// call方法会直接调用函数,第一参数为要指向的对象,第二个参数开始为依次传入函数的参数

fn.call( user, "hello" ) // "hello, Alex!"

2、apply方法的代码示例

function fn(greeting) {

console.log(greeting + ', ' + this.name + '!');

}

var user = { name: 'Alex' };

// 需要改变this指向的函数fn调用Function.prototype身上的apply方法

// apply方法会直接调用函数,第一参数为要指向的对象,要传入的参数放入第二参数的数组中

fn.apply( user, ["hello"] ) // "hello, Alex!"

3、bind方法的代码示例

function fn(greeting1, greeting2) {

console.log(greeting1 + ', ' + this.name + '!',greeting2);

}

var user = { name: 'Alex' };

// 需要改变this指向的函数fn调用Function.prototype身上的call方法

// bind方法会返回一个新函数,第一参数为要指向的对象,第二个参数开始为依次传入函数的参数

var newFn = fn.bind(user, "hello")

// 可以在调用返回函数的时候继续补充传参,这就是函数柯里化

newFn("hi") // "hello, Alex!" "hi"

// 这里需要注意,bind方法只能改变一次this指向,第二次改变的时候,依然执行第一次改变时的结果

// 不过可以在第二次改变的时候补充传参

var newFn1 = newFn.bind({ name: 'new' }, "hi")

newFn1() // "hello, Alex!" "hi"

注意:bind方法只能改变一次this指向,第二次改变后再调用,获取的还是第一次改变的this指向,bind返回的函数可以继续补充传递参数。

总之,使用call、apply和bind方法可以让我们更加灵活地操作函数的执行上下文,并且方便地将一个函数应用于不同的对象上,提高了代码的重用性和可读性。

二、手写代码

1、手写call方法

call方法的作用是改变函数的执行上下文,其实现思路主要有以下几步:

(1)将调用call方法的函数绑定到需要指向的对象上;

(2)执行绑定后的函数,获取执行结果并返回。

下面是 call 方法的代码实现:

Function.prototype.myCall = function(obj, ...args) {

// 如果没有传入要绑定的obj,或者值为null或undefined则赋值为window

obj = obj || window;

// 为obj添加一个属性,其值也指向该函数

const symbol = Symbol('fn');

obj[symbol] = this;

// 利用obj调用该函数,此时函数里的this就是obj了,并获取函数的返回结果

const result = obj[symbol](...args);

// 删掉绑定的函数

delete obj[symbol];

// 导出函数的返回结果

return result;

}

2、手写apply方法

apply方法和call方法的作用相同,也是改变函数的执行上下文,只是apply方法接受一个包含参数的数组作为函数的第二个参数。其实现思路主要有以下几步:

(1)将调用apply方法的函数绑定到需要指向的对象上;

(2)执行绑定后的函数,获取执行结果并返回。

下面是apply方法的代码实现:

Function.prototype.myApply = function(obj, args) {

// 如果没有传入要绑定的obj,或者值为null或undefined则赋值为window

obj= obj|| window;

// 为obj添加一个属性,其值也指向该函数

const symbol = Symbol('fn');

obj[symbol] = this;

// 利用obj调用该函数,此时函数里的this就是obj了,并获取函数的返回结果

// 将数组用展开运算符展开,作为实参传入函数中

const result = obj[symbol](...args);

// 删掉绑定的函数

delete obj[symbol];

// 导出函数的返回结果

return result;

}

3、手写bind方法

bind方法的作用是将函数与指定的上下文对象进行绑定,并返回一个绑定后的函数。其实现思路主要有以下几步:

(1)创建一个新函数,绑定上下文对象和新函数的参数;

(2)返回绑定后的新函数。

下面是bind方法的代码实现:

Function.prototype.myBind = function(obj, ...args1) {

// 缓存调用bind的函数

const self = this;

// 嵌套一层函数,返回函数调用apply方法的结果

// 此处也可以补充传参

return function(...args2) {

return self.apply(obj, [...args1, ...args2]);

}

}

在上述实现中,使用了rest参数语法(...args)来获取函数的参数,以及扩展语法(...)来合并函数的参数。实现了自定义的call、apply和bind方法以后,就可以方便地通过这些方法来改变函数的执行上下文,实现更加灵活和多样化的编程需求。

三、常见应用场景

1、函数继承

在进行函数继承时,可以使用call或apply方法将父函数的执行上下文绑定到子函数上,从而实现在子函数中调用父函数的方法或属性,例如:

function A(name) {

this.name = name

this.sayHi = function() {

console.log('Hi, ' + this.name)

}

}

function B(name) {

A.call(this, name) // 将父类的实例绑定到子类上

}

const b = new B('Tom')

b.sayHi() // Hi, Tom

2、改变函数执行上下文

在调用函数时,可以使用call或apply方法改变函数的执行上下文,从而实现在不同的环境中使用同一个函数,例如:

const obj1 = { name: 'Tom' }

const obj2 = { name: 'Jerry' }

function sayHi() {

console.log('Hi, ' + this.name)

}

sayHi.call(obj1) // Hi, Tom

sayHi.call(obj2) // Hi, Jerry

3、函数柯里化

函数柯里化(Currying)是一种特殊的函数套用技术,即将一个N元函数转换为n个一元函数,从而实现对函数参数的逐步细化。在函数柯里化过程中,可以使用bind方法来实现对函数的参数预置,例如:

function add(a, b, c) {

return a + b + c

}

const add2 = add.bind(null, 2) // 将 add 函数的第一个参数预置为 2

console.log(add2(3, 4)) // 9

在上述代码中,通过bind方法将add函数的第一个参数预置为2,从而生成了一个新函数add2,当调用add2函数时只需要传入剩余的两个参数,就可以得到预期的结果。

以上就是call、apply和bind方法的常见应用场景,它们可以极大地丰富我们编程的技巧和思路,提高代码的可读性和可维护性。

总结

虽然JavaScript中内置了call、apply和bind方法,但它们的背后的实现原理并不复杂,我们可以通过手写这些方法来更好地理解它们的本质。

手写call、apply和bind方法的主要思路就是利用 arguments 对象以及 Function.prototype 来模拟函数的执行环境和传参过程,然后使用 Function.prototype的call、apply和bind方法来绑定函数的执行上下文和参数列表。

尽管手写这些方法可能只在一些特殊场景下才会用到,但它们对于我们理解JavaScript中的函数调用机制和上下文切换等概念具有重要意义。

总的来说,熟练掌握函数的调用、应用和绑定方法,可以帮助我们更加灵活地开发JavaScript应用程序,提高代码的可读性、可维护性和性能,从而为构建更好的Web应用奠定坚实的基础。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值