JavaScript高级程序设计笔记 8
第8章 对象、类与面向对象编程 — 快速复习笔记
1. 理解对象
1.1 属性类型
- 数据属性:
[[Configurable]]:能否删除/修改属性特性(默认 true)[[Enumerable]]:能否 for-in 遍历(默认 true)[[Writable]]:能否修改值(默认 true)[[Value]]:实际存储的值(默认 undefined)
- 访问器属性(getter/setter):
[[Configurable]]、[[Enumerable]]同上[[Get]]:读取时调用(默认 undefined)[[Set]]:写入时调用(默认 undefined)
1.2 操作属性特性
Object.defineProperty(obj, prop, descriptor):定义单个属性Object.defineProperties(obj, props):定义多个属性Object.getOwnPropertyDescriptor(obj, prop):获取属性描述符Object.getOwnPropertyDescriptors(obj):获取所有属性描述符(ES2017)
let person = {};
Object.defineProperty(person, "name", {
writable: false,
value: "Nicholas"
});
console.log(person.name); // "Nicholas"
person.name = "Greg"; // 非严格模式静默失败,严格模式抛出错误
2. 创建对象
2.1 工厂模式
function createPerson(name, age) {
let o = new Object();
o.name = name;
o.age = age;
o.sayName = function() { console.log(this.name); };
return o;
}
let p1 = createPerson("Nicholas", 29);
- 问题:无法识别对象类型(都是 Object 实例)。
2.2 构造函数模式
function Person(name, age) {
this.name = name;
this.age = age;
this.sayName = function() { console.log(this.name); };
}
let p1 = new Person("Nicholas", 29);
- new 操作符过程:
- 创建新对象
- 将构造函数的作用域赋给新对象(this 指向新对象)
- 执行构造函数代码
- 返回新对象
- 问题:每个实例都有自己的方法副本(无法共享)。
2.3 原型模式
function Person() {}
Person.prototype.name = "Nicholas";
Person.prototype.sayName = function() { console.log(this.name); };
let p1 = new Person();
let p2 = new Person();
console.log(p1.sayName === p2.sayName); // true
- 原型对象:每个函数都有
prototype属性指向原型对象,实例有__proto__(或Object.getPrototypeOf())指向构造函数的原型。 - 原型链查找:实例属性 → 原型属性 → 更上层原型,直到
Object.prototype。 in操作符:无论实例属性还是原型属性,只要存在就返回 true。hasOwnProperty():仅当属性存在于实例本身时返回 true。- 问题:原型上的引用类型属性会被所有实例共享(如
Person.prototype.friends = ["a","b"],修改会影响所有实例)。
2.4 组合使用构造函数和原型模式(最常用)
function Person(name, age) {
this.name = name; // 实例独有
this.age = age;
this.friends = [];
}
Person.prototype.sayName = function() { // 共享方法
console.log(this.name);
};
2.5 动态原型模式
- 在构造函数中动态检查并添加原型方法(仅在需要时创建)。
function Person(name) {
this.name = name;
if (typeof this.sayName !== "function") {
Person.prototype.sayName = function() { console.log(this.name); };
}
}
3. 继承
3.1 原型链继承(基本但有问题)
function Parent() { this.name = "parent"; }
Parent.prototype.sayHi = function() { console.log("hi"); };
function Child() {}
Child.prototype = new Parent();
let c = new Child();
- 问题:
- 引用类型属性被所有实例共享
- 无法在不影响所有实例的情况下给父类构造函数传参
3.2 盗用构造函数(经典继承)
function Parent(name) { this.name = name; this.colors = ["red"]; }
function Child(name) {
Parent.call(this, name); // 借用构造函数
}
- 优点:解决引用共享和传参问题
- 缺点:方法必须在构造函数中定义,无法复用
3.3 组合继承(最常用)
function Parent(name) {
this.name = name;
this.colors = ["red"];
}
Parent.prototype.sayName = function() { console.log(this.name); };
function Child(name, age) {
Parent.call(this, name); // 第二次调用 Parent
this.age = age;
}
Child.prototype = new Parent(); // 第一次调用 Parent
Child.prototype.constructor = Child;
Child.prototype.sayAge = function() { console.log(this.age); };
- 缺点:父类构造函数被调用了两次。
3.4 寄生组合继承(最优)
function inheritPrototype(child, parent) {
let prototype = Object.create(parent.prototype); // 创建父原型副本
prototype.constructor = child;
child.prototype = prototype;
}
function Parent(name) { this.name = name; }
Parent.prototype.sayName = function() { console.log(this.name); };
function Child(name, age) {
Parent.call(this, name);
this.age = age;
}
inheritPrototype(Child, Parent);
Child.prototype.sayAge = function() { console.log(this.age); };
- 优点:只调用一次父构造函数,原型链干净。
4. 类(ES6+)
4.1 类定义
- 类声明:
class Person {} - 类表达式:
let Person = class {} - 类不会被提升(与函数声明不同)
4.2 构造函数与方法
class Person {
constructor(name) {
this.name = name;
}
sayName() {
console.log(this.name);
}
static create(name) {
return new Person(name);
}
}
let p = new Person("Nicholas");
- 实例方法:定义在
prototype上 - 静态方法:使用
static关键字,定义在类本身上 - getter/setter:在类中直接定义
get prop() {}
4.3 类继承
class Animal {
constructor(name) { this.name = name; }
speak() { console.log(`${this.name} makes a sound.`); }
}
class Dog extends Animal {
constructor(name, breed) {
super(name); // 调用父类构造函数
this.breed = breed;
}
speak() {
super.speak(); // 调用父类方法
console.log(`${this.name} barks.`);
}
}
super关键字:必须在子类构造函数中先调用super()才能使用thisextends后可以跟任意可构造的函数或类
4.4 类的内部机制
- ES6 类本质上还是基于原型的语法糖,但增加了一些特性:
- 类内部代码默认在严格模式下
- 类方法不可枚举
- 类必须用
new调用,不能当作普通函数 - 类名不会被提升
5. 常见面试题速查
属性与对象
-
Object.defineProperty与Object.defineProperties的区别?
→ 前者定义单个属性,后者定义多个属性。 -
hasOwnProperty和in操作符的区别?
→hasOwnProperty只检查实例属性,in检查实例和原型链上的属性。 -
如何获取对象的原型?
→Object.getPrototypeOf(obj)或obj.__proto__(非标准)。
创建对象
-
构造函数模式有什么缺点?
→ 每个实例的方法无法共享,造成内存浪费。 -
原型模式有什么缺点?
→ 引用类型属性会被所有实例共享,修改一个实例会影响其他实例。 -
组合构造函数与原型模式的优点?
→ 实例属性独立,方法共享,最常用。
继承
-
原型链继承的缺点?
→ 引用共享、无法传参。 -
组合继承的缺点?
→ 父类构造函数被调用两次,存在效率问题。 -
寄生组合继承为什么是最优?
→ 只调用一次父构造函数,原型链干净,避免了额外的开销。
ES6 类
-
ES6 类与 ES5 构造函数的区别?
→ 类必须用new调用、内部严格模式、方法不可枚举、无提升、可继承内置对象。 -
super关键字的作用?
→ 在子类构造函数中调用父类构造函数(super()),或在子类方法中调用父类方法(super.method())。 -
子类构造函数中为什么必须先调用
super()?
→ 为了继承父类的this对象,否则无法使用this。 -
静态方法如何定义?
→ 使用static关键字,通过类名调用,不通过实例。 -
类中如何定义 getter/setter?
→ 使用get prop()和set prop(value)。

浙公网安备 33010602011771号