前言

参考文章: https://juejin.cn/post/7007416743215759373

1. 显式原型和隐式原型

  • prototype: 显式原型

    • 是构造函数的一个属性, 用于定义实例共享的方法和属性
    • 它指向原型对象,该对象上的属性和方法会被实例继承
  • __proto__: 隐式原型

    • 是每个对象都有的内部属性
    • 它指向创建该对象的构造函数的prototype,用于建立原型链
  • 构造函数的prototype和其实例的__proto__指向同一个地方,这个地方叫做原型对象

  • 原型对象是实现继承的核心

function Person(name, age) { // 构造函数
this.name = name;
this.age = age;
}

const person1 = new Person('小明', 20); // 构造函数的实例
const person2 = new Person('小红', 30);

console.log(Person.prototype === person1.__proto__); // true
console.log(Person.prototype === person2.__proto__); // true

什么是构造函数

  • 可构造函数是可以用new操作符调用的函数,用于创建对象
  • 箭头函数不能作为构造函数
function Person(name, age) { // 这个就是构造函数
this.name = name
this.age = age
}

const person1 = new Person('小明', 20) // 这个是Person构造函数的实例
const person2 = new Person('小红', 30) // 这个也是Person构造函数的实例
  • 构造函数的prototype定义实例可以继承的方法
  • 实例的__proto__指向构造函数的prototype
function Person(name, age) {
this.name = name;
this.age = age;
}

Person.prototype.sayName = function() { // 在原型对象上定义方法
console.log(this.name);
};

const person1 = new Person('小明', 20);
const person2 = new Person('小红', 30);

// 验证 prototype 和 __proto__
console.log(Person.prototype); // { sayName: [Function] }
console.log(person1.__proto__); // { sayName: [Function] }
console.log(Person.prototype === person1.__proto__); // true
console.log(Person.prototype === person2.__proto__); // true

// 调用原型方法
person1.sayName(); // 小明
person2.sayName(); // 小红

2. 构造函数与对象的关系

  • 构造函数本质上是函数,用来创建对象。定义构造函数有以下几种常见方式
// 函数声明
function fn1(name, age) {
console.log(`我是${name}, 我今年${age}岁`);
}
fn1('abc', 10); // 输出:我是abc, 我今年10岁

// 函数表达式
const fn2 = function(name, age) {
console.log(`我是${name}, 我今年${age}岁`);
};
fn2('abc', 10); // 输出:我是abc, 我今年10岁

// 箭头函数
const arrowFn = (name, age) => {
console.log(`我是${name}, 我今年${age}岁`);
};
arrowFn('abc', 10); // 输出:我是abc, 我今年10岁

构造函数与原型的关系

  • 每个构造函数的prototype和其实例的__proto__都指向同一个原型对象
  • 同时,所有函数本身都是Function构造函数的实例
function fn1(name, age) {
console.log(`我是${name}, 我今年${age}岁`);
}
const fn2 = function(name, age) {
console.log(`我是${name}, 我今年${age}岁`);
};
const arrowFn = (name, age) => {
console.log(`我是${name}, 我今年${age}岁`);
};

console.log(Function.prototype === fn1.__proto__); // true
console.log(Function.prototype === fn2.__proto__); // true
console.log(Function.prototype === arrowFn.__proto__); // true

创建对象的方式

在开发中,创建对象有以下几种方式

  1. 构造函数创建对象: 使用new调用构造函数创建对象
function Person(name, age) {
this.name = name;
this.age = age;
}
const person1 = new Person('abc', 10);
console.log(person1); // 输出:Person { name: 'abc', age: 10 }
  1. 字面量创建对象
const person2 = { name: 'abc', age: 10 };
console.log(person2); // 输出:{ name: 'abc', age: 10 }
  1. new Object创建对象
const person3 = new Object();
person3.name = 'abc';
person3.age = 10;
console.log(person3); // 输出:{ name: 'abc', age: 10 }
  1. Object.create创建对象: 创建一个具有指定原型的空对象
const person4 = Object.create({});
person4.name = 'abc';
person4.age = 10;
console.log(person4); // 输出:{ name: 'abc', age: 10 }
  • 字面量和new Object创建的对象本质上都是Object构造函数的实例
