前言

想了想,有些东西挤在一个博客里可能并不好,还不如拆出来,也比较好回顾
参考文章: https://juejin.cn/post/7007416743215759373

基础知识

prototype__proto__

两者是什么

  • 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定义实例可以继承的方法
  • 实例的__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(); // 小红

构造函数与对象的关系

构造函数

  • 构造函数本质上是函数,用来创建对象。定义构造函数有以下几种常见方式
// 函数声明
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岁

构造函数的本质

  • 所有函数的本质都是由Function构造函数创建的
  • 上述三种写法等同于以下代码
const fn1 = new Function('name', 'age', 'console.log(`我是${name}, 我今年${age}岁`)');
const fn2 = new Function('name', 'age', 'console.log(`我是${name}, 我今年${age}岁`)');
const arrowFn = new Function('name', 'age', 'console.log(`我是${name}, 我今年${age}岁`)');

构造函数与原型的关系

  • 每个构造函数的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

创建对象的方式

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

  • 构造函数创建对象: 使用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 }
  • 字面量创建对象
const person2 = { name: 'abc', age: 10 };
console.log(person2); // 输出:{ name: 'abc', age: 10 }
  • new Object创建对象
const person3 = new Object();
person3.name = 'abc';
person3.age = 10;
console.log(person3); // 输出:{ name: 'abc', age: 10 }
  • Object.create创建对象: 创建一个具有指定原型的空对象
const person4 = Object.create({});
person4.name = 'abc';
person4.age = 10;
console.log(person4); // 输出:{ name: 'abc', age: 10 }

字面量与new Object的关系

  • 字面量和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

深入解析

原型链的起点与终点

原型的本质

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

原型链上的关系

  • Person.prototypeFunction.prototype都是Object构造函数的实例
  • 因此,它们的__proto__都指向Object.prototype
function Person() {}

console.log(Person.prototype.__proto__ === Object.prototype); // true
console.log(Function.prototype.__proto__ === Object.prototype); // true

什么是原型链

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

原型链的终点

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

原型链的实际应用(继承与查找)

原型继承的机制

  • 原型继承允许实例对象使用构造函数原型对象上的方法或属性
  • 实例对象会通过__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(原型上的方法)

高级特性

instanceof的机制

A instanceof B
  • 判断B.prototype是否在对象A的原型链上
  • instanceof通过检查A.__proto__是否等于B.prototype或者沿着原型链向上查找直到null,来判断结果
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
  • Person instanceof Function

    • 构造函数Person是函数,函数是Function的实例
    • Function.prototype === Person.__proto__
  • Person instanceof Object

    • 所有对象最终继承自Object,包括函数
    • Object.prototype === Function.prototype.__proto__
  • person instanceof Person

    • person.__proto__ === Person.prototype,因此返回true
  • person instanceof Object

    • person的原型链最终指向Object.prototype,因此返回true

自定义实现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是每个对象的一个属性,指向创建该对象的构造函数

  • prototypeconstructor的互指关系
    • 构造函数的prototype上有一个constructor属性,指向该构造函数本身
    • Fn.prototype.constructor === Fn
function Fn() {}
console.log(Fn.prototype.constructor === Fn); // true
  • 实例的constructor属性
    • 实例对象通过__proto__继承prototype,因此可以访问constructor
    • instance.constructor === Fn
const instance = new Fn();
console.log(instance.constructor === Fn); // true

原型链练习

原型链练习#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.a()

  • fF的实例,而F是通过构造函数创建的
  • f的原型链如下
f.__proto__ === F.prototype
F.prototype.__proto__ === Object.prototype
Object.prototype.a() 定义了方法
  • 因此,f可以通过原型链访问Object.prototype.a,输出'a'

f.b()

  • f的原型链中没有定义b方法
  • b是定义在Function.prototype上的,而f并不是函数,因此无法访问Function.prototype.b
  • 结果: 报错f.b is not a function

