内容简介:系列文章:昨天阅读mem 的源码之后,提出了当参数为 RegExp 类型时,运行结果会存在问题。今天又仔细思考了一下,对于 Symbol 类型,也会存在同样的问题。通过今天阅读的 npm 模块是mimic-fn,mimic 的意思是模仿,它通过对原函数的复制从而模仿原函数的行为,可以在不修改原函数的前提下,扩充函数的功能,当前版本为 1.2.0,周下载量约为 421 万。
系列文章:
昨天阅读mem 的源码之后,提出了当参数为 RegExp 类型时,运行结果会存在问题。今天又仔细思考了一下,对于 Symbol 类型,也会存在同样的问题。通过 mem - Issue #20 和作者 Sindre Sorhus 讨论之后,已经得出了初步的解决方法,相信这个 bug 会在最近被 fix :blush:
一句话介绍
今天阅读的 npm 模块是mimic-fn,mimic 的意思是模仿,它通过对原函数的复制从而模仿原函数的行为,可以在不修改原函数的前提下,扩充函数的功能,当前版本为 1.2.0,周下载量约为 421 万。
用法
const mimicFn = require('mimic-fn'); function foo() {} foo.date = '2018-08-27'; function wrapper() { return foo() {}; } console.log(wrapper.name); //=> 'wrapper' // 此处复制 foo 函数后, // foo 拥有的功能,wrapper 均有 mimicFn(wrapper, foo); console.log(wrapper.name); //=> 'foo' console.log(wrapper.date); //=> '2018-08-27' 复制代码
源码学习
实现mimic-fn 功能的难点在于如何获得原函数所有的属性并将其赋值给新函数。其实源码非常非常非常(重要的事情说三遍)短:
// 源码 3-1 module.exports = (to, from) => { for (const prop of Object.getOwnPropertyNames(from).concat(Object.getOwnPropertySymbols(from))) { Object.defineProperty(to, prop, Object.getOwnPropertyDescriptor(from, prop)); } return to; }; 复制代码
虽然源码只有四五行,但是涉及 JavaScript 中非常核心基础的内容 —— property descriptor
(属性描述符),还是值得好好研究一下的。
属性描述符介绍
形如 const obj = {x: 1}
是最简单的对象, x
是 obj
的一个属性。ES5 带给了我们对属性 x
进行定制化的能力。通过 Object.defineProperty(obj, 'x', descriptor)
可以实现一些有意思的效果:
不能被修改的属性
const obj = {}; // 定于不能被修改的 x 属性 Object.defineProperty(obj, 'x', { value: 1, writable: false, }); console.log(obj.x); // => 1 obj.x = 2; console.log(obj.x); // => 1 复制代码
不能被删除的属性
const obj = {}; // 定义不能被删除的 y 属性 Object.defineProperty(obj, 'y', { value: 1, configurable: false, }); console.log(obj.y); // => 1 console.log(delete obj.y); // => false console.log(obj.y); // => 1 复制代码
不能被遍历的属性
const obj = {}; // 定义不能被遍历的 z 属性 Object.defineProperty(obj, 'z', { value: 1, enumerable: false, }); console.log(obj, obj.z); // => {}, 1 for (const key in obj) { console.log(key, obj[key]); } // => 没有输出 复制代码
输入与输出不同的属性
const obj = {}; // 定义输入与输出不同的 u 属性 Object.defineProperty(obj, 'u', { get: function() { return this._u * 2; }, set: function(value) { this._u = value; }, }); obj.u = 1; console.log(obj.u); // => 2 复制代码
从上面的例子中可以了解到通过属性描述符的 value | writable | configurable | enumerable | set | get 字段可以实现神奇的效果,相信它们的含义大家也能猜出来,下面的介绍摘自 MDN - Object.defineProperty() :
undefined undefined
需要注意的是,属性描述符分为两类:
- 数据描述符(data descriptor):可设置 configurable | enumerable |value | writable。
- 存储描述符(access descriptor):可设置 configurable | enumerable | get | set。
可以看出,一个属性不可能同时设置 value 和 get 或者同时设置 writable 和 set 等。
对于我们最常用的对象自变量 const obj = {x: 1}
的属性 x,其属性描述符的值为:
{ value: 1, writable: true, enumerable: true, configurable: true, } 复制代码
函数的属性描述符
众所周知在 JavaScript 中一切皆对象,所以函数也有自己的属性描述符,通过 Object.getOwnPropertyDescriptors()
来看看对于一个已定义的函数,其具有哪些属性:
function foo(x) { console.log('foo..'); } console.log(Object.getOwnPropertyDescriptors(foo)); { length: { value: 1, writable: false, enumerable: false, configurable: true }, name: { value: 'foo', writable: false, enumerable: false, configurable: true }, arguments: { value: null, writable: false, enumerable: false, configurable: false }, caller: { value: null, writable: false, enumerable: false, configurable: false }, prototype: { value: foo {}, writable: true, enumerable: false, configurable: false } } 复制代码
从上面的代码中可以看出函数一共有 5 个属性,分别为:
-
length:函数定义的参数个数。
-
name:函数名,注意其
writable
为 false,所以直接改变函数名foo.name = bar
是不起作用的。 -
arguments:函数 执行时 的参数,是一个类数组,在 'use strict' 严格模式下无法使用。对于 ES6+,可以通过Rest Parameters 实现同样的功能,而且在严格模式下仍能使用。
function foo(x) { console.log('foo..', arguments); } function bar(...rest) { console.log('bar..', rest) } foo(); bar(); // => foo.. [Arguments] // => bar.. [] foo(1); bar(1); // => foo.. [Arguments] { '0': 1 } // => bar.. [ 1 ] foo(1, 2); bar(1, 2); // => foo.. [Arguments] { '0': 1, '1': 2 } // => bar.. [ 1, 2 ] 复制代码
-
caller:指向函数的调用者,在 'use strict' 严格模式下无法使用:
function foo() { console.log(foo.caller) } function bar() { foo() } bar(); // => [Function: bar] 复制代码
-
prototype:指向函数的原型,与 JavaScript 中的原型链相关,这里不做展开。
属性描述符操作
知道了属性描述符的字段和作用,那么当然要尝试对其进行修改,在 JavaScript 中有四种方法可以对其进行修改,分别为:
- Object.defineProperty(obj, prop, descriptor):当属性的 configurable 为 true 时,可以对已有的属性的描述符进行变更。
- Object.preventExtensions(obj):阻止 obj 被添加新的属性。
- Object.seal(obj):阻止 obj 被添加新的属性或者删除已有的属性。
- Object.freeze(obj):阻止 obj 被添加新的属性、删除已有的属性或者更新已有的属性。
通过这些函数可以实现一些有意思的功能,例如阻止数组新添或删除元素:
const arr = [ 1 ]; arr.push(2); // => TypeError: Cannot add property 1, object is not extensible arr.pop(); // => TypeError: Cannot delete property '0' of [object Array] 复制代码
回到源码
现在再来看mimic-fn 的源码就十分简单了,其实它只做了两件事情:
- 读取原函数的属性。
- 将原函数的属性设置到新函数上。
// 源码 3-1 module.exports = (to, from) => { for (const prop of Object.getOwnPropertyNames(from).concat(Object.getOwnPropertySymbols(from))) { Object.defineProperty(to, prop, Object.getOwnPropertyDescriptor(from, prop)); } return to; }; 复制代码
这段代码只有一个地方需要解释一下:当对象的属性为 Symbol 类型时, getOwnPropertyNames
无法获得,需要再通过 getOwnPropertySymbols
获得之后访问:
const obj= { x: 1, [Symbol('elvin')]: 2, }; console.log(Object.getOwnPropertyNames(obj)); // => [ 'x' ] console.log(Object.getOwnPropertySymbols(obj)); // => [ Symbol(elvin) ] console.log(Reflect.ownKeys(obj)); // => [ 'x', Symbol(elvin) ] 复制代码
可以看到 Object.getOwnPropertyNames()
只能获得 x,而 Object.getOwnPropertySymbols(obj)
只能获得 Symbol('elvin'),两者一起使用的话则可以获得对象所有的属性。
另外对于 Node.js >= 6.0,可以通过 Reflect.ownKeys(obj)
的方式来实现同样的功能,而且代码更加的简洁,所以我尝试做了如下的更改:
module.exports = (to, from) => { for (const prop of Reflect.ownKeys(from)) { Object.defineProperty(to, prop, Object.getOwnPropertyDescriptor(from, prop)); } return to; }; 复制代码
上述代码目前已被合进最新的 master 分支,详情可查看 mimic-fn PR#9 。
写在最后
今天所写的内容在平时工作中其实几乎不会用到,所以假如大家要问了解这个有什么用的话?
了解这个没用,看完忘记了也没问题,开心就好,权当对 JavaScript 内部机制多了一些了解。
以上所述就是小编给大家介绍的《每天阅读一个 npm 模块(3)- mimic-fn》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:- Nginx源码阅读笔记-事件处理模块
- 每天阅读一个 npm 模块(2)- mem
- 每天阅读一个 npm 模块(1)- username
- 每天阅读一个 npm 模块(6)- pify
- # 每天阅读一个 npm 模块(7)- delegates
- 《WebKit技术内幕》阅读摘要 —— WebKit 架构和模块
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
CSS禅意花园
[美] Dave Shea、Molly E. Holzschlag / 陈黎夫、山崺颋 / 人民邮电出版社 / 2007-6 / 49.00元
这本书的作者是世界著名的网站设计师,书中的范例来自网站设计领域最著名的网站——CSS Zen Garden(CSS禅意花园)。全书分为两个主要部分。第1章为第一部分,讨论网站“CSS禅意花同”及其最基本的主题,包含正确的标记结构和灵活性规划等。第二部分包括6章,占据了书中的大部分篇幅。 每章剖析“CSS禅意花园”收录的6件设计作品,这些作品围绕一个主要的设计概念展开,如文字的使用等。通过探索......一起来看看 《CSS禅意花园》 这本书的介绍吧!