理解对象继承

对象继承 什么是继承
继承可以使得子类具有父类的属性和方法或者重新定义、追加属性和方法
【理解对象继承】实现继承就需要完成两件事

  1. 子类可以获取父类的属性和方法
  2. 子类可以追加属性方法
怎么实现继承
对于js 来说,每个对象都可以添加属性和方法,所以我们更关注的是如何获取父类的属性和方法
根据js 的 原型链 的特性 我们可以通过原型链来获取父类的属性和方法
原型链为什么可以获取父类的属性和方法 当试图得到一个对象的某个属性时,
  1. 如果这个对象本身没有这个属性,
  2. 那么会去它的*proto*(即他的构造函数的prototype)中寻找。
  3. 如果没有,则会接着往上找,一直上溯到Object.prototype,知道null为止
也就是说所有对象都继承Object.prototype的属性,Object.prototype的原型是null,null没有任何属性和方法。
所以 只要把父类的属性和方法放在子类的构造函数的prototype 或者在之前的构造函数的prototype,就可以获取到父类的属性和方法
现有的继承方法
  1. 原型链继承
  2. 借用构造函数继承 伪造对象 或经典继承
  3. 组合继承(组合原型链继承和借用构造函数继承)(常用)
  4. 原型式继承
  5. 寄生式继承
  6. 寄生组合式继承(最理想)
  7. ES6继承 (class)(extends)
现在来实现对以下构造函数的继承
function Person(){ this.name = 'asa' ; this.age= '11'; this.class = ['en','math']; this.sayName = function(){ alert(this.name); } }Person.prototype.like = 'fruit'; Person.prototype.sayHi = function(){ alert('Hi'); }

1. 原型链继承 -- 【原型链】
顾名思义 直接使用原型链继承。
首先创建一个对象,
1.基本方式创建对象
var a = {a:b}; var b = new Object();

2.构造函数创建对象
function Child (name) { this.name = name; }

图2 为没有这一行 我们先不看继承的person
Child.prototype = new Person();

生成两个实例
var per1 = new Child("la"); var per2 = new Child("laa");

输出一下就发现已经获取到父类的属性和方法
console.log(per1.like); // fruit console.log(per1.age); //11 console.log(per1.name); //la per1.sayName(); per1.sayHi(); console.log(per2.like); // fruit console.log(per2.age); //11 console.log(per1.name); //laa per2.sayName(); per1.sayHi();

观察原型链
查看原型链 就相当与 a ,b 继承自Object
理解对象继承
文章图片
图1 构造函数生成的实例
看图可以知道 Child实例Per可以获取到 Child.prototype上的属性和方法
其实这就相当于与一个继承 Per 继承 Function Child的原型对象
为什么构造函数生成的对象 会继承构造函数的原型对象呢 这就是new干的事了
new会把新生成的对象的__proto__指向构造函数的原型对象
理解对象继承
文章图片
图2 但是我们要实现的继承 是对一个指定的类型继承,我们还要继承Person
查看上面的关键一行代码 就发先他就干了一件事~
Child.prototype = new Person();
如下图
橙色是记这个代码之前的原型链
由于 Child.prototype = new Person(); 所以 Child的原型就指向了Person实例的原型对象
好了这就完成了指定类的继承,这就是最基本的原型链继承
理解对象继承
文章图片
图3 这种办法最为简单但是也有很多问题,所以一般都不会单独使用哒
//问题 per1.calss.push('PE'); console.log(per1.class) // ['en','math','PE']; console.log(per2.class) //['en','math','PE'];

问题:
  1. 引用类型的问题 【所有实例继承的引用类型都指向同一个地址 所以per1 操作的时候 Per2 的值也会改变】
  2. 继承时不能向父类中传递参数
