美文网首页Object.create()和new Object()的区别
关于call, apply, bind方法的区别与内部实现

关于call, apply, bind方法的区别与内部实现

作者: 自律财富自由 | 来源:发表于2019-01-03 20:41 被阅读0次

    关于这三个方法,很多大神已经总结的很到位了,写这篇文章只是想加深自己的理解。
    一开始,我是在看设计模式这本书的时候,看到如下代码(new运算的过程):

    // new运算的过程
    /**
     * 1、创建一个空对象;
     * 2、该空对象的原型指向构造函数(链接原型):将构造函数的 prototype 赋值给对象的 __proto__属性;
     * 3、绑定 this:将对象作为构造函数的 this 传进去,并执行该构造函数;
     * 4、返回新对象:如果构造函数返回的是一个对象,则返回该对象;否则(若没有返回值或者返回基本类型),返回第一步中新创建的对象;
     */
    var Person = function(name) {
        this.name = name
        console.log('name is ', this.name)
    }
    Person.prototype.getName = function() {
        return this.name
    }
    var objectFactory = function() {
        // 1、创建一个空对象
        var obj = new Object()
        console.log('before shift arguments = ',arguments)
        获取构造函数
        Constructor = [].shift.call(arguments)
        console.log('after shift arguments = ', arguments)
        console.log(`Constructor = ${Constructor}`)
        // 2、该空对象的原型指向构造函数: 将构造函数的prototype 赋值给空对象的 __proto__属性;
        obj.__proto__ = Constructor.prototype
        // 3、将空对象作为构造函数的this传进去,并执行该构造函数
        var ret = Constructor.apply(obj, arguments)
        // 4、返回新对象:如果构造函数返回的是一个对象,则返回该对象;否则(若没有返回值或者返回基本类型),返回第一步中新创建的对象;
        return typeof ret == 'object' ? ret : obj
    }
    var a = objectFactory(Person, 'yandong')
    console.log('执行后的name = ', a.name)
    

    查找一番资料之后,才明白过来。
    原来,arguments是类数组,并不是真正的数组,所以不能直接调用数组的shiftf方法,但是可以通过call调用。
    call方法,表示传入的对象参数调用call前面对象的方法,并且被调用的函数会被执行,call方法的参数是当前上下文的对象以及参数列表
    apply也是如此,只不过它传入的参数是对象参数数组
    而bind,用法与apply, call一样,但是它被对象绑定的函数不会被执行,而是返回这个函数,需要你手动去调用返回的函数,才会返回结果。

    那我们来看看这句代码:
    Constructor = [].shift.call(arguments)
    意思就是:arguments对象调用数组的shift()方法。
    而shift()方法会删除并返回数组的第一个元素
    当我们执行objectFactory(Person, 'anne')的时候
    跳转到objectFactory函数内部,arguments这个类数组会全部接收Person参数以及‘anne’参数

    image.png
    我们可以看到,arguments类数组对象的值。
    所以Constructor = [].shift.call(arguments)这句代码删除并且返回的就是传入call方法或者apply方法的第一个对象参数,也就是Person。
    执行了shift之后,arguments就只剩下执行函数所需的参数列表或者参数数组了。如下图:
    image.png

    以上就是通过new返回一个对象实例的过程。

    call, apply, bind调用的区别

    // call, apply, bind的区别
    var a = {value: 1}
    function getValue(name, age) {
        console.log('arguments in fn = ', arguments)
        console.log(name, age)
        console.log(this.value)
    }
    getValue.call(a,'yandong1', 17)
    let bindFoo = getValue.bind(a, 'testBind', 45)
    console.log('bindFoo = ',bindFoo)
    bindFoo()
    getValue.apply(a,['yandong2', 18])
    var returnedFunc = getValue.bind(a,'yandong3', 19)
    console.log(returnedFunc)
    returnedFunc()
    

    执行结果:


    image.png

    我们可以看到,call, apply都是直接返回函数执行后的结果,而bind是返回一个函数,之后手动执行之后才会将结果返回。

    手动实现call方法

    // 手写模拟call方法的思想
    /**
     * call方法思想:改变this指向,让新的对象可以执行这个方法
     * 实现思路:
     * 1、给新的对象添加一个函数(方法),并让this(也就是当前绑定的函数)指向这个函数
     * 2、执行这个函数
     * 3、执行完以后删除这个方法
     * 4、可以将执行结果返回
     */
    Function.prototype.myCall = function(funcCtx) {
        // funcCtx是当前要调用函数的对象
        console.log('funcCtx = ',funcCtx)
        // this指被调用的函数
        console.log('this = ',this)
        if(typeof this != 'function') {
            throw new TypeError('Erorr')
        }
        let ctx = funcCtx || global
        console.log('arguemnets = ', arguments)
        let args = [...arguments].slice(1)
        console.log(`args = ${args}`)
    
        ctx.fn = this // 为当前对象添加一个函数fn, 值为要已经定义的要调用的函数
        console.log('ctx.fn = ', ctx.fn)
        // 执行添加的函数fn
        var result = ctx.fn(...args)
        // 执行完以后删除
        delete ctx.fn
        return result
    }
    getValue.myCall(a,'test', 20)
    

    执行结果:


    image.png

    手动实现apply方法

    与call方法的思想类似,只不过它需要判断一下参数数组是否存在

    // apply
    Function.prototype.myApply = function(funcCtx) {
        console.log(this)
        if(typeof this != 'function') {
            throw new TypeError('Erorr')
        }
        let ctx = funcCtx || global
    
        ctx.fn = this
        console.log('arguemnets = ', arguments)
        let result
        if(arguments[1]) {
            result = ctx.fn(...arguments[1])
        } else {
            result = ctx.fn()
        }
        delete ctx.fn
        return result
    }
    getValue.myApply(a, ['eo', 50])
    
    image.png

    手动实现bind方法

    //bind实现
    /**
     * 实现思想:
     * 1、返回一个函数,其他与call, apply类似
     * 2、如果返回的函数作为构造函数,bind时指定的 this 值会失效,但传入的参数依然生效。
     */
    Function.prototype.myBind = function(funcCtx) {
        let ctx = funcCtx || global
        console.log(this)
        let _this = this
        let args = [...arguments].slice(1)
        // 作为构造函数使用
        let Fbind = function() {
            let self = this instanceof Fbind ? this : ctx
            return _this.apply(self,args.concat(...arguments))
        }
        let f = function() {}
        f.prototype = this.prototype
        Fbind.prototype = new f()
        return Fbind
    }
    
    var value = 2
    var foo = {
        value: 1
    }
    function bar(name, age) {
        this.habbit = 'shopping'
        console.log('bar this.value = ', this.value)
        console.log(name, age)
    }
    bar.prototype.friend = 'shuaige'
    var bindFoo = bar.myBind(foo, 'testbind',111)
    // 返回的函数直接调用
    bindFoo()
    

    执行结果


    image.png
    // 当 bind 返回的函数作为构造函数的时候,bind 时指定的 this 值会失效,但传入的参数依然生效。
    var obj = new bindFoo('18')
    console.log('obj = ', obj)
    console.log(obj.friend)
    console.log(obj.habbit)
    

    执行结果


    image.png

    相关文章

      网友评论

        本文标题:关于call, apply, bind方法的区别与内部实现

        本文链接:https://www.haomeiwen.com/subject/cqmqrqtx.html