原型和构造函数密切相关,了解原型之前,我们先回顾一下构造函数。
一、构造函数
像 Object 和 Array 这样的原生构造函数,运行时可以直接在执行环境中使用。当然也可以自定义构造函数,以函数的形式为自己的对象类型定义属性和方法。——JavaScript高级程序设计
function Person(name, age){
this.name = name
this.age = age
this.hobby= function(){
console.log('sport')
}
}
const person1= new Person('Lisa', 18)
const person2 = new Person('Jony', 20)
person1.hobby()
person2.hobby()
需要注意的是,构造函数名称的首字母都是要大写的。函数体内使用this关键字,代表所要生成的对象实例。任何函数只要使用 new 操作符调用就是构造函数。
构造函数虽然有用,但是也存在着浪费内存的问题:每创建一个对象,都会在堆内存空间中新开辟一个空间来存储方法。这个问题可以通过引入原型对象来解决。
二、原型
原型就是一个对象,也叫原型对象。
1. 理解原型
- 每个函数都会创建一个 prototype 属性,属性值是一个普通对象。
function Person(){}
console.dir(Person)
- 原型上默认有一个叫做constructor的属性,指向这个构造函数本身。
console.log(Person.prototype.constructor === Person) // true
- 使用原型对象的好处是,在它上面定义的属性和方法可以被对象实例共享。原来在构造函数中直接赋给对象实例的值,可以直接赋值给它们的原型。
function Person(){}
Person.prototype.name = "Mike";
Person.prototype.age = 7;
// 公共的方法写到原型对象上 节约了内存
Person.prototype.hobby= function(){
console.log('sport')
}
let person1 = new Person()
const res = person1.name
console.log(res) // "Mike"
let person2 = new Person()
const res = person2.name
console.log(res) // "Mike"
console.log(person1.hobby== person2.hobby) // true
2. __proto__隐式原型
- 所有对象,都有一个__proto__属性,指向原型对象。
- 实例通过__proto__访问原型对象。
- 对象的隐式原型指向构造函数的显示原型
console.log(person1.__proto__ === Person.prototype) //true
proto 表示了实例和原型之间的一个关系,相当于就是一个桥梁,实例通过它访问原型。
三、构造函数、原型、实例的关系
- 构造函数.prototype ——> 原型对象
- 原型对象.constructor ——> 构造函数
- 实例对象.__ proto__ ——> 原型对象
- 构造函数 new , 创建出实例对象
实例与构造函数原型之间有直接的联系,但实例与构造函数之间没有直接联系。
四、原型的五条规则
- 所有的引用类型(对象 数组 函数),都具有对象的特征,可以自由扩展属性。
- 每一个对象都有一个__proto__属性,这个属性是一个对象。
- 所有的函数都有一个 prototype 属性,这个属性也是一个对象。
- 所有对象的__proto__属性值,都指向它的构造函数 prototype 属性值。
- 实例化对象想要获取的属性值如果构造函数没有,它会向构造函数的原型中寻找,如果原型也没有,则会向原型的__proto__中寻找,直到拿到或没有该属性值。
五、原型链
原型对象也可能拥有原型,并从中继承方法和属性,一层一层、以此类推。这种关系常被称为原型链,它解释了为何一个对象会拥有定义在其他对象中的属性和方法。
function Person(name){
this.name = name
}
const person = new Person()
- 对象的隐式原型指向构造函数的显示原型。
console.log(person.__proto__ === Person.prototype) // true
- 所有的对象都有一个__proto__,属性值也是一个对象,Person.prototype 也是一个对象,所以,它也有__proto__属性,属性值也是对象。原型上默认有一个 constructor 属性,指回的构造函数,所以 Person.prototype 的构造函数是 Object。
console.log(Person.prototype.__proto__) // __proto__ 访问到原型
// Person.prototype 这个原型对象 是Object这个构造函数创建的
console.log(Person.prototype.__proto__ === Object.prototype) // true
- Object 是一个构造函数,Object.prototype 得到的也是一个原型,所以 Object.prototype 这个原型上默认有一个constructor属性,指向构造函数本身。
Object.prototype.constructor === Object // Object.prototype指向构造函数本身
console.log(Person.prototype.__proto__ === Object.prototype) // true
- Object.prototype 也是一个原型对象,所以它也有一个__proto__属性。
// 正常的原型链终止到Object.prototype, 也就是 Object.prototype.__proto__ === null
console.log(Object.prototype.__proto__ === null) // true
根据以上结论,我们用一张图来更加清晰的理解原型链:
简单概况一下:
每个对象通过__proto__属性都能访问到它的原型对象,原型对象也有它的原型对象,当访问一个对象属性或方法的时候,先在自身中寻找,如果没有,就会沿着__proto__这条链向上查找,直到最顶层 Object.prototype 为止,最后 Object.prototype.proto === null。