资讯专栏INFORMATION COLUMN

重温JS基础--继承

sixleaves / 2224人阅读

摘要:继承了如上,我们通过方法借调了超类的构造函数,实际上是在新创建的实力环境下调用了构造函数。组合继承组合继承的基本思想将原型链和借用构造函数的技术组合到一块,从而发挥二者之长的一种继承模式。继承方法在上面这个例子中,构造函数定义了两个属性和。

在ECMAScript中只支持实现继承,而且实现继承主要是依靠原型链来实现的。

1. 什么是原型链

继承基本思想:利用原型让一个引用类型继承另一个引用类型的属性和方法。
构造函数,原型对象和实例对象的关系:每个构造函数都有一个原型对象,原型对象包含一个指向构造函数的指针,实例对象包含一个指向原型对象的内部指针。
假如我们让原型对象等于另外一个构造函数的实例,那么此时的原型对象将包含一个指向另外一个原型对象的指针,且该被指向的原型对象包含一个指向另一个构造函数的指针。假如另一个原型对象又是另一个类型的实例,那么上述关系依然成立,如此层层递进,就构成了实例与原型的链条,这就是原型链的基本概念。用代码描述如下:

function Super() {
    this.property = true
}
Super.prototype.getSuperValue = function() {
    return this.property
}

