上一篇从一道面试题,到“我可能看了假源码”中,由浅入深介绍了关于一篇经典面试题的解法。
最后在皆大欢喜的结尾中,突生变化,悬念又起。这一篇,就是为了解开这个悬念。

如果你还没有看过前传,可以参看前情回顾:

回顾1. 题目是模拟实现ES5中原生bind函数;
回顾2. 我们通过4种递进实现达到了完美状态;
回顾3. 可是ES5-shim中的实现,又让我们大跌眼镜...

ES5-shim的悬念

ES5-shim实现方式源码贴在了最后,我们看看他做了什么奇怪的事情:
1)从结果上看,返回了bound函数。
2)bound函数是这样子声明的:

bound = Function('binder', 'return function (' + boundArgs.join(',') + '){ return binder.apply(this, arguments); }')(binder);

3)bound使用了系统自己的构造函数Function来声明,第一个参数是binder,函数体内又binder.apply(this, arguments)。
我们知道这种动态创建函数的方式,类似eval。最好不要使用它,因为用它定义函数比用传统方式要慢得多。
4)那么ES5-shim抽风了吗?

追根问底

答案肯定是没抽风,他这样做是有理由的。

神秘的函数的length属性

你可能不知道,每个函数都有length属性。对,就像数组和字符串那样。函数的length属性,用于表示函数的形参个数。更重要的是函数的length属性值是不可重写的。我写了个测试代码来证明:

function test (){}
test.length  // 输出0
test.hasOwnProperty('length')  // 输出true
Object.getOwnPropertyDescriptor('test', 'length')
// 输出:
// configurable: false,
// enumerable: false,
// value: 4,
// writable: false

拨云见日

说到这里,那就好解释了。
ES5-shim是为了最大限度的进行兼容,包括对返回函数length属性的还原。如果按照我们之前实现的那种方式,length值始终为零。
所以:既然不能修改length的属性值,那么在初始化时赋值总可以吧!
于是我们可通过eval和new Function的方式动态定义函数来。
同时,很有意思的是,源码里有这样的注释:

// XXX Build a dynamic function with desired amount of arguments is the only
// way to set the length property of a function.
// In environments where Content Security Policies enabled (Chrome extensions,
// for ex.) all use of eval or Function costructor throws an exception.
// However in all of these environments Function.prototype.bind exists
// and so this code will never be executed.

他解释了为什么要使用动态函数,就如同我们上边所讲的那样,是为了保证length属性的合理值。但是在一些浏览器中出于安全考虑,使用eval或者Function构造器都会被抛出异常。但是,巧合也就是这些浏览器基本上都实现了bind函数,这些异常又不会被触发。

So, What a coincidence!

叹为观止

我们明白了这些,再看他的进一步实现:

if (!isCallable(target)) {throw new TypeError('Function.prototype.bind called on incompatible ' + target);
}

这是为了保证调用的正确性,他使用了isCallable做判断,isCallable很好实现:

isCallable = function isCallable(value) { if (typeof value !== 'function') { return false; }
}

重设绑定函数的length属性:

var boundLength = max(0, target.length - args.length);

构造函数调用情况,在binder中也有效兼容。如果你不明白什么是构造函数调用情况,可以参考上一篇。

if (this instanceof bound) { ... // 构造函数调用情况
} else {... // 正常方式调用
}if (target.prototype) {Empty.prototype = target.prototype;bound.prototype = new Empty();// Clean up dangling references.Empty.prototype = null;
}

无穷无尽

当然,ES5-shim里还归纳了几项todo...

// TODO
// 18. Set the [[Extensible]] internal property of F to true.
// 19. Let thrower be the [[ThrowTypeError]] function Object (13.2.3).
// 20. Call the [[DefineOwnProperty]] internal method of F with
//   arguments "caller", PropertyDescriptor {[[Get]]: thrower, [[Set]]:
//   thrower, [[Enumerable]]: false, [[Configurable]]: false}, and
//   false.
// 21. Call the [[DefineOwnProperty]] internal method of F with
//   arguments "arguments", PropertyDescriptor {[[Get]]: thrower,
//   [[Set]]: thrower, [[Enumerable]]: false, [[Configurable]]: false},
//   and false.
// 22. Return F.

比较简单,我就不再翻译了。

源码回放

bind: function bind(that) {var target = this;if (!isCallable(target)) {throw new TypeError('Function.prototype.bind called on incompatible ' + target);}var args = array_slice.call(arguments, 1);var bound;var binder = function () {if (this instanceof bound) {var result = target.apply(this,array_concat.call(args, array_slice.call(arguments)));if ($Object(result) === result) {return result;}return this;} else {return target.apply(that,array_concat.call(args, array_slice.call(arguments)));}};var boundLength = max(0, target.length - args.length);var boundArgs = [];for (var i = 0; i < boundLength; i++) {array_push.call(boundArgs, '$' + i);}bound = Function('binder', 'return function (' + boundArgs.join(',') + '){ return binder.apply(this, arguments); }')(binder);if (target.prototype) {Empty.prototype = target.prototype;bound.prototype = new Empty();Empty.prototype = null;}return bound;
}

总结

通过学习ES5-shim的源码实现bind方法,结合前一篇,希望读者能对bind和JS包括闭包,原型原型链,this等一系列知识点能有更深刻的理解。
同时在程序设计上,尤其是逻辑的严密性上,有所积累。

