一、构造函数

1.1、概述

​ 在面向对象编程语言中,比如说Java语言,是有类的概念的,类就是对象的模板,对象就是类的实例。但是在ES6之前,JavaScript并没有类的概念。

1
# 在 ES6 之前 ,对象不是基于类创建的,而是用一种称为【构造函数】的特殊函数来定义对象和它们的特征。换句话说,使用 构造函数 来去模拟类。

1.2、创建对象的方式

​ 在JavaScript中,创建对象的方式有三种:

  • 字面量对象
  • new Object()
  • 自定义构造函数

1.3、构造函数

1.3.1、概述

构造函数是一种特殊的函数,主要用来初始化对象,即为对象成员变量赋初始值,它总与 new 一起使用。我们可以把对象中一些公共的属性和方法抽取出来,然后封装到这个函数里面。

​ 在 JS 中,使用构造函数时要注意以下两点:

  • 构造函数用于创建某一类对象,其首字母要大写
  • 构造函数要和 new 一起使用才有意义

1.3.2、new 的含义

​ new 在执行时会做四件事情:

  • 在内存中创建一个新的空对象。
  • 让 this 指向这个新的对象。
  • 执行构造函数里面的代码,给这个新对象添加属性和方法。
  • 返回这个新对象(所以构造函数里面不需要 return )。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<body>
<script>
// 1、定义构造函数
function Star(uname, age) {
this.uname = uname;
this.age = age;
this.sing = function() {
console.log("我会唱歌");
}
}
// 2、通过构造函数创建对象、
var ldh = new Star('刘德华', 20);
var zxy = new Star('张学友', 21);
ldh.sing();
zxy.sing();
</script>
</body>

1.3.3、静态成员/实例成员

​ JavaScript 的构造函数中可以添加一些成员,可以在构造函数本身上添加,也可以在构造函数内部的 this 上添加。通过这两种方式添加的成员,就分别称为静态成员实例成员

  • 静态成员:在构造函数本上添加的成员称为==静态成员,只能由构造函数本身来访问。==
  • 实例成员:在构造函数内部创建的对象成员称为==实例成员,只能由实例化的对象来访问。==
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<body>
<script>
// 构造函数中的属性和方法我们称为成员, 成员可以添加

// 1、定义构造函数
function Star(uname, age) {
// >>
this.uname = uname;
this.age = age;
this.sing = function() {
console.log("我会唱歌");
}
}
// 1.实例成员就是构造函数内部通过this添加的成员 uname age sing 就是实例成员
// 实例成员只能通过实例化的对象来访问
var ldh = new Star('刘德华', 23);
console.log(ldh.uname);
ldh.sing();
console.log(Star.uname);
</script>
</body>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<body>
<script>
// 构造函数中的属性和方法我们称为成员, 成员可以添加

// 1、定义构造函数
function Star(uname, age) {
// >>
this.uname = uname;
this.age = age;
this.sing = function() {
console.log("我会唱歌");
}
}
// 2. 静态成员 在构造函数本身上添加的成员 sex 就是静态成员
// 静态成员只能通过构造函数来访问
Star.sex = '男';
console.log(Star.sex);
</script>
</body>

二、原型

2.1、构造函数创建对象的问题

​ 构造函数创建对象该方法很好用,但是存在浪费内存的问题,更好的方式是:属性属于对象的,方法是共享的。

0111

1
1. 我们希望所有的对象使用同一个函数,这样就比较节省内存,那么我们要怎样做呢?

2.2、构造函数原型prototype

2.2.1、说明

1
# 明确的说明:构造函数通过原型prototype分配的函数是所有对象所共享的。

2.2.2、概述

1
2
3
4
5
1. JavaScript 规定,每一个构造函数都有一个 prototype 属性,指向另一个对象。注意这个 prototype 就是一个对象,这个对象的所有属性和方法,都会被构造函数所拥有。

# 我们可以把那些不变的方法,直接定义在 prototype 对象上,这样所有对象的实例就可以共享这些方法。

# 简单来说:原型的作用就是用来共享方法的。

2.2.3、代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<body>
<script>
// 1、定义构造函数
function Star(uname, age) {
// >>
this.uname = uname;
this.age = age;
}
// 2、向原型对象身上添加sing方法
Star.prototype.sing = function() {
console.log("我会唱歌");
}

var ldh = new Star('刘德华', 20);
var zxy = new Star('张学友s', 21);

ldh.sing();
zxy.sing();

