JS基础三座大山之一-原型和原型链
先来看一个现象:为什么arr可以访问到数组的方法?
进入正题
1. 构造函数
// 构造函数
function Person(name, age) {
this.name = name;
this.age = age;
}
// 生成实例
const p = new Person('zhangsan', 18);
如上述代码所示,JS通过构造函数来生成实例。但是又出现了一个新的问题,在构造函数中通过this赋值的属性或者方法,是每个实例的实例属性以及实例方法,无法共享公共属性。所以又设计出了一个原型对象,来存储这个构造函数的公共属性以及方法
。
2. 原型对象 prototype
JS的每个函数在创建的时候,都会生成一个属性prototype
,这个属性指向一个对象,这个对象就是此函数的原型对象。该原型对象中有个属性为constructor,指向该函数。这样原型对象和它的函数之间就产生了联系即:Constructor.prototype.constructor === Constructor
现在我们创建一个People类(类也是函数)
class People {
constructor(name,age){
this.name = name;
}
sayHi(){
console.log(`Hello,我是父类: ${this.name}` )
}
sleep(){
console.log(`Hello,我是父类:我正在睡觉` )
}
}
const Chili = new People("Chili","20");
原型对象的作用:
- 存放一些属性和方法
- 在js中实现继承
此时,完善一下我们的Preson构造函数:
// 构造函数
function Preson(name, age) {
this.name = name;
this.age = age;
}
// 所有实例共享的公共方法
Preson.prototype.say = function (word) {
console.log(`${this.name}说:${word}`);
}
const p1 = new Preson('张三', 18); // 创建一个Person实例对象
p1.hasOwnProperty('say') // false 说明不是定义在其本身上的 hasOwnPrototype 判断是否是自己的属性
p1.say('hello world'); // 调用公共方法 打印:张三说:hello world
这里就要思考了,为什么我们构造的p1这个实例对象,它可以调用到Person这个构造函数的原型对象上的方法呢?明明只有在构造函数内部通过this来赋值的属性或者方法才会被实例所继承,为什么在构造函数的原型对象上定义的say方法也能通过实例来调用到呢?
在上述现象为什么arr可以访问到数组的方法,是因为arr是Array的实例,实例时可以访问到Array的公共方法,所以arr可以使用Array的方法,那么实例arr为什么就可以使用原型身上的方法呢?这里就引出了__propto__
和原型链的概念。
3. __propto__
隐式原型
__propto__
:每个对象
都有__propto__
属性- 作用:这个属性指向他的原型对象
prototype
属性
每个通过构造函数创建出来的实例对象,其本身有个属性__proto__,这个属性会指向该实例对象的构造函数的原型对象,这么说好像有点绕,我们看下图
看图也有点绕?简化上图得到下图:
还是看看上面创建的People类
class People {
constructor(name,age){
this.name = name;
}
sayHi(){
console.log(`Hello,我是父类: ${this.name}` )
}
sleep(){
console.log(`Hello,我是父类:我正在睡觉` )
}
}
const Chili = new People("Chili","20");
4. 原型链
现在我们知道了,当访问一个对象的某个属性时,会先在这个对象本身属性上查找,如果没有找到,则会通过它的__proto__隐式属性,找到它的构造函数的原型对象,如果还没有找到就会再在其构造函数的prototype
的__proto__
中查找(因为prototype
也是个对象,每个对象
都有__propto__
属性),这样一层一层向上查找就会形成一个链式结构
,我们称为原型链。
注意注意⚠
如果通过p1实例对象的__proto__属性赋值,则会改变其构造函数的原型对象,从而被所有实例所共享。
// 构造函数
function Preson(name, age) {
this.name = name;
this.age = age;
}
// 所有实例共享的公共方法
Preson.prototype.say = function (word) {
console.log(`${this.name}说:${word}`);
}
const p1 = new Preson('张三', 18); // 创建一个Person实例对象
const p2 = new Preson('李四', 20); // 新创建一个Proson实例对象
p1.say('hello world'); // 调用公共方法
p1.hasOwnProperty('say') // false 说明不是定义在其本身上的
p1.__proto__.do = function () {
console.log('往原型对象中添加方法');
}
p2.do(); // 打印出了-往原型对象中添加方法
这样会污染原本的构造函数,所以,我们在开发的时候,要注意不要通过实例对象去改变其构造函数的原型对象,这样会对其他通过该构造函数生成的实例对象造成影响。
proto 并不是语言本身的特性,这是各大厂商具体实现时添加的私有属性,虽然目前很多现代浏览器的 JS 引擎中都提供了这个私有属性,但依旧不建议在生产中使用该属性,避免对环境产生依赖。生产环境中,我们可以使用 Object.getPrototypeOf 方法来获取实例对象的原型,然后再来为原型添加方法/属性。(摘自阮一峰的ES6入门)
再在其构造函数的prototype的__proto__中查找是什么意思?
补充知识:原型链的尽头
所有的原型对象的__proto__
属性都是指向function Object
的原型对象。 而function Object
的原型对象在上图中我们可以得知是不存在__proto_
_这个属性的,它指向了null
。我们就得知了原型链的尽头是null
。
什么? constructor 和 prototype 把你搞晕了?
5. constructor 与 prototype 的关系
constructor
属性
位置:存在于类的原型对象(prototype
)上(Person.prototype.constructor
)
指向:类(构造函数)本身
作用:标识创建该实例的构造函数
// 其实类就是函数
// typeof Person 返回 'function'
class Person {
constructor(name) {
this.name = name;
}
}
console.log(Person.prototype.constructor === Person); // true
prototype
属性
位置:存在于类(构造函数)上(Person.prototype)
指向:该类的原型对象
作用:存储所有实例共享的方法和属性
- 实例的
__proto__
属性
位置:存在于所有对象上
指向:构造函数的 prototype 对象
作用:提供原型链查找机制
实例的 constructor 属性继承自原型对象
person.__proto__ === Person.prototype
6. 总结
原型: 函数都有 prototype
属性,称之为原型,也称之为原型对象
- 原型可以放一些属性和方法,共享给实例对象使用
- 原型可以做继承
原型链:对象
都有__proto__
属性这个属性指向他的原型对象,原型对象也是对象,也有__proto__
属性,指向原型对象的原型对象.这样一层一层形成的链式结构称之为原型链,最顶层找不到则返回null
原型系统关键点:
-
prototype
属性:只有函数
拥有,指向该函数的原型对象(实例对象是没有的) -
__proto__
属性:所有对象都拥有
,指向创建该对象的构造函数的原型 -
原型链:当访问对象属性时,JavaScript 会沿着
__proto__
链向上查找 -
constructor
属性:原型对象指向其构造函数的引用
原型系统核心概念详解:
- 构造函数 (Constructor)
-
用于创建对象的函数
-
通常以大写字母开头
-
包含
prototype
属性(是一个对象),指向原型对象 -
示例:
function Person() {}
- 原型对象 (Prototype)
-
每个函数都有一个原型对象
-
包含共享的属性和方法
-
包含
constructor
属性,指回构造函数 -
示例:
Person.prototype
- 实例对象 (Instance)
-
通过构造函数创建的对象
-
包含
__proto__
属性,指向构造函数的原型 -
示例:
const person = new Person()
- 原型链 (Prototype Chain)
-
对象属性的查找机制
-
查找顺序:实例自身 → 原型对象 → 原型对象的原型 → ... → null
-
原型链终点:
Object.prototype.proto === null
__proto__
与prototype
的关系
-
prototype
是函数特有的属性 -
__proto__
是所有对象都有的属性 -
关键关系:
instance.__proto__ === Constructor.prototype
-
Constructor.prototype.constructor === Constructor
- 原型继承的优势
-
内存效率:方法在原型上共享,不占用每个实例的内存
-
动态性:修改原型会影响所有实例
-
灵活性:可以在运行时修改原型
-
模拟类:在ES6之前实现面向对象编程