欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

手写实现apply、call、bind

程序员文章站 2022-07-14 14:28:34
...
个人觉得要学会这三种手写方法需要对apply、call、bind的基本用法要有一定的了解和认识,最重要的是对this指向也要有所了解。
三种方法的手写思路整体上差不多,只要看懂了一个,基本上就可以快速扫读其他的。这是面试的重点题。

手写实现apply

  • 在函数原型上封装myApply函数 , 实现和原生apply函数一样的效果
Function.prototype.myApply = function(context) {}
  • 存储要转移的目标对象
var _this = context || window
// 上面的式子等同于下面的
//var _this = context ? context : window
  • 在转移this的对象上设定一个独一无二的属性 , 并将函数赋值给它
let key = Symbol('key')
_this[key] = this // this 是当你调用apply改变this指向时的函数 例如:fn.apply(obj,arg),那么this就是当前当前调用的函数名fn
  • 将数组里存储的参数拆分开,作为参数调用函数
let res = arguments[1] ? _this[key](...arguments[1]) : _this[key]()
  • 执行完函数之后,需要将函数释放掉,最后返回函数值
delete _this[key]
return res
  • 完整代码如下
Function.prototype.myApply = function(context) {

    // 存储要转移的目标对象
   // _this = context? context : window
    _this = context || window

    // 在转移this的对象上设定一个独一无二的属性 , 并将函数赋值给它
    let key = Symbol('key')
    _this[key] = this
    // console.log(this) function showName

    // 将数组里存储的参数拆分开,作为参数调用函数
    let res = arguments[1]? _this[key](...arguments[1]) : _this[key]()

    // 删除
    delete _this[key]

    // 返回函数返回值
    return res
}
  • 测试例子
let obj = {
    'name': '张三'
}

function showName(first, second, third) {
    console.log(first, second, third);
    console.log(this.name);
}

  • 结果
    手写实现apply、call、bind

手写实现call

  • call 的实现过程与apply几乎一样,唯一不同的是call()方法接受的是参数列表,而apply()方法接受的是一个参数数组。
  • 由于call方法与apply的方法类似,这里不再详细描述,直接看代码
/*   在函数原型上封装myCall函数 , 实现和原生call函数一样的效果   */

Function.prototype.myCall = function(context) {

    // 存储要转移的目标对象
    // _this = context? context : window
    _this = context || window

    // 在转移this的对象上设定一个独一无二的属性 , 并将函数赋值给它
    let key = Symbol('key')
    _this[key] = this
    
    // 创建空数组,存储多个传入参数
    let args = []
    
    // 将所有传入的参数添加到新数组中
    for(let i = 1; i < arguments.length; i++) {
        args.push(arguments[i])
    }

    // 将新数组拆开作为多个参数传入,并调用函数
    let res = _this[key](...args)

    // 删除
    delete _this[key]

    // 返回函数返回值
    return res
}
  • 测试例子
let obj = {
    'name': '李四'
}

function showName(first, second, third) {
    console.log(first, second, third);
    console.log(this.name);
}

showName.myCall(obj, 1, 2, 3)
  • 结果
    手写实现apply、call、bind

手写实现bind

  • 其实bind实现的思路和apply、call实现的思路也差不多,唯一不同的是apply和call是一调用之后就会立即执行,bind则返回一个函数(这个过程相当于一个闭包的过程)
  • 首先和实现apply一样,在函数原型上封装myBind函数 , 实现和原生bind函数一样的效果
Function.prototype.myBind = function(context) {}
  • 存储要转移的目标对象
var _this = context || window
// 上面的式子等同于下面的
//var _this = context ? context : window
  • 在转移this的对象上设定一个独一无二的属性 , 并将函数赋值给它
let key = Symbol('key')
_this[key] = this // this 是当你调用apply改变this指向时的函数 例如:fn.apply(obj,arg),那么this就是当前当前调用的函数名fn
  • 最后需要返回一个函数,也就是在函数里创建闭包。
return function() {

        // 将所有参数先拆分开,再添加到新数组中,以此来支持多参数传入以及数组参数传入的需求
        let args = [].concat(...arguments)
        // console.log(args);

        // 调用函数
        let res = _this[key](...args)

        // 删除
        delete _this[key]

        // 返回函数返回值
        return res
    }
  • 完整代码
Function.prototype.myBind = function(context) {

    // 存储要转移的目标对象
    // _this = context? context : window
    _this = context || window

    // 在转移this的对象上设定一个独一无二的属性 , 并将函数赋值给它
    let key = Symbol('key')
    _this[key] = this

    // 创建函数闭包
    return function() {

        // 将所有参数先拆分开,再添加到新数组中,以此来支持多参数传入以及数组参数传入的需求
        let args = [].concat(...arguments)
        // console.log(args);

        // 调用函数
        let res = _this[key](...args)

        // 删除
        delete _this[key]

        // 返回函数返回值
        return res
    }
}
  • 在上面的实现过程中,在创建闭包的过程中(即返回函数),也可以采用apply和call的方法实现,这也是面试常考(最近看面试题看到的)。
  • 具体实现过程也非常简单
  • 完整代码如下
// 用apply和call实现bind
Function.prototype.myBind2 = function(context) {
    //存储要转移的目标对象
    // _this = context? context : window
    _this = context || window

    // 在转移this的对象上设定一个独一无二的属性 , 并将函数赋值给它
    let key = Symbol('key')
    _this[key] = this
    return function() {
        // 注意arguments的值: [Arguments] { '0': [ 4, 5, 6 ] }
        // console.log(arguments);
        // 将arguments转换数组的另一方法: Array.prototype.slice.call(arguments[0])
        let arr = Array.from(arguments[0]) || []
        // 使用apply实现
        // _this[key].apply(_this,arr)
        // 使用call实现
        _this[key].call(_this,...arr)
    }

}
  • 测试例子
let obj = {
    'name': '王五'
}

function showName(first, second, third) {
    console.log(first, second, third);
    console.log(this.name);
}

showName.myBind2(obj)([4,5,6])
  • 结果
    手写实现apply、call、bind

总结

这三种方法实现的共同思路就是往传递过来的对象添加一个新的属性,即为前面代码中的_this[key] = this,_this就是参数当中的对象(就是测试例子当中的obj),key就是添加到对象中的属性,而属性值就是当前调用myApply 、 myCall 、 myBind三者之一的函数(就是测试例子当中的showName)。后面的过程就是根据apply、call、bind的特性去完成。