「Typescript之旅」:探索Typescript设计哲学中的主观性

237 阅读5分钟

最近在更新一个在Typescript中旅行的专栏,这是第篇,如果对你有帮助那就帮我点个赞。

今日更文《探索Typescript设计哲学中的主观性》。

介绍

TypeScript 是具有类型语法的 JavaScript。它基于Javascript,并且是JS的超集,但是作为一个强类型编程语言,在设计上肯定与弱类型语言在特性上会有些不同,但是ts还是基于js的,在设计上还是要侧重于js使用者们的使用习惯与ES标准、DOM标准等规范。但是不仅是这些,还包含一些Typescript的设计们的一些的“主观性”,本文会从一个小例子来引出ts的设计目标,因为我平时看ts的官网或者其设计者写的文章的时候,并没有什么很大的触动性,但是如果有例子作为佐证,心里就会想原来是这样,对为啥这样设计也会有一定的了解。

js查找元素的方式

查找元素有两个大的方式

  1. query系列
  2. 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')

我们把这两种查找元素的方式返回的结果打印出来

image-20240613221458189

查找ul返回的是一样的,我们只看查找li打印的结果

image-20240613221632036

querySelector方法返回的结果是 NodeList集合, getElementsByTagName方法返回的结果是HTMLCollection集合。

这两种方式有啥区别?

  1. 在性能上,querySelector的查找性能要低于getElement的查找性能。
  2. 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中的表现也是不一样的。

image-20240613221941595

getElementById获取到的是HTMLElementquerySelector 获取到的是Element。但是我们用ts肯定想获取到更精确的类型,这样才能有最好的体验。这二者都不太精确。因为#appimg标签,我们想获取的是HTMLImageElement这个精确的类型。

在querySelector方式获取元素时,ts给我们提供了一个泛型坑位

const app1 = document.querySelector<HTMLImageElement>('#app')

通过传入的泛型坑位获取到了精确的提示。

image-20240613222454707

但是getElementById却不行

image-20240613222512277

所以只能用as操作符把将app断言为HTMLImageElement类型。

const app = document.getElementById('app') as HTMLImageElement
// app: HTMLImageElement | null

为了找到原因,按住ctrl/command 点击到tslib.dom.d.ts类型声明文件中

image-20240613222707849 image-20240613222729038

可以看到查找元素的getElement系列只有getElementsByTagName有泛型坑位来精确其返回类型。

同样的方式查看querySelector

image-20240613222809627

querySelect系列都有泛型坑位来精确返回值类型。

看到上面并没有对我们日常写业务代码有什么影响,但是有这么一种情况,当我们获取到元素后一般都会想对该元素进行一些操作,比如操作style样式

image-20240613223148637

querySelector返回的类型是Element,上面没有style,合理,因为在style这个DOM 属性是在 HTMLElement上定义的, ElementHTMLElement的父级,并没有这个属性,但是在DOM标准中规定了querySelectorgetElementById的返回元素类型都是Element, 如果没有则为null, 是ts自己改了这个标准

image-20240613223235592 image-20240613223247978

这是因为ts的设计者们根据过往的经验来看,getElementById获取到的元素大多都是HTMLElement,所以将返回类型定义为了HTMLElement。但是DOM标准确实是规定了返回的是Element。在ts的wiki中写道:

image-20240613223347327

所以,确实没错,这就是一些主观性规则中的其中一个。

总结

在使用ts的过程中,不管是高手还是菜鸟都会遇到很多很多奇奇怪怪的问题。有很多和设计有关。有时候不仅要掌握基础的语法特性,也要了解下设计哲学,这样在看到有关ts的问题才会更加游刃有余。