javascript面向对象之继承

首页 编程分享 JQUERY丨JS丨VUE 正文

袅袅牧童 推荐 转载 编程分享 2019-08-12 03:13:04

简介 了《javascript高级程序设计》(第三版),但内容不局限于,网上很多关于js继承的相关内容都是来自于这本书,有兴趣的同学可以翻阅


原型链继承

我们先通过一个栗子,了解一下原型链继承。留意代码注释内容

//创建自定义构造函数
function Hqg() {
    this.name = '洪七公';
}
//在当前构造函数的原型链上添加属性skill
Hqg.prototype.skill = '打狗棒'

//通过自定义构造函数Hqg实例化一个对象gj
const gj = new Hqg()
console.log(gj.skill);//=>打狗棒
//通过自定义构造函数Hqg实例化一个对象hr
const hr = new Hqg()
console.log(hr.skill);//=>打狗棒

一个简单的栗子,郭靖和黄蓉都从洪七公那里继承到了skill 打狗棒,貌似没什么问题,我们继续看demo

//上面代码省略
const gj = new Hqg()//通过自定义构造函数Hqg实例化一个对象gj
gj.skill = "降龙十八掌"//重新对gj的skill赋值
console.log(gj.skill);//=>降龙十八掌
//通过自定义构造函数Hqg实例化一个对象hr
const hr = new Hqg()
console.log(hr.skill);//=>打狗棒

对郭靖的skill重新赋值,也没有影响黄蓉的skill,我们打印一下gj


gj的skill屏蔽掉了原型链上的skill,所以gj的skill是降龙十八掌,而hr的skill依然是打狗棒

问题即将暴露

function Hqg() {
    this.name = '洪七公';
}
Hqg.prototype.skill = ['打狗棒']

const gj = new Hqg()
gj.skill.push ("降龙十八掌")//找到了原型链中的skill,并对其执行push操作,从而改变了构造函数中的skill属性
console.log(gj.skill); //=>["打狗棒", "降龙十八掌"]
const hr = new Hqg()
//构造函数中的skill已经被改变
console.log(hr.skill); //=>["打狗棒", "降龙十八掌"]

总结一下,gj和hr都是Hqg的实例,继承Hqg的属性和方法,当Hqg的属性或者方被改变了,后面的实例也会受影响,有时候这并不是我们希望的结果

借用构造函数

借用?就是使用call或者apply改变一下this指向,
就是子类的构造函数内部通过call或者apply调用父类的构造函数,如果对call方法有不了解的地方,可以翻看昨天的文章
举一个栗子

//创建一个构造函数,并添加一些属性
function Hqg() {
    this.name = '洪七公';
    this.job = '帮主';
    this.skill = ['降龙十八掌', '打狗棒']
}
//创建一个构造函数,并借用了Hqg的构造函数
function Hr() {
        Hqg.call(this)
        this.name = '黄蓉';
        this.job = ['相夫', '教子']
}
//创建一个构造函数,并借用了Hqg的构造函数
function Gj() {
    Hqg.call(this)
    this.name = '郭靖';
    this.job = ['吃饭', '睡觉']

}
const hr = new Hr();
console.log(hr);

const gj = new Gj();
console.log(gj);

输出


这样就避免了原型链继承中,构造函数中的属性或者方法被其他实例所改变的问题
⚠️:这里要注意call方法的执行顺序:

//部分代码省略
function Hr() {
    this.name = '黄蓉';
    this.job = ['相夫', '教子']
    Hqg.call(this)
}
function Gj() {
    this.name = '郭靖';
    this.job = ['吃饭', '睡觉']
    Hqg.call(this)
}
//部分代码省略

如果call在之后执行就会导致一个问题


值会被覆盖,这个要注意!

借用构造函数进行传参

这个算是一个升级的玩法吧

function Hqg(name,job,skill) {
  this.name = name;
  this.job = job;
  this.skill = skill
}
function Hr() {
  Hqg.call(this,'黄蓉',['相夫', '教子'],['打狗棒'])
}

function Gj() {
  Hqg.call(this,'郭靖',['吃饭', '睡觉'],['降龙十八掌'])
}
const hr = new Hr();
console.log(hr);

const gj = new Gj();
console.log(gj);

输出


组合继承

将原型链和借用构造函数技术组合到一起。
使用原型链实现对原型属性和方法的继承,用借用构造函数模式实现对实例属性的继承。
这样既通过在原型上定义方法实现了函数复用,又能保证每个实例都有自己的属性
一个栗子

function Hqg(name) {
  this.name = name
  this.skill = ["降龙十八掌","打狗棒"]
}
Hqg.prototype.sayName = function () {
  console.log(this.name);
}
function Hero(name, job) {
  Hqg.call(this, name);
  this.job = job
}
Hero.prototype = new Hqg();
Hero.prototype.constructor = Hero;
Hero.prototype.sayJob = function () {
  console.log(this.job)
}
var gj = new Hero('郭靖', '吃饭睡觉');
gj.skill.push("九阴真经");
console.log(gj);
var hr = new Hero('黄蓉', '相夫教子');
console.log(hr);

先看下输出


我们把这个组合继承和之前的两个原型链继承和借用构造函数继承进行比较

不难发现组合继承融合了他们的优点,成为javascript中最常用的继承模式

原型式继承

这种继承方式没有使用严格意义上的构造函数,借助原型还可以基于已有的对象创建新对象,同时还不必因此创建自定义类型

function object(o) {
    function F() {}
    F.prototype = o;
    return new F();
}

