作为前端开发者,你是否曾因 JavaScript 的“随心所欲”踩过坑?比如变量类型突然变化导致运行时报错、接手大型项目时看不懂参数类型、团队协作时因类型不统一产生 bug……TypeScript(简称 TS)作为 JavaScript 的超集,完美解决了这些痛点。
本文会从「TS 与 JS 的核心区别」入手,梳理 TS 核心知识点,最后通过一个实战案例串联所有知识点,帮你快速上手 TS,也适合作为博客素材直接使用。
一、TypeScript vs JavaScript 核心区别
TS 不是替代 JS 的新语言,而是给 JS 加了“类型约束”的超集——所有合法的 JS 代码都是合法的 TS 代码,TS 最终会编译成 JS 运行。二者核心差异如下:
| 对比维度 | JavaScript | TypeScript |
|---|---|---|
| 类型系统 | 动态弱类型(运行时确定类型,变量可随意改类型) | 静态强类型(编译时确定类型,提前约束) |
| 错误检测 | 运行时才暴露错误(比如上线后才发现类型错) | 编译阶段就发现错误(提前规避 bug) |
| 运行环境 | 直接运行在浏览器/Node.js | 需通过tsc编译成 JS 后运行 |
| 语法扩展 | 仅原生语法(无接口、泛型等) | 新增接口、泛型、枚举、访问修饰符等 |
| 工程化支持 | 大型项目易维护性差,IDE 提示弱 | 天然适配大型项目,VS Code 等 IDE 智能提示拉满 |
二、TypeScript 核心知识点梳理
TS 的核心是「类型约束」,所有知识点都围绕“给代码加类型规则”展开,以下是新手必学的核心点:
1. 最基础:类型注解
给变量、函数参数/返回值标注“类型”,是 TS 最基础的用法,也是和 JS 最直观的区别。
// JS 写法:无类型约束,变量可随意改类型letname="张三";name=123;// 不报错,但逻辑上不合理// TS 写法:加类型注解,约束类型letname:string="张三";name=123;// 编译报错:Type 'number' is not assignable to type 'string'// 函数参数/返回值注解functionadd(a:number,b:number):number{returna+b;// 强制返回数字,返回字符串会报错}2. 基础类型:扩展 JS 原生类型
TS 兼容 JS 所有原始类型(string/number/boolean/null/undefined/symbol),还新增了实用类型:
- 数组:两种写法,约束数组元素类型
letstrArr:string[]=["学习TS","写博客"];// 推荐letnumArr:Array<number>=[1,2,3];// 泛型写法 - 元组:固定长度和类型的数组(比如坐标、键值对)
letposition:[number,number]=[100,200];// 只能是两个数字position.push(300);// 允许追加(TS 小坑),但访问 position[2] 会报错 - 枚举:给一组常量起名字,避免“魔法值”
enumTodoStatus{Unfinished=0,Completed=1,}letstatus:TodoStatus=TodoStatus.Completed;// 语义化更强 - any:关闭 TS 类型检查(尽量少用,仅过渡时用)
lettemp:any="hello";temp=123;// 不报错,等同于 JS 无约束 - void:函数无返回值
functionlogMsg():void{console.log("TS 知识点");// 无 return 或 return undefined}
3. 接口(Interface):约束对象结构
JS 没有“接口”概念,TS 用接口定义对象的“形状”(属性名、类型、是否可选/只读),是前端开发中最常用的 TS 特性之一。
interfaceTodo{id:number|string;// 联合类型:id 可以是数字或字符串content:string;// 必选属性completed:boolean;// 必选属性createTime?:string;// 可选属性(加 ?)readonlyauthor:string;// 只读属性(不可修改)}// 符合接口的对象consttodo:Todo={id:1,content:"学习接口",completed:false,author:"前端开发者",};todo.author="新手";// 编译报错:只读属性不能修改4. 类型别名(Type Alias):灵活的类型封装
和接口类似,但比接口更灵活(可定义联合类型、交叉类型等),二者核心区别:接口侧重“对象结构”,类型别名侧重“类型复用”。
// 定义联合类型别名typeID=number|string;// 定义对象类型别名typeUser={name:string;age:number;};// 交叉类型:合并多个类型typeAdminUser=User&{role:string};5. 类(Class):新增访问修饰符
TS 给 JS 类新增了public/private/protected修饰符,实现属性封装,避免外部随意修改类内部数据。
classTodoManager{publicversion:string="1.0";// 公有的(默认),外部可访问privatetodos:Todo[]=[];// 私有的,仅类内部可访问protectednextId:number=1;// 受保护的,类内部和子类可访问// 获取待办列表(通过方法暴露私有属性,避免直接修改)getTodos():Todo[]{return[...this.todos];// 返回副本,而非原数组}}constmanager=newTodoManager();console.log(manager.version);// 正常访问console.log(manager.todos);// 编译报错:private 属性外部不可访问6. 泛型(Generic):解决类型复用问题
泛型是 TS 进阶核心,核心作用:让“类型也能传参”,避免重复定义相似类型。比如封装一个“返回任意类型数组”的函数:
// 泛型函数:T 是类型参数,调用时指定具体类型functionwrapValue<T>(value:T):T[]{return[value];}// 调用时指定类型:T = stringconststrArr=wrapValue<string>("学习泛型");// 自动推导类型:T = numberconstnumArr=wrapValue(123);7. 类型守卫:缩小类型范围
当变量是“联合类型”时,TS 无法确定具体类型,类型守卫(typeof/instanceof)可帮 TS 精准判断类型。
functionhandleId(id:number|string){if(typeofid==="string"){console.log(id.length);// TS 知道这里 id 是 string}else{console.log(id.toFixed(2));// TS 知道这里 id 是 number}}三、实战案例:TS 实现 TodoList 核心功能
用一个前端高频场景(待办事项)串联所有知识点,对比 JS 版本,你会直观感受到 TS 的优势。
需求
实现 TodoList 核心功能:添加待办、删除待办、切换待办状态,要求用 TS 约束所有类型,避免类型错误。
步骤 1:定义核心类型(接口 + 类型别名)
//定义接口interfaceTodo{id:number|string;content:string;completed:boolean;}//定义代办器类型接口interfaceITodoManager{addTodo:(content:string)=>void;createTodoItem:(todo:Todo)=>void;deleteTodoItem:(id:number|string)=>void;}步骤 2:封装 TodoManager 类(访问修饰符 + 函数类型)
classTodoListimplementsITodoManager{//Array 是 TS 泛型 必须明确数组里面存什么类型//private todos: Array = []; 写法错误//private todos: Todo[] = [];privatetodos:Array<Todo>=[];privatenextId:number=1;//todo-list容器privateelement:HTMLElement;//代办列表privateul:HTMLElement;//点击添加按钮privatebutton:HTMLElement;//输入框privateinput:HTMLInputElement;constructor(){constcontainer=document.querySelector(".todo-container");if(!container)thrownewError("未找到 .todo-container 容器");this.element=containerasHTMLElement;//获取 ulconsttodoListUl=this.element.querySelector(".todo-list")asHTMLElement;if(!todoListUl||todoListUl.tagName!=="UL"){thrownewError("未找到 .todo-list 列表");}this.ul=todoListUl;//获取按钮constaddBtn=this.element.querySelector(".add-btn");if(!addBtn)thrownewError("未找到 .add-btn 按钮");this.button=addBtnasHTMLElement;//获取输入框consttodoInput=this.element.querySelector(".todo-input");if(!todoInput)thrownewError("未找到 .todo-input 输入框");this.input=todoInputasHTMLInputElement;// 初始化事件绑定this.init();}init(){this.button.addEventListener("click",()=>{// 从输入框获取内容,调用 addTodothis.addTodo(this.input.value);});// 输入框回车事件(触发添加)this.input.addEventListener("keydown",(e)=>{if(e.key==="Enter"){this.addTodo(this.input.value);}});}addTodo(content:string):void{if(!content.trim()){alert("请输入代办项");return;}constnewTodo:Todo={id:this.nextId++,content:content.trim(),completed:false,};this.todos.push(newTodo);this.createTodoItem(newTodo);this.input.value="";}createTodoItem(todo:Todo):void{constli=document.createElement("li");li.className="todo-item";li.dataset.id=todo.id.toString();if(todo.completed){li.classList.add("todo-item-completed");}li.innerHTML=`<!-- 可选:完成状态复选框 --> <input type="checkbox" class="toggle-btn"${todo.completed?"checked":""}> <!-- 待办内容 --> <span class="todo-content">${todo.content}</span> <!-- 删除按钮 --> <button class="todo-delete-btn"> <i class="delete-btn"></i> 删除 </button>`;constdeleteBtn=li.querySelector(".todo-delete-btn")asHTMLElement;deleteBtn.addEventListener("click",()=>{this.deleteTodoItem(todo.id);li.remove();});constcheckbox=li.querySelector(".toggle-btn")asHTMLInputElement;checkbox.addEventListener("change",()=>{this.finishTodoItem(todo.id,checkbox.checked);});this.ul.appendChild(li);}deleteTodoItem(id:number|string):void{this.todos=this.todos.filter((item)=>item.id!==id);}finishTodoItem(id:number|string,isChecked:boolean):void{consttargetTodo=this.todos.find((item)=>item.id===id);console.log(targetTodo);if(targetTodo){targetTodo.completed=isChecked;}constli=this.ul.querySelector(`[data-id="${id}"]`)asHTMLElement;console.log(li);if(li){consttodoContent=document.querySelector(".todo-content",)asHTMLElement;todoContent.classList.toggle("todo-item-completed",isChecked);}}}步骤 3:使用 TodoManager(类型守卫 + 泛型)
importTodoListfrom"./modules/script.js";window.addEventListener("DOMContentLoaded",()=>{newTodoList();// 此时 HTML 所有元素都已解析,能找到 .add-btn/.todo-input});案例对比 JS 版本的优势
如果用纯 JS 实现这个 TodoList,可能出现以下问题:
- 给
addTodo传数字(比如addTodo(123)),JS 不报错,但逻辑错误; - 给
deleteTodo传布尔值(比如deleteTodo(true)),运行时才发现过滤逻辑失效; - 直接修改
todos数组(比如manager.todos = []),破坏类的封装性;
而 TS 编译阶段就会发现这些问题,提前规避 bug,且代码语义化更强,接手项目时能快速看懂每个参数/属性的类型。
https://github.com/Atopos-jy/todo-list_ts.git 代码仓库
四、总结
- TS 是 JS 的超集,核心优势是静态类型检查,编译阶段发现错误,提升大型项目可维护性;
- TS 核心知识点围绕“类型约束”展开:类型注解、接口/类型别名(约束对象)、泛型(类型复用)、类的访问修饰符(封装)是前端高频使用的特性;
- 初学 TS 无需追求“零 any”,可先用类型注解和接口覆盖核心逻辑,逐步过渡到泛型等进阶特性。
最后小提示:TS 不是“银弹”,小型项目用 JS 足够,中大型项目/团队协作场景下,TS 才能发挥最大价值。