JS 面向对象编程(OOP)深入讲解
🎯 引言
学会本篇文章后,你将能够理解 JavaScript 的对象模型,掌握构造函数和 new 的工作原理,了解原型与原型链的机制,熟悉属性查找过程,并能灵活应用多种继承方法。对前端开发中面向对象编程有扎实的基础,写出更规范、更高效的代码。
🔍 什么是 JavaScript 的对象模型
JavaScript 是一门基于对象的语言,简单来说,“万物皆对象”是它的核心思想。即使是数字、字符串这些看起来像“值”的东西,在 JavaScript 里也可以作为对象来操作。
值类型 vs 引用类型
先说说这个区分,刚入门的小伙伴一定要理解:
- 值类型(Primitive Types)包括
Number、String、Boolean、null、undefined、Symbol(ES6新增)
这些类型的变量存储的是具体的值,赋值操作是拷贝值本身。 - 引用类型(Reference Types)主要就是
Object、Array、Function等
这些类型的变量存储的是对象的内存地址(引用),赋值操作只是复制了引用地址,指向同一个对象。
举个生活中的例子:
你可以想象值类型就像是“快递包裹”,里面装的是具体物品,复制快递包裹就是复制了物品本身;引用类型就像是“房屋钥匙”,复制钥匙意味着可以打开同一扇门,访问同一套房子。
⚙️ 构造函数与 new 的工作机制
构造函数定义方式
在 JavaScript 里,构造函数其实就是普通的函数,但习惯上首字母大写表示它是用来创建对象的。
function Person(name, age) {
this.name = name;
this.age = age;
this.sayHello = function () {
console.log('Hello, my name is ' + this.name);
};
}
new 做了哪四件事?
当你使用 new Person('Tom', 20) 时,JavaScript 引擎帮你做了下面四件事情:
- 创建了一个新对象(空对象)
- 将这个新对象的内部
[[Prototype]]指向构造函数的.prototype属性 - 调用构造函数,将
this指向刚创建的新对象,执行函数体代码 - 如果构造函数没有返回对象,则返回新创建的对象
模拟实现 new
用代码模拟实现一个简单的 new 操作:
function myNew(fn, ...args) {
const obj = {};
Object.setPrototypeOf(obj, fn.prototype);
const result = fn.apply(obj, args);
return typeof result === 'object' && result !== null ? result : obj;
}
new 的过程,能帮助你理解为什么构造函数能创建对象,以及原型链是如何连接的。🧩 原型与 .prototype 属性
函数为什么有 .prototype?
每个函数在 JavaScript 中都有一个特殊属性叫 .prototype,它是一个对象,默认只有一个 constructor 属性,指向函数本身。
这个 .prototype 对象是构造函数创建实例对象时,实例的 __proto__(内部属性)指向的对象。
实例与原型的关系:proto vs .prototype
- 实例对象有一个
__proto__属性,指向它的构造函数的.prototype。 - 构造函数有一个
.prototype属性,指向原型对象。
这种关系就像桥梁,实例通过 __proto__ 连接到构造函数的原型,原型上可以定义共享方法。
const p = new Person('Jack', 25);
console.log(p.__proto__ === Person.prototype); // true
构造函数、原型对象、实例三者关系图
Person (构造函数)
|
| .prototype
↓
Person.prototype (原型对象) --- (上级原型链) ---> Object.prototype --- null
p (实例对象)
|__ __proto__ --> Person.prototype
🌲 原型链与属性查找机制
什么是原型链?
原型链就是实例对象通过 __proto__ 指向构造函数的 .prototype 对象,后者又指向它的上级原型,直到 Object.prototype,再往上是 null,这条链条就像一个链子。
属性/方法查找过程
当你访问对象的属性或方法时,JavaScript 会先在自身属性中查找,如果找不到,会沿着 __proto__ 指向的原型对象查找,一直找到 null 停止。
举个例子:
console.log(p.sayHello); // 首先找 p 对象自身的 sayHello
// 如果 p 自身没有,接着去 Person.prototype 查找
Object.create 的作用与原理
Object.create(proto) 创建了一个新对象,这个新对象的 __proto__ 指向 proto。这是创建继承链的一种简洁方法。
const obj = Object.create(Person.prototype);
Object.create 可以灵活地创建链式继承,而不是通过构造函数。🌿 继承的实现方式
原型链继承
让子类型的原型指向父类型的实例,实现继承。
function Parent() {
this.name = 'parent';
}
Parent.prototype.say = function () {
console.log('hello');
};
function Child() {}
Child.prototype = new Parent();
const c = new Child();
c.say(); // hello
缺点是所有子实例共享父实例的引用属性。
构造函数继承(call)
在子构造函数中调用父构造函数,改变 this 指向,避免引用共享。
function Child() {
Parent.call(this);
}
缺点是只能继承父构造函数的属性和方法,不能继承原型上的方法。
组合继承
结合上面两种方式,既继承属性也继承方法。
function Child(name) {
Parent.call(this);
this.name = name;
}
Child.prototype = new Parent(); // 所以 Child.prototype.constructor 是指向 parent
Child.prototype.constructor = Child; // 修正 constructor 指向为 child
class/extends 的语法糖和本质
ES6 引入 class 语法,让继承写法更简洁:
class Parent {
constructor() {
this.name = 'parent';
}
say() {
console.log('hello');
}
}
class Child extends Parent {
constructor() {
super();
}
}
本质还是基于原型链和构造函数,只是语法更清晰,更像传统面向对象语言。
🧾 小节总结
- JavaScript 的对象模型基于值类型和引用类型的区分,万物皆对象。
- 使用构造函数和
new可以创建对象,new做了四件关键事。 - 每个函数都有
.prototype,实例的__proto__指向构造函数的.prototype,实现原型继承。 - 属性查找沿着原型链进行,
Object.create是创建原型链的利器。 - 继承可以通过原型链、构造函数、组合继承等方式实现,ES6
class是语法糖,本质依旧是原型链和构造函数。
❓ 知识问答(Q&A)
Q:构造函数和普通函数有什么区别?
A:构造函数通常首字母大写,用来创建对象,配合 new 使用,普通函数没有这个限制。
Q:为什么要用原型链实现继承?
A:原型链可以让多个实例共享父类方法,节省内存,写法也更灵活。
Q:Object.create 和直接构造函数继承有什么区别?
A:Object.create 创建对象时能指定原型,更灵活地控制继承关系;构造函数继承更多是属性复制。
🧪 小练习
- 使用构造函数和
new创建一个Car对象,有brand和speed属性,以及一个run方法,实现打印汽车运行状态。
function Car(brand, speed) {
// 你的代码
}
const myCar = new Car('Toyota', 80);
myCar.run(); // 输出: Toyota is running at 80 km/h
- 用原型链方式实现一个动物(Animal)和狗(Dog)的继承,Animal 有
eat方法,Dog 继承 Animal,并新增bark方法。
function Animal() {
// 你的代码
}
Animal.prototype.eat = function () {
console.log('Eating food');
};
function Dog() {
// 你的代码
}
// 继承 Animal 原型
Dog.prototype.bark = function () {
console.log('Woof!');
};
const dog = new Dog();
dog.eat();
dog.bark();
🎉 恭喜你已经掌握 JavaScript 面向对象编程中对象模型、构造函数与 new、原型链、属性查找以及继承的关键技能啦!用这些知识去写更优雅的代码吧!