// 细节
// >> 1、查看对象是否共享了sing方法
console.log(ldh.sing == zxy.sing); // true
console.dir(Star); // 打印的结果看图
</script>
</body>

0112

1
# 总结:我们的公共属性定义到构造函数里面, 公共的方法我们放到原型对象身上。

2.3、对象原型proto

2.3.1、问题分析

​ 通过刚才2.2.3案例,可以发现,我们只是向构造函数的原型对象身上添加了sing方法,但是呢,对于通过构造函数所创建出来的实例对象ldhzxy 也能够使用sing方法,这个是为什么呢?

​ 原因就是在于每个实例对象身上都会有一个__proto__的属性。

2.3.2、说明

1
# 实例对象都会有一个属性 __proto__ 指向构造函数的 prototype 原型对象,之所以我们对象可以使用构造函数 prototype 原型对象的属性和方法,就是因为对象有 __proto__ 原型的存在。
  • proto对象原型和原型对象 prototype 是等价的。
  • proto对象原型的意义就在于为对象的查找机制提供一个方向,或者说一条路线,但是它是一个非标准属性,因此实际开发中,不可以使用这个属性,它只是内部指向原型对象 prototype

0113

2.3.3、代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<body>
<script>
// 1、定义构造函数
function Star(uname, age) {
this.uname = uname;
this.age = age;
}
// 2、向原型对象身上添加sing方法
Star.prototype.sing = function() {
console.log("我会唱歌");
}

var ldh = new Star('刘德华', 20);
var zxy = new Star('张学友s', 21);

ldh.sing();
zxy.sing();

// 细节
// >> 1、查看对象身上的__proto__属性
console.log(ldh); //对象身上系统自己添加一个 __proto__ 指向我们构造函数的原型对象 prototype
console.log(ldh.__proto__ === Star.prototype); // true

/*
方法的查找规则:
1、首先先看ldh 对象身上是否有 sing 方法,如果有就执行这个对象上的sing
2、如果没有sing 这个方法,因为有__proto__的存在,就去构造函数原型对象prototype身上去查 找sing这个方法
*/
</script>
</body>

2.4、constructor 构造函数

2.4.1、说明

对象原型(proto)构造函数(prototype)原型对象里面都有一个属性 constructor 属性 ,constructor 我们称为构造函数,因为它指回构造函数本身。

​ constructor 主要用于记录该对象引用于哪个构造函数,它可以让原型对象重新指向原来的构造函数。

一般情况下,对象的方法都在构造函数的原型对象中设置。如果有多个对象的方法,我们可以给原型对象采取对象形式赋值,但是这样就会覆盖构造函数原型对象原来的内容,这样修改后的原型对象 constructor 就不再指向当前构造函数了。此时,我们可以在修改后的原型对象中,添加一个 constructor 指向原来的构造函数。

2.4.2、案例1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
<body>
<script>
// 1、定义构造函数
function Star(uname, age) {
this.uname = uname;
this.age = age;
}
// 2、向原型对象身上添加方法
// >> 2.1 添加sing方法
Star.prototype.sing = function() {
console.log("我会唱歌");
};
// >> 2.2 添加movie方法
Star.prototype.movie = function() {
console.log("我会拍电影");
};

var ldh = new Star('刘德华', 20);
ldh.sing();
ldh.movie();

// 细节
// >> 1、查看构造函数身上的原型对象信息
console.log(Star.prototype); // {sing: ƒ, movie: ƒ, constructor: ƒ}
// >> 2、查看实例对象身上的对象原型信息
console.log(ldh.__proto__); // {sing: ƒ, movie: ƒ, constructor: ƒ}
// >> 3、查询原型对象所引用的是哪个构造函数
console.log(Star.prototype.constructor); // Star(uname, age) {}
// >> 4、查询对象原型所引用的是哪个构造函数
console.log(ldh.__proto__.constructor); // Star(uname, age) {}
</script>
</body>

2.4.3、案例2

2.4.2案例1属于很标准的代码,但是可以有改进的地方,如果我们需要往原型对象身上添加多个方法,那么每次都需要Star.prototype.方法这种方式来去添加。显然很麻烦的,那么更好的做法是这样的,将这些方法封装起来,以对象的形式赋值给原型对象。伪代码如下:

