hzzzzzzzq / Blog

这是我记录博客的地方,希望能多写一些技术博客,JS、ES、React、Vue等
14 stars 0 forks source link

深入理解JavaScript系列(1):原型与原型链 #42

Open hzzzzzzzq opened 2 years ago

hzzzzzzzq commented 2 years ago

原型与原型链

原型

构造函数创建对象

我们来写一个构造函数用来创建一个对象

function Person() {}
let person = new Person();
person.name = 'hzzzzzzzq';
console.log(person.name); // hzzzzzzzq

从例子中,我们使用 new 创建了一个实例对象 person

这我们都是可以理解的,new 创建实例对象。

prototype

每一个函数都有一个 prototype 属性,那么这个属性指向什么呢?

其实,函数的 prototype 属性指向了一个对象,这个对象就是调用该构造函数而创建的实例的原型,也就是例子中,person 的原型。

我们来打印一下 Personprototype,来看看是什么。

function Person() {}

console.log(Person.prototype);

prototype1

说了这么多,还是不知道什么是原型(0_o)。我们可以这样来理解原型:每一个 JS 对象(null 除外)在创建的时候就会有一个与之关联的另一个对象,而这个对象就是我们所说的原型,每一个对象都会从原型"继承"属性。

我们简单来表示一下构造函数与实例原型之间的关系:

prototype2

我们从这张图中,可以看出,Object.prototype 表示实例原型。 那么我们该怎么表示实例(person)与实例原型(Person.prototype)之间的关系呢?所以我们来看看第二个属性

__proto__

这是每一个 JS 对象(除了 null) 都具有的一个属性,叫 __proto__,这个属性会指向该对象的原型。

我们来验证一下。

function Person() {}
let person = new Person();
console.log(person.__proto__ === Person.prototype);
// true
console.log(person.__proto__ === Object.getPrototypeOf(person)); // true

注意: 当然,在这里我们要提一嘴,__proto__web 标准中,以及删除,但是一些浏览器目前还是支持的。

所以在这里,我顺便提一个 Object.getPrototypeOf(Object),用来获取实例的原型。

于是,我们可以将图进行更新了:

prototype3

我们现在,构造函数指向原型,实例指向原型,那么原型能否指向构造函数或者指向实例呢?

其实这时候我们就要提到下一个属性了,但是,指向实例的是没有的,因为一个构造函数可以生成多个实例。

constructor

这是我们提到的第三个属性了,而这个属性,就是用来实现由原型指向构造函数,每一个原型都会有一个 constructor 属性来指向关联的构造函数。

function Person() {}

console.log(Person === Person.prototype.constructor);
// true

所以我们继续更新关系图:

prototype4

到目前为止,我们已经得出来一些结论了。

function Person() {}
let person = new Person();
console.log(person.__protp__ === Person.prototype); //true
console.log(Person.prototype.constructor === Person); // true

了解了构造函数、实例原型和实例之间的关系后,我们来看看实例和原型的关系:

实例与原型

在我们读取实例的属性时,如果找不到,就会查找与对象关联的原型中的属性,如果还查不到,就会去找原型的原型,直到最顶层为止。

function Person() {}
Person.prototype.name = 'hzzzzzzzq';

let person = new Person();

person.name = 'qzzzzzzzh';
coonsole.log(person.name); // qzzzzzzzh

delete person.name;
coonsole.log(person.name); // hzzzzzzzq

从例子来看,我们给原型对象添加了 name 属性,然后在给实例对象添加了 name 属性,而首先打印时,打印了实例对象的 name

但是当我们删除了实例对象的 name 属性时,在实例中就找不到该属性,则会从实例的原型是那个去查找,所以在 Person.prototype 中找到。

但是,如果我们的原型中没找到呢?原型的原型又是什么呢?

原型的原型

原型是什么?其实原型也是一个对象,既然是对象,那我们就可以用 new Object() 来创建它:

let obj = new Object();
obj.name = 'hzzzzzzzq';
console.log(obj.name); // hzzzzzzzq

而我们再来看一下,Person.prototype 与 Object 的关系.

function Person() {}
console.log(Person.prototype.__proto__ === Object.prototype); // true

其实原型对象就是通过 Object 构造函数生成的,实例的 __proro__ 指向构造函数的 prototype,现在我们再来更新一下关系图:

prototype5

原型链

其实,看完原型的原型,还是会有疑问,Object.prototype 的原型是什么呢?

其实就是 null,我们可以来验证一下:

console.log(Obejct.prototype.__proto__ === null);
// true

null 是什么呢?

null 就是表示"没有对象",即在这里不应该有值。

因为 Object.prototype.__proto__ 的值为 null。所以查找属性的时候其实查到 Object.prototype 就可以停止了。

我们在来更新一下关系图:

prototype6

那什么是原型链呢?相互关联的原型组成的链状结构就是原型链,也就是这条红色的线。

补充

你以为这就结束了吗?

我们来看看在来补充一些神奇的事情,我们来看看打印。

function Person() {}
console.log(Person.__proto__ === Function.prototype); // true
console.log(Object.prototype.constructor.__proto__ === Function.prototype); //true
console.log(Object.prototype === Function.prototype.__proto__); // true

经过测试我们会发现,都会 true

这意味着什么呢?

于是,我们的图就可以更新为如下关系图:

prototype7

至此,我们的关系图已经基本完善。

再次,我也需要补充一张找到的图,应该说,真的很好,因为我自己画的比较乱。(0_o)

prototype8