原型链

原型链

原型链

原型链是 JavaScript 中实现继承的主要方式之一。在 JavaScript 中,每个对象都有一个内部属性,称为 Prototype(在大多数现代浏览器中可以通过 __proto__ 属性访问),这个属性引用了另一个对象。这个被引用的对象被称为原对象的“原型”。通过这种方式,对象可以继承和共享属性和方法。

原型链的基本概念

  1. 原型对象
    • 每个 JavaScript 对象都有一个原型对象,除了 Object.prototype,它是所有对象的最终原型。
  2. 构造函数的原型属性
    • 每个函数在创建时,都有一个 prototype 属性,它是一个对象,包含可以由通过该构造函数创建的对象继承的属性和方法。
  3. 对象的原型访问
    • 可以通过对象的 __proto__ 属性或者 Object.getPrototypeOf(obj) 方法访问一个对象的原型。
  4. 继承
    • 当尝试访问一个对象的属性或方法时,如果该对象本身没有这个属性或方法,JavaScript 引擎会沿着原型链向上查找,直到找到该属性或方法或者到达原型链的末端。

原型链的工作方式

当访问一个对象的属性时,JavaScript 引擎会首先在该对象自身上查找,如果找不到,就会查找该对象的原型,如果还是找不到,就继续查找原型的原型,这个过程会一直持续到找到属性为止,或者到达原型链的末端(Object.prototype 的原型是 null)。

示例代码

1
2
3
4
5
6
7
8
9
10
function Person(name) {
this.name = name;
}
Person.prototype.sayHello = function() {
return "Hello, I'm " + this.name;
};
var person1 = new Person("Kimi");
console.log(person1.sayHello()); // "Hello, I'm Kimi"
console.log(person1.hasOwnProperty('name')); // true
console.log(person1.hasOwnProperty('sayHello')); // false

在这个例子中,person1 对象通过构造函数 Person 创建,它拥有自己的属性 name,但是 sayHello 方法是继承自 Person.prototype 的。

原型链的问题

  1. 性能问题
    • 原型链过长可能会导致性能问题,因为属性查找需要遍历整个链。
  2. 共享问题
    • 所有对象共享原型上的数据,如果修改了原型上的属性,会影响所有继承自该原型的对象。
  3. 继承链的不可变性
    • 一旦原型链被创建,就很难改变,这限制了继承结构的灵活性。
      原型链是 JavaScript 中实现继承的一种方式,但它并不是唯一的方式。随着 ES6 的引入,类(class)和模块(module)提供了更现代和更灵活的方式来实现继承和代码复用。

几个等式

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.getName = function(){
console.log(this.name);
}
let obj = new Person("clerk")
console.log(obj.constructor);
console.log(obj.__proto__);
console.log(Person.prototype);
console.log(Object.getPrototypeOf(obj));
console.log(Object.getPrototypeOf(obj) === Person.prototype);
console.log(Object.getPrototypeOf(obj) === obj.__proto__);
console.log(Person.prototype === obj.__proto__);
console.log(obj.constructor === Person);
console.log(obj instanceof Person);
console.log(Person.prototype.isPrototypeOf(obj));

JavaScript 中的 constructor 属性

在 JavaScript 中,constructor 属性是每个对象都具有的一个属性,它指向创建该对象的构造函数。这个属性在对象的原型上定义,因此所有通过同一个构造函数创建的对象都会共享同一个 constructor 属性。

1. constructor 属性的基本用法

每个对象都有一个 constructor 属性,该属性是一个对函数的引用,指向创建该对象的构造函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const obj = new Object();
console.log(obj.constructor === Object); // true
const arr = new Array();
console.log(arr.constructor === Array); // true
const num = new Number(10);
console.log(num.constructor === Number); // true
const str = new String("Hello");
console.log(str.constructor === String); // true
const bool = new Boolean(true);
console.log(bool.constructor === Boolean); // true
const func = new Function();
console.log(func.constructor === Function); // true
const date = new Date();
console.log(date.constructor === Date); // true
const regex = new RegExp("pattern");
console.log(regex.constructor === RegExp); // true
const error = new Error("message");
console.log(error.constructor === Error); // true

2. 自定义构造函数的 constructor 属性

当你定义自己的构造函数时,通过该构造函数创建的对象也会有一个 constructor 属性,指向该构造函数。

1
2
3
4
5
6
7
8
function Person(name, age) {
this.name = name;
this.age = age;
}
const person1 = new Person('Alice', 30);
console.log(person1.constructor === Person); // true
const person2 = new Person('Bob', 25);
console.log(person2.constructor === Person); // true

3. 修改原型链时的 constructor 属性

