本文已参与「新人创作礼」活动,一起开启掘金创作之路。
前言
面向对象编程很重要的一个方面,就是对象的继承。A 对象通过继承 B 对象,就能直接拥有 B 对象的所有属性和方法。这对于代码的复用是非常有用的。
大部分面向对象的编程语言,都是通过“类”(class)来实现对象的继承。但JavaScript严格意义上并不是面向对象的语言, 它的继承是通过“原型对象”(prototype)实现的。
原型(Prototype)
先来放张图:
上图中涉及到了三个“角色”,构造函数Person,Prototype对象和Person的实例对象person。
function Person(){
}
Person.prototype.say = () => {
console.log('我可以说话!')
}
Person.prototype.eat = () => {
console.log('我可以吃饭!')
}
console.log(Person.prototype.constructor)//[Function: Person]
let person = new Person();
console.log(person.__proto__);//{ say: [Function (anonymous)], eat: [Function (anonymous)] }
person.say();//我可以说话!
person.eat();//我可以吃饭!
看完图和代码,你可以用自己的话来讲述什么是原型了吗?
构造函数(Person)存在一个prototype属性,指向一个对象(就是原型Prototype),这个对象也存在contructor属性,指向构造函数Person,我们可以通过Person.prototype访问并修改原型对象。
当使用new操作符得到一个Person的实例person后,得到的person实例对象存在一个
__proto__属性,同样指向构造函数Person指向的原型对象,并且可以直接访问原型对象中的内容,例如say方法和eat方法。
原型链
我们了解了原型的基本概念,原型Prototype本质上也是一个对象,那么对象就会存在__proto__属性,指向自己的原型对象,就和peron一样,也就说原型对象也是另一个构造函数的实例对象,例如Person.prototype其实也是Object构造函数的实例:
console.log(Person.prototype.__proto__);//[Object: null prototype] {}
console.log(person.prototype.__proto__.constructor);//[Function: Object]
//等价于
console.log(person.__proto__.__proto__);//[Object: null prototype] {}
console.log(person.__proto__.__proto__.constructor);//[Function: Object]
那么什么时候这条“链子”才到头呢?Object是最顶级的构造函数,那么Object.prototype.__proto__便是尽头了,指向null。
console.log(Object.prototype.__proto__)//null
那么现在我们打印peron.toString(),是什么样的流程呢?
console.log(person.toString())//[object Object]
首先,person现在自己“身上”查找,有没有toString方法,显然没有,那么就顺着原型链,去person.__proto__上找,没有找到,接着去person.__proto__.__proto__上(即Object.prototype)上找,发现存在toString方法,所以person能够调用toString方法。
我们验证一下,在Person.prototype上修改toString()方法:
Person.prototype.toString = () => {
return "Person.prototype上的toString!"
}
console.log(person.toString())//Person.prototype上的toString!
同样,我们打印person.sayHello(),这个sayHello方法是我乱编的,所以顺着原型链查找,最总找到Object.__proto__.__proto__上(null),即到了尽头,直接报错。
console.log(person.sayHello())//TypeError: person.sayHello is not a function