关于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’参数
我们可以看到,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