likes
comments
collection
share

面试官:手写 call、apply 及 bind 函数

作者站长头像
站长
· 阅读数 2

call 函数

简介

call 函数的用法

    fn.call(obj,1,2);
  • call() 函数中的第一个参数表示:想让 this 指向的对象 (obj);
  • call() 函数中的第二及以后参数表示:传进去的实参;

call 函数的功能:

  • 让函数立执行;
  • 可改变 this 的指向;
  • 可实现继承问题;

call 函数的调用

const user1 = {
    name : '张三',
    age : 10
}
const user2 = {
    name: '李四',
    age: 20
}

function say(){
    console.log('name:', this.name)
    const sum = [...arguments].reduce((res, current)=> {
        res = res + current
        return res
    })
    console.log(sum);
}

say.call(user1, 1,2,3)
say.call(user2, 4,2,3)

以上是一个 call 函数调用的示例

  • 定义了两个对象分别是 user1 与 user2
  • 定义say()函数,输出当前调用的 name 与剩余参数的求和结果
  • 通过 call 函数执行 say 函数,分别传参 user1 与 user2

分别将当前 this 绑定至 user1 与 user2 ,输出结果为:

面试官:手写 call、apply 及 bind  函数

手写 call 函数

下面我们自己来实现 call 函数:

const user1 = {
    name : '张三',
    age : 10
}
const user2 = {
    name: '李四',
    age: 20
}

function say(){
    console.log('name:', this.name)
    const sum = [...arguments].reduce((res, current)=> {
        res = res + current
        return res
    })
    console.log(sum);
}

Function.prototype.myCall = function(ctx, ...args){
    ctx = (ctx===null || undefined)? globalThis : Object(ctx)
    const key = Symbol('temp');
    Object.defineProperty(ctx, key, {
        enumerable: false,
        value: this
    })
    const result = ctx[key](...args)
    delete ctx[key]
    return result

}
say.myCall(user1,1,2,3)
  • 首先还是先定义对象 obj 与 obj2,函数 say()
  • 在函数原型上新增 myCall方法,接收两个参数,ctx 为必填参数表示 this 的指向,...args接收剩余参数
  • 判断 ctx 的是否为 null 或者 undefined,如果是的话,当前 this 指向 globalThis, (globalThis旨在通过定义一个标准的全局属性来整合日益分散的访问全局对象的方法。该提案目前处于第四阶段,这意味着它已经准备好被纳入ES2020标准。所有流行的浏览器,包括Chrome 71+、Firefox 65+和Safari 12.1+,都已经支持这项功能。你也可以在Node.js 12+中使用它。)

面试官:手写 call、apply 及 bind  函数

  • 需要给ctx 定义个属性将剩余参数传入进去,为了避免与绑定的 ctx 参数重复,使用 Symbol()定义 key 值

  • 定义的 key 值通过 Object.defineProperty(obj,key,decriptor)方法传入,obj为目标对象,key 为 属性 值,descriptor是我们操作属性的具体描述,这里我们使用enumerable不可被枚举,具体包含的参数有:

    • value:被定义的属性的值,默认值为undefind
    • writable:是否可以被重写,也就是value属性是否可以被赋值,当writable的值为true的时候,可以被重写,反之不可以。
    • enumerable:该属性是否可以被枚举,值为true可以被枚举,反之则不可以。
    • configurable:该属性是否可以被删除,或者是否可以被修改,值为true可以被删除或者被修改,反之则不可以。
  • 将剩余参数传递给定义好的函数

  • 将定义的属性删除,return 结果出去

  • 最后,使用 say().myCall(user1,1,2,3)调用,输出的结果为:

面试官:手写 call、apply 及 bind  函数

总结

使用 globalThis 可以在浏览器以及 node 环境上运行,Symbol定义唯一 key 值等操作

apply函数

简介

apply 函数跟 call 函数的区别在于穿参形式不一样,apply 函数的第二个参数是一个数组,示例:

const user1 = {
    name : '张三',
    age : 10
}
const user2 = {
    name: '李四',
    age: 20
}

