// 示例 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