JavaScript构造函数和原型

JavaScript构造函数和原型

1. 前言

image-20230501165753000

  • 创建对象的三种方式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
function Restudy() {
//以下代码是对对象的复习

//1.字面量创建对象
var ldh = {
name: '刘德华',
age: 18
}
console.log(ldh);

// 2.new object创建对象
var zxy = new Object()
zxy.name = '张学友'
zxy.age = 19
console.log(zxy);

//3.构造函数创建对象
function Star(name, age) {
this.name = name;
this.age = age;
}
var lm = new Star('黎明', 20)//实例化对象
console.log(lm);
}

2. 概念

image-20230501165852527

3. new执行过程

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

① 在内存中创建一个新的空对象。

② 让 this 指向这个新的对象。

③ 执行构造函数里面的代码,给这个新对象添加属性和方法。

④ 返回这个新对象(所以构造函数里面不需要 return )。

4. 静态成员和实例成员

4.1 概念

image-20230501170018217

4.2 实例成员

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 构造函数中的属性和方法我们称之为成员,成员可以添加
function Star(uname, age) {
this.uname = uname;
this.age = age;
this.sing = function () {
console.log('i 1can sing');
}
}
var lm = new Star('黎明', 20);

// 1.实例成员就是构造函数内部通过this添加的成员 uname age sing 就是实例成员
// 实例成员只能通过实例化的对象来访问
lm.sing();
console.log(lm.uname);
// 不能通过构造函数来访问实例成员
console.log(Star.uname);//undefined;

4.3 静态成员

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 构造函数中的属性和方法我们称之为成员,成员可以添加
function Star(uname, age) {
this.uname = uname;
this.age = age;
this.sing = function () {
console.log('i 1can sing');
}
}
var lm = new Star('黎明', 20);

// 2.静态成员 在构造函数本身上添加的成员
Star.sex = 'man'//sex就是静态成员
// 静态成员只能通过构造函数来访问
console.log(Star.sex);
// 静态成员不能通过对象的方式访问
console.log(lm.sex);//undefined;

5. 构造函数的问题

image-20230501170155555

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 1.构造函数的问题:浪费内存(同时实例化多个对象会开辟多个相同的对象内存空间)
function Star(uname, age) {
this.uname = uname;
this.age = age;
this.sing = function () {
console.log('我会唱歌');
}
}
var ldh = new Star('刘德华', 18);
var zxy = new Star('张学友', 19);
console.log(ldh.sing === zxy.sing);//false
// ldh.sing === zxy.sing,比较地址,俩个对象的内存地址指向不同
ldh.sing();//我会唱歌
zxy.sing();//我会唱歌

6. 构造函数原型 prototype

image-20230501170258280

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 1.构造函数的问题解决:prototype 对象 Star.prototype.sing
function Star(uname, age) {
this.uname = uname;
this.age = age;
// this.sing = function () {
// console.log('我会唱歌');
// }
}
// 构造函数通过原型分配的函数是所有对象所共享的。
// 原型:JavaScript 规定,每一个构造函数都有一个prototype 属性,指向另一个对象。
// 注意这个prototype就是一个对象,这个对象的所有属性和方法,都会被构造函数所拥有。
// 一般情况下,我们的公共属性定义到构造函数里面,公共的方法我们放到原型对象身上
// 作用:我们可以把那些不变的方法,直接定义在 prototype 对象上,这样所有对象的实例就“可以共享这些方法”。
// console.dir(Star);
Star.prototype.sing = function () {
console.log('我会唱歌');
}
var ldh = new Star('刘德华', 18);
var zxy = new Star('张学友', 19);
console.log(ldh.sing === zxy.sing);//true
ldh.sing();//我会唱歌
zxy.sing();//我会唱歌

image-20230501170415857

7. 对象原型 proto

image-20230501170440298

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
function Star(uname, age) {
this.uname = uname;
this.age = age;
}
// __proto__对象原型的意义就在于为对象的查找机制提供一个方向,或者说一条路线,
// 但是它是一个非标准属性,因此实际开发中,不可以使用这个属性,它只是内部指向原型对象 prototype
Star.prototype.sing = function () {
console.log('我会唱歌');
}
var ldh = new Star('刘德华', 18);
var zxy = new Star('张学友', 19);
zxy.sing();//我会唱歌
console.log(ldh.sing === zxy.sing);//true
// 对象都会有一个属性 __proto__ 指向构造函数的 prototype 原型对象,
console.log(ldh);
// 对象身上系统自己添加一个_proto_指向我们构造函数的原型对象prototype,之所以我们对象可以使用构造函数 prototype 原型对象的属性和方法,就是因为对象有 __proto__ 原型的存在。

