本文的翻译于\<\>, 特别感谢!! ps: 本文会用简洁, 易懂的语言描述原书的所有要点. 如果能看懂这文章,将节省许多阅读时间. 如果看不懂,务必给我留言, 我回去修改.
技巧43:用类型安全的方法打猴子补丁
js最出名的一个特性就是:所有的对象,类都是开放的,也就是你可以给其添加任意属性。利用这一特性给window和document添加属性,这也就意味着添加了全局变量:
window.monkey = 'Tamarin'; document.monkey = 'Howler';
或者利用这一特性给DOM元素添加数据:
const el = document.getElementById('colobus'); el.home = 'tree';
这种风格在jQuery代码中十分常见。
你甚至可以在原型上添加属性:
> RegExp.prototype.monkey = 'Capuchin' "Capuchin" > /123/.monkey "Capuchin"
这种方法通常不是好的代码设计,给window和document添加属性,这也就意味着添加了全局变量。这有可能在不经意间,给两个毫无关系的程序添加依赖。也就意味着,你调用一个函数你必须考虑会引起的副作用。
同时,当引入ts后,这种设计也会有问题:ts知道window,document的类型,但是不知道你新增属性的类型:
document.monkey = 'Tamarin'; // ~~~~~~ Property 'monkey' does not exist on type 'Document'
最直接的修复这个错误的方式:any断言
(document as any).monkey = 'Tamarin'; // OK
这能通过ts的类型检查,与此同时你失去了类型安全和语言服务:
(document as any).monky = 'Tamarin'; // Also OK, misspelled (document as any).monkey = /Tamarin/; // Also OK, wrong type
当你必须给document或者说是DOM元素添加data,有一个更好的办法:类型增强,这也是interface最特别的能力:
interface Document { /** Genus or species of monkey patch */ monkey: string; } document.monkey = 'Tamarin'; // OK
这和any断言相比有几处改进:
获得了类型安全:类型检查能检查出你的拼写错误,和类型错误。
你可以将documentation加到property
获得属性的自动补全
能获得猴子补丁的确切记录 在模块上下文中(import,export),你需要添加declare global让其生效:
export {}; declare global { interface Document { /** Genus or species of monkey patch */ monkey: string; } } document.monkey = 'Tamarin'; // OK
这种类型增强主要的问题在于范围:
类型增强用于全局,你无法在部分代码中隐藏。
因为类型增强发生在你应用运行中,只有类型增强这段代码运行后,才能获得改全局变量,再次之前没有。这也就意味着,当你想获得这个全局变量,你很难确定其是否存在。
更好的方法,用精准的断言:
interface MonkeyDocument extends Document { /** Genus or species of monkey patch */ monkey: string; } (document as MonkeyDocument).monkey = 'Macaque';
这种方法能类型安全的对document赋值,同时没有对Document的类型进行全局修改。当你引用这个猴子补丁的时候你需要写断言。
原文:https://juejin.cn/post/7094815719069581325