优雅手撕bind函数




前言:

  • 为什么面试官总爱让实现一个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;
}

打完收工

优雅手撕bind函数(面试官常问)的更多相关文章

  1. Android相关面试题---面试官常问问题

    版权声明:本文为寻梦-finddreams原创文章,请关注: http://blog.csdn.net/finddreams/article/details/44513579 一般的面试流程是笔试完就 ...

  2. 面试官常问的Nginx的那几个问题?

    什么是Nginx? Nginx是一个高性能的HTTP和反向代理服务器,也是一个IMAP/POP3/SMTP服务器 Nginx是一款轻量级的Web服务器/反向代理服务器及电子邮件(IMAP/POP3)代 ...

  3. 面试官常问的Nginx的几个问题

    1.什么是Nginx? Nginx是一个高性能的HTTP和反向代理服务器,也是一个IMAP/POP3/SMTP服务器 Nginx是一款轻量级的Web服务器/反向代理服务器及电子邮件(IMAP/POP3 ...

  4. 面试官常问的20道Java题目(附答案)-来自Java1234

    1. 以下代码的输出结果是(A) int i =3; i = i++; System.out.println(i); A .3  B.4  C.5 a=b++是先将b值赋值给a后b再自增. 2. Ma ...

  5. 面试官常问的10个Linux问题

    1.如何暂停一个正在运行的进程,把其放在后台(不运行)? 为了停止正在运行的进程,让其再后台运行,我们可以使用组合键Ctrl+Z. 2.什么是安装Linux所需的最小分区数量,以及如何查看系统启动信息 ...

  6. JVM工作原理和特点(一些二逼的逼神面试官会问的问题)

    作为一种阅读的方式了解下jvm的工作原理 ps:(一些二逼的逼神面试官会问的问题) JVM工作原理和特点主要是指操作系统装入JVM是通过jdk中Java.exe来完毕,通过以下4步来完毕JVM环境. ...

  7. 【Nginx】面试官竟然问我Nginx如何生成缩略图,还好我看了这篇文章!!

    写在前面 今天想写一篇使用Nginx如何生成缩略图的文章,想了半天题目也没想好,这个题目还是一名读者帮我起的.起因就是这位读者最近出去面试,面试官正好问了一个Nginx如何生成缩略图的问题.还别说,就 ...

  8. 面试官再问我如何保证 RocketMQ 不丢失消息,这回我笑了!

    最近看了 @JavaGuide 发布的一篇『面试官问我如何保证Kafka不丢失消息?我哭了!』,这篇文章承接这个主题,来聊聊如何保证 RocketMQ 不丢失消息. 0x00. 消息的发送流程 一条消 ...

  9. 手写bind函数

    实现bind函数 参考MDN提供的Polyfill方案 Function.prototype.myBind = function(context){ //这里对调用者做一个判断,如果不是函数类型,直接 ...

随机推荐

  1. 提交 linux kernel 补丁流程备忘录

    1. 订阅 linux 邮件列表 linux 邮件列表 Kernel Mailing Lists 是所有 linux kernel 开源贡献者协同工作的平台,可以通过向 VGER.KERNEL.ORG ...

  2. 基础篇:JAVA内部类的使用介绍

    目录 1 四种内部类 2 内部类的使用场景和优点 3 成员内部类 4 成员内部类的访问范围详解 5 静态内部类 6 局部内部类 7 匿名内部类 欢迎指正文中错误 关注公众号,一起交流 参考文章 1 四 ...

  3. Spring学习(八)--Spring的AOP

    自工作以后身不由己,加班无数,996.995不可控制,高高立起的flag无法完成,无奈,随波逐流,尽力而已! 1.advice通知 advice主要描述Spring AOP 围绕奥方法调用而注入的切面 ...

  4. Metasploit之令牌窃取

    令牌简介及原理 令牌(Token) 就是系统的临时密钥,相当于账户名和密码,用来决定是否允) 许这次请求和判断这次请求是属于哪一个用户的.它允许你在不提供密码或其他凭证的前提下,访问网络和系统资源.这 ...

  5. 三级菜单打怪升级,young -> plus -> pro

    young -> simple 三级菜单超简单,每层都是小循环 小可爱,不是if就是for,真可爱 def menu(message): print('按q返回上一层') print('按e退出 ...

  6. Android Handler MessageQueue Looper 消息机制原理

    提到Android里的消息机制,便会提到Message.Handler.Looper.MessageQueue这四个类,我先简单介绍以下这4个类 之间的爱恨情仇. Message 消息的封装类,里边存 ...

  7. Python字符编码和二进制不得不说的故事

    二进制 核心思想: 冯诺依曼 + 图灵机 电如何表示状态,才能稳定? 计算机开始设计的时候并不是考虑简单,而是考虑能自动完成任务与结果的可靠性, 简单始终是建立再稳定.可靠基础上 经过尝试10进制,但 ...

  8. linux 漏洞列表

    #CVE #Description #Kernels CVE-2017-1000367 [Sudo](Sudo 1.8.6p7 - 1.8.20) CVE-2017-7494 [Samba Remot ...

  9. 常见的Mysql十款高可用方案

    简介 我们在考虑MySQL数据库的高可用架构时,主要考虑如下几方面: 如果数据库发生了宕机或者意外中断等故障,能尽快恢复数据库的可用性,尽可能的减少停机时间,保证业务不会因为数据库的故障而中断. 用作 ...

  10. CSS语法规范与代码风格

    CSS语法规范与代码风格 1. 语法规范 CSS规则又两个主要的部分构成:选择器+一条或多条声明. 选择器:用于指定CSS样式的HTML标签,花括号内的是设置的具体样式 属性与属性值以键值对的形式出现 ...