概述
Javascript 函数有一个 prototype 属性,该属性是一个指针,指向一个对象,该对象包含特定类型的所有实例共享的属性和方法,称为实例对象的原型对象。
简单来说,在 JavaScript 中创建一个新函数,就会自动获取 prototype 属性,通过 new 命令创建实例对象,实例对象会获取 prototype 属性所指向的原型对象的属性和方法。打个比方,在 Java 中有类和继承的概念,prototype 所指向的原型对象就好比 Java 中的基类,而新函数好比是 Java 中的新类,继承基类中的属性和方法,那么创建实例后,除了能访问到本身的属性和方法外,还能访问到基类中的属性和方法。
构造函数与原型对象之间的关系
function Person() {}
let person = new Person();
person.age = 12;
console.log(person.age); // 12
以上例子,Person 是一个构造函数,通过 new 命令实例化对象。通过构造函数实例化对象的方式,如果多个实例对象都拥有属性 age 时,则需要在每个实例对象本身设置该属性,这样的操作显得有点冗余。那么有没有一种方式可以把属性 age 设置为公有,实例化对象时就自动拥有公共的属性呢,而对于实例对象本身只需要添加自己需要的属性即可。答案是有的。
这里就需要引出 prototype 属性,那么 prototype 属性是什么呢?除了bind 函数、箭头函数以及 Function.prototype 外,每个函数都有一个属性 prototype,该属性是一个指针,指向一个对象,该对象是函数的原型对象,原型对象包含了公共的属性和方法,实例化的所有对象都共享原型对象的属性和方法。*需要注意的是 prototype 属性是函数才拥有的。
Person.prototype.age = 12;
Person.prototype.name = "Jack";
Person.prototype.say = () => {
console.log("Hello World !");
};
let person1 = new Person();
let person2 = new Person();
console.log(person1.age); // 12
console.log(person1.name); // Jack
console.log(person2.age); // 12
console.log(person2.name); // Jack
console.log(person1.say()); // Hello World
console.log(person2.say()); // Hello World
person1.job = "test";
console.log(person1.job); // test
console.log(person2.job); // undefined
从以上例子可以得知,通过 prototype 属性添加 age 和 name 属性,然后实例化两个对象,分别打印出两个属性是有值的,说明实例对象拥有了原型对象的属性和方法。
现在我们已经知道构造函数与原型对象的单向关系,即构造函数通过 prototype 属性指向原型对象。那么原型对象能反过来通过某种方式指向原先的构造函数吗?答案是可以的,这时就需要 constructor 派上场了。
每个原型对象都会有一个 constructor 属性,而 constructor 属性包含一个指向 prototype 属性所在函数的指针。以上面例子为例,即
Person.prototype.constructor === Person(true)。
至此,我们理清了构造函数与原型对象之间的关系,关系图如下:
实例对象与原型对象之间的关系
当读取实例对象属性时,首先会在实例对象本身查找该属性是否存在,不存在的话,就会往实例对象与之关联的原型对象查找,再查找不到时,直向原型的原型查找,查找到最上层为止。
例子如下:
function Person () {}
Person.prototype.name = "Jack";
Person.prototype.desc = "desc";
let person = new Person();
console.log(person.name); // Jack
可见,实例对象 person 本身没有属性 name,但是还能查找到该属性,这是通过原型对象找到的。那么实例对象是通过什么方式与原型对象关联呢?其实实例对象有一个属性 __proto__,指向原型对象,即
person.__proto__ === Person.prototype(true)。
原型对象的原型对象
从上面我们已经理清了构造函数与原型对象、实例对象与原型对象之间的关系,那么我们会不会产生一个疑惑:原型对象的原型对象是什么,即 Person.prototype 的原型对象是什么?
JavaScript 中的所有对象都来自 Object,并且所有对象都从 Object.prototype 继承属性和方法,尽管它们可能被覆盖。因此可以推出:
Person.prototype.__proto__ === Object.prototype(true)。
图中画虚线的表示通过 __prototype__ 形成原型链;至此,已经把原型对象及原型链的知识讲完。最后放一张网上对原型的理解画得比较全面的关系图,如下:
注:以上只是自己整理的学习笔记,由于水平有限难免有错误,欢迎指出。随着自己对原型的深入理解,笔记也会做相应的修改,最后放上资料参考来源。
-
《JavaScript 高级程序设计》(第 3 版)