原型链
原型链
原型链是 JavaScript 中实现继承的主要方式之一。在 JavaScript 中,每个对象都有一个内部属性,称为 Prototype(在大多数现代浏览器中可以通过 __proto__ 属性访问),这个属性引用了另一个对象。这个被引用的对象被称为原对象的“原型”。通过这种方式,对象可以继承和共享属性和方法。
原型链的基本概念
- 原型对象:
- 每个 JavaScript 对象都有一个原型对象,除了
Object.prototype,它是所有对象的最终原型。
- 每个 JavaScript 对象都有一个原型对象,除了
- 构造函数的原型属性:
- 每个函数在创建时,都有一个
prototype属性,它是一个对象,包含可以由通过该构造函数创建的对象继承的属性和方法。
- 每个函数在创建时,都有一个
- 对象的原型访问:
- 可以通过对象的
__proto__属性或者Object.getPrototypeOf(obj)方法访问一个对象的原型。
- 可以通过对象的
- 继承:
- 当尝试访问一个对象的属性或方法时,如果该对象本身没有这个属性或方法,JavaScript 引擎会沿着原型链向上查找,直到找到该属性或方法或者到达原型链的末端。
原型链的工作方式
当访问一个对象的属性时,JavaScript 引擎会首先在该对象自身上查找,如果找不到,就会查找该对象的原型,如果还是找不到,就继续查找原型的原型,这个过程会一直持续到找到属性为止,或者到达原型链的末端(Object.prototype 的原型是 null)。
示例代码
1 | function Person(name) { |
在这个例子中,person1 对象通过构造函数 Person 创建,它拥有自己的属性 name,但是 sayHello 方法是继承自 Person.prototype 的。
原型链的问题
- 性能问题:
- 原型链过长可能会导致性能问题,因为属性查找需要遍历整个链。
- 共享问题:
- 所有对象共享原型上的数据,如果修改了原型上的属性,会影响所有继承自该原型的对象。
- 继承链的不可变性:
- 一旦原型链被创建,就很难改变,这限制了继承结构的灵活性。
原型链是 JavaScript 中实现继承的一种方式,但它并不是唯一的方式。随着 ES6 的引入,类(class)和模块(module)提供了更现代和更灵活的方式来实现继承和代码复用。
- 一旦原型链被创建,就很难改变,这限制了继承结构的灵活性。
几个等式
1 | function Person(name) { |
JavaScript 中的 constructor 属性
在 JavaScript 中,constructor 属性是每个对象都具有的一个属性,它指向创建该对象的构造函数。这个属性在对象的原型上定义,因此所有通过同一个构造函数创建的对象都会共享同一个 constructor 属性。
1. constructor 属性的基本用法
每个对象都有一个 constructor 属性,该属性是一个对函数的引用,指向创建该对象的构造函数。
1 | const obj = new Object(); |
2. 自定义构造函数的 constructor 属性
当你定义自己的构造函数时,通过该构造函数创建的对象也会有一个 constructor 属性,指向该构造函数。
1 | function Person(name, age) { |
3. 修改原型链时的 constructor 属性
当你修改对象的原型时,constructor 属性可能会被覆盖。为了保持正确的 constructor 引用,你可能需要手动修复它。
1 | function Person(name, age) { |
4. 使用 constructor 属性创建新对象
constructor 属性可以用于创建新对象,特别是当你不确定对象的类型时,可以动态地创建相同类型的新对象。
1 | function Person(name, age) { |
5. constructor 属性在继承中的使用
在继承中,constructor 属性可以帮助你理解对象的构造层次结构。
1 | function Animal(name) { |
总结
constructor 属性是一个指向创建对象的构造函数的引用,它在对象的原型上定义。通过 constructor 属性,可以动态地创建新对象,修复原型链中的构造函数引用,以及在继承中理解对象的层次结构。理解 constructor 属性的用法和行为,可以帮助你更有效地管理和操作 JavaScript 对象。
实例
- Son.prototype=Father.prototype, 所以Father.prototype 被覆盖了
1 | function Father(age){ |
- 直接量没有 prototype,typeof 优先级低
1 | console.log(typeof ''.prototype);//''.prototype undefined |
- 对象a自身具有属性x,属性值为2,同时其原型对象上也有属性x,属性值为1;对象b在初始化时,也是自身具有属性x,属性值为3,同时其原型对象为函数A的实例,同样具有属性x,由于没有传参,其属性x的值为undefined。当使用delete b.x时,对象b自身的x属性被删除,但是其原型对象上的x属性不会被删除。另外,根据对象属性查找的作用域链规则,访问对象属性时,会先查找对象自身的属性,如果不存在,才会继续在其原型对象上进行查找,故a.x的返回结果为2,b.x的返回结果为undefined
1 | function A(x){ |
哪些对象有prototype属性
在JavaScript中,几乎所有函数对象都有prototype属性,但普通对象(即通过字面量或Object.create创建的对象)通常没有prototype属性。下面详细解释这一点。
函数对象的prototype属性
- 函数对象
- 在JavaScript中,函数也是对象,称为函数对象。每个函数对象都有一个
prototype属性。 prototype属性是一个对象,用于定义该函数作为构造函数时,其创建的实例对象的原型。- 例如:
1
2
3
4
5
6
7
8
9function Person(name) {
this.name = name;
}
Person.prototype.sayHello = function() {
console.log('Hello, ' + this.name);
};
const person1 = new Person('Alice');
person1.sayHello(); // 输出: Hello, Alice
console.log(person1.__proto__ === Person.prototype); // true
- 在JavaScript中,函数也是对象,称为函数对象。每个函数对象都有一个
- 内置构造函数
- 内置构造函数(如
Array、Object、Function等)也有prototype属性。 - 例如:
1
2
3console.log(Array.prototype); // Array(0) []
console.log(Object.prototype); // {}
console.log(Function.prototype); // function () {}
- 内置构造函数(如
普通对象的__proto__属性
- 普通对象
- 普通对象(即通过字面量或
Object.create创建的对象)没有prototype属性,但它们有一个内部属性Prototype,可以通过__proto__属性或Object.getPrototypeOf方法访问。 - 例如:
1
2
3
4
5
6const obj = { a: 1 };
console.log(obj.__proto__); // {}
console.log(Object.getPrototypeOf(obj)); // {}
const obj2 = Object.create({ b: 2 });
console.log(obj2.__proto__); // { b: 2 }
console.log(Object.getPrototypeOf(obj2)); // { b: 2 }
- 普通对象(即通过字面量或
- 实例对象
- 通过构造函数创建的实例对象,其
__proto__属性指向构造函数的prototype属性。 - 例如:
1
2
3
4
5
6
7
8function Person(name) {
this.name = name;
}
Person.prototype.sayHello = function() {
console.log('Hello, ' + this.name);
};
const person1 = new Person('Alice');
console.log(person1.__proto__ === Person.prototype); // true
- 通过构造函数创建的实例对象,其
总结
- 函数对象:所有函数对象都有
prototype属性,用于定义该函数作为构造函数时,其创建的实例对象的原型。 - 普通对象:普通对象没有
prototype属性,但有一个内部属性Prototype,可以通过__proto__属性或Object.getPrototypeOf方法访问。 - 实例对象:通过构造函数创建的实例对象,其
__proto__属性指向构造函数的prototype属性。
示例
1 | // 函数对象 |
通过这些示例,可以清楚地看到哪些对象有prototype属性,哪些对象有__proto__属性。