- 为何面试官总爱让实现一个bind函数?
- 他想从bind中知道些什么?
- 一个小小的bind里面内有玄机?
今天来刨析一下实现一个bind要懂多少相关知识点,也方便咱们将零碎的知识点串联起来。
👍 看完有用的同窗记得点个赞再走,您的鼓励-我莫大的动力
看完能学到什么javascript
- 实现bind
- new原理
本文章的叙事步骤java
- bind函数做用
- 模拟bind的要点
- 实现思路
- new函数特殊状况(this&父原型)
-------------人工分割线-------------面试
返回一个可以改变this指向的函数。app
建立一个待返回的函数,函数内部利用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); } }
来份数据测试一下:this
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.net
还挺简单的是吧!但考点一般不止如此。接着看:prototype
function Person() { this.name = 'person'; this.getName = function (age, height) { console.log(this.name); console.log('age:' + age, 'height:' + height); } }
这个时候:rest
let PersonMyBind = Person.myBind(window); let per3 = new PersonMyBind(); per3.getName();
思考一下会打印person吗?
答案:实际上per3是一个空对象。
那么为何会出现这样的错误。这就牵扯到关于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; }
到这里还没结束,咱们还要解决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; }
打完收工