实现 call
来看下 call 的原生表现形式:
var foo = { value: 1,}function bar() { console.log(this.value)}bar.call(foo) // 1
从下面代码的执行后果,咱们能够看到,call 首先扭转了 this 的指向,使函数的 this 指向了 foo,而后使 bar 函数执行了。
总结如下:
- call 扭转函数 this 指向,
- 调用函数
思考一下:咱们如何实现下面的成果呢?代码革新如下:
//将bar函数挂载到foo对象上,使其成为foo的办法,用foo.bar来调用
var foo = { value: 1, bar: function () { console.log(this.value) },}foo.bar() //1
为了模仿 call 办法,咱们能够这样做:
- 将函数设为某个对象的属性(或者办法)
- 通过该对象的属性调用该函数
- 删除该对象上的这个属性(或者办法)
代码如下:
Function.prototype.myCall = function (context) { const fn = Symbol('fn') // 申明一个独有的Symbol属性, 避免fn笼罩已有属性 context = context || window // 若没有传入this, 默认绑定window对象 context.fn = this // 将函数挂载到对象的fn属性上 const args = [...arguments].slice(1) // 解决传入的参数 const result = context.fn(...args) // 通过对象的属性调用该办法 delete context.fn // 删除该属性 return result}// 测试function test(arg1, arg2) { console.log(arg1, arg2) console.log(this.a, this.b)}test.myCall( { a: 'a', b: 'b', }, 1, 2)
咱们看一下下面的代码:
- 首先咱们对参数 context 做了兼容解决,不传值,context 默认值为 window。
- 而后咱们将函数挂载到 context 下面,context.fn = this;
- 解决参数,将传入 myCall 的参数截取,去除第一位,而后转为数组;
- 调用 context.fn,此时 fn 的 this 指向 context;
- 删除对象上的属性 delete context.fn
- 将后果返回。
实现 apply
apply 和 call 实现相似,只是传入的参数模式是数组模式,而不是逗号分隔的参数序列。
因而,借助 es6 提供的...运算符,就能够很不便的实现数组和参数序列的转化。
Function.prototype.myApply = function (context) { const fn = Symbol('fn') // 申明一个独有的Symbol属性, 避免fn笼罩已有属性 context = context || window // 若没有传入this, 默认绑定window对象 context.fn = this // 将函数挂载到对象的fn属性上 const args = [...arguments].slice(1) // 解决传入的参数 const result = context.fn(args) // 通过对象的属性调用该办法 delete context.fn // 删除该属性 return result}// 测试function test(arr) { console.log(arr) console.log(this.a, this.b)}test.myApply( { a: 'a', b: 'b', }, [1, 2])
实现 bind
在模仿 bind 的实现之前,先看一下 bind 的应用案例:
var obj = { a: 1 }function bar() { console.log(this.a)}bar.bind(obj)() //here
bind 函数尽管也能扭转 bar 函数的 this,然而扭转后,函数并不会执行,只是返回一个新的函数,想执行就得持续调用,仔细观察第五行代码的写法。
依据下面的应用案例,咱们先实现一个简略版本的 bind:
Function.prototype.myBind = function (context) { return () => { // 要用箭头函数,否则 this 指向谬误 return this.call(context) }}var obj = { a: 1 }function bar() { console.log(this.a)}bar.myBind(obj)()
然而这样比拟简陋,函数的参数一多就不能解决了,如上面这种状况:
bar.bind(obj, 2)(2)// orbar.bind(obj)(2, 2)
为了兼容 bind 调用时满足参数传递的不同形式,代码批改如下:
Function.prototype.myBind = function (ctx, ...arg1) { return (...arg2) => { return this.call(ctx, ...arg1, ...arg2) }}//测试代码var obj = { a: 1 }function bar(b, c) { console.log(this.a + b + c)}bar.myBind(obj)(20, 30)bar.myBind(obj, 20, 30)()