// __proto__对象原型和原型对象 prototype 是等价的
console.log(ldh.__proto__ === Star.prototype);//true
// 方法的查找规则:首先看看ldh对象身上是否有sing方法,
// 如果有就执行这个对象上的sing,
// 如果没有sing这个方法,因为有_proto_的存在,就去构造函数原型对象prototype身上去查找sing这个方法

8. constructor 构造函数

image-20230501170544974

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
// 对象原型( __proto__)和构造函数(prototype)原型对象里面都有一个属性 constructor 属性 ,constructor 我们称为构造函数,因为它指回构造函数本身。
// constructor 主要用于记录该对象引用于哪个构造函数,它可以让原型对象重新指向原来的构造函数。
// 一般情况下,对象的方法都在构造函数的原型对象中设置。如果有多个对象的方法,我们可以给原型对象采取对象形式赋值,
// 但是这样就会覆盖构造函数原型对象原来的内容,这样修改后的原型对象 constructor 就不再指向当前构造函数了。
// 此时,我们可以在修改后的原型对象中,添加一个 constructor 指向原来的构造函数。

function Star(uname, age) {
this.uname = uname;
this.age = age;
}
// Star.prototype.sing = function () {
// console.log('我会唱歌');
// }
// Star.prototype.act = function () {
// console.log('我会演戏');
// }
Star.prototype = {
// 如果我们修改了原来的原型对象,给原型对象赋值的是一个对象,则必须手动的利用constructor指回原来的构造函数
constructor: Star,
sing: function () {
console.log('我会唱歌');
},
act: function () {
console.log('我会演戏');
}

}
var ldh = new Star('刘德华', 18);
var zxy = new Star('张学友', 19);
console.log(zxy);
console.log(Star.prototype);
console.log(ldh.__proto__);
console.log(Star.prototype.constructor);
console.log(ldh.__proto__.constructor);

image-20230501170627332

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

1
2
3
1.构造函数的prototype属性指向了构造函数原型对象
2.实例对象是由构造函数创建的,实例对象的__proto__属性指向了构造函数的原型对象
3.构造函数的原型对象的constructor属性指向了构造函数,实例对象的原型的constructor属性也指向了构造函数

image-20230501170716121

10. 原型链

10.1 概念

原型链是 JavaScript 中实现继承的一种机制。每个 JavaScript 对象都有一个内部属性 [[Prototype]],它指向另一个对象,这个对象就是当前对象的原型。如果在当前对象上访问一个属性或方法时,如果当前对象本身没有这个属性或方法,那么 JavaScript 就会沿着原型链向上查找,直到找到该属性或方法或者到达原型链的末端(即 Object.prototype),如果还没有找到,那么就返回 undefined。

例如,假设有一个对象 obj,当你访问 obj.prop 时,JavaScript 首先会查找 obj 是否有属性 prop,如果没有,那么 JavaScript 就会查找 obj 的原型,也就是 obj.[[Prototype]] 所指向的对象,看看该对象是否有属性 prop,如果还是没有找到,那么 JavaScript 就会继续查找 obj 的原型的原型,以此类推,直到找到该属性或者到达原型链的末端。

JavaScript 中的原型链是一个单向的链表结构,每个节点都是一个对象,节点之间通过 [[Prototype]] 属性连接起来。当一个对象被创建时,它会自动关联到它的原型对象上,这种关联关系是通过 JavaScript 的 new 操作符实现的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function Star(uanme, age) {
this.uanme = uanme;
this.age = age;
}
Star.prototype.sing = function () {
console.log('i can sing');
}
var ldh = new Star('刘德华', 20)
// 1.只要是对象就有_proto_原型,指向原型对象
console.log(Star.prototype);
// 2.Star原型对象prototype指向Object原型对象prototype
console.log(Star.prototype.__proto__ === Object.prototype);//true
// 3.Object原型对象prototype指向?
console.log(Object.prototype.__proto__);//null

10.2 图解

image-20230501170816625

10.3 成员查找机制(规则)

image-20230501170828873

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
// 任何对象都有原型对象, 也就是prototype属性, 任何原型对象也是一个对象, 该对象就有__proto__属性, 这样一层一层往上找, 就形成了一条链, 我们称此为原型链;

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

