Skip to content
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

JavaScript 之实现 bind #5

Open
zenglinan opened this issue Aug 21, 2019 · 0 comments
Open

JavaScript 之实现 bind #5

zenglinan opened this issue Aug 21, 2019 · 0 comments

Comments

@zenglinan
Copy link
Owner

实现bind

bind会创建一个新的函数返回, 这个函数被调用的时候, this指向bind的第一个参数, 之后的一系列参数都会传入作为函数的参数

  1. 返回一个函数
  2. 指定这个函数的this
  3. 传入参数的实现

1. 实现返回函数和指定this

Function.prototype.bind = function(context){
  return () => {  // 返回新函数
    return this.apply(context)  // 新函数内部执行原函数, 并指定this, 将执行结果返回
  }
}

2. 实现传参

bind的传参机制有点奇特, 它支持分两次传参, 也就是所需参数不用一次传完, 可以分两次传

var foo = {value: 1};
function bar(name, age) {
    console.log(this.value);
    console.log(name);
    console.log(age);
}
var bindFoo = bar.bind(foo, 'daisy');  // 实现参数固化, 每次调用barFoo时, 原本所需的第一个参数固定了下来, 只用指定第二个
bindFoo('18');
// 1
// daisy
// 18

实现思路: 分别用arguments接收, 然后拼接起来

Function.prototype.bind = function(context){
  let arg = Array.from(arguments).slice(1)
  return () => {  // 返回新函数
    let arg2 = Array.from(arguments)
    return this.apply(context, arg.concat(arg2))  // 拼接参数数组返回
  }
}

以上就基本实现了bind, 但还差点东西:

  1. 如果调用bind的是一个构造函数, bind指定的this会失效
  2. 返回函数的prototype应指向原函数的prototype, 使原型链继承下来

3. 实现构造函数的判断

因为需要获取到实例的this, 所以这里就不用箭头函数了

Function.prototype.bind = function(context){
  let _this = this  // 这里的this指向调用bind的函数
  let arg = Array.from(arguments).slice(1)
  let bindFn = function(){
    let arg2 = Array.from(arguments)
    // 只需判断this的原型是否为返回函数.prototpye即可
    // 如果返回函数被用作构造函数, 内部的this指向实例, this.__proto__ === bindFn.prototype, 下面三元表达式结果为true, 此时不改变this指向
    // 如果返回函数被用作普通函数, 内部this指向window, this.__proto__ === Window.prototype, 下面三元表达式结果为false, 将this绑定为指定的context
    return _this.apply(this instanceof bindFn ? this : context, arg.concat(arg2))
  }
  return bindFn
}

4. 保持函数的原型

因为调用bind的函数可能是用作构造函数, 所以原函数上可能有些属性需要提供给实例访问

举个例子, 上面代码, 原有函数bind之前, 原有函数new出来的实例的原型指向原函数.prototype

但bind之后, 返回了bindFn, new出来的实例的原型指向bindFn.prototype, 丢失了原函数.prototype这一原型

Function.prototype.bind = function(context){
  let _this = this
  let arg = Array.from(arguments).slice(1)
  let bindFn = function(){
    let arg2 = Array.from(arguments)
    return _this.apply(this instanceof bindFn ? this : context, arg.concat(arg2))
  }
  // 这里不能直接写 bindFn.prototype = _this.prototype
  // 如果这样写, 那 bindFn.prototype 和 原函数.prototype 会持有同一个对象的引用, 一旦我们修改了其中一个, 另一个也会改变
  // 所以我们用一个函数中转
  let bus = function(){}
  bus.prototype = _this.prototype
  bindFn.prototype = new bus()  // 此时, new bindFn()实例的 __proto__ 指向这个new bus()实例, 这个new bus()实例的 __proto__ 又指向原函数.prototype, 原型链构建成功(*^▽^*)
  return bindFn
}

5. 边界情况考虑

假如 bind 的不是一个函数呢?

拒绝。

if(typrof this !== "function"){
  throw new Error("bind必须是函数调用")
  return
}

最终代码:

Function.prototype.bind = function(context){
  if(typrof this !== "function"){
    throw new Error("bind必须是函数调用")
    return
  }
  let _this = this
  let arg = Array.from(arguments).slice(1)
  let bindFn = function(){
    let arg2 = Array.from(arguments)
    return _this.apply(this instanceof bindFn ? this : context, arg.concat(arg2))
  }
  let bus = function(){}
  bus.prototype = _this.prototype
  bindFn.prototype = new bus()
  return bindFn
}

参考: mqyqingfeng/Blog#12

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

No branches or pull requests

1 participant