1
2
3
4
5
6
7
8
Star.prototype = {
sing: function() {

},
movie: function() {

}
}
1
# 注意:这种方式并不是向原型对象身上添加sing方法和movie方法,而是将原型对象重新赋值给了一个新的对象。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<body>
<script>
// 1、定义构造函数
function Star(uname, age) {
this.uname = uname;
this.age = age;
}
// 2、给原型对象赋值为一个新的对象,对象中有sing方法和movie方法
// >> 这种方式并不是向原型对象身上添加sing和movie方法,而是将原型对象重新赋值给了一个新的对象。
Star.prototype = {
sing: function() {
console.log("我会唱歌");
},
movie: function() {
console.log("我会拍电影");
}
}

var ldh = new Star('刘德华', 20);
ldh.sing();
ldh.movie();

// 细节
// >> 1、查看构造函数身上的原型对象信息
console.log(Star.prototype); // {sing: ƒ, movie: ƒ}
// >> 2、查看实例对象身上的对象原型信息
console.log(ldh.__proto__); // {sing: ƒ, movie: ƒ}
// >> 3、查询原型对象所引用的是哪个构造函数
console.log(Star.prototype.constructor); // Object();
// >> 4、查询对象原型所引用的是哪个构造函数
console.log(ldh.__proto__.constructor); // Object();
</script>
</body>

2.4.4、案例3

2.4.3案例2发现,查询构造函数身上的原型对象的信息再也找不到constructor属性了,即不知道这个原型对象所引用的构造函数是哪个了,那该怎么解决呢?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
<body>
<script>
// 1、定义构造函数
function Star(uname, age) {
this.uname = uname;
this.age = age;
}
// 2、给原型对象赋值为一个新的对象,对象中有sing方法和movie方法
// >> 这种方式并不是向原型对象身上添加sing和movie方法,而是将原型对象重新赋值给了一个新的对象。
Star.prototype = {

// >> 很多情况下,我们需要手动的利用constructor 这个属性指回 原来的构造函数
// >> 如果我们修改了原来的原型对象,给原型对象赋值的是一个对象,则必须手动的利用constructor指 回原来的构造函数
constructor: Star,

sing: function() {
console.log("我会唱歌");
},
movie: function() {
console.log("我会拍电影");
}
}

var ldh = new Star('刘德华', 20);
ldh.sing();
ldh.movie();

// 细节
// >> 1、查看构造函数身上的原型对象信息
console.log(Star.prototype); // {sing: ƒ, movie: ƒ, constructor: ƒ}
// >> 2、查看实例对象身上的对象原型信息
console.log(ldh.__proto__); // {sing: ƒ, movie: ƒ, constructor: ƒ}
// >> 3、查询原型对象所引用的是哪个构造函数
console.log(Star.prototype.constructor); // Star(uname, age) {}
// >> 4、查询对象原型所引用的是哪个构造函数
console.log(ldh.__proto__.constructor); // Star(uname, age) {}
</script>
</body>

2.5、构造函数、实例对象、原型对象三者之间的关系

2.5.1、图示

0114

三、原型链

3.1、引入

​ 刚才讲解了构造函数原型对象至此已经完毕了,现在再思考一个问题,构造函数原型对象,它本身也是一个对象,既然是对象,那么也应该有一个__proto__属性,那么这个属性又指向的是哪个原型对象呢?

​ 答案:指向的是Object原型对象。

0115

1
2
3
4
# 注意
1. 只要是对象,就有__proto__ 原型, 指向原型对象
2. 我们Star原型对象里面的__proto__原型指向的是 Object.prototype
3. 我们Object.prototype原型对象里面的__proto__原型 指向为 null

3.1.1、代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<body>
<script>
// 1、定义构造函数
function Star(uname, age) {
this.uname = uname;
this.age = age;
}
// 2、给原型对象添加sing方法
Star.prototype.sing = function() {
console.log("我会唱歌");
}

var ldh = new Star('刘德华', 20);
ldh.sing();

// 细节
// >> 1、只要是对象,就有__proto__属性,指向原型对象
// >> 我们Star原型对象里面的__proto__原型指向的是 Object.prototype
console.log(Star.prototype.__proto__ == Object.prototype); // true
// >> 2、我们Object.prototype原型对象里面的__proto__原型 指向为 null
console.log(Object.prototype.__proto__); // null
</script>
</body>

3.2、对象成员查找机制(规则)

3.2.1、说明

  • 当访问一个对象的属性(包括方法)时,首先查找这个对象自身有没有该属性。
  • 如果没有就查找它的原型(也就是 proto指向的 prototype 原型对象)。
  • 如果还没有就查找原型对象的原型(Object的原型对象)。
  • 依此类推一直找到 Object 为止(null)。
  • proto对象原型的意义就在于为对象成员查找机制提供一个方向,或者说一条路线。