在object函数内部,先创建一个临时性的构造函数F,然后将传入的对象作为这个构造函数的原型,最后返回这个临时类型的一个新实例。
本质上来说,object对传入其中的对象执行了一次浅复制。

function object(o) {
    function F() {}
    F.prototype = o;
    return new F();
}
const hqg = {
    name: "洪七公",
    skill: ['降龙十八掌', '打狗棒']
}

const gj = object(hqg);
gj.name = '郭靖';
gj.skill.push('九阴真经')

const hr = object(hqg);
hr.name = '黄蓉';
hr.skill.push('落英神剑掌');

console.log(gj);
console.log(hr);
console.log(hqg);

这种模式要去你必须有一个对象作为另一个对象的基础。
在这个例子中,hqg作为另一个对象的基础,把hqg传入object中,该函数就会返回一个新的对象。
这个新对象将hqg作为原型,所以它的原型中就包含一个基本类型和一个引用类型。
所以意味着如果还有另外一个对象关联了hqg,gj和hr修改数组skill的时候,也会体现在这个对象中。
实际上就相当于创建了hqg对象的两个副本


ES5新增Object.create规范了原型式继承

Object.create

Object.create这是什么?
先简单了解一下

object.create() 是使用指定的原型proto对象及其属性propertiesObject去创建一个新的对象。

 Object.create(proto,propertiesObject)

proto 是必填参数,就是新创建出来的对象的原型 (新对象的 __proto__属性指向的对象),值得注意的是当proto为null的时候创建的新对象完全是一个空对象,没有原型,也就是没有继承Object.prototype上的方法。(如hasOwnProperty() toString() 等)

 console.log(Object.create(null));//这么创建出来的对象特别干净
console.log(new Object());//做比较
console.log({});//做比较


所以我们也可以这么说,以下三种创建等效

const a = {};
const b = new Object()
const c = Object.create(Object.prototype);

propertiesObject是可选参数,作用就是给新对象添加新属性以及描述器,需要注意的是新添加的属性是新对象自身具有的属性也就是通过hasOwnProperty() 方法可以获取到的属性,而不是添加在原型对象里。
第二个是可选参数具体可以看这里
不展开讨论
看一个栗子,重点看gj.favourite

const hqg = {
    name: "洪七公",
    skill: ['降龙十八掌']
};
const gj = Object.create(hqg, {
    favourite: {
        value: "吃", //favourite 属性值
        writable: true //属性可写
    }
});
gj.name = "郭靖"
gj.skill.push('九阴真经')
gj.favourite = "睡觉"
console.log(gj.favourite);//=>睡觉
const hqg = {
    name: "洪七公",
    skill: ['降龙十八掌']
};
const gj = Object.create(hqg, {
    favourite: {
        value: "吃", //favourite 属性值
        writable: false //属性不可写
    }
});
gj.name = "郭靖"
gj.skill.push('九阴真经')
gj.favourite = "睡觉"
console.log(gj.favourite);//=>吃

favourite下属性还有很多,不再一一列举,可参看这里查看

区别

  1. Object.cerate()继承指定对象
  2. new Object() 继承内置对象Object
  3. 可以通过Object.create(null) 创建一个干净的对象,也就是没有原型,而 new Object() 创建的对象是 Object的实例,原型永远指向Object.prototype。

寄生式继承

寄生式继承是与原型式继承紧密相关的一种思路,它创造一个仅用于封装继承过程的函数,在函数内部以某种方式增强对象,最后再返回对象。

function object(o) {
    function F() {}
    F.prototype = o;
    return new F();
}

function hero(params) {
    const clone = object(params) //通过调用函数创建一个新对象
    clone.sayHi = () => {
        console.log('hi');
    }
    return clone
}
const hqg = {
    name: "洪七公",
    skill: ['降龙十八掌', '打狗棒']
}
const gj = hero(hqg);
gj.name = '郭靖';
gj.skill.push('九阴真经')
console.log(gj);


寄生式继承和原型式继承,方法接近

寄生组合式继承

组合继承有个弊端就是会调用两次被继承者的构造函数,解决方法就是使用寄生组合式继承。这又是什么呢?这个相对之前的比较复杂,但是高效的一点是只调用一次被继承者构造函数,原理就是通过寄生方式创建一个被继承者的副本,副本和被继承者共用一个prototype,这样就解决了之前的问题

function object(o) {
    function F() {}
    F.prototype = o;
    return new F();
}

function hero(Children, Hqg) {
    const proto = object(Hqg.prototype); //返回Hqg的一个副本
    proto.constructer = Children; //设置constructor指向, 因为新副本的原型对象被重写
    Children.prototype = proto; //副本作为sub的原型对象
}

function Hqg() {
    this.name = '洪七公';
}
Hqg.prototype.show = function () {
    console.log(this.name);
}

function Gj() {
    Hqg.call(this);
    this.name = '郭靖'
}

function Hr() {
    Hqg.call(this);
    this.name = '黄蓉'
}
hero(Gj, Hqg);
hero(Hr, Hqg);
const gj = new Gj();
const hr = new Hr();
gj.show(); // =>郭靖
hr.show(); //=> 黄蓉

转载链接:https://www.qdtalk.com/2018/12/16/javascript面向对象之es5和es6的继承/


Tags:


本篇评论 —— 揽流光,涤眉霜,清露烈酒一口话苍茫。


    声明:参照站内规则,不文明言论将会删除,谢谢合作。


      最新评论




ABOUT ME

Blogger:袅袅牧童 | Arkin

Ido:PHP攻城狮

WeChat:nnmutong

Email:nnmutong@icloud.com

标签云