JavaScript prototype 详解
JavaScript 中,prototype(原型) 是面向对象编程的核心概念之一。它通过 原型链(Prototype Chain) 实现继承,使对象可以共享其他对象的属性和方法。理解原型机制是掌握 JavaScript 面向对象编程的关键。
什么是 prototype?
每个 JavaScript 函数(构造函数)都有一个 prototype 属性,它是一个对象。所有由该函数创建的 实例对象 都会继承这个原型对象的属性和方法。- function Person(name) {
- this.name = name;
- }
- // 方法添加到原型,所有实例共享
- Person.prototype.sayHello = function() {
- console.log(`Hello, my name is ${this.name}`);
- };
- const p1 = new Person("Joe");
- const p2 = new Person("Mary");
- p1.sayHello(); // Hello, my name is Joe
- p2.sayHello(); // Hello, my name is Mary
- // 实例的 __proto__ 指向构造函数的 prototype
- console.log(p1.__proto__ === Person.prototype); // true
复制代码 在上面的代码中:
- Person.prototype 是 Person 构造函数的原型对象。
- sayHello 方法被所有 Person 的实例共享,而不是每个实例都创建一份新的拷贝,节省内存。
- p1.__proto__ 指向 Person.prototype,表示 p1 继承了 Person.prototype 上的方法。
- __proto__ 是实例对象的隐式原型引用(非标准属性,可以用 Object.getPrototypeOf() 替代)。
属性__proto__ 与 prototype 关系
JavaScript 中每个对象都有一个隐藏的 __proto__ 属性(这个并非标准属性,虽然大部分浏览器都支持),它指向创建该对象的构造函数的 prototype:- console.log(p1.__proto__ === Person.prototype); // true
- console.log(Person.prototype.__proto__ === Object.prototype); // true
- console.log(Object.prototype.__proto__ === null); // true
复制代码 这个原型链的结构如下:- // 访问对象属性时,若当前对象没有,则沿原型链向上查找。
- // Object.prototype 是原型链的终点,其 __proto__ 为 null。
- p1 → Person.prototype → Object.prototype → null
复制代码 原型链继承
可以通过 prototype 让一个构造函数继承另一个构造函数的方法和属性。
注意,使用 Object.create 创建的子对象不会调用父构造函数,仅用于设置原型:- function Parent(name) {
- this.name = name;
- }
- Parent.prototype.makeSound = function() {
- console.log("Parent are saying.");
- };
- function Child(name, age) {
- Parent.call(this, name); // 继承属性
- this.age = age;
- }
- // 使用 Object.create 创建新的原型对象,让 Child 继承 Parent 的方法
- Child.prototype = Object.create(Parent.prototype);
- // 修正 constructor 指向,否则 Child.prototype.constructor 会指向 Parent
- Child.prototype.constructor = Child;
- Child.prototype.speak = function() {
- console.log("Child is talking.");
- };
- const d = new Child("Child1", 18);
- d.makeSound(); // Parent are saying.
- d.speak(); // Child is talking.
复制代码 步骤分析:
- Object.create(Parent.prototype)
创建一个新对象,其原型指向 Parent.prototype,确保子类原型不污染父类。
- 修复 constructor 指向
若不修复,Child.prototype.constructor 将指向 Parent,导致实例的 constructor 错误。
- 构造函数借用 (Parent.call)
在子类构造函数中调用父类构造函数,初始化实例属性。
ES6 class 语法的 prototype
ES6 的 class 是原型的语法糖,本质仍基于原型链:- class Person {
- constructor(name) {
- this.name = name;
- }
-
- // 方法自动添加到 Person.prototype
- sayHello() {
- console.log(`Hello, my name is ${this.name}`);
- }
- }
- const p = new Person("Tom");
- p.sayHello(); // Hello, my name is Tom
- // 静态方法添加到构造函数本身
- Person.staticMethod = function() {
- console.log("This is a static method.");
- };
- console.log(Object.getPrototypeOf(p) === Person.prototype); // true
复制代码 在这个例子中:
- sayHello 方法实际存储在 Person.prototype。
- static 关键字定义的方法属于构造函数本身,而非原型。
prototype验证
构造函数与原型的关系- // 验证 Object 是 Function 的实例
- console.log(Object instanceof Function); // 输出: true
- // 验证 Function 继承自 Object.prototype
- console.log(Object.getPrototypeOf(Function.prototype) === Object.prototype); // 输出: true
- // 自定义函数和对象
- function A() {}
- const a = new A();
- // 验证自定义函数是 Function 的实例
- console.log(A instanceof Function); // 输出: true
- // 验证自定义对象的原型是自定义函数的 prototype
- console.log(Object.getPrototypeOf(a) === A.prototype); // 输出: true
- // 验证自定义函数的 prototype 的原型是 Object.prototype
- console.log(Object.getPrototypeOf(A.prototype) === Object.prototype); // 输出: true
- // 原型链的终点
- console.log(Object.prototype.__proto__); // null
复制代码 prototype图形展示
- // https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Inheritance_and_the_prototype_chain
- +----------------+ constructor +---------------------+
- | Function | <------------ | Function.prototype |
- | (Function 本身) | ------------> | (Function 原型) |
- +----------------+ prototype +---------------------+
- __proto__ ^ |
- | |
- |------------------------------ | __proto__
- | __proto__ |
- | v
- +----------------+ constructor +------------------+ +--------+
- | Function | <---------- | Object.prototype | ---------> | 终点 |
- | (Object 函数) | ----------> | (所有对象的基类) | __proto__ | null |
- +----------------+ prototype +------------------+ +--------+
- ^
- |
- | __proto__
- |
- +----------------+ constructor +----------------------+
- | function Foo() | <--------- | Foo.prototype |
- | (自定义函数) | ----------> | (自定义函数原型对象) |
- +----------------+ prototype +----------------------+
- ^
- |
- |
- +----------------+ |
- | new Foo() | -----------------------|
- | (函数实例对象) | __proto__
- +----------------+
复制代码 实例化对象步骤(new 关键字的执行过程)
实例化 const newFoo = new Foo(); 的步骤- function Foo() {}
- const newFoo = new Foo();
复制代码 1. 创建一个新对象
JavaScript 先创建一个新的空对象 newFoo。2. 设置新对象的原型
newFoo.__proto__ 被设置为 Foo.prototype,即 newFoo 继承了 Foo.prototype 的属性和方法。- newFoo.__proto__ = Foo.prototype;
复制代码 3. 执行构造函数,并绑定 this- const result = Foo.apply(newFoo, arguments);
复制代码 调用 Foo 构造函数,并将 newFoo 作为 this 传入。
若 Foo 显式返回一个对象,则 new 操作符返回该对象;否则返回 newFoo。
4. 返回对象
若构造函数返回对象,则返回该对象。否则返回新创建的 obj。- return typeof result === "object" && result !== null ? result : newFoo;
复制代码 原型链分析
基于上面 const newFoo = new Foo(); 进行分析。
原型链指向- newFoo.__proto__ === Foo.prototype // ✅ `newFoo` 的原型是 `Foo.prototype`
- Foo.prototype.__proto__ === Object.prototype // ✅ `Foo.prototype` 的原型是 `Object.prototype`
- Object.prototype.__proto__ === null // ✅ `Object.prototype` 的原型是 `null`(即原型链的终点)
复制代码 构造器关系- newFoo.constructor === Foo.prototype.constructor // ✅ `newFoo` 的构造函数是 `Foo`
- Foo.prototype.constructor === Foo // ✅ `Foo.prototype` 的 `constructor` 指向 `Foo` 本身
- Foo.prototype.constructor.prototype === Foo.prototype // ✅ `Foo.prototype.constructor` 的 `prototype` 仍然是 `Foo.prototype`
复制代码 说明:
- 当我们创建一个新对象时,它的 constructor 属性通常来源于它的原型(即 Foo.prototype.constructor)。
- 使用 Object.create 或修改原型时,有可能需要手动修正 constructor 指向。
Function 和 Object 互相指向- Foo.prototype.__proto__.constructor.__proto__ === Function.prototype // ✅ `Object` 构造函数的 `__proto__` 指向 `Function.prototype`
- Function.prototype === Object.__proto__ // ✅ `Function.prototype` 就是 `Object` 的 `__proto__`
- Function.prototype.__proto__.__proto__ === null // ✅ `Function.prototype.__proto__` 是 `Object.prototype`,再往上是 `null`
复制代码 构造器和原型链的循环指向- Foo.prototype.constructor.prototype.constructor === Foo // ✅ 循环指向 `Foo`
- Foo.prototype.constructor.prototype.constructor.prototype === Foo.prototype // ✅ 再次循环指向 `Foo.prototype`
- Foo.prototype.constructor === Foo // ✅ `Foo.prototype.constructor` 仍然指向 `Foo`
复制代码 Object 和 Function 之间的关系- Object.prototype.constructor === Object // ✅ `Object.prototype` 的 `constructor` 是 `Object`
- Object.prototype.constructor.__proto__ === Function.prototype // ✅ `Object` 构造函数本身是 `Function` 的一个实例
- Function.constructor.__proto__ === Function.prototype // ✅ `Function` 构造函数的 `__proto__` 也是 `Function.prototype`
- Function.prototype.__proto__ === Object.prototype // ✅ `Function.prototype` 继承自 `Object.prototype`
- Function.__proto__.__proto__ === Object.prototype // ✅ `Function.__proto__` 继承自 `Function.prototype`,最终指向 `Object.prototype`
- Object.prototype.__proto__ === null // ✅ `Object.prototype` 是原型链终点
复制代码 原型使用的注意事项
- 避免直接修改内置原型
如 Array.prototype.myMethod = ... 可能导致兼容性问题。
- 原型属性的共享特性
引用类型(如数组)的属性可能被所有实例意外修改:
- function MyClass() {}
- MyClass.prototype.data = [];
- const a = new MyClass();
- a.data.push(1); // 所有实例的 data 都会变化
复制代码
- 性能优化
将方法定义在原型上,而非构造函数内,减少内存占用。
总结
- 每个 JavaScript 函数 都有一个 prototype 属性(除了箭头函数)。
- prototype 是一个对象,所有由该函数创建的实例都会共享 prototype 上的方法。
- __proto__ 指向该对象的原型(即构造函数的 prototype),形成原型链。
- 通过 Object.create() 进行原型继承,ES6 class 语法是 prototype 的语法糖。
- 原型链终点 为 Object.prototype,其 proto 为 null。
- 创建一个新对象 newFoo
- 设置 newFoo.__proto__ = Foo.prototype
- 执行 Foo 并绑定 this
- 返回 newFoo 或构造函数返回的对象
- Foo.prototype 继承自 Object.prototype
- Object.prototype 是所有对象的原型链终点
- Object 和 Function 互相指向,Object 也是 Function 的一个实例
- Function.prototype.__proto__ === Object.prototype,最终 Function 也继承自 Object
更多链接:
https://github.com/microwind/design-patterns
来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! |