目标:掌握对象的创建、属性特性、原型与原型链、各类继承方式,以及 ES6 类语法的本质与坑点。
目录
对象基础与属性特性
构造函数、实例与原型
内建对象与原型
继承方式详解
常见坑与排错
代码片段(直接可用)
实战应用场景
速记与总结
1. 对象基础与属性特性 1.1 对象的定义方式 在 JavaScript 中,对象是键值对的集合,是 JavaScript 中最基本的数据结构之一。有多种方式可以创建对象:
1.1.1 对象字面量(最常用) 这是最简单直接的方式,适合创建单个对象:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 const person = { name : '张三' , age : 25 , greet ( ) { console .log (`你好,我是${this .name} ` ); } }; const key = 'dynamicKey' ;const obj = { [key]: 'value' , [`${key} 2` ]: 'value2' }; const name = '李四' ;const age = 30 ;const person2 = { name, age };
适用场景 :创建配置对象、数据传递、单例对象等。
1.1.2 构造函数 + new(传统方式) 使用构造函数可以创建多个相似的对象:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 function Person (name, age ) { this .name = name; this .age = age; this .greet = function ( ) { console .log (`你好,我是${this .name} ` ); }; } const person1 = new Person ('张三' , 25 );const person2 = new Person ('李四' , 30 );console .log (person1.name ); console .log (person2.name );
注意 :每个实例都会创建自己的 greet 方法,造成内存浪费。更好的做法是将方法放在原型上(见后续章节)。
1.1.3 Object.create()(精确控制原型) Object.create() 可以创建一个以指定对象为原型的新对象,这是最灵活的方式:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 const cleanObj = Object .create (null );console .log (cleanObj.toString ); const parent = { name : '父对象' , say ( ) { console .log ('我是父对象' ); } }; const child = Object .create (parent);child.age = 10 ; console .log (child.name ); console .log (child.age ); const obj = Object .create (parent, { id : { value : 1 , writable : false , enumerable : true , configurable : false }, name : { get ( ) { return '自定义名称' ; }, enumerable : true } });
适用场景 :需要精确控制原型链、创建纯净对象、实现继承等。
1.1.4 ES6 类语法(现代推荐) class 是 ES6 引入的语法糖,本质仍然是构造函数 + 原型,但语法更清晰:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 class Person { constructor (name, age ) { this .name = name; this .age = age; } greet ( ) { console .log (`你好,我是${this .name} ` ); } static createDefault ( ) { return new Person ('默认名称' , 0 ); } } const person = new Person ('张三' , 25 );person.greet (); const defaultPerson = Person .createDefault ();
优势 :
语法更接近传统面向对象语言
自动处理原型链
支持继承(extends)
代码更易读易维护
1.2 属性类型与特性(属性描述符) JavaScript 中的每个属性都有描述符(descriptor),用于控制属性的行为。理解描述符是掌握对象高级特性的关键。
1.2.1 数据属性(Data Property) 数据属性包含一个值,有四个特性:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 const obj = {};obj.name = '张三' ; Object .defineProperty (obj, 'name' , { value : '张三' , writable : true , enumerable : true , configurable : true }); Object .defineProperty (obj, 'id' , { value : 1 , writable : false , enumerable : true , configurable : false }); obj.id = 2 ; delete obj.id ;
四个特性的详细说明 :
**value**:属性的值
1 2 Object .defineProperty (obj, 'x' , { value : 10 });console .log (obj.x );
**writable**:是否可写
1 2 3 4 5 Object .defineProperty (obj, 'readonly' , { value : '不可修改' , writable : false }); obj.readonly = '新值' ;
**enumerable**:是否可枚举
1 2 3 4 5 6 7 8 9 10 Object .defineProperty (obj, 'hidden' , { value : '隐藏属性' , enumerable : false }); console .log (obj.hidden ); for (let key in obj) { console .log (key); } console .log (Object .keys (obj));
**configurable**:是否可配置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 Object .defineProperty (obj, 'locked' , { value : '锁定' , configurable : false }); delete obj.locked ; Object .defineProperty (obj, 'locked' , { writable : false });
1.2.2 访问器属性(Accessor Property) 访问器属性不包含值,而是通过 get 和 set 函数来控制读写:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 const user = { _age : 0 }; Object .defineProperty (user, 'age' , { get ( ) { console .log ('读取 age' ); return this ._age ; }, set (value ) { console .log ('设置 age' ); if (value < 0 || value > 150 ) { throw new Error ('年龄必须在 0-150 之间' ); } this ._age = value; }, enumerable : true , configurable : true }); user.age = 25 ; console .log (user.age ); user.age = 200 ;
访问器属性的特点 :
没有 value 和 writable 特性
只有 get 和 set 函数
可以实现数据验证、计算属性、副作用等
实际应用示例 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 const rectangle = { width : 10 , height : 20 }; Object .defineProperty (rectangle, 'area' , { get ( ) { return this .width * this .height ; }, enumerable : true }); console .log (rectangle.area ); rectangle.width = 15 ; console .log (rectangle.area ); const form = { _email : '' }; Object .defineProperty (form, 'email' , { get ( ) { return this ._email ; }, set (value ) { if (!value.includes ('@' )) { throw new Error ('邮箱格式不正确' ); } this ._email = value; } });
1.2.3 批量定义属性 使用 Object.defineProperties() 可以一次性定义多个属性:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 const obj = {};Object .defineProperties (obj, { name : { value : '张三' , writable : false , enumerable : true }, age : { value : 25 , writable : true , enumerable : true }, id : { get ( ) { return Math .random ().toString (36 ); }, enumerable : true } });
1.2.4 获取属性描述符 使用 Object.getOwnPropertyDescriptor() 查看属性的描述符:
1 2 3 4 5 6 7 8 9 10 11 12 13 const obj = { name : '张三' };const descriptor = Object .getOwnPropertyDescriptor (obj, 'name' );console .log (descriptor);const allDescriptors = Object .getOwnPropertyDescriptors (obj);
1.3 可枚举性与遍历 理解可枚举性对于正确遍历对象至关重要。不同的遍历方法有不同的行为。
1.3.1 什么是可枚举性? 可枚举性(enumerable)决定了属性是否会在某些遍历操作中出现。默认情况下,直接定义的属性都是可枚举的。
1 2 3 4 5 6 7 8 9 10 const obj = { name : '张三' , age : 25 }; Object .defineProperty (obj, 'id' , { value : 1 , enumerable : false });
1.3.2 各种遍历方法对比 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 const parent = { parentProp : '父属性' };const obj = Object .create (parent);obj.name = '张三' ; obj.age = 25 ; Object .defineProperty (obj, 'id' , { value : 1 , enumerable : false }); const sym = Symbol ('symbolKey' );obj[sym] = 'symbol值' ; console .log ('=== for...in ===' );for (let key in obj) { console .log (key); } console .log ('=== Object.keys() ===' );console .log (Object .keys (obj)); console .log ('=== Object.getOwnPropertyNames() ===' );console .log (Object .getOwnPropertyNames (obj)); console .log ('=== Object.getOwnPropertySymbols() ===' );console .log (Object .getOwnPropertySymbols (obj)); console .log ('=== Reflect.ownKeys() ===' );console .log (Reflect .ownKeys (obj)); console .log ('=== Object.values() / Object.entries() ===' );console .log (Object .values (obj)); console .log (Object .entries (obj));
选择指南 :
方法
遍历范围
包含不可枚举
包含 Symbol
包含继承
for...in
可枚举
❌
❌
✅
Object.keys()
自有可枚举
❌
❌
❌
Object.getOwnPropertyNames()
自有所有字符串键
✅
❌
❌
Object.getOwnPropertySymbols()
自有 Symbol 键
-
✅
❌
Reflect.ownKeys()
自有所有键
✅
✅
❌
1.3.3 实际应用场景 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 function getOwnKeys (obj ) { const keys = []; for (let key in obj) { if (obj.hasOwnProperty (key)) { keys.push (key); } } return keys; } const ownKeys = Object .keys (obj);function deepClone (obj ) { const cloned = {}; for (let key in obj) { if (obj.hasOwnProperty (key)) { cloned[key] = typeof obj[key] === 'object' ? deepClone (obj[key]) : obj[key]; } } return cloned; } function getAllKeys (obj ) { return Reflect .ownKeys (obj); }
1.4 对象冻结/密封/扩展控制 JavaScript 提供了三个层次的对象保护机制,用于防止对象被意外修改。
1.4.1 Object.preventExtensions() - 禁止扩展 禁止向对象添加新属性,但可以修改和删除现有属性:
1 2 3 4 5 6 7 8 9 const obj = { name : '张三' , age : 25 };Object .preventExtensions (obj);obj.name = '李四' ; delete obj.age ; obj.newProp = '新属性' ; console .log (Object .isExtensible (obj));
适用场景 :防止意外添加属性,但允许修改现有属性。
1.4.2 Object.seal() - 密封对象 在 preventExtensions 的基础上,还将所有现有属性设为 configurable: false:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 const obj = { name : '张三' , age : 25 };Object .seal (obj);obj.name = '李四' ; delete obj.age ; obj.newProp = '新属性' ; Object .defineProperty (obj, 'name' , { enumerable : false }); console .log (Object .isSealed (obj));
特点 :
不能添加新属性
不能删除现有属性
不能修改属性描述符
可以修改属性值
1.4.3 Object.freeze() - 冻结对象 在 seal 的基础上,还将所有数据属性设为 writable: false:
1 2 3 4 5 6 7 8 9 const obj = { name : '张三' , age : 25 };Object .freeze (obj);obj.name = '李四' ; delete obj.age ; obj.newProp = '新属性' ; console .log (Object .isFrozen (obj));
注意:浅冻结
Object.freeze() 只冻结对象本身,不会冻结嵌套对象:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 const obj = { name : '张三' , address : { city : '北京' , street : '某街道' } }; Object .freeze (obj);obj.name = '李四' ; obj.address .city = '上海' ; function deepFreeze (obj ) { Object .freeze (obj); for (let key in obj) { if (obj.hasOwnProperty (key) && typeof obj[key] === 'object' && obj[key] !== null ) { deepFreeze (obj[key]); } } return obj; } deepFreeze (obj);obj.address .city = '上海' ;
1.4.4 三种保护级别的对比
操作
正常对象
preventExtensions
seal
freeze
添加属性
✅
❌
❌
❌
删除属性
✅
✅
❌
❌
修改属性值
✅
✅
✅
❌
修改属性描述符
✅
✅
❌
❌
1.4.5 实际应用场景 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 const config = { apiUrl : 'https://api.example.com' , timeout : 5000 }; Object .freeze (config);const CONSTANTS = { STATUS : { PENDING : 'pending' , SUCCESS : 'success' , ERROR : 'error' } }; Object .freeze (CONSTANTS );Object .freeze (CONSTANTS .STATUS ); function createImmutableUser (name, age ) { const user = { name, age }; return Object .freeze (user); }
2. 构造函数、实例与原型 这是 JavaScript 面向对象编程的核心。理解构造函数、实例和原型的关系,是掌握 JavaScript 继承机制的基础。
2.1 构造函数与实例关系 2.1.1 new 操作符的执行过程 当你使用 new 操作符调用函数时,JavaScript 会执行以下 4 个步骤:
1 2 3 4 5 6 function Person (name, age ) { this .name = name; this .age = age; } const person = new Person ('张三' , 25 );
详细步骤解析 :
手动模拟 new 操作符 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 function myNew (Constructor, ...args ) { const obj = {}; Object .setPrototypeOf (obj, Constructor .prototype ); const result = Constructor .apply (obj, args); return result instanceof Object ? result : obj; } const person = myNew (Person , '张三' , 25 );
2.1.2 构造函数返回值的特殊情况 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 function Person1 (name ) { this .name = name; return '字符串' ; } const p1 = new Person1 ('张三' );console .log (p1); function Person2 (name ) { this .name = name; return { custom : '自定义对象' }; } const p2 = new Person2 ('张三' );console .log (p2); function Person3 (name ) { this .name = name; } const p3 = new Person3 ('张三' );console .log (p3);
最佳实践 :构造函数通常不应该返回值,让 JavaScript 自动返回新对象。
2.1.3 判断函数是否作为构造函数调用 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 function Person (name ) { if (!(this instanceof Person )) { throw new Error ('必须使用 new 调用' ); } if (new .target !== Person ) { throw new Error ('必须使用 new 调用' ); } this .name = name; } Person ('张三' ); new Person ('张三' );
2.2 原型对象(prototype)与实例原型([[Prototype]]/__proto__) 这是最容易混淆的概念。让我们详细区分它们。
2.2.1 三个关键概念 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 function Person (name ) { this .name = name; } Person .prototype .sayHello = function ( ) { console .log (`你好,我是${this .name} ` ); }; const person = new Person ('张三' );console .log (person.__proto__ === Person .prototype );
关系图 :
1 2 3 4 5 6 7 8 9 Person (构造函数) └── prototype (属性) └── Person.prototype (原型对象) ├── sayHello (方法) └── constructor (指向 Person) person (实例) └── [[Prototype]] (内部属性,通过 __proto__ 访问) └── 指向 Person.prototype
2.2.2 推荐的原型操作方法 虽然 __proto__ 被广泛支持,但推荐使用标准方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 const person = new Person ('张三' );person.__proto__ = someProto; const proto = Object .getPrototypeOf (person);console .log (proto === Person .prototype ); Object .setPrototypeOf (person, someProto);const newObj = Object .create (Person .prototype );
为什么推荐标准方法?
性能 :Object.setPrototypeOf() 虽然也有性能问题,但 __proto__ 在某些引擎中可能被优化掉
可移植性 :标准方法在所有环境中都可用
语义清晰 :明确表达意图
2.2.3 prototype 属性的特殊性 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 function Person ( ) {}console .log (typeof Person .prototype ); console .log (Person .prototype .constructor === Person ); Person .prototype .constructor = function FakePerson ( ) {};function Animal ( ) {}function Dog ( ) {}Dog .prototype = Object .create (Animal .prototype );Dog .prototype .constructor = Dog ;
2.3 原型链查找规则 理解原型链查找是掌握 JavaScript 继承的关键。
2.3.1 属性读取的查找过程 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 function Person (name ) { this .name = name; } Person .prototype .sayHello = function ( ) { console .log ('你好' ); }; Person .prototype .species = '人类' ;const person = new Person ('张三' );console .log (person.name ); console .log (person.sayHello ); console .log (person.species ); console .log (person.toString ); console .log (person.unknown );
查找路径可视化 :
1 2 3 4 5 6 7 8 9 person ├── name: '张三' ✅ (找到,返回) └── __proto__ → Person.prototype ├── sayHello: function ✅ (找到,返回) ├── species: '人类' ✅ (找到,返回) └── __proto__ → Object.prototype ├── toString: function ✅ (找到,返回) ├── valueOf: function └── __proto__ → null ❌ (停止,返回 undefined)
2.3.2 属性写入的特殊规则 重要 :写入属性时,不会修改原型链上的属性,而是在实例自身创建新属性:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 function Person (name ) { this .name = name; } Person .prototype .species = '人类' ;const person1 = new Person ('张三' );const person2 = new Person ('李四' );console .log (person1.species ); console .log (person2.species ); person1.species = '外星人' ; console .log (person1.species ); console .log (person2.species ); console .log (person1.hasOwnProperty ('species' )); console .log (person2.hasOwnProperty ('species' )); console .log (Person .prototype .species );
特殊情况:访问器属性
如果原型上的属性是访问器属性(有 setter),写入时会调用 setter:
1 2 3 4 5 6 7 8 9 10 11 12 const parent = { _value : 0 , get value () { return this ._value ; }, set value (v ) { console .log ('设置值' ); this ._value = v; } }; const child = Object .create (parent);child.value = 10 ;
2.3.3 方法共享的优势 将方法定义在原型上可以节省内存:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 function Person1 (name ) { this .name = name; this .sayHello = function ( ) { console .log ('你好' ); }; } const p1 = new Person1 ('张三' );const p2 = new Person1 ('李四' );console .log (p1.sayHello === p2.sayHello ); function Person2 (name ) { this .name = name; } Person2 .prototype .sayHello = function ( ) { console .log ('你好' ); }; const p3 = new Person2 ('张三' );const p4 = new Person2 ('李四' );console .log (p3.sayHello === p4.sayHello );
内存对比 :
1000 个实例,每个实例有 10 个方法
方法在构造函数中:1000 × 10 = 10,000 个函数对象
方法在原型上:10 个函数对象(所有实例共享)
2.4 判断关系 JavaScript 提供了多种方法来判断对象之间的关系。
2.4.1 hasOwnProperty() - 判断自有属性 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 function Person (name ) { this .name = name; } Person .prototype .species = '人类' ;const person = new Person ('张三' );console .log (person.hasOwnProperty ('name' )); console .log (person.hasOwnProperty ('species' )); console .log (person.hasOwnProperty ('toString' )); const cleanObj = Object .create (null );cleanObj.name = 'test' ; Object .prototype .hasOwnProperty .call (cleanObj, 'name' ); Object .hasOwn (cleanObj, 'name' );
2.4.2 in 操作符 - 判断属性是否存在 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 function Person (name ) { this .name = name; } Person .prototype .species = '人类' ;const person = new Person ('张三' );console .log ('name' in person); console .log ('species' in person); console .log ('toString' in person); console .log ('unknown' in person); function getOwnProperties (obj ) { const own = []; for (let key in obj) { if (obj.hasOwnProperty (key)) { own.push (key); } } return own; }
2.4.3 instanceof - 判断实例关系 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 function Person (name ) { this .name = name; } function Student (name, grade ) { Person .call (this , name); this .grade = grade; } Student .prototype = Object .create (Person .prototype );Student .prototype .constructor = Student ;const student = new Student ('张三' , 3 );console .log (student instanceof Student ); console .log (student instanceof Person ); console .log (student instanceof Object ); function myInstanceof (obj, Constructor ) { let proto = Object .getPrototypeOf (obj); const prototype = Constructor .prototype ; while (proto !== null ) { if (proto === prototype) { return true ; } proto = Object .getPrototypeOf (proto); } return false ; }
instanceof 的陷阱 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 console .log ('string' instanceof String ); console .log (new String ('string' ) instanceof String ); const iframe = document .createElement ('iframe' );document .body .appendChild (iframe);const iframeArray = iframe.contentWindow .Array ;const arr = new iframeArray ();console .log (arr instanceof Array ); console .log (Array .isArray (arr)); function Person ( ) {}const person = new Person ();Person .prototype = {}; console .log (person instanceof Person );
2.4.4 isPrototypeOf() - 判断原型关系 1 2 3 4 5 6 7 8 9 10 11 12 13 function Person (name ) { this .name = name; } const person = new Person ('张三' );console .log (Person .prototype .isPrototypeOf (person)); console .log (Object .prototype .isPrototypeOf (person)); const proto = { x : 1 };const obj = Object .create (proto);console .log (proto.isPrototypeOf (obj));
isPrototypeOf vs instanceof :
1 2 3 4 5 6 7 8 9 10 const proto = { x : 1 };const obj = Object .create (proto);console .log (proto.isPrototypeOf (obj)); function Person ( ) {}const person = new Person ();console .log (person instanceof Person );
3. 内建对象与原型 理解 JavaScript 内建对象的原型链结构,有助于理解整个语言的设计。
3.1 内建构造器的原型链 所有内建构造器(Object、Array、Function 等)本质上都是函数,它们都有自己的 prototype 属性。
3.1.1 数组的原型链 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 const arr = [1 , 2 , 3 ];console .log (arr.__proto__ === Array .prototype ); console .log (Array .prototype .__proto__ === Object .prototype ); console .log (Object .prototype .__proto__ === null ); console .log (arr.push === Array .prototype .push ); console .log (arr.map === Array .prototype .map ); console .log (arr.toString === Object .prototype .toString ); console .log (arr.hasOwnProperty === Object .prototype .hasOwnProperty );
可视化 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 arr [1, 2, 3] ├── 0: 1 ├── 1: 2 ├── 2: 3 ├── length: 3 └── __proto__ → Array.prototype ├── push: function ├── pop: function ├── map: function ├── filter: function └── __proto__ → Object.prototype ├── toString: function ├── hasOwnProperty: function └── __proto__ → null
3.1.2 函数的原型链 1 2 3 4 5 6 7 8 9 10 11 12 13 14 function fn ( ) {}console .log (fn.__proto__ === Function .prototype ); console .log (Function .prototype .__proto__ === Object .prototype ); console .log (typeof Function .prototype ); console .log (Function .prototype .__proto__ === Object .prototype ); console .log (fn.call === Function .prototype .call ); console .log (fn.apply === Function .prototype .apply ); console .log (fn.bind === Function .prototype .bind );
有趣的事实 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 function Person ( ) {}const Arrow = ( ) => {};class MyClass {}console .log (Person .__proto__ === Function .prototype ); console .log (Arrow .__proto__ === Function .prototype ); console .log (MyClass .__proto__ === Function .prototype ); console .log (typeof Function ); console .log (typeof Object ); console .log (Function .__proto__ === Function .prototype ); console .log (Object .__proto__ === Function .prototype );
3.1.3 其他内建对象的原型链 1 2 3 4 5 6 7 8 9 10 11 12 13 14 const date = new Date ();const regex = /test/ ;const map = new Map ();const set = new Set ();
3.2 修改内建原型(猴子补丁) 修改内建对象的原型被称为”猴子补丁”(Monkey Patching),虽然可以实现功能扩展,但需要非常谨慎。
3.2.1 为什么需要谨慎? 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 Array .prototype .last = function ( ) { return this [this .length - 1 ]; }; const arr = [1 , 2 , 3 ];console .log (arr.last ()); for (let key in arr) { console .log (key); }
3.2.2 安全的做法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 function arrayLast (arr ) { return arr[arr.length - 1 ]; } console .log (arrayLast ([1 , 2 , 3 ])); const last = Symbol ('last' );Array .prototype [last] = function ( ) { return this [this .length - 1 ]; }; const arr = [1 , 2 , 3 ];console .log (arr[last]()); if (!Array .prototype .last ) { Array .prototype .last = function ( ) { return this [this .length - 1 ]; }; } class MyArray extends Array { last ( ) { return this [this .length - 1 ]; } } const myArr = new MyArray (1 , 2 , 3 );console .log (myArr.last ());
3.2.3 实际应用场景 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 if (!Array .prototype .includes ) { Array .prototype .includes = function (searchElement, fromIndex ) { const O = Object (this ); const len = parseInt (O.length ) || 0 ; if (len === 0 ) return false ; const n = parseInt (fromIndex) || 0 ; let k = n >= 0 ? n : Math .max (len + n, 0 ); function sameValueZero (x, y ) { return x === y || (typeof x === 'number' && typeof y === 'number' && isNaN (x) && isNaN (y)); } for (; k < len; k++) { if (sameValueZero (O[k], searchElement)) { return true ; } } return false ; }; }
4. 继承方式详解 JavaScript 有多种实现继承的方式,每种都有其适用场景和优缺点。
4.1 原型式继承(基于对象) 这是最简单的继承方式,直接以某个对象为原型创建新对象。
4.1.1 基本用法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 const parent = { name : '父对象' , sayHello ( ) { console .log (`你好,我是${this .name} ` ); }, hobbies : ['读书' , '运动' ] }; const child = Object .create (parent);child.name = '子对象' ; child.sayHello (); console .log (child.hobbies );
4.1.2 共享引用属性的问题 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 const parent = { hobbies : ['读书' , '运动' ] }; const child1 = Object .create (parent);const child2 = Object .create (parent);child1.hobbies .push ('编程' ); console .log (child1.hobbies ); console .log (child2.hobbies ); console .log (parent.hobbies ); child1.hobbies = [...child1.hobbies , '编程' ]; child1.hobbies = child1.hobbies .slice (); child1.hobbies .push ('编程' );
4.1.3 适用场景
快速创建相似对象
不需要构造函数的简单场景
对象组合而非类继承
1 2 3 4 5 6 7 8 9 10 11 12 const defaultConfig = { apiUrl : 'https://api.example.com' , timeout : 5000 , retries : 3 }; const devConfig = Object .create (defaultConfig);devConfig.apiUrl = 'https://dev-api.example.com' ; const prodConfig = Object .create (defaultConfig);
4.2 构造函数继承(借用构造函数) 通过在子类型构造函数中调用父类型构造函数来实现继承。
4.2.1 基本实现 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 function Parent (name ) { this .name = name; this .hobbies = ['读书' , '运动' ]; } function Child (name, age ) { Parent .call (this , name); this .age = age; } const child1 = new Child ('张三' , 20 );const child2 = new Child ('李四' , 25 );child1.hobbies .push ('编程' ); console .log (child1.hobbies ); console .log (child2.hobbies );
4.2.2 优点和缺点 优点 :
✅ 每个实例都有独立的属性副本(解决引用类型共享问题)
✅ 可以向父构造函数传递参数
缺点 :
❌ 无法继承父原型上的方法
❌ 方法无法复用(每个实例都要重新创建)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 function Parent (name ) { this .name = name; } Parent .prototype .sayHello = function ( ) { console .log (`你好,我是${this .name} ` ); }; function Child (name, age ) { Parent .call (this , name); this .age = age; } const child = new Child ('张三' , 20 );
4.3 组合继承(构造函数继承 + 原型继承) 组合继承结合了构造函数继承和原型继承的优点,是最常用的 ES5 继承方式。
4.3.1 实现步骤 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 function Parent (name ) { this .name = name; this .hobbies = ['读书' , '运动' ]; } Parent .prototype .sayHello = function ( ) { console .log (`你好,我是${this .name} ` ); }; function Child (name, age ) { Parent .call (this , name); this .age = age; } Child .prototype = Object .create (Parent .prototype );Child .prototype .constructor = Child ;Child .prototype .sayAge = function ( ) { console .log (`我${this .age} 岁` ); }; const child1 = new Child ('张三' , 20 );const child2 = new Child ('李四' , 25 );child1.sayHello (); child1.sayAge (); child1.hobbies .push ('编程' ); console .log (child1.hobbies ); console .log (child2.hobbies );
4.3.2 存在的问题 问题:父构造函数被调用了两次
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 function Parent (name ) { this .name = name; console .log ('Parent 构造函数被调用' ); } function Child (name, age ) { Parent .call (this , name); this .age = age; } Child .prototype = new Parent (); Child .prototype = Object .create (Parent .prototype ); const child = new Child ('张三' , 20 );
旧写法的问题 :
1 2 3 4 5 6 7 Child .prototype = new Parent (); Child .prototype = Object .create (Parent .prototype );
4.3.3 完整示例 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 function Animal (name ) { this .name = name; this .species = '动物' ; } Animal .prototype .eat = function ( ) { console .log (`${this .name} 在吃东西` ); }; Animal .prototype .sleep = function ( ) { console .log (`${this .name} 在睡觉` ); }; function Dog (name, breed ) { Animal .call (this , name); this .breed = breed; } Dog .prototype = Object .create (Animal .prototype );Dog .prototype .constructor = Dog ;Dog .prototype .bark = function ( ) { console .log (`${this .name} 在叫:汪汪!` ); }; const dog = new Dog ('旺财' , '金毛' );dog.eat (); dog.bark ();
4.4 寄生组合式继承(推荐的 ES5 写法) 这是组合继承的优化版本,解决了父构造函数被调用两次的问题。
4.4.1 核心实现 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 function inherit (Sub, Sup ) { Sub .prototype = Object .create (Sup .prototype ); Sub .prototype .constructor = Sub ; } function Parent (name ) { this .name = name; console .log ('Parent 被调用' ); } Parent .prototype .sayHello = function ( ) { console .log (`你好,我是${this .name} ` ); }; function Child (name, age ) { Parent .call (this , name); this .age = age; } inherit (Child , Parent ); Child .prototype .sayAge = function ( ) { console .log (`我${this .age} 岁` ); }; const child = new Child ('张三' , 20 );child.sayHello (); child.sayAge ();
4.4.2 为什么这是最佳实践?
只调用一次父构造函数 :性能更好
原型链正确 :子类可以访问父类方法
实例属性独立 :每个实例有自己的属性副本
可以传递参数 :支持向父构造函数传参
4.4.3 完整模板 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 function inherit (Sub, Sup ) { Sub .prototype = Object .create (Sup .prototype ); Sub .prototype .constructor = Sub ; } function Animal (name ) { this .name = name; } Animal .prototype .say = function ( ) { return `I am ${this .name} ` ; }; function Dog (name, color ) { Animal .call (this , name); this .color = color; } inherit (Dog , Animal ); Dog .prototype .bark = function ( ) { return 'woof' ; }; const dog = new Dog ('旺财' , '黄色' );console .log (dog.say ()); console .log (dog.bark ());
4.5 ES6 类与 extends ES6 的 class 和 extends 是语法糖,本质仍然是构造函数和原型链,但语法更清晰。
4.5.1 基本语法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 class Animal { constructor (name ) { this .name = name; } say ( ) { return `I am ${this .name} ` ; } static kind ( ) { return 'animal' ; } } class Dog extends Animal { constructor (name, color ) { super (name); this .color = color; } bark ( ) { return 'woof' ; } say ( ) { return `${super .say()} , and I am a dog` ; } static kind ( ) { return 'dog' ; } } const dog = new Dog ('旺财' , '黄色' );console .log (dog.say ()); console .log (dog.bark ()); console .log (Dog .kind ()); console .log (Animal .kind ());
4.5.2 extends 建立的原型链 extends 会自动建立两条原型链:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 class Parent {}class Child extends Parent {}const child = new Child ();console .log (child.__proto__ === Child .prototype ); console .log (Child .prototype .__proto__ === Parent .prototype ); console .log (Child .__proto__ === Parent ); Parent .staticMethod = function ( ) { return 'static' ; };console .log (Child .staticMethod ());
可视化 :
1 2 3 4 5 6 7 8 9 10 Child (构造函数) └── __proto__ → Parent (构造函数) └── staticMethod Child.prototype (原型对象) └── __proto__ → Parent.prototype └── instanceMethod child (实例) └── __proto__ → Child.prototype
4.5.3 super 关键字详解 super 在不同上下文中有不同的含义:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 class Parent { constructor (name ) { this .name = name; } sayHello ( ) { return `Hello, I'm ${this .name} ` ; } static staticMethod ( ) { return 'Parent static' ; } } class Child extends Parent { constructor (name, age ) { super (name); this .age = age; } sayHello ( ) { return `${super .sayHello()} , age ${this .age} ` ; } static staticMethod ( ) { return `${super .staticMethod()} - Child` ; } }
super 的规则 :
在派生类构造函数中必须先调用 super()
1 2 3 4 5 6 7 class Child extends Parent { constructor ( ) { super (); this .name = 'test' ; } }
super 不是变量,是关键字
1 2 3 4 5 const superAlias = super ;super .method ();
4.5.4 类字段(Class Fields) ES2022 引入了类字段语法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 class Person { name = '默认名称' ; age = 0 ; #privateField = '私有' ; static count = 0 ; constructor (name, age ) { this .name = name; this .age = age; Person .count ++; } getPrivate ( ) { return this .#privateField; } } const person = new Person ('张三' , 25 );console .log (person.name ); console .log (Person .count ); console .log (person.getPrivate ());
4.5.5 class 的本质 class 是语法糖,可以转换为 ES5 代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 class Person { constructor (name ) { this .name = name; } sayHello ( ) { console .log (`Hello, ${this .name} ` ); } static create (name ) { return new Person (name); } } function Person (name ) { this .name = name; } Person .prototype .sayHello = function ( ) { console .log (`Hello, ${this .name} ` ); }; Person .create = function (name ) { return new Person (name); };
但 class 有一些特殊之处 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 class Person {}Person (); const p = new Person (); class Person {}class Person { constructor ( ) { undeclaredVar = 1 ; } }
4.6 super 的含义(详细补充) 4.6.1 super 在构造函数中 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 class Parent { constructor (name ) { this .name = name; console .log ('Parent 构造函数' ); } } class Child extends Parent { constructor (name, age ) { super (name); this .age = age; console .log ('Child 构造函数' ); } } const child = new Child ('张三' , 20 );
4.6.2 super 在实例方法中 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 class Parent { getName ( ) { return this .name ; } } class Child extends Parent { getName ( ) { return `Child: ${super .getName()} ` ; } } const child = new Child ();child.name = '张三' ; console .log (child.getName ());
4.6.3 super 在静态方法中 1 2 3 4 5 6 7 8 9 10 11 12 13 14 class Parent { static getType ( ) { return 'Parent' ; } } class Child extends Parent { static getType ( ) { return `Child of ${super .getType()} ` ; } } console .log (Child .getType ());
4.7 混入(Mixin) 混入是一种组合模式,通过对象复制来扩展功能,不改变原型链。
4.7.1 基本混入 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 const Flyable = { fly ( ) { console .log (`${this .name} 在飞` ); } }; const Singable = { sing ( ) { console .log (`${this .name} 在唱歌` ); } }; class Bird { constructor (name ) { this .name = name; } } Object .assign (Bird .prototype , Flyable , Singable );const bird = new Bird ('小鸟' );bird.fly (); bird.sing ();
4.7.2 混入函数 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 function mixin (target, ...sources ) { Object .assign (target, ...sources); } function mixinAdvanced (target, source ) { Object .getOwnPropertyNames (source).forEach (name => { const descriptor = Object .getOwnPropertyDescriptor (source, name); Object .defineProperty (target, name, descriptor); }); } class Dog { constructor (name ) { this .name = name; } } mixin (Dog .prototype , Flyable , Singable );
4.7.3 混入的优势
✅ 不改变原型链(横向扩展)
✅ 可以组合多个功能
✅ 灵活,可以动态添加
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 const Movable = { move (x, y ) { this .x = x; this .y = y; console .log (`移动到 (${x} , ${y} )` ); } }; const Attackable = { attack (target ) { console .log (`${this .name} 攻击${target.name} ` ); } }; class Character { constructor (name ) { this .name = name; } } class Warrior extends Character {}class Mage extends Character {}Object .assign (Warrior .prototype , Movable , Attackable );Object .assign (Mage .prototype , Movable );const warrior = new Warrior ('战士' );warrior.move (10 , 20 ); warrior.attack ({ name : '敌人' }); const mage = new Mage ('法师' );mage.move (5 , 15 );
5. 常见坑与排错 5.1 误用 proto 问题描述 1 2 3 4 5 6 7 8 const obj = {};obj.__proto__ = someProto;
解决方案 1 2 3 4 5 6 7 8 const obj = Object .create (someProto);Object .setPrototypeOf (obj, someProto);const proto = Object .getPrototypeOf (obj);
5.2 共享引用属性 问题描述 1 2 3 4 5 6 7 8 9 10 11 function Person (name ) { this .name = name; } Person .prototype .hobbies = ['读书' ]; const p1 = new Person ('张三' );const p2 = new Person ('李四' );p1.hobbies .push ('运动' ); console .log (p1.hobbies ); console .log (p2.hobbies );
解决方案 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 function Person (name ) { this .name = name; this .hobbies = ['读书' ]; } Object .defineProperty (Person .prototype , 'defaultHobbies' , { value : ['读书' ], writable : false , enumerable : true }); Person .prototype .addHobby = function (hobby ) { if (!this .hobbies ) { this .hobbies = []; } this .hobbies .push (hobby); };
5.3 忘记重设 constructor 问题描述 1 2 3 4 5 6 7 8 function Parent ( ) {}function Child ( ) {}Child .prototype = Object .create (Parent .prototype );const child = new Child ();console .log (child.constructor );
解决方案 1 2 3 4 5 6 7 8 function Parent ( ) {}function Child ( ) {}Child .prototype = Object .create (Parent .prototype );Child .prototype .constructor = Child ; const child = new Child ();console .log (child.constructor );
5.4 super 调用顺序 问题描述 1 2 3 4 5 6 class Child extends Parent { constructor (name, age ) { this .age = age; super (name); } }
解决方案 1 2 3 4 5 6 class Child extends Parent { constructor (name, age ) { super (name); this .age = age; } }
5.5 修改内建原型 问题描述 1 2 3 4 5 6 7 8 9 Array .prototype .last = function ( ) { return this [this .length - 1 ]; };
解决方案 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 function arrayLast (arr ) { return arr[arr.length - 1 ]; } const last = Symbol ('last' );Array .prototype [last] = function ( ) { return this [this .length - 1 ]; }; if (!Array .prototype .last ) { Array .prototype .last = function ( ) { return this [this .length - 1 ]; }; } class MyArray extends Array { last ( ) { return this [this .length - 1 ]; } }
5.6 instanceof 与多环境 问题描述 1 2 3 4 5 6 7 const iframe = document .createElement ('iframe' );document .body .appendChild (iframe);const iframeArray = iframe.contentWindow .Array ;const arr = new iframeArray ();console .log (arr instanceof Array );
解决方案 1 2 3 4 5 6 7 8 9 10 11 12 13 14 console .log (Array .isArray (arr)); function isArray (obj ) { return Object .prototype .toString .call (obj) === '[object Array]' ; } function isArrayLike (obj ) { return obj != null && typeof obj.length === 'number' && typeof obj[Symbol .iterator ] === 'function' ; }
6. 代码片段(直接可用) 6.1 属性描述符与只读属性 1 2 3 4 5 6 7 8 9 10 11 const user = {};Object .defineProperty (user, 'id' , { value : 1 , writable : false , enumerable : true , configurable : false }); user.id = 2 ; delete user.id ;
6.2 寄生组合继承模板 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 function inherit (Sub, Sup ) { Sub .prototype = Object .create (Sup .prototype ); Sub .prototype .constructor = Sub ; } function Animal (name ) { this .name = name; } Animal .prototype .say = function ( ) { return `I am ${this .name} ` ; }; function Dog (name, color ) { Animal .call (this , name); this .color = color; } inherit (Dog , Animal ); Dog .prototype .bark = function ( ) { return 'woof' ; }; const dog = new Dog ('旺财' , '黄色' );console .log (dog.say ()); console .log (dog.bark ());
6.3 ES6 类继承示例 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 class Animal { constructor (name ) { this .name = name; } say ( ) { return `I am ${this .name} ` ; } static kind ( ) { return 'animal' ; } } class Dog extends Animal { constructor (name, color ) { super (name); this .color = color; } bark ( ) { return 'woof' ; } static kind ( ) { return 'dog' ; } } const dog = new Dog ('旺财' , '黄色' );console .log (dog.say ()); console .log (dog.bark ()); console .log (Dog .kind ());
6.4 混入示例 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 const Flyable = { fly ( ) { console .log ('fly' ); } }; const Singable = { sing ( ) { console .log ('sing' ); } }; class Bird { constructor (name ) { this .name = name; } } Object .assign (Bird .prototype , Flyable , Singable );const bird = new Bird ('小鸟' );bird.fly (); bird.sing ();
6.5 深冻结函数 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 function deepFreeze (obj ) { Object .getOwnPropertyNames (obj).forEach (name => { const value = obj[name]; if (value && typeof value === 'object' ) { deepFreeze (value); } }); return Object .freeze (obj); } const obj = { name : '张三' , address : { city : '北京' , street : '某街道' } }; deepFreeze (obj);obj.name = '李四' ; obj.address .city = '上海' ;
6.6 安全的属性检查 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 function hasOwnProperty (obj, prop ) { return Object .prototype .hasOwnProperty .call (obj, prop); } function hasOwn (obj, prop ) { return Object .hasOwn (obj, prop); } const cleanObj = Object .create (null );cleanObj.name = 'test' ; console .log (hasOwnProperty (cleanObj, 'name' )); console .log (hasOwn (cleanObj, 'name' ));
6.7 手动实现 new 操作符 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 function myNew (Constructor, ...args ) { const obj = {}; Object .setPrototypeOf (obj, Constructor .prototype ); const result = Constructor .apply (obj, args); return result instanceof Object ? result : obj; } function Person (name ) { this .name = name; } const person = myNew (Person , '张三' );console .log (person.name );
6.8 手动实现 instanceof 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 function myInstanceof (obj, Constructor ) { if (obj === null || typeof obj !== 'object' ) { return false ; } let proto = Object .getPrototypeOf (obj); const prototype = Constructor .prototype ; while (proto !== null ) { if (proto === prototype) { return true ; } proto = Object .getPrototypeOf (proto); } return false ; } function Person ( ) {}function Student ( ) {}Student .prototype = Object .create (Person .prototype );const student = new Student ();console .log (myInstanceof (student, Student )); console .log (myInstanceof (student, Person )); console .log (myInstanceof (student, Object ));
7. 实战应用场景 7.1 实现一个简单的类库系统 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 class Component { constructor (name ) { this .name = name; this .children = []; } add (child ) { this .children .push (child); return this ; } render ( ) { return `<${this .name} >${this .children.map(c => c.render()).join('' )} </${this .name} >` ; } } class TextNode { constructor (text ) { this .text = text; } render ( ) { return this .text ; } } const div = new Component ('div' );div.add (new TextNode ('Hello' )); div.add (new Component ('span' ).add (new TextNode ('World' ))); console .log (div.render ());
7.2 实现事件系统 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 class EventEmitter { constructor ( ) { this .events = {}; } on (event, handler ) { if (!this .events [event]) { this .events [event] = []; } this .events [event].push (handler); } emit (event, ...args ) { if (this .events [event]) { this .events [event].forEach (handler => handler (...args)); } } off (event, handler ) { if (this .events [event]) { this .events [event] = this .events [event].filter (h => h !== handler); } } } class User extends EventEmitter { login ( ) { console .log ('用户登录' ); this .emit ('login' , { time : new Date () }); } } const user = new User ();user.on ('login' , (data ) => { console .log ('登录事件触发' , data); }); user.login ();
7.3 实现插件系统 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 class Plugin { constructor (name ) { this .name = name; } install (app ) { } } class App { constructor ( ) { this .plugins = []; } use (plugin ) { if (plugin instanceof Plugin ) { this .plugins .push (plugin); plugin.install (this ); } } run ( ) { console .log ('应用运行中...' ); } } class LoggerPlugin extends Plugin { install (app ) { const originalRun = app.run .bind (app); app.run = function ( ) { console .log (`[${this .name} ] 开始运行` ); originalRun (); }; } } const app = new App ();app.use (new LoggerPlugin ('Logger' )); app.run ();
8. 速记与总结 8.1 核心概念速记
“读属性先看自己,再爬链;找不到就是 undefined。”
读取属性时,先在对象自身查找,找不到则沿原型链向上查找,直到 null。
“构造四步:建对象、连原型、绑 this、看返回。”
new 操作符的四个步骤:创建空对象、连接原型、绑定 this 执行构造函数、返回结果。
“共享要小心:引用属性放构造,方法放原型。”
引用类型属性(数组、对象)应放在构造函数中,避免共享;方法应放在原型上,节省内存。
“extends 仍是原型链 + super 初始化 this。”
ES6 的 class 和 extends 本质仍是原型链,super() 用于初始化 this。
“别乱改内建原型,想复用用 mixin/工具函数。”
修改内建原型(如 Array.prototype)风险大,应使用工具函数或混入模式。
8.2 继承方式选择指南
场景
推荐方式
原因
ES6+ 项目
class + extends
语法清晰,自动处理细节
ES5 项目
寄生组合继承
性能好,只调用一次父构造
简单对象继承
Object.create()
快速,适合配置对象等
功能组合
Mixin
灵活,不改变原型链
需要多继承
Mixin 组合
JavaScript 不支持多继承,用 Mixin 模拟
8.3 常见错误检查清单
8.4 性能优化建议
方法放在原型上 :避免每个实例都创建新函数
避免深度原型链 :过深的原型链会影响查找性能
使用 Object.create(null) 创建纯净对象 :没有原型链,查找更快
避免频繁修改原型 :会影响引擎优化
使用类字段初始化 :比在构造函数中赋值稍快
8.5 学习路径建议
基础 :理解对象字面量、属性描述符
进阶 :掌握构造函数、原型、原型链
高级 :理解各种继承方式、ES6 类的本质
实战 :在项目中应用,理解设计模式
结语 :JavaScript 的原型系统虽然复杂,但一旦理解其本质,就能灵活运用。记住,class 只是语法糖,底层仍然是原型链。多实践,多思考,才能真正掌握。