3.2.2、案例1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<body>
<script>
// 1、定义构造函数
function Star(uname, age) {
this.uname = uname;
this.age = age;
}
// 2、给原型对象添加sex属性
Star.prototype.sex = '男';

var ldh = new Star('刘德华', 20);
console.log(ldh.sex);

/**
* 总结:
* ldh这实例对象身上并没有sex属性,按道理来说是不能调用的,但是发现居然可以打印
* 出来值,原因在于自己对象本身身上没有,但是我指向的原型对象身上有,那么就会去
* 找原型对象身上的sex.
*/
</script>
</body>

3.2.3、案例2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<body>
<script>
// 1、定义构造函数
function Star(uname, age) {
this.uname = uname;
this.age = age;
}
// 2、给原型对象添加sex属性
Star.prototype.sex = '男';

var ldh = new Star('刘德华', 20);
ldh.sex = '女';
console.log(ldh.sex);

/**
* 总结:
* ldh这实例对象身上有sex属性,并且所指向的原型对象身上也有sex属性,那么当去
* ldh.sex去使用实例对象身上的sex属性的时候,优先看自己有没有,自己有就使用
* 自己身上的。
*/
</script>
</body>

3.2.4、案例3

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<body>
<script>
// 1、定义构造函数
function Star(uname, age) {
this.uname = uname;
this.age = age;
}
// 2、给原型对象添加sex属性
Star.prototype.sex = '男';

var ldh = new Star('刘德华', 20);
console.log(ldh.toString());

/**
* 总结:
* ldh这实例对象身上没有toString()方法,实例对象的__proto__所指向的原型对象身上
* 也没有toString()方法,但是__proto__原型对象的原型对象(也就是Object的原型对象)
* 身上是有toString()方法的。
*/
</script>
</body>

3.3、原型对象this指向

3.3.1、说明

1
2
1. 构造函数中的 this 指向我们实例对象。
2. 原型对象里面放的是方法, 这个方法里面的this 指向的是 这个方法的调用者, 也就是这个实例对象。

3.3.2、案例1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<body>
<script>
// 0、定义局部变量
var that = null;

// 1、定义构造函数
function Star(uname, age) {
this.uname = uname;
this.age = age;
that = this;
}
var ldh = new Star('刘德华', 20);
console.log(ldh === that);

/**
* 总结:
* 在构造函数中,里面this指向的是对象实例 ldh
*/
</script>
</body>

3.3.3、案例2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<body>
<script>
// 0、定义局部变量
var that = null;

// 1、定义构造函数
function Star(uname, age) {
this.uname = uname;
this.age = age;
}

// 2、向原型对象添加sing方法
Star.prototype.sing = function() {
console.log('我会唱歌');
that = this;
}

var ldh = new Star('刘德华', 20);
ldh.sing();
console.log(ldh === that);

/**
* 总结:
* 原型对象方法里面的this 指向的是 实例对象 ldh(谁调用了sing方法,那么方法中的this就指向谁)
*/
</script>
</body>

3.4、扩展内置对象

3.4.1、说明

​ 可以通过原型对象,对原来的内置对象进行扩展自定义的方法。比如给数组增加自定义求和的功能。这样的话,通过给数组的原型对象添加方法,那么当我们去创建数组对象的时候,也就拥有了该方法。

​ 比如:我对数组Array的原型对象添加sum求和的方法,那么new Array()创建数组实例对象的时候,也会拥有该sum方法。

​ 伪代码如下:

1
2
3
4
5
6
7
8
9
10
<body>
<script>
Array.prototype.sum = function() {

}

var arrs = new Array();
arrs.sum();
</script>
</body>

3.4.2、代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<body>
<script>
Array.prototype.sum = function() {
var result = 0;
for (var i = 0; i < this.length; i++) {
result = result + this[i];
}
return result;
}

var arrs = new Array(1, 2, 3, 4, 5);
console.log(arrs.sum());
</script>
</body>

3.4.3、注意问题

​ 刚刚讲到,向原型对象中添加方法,其实可以将添加的方法用对象的形式封装起来,那么将这个对象赋值给原型对象,如果要这样做,该怎么写呢?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<body>
<script>
Array.prototype = {
sum: function() {
var result = 0;
for (var i = 0; i < this.length; i++) {
result = result + this[i];
}
return result;
}
};

