优雅手撕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. 国产化之路-安装WEB服务器

    专题目录 国产化之路-统信UOS操作系统安装 国产化之路-国产操作系统安装.net core 3.1 sdk 国产化之路-安装WEB服务器 国产化之路-安装达梦DM8数据库 国产化之路-统信UOS + ...

  2. MFC 简介

    参考:https://baike.baidu.com/item/MFC/2236974 MFC (微软基础类库) 编辑 锁定 讨论999   MFC(Microsoft Foundation Clas ...

  3. OneWire应用 单总线温度传感器DS18系列

    OneWire DS18S20, DS18B20, DS1822 Temperature DS18B20 The DS18B20 digital thermometer provides 9-bit ...

  4. Ubuntu通过iptables防止ssh暴力破解

    步骤 安装iptables-persistent用于保存iptables规则 配置iptables规则 实时更新iptables规则以拦截IP访问 安装iptables-persistent sudo ...

  5. ORA-00017: session requested to set trace event 请求会话以设置跟踪事件

    ORA-00017: session requested to set trace event   ORA-00017: 请求会话以设置跟踪事件 Cause:       The current se ...

  6. laravel 500错误的一种可能

    报这个错误,我一度认为,再加上,百度,大家都说是配置有问题,经过我不断地问我学长,结果就是一个小错误,简直太丢人了. 居然是少了一个括号的问题,自闭了

  7. xshell的下载与使用

    昨天刚刚立下每天一篇原创的宏图,今天就停电,到11:05才来电,没办法,学习也学不了了,就只有发一下学过的东西,才能维持得了立下的flag的那个样子,而且,老铁们,今天就不写什么原创博客了,今天转载, ...

  8. MeteoInfoLab脚本示例:AIRS Grid HDF数据

    AIRS Grid HDF数据是HDF4 EOS格式,数据地理坐标信息可以被MeteoInfo自动识别,脚本程序更为简单.需要注意的是读取数据时Y轴是反向的(卫星数据通常如此).脚本程序: #Add ...

  9. python操作excel xlwt (转)

    Python中xlrd和xlwt模块使用方法   阅读目录 安装 xlrd模块使用 xlwt模块 xlrd模块实现对excel文件内容读取,xlwt模块实现对excel文件的写入. 回到顶部 安装 ? ...

  10. Python字典的初识、增删改查及嵌套

    为什么要有字典? 列表可以存储大量的数据,但数据间的关联型不强 列表的查询速度相对慢 dict:字典,容器型数据类型 数据类型的分类: 可变与不可变 可变(不可哈希)的数据类型: 列表list,字典d ...