我来重新学习js 的面向对象(part 5)

2018-10-14  本文已影响2人  dnaEMx

这是最后的最后了,我会顺便总结一下各种继承方式的学习和理解。(老板要求什么的,管他呢)

一、继承-组合继承、伪经典继承

image
图片来自:http://www.joyme.com/xinwen/201711/22209132.html

这是一种将原型链和借用构造函数的技术结合起来的一种继承模式。不是假合体,是真合体!

核心思想是:

很像之前说过的组合使用构造函数模式和原型模式

// 父类构造函数
function Food(name) {
  this.name = name;
  this.colors = ["red", "blue"];
}
// 父类原型对象的方法
Food.prototype.sayName = function() {
  console.log("我是" + this.name);
};
// 子类构造函数
function Fruit(name, place) {
  // 在构造函数里面调用父类搞糟函数,实现属性继承
  Food.call(this, name);
  this.place = place;
}
// 将父类的实例赋值给子类的原型对象,实现方法继承
Fruit.prototype = new Food();
// 添加子类原型对象的方法
Fruit.prototype.sayPlace = function() {
  console.log(this.place);
};

var food1 = new Fruit("苹果", "非洲");
food1.colors.push("black");
console.log(food1.colors); // 返回 [ 'red', 'blue', ' black' ]
food1.sayName(); // 返回 我是苹果
food1.sayPlace(); // 返回 非洲

var food2 = new Fruit("香蕉", "亚洲");
food2.colors.push("yellow");
console.log(food2.colors); // 返回 [ 'red', 'blue', 'yellow' ]
food2.sayName(); // 返回 我是香蕉
food2.sayPlace(); // 返回 亚洲

也存在一些问题

// 。。。。。。。。
// 子类构造函数
function Fruit(name, place) {
  // 在构造函数里面调用父类搞糟函数,实现属性继承
  Food.call(this, name); // 第二次调用父类构造函数
  this.place = place;
}
// 将父类的实例赋值给子类的原型对象,实现方法继承
Fruit.prototype = new Food(); // 第一次调用父类构造函数
Fruit.prototype.constrcutor=Fruit;//因重写原型而失去constructor属性,所以要对constrcutor重新赋值
// 添加子类原型对象的方法
Fruit.prototype.sayPlace = function() {
  console.log(this.place);
};
// 。。。。。。。

在一般情况下,这是我们在 javascript 程序开发设计中比较常用的继承模式了。

基于以上原因,我们需要引入寄生组合式继承来解决它的存在的问题,实现完美的继承。但是在了解它之前,需要先了解寄生式继承,而了解寄生式继承之前,需要了解原型式继承,他们是一个接一个的推导出来的。

二、继承-原型式继承

image
图片来自:http://acg.shunwang.com/2014/0702/61262.shtml

核心思想是借助原型可以基于已有的对象创建新对象,同时不必因此创建自定义类型。

// 原型式继承的关键-复制
function object(o) {
  function F() {}
  F.prototype = o;
  return new F();
}

var food1 = {
  name: "苹果",
  colors: ["red", "blue"]
};
// 继承
var food2 = object(food1);

food2.name = "香蕉";
food2.colors.push("black");

//。。。。。。无限增殖

console.log(food1.name); // 返回 苹果
console.log(food2.name); // 返回 香蕉
console.log(food1.colors); // 返回 [ 'red', 'blue', 'black' ]
console.log(food2.colors); // 返回 [ 'red', 'blue', 'black' ]

2.1 使用 es5的新语法:Object.create()

Object.create()方法会创建一个新对象,使用现有的对象来提供新创建的对象的__proto__

Object.create()是es5新增的,用来规范原型式继承。

如果单纯使用的话,效果跟之前的差别不大,参考下面例子:

var food1 = {
  name: "苹果",
  colors: ["red", "blue"]
};

var food2 = Object.create(food1);
food2.name = "香蕉";
food2.colors.push("black");

console.log(food1.name); // 返回 苹果
console.log(food2.name); // 返回 香蕉
console.log(food1.colors); // 返回 [ 'red', 'blue', 'black' ]
console.log(food2.colors); // 返回 [ 'red', 'blue', 'black' ]

如果注意使用它的第二个参数的话,差别就不一样了:


image
var food1 = {
  name: "苹果",
  colors: ["red", "blue"]
};

var food2 = Object.create(food1, {
  name: { value: "香蕉" },
  colors: { // !!!!!
    value: ["red", "blue", "black"]
  }
});

console.log(food1.name); // 返回 苹果
console.log(food2.name); // 返回 香江
console.log(food1.colors); // 返回 [ 'red', 'blue' ]  !!!!!
console.log(food2.colors); // 返回 [ 'red', 'blue', 'black' ]

可以看到引用类型的数值不会被共享,实现了很好的继承效果。

出现这个情况主要是因为如果使用 push 的话,还是操作同一个内存指针,使用Object.create的话,会重新添加到新创建对象的可枚举属性,不是同一个内存指针了。

2.2 发现一些有价值的东西

image
图片来自:http://www.cifnews.com/article/20498

参考 mdn 里面的介绍,会发现一些更有价值的东西,可以用 Object.create实现类式继承:

// Shape - 父类(superclass)
function Shape() {
  this.x = 0;
  this.y = 0;
}

// 父类的方法
Shape.prototype.move = function(x, y) {
  this.x += x;
  this.y += y;
  console.info('Shape moved.');
};

// Rectangle - 子类(subclass)
function Rectangle() {
  Shape.call(this); // call super constructor.
}

// 子类续承父类
Rectangle.prototype = Object.create(Shape.prototype);
Rectangle.prototype.constructor = Rectangle;

var rect = new Rectangle();