function Star(uanme, age) {
this.uanme = uanme;
this.age = age;
}
Star.prototype.sing = function () {
console.log('i can sing');
}
// 2.如果没有就查找它的原型(也就是 __proto__指向的 prototype 原型对象)。
// Star.prototype.sex = 'woman'

// 3.如果还没有就查找原型对象的原型(Object的原型对象)。
// Object.prototype.sex = 'woman'

// 4.依此类推一直找到 Object 为止(null)。undefined

var ldh = new Star('刘德华', 20)
// 1.当访问一个对象的属性(包括方法)时,首先查找这个对象自身有没有该属性。
// ldh.sex = 'man'
console.log(ldh.sex);//undefined

// 注意:当实例对象、原型对象、原型对象的原型Object都有属性时,会根据就近原则查找,如果都没有,就查到找不到返回null为止

11. 原型对象this指向

image-20230501170925567

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function Star(uanme, age) {
this.uanme = uanme;
this.age = age;
}
var that
Star.prototype.sing = function () {
console.log('i can sing');
that = this;
}
var ldh = new Star('刘德华', 20)
// 1.在构造函数中。里面的this指向的对象实例 ldh
ldh.sing()
console.log(that === ldh);//true
// 2.构造函数中的this和原型对象的this,都指向我们new出来的实例对象

12. 扩展内置对象

image-20230501171007104

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
// 原型对象的应用,扩展内置对象方法
Array.prototype.sum = function () {
var sum = 0;
for (var i = 0; i < this.length; i++) {
sum += this[i];
}
return sum;
}
Array.prototype.differ = function (a, b) {
return a - b;
}
// 注意:数组和字符串内置对像不能给原型对象覆盖操作**`Arry.prototype={}`,只能是`Array.prototype.x=function(){}`的方式。
// Array.prototype = {
// constructor: Array,
// sum: function () {
// var sum = 0;
// for (var i = 0; i < this.length; i++) {
// sum += this[i];
// }
// return sum;
// },
// differ: function (a, b) {
// return a - b
// }
// }
// 实例
var arr = [1, 2, 3, 4, 6, 6, 7, 7, 8, 9]
console.log(arr.sum());
var arr1 = new Array(11, 22, 33)
console.log(arr1.sum());
console.log(arr.differ(arr[0],arr[1]));
// 可以通过“内置对象.prototype”这种方式查看里面的所有方法
console.log(Array.prototype);

13. 继承

13.1 说明

image-20230501171057823

13.2 call()

image-20230501171105984

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// ES6之前并没有给我们提供extends继承。我们可以通过构造函数+原型对象模拟实现继承,被称为组合继承。
//call方法
function fn(a, b) {
console.log('自行车创造性');
console.log(this);
console.log(a + b);
}
// 1.传统调用函数
// fn()
// 2.call方法调用函数
// fn.call();//此时还是指向window
// call()可以修改this的指向,使用call()的时候 参数一是修改后的this指向,参数2,参数3..使用逗号隔开连接
var demo = {
name: 'solar'
}
fn.call(demo, 1, 3);

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

image-20230501171147933

13.4 借用原型对象继承父类型方法

image-20230501171226743

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
39
40
41
// 借用父构造函数继承属性
// 1. 父构造函数
function Father(uname, age) {
// this 指向父构造函数的对象实例
this.uname = uname;
this.age = age;
}
Father.prototype.money = function () {
console.log(100000);

};
// 2 .子构造函数
function Son(uname, age, score) {
// this 指向子构造函数的对象实例
Father.call(this, uname, age);
this.score = score;
}
// Son.prototype = Dad.prototype
// 把Son.prototype指向Dad.prototype(把子原型对象指向了父原型对象)
// 这样赋值会有问题,如果修改了子原型对象,父原型对象也会跟着一起变化

// 核心:
Son.prototype = new Father();
// 更新了父构造函数实例对象
// 使子原型对象指向父实例对象,而父实例对象是指向父原型对象的
// 这样子原型对象也就指向了父原型对象,就可以使用父原型对象的方法了

Son.prototype.constructor = Son;

// 这个是子构造函数专门的方法
Son.prototype.exam = function () {
console.log('i can exam');
}

var son = new Son('qwe', 20, 100);
console.log(son);
son.money()//100000
son.exam()//i can exam
// console.log(Dad.prototype);//bug:父亲会有儿子的方法Son.prototype = Dad.prototype
console.log(Father.prototype);//bugsolve:Son.prototype = new Dad();
console.log(Son.prototype.constructor);