优雅手撕bind函数(面试官常问)



优雅手撕bind函数


前言:
  • 为何面试官总爱让实现一个bind函数?
  • 他想从bind中知道些什么?
  • 一个小小的bind里面内有玄机?
    今天来刨析一下实现一个bind要懂多少相关知识点,也方便咱们将零碎的知识点串联起来。

👍 看完有用的同窗记得点个赞再走,您的鼓励-我莫大的动力

看完能学到什么javascript

  • 实现bind
  • new原理

本文章的叙事步骤java

  • bind函数做用
  • 模拟bind的要点
  • 实现思路
  • new函数特殊状况(this&父原型)

-------------人工分割线-------------面试

bind函数的做用

返回一个可以改变this指向的函数。app

模拟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);
        }
    }

来份数据测试一下: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函数特殊状况-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;
    }

打完收工

相关文章
相关标签/搜索