01. 从原型到对象:对象模型与原型链
01.1 原型对象的作用
在 JavaScript 的对象模型 中,原型对象扮演着“属性与方法的共享库”的角色,帮助同类对象复用代码而非逐个拷贝。通过理解 原型对象的职责,可以实现更高效的对象设计与内存利用。
对象实例并不需要为每个实例存放同样的方法,而是通过原型链在原型对象上查找实现,这让方法的一次性创建与多份复用成为可能。
01.2 原型链的工作机制
当访问一个对象的属性时,优先在对象本身查找自有属性,若未找到则沿着原型链向上查找,直到顶层原型对象为止。有时你可能会发现某个属性来自原型而非自有属性,这正是原型链的力量所在。
理解这点有助于明确两点:第一,hasOwnProperty检查的是自有属性;第二,in运算符会在原型链上继续查找。这两者的区别对调试和性能分析至关重要。
function Person(name) {this.name = name;
}
Person.prototype.greet = function() {console.log('Hello, ' + this.name);
};const p = new Person('Alice');
p.greet(); // 通过原型链访问到 greet
console.log(p.hasOwnProperty('name')); // true
console.log('name' in p); // true
02. JavaScript 的对象创建模式与封装
02.1 构造函数与原型的关系
在传统的创建模式中,构造函数负责实例化,而 原型对象承载共享方法。这种分工帮助你将每个实例的独有数据和可复用行为分离开来,从而提高内存效率。
通过这种模式,你可以实现 实例的唯一性与方法的复用,也为后续的继承和混入打下基础。
02.2 ES6-class 的语法糖与对比
ES6 的 class 提供了一种更直观的语法来表达构造函数与原型之间的关系,实质上仍然是基于原型的实现。类语法并不会带来新的原型能力,只是语法上的糖,但它能让代码结构更清晰、可维护性更高。
下面给出两种等价的实现:一种是传统的构造函数+原型,另一种是 ES6 class。通过对比可以看到他们在运行时的原型关系其实是一致的。
// 构造函数 + 原型
function User(name) {this.name = name;
}
User.prototype.say = function() {console.log('Hi, ' + this.name);
};// ES6 class
class UserClass {constructor(name) { this.name = name; }say() { console.log('Hi, ' + this.name); }
}
03. 面向对象设计中的继承、组合与职责分离
03.1 继承的实现方式
在 JavaScript 中,继承有多种实现方式,其中最常用的是通过 原型继承和 class extends。无论哪种方式,核心目标都是让子类在保留自有特性的同时复用父类的行为。
示例展示了两种常见的实现路径:通过传统的构造函数+原型链进行继承,以及使用 ES6 class 的继承语法。了解两者的等价性有助于在大型项目中选取更合适的写法。
// 传统原型继承
function Animal(name) { this.name = name; }
Animal.prototype.speak = function() { console.log(this.name); };function Dog(name) { Animal.call(this, name); }
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog;
Dog.prototype.speak = function() { console.log(this.name + ' barks'); };// ES6 class 继承
class Animal {constructor(name) { this.name = name; }speak() { console.log(this.name); }
}
class Dog extends Animal {speak() { console.log(this.name + ' barks'); }
}
03.2 组合优于继承的实践
在实际场景中,组合(Composition)比起深层的类继承更灵活,它通过将行为注入对象来实现多样化能力,避免了僵化的层级结构。
通过 对象混入(mixin),你可以把共享行为提取成独立的模块,然后按需将它们注入到不同的对象中,达到“职责分离”的效果。
// 组合示例:使用混入实现行为注入
const Flyer = {fly() { console.log(this.name + ' flies'); }
};function Bird(name) { this.name = name; Object.assign(this, Flyer); }class Fish {constructor(name) { this.name = name; }swim() { console.log(this.name + ' swims'); }
}// 通过组合将行为注入实例
const sparrow = new Bird('sparrow');
sparrow.fly();04. 实战要点:私有化、封装与调试
04.1 私有字段与访问控制
在现实的 JavaScript 应用中,私有字段用于保护内部状态,避免外部未经授权的直接访问。通过前缀 # 声明私有属性,外部代码无法直接访问,从而实现更强的封装性。
通过 公有方法或访问器对外暴露受控接口,可以实现对内部状态的验证和保护。私有字段的引入提升了 对象安全性与维护性,尤其是在大型团队协作中尤为重要。
// 私有字段(私有属性)用 # 前缀
class User {#password;constructor(name, password) {this.name = name;this.#password = password;}verify(pwd) { return this.#password === pwd; }getPublicInfo() { return { name: this.name }; }
}
const u = new User('alice', 'secret');
console.log(u.name); // 自有属性
// console.log(u.#password); // 语法错误,私有字段不可访问
04.2 调试原型链与性能诊断
在调试阶段,查看原型链结构和实例之间的关系十分关键。工具如 Object.getPrototypeOf、console.dir 与 hasOwnProperty 等都是强有力的诊断手段。
通过系统地分析原型链,可以定位属性来自自有属性还是原型,进而优化性能与代码结构。合理使用原型与对象的分工,是提升 JavaScript 面向对象编程的实战价值的要点。

const p = new (class {})();
console.dir(p); // 展示原型链信息
console.log(Object.getPrototypeOf(p) === Object.prototype); // 是否指向默认原型
console.log('toString' in p); // in 操作符在原型链上也会搜索


