JavaScript手写系列之bind
// 示例 1: const goods = { name: 'orange', getName: function() { return this.name } } const unBindGetName = goods.getName; console.log(unBindGetName()); // this 指向的是 window,所有打印结果是 '' const bindGetName = goods.getName.bind(goods); console.log('bindGetName :>> ', bindGetName); // getName: function() { return this.name } console.log('bindGetName :>> ', bindGetName()); // bindGetName :>> orange // 示例 2: function Point(x, y) { this.x = x this.y = y } Point.prototype.getPos = function() { return `${this.x}, ${this.y}` } const p = new Point(1, 2) console.log('p.getPos :>> ', p.getPos()) // p.getPos :>> 1, 2 const obj = { x: 7, y: 8 } const BindPoint = Point.bind(obj, 3, 4) const bindP = new BindPoint(5, 6) console.log('BindPoint :>> ', BindPoint); // ƒ Point(x, y) { this.x = x this.y = y } console.log('Point :>> ', Point); // ƒ Point(x, y) { this.x = x this.y = y } console.log('bindP.getPos :>> ', bindP.getPos()) // bindP.getPos :>> 3, 4 console.log('bindP instanceof Point :>> ', bindP instanceof Point); // bindP instanceof Point :>> true console.log('bindP instanceof BindPoint :>> ', bindP instanceof BindPoint); // bindP instanceof BindPoint :>> true console.log('BindPoint.prototype :>> ', BindPoint.prototype); // BindPoint.prototype :>> undefined console.log('Point.prototype :>> ', Point.prototype); // constructor: function Point(x, y) {} /** * bind 源码分析 * bind 方法创建一个新的函数 * 在 bind() 函数被调用时 * 新函数的 this 会被指定为 bind() 的第一个参数 * 其余参数将作为新函数的参数 */ // 第一种实现方式 ;(function(window) { if(!Function.prototype._bind) { Function.prototype._bind = function() { let fn = this, // 谁调用 _bind 谁就是 this,也就是原始函数 slice = Array.prototype.slice, context = arguments[0], // 取到 bind 函数的第一个入参,也就是要绑定的上下文环境 args = slice.call(arguments, 1) // 截取参数,供函数调用时使用 if(typeof fn !== 'function') { // 如果不是函数,则提示 throw new TypeError('必须是函数才能绑定') } return function() { console.log('this :>> ', this); // window const bindArgs = slice.call(arguments) // bind 后的函数,传入的入参 const allArgs = args.concat(bindArgs) // 拼接 bind 的入参和 bind 之后的函数的入参 return fn.apply(context, allArgs) // 执行函数,使用 apply 改变函数的执行者 } } } })(window) // 测试 const testObj = { language: 'JavaScript', getLang: function() { return this.language } } const unBindGetLang = testObj.getLang const bindGetLang = unBindGetLang._bind(testObj) console.log('bindGetLang() :>> ', bindGetLang()) // bindGetLang() :>> JavaScript // 第二种实现方式 ;(function(window) { if(!Function.prototype.myBind) { Function.prototype.myBind = function() { let fn = this, // 谁执行 bind,this 就指向谁,this此时指向的原函数 slice = Array.prototype.slice, // 缓存数组的 slice 方法 context = arguments[0], // 获取 bind() 函数的第一个参数,也就是要绑定的上下文环境 args = slice.call(arguments, 1), // 获取 bind() 函数的入参 fNOP = function() {}, // 定义一个空函数 fnBind = function() { const bindArgs = slice.call(arguments) // 获取入参,转为数组 const allArgs = args.concat(bindArgs) // 将 bind() 函数的入参与返回函数的入参拼接起来 console.log(this); // fnBind {} this 指向 fnBind 构造函数的实例 或者指向 Window /** * 此处需要判断是不是 new 操作符构造出来的实例 * 1.如果是 new 操作符执行的,则 this 指向的是 new 出来的实例,即 fnBind {} * 2.否则,this 指向 window 对象 */ // 第一种判断方法, __proto__ 在IE下存在兼容性问题,不推荐使用 const isUseNew1 = this.__proto__ === fnBind.prototype; console.log('isUseNew1 :>> ', isUseNew1); // 第二种判断方法 const isUseNew2 = this instanceof fNOP console.log('isUseNew2 :>> ', isUseNew2); // 第三种判断方法 const isUseNew3 = fNOP.prototype.isPrototypeOf(this) console.log('isUseNew3 :>> ', isUseNew3); const fnContext = isUseNew3 ? this : context return fn.apply(fnContext, allArgs) } if(typeof fn !== 'function') { throw new TypeError('bind 绑定的必须是函数') } // 如果原函数有 prototype 属性,就将原函数的 prototype 赋值给 fNOP的原型 // fNOP 和 原函数原型就指向同一块内存空间 if(fn.prototype) { fNOP.prototype = fn.prototype } // fnBind 的原型指向 fNOP的实例,通过原型链实现了一个继承 fnBind.prototype = new fNOP() // fnBind.prototype = Object.create(fNOP.prototype) // 包装对象 return fnBind } } })(window) // test function Person(name, age) { this.name = name this.age = age } Person.prototype.getInfo = function() { return `${this.name}, ${this.age}` } const personObj = { name: 'Jack', age: 18 } const BindPerson = Person.myBind(personObj, 'Alen', 20) // bindPerson.getInfo :>> Alen, 20 const bindPerson = new BindPerson() console.log('bindPerson.getInfo :>> ', bindPerson.getInfo()); console.log('bindPerson :>> ', bindPerson); // bindPerson :>> fnBind {name: "Alen", age: 20} // test function getX() { return this.x } const obj2 = { x: 2 } const bindGetX = getX.myBind(obj2) console.log('bindGetX() :>> ', bindGetX()) // bindGetX() :>> 2
参考文献:
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/isPrototypeOf
https://zhuanlan.zhihu.com/p/83778815
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Function/bind