function say(){
    console.log('name:', this.name)
    console.log('args:',[...arguments])
    const sum = [...arguments].reduce((res, current)=> {
        res = res + current
        return res
    })
    console.log('sum:',sum);
}


say.apply(user1, [1,2,3])
say.apply(user2, [2,3,4])

运行结果:

面试官:手写 call、apply 及 bind  函数

面试官:手写 call、apply 及 bind  函数

手写 apply 函数

将 myCall 函数改造 下

const user1 = {
    name : '张三',
    age : 10
}
const user2 = {
    name: '李四',
    age: 20
}

function say(){
    console.log('name:', this.name)
    console.log('args:',[...arguments])
    const sum = [...arguments].reduce((res, current)=> {
        res = res + current
        return res
    })
    console.log('sum:',sum);
}

Function.prototype.myApply = function(ctx, ...args){
    ctx = (ctx===null || undefined)? globalThis : ctx
    const key = Symbol('temp');
    Object.defineProperty(ctx, key, {
        enumerable: false,
        value: this
    })
    console.log(...args)
    // 注意该处的传参形式
    const result = ctx[key](...args[0])
    delete ctx[key]
    return result

}
say.myApply(user2,[2,3,4])

总结

myApply 函数跟 call 函数仅在传参上有区别,传参形式改为数组即可

bind 函数

简介

先看bind函数的用法

globalThis.c = 1
var a = {
    b: function() {
        var func = function() {
            var c = 3
            console.log(this.c);
            console.log(this)
        }
        func();
    },
    c: 'hello'
}
a.b(); // 1

输出结果为:

面试官:手写 call、apply 及 bind  函数

要使输出结果为 hello,用 bind 函数有以下几种写法

// 方式一 保存 当前 this
globalThis.c = 1
var a = {
    b: function() {
        const that = this
        var func = function() {
            console.log(that.c);
        }
        func();
    },
    c: 'hello'
}
a.b(); // 'hello'


// bind 写法 1
var a = {
    b: function() {
        var func = function() {
            console.log(this.c);
        }.bind(this)
        func();
    },
    c: 'hello'
}
a.b(); // 'hello'


// bind 写法 2
var a = {
    b: function() {
        var func = function() {
            console.log(this.c);
        }
        func.bind(this)();
    },
    c: 'hello'
}
a.b(); // 'hello'


// bind 带参传入
var a = {
    b: function(x,y) {
        var func = function(x,y) {
            console.log(this,x,y);
            return this.c+x+y
        }
        return func.bind(this)(x,y);
    },
    c: 'Hello'
}
const result = a.b(',World','!'); 
console.log(result) // 'Hello,World!'

手写bind函数

const user1 = {
    name: '张三',
    age: 10
}

function getUserInfo () {
    return `我叫${this.name},年龄${this.age}。args是${[...arguments]}`
}

Function.prototype.myBind = function (ctx, ...args) {
    const fn = this; //保存原函数
    const resFn =  function F(...fargs) {
        if (Object.getPrototypeOf(this) === F.prototype) {
            return new fn(...args,...fargs)
        } else {
            return fn.apply(ctx, [...args,...fargs])
        }
    }
    return resFn
}

const result = getUserInfo.myBind(user1,2,3)(1,4);
var obj = getUserInfo.myBind(user1,2,3);
const objRes = new obj(1,4)
  • 定义 user1 对象,里面有两个属性分别是name、age
  • 定义函数 getUserInfo 获取 name、age 的值,将剩余参数一起返回
  • 在 function 的原型链上增加 myBind 方法,接收两个参数,第一个需要绑定 this 的目标对象,...args 接收剩余参数
  • 将当前 this 复制给 fn 保存原函数
  • bind 函数返回的是一个函数,对于函数调用有两种方式,一种是直接调用,另外一种是通过 new 的方式。
  • 判断当前是否是 new 方式调用,是的话直接 new fn()将所有参数拼接起来,传递给 fn
  • 函数直接调用则返回 fn.apply(), 第一个参数为ctx,剩余参数放在数组中传递给 fn。