从一道面试题,到“我可能看了假源码[2]
来源:互联网 发布:淘宝宝贝详情图模板 编辑:程序博客网 时间:2024/05/17 15:19
转载自:http://www.jianshu.com/p/3d4e8e2592a8
上一篇从一道面试题,到“我可能看了假源码”中,由浅入深介绍了关于一篇经典面试题的解法。
最后在皆大欢喜的结尾中,突生变化,悬念又起。这一篇,就是为了解开这个悬念。
如果你还没有看过前传,可以参看前情回顾:
回顾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 // 输出0test.hasOwnProperty('length') // 输出trueObject.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]
- 从一道面试题,到“我可能看了假源码”
- 从一道面试题看指针与数组的区别
- 从一道面试题看指针与数组的区别
- 从一道面试题看指针与数组的区别
- 从一道面试题看指针与数组的区别
- 从一道面试题看C++隐式类型转换
- 从一道面试题看C++隐式类型转换
- 从一道面试题看C++隐式类型转换
- 从一道基础面试题看:数组形参
- 从一道面试题看深拷贝浅拷贝问题
- 从一道面试题看ES6箭头函数
- 从一道面试题说去 2
- Python之美[从菜鸟到高手]--读"一道面试题看 HashMap 的存储方式"的联想
- 一道让我纠结了几天的面试题
- 从一道面试题看深拷贝、浅拷贝构造函数问题 (经典)
- (摘抄笔记)从一道面试题看struct中的内存对齐
- 从一道MS试题看自己
- 安装CocoaPods过程和遇到各种坑
- 【Python】学习笔记——-12、正则表达式
- 使用 IntelliJ Debug Android 源码
- FreeRTOS之事件标志组及实现FreeRTOS看门狗
- 北大 C++ 1.4 引用
- 从一道面试题,到“我可能看了假源码[2]
- js--this
- PAT甲级1002. A+B for Polynomials (25)
- 编程规范入门篇 空格和tab的区别
- Linux的SOCKET编程详解
- 底部弹出密码输入框
- 面试笔记--二维数组的查找
- vue.js学习笔记之prototype
- 往夜 -- 原来世界如此性感