var arrs = new Array(1, 2, 3, 4, 5);
console.log(Array.prototype); // 结果并不是我们赋值的对象
console.log(arrs.sum()); // 报错
</script>
</body>

0116

3.4.4、总结

​ 会发现,即使我们为内置对象Array的原型对象重新赋值了一个新的对象,发现,还是没有的,就说明:

1
# 注意:数组和字符串内置对象不能给原型对象覆盖操作 Array.prototype = {} ,只能是 Array.prototype.xxx = function(){} 的方式。

四、继承

4.1、学习准备

4.1.1、注意点

​ ES6之前并没有给我们提供 extends 关键字实现继承。我们可以通过构造函数+原型对象模拟实现继承,被称为组合继承

4.1.2、call方法

call()方法的学习。该方法的作用是可以改变一个函数内部this的指向。

1
2
3
4
5
# 语法
fun.call(thisArg, arg1, arg2, ...)

1. thisArg :当前调用函数 this 的指向对象
2. arg1,arg2:传递的其他参数

4.1.3、案例1

1
2
3
4
5
6
7
8
9
10
11
<body>
<script>
function fn(x, y) {
console.log("x + y =" + (x + y));
console.log(this); // window
}

// 普通函数调用,函数的内部this默认指向的是Window。
fn(10, 20);
</script>
</body>

4.1.4、案例2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<body>
<script>
function fn(x, y) {
console.log("x + y = " + (x + y));
console.log(this);
}

var person = {
name: 'HelloWorld'
}

// call() 可以改变这个 fn 函数的this指向 此时这个函数的this 就指向了person这个对象
fn.call(person, 1, 3);
</script>
</body>

0117

4.2、借用构造函数继承父类型属性

4.2.1、核心原理

1
# 通过 call() 把父类型的 this 指向子类型的 this ,这样就可以实现子类型继承父类型的属性。

4.2.2、案例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<body>
<script>
// 定义父类型
function Father(uname, age) {
// >> this 指向父构造函数的对象实例
this.uname = uname;
this.age = age;
}
// 定义子类型
function Child(uname, age, password) {
// 此代码表示调用Father构造函数并且将Father构造函数中的this修改为当前的this(Child实例)
Father.call(this, uname, age);
this.password = password;
}
var child = new Child('HelloWorld', 20, '123456');
console.log(child.uname);
console.log(child.age);
console.log(child.password);
</script>
</body>

4.3、借用构造函数继承父类型方法

4.3.1、说明

1
# 一般情况下,对象的方法都在构造函数的原型对象中设置,通过构造函数无法继承父类方法。  

4.3.2、代码错误实现1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<body>
<script>
// 定义父类型
function Father(uname, age) {
// >> this 指向父构造函数的对象实例
this.uname = uname;
this.age = age;
}
// 向父构造函数的原型对象添加sing方法
Father.prototype.sing = function() {
console.log('---我会唱歌---');
};
// 定义子类型
function Child(uname, age, password) {
// 此代码表示调用Father构造函数,将Father构造函数中的this修改为当前的this(Child实例对象)
Father.call(this, uname, age);
this.password = password;
}
var child = new Child('HelloWorld', 20, '123456');
child.sing();
</script>
</body>

0118

1
1. 说明: 仅仅向父构造函数的原型对象上添加方法,子类是不能继承的。

4.3.3、代码实现2

4.3.2代码错误实现1会发现,单纯的向父构造函数的原型对象添加方法是不行的,所以我们自然的可以想到,那么将父构造函数的原型对象再赋值给子构造函数的原型对象,不就可以了嘛,形如这样的:

1
Child.prototype = Father.prototype
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<body>
<script>
// 定义父类型
function Father(uname, age) {
// >> this 指向父构造函数的对象实例
this.uname = uname;
this.age = age;
}
// 向父构造函数的原型对象添加sing方法
Father.prototype.sing = function() {
console.log('---我会唱歌---');
};
// 定义子类型
function Child(uname, age, password) {
// 此代码表示调用Father构造函数,将Father构造函数中的this修改为当前的this(子构造函数的实例对象)
Father.call(this, uname, age);
this.password = password;
}
// 将父构造函数的原型对象赋值给子构造函数的原型对象!!!!
Child.prototype = Father.prototype;

var child = new Child('HelloWorld', 20, '123456');
child.sing();
</script>
</body>

0119

0120

4.3.4、代码实现3