PS:百度知识搜索部大前端继续招兵买马,有意向者火速联系。。。

从一道面试题,到“我可能看了假源码[2]相关推荐

  1. 字节跳动面试官问我看过哪些源码,然后就没有然后了

    最近,我的一位朋友在找工作,已经拿到了美团.快手等公司的Offer,准备选择其中一家入职了. 后来他又接到了字节跳动的电话,通知他去参加三面.从二面到三面之间隔了挺久的,他以为都没戏了,结果就收到了通 ...

  2. 面试有没有看过spring源码_如何看Spring源码、Java每日六道面试分享,打卡第二天...

    原标题:如何看Spring源码.Java每日六道面试分享,打卡第二天 想要深入的熟悉了解Spring源码,我觉得第一步就是要有一个能跑起来的极尽简单的框架,下面我就教大家搭建一个最简单的Spring框 ...

  3. 看完Spring源码记不住,是我脑子不太好吗?

    都说大厂面试必问源码,可很多人看完Spring源码记不住,是脑子有问题吗?当然不是!是因为你没有掌握学习源码的技巧. 看完源码的我- 我的朋友"路神"子路和"大魔王&qu ...

  4. [动态代理三部曲:下] - 从动态代理,看Retrofit的源码实现

    前言 关于动态代理的系列文章,到此便进入了最后的"一出好戏".前俩篇内容分别展开了:从源码上,了解JDK实现动态代理的原理:以及从动态代理切入,学会看class文件结构的含义. 如 ...

  5. 51ak带你看MYSQL5.7源码2:编译现有的代码

    从事DBA工作多年 MYSQL源码也是头一次接触 尝试记录下自己看MYSQL5.7源码的历程 申明:个人Python编程很溜,但是C++还停在白痴水平,源码理解方面有点弱,如发现有错误的地方,轻喷 目 ...

  6. Java多线程——带你看AQS框架源码

    AQS,全称AbstractQueuedSynchronizer,是Concurrent包锁的核心,没有AQS就没有Java的Concurrent包.它到底是个什么,我们来看看源码的第一段注解是怎么说 ...

  7. mysql 黑名单_51ak带你看MYSQL5.7源码4:实现SQL黑名单功能

    博客迁移至: 从事DBA工作多年 MYSQL源码也是头一次接触 尝试记录下自己看MYSQL5.7源码的历程 申明:个人Python编程很溜,但是C++还停在白痴水平,源码理解方面有点弱,如发现有错误的 ...

  8. 基于Android的看小说APP源码Android本科毕业设计Android小说阅读器、小说APP源码

    基于kotlin + 协程 + MVVM 模式来编写的看小说APP. 完整代码下载地址:基于Android的看小说APP源码Android本科毕业设计Android小说阅读器.小说APP源码 主要框架 ...

  9. (免费分享)基于JavaWeb的高校试题库管理系统设计与实现 毕业论文+项目源码及数据库文件(已发)

     源码获取:我的博客资源页面可以下载!!!! 项目名称 (免费分享)基于JavaWeb的高校试题库管理系统设计与实现 毕业论文+项目源码及数据库文件(已发) 系统说明 随着信息技术的不断发展,我们已经 ...

最新文章

  1. 《预训练周刊》第9期:TABBIE:表格数据的预训练表示、「视觉预训练神作」:不用图片却训出图像识别SOTA?...
  2. [转自scott]ASP.NET MVC框架 (第二部分): URL路径选择
  3. JavaScript原型链的理解
  4. struts2-ognl 访问静态方法
  5. Linux运维工程师面试题第三套
  6. Python中的序列操作
  7. 少儿编程几种语言_您使用了几种编程语言?
  8. 解题报告 『[Poetize6]IncDec Sequence(差分)』
  9. 485串口测试工具软件_探索者 STM32F407 开发板资料连载第三十一章 485 实验
  10. liunux中的 【ifconfig】 命令 查看系统IP。
  11. 洛谷P3233 [HNOI2014]世界树
  12. 远程诊断技术在汽车 OTA 刷新应用的研究
  13. android httpClient 支持HTTPS的2种处理方式
  14. C-11 Problem H: 开宝箱2
  15. 02_Python算法+数据结构笔记-冒泡排序-选择排序-插入排序-快排-二叉树
  16. Android开发十年,面试百度竟被刷!柳暗花明2020获字节跳动Offer(面试总结)
  17. AndroidStudio中Files under the “build“ folder are generated and should not be edited的解决方法
  18. LA4043 KM算法
  19. 【程序人生】1024 程序员节——闲言鹤语
  20. jQuery里面的选择属性和修改属性

热门文章

  1. mysql中如何操作字符串_mysql 字符串操作
  2. 分布式消息技术 Kafka
  3. hadoop 文本统计一个字符的个数_使用hadoop统计多个文本中每个单词数目
  4. 面试题php2018,2018php最新面试题之PHP核心技术
  5. exchange 只发送邮件 不能登录_springboot + rabbitmq发送邮件
  6. javadrawstring设置字符大小_LaTex学术写作——编辑文档格式 设置论文标题与摘要...
  7. Puppeteer + TypeScript 模拟 Ctrl + A 操作
  8. 2020-12-18 Simulink实现ESO(扩张状态观测器)
  9. SIM7600X 获取GPS信号 TCP/IP连接与PPP拨号上网 4G上网
  10. 计算机文献检索综合性实验报告,文献检索综合性实验报告模板.doc