console.log(rect instanceof Rectangle); // true
console.log(rect instanceof Shape); // true
rect.move(1, 1); // Outputs, 'Shape moved.'

还有更屌炸飞的东西,如果你希望能继承到多个对象,则可以使用混入的方式。

function MyClass() {
     SuperClass.call(this);
     OtherSuperClass.call(this);
}

// 继承一个类
MyClass.prototype = Object.create(SuperClass.prototype);
// 混合其它
Object.assign(MyClass.prototype, OtherSuperClass.prototype);
// 重新指定constructor
MyClass.prototype.constructor = MyClass;

MyClass.prototype.myMethod = function() {
     // do a thing
};

与时俱进,红宝书《javascript 高级程序设计第三版》 也并不是无敌的,当然,一下子知识量太大,我们吸收不了,所以这里不展开细说。

三、继承-寄生式继承

在引入寄生组合式继承之前,需要了解什么是寄生式继承。

image
图片来自:https://2ch.hk/b/arch/2017-01-08/res/143930826.html

寄生式继承的思路跟寄生构造函数模式和工厂模式很类似,核心思想是创建一个仅用于封装继承过程的函数,该函数在内部以某种方式来增强对象,最后再像真得是它做了所有工作一样返回对象。

感觉像是原型式继承的升级版!

// 原型式继承的关键-复制
function object(o) {
  function F() {}
  F.prototype = o;
  return new F();
}

function createFood(original) {
  var clone = object(original);
  clone.sayName = function(name) {
    console.log(name);
  };
  return clone;
}

var food1 = {
  name: "苹果"
};
var food2 = createFood(food1);
console.log(food2.name); // 返回苹果
food2.sayName("香蕉"); // 返回香蕉

这是一种比较简单的实现继承的方式,在不考虑自定义类型和构造函数的情况下,也算是一种有用的模式。

四、继承-寄生组合式继承

终于到了主角了。

image
图片来自:https://www.9yread.com/book/10001318

寄生组合式继承的核心思想是:

好复杂的解释,先看看代码吧:

// object 函数可以用 Object.create 来代替。
function object(o) {
  function F() {}
  F.prototype = o;
  return new F();
}

// 这里是关键
function inheritPrototype(subType, superType) {
  // ①将超类原型放到一个临时的对象里面(创建超类型圆形的副本)
  var prototype = object(superType.prototype);
  // ②重新指定这个临时对象的constructor 为 子类构造函数
  prototype.constructor = subType;
  // ③将这个临时对象赋值给子类的原型对象
  subType.prototype = prototype;
}

function Food(name) {
  this.name = name;
  this.colors = ["red", "blue"];
}

Food.prototype.sayName = function() {
  console.log(this.name);
};

function Fruit(name, place) {
  Food.call(this, name);
  this.place = place;
}

inheritPrototype(Fruit, Food);
Fruit.prototype.sayPlace = function() {
  console.log(this.place);
};

var food1 = new Fruit("苹果", "非洲");
var food2 = new Fruit("香蕉", "亚洲");
console.log(food1.sayName()); // 返回 苹果
console.log(food1.sayPlace()); // 返回 非洲

food1.colors.push("black");
console.log(food1.colors); // 返回 [ 'red', 'blue', 'black' ]
console.log(food2.colors); // 返回 [ 'red', 'blue' ]

console.log(food1 instanceof Fruit); // 返回 true
console.log(food1 instanceof Food); // 返回 true
console.log(Fruit.prototype.isPrototypeOf(food1)); // 返回 true
console.log(Food.prototype.isPrototypeOf(food1)); // 返回 true

object 函数可以用 Object.create来代替。

借助这个图理解一下,这种继承模式拆开来看就是寄生式(复制)+组合式(原型链+构造函数)

image
图片来自https://www.jianshu.com/p/0045cd01e0be

总结

几乎涵盖了所有 javascript 的继承模式了:

image
图片来自:https://zhuanlan.zhihu.com/p/41656666

有几点是我觉得可以总结一下,前人栽树,后人乘凉:

  1. 书不要读死,如果单纯读《javascript 高级程序设计第三版》是不可能完整了解 javascript 的,起码在面向对象这部分是不行的,很多网上的大(zhuang)牛(bi)都会叫你认真阅读这本书,但是对于初学者来说,基本是很难理解得到作者的思路和意思的,不是资质问题,是阅历和经验和知识含量不足的限制。
  2. 看不懂,不要紧,多看,多查阅资料,记得用 google 查,baidu 只会让你多了解一些广告罢了。
  3. 网上的文章质量也是参差不齐的,就算是我这篇装逼文,也是我自己觉得很好,但是未必能够面面俱到,但是人生本来就难以面面俱到,不是吗?重要的是,我用我的经验写了,你能看明白一些是一些,看不明白就当饭后尔尔罢了,不用纠结。
  4. 要自己做实验,自己输出一些结果,对比理论,对比别人的结果和分析,这样才能理解得好一些。
  5. 学习第一次发现完全懵逼的话,就尝试去组织一个脉络结构,就好像我这样,尝试做一个故事代入,一环扣一环来理解,虽然《javascript 高级程序设计第三版》这本书里面也有,但是感觉后面开始省略很多一部分了,以致迷失了。
  6. 不要怕,多学习,莫道前路无知己,天下谁人不识君,加油加油,也是自勉。

参考内容

  1. 红宝书,javascript 高级程序设计第三版

原文转载:
https://www.godblessyuan.com/2018/08/%E6%88%91%E6%9D%A5%E9%87%8D%E6%96%B0%E5%AD%A6%E4%B9%A0%20js%20%E7%9A%84%E9%9D%A2%E5%90%91%E5%AF%B9%E8%B1%A1-5.html

上一篇下一篇

猜你喜欢

热点阅读