2. 借用构造函数继承 伪造对象 或经典继承
为了解决原型链继承的问题就有了经典继承的方法
怎样解决引用类型的问题,那就是每个实例都有独立的属性,而不是去获取原型链上的属性,这样就不会相互影响了,在实例生成的时候就立刻给这个实例附上所有属性和方法
代码:
function Child2(name){ Person.call(this,"jer",10)// 调用了 父构造函数 可以传参 提高自由度 // Person2.call(this,"") 多个构造函数多继承 this.name = name; }

这个实际上就不是把属性和方法放在原型上 ,而是直接添加到每个子实例中 添加方法就是运行一遍父构造函数,运行的时候还可以传参,就同时解决了第二个传参的问题
var per3 = new Child2('rr'); var per4 = new Child2('ss');

验证一下 就发现 Person 已经不在 实例的原型上了,因为我们只是把构造函数当做普通函数运行了一遍
console.log(per3 instanceoof Person)

验证一下 果然就不会产生两个实例的属性已经互不影响了
per3.calss.push('PE'); console.log(per3.class) // ['en','math','PE']; console.log(per4.class) //['en','math'];

原型链 与上面没有继承父类的原型链长得一模一样 ,但是每个实例上都添加了方法和属性,这就产生了新的问题。这样就会发现
问题1
我们看着两个函数,根据上面的代码运行后 就生产了两个作用一致代码一致并且不会变动的函数,这就会造成冗余了
其实原型链那种方式 这两个函数也是有冗余的,但是那种方法可以把这个函数放在超类的原型上,所以实际没有这种问题
per3.sayName(); per4.sayName();

问题2
Person 不在原型链上 所以Person的原型对象上的方法也就不能被子类实例所使用啦
per3.sayHi(); // 会报错 没有该函数 per4.sayHi();

问题 :
  1. 冗余问题
  2. 继承时不能向父类中传递参数
由于以上问题,我们一般也不单独使用借用构造函数哒~
3.最常用的方法 -- 组合继承
那么,既然两种方法互补,那么合成大法来了---组合继承
想一下 把sayName这种公用的方法,或者公用的属性,放在原型链上,用原型链方式继承
class这种独有的属性,借用构造函数继承,就很完美了~
代码: 借用构造函数继承属性 此时 Person调用一次
function Child3(name){ Person.call(this,name); }

组合原型继承方法 new的时候又调用一次
Child3.prototype = new Person(); var per5 = new Child3("xiaomi"); var per6 = new Child3("xiaoming");

输出一下就发现已经获取到父类的属性和方法
console.log(per1.like); // fruit console.log(per1.age); //11 per1.sayName(); console.log(per2.like); // fruit console.log(per2.age); //11 per2.sayName();

验证一下 这两个实例的属性也是互不影响了
per3.calss.push('PE'); console.log(per3.class) // ['en','math','PE']; console.log(per4.class) //['en','math'];

而且可以获取 原型链上的方法 可以有公共的方法
per5.sayHi(); per6.sayHi();

组合继承很好的避免了原型链和借用构造函数的缺陷,融合了他们的优点
问题 不过组合继承也有一个小小的问题,那就是超类构造函数会运行两次
以下几个有时间再好好看,现在还不太理解,暂时放着 //todo
4.原型式继承
5.寄生式继承
6.寄生组合式继承(最理想)
7.ES6继承 (class)(extends)
es6 出了新的语法糖class之间通过使用extends关键字,这比通过修改原型链实现继承,要方便清晰许多
新的class写法只是让对象原型的写法更加清晰、更像面向对象编程的语法而已。实际上还是运用上面的组合继承的方法。通过原型链继承方法,借用了构造函数实现专有属性。
代码: 先来看class
class 我的理解是 class 更像是构造函数的原型对象,他可以找到构造函数,并可以存放属性和方法
class Point{ constructor(x,y){ this.x = x; this.y = y; } toString(){ console.log(this.x); } }

es5 实现 你就发现 class 实际就是一个构造函数 和 原型上的方法组成~ 不过就是更简单清晰了
function Point(x,y){ this.x = x; this.y = y; }Point.prototype.toString(){ console.log(this.x); }

再来看extends
class Colorpoint extends Point { //这个就是默认方法使用new 生成实例时会调用这个方法, 会把Point加在原型链上 //如果未定义 会自动添加 constructor(x,y,color){//子类必须在constructor方法中调用super方法,否则新建实例时会报错 //这是因为子类没有自己的this对象,而是继承父类的this对象,然后对其进行加工,如果不调用super方法,子类就得不到this对象。 super(x,y); //调用父类构造函数(Point.prototype.constructor.call(this,x,y)) this.color = color//隐式返回 this // 如果显示返回对象 就是该对象 } toString(){ //通过 super调用父类的方法 return this.color + ' ' + super.toString(); } }

差异
ES5 的继承,实质是先创造子类的实例对象this,然后再将父类的方法添加到this上面(Parent.apply(this))。ES6 的继承机制完全不同,实质是先将父类实例对象的属性和方法,加到this上面(所以必须先调用super方法),然后再用子类的构造函数修改this

    推荐阅读