面向对象(继承)--总结

分手后的思念是犯贱 2022-01-06 03:59 449阅读 0赞

1.面向对象(继承)—原型链01

假如我们让原型对象等于另一个类型的实例,结果会怎么样呢?显然,此时的原型对象将包含一个指向另一个原型的指针,相应地,另一个原型中也包含着一个指向另一个构造函数的指针。假如另一个原型又是另一个类型的实例,那么上述关系依然成立,如此层层递进,就构成了实例与原型的链条。

  1. function SuperType() {
  2. this.property = true;
  3. }
  4. SuperType.prototype.getSuperValue = function() {
  5. return this.property;
  6. };
  7. function SubType() {
  8. this.subproperty = false;
  9. }
  10. //继承了 SuperType
  11. SubType.prototype = new SuperType();
  12. SubType.prototype.getSubValue = function() {
  13. return this.subproperty;
  14. };
  15. var instance = new SubType();
  16. alert(instance.getSuperValue()); //true

70

存在的问题:包含引用类型值的原型。引用类型值的原型属性会被所有实例共享。

  1. function SuperType() {
  2. this.colors = ["red", "blue", "green"];
  3. }
  4. function SubType() {}
  5. //继承了 SuperType
  6. SubType.prototype = new SuperType();
  7. var instance1 = new SubType();
  8. instance1.colors.push("black");
  9. alert(instance1.colors); //"red,blue,green,black"
  10. var instance2 = new SubType();
  11. alert(instance2.colors); //"red,blue,green,black"

这个例子中的 SuperType 构造函数定义了一个 colors 属性,该属性包含一个数组(引用类型值)。SuperType 的每个实例都会有各自包含自己数组的 colors 属性。当 SubType 通过原型链继承了SuperType 之后, SubType.prototype 就变成了 SuperType 的一个实例,因此它也拥有了一个它自己的 colors 属性——就跟专门创建了一个 SubType.prototype.colors 属性一样。但结果是什么呢?结果是 SubType 的所有实例都会共享这一个 colors 属性。而我们对 instance1.colors 的修改能够通过 instance2.colors 反映出来。

港真,上面的话理解起来还是很难的,老老实实画图说吧

watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2ppYW9qc3Vu_size_16_color_FFFFFF_t_70

2. 借用构造函数

基本思想:即在子类型构造函数的内部调用超类型构造函数。函数只不过是在特定环境中执行代码的对象,因此通过使用 apply()和 call()方法也可以在(将来)新创建的对象上执行构造函数。

  1. function SuperType() {
  2. this.colors = ["red", "blue", "green"];
  3. }
  4. function SubType() {
  5. // 继承了 SuperType
  6. console.log(this)
  7. SuperType.call(this);
  8. }
  9. var instance1 = new SubType();
  10. instance1.colors.push("black");
  11. alert(instance1.colors); //"red,blue,green,black"
  12. var instance2 = new SubType();
  13. alert(instance2.colors); //"red,blue,green"

代码中加粗的那一行代码“借调”了超类型的构造函数。通过使用 call() 方法(或 apply() 方法也可以),我们实际上是在(未来将要)新创建的 SubType 实例的环境下调用了 SuperType 构造函数。这样一来,就会在新 SubType 对象上执行 SuperType() 函数中定义的所有对象初始化代码。结果,SubType 的每个实例就都会具有自己的 colors 属性的副本了。

watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2ppYW9qc3Vu_size_16_color_FFFFFF_t_70 1

存在问题

借用构造函数的技术也是很少单独使用的。方法都在构造函数中定义,因此函数复用就无从谈起了。而且,在超类型的原型中定义的方法,对子类型而言也是不可见的。

3.组合继承(最常用)

使用原型链实现对原型属性和方法的继承,通过借用构造函数来实现对实例属性的继承。

  1. function SuperType(name) {
  2. this.name = name;
  3. this.colors = ["red", "blue", "green"];
  4. }
  5. SuperType.prototype.sayName = function () {
  6. alert(this.name);
  7. };
  8. function SubType(name, age) {
  9. //继承属性
  10. SuperType.call(this, name);
  11. this.age = age;
  12. }
  13. //继承方法
  14. SubType.prototype = new SuperType();
  15. /* 每创建一个函数,就会同时创建它的 prototype 对象,
  16. 这个对象也会自动获得 constructor 属性 */
  17. SubType.prototype.constructor = SubType;
  18. SubType.prototype.sayAge = function () {
  19. alert(this.age);
  20. };
  21. var instance1 = new SubType("Nicholas", 29);
  22. instance1.colors.push("black");
  23. alert(instance1.colors); //"red,blue,green,black"
  24. instance1.sayName(); //"Nicholas";
  25. instance1.sayAge(); //29
  26. var instance2 = new SubType("Greg", 27);
  27. alert(instance2.colors); //"red,blue,green"
  28. instance2.sayName(); //"Greg";
  29. instance2.sayAge(); //27

在这个例子中, SuperType 构造函数定义了两个属性: name 和 colors 。 SuperType 的原型定义了一个方法 sayName() 。 SubType 构造函数在调用 SuperType 构造函数时传入了 name 参数,紧接着又定义了它自己的属性 age 。然后,将 SuperType 的实例赋值给 SubType 的原型,然后又在该新原型上定义了方法 sayAge() 。这样一来,就可以让两个不同的 SubType 实例既分别拥有自己属性——包括 colors 属性,又可以使用相同的方法了。
组合继承避免了原型链和借用构造函数的缺陷,融合了它们的优点,成为 JavaScript 中最常用的继承模式。

watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2ppYW9qc3Vu_size_16_color_FFFFFF_t_70 2

4.原型式继承

ECMAScript 5 的 Object.create() 方法规范化了原型式继承。这个方法接收两个参数:一个用作新对象原型的对象和(可选的)一个为新对象定义额外属性的对象。在传入一个参数的情况下,Object.create() 与 object() 方法的行为相同。

那先回忆一下什么叫object()

  1. <script>
  2. function object(o){
  3. function F(){}
  4. F.prototype = o;
  5. return new F();
  6. }
  7. var person = {
  8. name: "Nicholas",
  9. friends: ["Shelby", "Court", "Van"]
  10. };
  11. var anotherPerson = object(person);
  12. console.log(anotherPerson)
  13. </script>

20190625125812410.png

watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2ppYW9qc3Vu_size_16_color_FFFFFF_t_70 3

然后看看Object.create()的用法

  1. var person = {
  2. name: "Nicholas",
  3. friends: ["Shelby", "Court", "Van"]
  4. };
  5. var anotherPerson = Object.create(person, {
  6. name: {
  7. value: "Greg"
  8. }
  9. });
  10. console.log(anotherPerson);
  11. alert(anotherPerson.name); //"Greg"

打印

watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2ppYW9qc3Vu_size_16_color_FFFFFF_t_70 4

如果只想让一个对象与另一个对象保持类似的情况下,原型式继承是完全可以胜任的。不过别忘了,包含引用类型值的属性始终都会共享相应的值,就像使用原型模式一样。

5.寄生式继承

寄生式函数是什么样的:①创建一个仅用于封装继承过程的函数,②该函数在内部以某种方式来增强对象,③返回对象。以下代码示范了寄生式继承模式。

  1. <script>
  2. function object(o){
  3. function F(){}
  4. F.prototype = o;
  5. return new F();
  6. }
  7. function createAnother(original) {
  8. var clone = object(original); //通过调用函数创建一个新对象
  9. clone.sayHi = function () { //以某种方式来增强这个对象
  10. alert("hi");
  11. };
  12. return clone; //返回这个对象
  13. }
  14. var person = {
  15. name: "Nicholas",
  16. friends: ["Shelby", "Court", "Van"]
  17. };
  18. var anotherPerson = createAnother(person);
  19. console.log(anotherPerson)
  20. </script>

20190625141257381.png

好处:在主要考虑对象而不是自定义类型和构造函数的情况下有用。

问题:使用寄生式继承来为对象添加函数,会由于不能做到函数复用而降低效率。

6. 寄生组合式继承

组合继承的问题是有两组 name 和 colors 属性:一组在实例上,一组在 SubType 原型中。寄生组合式继承可以解决这个问题

先回忆一下object()函数

  1. function object(o){
  2. function F(){}
  3. F.prototype = o;
  4. return new F();
  5. }

寄生组合式继承的基本模式如下所示。

  1. function inheritPrototype(subType, superType) {
  2. var prototype = object(superType.prototype); //创建对象
  3. prototype.constructor = subType; //增强对象
  4. subType.prototype = prototype; //指定对象
  5. }

在函数内部,第一步是创建超类型原型的一个副本。第二步是为创建的副本添加 constructor 属性,从而弥补因重写原型而失去的默认的 constructor 属性。最后一步,将新创建的对象(即副本)赋值给子类型的原型。

  1. function SuperType(name) {
  2. this.name = name;
  3. this.colors = ["red", "blue", "green"];
  4. }
  5. SuperType.prototype.sayName = function () {
  6. alert(this.name);
  7. };
  8. function SubType(name, age) {
  9. SuperType.call(this, name);
  10. this.age = age;
  11. }
  12. inheritPrototype(SubType, SuperType);
  13. SubType.prototype.sayAge = function () {
  14. alert(this.age);
  15. };

这个例子的高效率体现在它只调用了一次 SuperType 构造函数,并且因此避免了在 SubType.prototype 上面创建不必要的、多余的属性。与此同时,原型链还能保持不变;

watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2ppYW9qc3Vu_size_16_color_FFFFFF_t_70 5

蓝色的线表示 inheritPrototype里面的内容

发表评论

表情:
评论列表 (有 0 条评论,449人围观)

还没有评论,来说两句吧...

相关阅读

    相关 面向对象-继承

    继承: 1、提高了代码的复用性 2、让类与类之间产生了关系,有了这个关系,才有了多态的特性。 注意:千万不要为了获取其他类的功能,简化代码而继承。 必

    相关 面向对象 继承

    利用封装和继承的特性完成如下操作: 小学生: 属性: 姓名 学号 年龄 性别 行为: 学习 打架 中学生: 属性: 姓名 学号 年龄 性别 行为

    相关 面向对象——继承

    继承的特点 1,描述类与类之间的关系 2,降低类和类之间的重复代码 3,降低对象和对象之间的代码重复使用静态变量 4,降低类与类之间的代码重复使用就用继承

    相关 面向对象继承

    今日类容 1.什么是继承 2.继承的基本语法 3.继承与抽象 4.属性的查找顺序 5.派生与覆盖 6.如何实现一个可以限制元素的类型的列表 7.子类访问父类的内容