手写call , apply , bind 方法的实现

2024-07-29 17:13:09 浏览数 (3)

call 方法的实现

先看测试示例,搞懂call方法是干什么用的

代码语言:javascript复制
const animal = {
    name: "小鸟"
}
function foo(num1, num2) {
    console.log(num1,num2);
    console.log(this.name, "会飞翔");
}
foo.call(animal, 2, 3) 
// 输出: 
// 2,3
// 小鸟会飞翔

通过上面的测试用例,我们可以明白:

  1. call方法 修改了foo 函数的this指向 (指向了我们上方定义的animal对象)
  2. 只有函数类型才可以调用该方法
  3. 调用call 方法之后 会立即执行原函数(调用者)

有了上方的总结之后,我们便可以实现一下call方法了

1. 定义方法 (如何定义)

代码语言:javascript复制
//所有函数身上都有的方法 需要我们在Funtion 原型身上去定义
Function.prototype.myCall = function (){
...
}

2. 解决this 的指向问题

代码语言:javascript复制
Function.prototype.myCall = function(ctx){
// 创建不会和this指向的对象里面的重名的属性(方法)
        const key = Symbol()
        // 这里其实就是将调用者(哪个函数调用) 的this 绑定到传进来的对象身上
        ctx[key] = this // 这里的this就是foo() 相当于 给ctx 对象身上添加了一个函数foo 
        ctx[key]() // 然后进行调用
        delete ctx[key] // 接着显式删除这个属性
}

/* ------测试用例------- */
const animal = {
    name: "小鸟"
}
function foo() {
    console.log(this);
    console.log(this.name, "会飞翔");
}
foo.myCall(animal)

提几个东西,方便大家更好地去理解上方的代码

Symbol : 每个从 Symbol() 返回的 symbol 值都是唯一的。一个 symbol 值能作为对象属性的标识符;

代码语言:javascript复制
const s1 = Symbol()
const s2 = Symbol()
console.log(s1 === s2); // false

const s3 = Symbol("123")
const s4 = Symbol("123")

console.log(s3 === s4); //false

上方我们用到Symbol的目的就是为了避免和原对象身上的属性相冲突,所以需要一个唯一值.

其次就是ctx[key] = this 这段代码如何去理解

代码语言:javascript复制
const a = {
    color:"green"
}
function f() {
    console.log(this.color);
}
a.say = f
a.say() // green

这里我们就可以将a.say = f 等同于 ctx[key] = this,说白了 就是将foo()函数 [this]当作一个方法 挂载在 传入的那个对象animal的身上, 这样的this 统一指向animal foo.myCall(animal)

3. 解决传参的问题

首先我们要明白的一点是 参数的数量是不确定的, 这是由调用者函数定义了几个参数来决定的.

... es6 展开运算符

代码语言:javascript复制
Function.prototype.myCall = function (ctx,...args) {
    // 生成唯一标识符
    const key = Symbol('key')
    // 这里的this会指向调用者(原函数的this)
    ctx[key] = this // animal[key] = foo()
    const res = ctx[key](...args) // 这里相当于 直接调用并执行foo() 原函数
    delete ctx[key]
    return res
}

现在已经实现了, 我们可以进行测试一下

代码语言:javascript复制
const animal = {
    name: "小鸟"
}
function foo(num1, num2) {
    console.log(this); // {name: '小鸟', Symbol(key): ƒ}
    let sum = num1   num2
    console.log(this.name, "会飞翔"); //  小鸟 会飞翔
    return sum;
}
const res = foo.myCall(animal)
console.log(res); // 5

apply方法的实现

其实apply 和 call 方法非常相似,唯一的区别就是再调用的时候传参的方式的不同

参数传递方式:

  • call 方法接受参数的方式是直接列出每个参数。例如,如果你要传递两个参数,你会这样写:func.call(context, arg1, arg2)
  • apply 方法接受参数的方式是通过一个数组或者类数组对象。例如,如果你要传递多个参数,你会这样写:func.apply(context, [arg1, arg2])
代码语言:javascript复制
foo.call(animal, 2, 3)
 foo.apply(animal,[ 2, 3])

下面我们直接看代码实现吧

代码语言:javascript复制
Function.prototype.myApply = function (context, args) {
    const key = Symbol("key")
    context[key] = this
    const result = context[key](...args)
    delete context[key]
    return result
}

相比较之前call 方法的实现唯一区别就是 接收参数时候的不同, 因为第二个参数是一个数组

bind方法的实现

这里我们通过调用call 直接修改返回新函数的this指向

代码语言:javascript复制
// 1. 返回一个新函数(this指向已经修改) 指向调用者(原函数)

// 2. 参数分批传递

Function.prototype.myBind = function (context, ...args) {

    // 返回一个新的函数
    return (...reargs) => {
        // 这里的this 我们需要绑定一下 即外层的函数的this
        // 参数呢 就是我们不确定外层函数传入多少个参数, 还有 返回的新函数 又传入多少个参数
        // 那么我们便可以使用...展开运算符 
        const res = this.call(context, ...args, ...reargs)
        // 因为新函数 需要返回值,所以我们
        return res
    }
}


const food = {
    name: 'rice'
}

function a(num1, num2, num3, num4) {
    console.log(num1, num2, num3, num4);
    console.log(this.name);
    return num1   num2   num3   num4;
}

const b = a.myBind(food, 1)

const res = b(2, 3, 4)
console.log(res); // 10

0 人点赞