function Sub() {
    this.subproperty = false


//继承Super
Sub.prototype = new Super()
Sub.prototype.getSubValue = function() {
    return this.subproperty
}
var instance = new Sub()
console.log(instance.getSuperValue()) //true

如上代码,很好的描述了利用原型链进行继承。Super和Sub两个构造函数。每个类型分别有一个属性和一个方法。Sub继承了Spuer,而继承时通过创建了Super的实例,并将该实例赋给Sub.prototype实现。这实现的本质是重写了原型对象。在确立继承关系后我们又给Sub.prototype上面加了一个新方法。注意现在instance.constructor现在指向的值Super,因为Sub.prototype中的constructor被重写了。

2. 别忘记默认的原型

我们知道,所有引用类型默认都继承Object, 而这个继承也是通过原型链实现的。所有函数的默认原型对象都是Object的实例。因此默认原型都会包含一个指向内部的指针,指向Object.prototype。

3. 确定原型和实例的关系

可以通过两种方式来确定原型和实例之间的关系。第一种是通过instanceof操作符,只要用这个操作符来测试实例与原型链中出现过的构造函数,结果就会返回true。

instance instanceof Object //true
instance instanceof Super // true
instance instanceof Sub //true

由于原型链的关系,我们可以说instance是Object, Super, Sub中任何一个类型的实例。
第二种方式是使用isPrototypeOf()方法。同样,只要是原型链中出现过得原型,都可以说是该原型链所派生的实例的原型。

Object.prototype.isPrototypeOf(instance) //true
Super.prototype.isPrototypeOf(instance) //true
Sub.prototype.isPrototypeOf(instance) //true
4. 谨慎的定义方法

子类型有时需要重写超类中的某个方法,或者需要添加超类中不存在的某个方法。给原型添加方法的代码一定要放在替换原型的语句之后.

function Super() {
    this.property = true
}
Super.prototype.getSuperValue = function() {
    return this.property
}
function Sub() {
    this.subproperty = false
}
//继承Super
Sub.prototype = new Super()
//添加新方法
Sub.prototype.getSubValue = function() {
    return this.subproperty
}
//重写超类中的方法
Sub.prototype.getSuperValue = function() {
    return false
}

var instance = new Sub()
console.log(instance.getSuperValue()) //false

还有一点需要注意的是,通过原型链继承的时候,不能使用对象字面量的形式创建原型方法。这是因为会重写原型链。

function Super() {
    this.property = true
}
Super.prototype.getSuperValue = function() {
    return this.property
}

function Sub() {
    this.subproperty = false
}
//继承Super
Sub.prototype = new Super()

//使用字面量添加新方法,会导致上一行代码无效

Sub.prototype = {
    getSubValue: function() {
        return this.subproperty
    }
}

var instance = new Sub()
alert(instance.getSuperValue()) //error

如上,Sub继承了Super,紧接着又将原型替换成了一个对象字面量而导致问题。由于现在的原型包含的是一个Object的实例,而非SuperType的实例,因此我们设想的原型链已经被切断。

5. 原型链的问题

使用原型链继承的主要问题还是来自于包含引用类型值得原型。通过原型来实现继承时,原型实际上会变成另一个类型的实例。于是,原先的实例属性也就成了现在的原型属性了。因为原型属性上是实例共享的,那么就会出现问题。

function Super() {
    this.colors = ["red", "blue", "green"]
}
function Sub() {}

//继承Super
Sub.prototype = new Super()

var p1 = new Sub()
p1.colors.push("black")

var p2 = new Sub()
console.log(p2.colors) //["red", "blue", "green", "black"]

原型链的第二个问题就是,创建子类型实例时,不能向超类型的构造函数传递参数。实际上,应该说是没有办法在不影响所有对象实例的情况下,给超类型的构造函数传递参数。

6. 借用构造函数

在解决原型对象中包含引用类型值所带来问题得过程中,使用一种叫做借用构造函数的技术。
基本思想:在子类型构造函数的内部调用超类型构造函数。(函数只不过是在特定环境下执行代码的对象)因此,可以通过使用apply()call()方法可以在新创建的对象上执行构造函数。

function Super() {
    this.colors = ["red", "blue", "green"]
}

function Sub() {
    //继承了Super
    Super.call(this)
}

var p = new Sub()
p.colors.push("black")
console.log(p.colors) //"red, blud, green, black"

var p2 = new Sub()
console.log(p2.colors) //"red, blue, green, black"

如上,我们通过call()方法借调了超类的构造函数,实际上是在新创建的Sub实力环境下调用了Super构造函数。这样,就会在新Sub对象上执行Super()函数中定义的所有对象初始化代码,结果Sub的每个实例都会有自己的colors属性的副本。

相对于原型链而言,借用构造函数有一个很大的优势,可以在子类型构造函数中向超类型构造函数传递参数。

function Super(name) {
    this.name = name
}
function Sub() {
    Super.call(this, "Nicholas")
    this.age = 29
}

var p = new Sub()
console.log(p.name) //"Nicholas"
console.log(p.age) // 29

以上代码中的Super只接受一个参数name,该参数会直接赋给一个属性。在Sub构造函数内部调用Super构造函数时,实际上是为Sub的实例设置了name属性。为了确保Super构造函数不会重写子类型的属性,可以在调用超类型构造函数后,再添加应该在子类型中定义的属性。

问题:利用借用构造函数也会有一些问题,方法都在构造函数中定义,因此函数服用就无从谈起。而且超类型原型对象中定义的方法,对子类型而言也是不可见的。

7. 组合继承

组合继承的基本思想:将原型链和借用构造函数的技术组合到一块,从而发挥二者之长的一种继承模式。
思路:使用原型链实现对原型属性和方法的继承,通过借用构造函数是实现对实例属性的继承。

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

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

function Sub(name, age) {
    Super.call(this, name)
    this.age = age
} 

//继承方法
Sub.prototype = new Super()
Sub.prototype.constructor = Sub
Sub.prototype.sayAge = function() {
    console.log(this.age)
}

var p1 = new Sub("Nicholas", 29)
p1.colors.push("black")
console.log(p1.colors) //"red, blue, green, black"
p1.sayName() //"Nicholas"
p1.sayAge() //29

var p2 = new Sub("Greg", 27)
console.log(p2.colors) //"red, blue, green"
p2.sayName() //"Greg"
p2.sayAge() //27

在上面这个例子中,Super构造函数定义了两个属性:name和colors。Super的原型定义了一个方法sayName()。Sub构造函数在调用Super构造函数时传入了name参数,紧接着又定义了它自己的属性age。然后将Super的实例赋值给Sub的原型对象,然后又在该新原型上定义了方法sayAge()。这样Sub构造函数不同的实例分别拥有自己的属性,又可以使用相同的方法了。

8. 原型式继承

原型式继承的想法时借助原型可以基于已有的对象创建新对象,同时还不必因此创建自定义类型。

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

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

var person = {
    name: "Nicholas",
    friends: ["Shelby", "Court", "Van"]
}

var p1 = object(person)
p1.name = "Greg"
p1.friends.push("Rob")

var p2 = object(person)
p2.name = "Linda"
p2.friends.push("Barbie")

console.log(person.friends) //"Shelby, Court, Van, Rob, Barbie"

以上这种继承方式要求你必须有一个对象作为另一个对象的基础。如果有这么一个对象的话,可以把它传给object()函数,然后再根据具体需求对得到的对象加以修改即可。在上面例子中,person对象作为基础对象,然后传入到object(),然后该函数就会返回一个新对象。这个新对象将person作为原型,所以它的原型中就包含一个基本类型值属性和一个引用类型值属性。这意味着person.friends不仅属于person所有,而且也会被p1和p2共享。实际上相当于创建了person对象的两个副本。

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

后面还有寄生式继承和寄生组合继承....表示看不下去了,前面这几个已经够用, 有时间再学习下~

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/89411.html

相关文章

  • 重温基础】15.JS对象介绍

    摘要:构造函数通常首字母大写,用于区分普通函数。这种关系常被称为原型链,它解释了为何一个对象会拥有定义在其他对象中的属性和方法。中所有的对象,都有一个属性,指向实例对象的构造函数原型由于是个非标准属性,因此只有和两个浏览器支持,标准方法是。 从这篇文章开始,复习 MDN 中级教程 的内容了,在初级教程中,我和大家分享了一些比较简单基础的知识点,并放在我的 【Cute-JavaScript】系...

    booster 评论0 收藏0
  • 重温基础】12.使用对象

    摘要:本文是重温基础系列文章的第十二篇。注意对象的名称,对大小写敏感。基础用法第一个参数是目标对象,后面参数都是源对象。用途遍历对象属性。用途将对象转为真正的结构。使用场景取出参数对象所有可遍历属性,拷贝到当前对象中。类似方法合并两个对象。 本文是 重温基础 系列文章的第十二篇。 今日感受:需要总结下2018。 这几天,重重的感冒发烧,在家休息好几天,伤···。 系列目录: 【复习资料...

    garfileo 评论0 收藏0
  • 重温基础】4.函数

    摘要:本文是重温基础系列文章的第四篇。系列目录复习资料资料整理个人整理重温基础语法和数据类型重温基础流程控制和错误处理重温基础循环和迭代本章节复习的是中的基础组件之一,函数,用来复用特定执行逻辑。箭头函数不能使用命令,即不能用作函数。 本文是 重温基础 系列文章的第四篇。今日感受:常怀感恩之心,对人对己。 系列目录: 【复习资料】ES6/ES7/ES8/ES9资料整理(个人整理) 【重温基...

    maxmin 评论0 收藏0
  • 重温基础】instanceof运算符

    摘要:需要测试的函数构造函数即用运算符来检测是否存在参数的原型链。区别只能用来判断对象函数和数组,不能用来判断字符串和数字等用于判断一个表达式的原始值,返回一个字符串。一般返回结果有函数数组,对象。 最近开始在整理ES6/ES7/ES8/ES9的知识点(已经上传到 我的博客 上),碰到一些知识点是自己已经忘记(用得少的知识点),于是也重新复习了一遍。 这篇文章要复习的 instanc...

    jimhs 评论0 收藏0
  • 重温JS基础--引用类型(三)

    摘要:今天把接下来引用类型中的一些内容全部记录完毕基本包装类型为了便于操作基本类型值,还提供了种特殊的引用类型。这三种类型具有与各自的基本类型响应的特殊行为。重写后的返回对象表示的数值类型,另外两个方法则返回字符串形式的数值。 今天把接下来引用类型中的一些内容全部记录完毕~ 基本包装类型 为了便于操作基本类型值,JavaScript还提供了3种特殊的引用类型:Boolean, Number,...

    kel 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<