const person2 = { name: 'abc', age: 10 };
const person3 = new Object();
person3.name = 'abc';
person3.age = 10;

console.log(Object.prototype === person2.__proto__); // true
console.log(Object.prototype === person3.__proto__); // true

FunctionObject

  • 函数是Function构造函数的实例
function fn() {}
console.log(Function.prototype === fn.__proto__); // true
  • 对象是Object构造函数的实例
const obj = {};
console.log(Object.prototype === obj.__proto__); // true
  • 特殊关系
    • Object是一个函数,因此它是Function的实例
    • Function本身也是一个函数,因此它是自己的实例
console.log(Function.prototype === Object.__proto__); // true
console.log(Function.prototype === Function.__proto__); // true

3. 原型链的起点与终点

原型的本质

  • Person.prototype是构造函数Person的原型对象
  • Function.prototype是构造函数Function的原型对象
  • 所有的原型对象都是对象,因此本质上它们是通过new Object()创建的
function Person() {}

// true
console.log(Person.prototype.__proto__ === Object.prototype);
// true
console.log(Function.prototype.__proto__ === Object.prototype);
  • Person.prototypeFunction.prototype都是Object构造函数的实例
  • 因此,它们的__proto__都指向Object.prototype

什么是原型链

  • 原型链是通过__proto__属性连接起来的对象链
  • 当访问一个对象的属性时,如果该对象没有这个属性,会通过__proto__向上查找,直到null为止

原型链的终点

  • 原型链的终点是null,这是JavaScript中的对象链的终止点
  • 任何对象的__proto__最终都会指向Object.prototype,而Object.prototype.__proto__ === null


4. 原型链的实际应用

原型继承的机制

  • 原型继承允许实例对象使用构造函数原型对象上的方法或属性
  • 实例对象会通过__proto__链接到构造函数的prototype,实现属性和方法的继承
function Person(name) { // 构造函数
this.name = name; // 实例属性
}

Person.prototype.sayName = function() { // 定义在原型对象上的方法
console.log(this.name);
};

const person = new Person('abc'); // 实例化对象
person.sayName(); // 输出:abc
  • person实例对象的__proto__指向Person.prototype
  • 调用person.sayName()时,JavaScript引擎会通过__proto__查找到原型对象上的sayName方法

属性查找规则

  • 优先查找实例对象本身的属性
  • 如果实例对象中没有该属性,会沿着原型链向上查找,直到null
  • 如果在原型链中也未找到,返回undefined
function Animal(type) {
this.type = type;
}
Animal.prototype.sayType = function() {
console.log(this.type);
};

const dog = new Animal('dog');
console.log(dog.type); // 输出:dog
console.log(dog.hasOwnProperty('type')); // true(实例自身属性)
dog.sayType(); // 输出:dog
console.log(dog.__proto__.hasOwnProperty('sayType')); // true(原型上的方法)

5. 原型链核心机制

instanceof

  • 原理: 判断 B.prototype 是否在对象 A 的原型链上。
  • instanceof 通过检查 A.__proto__ 是否等于 B.prototype,如果不等则沿着原型链向上查找,直到找到匹配或到达 null
A instanceof B
function Person(name) {
this.name = name;
}

const person = new Person('abc');

console.log(Person instanceof Function); // true
console.log(Person instanceof Object); // true
console.log(person instanceof Person); // true
console.log(person instanceof Object); // true

结果分析

  1. Person instanceof Function → true
    Person 是函数,函数是 Function 的实例
    原型链:Person.proto === Function.prototype

  2. Person instanceof Object → true
    所有对象(包括函数)最终继承自 Object
    原型链:Person.proto.proto === Object.prototype

  3. person instanceof Person → true
    person 是 Person 的实例
    原型链:person.proto === Person.prototype

  4. person instanceof Object → true
    所有实例对象最终继承自 Object
    原型链:person.proto.proto === Object.prototype

自定义实现instanceof

  • 以下是instanceof的核心逻辑,用递归实现
function myInstanceof(A, B) {
let proto = A.__proto__;
while (proto) {
if (proto === B.prototype) return true;
proto = proto.__proto__;
}
return false;
}

