首先来了解几个常见的名称(以下解释依据本文实力代码场景)
Object:对象(对象的值可以为任意类型,array,object,function......)
prototype:原型对象
constructor:构造函数
__proto__:指向Person.prototype的原型对象
一、函数的prototype属性
JavaScript规定,每一个构造函数都有一个特别的属性prototype,该属性的值是一个对象,这个对象就是我们常说的“原型对象”。这个原型对象的所有属性和方法,都会被构造函数的实例对象继承。
1.现,有如下函数Person
function Person(name){ this.name = name }
2.Person函数的prototype属性(即,原型对象),如下
根据打印出来的结果显示:
2.1.Person.prototype的属性包含两项constructor(构造函数)和__proto__
2.2.其中Person.prototype的构造函数constructor为Person函数本身,即
console.log(Person.prototype.constructor === Person) // true
2.3.Person.prototype的__proto__属性指向对象的原型对象,即
函数是Object的实例。
Person.prototype.__proto__ == Object.prototype
二、通过prototype和__proto__来实现继承
通过将对象A的__proto__属性赋值为对象B
即,A.__proto__ = B
此时使用A.__proto__便可以访问B的属性和方法
通过以上可以得出结论:
JavaScript可以在两个对象之间创建一个关联,使得一个对象可以访问另一个对象的属性和方法,从而实现了继承。
三、构造函数和实例对象
function Person(name){ this.name = name }
var lily = new Person('Lily')
其中:lily.__proto__ = Person.prototype
结论:
- 每个函数的原型对象(Person.prototype)都拥有constructor属性,指向该原型对象的构造函数(Person)
- 使用构造函数(Person)可以创建对象( new Person() ),创建的对象称为实例对象(lily)
- 实例对象通过将__proto__属性指向构造函数的原型对象(Person.prototype),实现了该原型对象的继承(lily.__proto__=Person.prototype)
四、prototype和__proto__的关系
- 每个对象都有__proto__属性来标识自己所继承的原型对象,但只有函数才有prototype属性。
- 对于函数来说,每个函数都有一个prototype属性,该属性为该函数的原型对象。
- 通过将实例对象的__proto__属性赋值为其构造函数的原型对象prototype,JavaScript可以使用构造函数创建对象的方式来实现继承。
五、通过原型链访问对象的方法和属性
1.首先会优先在该对象上搜寻
2.如果找不到,还会一次层层向上搜索该对象的原型对象、该对象的原型对象的原型对象等(套娃告警)
lily.__proto__ = Person.prototype
Person.prototype.__proto__ = Object.prototype
Object.prototype.__proto__ = null
3.JavaScript中的所有对象都是来自Object
4.Object.prototype.__proto__ === null
5.null没有原型,并作为这个原型链中的最后一个环节
6.JavaScript会遍历访问对象的整个原型链
7.如果最后依然找不到,此时会认为该对象的属性值为undefined
8.示例解释,基于原型链的对象属性的访问过程,如下
六、通过原型链访问属性的性能特点
function Person(name){ this.name = name }
var lily = new Person('Lily');
lily.__proto__ = Person.prototype Person.prototype.__proto__ = Object.prototype Object.prototype.__proto__ = null
当试图访问不存在的属性时,会遍历整个原型链,在原型链上查找属性比较耗时,对性能有副作用,这在性能要求苛刻的情况下很重要。
七、JavaScript中实现继承的方式
1.经典继承(盗用构造函数)
可以实现实例属性私有,但要求类型只能通过构造函数来定义
function Person(name){ // 私有属性,不共享 this.name = name }
2.组合继承(JavaScript中常见的继承模式)
通过将需要复用、共享的方法定义在父类原型上,结合构造函数和原型式继承的优点
function Person(name){ // 私有属性,不共享 this.name = name } Parent.prototype.speak = function(){ console.log("hello") }
3.原型式继承
引用类型的属性被所有实例共享,无法做到实例私有