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){
 如果第一个参数没有传值,默认指向window
let quote = quoteObj || window;
新建一个唯一symbol变量,避免变量重复
let func = Symbol()
例如:使用时是print.myCall(originPerson),所以这里的this指向 print
print定义在quote.fun上(为了能以对象调用形式绑定this)
quote[func] = this;
在此处看看quote是什么样的:
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 : []
以对象调用形式调用func
const res = args.length > 0 ? quote[func](...args) : quote[func]()
删除该方法,不然会对quoteObj造成污染(添加了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)
                }
            }