最近在更新一个在Typescript中旅行的专栏,这是第八篇,如果对你有帮助那就帮我点个赞。
- 1、「Typescript之旅」:小红的Typescript之旅【入职】
- 2、「Typescript之旅」:小红学Typescript: 带你轻松掌握枚举🧐
- 3、一行代码引发的离奇问题,众多大佬纷纷参与点评,最终Typescript之父出手解决
- 4、「Typescript之旅」:学会satisfies操作符,让你的Typescript功力倍增!
- 5、「Typescript之旅」:看一遍就理解Ts中的class
- 6、一文盘点Typescript中23个内置类型工具! (建议收藏)
- 7、「Typescript之旅」: 彻底理解Typescript中的逆变和协变
今日更文《探索Typescript设计哲学中的主观性》。
介绍
TypeScript 是具有类型语法的 JavaScript。它基于Javascript,并且是JS的超集,但是作为一个强类型编程语言,在设计上肯定与弱类型语言在特性上会有些不同,但是ts还是基于js的,在设计上还是要侧重于js使用者们的使用习惯与ES标准、DOM标准等规范。但是不仅是这些,还包含一些Typescript的设计们的一些的“主观性”,本文会从一个小例子来引出ts的设计目标,因为我平时看ts的官网或者其设计者写的文章的时候,并没有什么很大的触动性,但是如果有例子作为佐证,心里就会想原来是这样,对为啥这样设计也会有一定的了解。
js查找元素的方式
查找元素有两个大的方式
- query系列
- getElement系列
对于第一种来说有querySelector、querySelectorAll这些方法,对于第二种有getElementById、getElementsByTagName这些。
查找方式的不同
假如html结构中有一系列的li元素
<html>
<ul>
<li>111</li>
<li>222</li>
<li>333</li>
</ul>
</html>
想要查找li元素可以用query的方式
const ul1 = document.querySelector('ul')
const li1 = ul.querySelectorAll('li')
也可以用getElement的方式
const ul2 = document.getElementsByTagName('ul')[0]
const li2 = document.getElementsByTagName('li')
我们把这两种查找元素的方式返回的结果打印出来
查找ul返回的是一样的,我们只看查找li打印的结果
querySelector方法返回的结果是 NodeList集合, getElementsByTagName方法返回的结果是HTMLCollection集合。
这两种方式有啥区别?
- 在性能上,querySelector的查找性能要低于getElement的查找性能。
- querySelector返回的结果是静态的,getElement返回的结果动态的。
- 前者会指选择的元素不会随着文档操作改变,而后者会根据文档的改变自动去更新结果以反映这些变化。
第二点有点不太好理解,我们在ul中插入几条数据改变下结构看看
const ul1 = document.querySelector('ul')
const li1 = ul1.querySelectorAll('li')
for(let i =0; i<3; i++){
ul1.appendChild(document.createElement('li'))
}
console.log(li1.length); // 打印 3
li1的集合长度是3。结果没有因为我们的改变而自动更新
const ul2 = document.getElementsByTagName('ul')[0]
const li2 = document.getElementsByTagName('li')
for(let i =0; i<3; i++){
ul2.appendChild(document.createElement('li'))
}
console.log(li2.length); // 打印 6
li1的集合长度是6。结果根据我们的改变自动更新了。
在ts中的表现
这两种方式在ts中的表现也是不一样的。
getElementById获取到的是HTMLElement, querySelector 获取到的是Element。但是我们用ts肯定想获取到更精确的类型,这样才能有最好的体验。这二者都不太精确。因为#app是img标签,我们想获取的是HTMLImageElement这个精确的类型。
在querySelector方式获取元素时,ts给我们提供了一个泛型坑位
const app1 = document.querySelector<HTMLImageElement>('#app')
通过传入的泛型坑位获取到了精确的提示。
但是getElementById却不行
所以只能用as操作符把将app断言为HTMLImageElement类型。
const app = document.getElementById('app') as HTMLImageElement
// app: HTMLImageElement | null
为了找到原因,按住ctrl/command 点击到ts的lib.dom.d.ts类型声明文件中
可以看到查找元素的getElement系列只有getElementsByTagName有泛型坑位来精确其返回类型。
同样的方式查看querySelector
querySelect系列都有泛型坑位来精确返回值类型。
看到上面并没有对我们日常写业务代码有什么影响,但是有这么一种情况,当我们获取到元素后一般都会想对该元素进行一些操作,比如操作style样式
querySelector返回的类型是Element,上面没有style,合理,因为在style这个DOM 属性是在 HTMLElement上定义的, Element是HTMLElement的父级,并没有这个属性,但是在DOM标准中规定了querySelector和getElementById的返回元素类型都是Element, 如果没有则为null, 是ts自己改了这个标准。
这是因为ts的设计者们根据过往的经验来看,getElementById获取到的元素大多都是HTMLElement,所以将返回类型定义为了HTMLElement。但是DOM标准确实是规定了返回的是Element。在ts的wiki中写道:
所以,确实没错,这就是一些主观性规则中的其中一个。
总结
在使用ts的过程中,不管是高手还是菜鸟都会遇到很多很多奇奇怪怪的问题。有很多和设计有关。有时候不仅要掌握基础的语法特性,也要了解下设计哲学,这样在看到有关ts的问题才会更加游刃有余。