设为首页 加入收藏

TOP

重学JavaScript之面向对象的程序设计(继承)(一)
2019-09-30 16:46:11 】 浏览:53
Tags:重学 JavaScript 面向 对象 程序设计 继承

1. 继承

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

2. 原型链

ES中 描述了 原型链的概念,并将原型链作为实现继承的主要方法。其基本思想是利用原型让一个引用类型继承另一个引用类型的属性和方法。

回顾一下构造函数、原型和实例的关系

每个构造函数都有一个原型对象,原型对象都包含一个指向构造函数的指针,而实例都包含一个指向原型对象的内部指针。那么假如我们让原型对象等于另一个类型的实例。那么此时的原型对象将包含一个指向另一个原型的指针,相应地,另一个原型中也包含着一个指向另一个构造函数的指针。

另外,假如另一个原型又是另一个类型的实例,如此层层递进,就构成了实例与原型的链条。这就是原型链的基本概念。

function SuperType(){
    this.property = true
}

SuperType.prototype.getSuperValue = function(){
    retrun this.property
}

function SubType(){
    this.subproperty = false
}

// 继承了 SuperType
SubType.prototype = new SuperType()
SubType.prototype.getSubValue = function(){
    retrun this.subproperty;
}

var instance = new SubType()
instance.getSuperValue()    // true
上面代码中定义了两个类型:

SuperType 和SubType。每个类型分别有一个属性和一个方法。它们的主要区别是:

SubType继承了SuperType,而继承是通过创建SuperType的实例,并将该实例赋给SubType.prototype实现的。实现的本质是重写原型对象,代之以一个新类型的实例。

也就是说,原来存在于SuperType的实例中的所有方法和属性,现在也存在于SubType.prototype中。在给SubType.prototype添加一个方法后,这样就在继承了SuperType的属性和方法的基础上又添加了一个新的方法。这样就实现了实例以及构造函数和原型之间的关系。

通过实现原型链,本质上扩展了原型搜索机制,当以读取模式访问一个实例属性时,首先会在实例中搜索该属性。如果没有找到该属性,则会继续搜索实例的原型。在通过原型链实现继承的情况下,搜索过程就得以沿着原型链继续向上。直到最后一步找到该方法。在找不到属性和方法的情况下,搜索过程总是要一环一环地前行到原型链末端才会停下来。

3. 默认的原型

所有引用类型默认都继承了Object,而这个继承也是通过原型链实现的。所有函数的默认原型都是Object的实例 因此默认原型都会包含一个内部指针,指向 Object.prototype。这也是所有自定义类型都会继承 toString()、valueOf()默认方法的原因

4. 原型和实例的关系

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

instance instanceof Object  // true

第二种方法就是 isPrototypeOf() 方法,同样,只要原型链中出现过的原型,都可以说是该原型链所派生的实例原型,因此 isPrototypeOf() 方法也会返回 true

Object.prototype.isPrototypeOf(instance)    // true

5. 谨慎地定义方法

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

function  SuperType() {
    this.property = true
}

SuperType.prototype.getSuperValue = function(){
    retrun this.property
}

function SubType() {
    this.subproperty = false
}
----
// 继承了SuperType
SubType.prototype = new SuperType()

// 添加新方法
SubType.prototype.getSubValue = function(){
    retrun this.subproperty
}
//重写超类型中的方法
SubType.prototype.getSuperValue = function(){
    retrun false
}
-----
var instance = new SubType()
instance.getSuperValue()    // false
以上代码中分隔的部分是两个方法的定义。

第一个方法 getSubValue()被添加到了SubType中,第二个方法 getSuperValue()是原型链中已经存在的一个方法,但重写这个方法将会屏蔽原来那个方法.

换句话说,当 SubType 的实例调用 getSuperValue()时,调用的就是这个重新定义的方法,但通过 SuperType的实例调用 getSuperValue()时,还会继续调用原来的那个方法,所有必须要在用SuperType的实例替换原型之后,在定义这两个方法。

注意:即在通过原型链实现继承的时候,不能使用对象字面量创建原型方法,因为这样做会重写原型。

6. 原型链的问题

原型链虽然很强大,可以用它来实现继承,但也存在一定的问题。

1、来自包含引用类型值的原型。在之前说过包含引用类型值的原型属性会被所有实例共享。所以这也是为什么要在构造函数中,而不是在原型对象中定义属性的原因。在通过原型来实现继承时,原型实际上会变成另一个类型的实例。于是,原先的实例属性也就顺理成章地变成了现在的原型属性。

2、在创建子类型的实例时,不能向超类型的构造函数中传递参数。实际上可以说是没有办法再不影响所有对象实例的情况下,给超类型的构造函数传递参数a。

7. 借用构造函数

利用在子类型构造函数的内部调用超类型构造函数。即可以通过 apply() 和 call()方法在新创建的对象上执行构造函数。

7.1 传递参数

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

function s(name) {
    this.name = name
}

function b() {  
    // 继承 s,同时还传递参数
    s.call(this, 'nnn')
    // 实例属性
    this.age = 23
}

let i = new B()

i.name // nn
i.age  // 29

7.2 借用构造函数的问题

如果仅仅是借用构造函数,那么也就无法避免构造函数模式存在的问题,方法都在构造函数中定义,因此函数复用就无法实现。另外,在超类型的原型中定义的方法,对子类型而言也是不可见的。结果所有类型都只能使用构造 函数模式。所以借用构造函数模式很少单独使用。

8. 组合继承

也叫做伪 经典继承,指的是将原型链和借用构造函数的技术组合到一块。发挥二者的长处的一种继承模

首页 上一页 1 2 下一页 尾页 1/2/2
】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
上一篇松软科技带你学前端:JavaScript .. 下一篇JS中数据类型转换

最新文章

热门文章

Hot 文章

Python

C 语言

C++基础

大数据基础

linux编程基础

C/C++面试题目