// 示例
console.log(myInstanceof(person, Person)); // true
console.log(myInstanceof(person, Object)); // true
console.log(myInstanceof(person, Function)); // false

constructor

  • constructor 是对象的一个属性,指向创建该对象的构造函数
  • 原型与构造函数的互指关系
    • 构造函数的 prototype 对象有一个 constructor 属性,指向该构造函数本身
function Fn() {}

// true
console.log(Fn.prototype.constructor === Fn);
  • 实例访问 constructor
    • 实例对象通过 __proto__ 继承 prototype,因此可以访问 constructor
const instance = new Fn();

// true
console.log(instance.constructor === Fn);
// false (继承的属性)
console.log(instance.hasOwnProperty('constructor'));
Fn.prototype.constructor → Fn

instance.__proto__ (继承)

原型链练习

原型链练习#1

var F = function() {};

Object.prototype.a = function() {
console.log('a');
};

Function.prototype.b = function() {
console.log('b');
};

var f = new F();

f.a(); // ?
f.b(); // ?

F.a(); // ?
F.b(); // ?

答案

f.a(); // 输出 'a'
f.b(); // 报错:f.b is not a function

F.a(); // 输出 'a'
F.b(); // 输出 'b'

原型链

  • f 的原型链:f.proto → F.prototype → Object.prototype → null
  • F 的原型链:F.proto → Function.prototype → Object.prototype → null

关键点

  • f 是实例对象,只能访问 Object.prototype
  • F 是函数对象,既能访问 Function.prototype 也能访问 Object.prototype

原型链练习 #2

var A = function() {};
A.prototype.n = 1;
var b = new A();
A.prototype = {
n: 2,
m: 3
};
var c = new A();

console.log(b.n); // ?
console.log(b.m); // ?

console.log(c.n); // ?
console.log(c.m); // ?

答案

console.log(b.n); // 1
console.log(b.m); // undefined

console.log(c.n); // 2
console.log(c.m); // 3

关键点

重新赋值 A.prototype 不会影响已创建的实例

  • b 创建时:b.proto → 旧原型 {n: 1}
  • A.prototype 重新赋值:A.prototype → 新原型 {n: 2, m: 3}
  • c 创建时:c.proto → 新原型 {n: 2, m: 3}

原型链状态

b.__proto__ → {n: 1} (旧原型)
c.__proto__ → {n: 2, m: 3} (新原型)

原型链练习 #3

var foo = {},
F = function() {};
Object.prototype.a = 'value a';
Function.prototype.b = 'value b';

console.log(foo.a); // ?
console.log(foo.b); // ?

console.log(F.a); // ?
console.log(F.b); // ?

答案

console.log(foo.a); // 'value a'
console.log(foo.b); // undefined

console.log(F.a); // 'value a'
console.log(F.b); // 'value b'

关键点

  • foo 是普通对象,只能访问 Object.prototype
  • F 是函数对象,既能访问 Function.prototype 也能访问 Object.prototype

原型链

foo.__proto__ → Object.prototype → null
F.__proto__ → Function.prototype → Object.prototype → null

原型链练习#4

function A() {}
function B(a) {
this.a = a;
}
function C(a) {
if (a) {
this.a = a;
}
}
A.prototype.a = 1;
B.prototype.a = 1;
C.prototype.a = 1;

console.log(new A().a); // ?
console.log(new B().a); // ?
console.log(new C(2).a); // ?

答案

console.log(new A().a); // 1
console.log(new B().a); // undefined
console.log(new C(2).a); // 2

关键点

实例自身的属性优先于原型链上的属性

  • new A().a → 构造函数未设置 a,从原型链获取 → 1
  • new B().a → 构造函数设置了 this.a = undefined,实例自身有 a 属性 → undefined
  • new C(2).a → 构造函数设置了 this.a = 2,实例自身有 a 属性 → 2

原型链练习 #5

console.log(123['toString'].length + 123); // ?

答案

console.log(123['toString'].length + 123); // 124

关键点

  • 数字 123 包装为 Number 对象
  • 沿原型链找到 Number.prototype.toString
  • toString.length 为 1(表示接受 1 个可选参数 radix)
  • 1 + 123 = 124

参考:https://juejin.cn/post/7003369591967596552