Please enable Javascript to view the contents

从头学习js-11-模拟实现bind

 ·  ☕ 2 分钟

这个系列是我读冴羽老师博客的感悟,
加入了个人的解读和练习题的解答

MDN 对 bing 的定义

bind()方法会创建一个新函数。当这个函数被调用时,bind()的第一个参数将作为它运行时的 this,之后的一序列参数将会在传递的实参前传入作为它的参数

bind 函数的两个特点:
1.返回一个函数
2.可以传入参数

例子 1:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
var foo = {
  value: 1,
}

function bar() {
  console.log(this.value)
}

var bindFoo = bar.bind(foo)
bindFoo() //1

第一版

1
2
3
4
5
6
function myBind(context) {
  var self = this
  return function () {
    return self.apply(context)
  }
}

刚才的定义中提到 bind 还支持传参数来看个例子:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
var foo = {
  value: 1,
}

function bar(name, age) {
  console.log(this.value)
  console.log(name)
  console.log(age)
}

var bindFoo = bar.bind(foo, 'daisy')
bindFoo('18')
// 1
// daisy
// 18

第二版

1
2
3
4
5
6
7
8
9
function myBind(context) {
  var self = this
  var args = Array.prototype.slice.call(arguments, 1)

  return function () {
    var bindArgs = Array.prototype.slice.call(arguments)
    return self.apply(context, args.concat(bindArgs))
  }
}

此时到了最难的部分,bind 还有一个特性:
一个绑定函数也能使用 new 操作符创建对象:这种行为就像把原函数当成构造器。提供的 this 值被忽略,同时调用时的参数被提供给模拟函数。

来看例子:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
var value = 2

var foo = {
  value: 1,
}

function bar(name, age) {
  this.habit = 'shopping'
  console.log(this.value)
  console.log(name)
  console.log(age)
}

bar.prototype.friend = 'kevin'

var bindFoo = bar.bind(foo, 'daisy')

var obj = new bindFoo('18')
// undefined 提供的this值被忽略
// daisy 调用bind时的参数被一起提供了
// 18
console.log(obj.habit)
console.log(obj.friend)
// shopping
// kevin

第三版

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
function myBind(context) {
  var self = this;
  var args = Array.prototype.slice.call(arguments, 1)
  var fBound = function(){
    var bindArgs = Array.prototype.slice.call(arguments)
    return self.apply(this instanceOf fBound? this : context, args.concat(bindArgs))
  }
  fBound.prototype = this.prototype;
  return fBound;
}

但是在这个写法中,我们直接将 fBound.prototype = this.prototype,
我们直接修改 fBound.prototype 的时候,也会直接修改绑定函数的 prototype。
这个时候,我们可以通过一个空函数来进行中转:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
function myBind(context) {
  var self = this;
  var args = Array.prototype.slice.call(arguments, 1)

  var fNop = function(){}
  var fBound = function(){
    var bindArgs = Array.prototype.slice.call(arguments)
    return self.apply(this instanceOf fNop? this : context, args.concat(bindArgs))
  }


  fNop.prototype = this.prototype;
  fBound.prototype = new fNop()
  return fBound;
}

以后要在生产环境中使用时,我们再补上对参数和函数名字的判断

第四版

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
Function.prototype.bind = Function.prototype.bind || function(context) {
  if (typeof this !== "function") {
    throw new Error(this + ".bind is not a function!")
  }

  var self = this;
  var args = Array.prototype.slice.call(arguments, 1);

  var fNop = function(){}

  var fBound = function() {
    var bindArgs = Array.prototype.slice.call(arguments)
    return self.apply(this instanceOf fNop? this : context, args.concat(bindArgs))
  }

  fNop.prototype = this.prototype;
  fBound.prototype = new fNop();
  return fBound;
}

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
const bind = function (newThis) {
  const self = this
  const args = Array.prototype.slice.call(arguments, 1)
  const resultFn = function () {
    const args2 = Array.prototype.slice.call(arguments)
    self.apply(this instanceof resultFn ? this : newThis, args.concat(args2))
  }
  resultFn.prototype = self.prototype
  return resultFn
}

Function.prototype.bind2 = bind

function test1() {
  const testFn = function () {
    console.log(this, arguments)
  }

  const bindFn = testFn.bind2(
    {
      name: 'Neo',
    },
    123
  )

  bindFn(456)
}

function test2() {
  const a = function (name, age) {
    this.name = name
    this.age = age
  }
  a.prototype.sayHi = function () {
    console.log('hi')
  }

  const aaa = a.bind2(undefined, 'Neo', 20)

  const object = new aaa()
  console.log('object', object)
  console.log(a.prototype.isPrototypeOf(object))
  console.log(typeof object.sayHi === 'function')
}

test1() //测试 bind api是否正常
test2() //测试 bind 是否支持new
分享

Llane00
作者
Llane00
Web Developer