F.a()

  • F是函数,函数是Function构造函数的实例
  • F的原型链如下
F.__proto__ === Function.prototype
Function.prototype.__proto__ === Object.prototype
  • 因此,F可以通过原型链访问到Object.prototype.a,输出'a'

F.b()

  • F是函数,b方法定义在Function.prototype
  • 因为F.__proto__ === Function.prototype,所以F可以直接访问到b方法
  • 结果: 输出'b'

原型链示意图

  • f的原型链
f.__proto__ → F.prototypeObject.prototypenull
  • F的原型链
F.__proto__Function.prototypeObject.prototypenull

原型链练习#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.n = 1

  • A.prototype初始化时,包含属性n,值为1

var b = new A(), 创建实例b

  • b.__proto__指向当前的A.prototype,即{ n: 1 }
  • 通过b.n可以访问A.prototype.n,值为1
  • b的原型链为
b.__proto__ === A.prototype
A.prototype === { n: 1 }

重新赋值A.prototype

  • A.prototype被重新赋值为一个新的对象{ n: 2, m: 3 }
  • 注意: 这不会影响已经创建的实例b,因为b.__proto__指向的是旧的A.prototype
A.prototype === { n: 2, m: 3 }

var c = new A(), 创建实例c

  • c.__proto__指向当前的A.prototype,即{ n: 2, m: 3 }
  • 通过c.nc.m可以访问新A.prototype上的nm

原型链示意图

  • 创建b
b.__proto__ → { n: 1 }
  • 重新赋值A.prototype
A.prototype → { n: 2, m: 3 }
  • 创建c
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.a

  • foo是一个普通对象,其原型链如下
foo.__proto__ === Object.prototype
Object.prototype.a = 'value a'
  • 因此,foo.a可以通过原型链访问到Object.prototype.a,结果为'value a'

foo.b

  • foo的原型链中没有定义b方法或属性
  • foo.__proto__ === Object.prototype中没有b属性
  • 因此,foo.b返回undefined

F.a

  • F是一个函数,函数也是对象,因此它的原型链如下
F.__proto__ === Function.prototype
Function.prototype.__proto__ === Object.prototype
Object.prototype.a = 'value a'
  • F可以通过原型链访问到Object.prototype.a,结果为'value a'

F.b

  • F是一个函数,而b是定义在Function.prototype
F.__proto__ === Function.prototype
Function.prototype.b = 'value b'
  • 因此,F.b可以直接访问到Function.prototype.b,结果为'value b'

原型链示意图

  • foo的原型链
foo.__proto__Object.prototypenull
  • F的原型链
F.__proto__Function.prototypeObject.prototypenull

原型链练习#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

  • 使用new A()创建实例
  • A的构造函数中没有定义a属性
  • 查找a时,沿原型链访问A.prototype.a,值为1
  • 输出: 1

new B().a

  • 使用new B()创建实例
  • B的构造函数有this.a = a;,此时a的值为undefined(没有传入参数)
  • 因此,this.a被赋值为undefined
  • 因为实例自身的a属性已经定义了,优先于原型链上的属性
  • 输出: undefined

new C(2).a

  • 使用new C(2)创建实例
  • C的构造函数中有if (a) { this.a = a; },传入参数a = 2,条件为true
  • 因此,this.a被赋值为2
  • 实例自身的a属性覆盖了原型链上的C.prototype.a
  • 输出: 2

原型链示意图

  • new A()的原型链
instance → A.prototypeObject.prototypenull
  • new B()的原型链
instance → B.prototypeObject.prototypenull
  • new C(2)的原型链
instance → C.prototypeObject.prototypenull

原型链练习#5

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

答案: 123是数字,数字本质是new Number(),数字本身没有toString方法,则沿着__proto__function Number()prototype上找,找到toString方法,toString方法的length11 + 123 = 124,至于为什么length1,参考下面文章

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

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

附录

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