整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:

在面向对象编程中,子类可以覆盖父类的方法

面向对象编程中,子类可以覆盖父类的方法,但有时你可能需要在子类中调用被覆盖的父类方法。这通常有几种不同的方法,具体取决于你使用的编程语言。以下是一些常见编程语言中调用父类被覆盖方法的方式:

### Java

在Java中,可以使用`super`关键字来调用父类的方法:

```

ocation 对象

location 对象包含有关当前 URL 的信息。

location 对象是 Window 对象的一个部分,可通过 window.location 属性来访问。

<html>
<head>
<script type="text/javascript">
function currLocation()
{
alert(window.location);
}
function newLocation()
{
window.location="/index.html";
}
</script>
</head>
<body>
<input type="button" onclick="currLocation()" value="显示当前的 URL">
<input type="button" onclick="newLocation()" value="改变 URL">
</body>
</html>

location 对象属性

hash 设置或返回从井号 (#) 开始的 URL(锚)。

host 设置或返回主机名和当前 URL 的端口号。

hostname 设置或返回当前 URL 的主机名。

href 设置或返回完整的 URL。

pathname 设置或返回当前 URL 的路径部分。

port 设置或返回当前 URL 的端口号。

protocol 设置或返回当前 URL 的协议。

search 设置或返回从问号 (?) 开始的 URL(查询部分)。

http://example.com:1234/test/test.htm#part2:

hash: #part2

host:example.com:1234

hostname:example.com

href:http://example.com:1234/test.htm#part2

pathname:/test/test.htm

port:1234

protocol:http:

假设当前的 URL 是: http://www.w3school.com.cn/tiy/t.asp?f=hdom_loc_search

search:?f=hdom_loc_search

<script type="text/javascript">
document.write(location.host);
</script>

输出:example.com:1234

location 对象方法

assign() 加载新的文档。

reload() 重新加载当前文档。

replace() 用新的文档替换当前文档。

assign() 方法可加载一个新的文档。

location.assign(URL)

<html>
<head>
<script type="text/javascript">
function newDoc()
{
window.location.assign("http://www.w3school.com.cn");
}
</script>
</head>
<body>
<input type="button" value="Load new document" onclick="newDoc()" />
</body>
</html>

reload() 方法用于重新加载当前文档。

location.reload(bool)

参数如果是false则从浏览器的缓存中重载,如果为true则从服务器上重载,默认值为false;

<html>
<head>
<script type="text/javascript">
function reloadPage(){
window.location.reload();
}
</script>
</head>
<body>
<input type="button" value="Reload page" onclick="reloadPage()" />
</body>
</html>

replace() 方法可用一个新文档取代当前文档。

location.replace(newURL)

replace() 方法不会在 History 对象中生成一个新的记录。当使用该方法时,新的 URL 将覆盖 History 对象中的当前记录。

<html>
<head>
<script type="text/javascript">
function replaceDoc(){
window.location.replace("http://www.w3school.com.cn");
}
</script>
</head>
<body>
<input type="button" value="Replace document" onclick="replaceDoc()" />
</body>
</html>

实例:ThinkPHP框架

<a href='{:U('Circle/circleDetail',array('id'=>$vo['share_id']))}'> <!-- 此种方式会影响页面显示效果 -->
<div class="item item-avatar item-button-right">
<img src="{$vo.head_pic}">
<h2>{$vo.user_name}</h2>
<p>{:friend_date($vo['public_time'])}</p>
<i class=" button button-icon button-outline button-assertive" onclick="setCollection(this, '{$vo.share_id}', '{$user.user_id}')">{$vo.isCollection}</i>
</div>
</a>

代替a链接功能

. 原型链

原型链是ES5中实现继承的主要手段, 因此相对比较重要, 我们需要深入理解原型链.

1.1. 深入理解原型链

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

  • 每个构造函数都有一个原型对象, 通过prototype指针指向该原型对象.
  • 原型对象都包含一个指向构造函数的指针, 通过constructor指针, 指向构造函数
  • 而实例都包含一个指向原型对象的内部指针, 该内部指针我们通常使用__proto__来描述.

思考如下情况:

  • 我们知道, 可以通过Person.prototype = {}的方式来重写原型对象.
  • 假如, 我们后面赋值的不是一个{}, 而是另外一个类型的实例, 结果会是怎么样呢?
  • 显然,此时的原型对象将包含一个指向另一个原型的指针,相应地,另一个原型中也包含着一个指向另一个构造函数的指针。
  • 假如另一个原型又是另一个类型的实例,那么上述关系依然成立,如此层层递进,就构成了实例与原型的链条。这就是所谓原型链的基本概念。

有些抽象, 我们通过代码来理解:

// 创建Person构造函数
function Person() {
}

// 设置Animal的原型
Person.prototype = {
}

我们将代码修改成原型链的形式:

// 1.创建Animal的构造函数
function Animal() {
    this.animalProperty = "Animal"
}

// 2.给Animal的原型中添加一个方法
Animal.prototype.animalFunction = function () {
    alert(this.animalProperty)
}

// 3.创建Person的构造函数
function Person() {
    this.personProperty = "Person"
}

// 4.给Person的原型对象重新赋值
Person.prototype = new Animal()

// 5.给Person添加属于自己的方法
Person.prototype.personFunction = function () {
    alert(this.personProperty)
}

// 6.创建Person的实例
var person = new Person()
person.animalFunction()
person.personFunction()

代码解析:

  • 代码有一些复杂, 但是如果你希望学习好原型链, 必须耐心去看一看上面的代码, 你会发现其实都是我们学习过的.
  • 重点我们来看第4步代码: 给Person.prototype赋值了一个Animal的实例. 也就是Person的原型变成了Animal的实例.
  • Animal实例本身有一个__proto__可以指向Animal的原型.
  • 那么, 我们来思考一个问题: 如果现在搜索一个属性或者方法, 这个时候会按照什么顺序搜索呢?
    • 第一步, 在person实例中搜索, 搜索到直接返回或者调用函数. 如果没有执行第二步.
    • 第二步, 在Person的原型中搜索, Person的原型是谁? Animal的实例. 所以会在Animal的实例中搜索, 无论是属性还是方法, 如果搜索到则直接返回或者执行. 如果没有, 执行第三步.
    • 第三步, 在Animal的原型中搜索, 搜索到返回或者执行, 如果没有, 搜索结束. (当然其实还有Object, 但是先不考虑)

画图解析可能更加清晰:

当代码执行到第3步(上面代码的序号)的时候, 如图所示:

img

当代码执行第4步(上面代码的序号)时, 发生了如图所示的变化

  • 注意图片中的红色线, 原来指向的是谁, 现在指向的是谁.

img

代码继续执行

  • Person.prototype.personFunction = function (){}
  • 当执行第5步, 也就是给Person的原型赋值了一个函数时, 事实上在给new Animal(Animal的实例)赋值了一个新的方法.

img

代码继续执行, 我们创建了一个Person对象

  • 创建Person对象, person对象会有自己的属性, personProperty.
  • 另外, person对象有一个__prototype__指向Person的原型.
  • Person的原型是谁呢? 就是我们之前的new Animal(Animal的一个实例), 所以会指向它.

原型链简单总结:

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

1.2. 原型和实例的关系

如果我们希望确定原型和实例之间的关系, 有两种方式:

  • 第一种方式是使用instanceof操作符,只要用这个操作符来测试实例与原型链中出现过的构造函数,结果就会返回true。
  • 第二种方式是使用isPrototypeOf()方法。同样,只要是原型链中出现过的原型,都可以说是该原型链所派生的实例的原型,因此isPrototypeOf()方法也会返回true

instanceof操作符

// instanceof
alert(person instanceof Object) // true
alert(person instanceof Animal) // true
alert(person instanceof Person) // true

isPrototypeOf()函数

// isPrototypeOf函数
alert("isPrototypeOf函数函数")
alert(Object.prototype.isPrototypeOf(person)) // true
alert(Animal.prototype.isPrototypeOf(person)) // true
alert(Person.prototype.isPrototypeOf(person)) // true

1.3. 添加新的方法

添加新的方法

  • 在第5步操作中, 我们为子类型添加了一个新的方法. 但是这里有一个注意点.
  • 无论是子类中添加新的方法, 还是对父类中方法进行重写. 都一定要将添加方法的代码, 放在替换原型语句之后.
  • 否则, 我们添加的方法将会无效.

错误代码引起的代码:

// 1.定义Animal的构造函数
function Animal() {
    this.animalProperty = "Animal"
}

// 2.给Animal添加方法
Animal.prototype.animalFunction = function () {
    alert(this.animalProperty)
}

// 3.定义Person的构造函数
function Person() {
    this.personProperty = "Person"
}

// 4.给Person添加方法
Person.prototype.personFunction = function () {
    alert(this.personProperty)
}

// 5.给Person赋值新的原型对象
Person.prototype = new Animal()

// 6.创建Person对象, 并且调用方法
var person = new Person()
person.personFunction() // 不会有任何弹窗, 因为找不到该方法

代码解析:

  • 执行上面的代码不会出现任何的弹窗, 因为我们添加的方法是无效的, 被赋值的新的原型覆盖了.
  • 正确的办法是将第4步和第5步操作换一下位置即可.

总结

  • 其实这个问题没什么好说的, 只要你理解了原型链(好好看看我上面画的图, 或者自己画一下图)
  • 但是, 切记在看图的过程中一样扫过, 因为这会让你错过很多细节, 对原型链的理解就会出现问题.

1.4. 原型链的问题

原型链对于继承来说:

  • 原型链似乎对初学JavaScript原型的人来说, 已经算是比较高明的设计技巧了, 有些人理解起来都稍微有些麻烦.
  • 但是, 这种设计还存在一些缺陷, 不是最理性的解决方案. (但是后续的解决方案也是依赖原型链, 无论如何都需要先理解它)

原型链存在的问题:

  • 原型链存在最大的问题是关于引用类型的属性.
  • 通过上面的原型实现了继承后, 子类的person对象继承了(可以访问)Animal实例中的属性(animalProperty).
  • 但是如果这个属性是一个引用类型(比如数组或者其他引用类型), 就会出现问题.

引用类型的问题代码:

// 1.定义Animal的构造函数
function Animal() {
    this.colors = ["red", "green"]
}

// 2.给Animal添加方法
Animal.prototype.animalFunction = function () {
    alert(this.colors)
}

// 3.定义Person的构造函数
function Person() {
    this.personProperty = "Person"
}

// 4.给Person赋值新的原型对象
Person.prototype = new Animal()

// 5.给Person添加方法
Person.prototype.personFunction = function () {
    alert(this.personProperty)
}

// 6.创建Person对象, 并且调用方法
var person1 = new Person()
var person2 = new Person()

alert(person1.colors) // red,green
alert(person2.colors) // red,green

person1.colors.push("blue")

alert(person1.colors) // red,green,blue
alert(person2.colors) // red,green,blue

代码解析:

  • 我们查看第6步的操作
  • 创建了两个对象, 并且查看了它们的colors属性
  • 修改了person1中的colors属性, 添加了一个新的颜色blue
  • 再次查看两个对象的colors属性, 会发现person2的colors属性也发生了变化
  • 两个实例应该是相互独立的, 这样的变化如果我们不制止将会在代码中引发一些列问题.

原型链的其他问题:

  • 在创建子类型的实例时,不能向父类型的构造函数中传递参数。
  • 实际上,应该说是没有办法在不影响所有对象实例的情况下,给父类型的构造函数传递参数。
  • 从而可以修改父类型中属性的值, 在创建构造函数的时候就确定一个值.

二. 经典继承

为了解决原型链继承中存在的问题, 开发人员提供了一种新的技术: constructor stealing(有很多名称: 借用构造函数或经典继承或伪造对象), steal是偷窃的意思, 但是这里可以翻译成借用.

2.1. 经典继承的思想

经典继承的做法非常简单: 在子类型构造函数的内部调用父类型构造函数.

  • 因为函数可以在任意的时刻被调用
  • 因此通过apply()和call()方法也可以在新创建的对象上执行构造函数.

经典继承代码如下:

// 创建Animal的构造函数
function Animal() {
    this.colors = ["red", "green"]
}

// 创建Person的构造函数
function Person() {
    // 继承Animal的属性
    Animal.call(this)

    // 给自己的属性赋值
    this.name = "Coderwhy"
}

// 创建Person对象
var person1 = new Person()
var person2 = new Person()

alert(person1.colors) // red,greem
alert(person2.colors) // red,greem
person1.colors.push("blue")
alert(person1.colors) // red,green,blue
alert(person2.colors) // red,green

代码解析:

  • 我们通过在Person构造函数中, 使用call函数, 将this传递进去.
  • 这个时候, 当Animal中有相关属性初始化时, 就会在this对象上进行初始化操作.
  • 这样就实现了类似于继承Animal属性的效果.

这个时候, 我们也可以传递参数, 修改上面的代码:

// 创建Animal构造函数
function Animal(age) {
    this.age = age
}

// 创建Person构造函数
function Person(name, age) {
    Animal.call(this, age)
    this.name = name
}

// 创建Person对象
var person = new Person("Coderwhy", 18)
alert(person.name)
alert(person.age)

2.2. 经典继承的问题

经典继承的问题:

  • 对于经典继承理解比较深入, 你已经能发现: 经典继承只有属性的继承, 无法实现方法的继承.
  • 因为调用call函数, 将this传递进去, 只能将父构造函数中的属性初始化到this中.
  • 但是如果函数存在于父构造函数的原型对象中, this中是不会有对应的方法的.

回顾原型链和经典继承:

  • 原型链存在的问题是引用类型问题和无法传递参数, 但是方法可以被继承
  • 经典继承是引用类型没有问题, 也可以传递参数, 但是方法无法被继承.
  • 怎么办呢? 将两者结合起来怎么样?

三. 组合继承

如果你认识清楚了上面两种实现继承的方式存在的问题, 就可以很好的理解组合继承了.

组合继承(combination inheritance, 有时候也称为伪经典继承), 就是将原型链和经典继承组合在一起, 从而发挥各自的优点.

3.1. 组合继承的思想

组合继承:

  • 组合继承就是发挥原型链和经典继承各自的优点来完成继承的实现.
  • 使用原型链实现对原型属性和方法的继承.
  • 通过经典继承实现对实例属性的继承, 以及可以在构造函数中传递参数.

组合继承的代码:

// 1.创建构造函数的阶段
// 1.1.创建Animal的构造函数
function Animal(age) {
    this.age = age
    this.colors = ["red", "green"]
}

// 1.2.给Animal添加方法
Animal.prototype.animalFunction = function () {
    alert("Hello Animal")
}

// 1.3.创建Person的构造函数
function Person(name, age) {
    Animal.call(this, age)
    this.name = name
}

// 1.4.给Person的原型对象重新赋值
Person.prototype = new Animal(0)

// 1.5.给Person添加方法
Person.prototype.personFunction = function () {
    alert("Hello Person")
}

// 2.验证和使用的代码
// 2.1.创建Person对象
var person1 = new Person("Coderwhy", 18)
var person2 = new Person("Kobe", 30)

// 2.2.验证属性
alert(person1.name + "-" + person1.age) // Coderwhy,18
alert(person2.name + "-" + person2.age) // Kobe,30

// 2.3.验证方法的调用
person1.animalFunction() // Hello Animal
person1.personFunction() // Hello Person

// 2.4.验证引用属性的问题
person1.colors.push("blue")
alert(person1.colors) // red,green,blue
alert(person2.colors) // red,green

代码解析:

  • 根据前面学习的知识, 结合当前的代码, 大家应该可以理解上述代码的含义.
  • 但是我还是建议大家一定要多手动自己来敲代码, 来理解其中每一个步骤.
  • 记住: 看懂, 听懂不一定真的懂, 自己可以写出来, 才是真的懂了.

3.2. 组合继承的分析

组合继承是JavaScript最常用的继承模式之一.

  • 如果你理解到这里, 点到为止, 那么组合来实现继承只能说问题不大.
  • 但是它依然不是很完美, 存在一些问题不大的问题.(不成问题的问题, 基本一词基本可用, 但基本不用)

组合继承存在什么问题呢?

  • 组合继承最大的问题就是无论在什么情况下, 都会调用两次父类构造函数.
  • 一次在创建子类原型的时候
  • 另一次在子类构造函数内部(也就是每次创建子类实例的时候).
  • 另外, 如果你仔细按照我的流程走了上面的每一个步骤, 你会发现: 所有的子类实例事实上会拥有两份父类的属性
  • 一份在当前的实例自己里面(也就是person本身的), 另一份在子类对应的原型对象中(也就是person.__proto__里面)
  • 当然, 这两份属性我们无需担心访问出现问题, 因为默认一定是访问实例本身这一部分的.

怎么解决呢?

  • 看起来组合继承也不是非常完美的解决方案, 虽然也可以应用.
  • 有没有终极的解决方案呢? 预知后事如何, 且听下回分解.

原文来自:https://mp.weixin.qq.com/s?__biz=Mzg5MDAzNzkwNA==&mid=2247483876&idx=1&sn=d9241b9be5462019dbff10fb539e593f&chksm=cfe3f21bf8947b0db29ef6cc5c85acb7d2e60ecc6fb110d897db65af09962a5f34f334c4a4b0&cur_album_id=1566035091556974596&scene=189#wechat_redirect,如有侵权,请联系删除;