8000 深入浅出JavaScript之手写bind · Issue #19 · tiger5wang/blog · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

深入浅出JavaScript之手写bind #19

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
tiger5wang opened this issue Apr 7, 2021 · 1 comment
Open

深入浅出JavaScript之手写bind #19

tiger5wang opened this issue Apr 7, 2021 · 1 comment

Comments

@tiger5wang
Copy link
Owner
tiger5wang commented Apr 7, 2021

原文:mqyqingfeng/Blog#12

一句话介绍 bind:

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

由此我们可以首先得出 bind 函数的两个特点:

  1. 返回一个函数
  2. 可以传入参数

返回函数的实现方法

从第一个特点开始,举个例子:

let foo = {
  value: 1
}

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

// 返回一个函数
let bindFoo = bar.bind(foo)

bindFoo() // 1
Function.proptotype.bind2 = function(context) {
  var self = this;
  return function() {
    return self.apply(context);
  }
}

此外,之所以 return self.apply(context),是考虑到绑定函数可能是有返回值的。同样是上面的例子,更改一下:

let foo = {
  value: 1
}

function bar() {
  return this.value
}

// 返回一个函数
let bindFoo = bar.bind(foo)

console.log(bindFoo()) // 1

传参的实现方法

接下来看一下第二点,可以传参,这里有两个问题,一个是bind 的时候传参,一个是 执行bind 返回函数的时候传参,是不是这两个地方都可以传参呢?先看一下实际bind 的使用:

let foo = {
  value: 1
}

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

let bindFoo = bar.bind(foo, 'tom');
bindFoo(18);
// 1
// tom
// 18

函数需要传 name 和 age 两个参数,竟然还可以在 bind 的时候,只传一个 name,在执行返回的函数的时候,再传另一个参数 age!

这种情况我们使用 arguments 进行处理:

Function.prototype.bind2 = function(context) {
  let self = this;
  // 这里得到的是 bind 的时候传递的参数
  let bindArgs = Array.prototype.slice.call(arguments, 1)
  
  return function() {
    // 这里获取的是 bind 生成的新函数的参数
    let args = Array.prototype.slice.call(arguments);
    return self.apply(context, bindArgs.concat(args);
  }
}

构造函数效果的实现方法

完成了这两点,最难的部分到啦!因为 bind 还有一个特点,就是

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

也就是说当 bind 返回的函数作为构造函数的时候,bind 时指定的 this 值会失效,但传入的参数依然生效。举个例子:

let value = 1;

let foo = {
  value: 2
}

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

bar.prototype.friend = 'kailen';

bindFoo = bar.bind(foo, 'tom');

let obj = new bindFoo(18)
// undefined
// tom
// 18

console.log(obj.habit) // playing
console.log(obj.friend) // kailen

注意:尽管在全局和 foo 中都声明了 value 值,最后依然返回了 undefind,说明绑定的 this 失效了,如果了解 new 的实现,就会知道这个时候的 this 已经指向了 obj。

可以通过修改返回函数的原型实现:

Function.prototype.bind2 = function(context) {
  let self = this;
  let bindArgs = Array.prototype.slice(arguments, 1);
  
  function fbound() {
    let args = Array.prototype.slice(arguments);
    // 作为构造函数时,this 执行实例,将绑定函数的 this 指向该实例,可以让实例获取来自绑定函数的值
    // 作为普通函数时,this 指向 window,将绑定函数的 this 指向 context
    return self.apply(this instanceof fbound ? this: context, args.concat(bindArgs))
  }
  // 修改返回函数的 prototype 为绑定函数的 prototype,这样实例就可以继承 绑定函数原型中的值
  fbound.prototype = this.prototype;
  return fbound;
  

构造函数的优化实现

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

Function.prototype.bind2 = function(context) {
  let self = this;
  let bindArgs = Array.prototype.slice.call(arguments, 1);
  
  function fbound () {
    let args = Array.prototype.slice.call(arguments);
    return self.apply(this instanceof fbound ? this: context, args.concat(bindArgs))
  }
  
  function fNOP () {}
  
  fNOP.prototype = this.prototype;
  fbound.prototype = new fNOP();
  return fbound;
}

容错处理

如果调用 bind 的不是 函数需要抛出错误:

if (typeof this !== 'function') {
  throw new Error('Function.prototype.bind - what is trying to be bound is not callable')
}

最终代码

Function.prototype.bind2 = function(context) {
  if (typeof this !== 'function') {
    throw new Error('Function.prototype.bind - what is trying to be bound is not callable')
  }
  
  let self = this;
  let bindArgs = Array.prototype.slice.call(arguments, 1);
  
  function fbound () {
    let args = Array.prototype.slice.call(arguments);
    return self.apply(this instanceof fbound ? this: context, args.concat(bindArgs));
  }
  
  function fNOP () {};
  fNOP.prototype = this.prototype;
  fbound.prototype = new fNOP();
  
  return fbound;
}
@xionglinyong
Copy link

大佬,这几行代码
function fNOP () {}; fNOP.prototype = this.prototype; fbound.prototype = new fNOP();
可以优化成这行代码:
fbound.prototype = self.prototype

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants
0