1. 原型对象
-
构造函数的原型属性 prototype, 指向其原型对象;原型对象的 constructor 属性 指向该原型对象对应的构造函数。
-
作用:共享属性和方法;当实例对象找不到成员时,便会找原型对象上的成员。
-
场景:构造函数中创建属性,在原型对象中创建方法,使得代码更加灵活。
<script>
function Person(uname, age, sex) {
this.uname = 'yb'
this.age = 23
this.sex = '男'
this.sing = function () {
console.log('构造函数sing')
}
}
Person.prototype.sex = '女'
Person.prototype.sing = function () {
console.log('原型sing')
}
Person.prototype.dancing = function () {
console.log('原型dancing')
}
let obj = new Person()
console.log(obj)
// 当构造函数和其原型对象的方法、属性同名时,以构造函数为准
// 访问实例对象方法和属性,会先去它的构造函数中找,当构造函数中没有时,再去构造函数的原型对象中找
console.log(obj.sex)
obj.sing()
obj.dancing()
</script>
2. 封装和继承
2.1 封装
- 构造函数体现了面向对象的封装特性。
- 构造函数实例创建的对象彼此独立、互不影响。
2.2 继承
2.2.1 原型继承
把父类的实例对象赋值给子类的原型对象,再指回子类构造函数本身。
操作:独立创建公共对象,里面包含公共属性和方法,然后实例化赋值给构造函数的原型对象。这样可以使得公共属性和方法共享。
<script>
// 1.字面量创建对象,存放公共属性和方法
let obj = {
college: '黑魔法学院',
faculty: '计算机学院',
year: 2022,
major: function () {
console.log('计算机科学与技术')
}
}
// 2.创建独立构造函数
function Stu1() {
this.uname = '小摩托'
this.age = 21
this.id = 2022762018
this.hobby = function () {
console.log('赛车、滑板')
}
}
// 3.继承公共对象中的属性和方法
// 3.1 将公共对象赋值给独立构造函数的原型对象(赋值既覆盖)
Stu1.prototype = obj
// 3.2 再将原型对象指回构造函数
Stu1.prototype.constructor = Stu1
console.log(Stu1.prototype)
// 4.实例化独立对象
let n1 = new Stu1()
console.log(n1)
console.log(n1.college)
</script>
结果如下图所示:
- 这种用字面量创建公共对象的继承会出现问题,如下:
<script>
// 1.字面量创建对象,存放公共属性和方法
let obj = {
college: '黑魔法学院',
faculty: '计算机学院',
year: 2022,
major: function () {
console.log('计算机科学与技术')
}
}
// 2.创建独立构造函数
function Stu1() {
this.uname = '小摩托'
this.age = 21
this.id = 2022762018
this.hobby = function () {
console.log('赛车、滑板')
}
}
function Stu2() {
this.uname = '小飞侠'
this.age = 22
this.id = 2022782023
this.hobby = function () {
console.log('画画、花草')
}
}
// 3.继承公共对象中的属性和方法
Stu1.prototype = obj
Stu1.prototype.constructor = Stu1
// 4.实例化独立对象
let n1 = new Stu1()
console.log(n1)
// console.log(n1.college)
// Stu2继承
Stu2.prototype = obj
Stu2.prototype.constructor = Stu2
console.log(Stu2.prototype)
// 实例化第二个对象
let n2 = new Stu2
console.log(n2)
</script>
如图:Stu1 的原型对象的constructor指向发生了变化,不再是其原来对应的构造函数:
问题分析:
Stu1 和Stu2 的原型对象用的是同一份数据,传递的是同一份地址,当Stu2 的原型对象指向被修改,Stu1的指向也会被修改。
- 最终原型继承:使用构造函数创建对象
<script>
// 1. 创建公共构造函数
function Students() {
this.college = '黑魔法学院'
this.faculty = '计算机学院'
this.year = 2022
major = function () {
console.log('计算机科学与技术')
}
}
// 2. 创建独立构造函数
function Stu1() {
this.uname = '小摩托'
this.age = 21
this.id = 2022762018
this.hobby = function () {
console.log('赛车、滑板')
}
}
function Stu2() {
this.uname = '小飞侠'
this.age = 22
this.id = 2022782023
this.hobby = function () {
console.log('画画、花草')
}
}
// 3. 继承
// 3.1 实例化公共对象 并将 实例化对象 赋值给 独立构造函数的原型对象
Stu1.prototype = new Students()
// 3.2 再将原型对象指回构造函数
Stu1.prototype.constructor = Stu1
// Stu2 继承
Stu2.prototype = new Students()
Stu2.prototype.constructor = Stu2
// 实例化独立对象
let n1 = new Stu1()
console.log(n1)
let n2 = new Stu2
console.log(n2)
</script>
每一次实例化公共对象,都相当于重新创建一份对象 ,使得两个构造函数的原型对象不再使用同一份地址,其指向也就互不影响。
- 方法中的this指向的是当前实例化对象,而不是方法 所在的原型对象。(谁调用,指向谁)
<script>
function fn() {
this.a = 1
this.b = 3
this.sum = function () {
return this.a + this.b
}
}
function fn2() {
this.a = 3
this.b = 4
}
// fn2 继承了fn的函数 函数被调用时,this 指向的是当前继承的对象,而不是继承的函数所在的原型对象。
fn2.prototype = new fn()
fn2.prototype.constructor = fn2
let n = new fn2()
console.log(n.sum()) // 结果为 7 而不是 4
</script>
2.2.2 原型链
-
每个实例对象都有一个属性 _ proto _,指向构造函数的原型对象。原型对象也有自己的原型对象,层层指向直到一个对象的原型对象为 null为止。
-
原型链:由原型构成的链状结构,提供了成员查找机制。
当前实例对象能够调用和访问原型链上的所有属性和方法,当有相同属性和方法时,采用就近原则。 -
在对象原型上添加方法
<script>
// 1.
let obj = new Object({ a: 'wel', b: 'come' })
Object.prototype.join = function () {
return this.a + this.b
}
console.log(obj.join())
// 2.
let arr = new Array(1, 2, 3, 5, 7, 8)
Array.prototype.sum = function () {
let add = 0
for (let i = 0; i < this.length; i++) {
add += this[i]
}
return add
}
console.log(arr.sum())
</script>