前言:
为什么面试官总爱让实现一个bind函数?
他想从bind中知道些什么?
一个小小的bind里面内有玄机?
今天来刨析一下实现一个bind要懂多少相关知识点,也方便我们将零碎的知识点串联起来。
👍 看完有用的同学记得点个赞再走,您的鼓励-我莫大的动力
看完能学到什么
- 实现bind
- new原理
本文章的叙事步骤
- bind函数作用
- 模拟bind的要点
- 实现思路
- new函数特殊情况(this&父原型)
-------------人工分割线-------------
bind函数的作用
返回一个能够改变this指向的函数。
模拟bind的要点
- 改变this指向
- 返回函数
实现思路
创建一个待返回的函数,函数内部利用call/apply改变指向,call/apply的参数从arguments中获取。
实现代码如下:
Function.prototype.myBind = function () {
let exeFunc = this;
let beThis = arguments[0];
let args = [].slice.call(arguments ,1);
return function () {
exeFunc.apply(beThis,args);
}
}
来份数据测试一下:
let other = {
name: 'other'
}
let obj = {
name: 'obj',
getName : function (age,height) {
console.log(this.name);
console.log('年龄' + age);
console.log('身高' + height);
}
}
obj.getName.myBind(other, 14, 200)();
测试结果正常。打印的是other
还挺简单的是吧!但考点通常不止如此。接着看:
function Person() {
this.name = 'person';
this.getName = function (age, height) {
console.log(this.name);
console.log('age:' + age, 'height:' + height);
}
}
这个时候:
let PersonMyBind = Person.myBind(window);
let per3 = new PersonMyBind();
per3.getName();
思考一下会打印person吗?
答案:实际上per3是一个空对象。
new函数特殊情况-this
那么为什么会出现这样的错误。这就牵扯到关于new的知识:
如果不太明白的可便宜看下这篇文章
这是一段关于new的模拟代码
function New (constructFunc) {
// 生命中间对象,最后作为返回的实例,相当于let obj = New(Obj); => obj = res
var res = {};
if(constructFunc.prototype !== null) {
// 将实例的原型指向构造函数的原型
res.__proto__ = constructFunc.prototype;
}
// 重点重点 ret为该构造函数执行的结果,将构造函数的this改为执行res
var ret = constructFunc.apply(res, Array.prototype.slice.call(arguments, 1));
// 如果构造函数有返回值,则直接返回
if((typeof rest === "object" || typeof ret === "function") && ret !== null) {
return ret;
}
// 否则返回该实例
return res;
}
其中,下面一行代码就是导致我们写的bind不能如愿以偿将name、getName属性创建到对象的致命原因,且听我细细道来:
var ret = constructFunc.apply(res, Array.prototype.slice.call(arguments, 1));
当我们执行Person.myBind()的时候,我的得到的返回结果是一个函数:function () {exeFunc.apply(beThis,args);},来个图明显一点。
那么当这一行代码执行时:
var ret = constructFunc.apply(res, Array.prototype.slice.call(arguments, 1));
来张图来看清new Person与new PersonMyBind()的区别:
在知道产生这种现象的原因之后我们该如何解决?其实非常简单,如果是new的情况:
let resultFunc = function () {
exeFn.apply(this, args) // 这里传入的是this对象,对应着new过程中的res
}
所以这个时候问题就是该如何区分new Person()和Person()!答案还是在new的实现原理中找答案,我们可以找到上面new的模拟代码中的这一行:
// 将实例的原型指向构造函数的原型
res.__proto__ = constructFunc.prototype;
也就是说在执行
let resultFunc = function () {
// 此时的this__proto__等于Person.prototype
exeFn.apply(this, args)
}
此时的this.__proto__等于Person.prototype,利用这一特性就ok了。
升级我们的myBind
Function.prototype.myBind = function () {
if(typeof this !== 'function') {
throw new Error('调用者必须为function类型');
}
let exeFn = this; // this 为待执行函数
let currentThis = arguments[0]; // 待指定的this
let args = [].slice.call(arguments,1); // 剩余的都作为参数传递
let resultFunc = function () {
// 区分new调用与普通调用
exeFn.apply(this.__proto__=== resultFunc.prototype ? this : currentThis, args)
}
return resultFunc;
}
new函数特殊情况-父原型
到这里还没结束,我们还要解决Person加入有父原型的情况,在知道上面的知识点后解决这个也非常easy
再升级一版:
Function.prototype.myBind = function () {
if(typeof this !== 'function') {
throw new Error('调用者必须为function类型');
}
let exeFn = this; // this 为待执行函数
let currentThis = arguments[0]; // 待指定的this
let args = [].slice.call(arguments,1); // 剩余的都作为参数传递
let resultFunc = function () {
// 区分new调用跟普通调用
exeFn.apply(this.__proto__=== resultFunc.prototype ? this : currentThis, args)
}
// 维持原来函数的父原型
if (this.prototype) {
resultFunc.prototype = this.prototype;
}
return resultFunc;
}
打完收工