ts与面向对象
刚学前端那会儿,我总被 “面向对象编程” 这个词搞得头大,感觉它特别高深莫测。后来我发现,其实它就像我们整理房间一样,把相似的东西归类,用起来更方便。
一、面向对象是啥?先从生活找灵感
想象一下,你有一柜子衣服,T 恤、牛仔裤、外套堆在一起,找件衣服得翻半天。但如果你把 T 恤放一个抽屉,裤子放另一个,外套单独挂起来,是不是找起来快多了?这就是面向对象的核心理念 —— 把相似的东西归类成 “对象”,每个对象有自己的 “属性” 和 “行为”。
在前端里,一个按钮可以是一个对象,它有颜色、大小这些属性,还有点击后跳转页面这种行为;一个用户信息框也是对象,属性包括用户名、头像,行为可能是修改密码、退出登录。
二、对象的基础:属性和方法
用 TypeScript 创建一个简单的 “人” 对象,看看属性和方法怎么玩,这里我们给属性和方法都加上类型注解:
// 创建一个“人”对象
const person: { name: string; age: number; sayHello: () => void } = {name: "蔡徐坤", // 属性:名字age: 25, // 属性:年龄sayHello: function() { // 方法:打招呼console.log("大家好,我是练习时长2年半的个人练习生" + this.name);}
};
// 调用对象的方法
person.sayHello();
在这个例子里,name和age是对象person的属性,就像人的名字和年龄;sayHello是方法,是person能做的事情。这里this指的就是当前对象person,如果在其他函数里乱用this会出大问题,后面咱们会细说。
三、构造函数
要是有很多人,一个个创建对象太麻烦了,这时候构造函数就派上用场,它就像工厂的流水线,能批量生产相似的对象。在 TypeScript 中,我们这样定义构造函数:
// 定义一个“人”的构造函数
class Person {name: string;age: number;constructor(name: string, age: number) {this.name = name;this.age = age;}sayHello(): void {console.log("大家好,我是练习时长2年半的个人练习生" + this.name);}
}
// 用构造函数创建两个“人”对象
const person1 = new Person("小蔡", 22);
const person2 = new Person("小坤", 28);
person1.sayHello();
person2.sayHello();
这里Person就是构造函数(在 TypeScript 用类的形式定义),用new关键字就能创建新对象。每个新对象都有自己独立的name、age属性和sayHello方法,就像不同的人都有自己的名字、年龄,都能打招呼。
不过构造函数有个小问题,每个对象的sayHello方法虽然代码一样,但在内存里是分开存的,浪费空间。这时候就需要原型来优化了。
四、原型
原型就像对象的公共图书馆,所有同类型对象都能共享里面的 “技能”。还是以 “人” 为例,在 TypeScript 中利用原型的方式如下:
class Person {name: string;age: number;constructor(name: string, age: number) {this.name = name;this.age = age;}
}
// 把sayHello方法放到原型上
Person.prototype.sayHello = function(): void {console.log("大家好,我是练习时长2年半的个人练习生" + this.name);
};
const person1 = new Person("小蔡", 23);
const person2 = new Person("小坤", 26);
person1.sayHello();
person2.sayHello();
现在sayHello方法存在Person的原型里,person1和person2不用各自复制一份,节省了内存。当调用person1.sayHello()时,TypeScript 会先在person1对象里找sayHello,找不到就去原型里找,找到就执行。
五、类:更简洁的语法糖
在 TypeScript 中,类的语法更加完善,它不仅是语法糖,还能利用类型系统让代码更健壮。还是以 “人” 为例:
class Person {constructor(public name: string, public age: number) {}sayHello(): void {console.log("大家好,我是练习时长2年半的个人练习生" + this.name);}
}
const person1 = new Person("小蔡", 24);
const person2 = new Person("小坤", 27);
person1.sayHello();
person2.sayHello();
这里在constructor参数前面加上public,会自动创建同名的公有属性,比 JavaScript 写法更简洁。sayHello方法直接写在类里面,类型明确,代码结构也更清晰。
六、实际应用场景
比如做一个简单的图片轮播效果,把整个轮播组件当成一个对象,在 TypeScript 中实现如下:
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>图片轮播</title>
</head>
<body><div id="slider"><img src="img1.jpg" alt="图片1"><img src="img2.jpg" alt="图片2"><img src="img3.jpg" alt="图片3"></div><script>class ImageSlider {slider: HTMLElement;images: NodeListOf<HTMLImageElement>;currentIndex: number;constructor(sliderId: string) {const element = document.getElementById(sliderId);if (!element) {throw new Error(`找不到id为 ${sliderId} 的元素`);}this.slider = element;this.images = this.slider.getElementsByTagName('img');this.currentIndex = 0;this.showImage();}showImage(): void {for (let i = 0; i < this.images.length; i++) {this.images[i].style.display = 'none';}this.images[this.currentIndex].style.display = 'block';}nextImage(): void {this.currentIndex = (this.currentIndex + 1) % this.images.length;this.showImage();}}
const slider = new ImageSlider('slider');// 模拟点击下一张图片setInterval(() => {slider.nextImage();}, 3000);</script>
</body>
</html>
在这个ImageSlider类里,constructor初始化轮播组件,我们添加了更严格的类型检查,比如确保获取到正确的 DOM 元素,否则抛出错误。showImage方法负责显示当前图片,nextImage方法切换到下一张图片。把整个轮播逻辑封装成一个对象,借助 TypeScript 的类型系统,代码结构更清晰,维护起来也更方便,还能减少很多运行时的错误。
七、面向对象的坑:this的陷阱
this在面向对象编程里特别容易搞混。在 TypeScript 中,this的值同样取决于函数的调用方式:
作为对象的方法调用,this指向这个对象,比如person.sayHello()里this就是person。
直接调用函数,this在非严格模式下指向全局对象(浏览器里是window),在严格模式下是undefined。
function test(): void {console.log(this);
}
test(); // 在浏览器里会打印window
用call、apply、bind调用函数,可以手动指定this的值:
class Person {name: string;constructor(name: string) {this.name = name;}sayHi(): void {console.log("嗨,我是练习时长2年半的个人练习生" + this.name);}
}
const person = new Person("蔡徐坤");
const anotherPerson = { name: "爱坤" };
person.sayHi.call(anotherPerson); // 打印:嗨,我是爱坤
八、总结
通过这些例子可以看出,面向对象编程能把复杂的代码拆分成一个个独立的对象,每个对象负责自己的事情,就像分工明确的团队。它的好处有:
代码复用:一个对象的方法和属性可以在其他地方重复使用,不用重复写代码。
结构清晰:对象之间职责分明,改代码或者加功能时,很容易找到对应的地方。
可维护性强:出了问题可以快速定位到某个对象,修改也不会影响其他部分。
类型安全(TypeScript 特有的优势):提前发现很多类型错误,让代码更健壮。
但也不是所有场景都适合用面向对象,比如一些简单的页面效果,用面向过程的编程方式可能更简单直接。一般在开发大型项目、复杂交互组件时,面向对象的优势就能充分发挥出来。