当你修改对象的原型时,constructor 属性可能会被覆盖。为了保持正确的 constructor 引用,你可能需要手动修复它。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function Person(name, age) {
this.name = name;
this.age = age;
}
// 修改原型
Person.prototype = {
greet: function() {
console.log(`Hello, my name is ${this.name} and I am ${this.age} years old.`);
}
};
const person1 = new Person('Alice', 30);
console.log(person1.constructor === Person); // false
console.log(person1.constructor === Object); // true
// 修复 constructor 属性
Person.prototype.constructor = Person;
const person2 = new Person('Bob', 25);
console.log(person2.constructor === Person); // true

4. 使用 constructor 属性创建新对象

constructor 属性可以用于创建新对象,特别是当你不确定对象的类型时,可以动态地创建相同类型的新对象。

1
2
3
4
5
6
7
8
9
10
11
12
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.greet = function() {
console.log(`Hello, my name is ${this.name} and I am ${this.age} years old.`);
};
const person1 = new Person('Alice', 30);
const person2 = new person1.constructor('Bob', 25);
console.log(person2.name); // Bob
console.log(person2.age); // 25
person2.greet(); // Hello, my name is Bob and I am 25 years old.

5. constructor 属性在继承中的使用

在继承中,constructor 属性可以帮助你理解对象的构造层次结构。

1
2
3
4
5
6
7
8
9
10
11
12
13
function Animal(name) {
this.name = name;
}
function Dog(name, breed) {
Animal.call(this, name);
this.breed = breed;
}
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog;
const myDog = new Dog('Buddy', 'Golden Retriever');
console.log(myDog.constructor === Dog); // true
console.log(myDog instanceof Dog); // true
console.log(myDog instanceof Animal); // true

总结

constructor 属性是一个指向创建对象的构造函数的引用,它在对象的原型上定义。通过 constructor 属性,可以动态地创建新对象,修复原型链中的构造函数引用,以及在继承中理解对象的层次结构。理解 constructor 属性的用法和行为,可以帮助你更有效地管理和操作 JavaScript 对象。

实例

  • Son.prototype=Father.prototype, 所以Father.prototype 被覆盖了
1
2
3
4
5
6
7
8
9
10
11
12
13
function Father(age){
this.age = age
}
function Son(age){
Father.call(this);
}
Son.prototype = Father.prototype;
Father.prototype.getAge = function(){console.log(40);}
Son.prototype.getAge = function(){console.log(18);}
var father = new Father(40);
var son = new Son(18);
son.getAge();
father.getAge();
  • 直接量没有 prototype,typeof 优先级低
1
2
3
4
console.log(typeof ''.prototype);//''.prototype undefined
console.log(typeof ''.__proto__); //object
console.log(typeof ''.__proto__ === typeof ''.prototype);
console.log(typeof ''.__proto__ === typeof ''.prototype); //false
  • 对象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
2
3
4
5
6
7
8
9
10
function A(x){
this.x = x;
}
A.prototype.x = 1;
function B(x){
this.x = x;
}
B.prototype = new A();
var a = new A(2), b = new B(3);
delete b.x;

哪些对象有prototype属性

在JavaScript中,几乎所有函数对象都有prototype属性,但普通对象(即通过字面量或Object.create创建的对象)通常没有prototype属性。下面详细解释这一点。

函数对象的prototype属性

  1. 函数对象
    • 在JavaScript中,函数也是对象,称为函数对象。每个函数对象都有一个prototype属性。
    • prototype属性是一个对象,用于定义该函数作为构造函数时,其创建的实例对象的原型。
    • 例如:
      1
      2
      3
      4
      5
      6
      7
      8
      9
      function 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
  2. 内置构造函数
    • 内置构造函数(如ArrayObjectFunction等)也有prototype属性。
    • 例如:
      1
      2
      3
      console.log(Array.prototype); // Array(0) []
      console.log(Object.prototype); // {}
      console.log(Function.prototype); // function () {}

普通对象的__proto__属性

  1. 普通对象
    • 普通对象(即通过字面量或Object.create创建的对象)没有prototype属性,但它们有一个内部属性Prototype,可以通过__proto__属性或Object.getPrototypeOf方法访问。
    • 例如:
      1
      2
      3
      4
      5
      6
      const 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 }
  2. 实例对象
    • 通过构造函数创建的实例对象,其__proto__属性指向构造函数的prototype属性。
    • 例如:
      1
      2
      3
      4
      5
      6
      7
      8
      function 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
2
3
4
5
6
7
8
9
10
11
12
13
14
// 函数对象
function MyFunction() {}
console.log(MyFunction.prototype); // {}
// 内置构造函数
console.log(Array.prototype); // Array(0) []
console.log(Object.prototype); // {}
console.log(Function.prototype); // function () {}
// 普通对象
const obj = { a: 1 };
console.log(obj.__proto__); // {}
console.log(Object.getPrototypeOf(obj)); // {}
// 实例对象
const person = new MyFunction();
console.log(person.__proto__ === MyFunction.prototype); // true

通过这些示例,可以清楚地看到哪些对象有prototype属性,哪些对象有__proto__属性。