4.3.3代码实现2发现,确实解决了问题,但是有一个不好的地方,就是如果子构造函数的原型对象身上本身也有方法呢?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<body>
<script>
// 定义父类型
function Father(uname, age) {
// >> this 指向父构造函数的对象实例
this.uname = uname;
this.age = age;
}
// 向父构造函数的原型对象添加sing方法
Father.prototype.sing = function() {
console.log('---我会唱歌---');
};
// 定义子类型
function Child(uname, age, password) {
// 此代码表示调用Father构造函数,将Father构造函数中的this修改为当前的this(Child实例对象)
Father.call(this, uname, age);
this.password = password;
}
// 将父构造函数的原型对象赋值给子构造函数的原型对象!!!!
Child.prototype = Father.prototype;
// >> 向子构造中添加自己的方法
Child.prototype.run = function() {
console.log('跑步');
};
var child = new Child('HelloWorld', 20, '123456');
child.sing();
child.run();
console.log(Father.prototype);
</script>
</body>

0121

0122

​ 总结:本身run方法是属于子类身上的,但是现在由于子构造函数的原型对象指向了父构造函数的原型对象,所以导致父原型对象身上也有run方法,这个是不太合适的。

4.3.5、正确代码实现1

1
2
3
4
5
6
# 思路
1. 将子类所共享的方法提取出来,让子类的 prototype 原型对象 = new 父类()

2. 本质:子类原型对象等于是实例化父类,因为父类实例化之后另外开辟空间,就不会影响原来父类原型对象

3. 将子类的 constructor 从新指向子类的构造函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<body>
<script>
// 定义父类型
function Father(uname, age) {
// >> this 指向父构造函数的对象实例
this.uname = uname;
this.age = age;
}
// 向父构造函数的原型对象添加sing方法
Father.prototype.sing = function() {
console.log('---我会唱歌---');
};
// 定义子类型
function Child(uname, age, password) {
// 此代码表示调用Father构造函数,将Father构造函数中的this修改为当前的this(Child实例对象)
Father.call(this, uname, age);
this.password = password;
}
// 创建Father构造函数的实例对象赋值给子构造函数的原型对象!!!!
Child.prototype = new Father();
// >> 向子构造中添加自己的方法
Child.prototype.run = function() {
console.log('跑步');
};
var child = new Child('HelloWorld', 20, '123456');
child.sing();
child.run();
</script>
</body>

0123

4.3.6、正确代码实现1改进

4.3.5正确代码实现1确实解决了父构造函数的原型对象身上不会再有子类所特有的方法了,但是以前说过,如果把一个新的对象直接复制给构造函数的原型对象,那么这个构造函数的原型对象的constructor属性就不再指向原来的构造函数了,而是指向新创建的对象了,可以尝试看看:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<body>
<script>
// 定义父类型
function Father(uname, age) {
// >> this 指向父构造函数的对象实例
this.uname = uname;
this.age = age;
}
// 向父构造函数的原型对象添加sing方法
Father.prototype.sing = function() {
console.log('---我会唱歌---');
};
// 定义子类型
function Child(uname, age, password) {
// 此代码表示调用Father构造函数,将Father构造函数中的this修改为当前的this(Child实例对象)
Father.call(this, uname, age);
this.password = password;
}
// 创建Father构造函数的实例对象赋值给子构造函数的原型对象!!!!
Child.prototype = new Father();
// >> 向子构造中添加自己的方法
Child.prototype.run = function() {
console.log('跑步');
};
var child = new Child('HelloWorld', 20, '123456');
console.log(Child.prototype.constructor); // Father(uname,age)
</script>
</body>

​ 正确的方式如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<body>
<script>
// 定义父类型
function Father(uname, age) {
// >> this 指向父构造函数的对象实例
this.uname = uname;
this.age = age;
}
// 向父构造函数的原型对象添加sing方法
Father.prototype.sing = function() {
console.log('---我会唱歌---');
};
// 定义子类型
function Child(uname, age, password) {
// 此代码表示调用Father构造函数,将Father构造函数中的this修改为当前的this(Child实例对象)
Father.call(this, uname, age);
this.password = password;
}
// 创建Father构造函数的实例对象赋值给子构造函数的原型对象!!!!
Child.prototype = new Father();
// 如果利用对象的形式修改了原型对象,别忘了利用constructor 指回原来的构造函数!!!!!
Child.prototype.constructor = Child;
// >> 向子构造中添加自己的方法
Child.prototype.run = function() {
console.log('跑步');
};
var child = new Child('HelloWorld', 20, '123456');
console.log(Child.prototype.constructor); // Child(uname, age, password) {}
</script>
</body>