通过类的创建与继承解读原型和原型链
在开始之前,先和大家说明
es5中的类用的是构造函数,es6才有了类(本质还是构造函数)
类创建的实例有两种属性,一种是实例自身的,一种是公共的(公共的存在原型对象中)
ES5
es5中构造函数就是类,所以下文可能会出现两种称呼,我们知道是一个东西就好
构造函数的创建与实例化对象
// 类中创建的实例有两种属性,一种是实例自身的
function Animal(){
this.type = '哺乳类'
}
// 一种是通过原型对象绑定的 公共的
// 这些公共的方法,可以让所有的实例对象共享
// 即构造函数的prototype
Animal.prototype.eat = function(){
console.log('eat');
}
let animal = new Animal
// 实例的animal对象具有类中所有的属性和方法
// 实例的__proto__指向构造函数的原型对象
console.log(animal.__proto__ === Animal.prototype);
// 输出true
constructor
原型对象中有一个constructor,里面存放着构造函数本身
// 上面的内容中,我们讲到了
// Animal.prototype是自己的原型对象
// animal.__proto__指向Animal.prototype
console.log(Animal.prototype.constructor);
// 输出的是构造函数Animal的代码
// 有时候我们代码写的非常狠啊
// 不是给原型对象追加内容,而是直接重写修改了原型对象
Animal.prototype = {
fun:function(){
console.log('我重写了原型对象');
}
}
// 此时constructor已经被我们重写搞没了
// 所以需要将constructor指回去
Animal.prototype = {
fun:function(){
console.log('我重写了原型对象');
}
constructor:Animal
}
通过上面的介绍,我们已经基本了解了构造函数实例对象的一套流程,以及他们的一些属性,接下来我们将讲一讲es5中的继承
构造函数的继承与原型链
在实际的使用场景中,Animal属于一种很宽泛的类,我们一般不会将其实例化,而是让更具体的类去继承他的属性
function Animal(){
// new有一个target属性
// 限制不能new Animal
if(new.target === Animal){
// 如果new了一个Animal类,就抛出错误
throw new Error('animal类不能被new 可以被继承')
}
this.type = '哺乳类'
}
Animal.prototype.eat = function(){
console.log('eat');
}
// 创建Tiger类并继承Animal类
function Tiger(){
// 继承父类的属性
Animal.call(this)
}
// 继承父类原型对象
Tiger.prototype.__proto__ = Animal.prototype
// 这里提前穿插一句es6的写法
// 上面继承原型对象写法很不优雅,于是es6有了
Object.setPrototypeOf(Tiger.prototype,Animal.prototype)
继承原型对象
上面在继承父类原型对象时,可能会产生这样的疑问,为什么不直接把子类的原型对象指向父类,像下面这样
// 错误写法
Tiger.prototype = Animal.prototype
这样写会造成,二者所指向的是同一块原型对象,那修改子类的原型对象时,父类也会跟着变,就乱套了
所以我们采用的是原型链的形式
Tiger.prototype.__proto__ = Animal.prototype
上图即为原型链,tiger实例会先从自己的__proto__也就是Tiger的原型对象开始寻找方法,找不到再从Tiger.prototype.__proto__寻找,找不到再从Animal.prototype.__proto__寻找,找不到再到Object.prototype.__proto__,最后到null没找到,那就确实没有
这,就是原型链
继承原型对象还有一种方法(不建议)
Tiger.prototype = Object.create(Animal.prototype)
这种方法相当于继承了创建了一个第三方的Animal.prototype,然后将Tiger.prototype指向这个第三方的Animal原型对象,也能完成操作
ES6
es6就有了类的概念,相对来说会便捷很多
类的创建
// es6里提供了类,类只能new
class Animal{
// es6为我们提供了constructor()写类自身的属性
constructor(){
if(new.target === Animal){
throw new Error('animal类不能被new 可以被继承')
}
this.type = '哺乳类'
}
// constructor()外面写的就是原型上的
eat(){
console.log('eat');
}
// 清清脑子,下面这样独特的写法虽然在外面,但是属于Animal类自己的静态属性
static get flag(){
return '动物'
}
}
// 类自己的静态属性当然可以直接调用
console.log(Animal.flag);
// 输出:动物
类的继承
// es6的继承
// extends自动进行了下面的操作
// Tiger.__proto__ = Animal
// Animal.call(this)
// Tiger.prototype = Object.create(Animal.prototype)
class Tiger extends Animal{
}
let tiger = new Tiger()
console.log(Tiger.flag);
console.log(tiger.type);
console.log(tiger.eat());
// 上面的内容都能正常输出
如果只是单纯的继承的话,上面就完成了
但是当我们要写一些自己的东西时,就要重新搞了
// 什么都不写,extends会帮你做好
// 但是 你如果你要自己重写constructor() 那就要重新搞了
class Tiger extends Animal{
constructor(){
// 在使用this之前必须调用super
super(); // 功能和Animal.call(this)
}
// 接下来,我们如果想在子类的属性或方法中调用父类的,就使用super
static getFlag(){
return super.flag
}
eat(){
// 重写类
// 调用父类的原型
super.eat()
console.log('我调用到了父类的方法');
}
}
let tiger = new Tiger()
console.log(Tiger.flag);
console.log(Tiger.getFlag());
console.log(tiger.eat());
// 上面的内容都能正常输出