Object.create()和new Object()的区别

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

2019-01-03  本文已影响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
上一篇下一篇

猜你喜欢

热点阅读