call() apply() bind() 都是改变this指向的方法
call() apply() bind() 的第一个参数都是this的指向对象,后面的参数是给调用的方法传参
背景~例如 有以下代码:
var name = "window-name", age = 1000, skil = "anyThing" ; var originPerson = { name: 'liLi', age: '18', skil: 'dance' } function print(valOne,valTwo){ console.log("name:",this.name) console.log("age:",this.age) console.log("skil:",this.skil) console.log("valOne:",valOne) console.log("valTwo:",valTwo) }
调用print,并传两个参数1和2:
print(1,2) //name: window-name //age: 1000 //skil: anyThing //valOne: 1 //valTwo: 2
第一部分 call(),apply(),bind()的区别:
1:call(), apply() 立即执行,bind()要调用再执行
上代码:
print.call(originPerson) // name: liLi // age: 18 // skil: dance // valOne: undefined // valTwo: undefined print.apply(originPerson) // name: liLi // age: 18 // skil: dance // valOne: undefined // valTwo: undefined print.bind(originPerson) // 此处未打印出东西 print.bind(originPerson)() // name: liLi // age: 18 // skil: dance // valOne: undefined // valTwo: undefined
由上代码可见,print.bind()之后,并没有打印出东西,要再调用之后才执行print()方法
注:这里没有给print()传参,所以valOne, valTwo 打印都是undefined,那么传参要怎么传呢?这就要讲一下第二点区别了:
2. call(),apply(),bind()第一个参数后面参数的传递方式不一样了:
call(),bind()的第二个,第三个,第四个...参数,是用逗号隔开,传递的
apply() 需要把多个参数放在一个数组中,作为第二个参数传递
上代码:
print.call(originPerson,"call1","call2") // name: liLi // age: 18 // skil: dance // valOne: call1 // valTwo: call2 print.apply(originPerson,"apply1","apply2") // Uncaught TypeError print.apply(originPerson,["apply1","apply2"]) // name: liLi // age: 18 // skil: dance // valOne: apply1 // valTwo: apply2 print.bind(originPerson,"bind1","bind2")() // name: liLi // age: 18 // skil: dance // valOne: bind1 // valTwo: bind2
由上代码可见,apply传多个参数会报错,需要把多个参数放在一个数组中传
第二部分:手动实现以上三个方法
1:手动实现call方法
上代码:
Function.prototype.myCall = function(quoteObj,...args){ let quote = quoteObj || window; // 新建一个唯一symbol变量,避免变量重复 let func = Symbol() // 将当前被调用的方法定义在quote.fun上 quote[func] = this; args = args ? args : [] const res = args.length > 0 ? quote[func](...args) : quote[func]() // 删除该方法,不然会对传入对象造成污染(添加该方法) delete quote[func] return res }
先来看实现效果:
print.myCall(originPerson,"call1","call2") // name: liLi // age: 18 // skil: dance // valOne: call1 // valTwo: call2
以上代码的实现思路及解析:
首先确定方法的参数
第一个参数quoteObj:this的指向对象
Function.prototype.myCall = function(quoteObj,...args){
let quote = quoteObj || window;
let func = Symbol()
quote[func] = this;
console.log(quote)
可见print方法已经定义在了quote.fun上
而我们调用print方法时:
在当前print方法的作用域里找不到name这个值,就会向父级作用域里找,
此时print的父是quote,也就是先找我们传入的第一个参数里有没有name这个值,
如果有打印的就是quote里的值,没有 就会打印出undefined
console.log(quote[func](...args)) // name: liLi // age: 18 // skil: dance // valOne: undefined // valTwo: undefined
这里做个容错
args = args ? args : []
const res = args.length > 0 ? quote[func](...args) : quote[func]()
delete quote[func]
最后myCall方法是返回出res就好了
return res }
2:手动实现apply方法
由第一部分 call(),apply(),bind()的区别 我们知道:apply前部分与call一样,第二个参数可以不传,但类型必须为数组或者类数组
所以代码实现如下:
Function.prototype.myApply = function(quoteObj,args = []){ let quote = quoteObj || window; // 新建一个唯一symbol变量,避免变量重复 let func = Symbol() // 将当前被调用的方法定义在quote.fun上 quote[func] = this; const res = args.length > 0 ? quote[func](...args) : quote[func]() // 删除该方法,不然会对传入对象造成污染(添加该方法) delete quote[func] return res }
实现效果:
print.myApply(originPerson,["apply1","apply2"]) // name: liLi // age: 18 // skil: dance // valOne: apply1 // valTwo: apply2
代码实现思路跟call差不多 ,在此就不多做赘述了
3:手动实现bind方法
由第一部分 call(),apply(),bind()的区别 我们知道:
1)bind的传参与call一样,
2)bind创建的新函数可能传入多个参数
所以代码实现如下:
Function.prototype.myBind = function(quoteObj,...args){ // 新建一个变量 赋值为this,表示当前函数 const fn = this args = args ? args : [] // 返回一个newFn函数,在里面调用fn return function newFn(...newFnArgs) { // 新的函数被当作构造函数时,返回new fn if (this instanceof newFn) { return new fn(...args,...newFnArgs) } // 这里偷个懒,直接返回当前函数调用call方法,以实现this的改变 return fn.call(quoteObj,...args,...newFnArgs) } }