1.2 __proto__ 和 prototype 的相等关系
前言
理解JS中的原型和原型链,是深入学习JavaScript的必要步骤之一。无论是实现JS继承、掌握new
关键字的原理,甚至在封装组件、优化代码时,搞清楚这些概念都是前提条件。本文会用通俗易懂的语言和代码实例,帮助大家快速掌握原型和原型链,并理解其存在的意义。
一、JS中的原型与原型链
首先,我们明确两个重要概念:
- JS的对象分为两类:函数对象和普通对象。
- 所有对象都有
__proto__
属性,但只有函数对象才有prototype
属性。
这两个概念理解起来并不难。我们一起来看看JS中的构造函数、__proto__
和 prototype
是如何关联的。
1.1 原型与构造函数
在JS中,构造函数通过 new
关键字创建实例对象,而这些实例对象都可以访问构造函数的原型对象上的属性和方法。
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.motherland = 'China';
let person1 = new Person('小明', 18);
console.log(person1.motherland); // 输出: China
上面代码中,Person.prototype
是构造函数 Person
的原型对象,它存放了所有实例共享的属性 motherland
。通过构造函数创建的实例对象 person1
可以通过原型链访问到 motherland
属性。
1.2 __proto__
和 prototype
的相等关系
我们接下来理解两个重要的准则:
- 准则1:原型对象(
Person.prototype
)的constructor
指向构造函数本身。 - 准则2:实例对象的
__proto__
属性指向构造函数的原型对象。
console.log(Person.prototype.constructor === Person); // true
console.log(person1.__proto__ === Person.prototype); // true
Person.prototype.constructor
表示原型对象的constructor
属性指向Person
。person1.__proto__
指向的是Person.prototype
,即person1
的原型对象是Person
的原型对象。
二、原型链的构造与延伸
2.1 原型链的层级结构
原型链的本质就是当我们访问一个对象的属性时,JS引擎会先查找该对象自身的属性。如果找不到,则会顺着 __proto__
向上查找,直到找到属性或到达 null
(表示原型链的尽头)。
console.log(person1.__proto__ === Person.prototype); // true
console.log(Person.prototype.__proto__ === Object.prototype); // true
console.log(Object.prototype.__proto__ === null); // true
上面的代码展示了原型链的层级结构:
person1.__proto__
指向Person.prototype
,即实例的原型是构造函数的原型。Person.prototype.__proto__
指向Object.prototype
,即所有构造函数的原型对象最终都会连接到Object.prototype
。Object.prototype.__proto__
指向null
,代表原型链的尽头。
2.2 图解原型链结构
图中的箭头表示原型链的查找方向,随着层层查找,最终原型链在 Object.prototype
终结。
三、原型与原型链的意义
3.1 共享属性与方法
原型的存在使得多个实例对象可以共享属性和方法,从而节省内存开销。我们再看一个例子:
Person.prototype.hairColor = 'black';
Person.prototype.eat = function() {
console.log('We usually eat three meals a day.');
};
let person2 = new Person('小花', 20);
console.log(person1.hairColor); // 输出: black
console.log(person2.eat()); // 输出: We usually eat three meals a day.
此时,person1
和 person2
都通过原型链继承了 hairColor
和 eat
方法,且这些属性方法只存在于 Person.prototype
中。这样,我们不需要为每个实例单独声明这些属性和方法,达到了代码复用的效果。
3.2 覆盖原型上的属性和方法
如果我们在实例对象上定义了与原型对象相同的属性或方法,实例会优先使用自己的属性或方法,而不会访问原型上的同名属性。这种机制称为属性遮蔽。
person1.hairColor = 'yellow';
console.log(person1.hairColor); // 输出: yellow
console.log(person2.hairColor); // 输出: black
上面的代码中,person1
重新定义了 hairColor
属性,但这不会影响到 person2
,因为 person2
仍然继承了原型对象上的 hairColor
。实例上的同名属性不会修改原型上的属性。
四、结语
通过本篇文章,我们通俗地讲解了JS中的原型和原型链,重点理解了 __proto__
和 prototype
的相等关系,以及原型链的层次结构。原型和原型链的存在意义在于:通过共享属性和方法,节省内存,并且动态扩展实例对象的功能。掌握这些基础知识后,你将更容易理解JS的继承机制和构造函数的实现原理。
如果你对本文的解释有任何疑问或建议,欢迎在评论区讨论,
最后附上一张神图