什么是面向对象?
面向过程就是分析出解决问题所需要的步骤,然后用函数把这些步骤一步一步实现,使用的时候再一个一个的依次调用就可以了。 举个栗子:蛋炒饭 面向过程,就是按照我们分析好了的步骤,按照步骤解决问题。
面向对象编程概念介绍
目标
能够理解什么是面向对象编程
什么是面向对象?
面向对象是把事务分解成为一个个对象,然后由对象之间分工与合作。
举个栗子:盖浇饭 面向对象是以对象功能来划分问题,而不是步骤。
在面向对象程序开发思想中,每一个对象都是功能中心,具有明确分工。
面向对象编程具有灵活、代码可复用、容易维护和开发的优点,更适合多人合作的大型软件项目。
面向对象的特性:
- 封装性
- 继承性
- 多态性
编程思想-面向过程和面向对象的对比
面向过程编程
优点:性能比面向对象高,适合跟硬件联系很紧密的东西,例如单片机就采用的面向过程编程。
缺点:没有面向对象易维护、易复用、易扩展
面向对象编程
优点:易维护、易复用、易扩展,由于面向对象有封装、继承、多态性的特性,可以设计出低耦合的系统,使系统更加灵活、更加易于维护
缺点:性能比面向过程低
构造函数
目标
能够理解构造函数
构造函数
封装是面向对象思想中比较重要的一部分,js面向对象可以通过构造函数实现的封装。
同样的将变量和函数组合到了一起并能通过 this 实现数据的共享,所不同的是借助构造函数创建出来的实例对象之间是彼此不影响的
function Star(uname,age) {
this.uname = uname
this.age = age
this.sing = function() {
console.log('我会唱歌')
}
}
// 实例化对象
const ldh = new Star('刘德华', 18)
const zxy = new Star('张学友', 19)
::: tip 总结
- 构造函数体现了面向对象的封装特性
- 构造函数实例创建的对象彼此独立、互不影响
构造函数存在的问题
目标
能够理解构造函数中存在的问题
讲解
封装是面向对象思想中比较重要的一部分,js面向对象可以通过构造函数实现的封装。
前面我们学过的构造函数方法很好用,但是方法存在浪费内存的问题
function Pig(name, age) {
// this 表示实例对象
this.name = name // obj.name = '佩奇'
this.age = age
this.say = function () {
console.log('hello')
}
this.eat = function () {
console.log('吃饭')
}
}
let obj1 = new Pig('佩奇', 2)
let obj2 = new Pig('乔治', 3)
let obj3 = new Pig('老段', 4)
console.log(obj1)
console.log(obj2)
console.log(obj3)
问题所在:构造函数里面的方法被调用时方法指向不同的内存空间,会造成内存浪费 如何解决?见下面的原型对象。
原型对象
目标
能够利用原型对象实现方法共享
问题引入
问:在构造函数里面添加方法,会造成浪费内存,应该如何解决呢?
构造函数通过原型分配的函数是所有对象所 共享的。
JavaScript 规定,每一个构造函数都有一个 prototype 属性,指向另一个对象,所以我们也称为原型对象 这个对象可以挂载函数,对象实例化不会多次创建原型上函数,节约内存
我们可以把那些不变的方法,直接定义在 prototype 对象上,这样所有对象的实例就可以共享这些方法。
function Star(uname,age) {
this.uname = uname
this.age = age
}
// 给 构造函数的原型对象 添加方法
// 对象.属性名 = 值
// 对象.方法名 = 值
Star.prototype.sayName = function() {
console.log('hello')
}
// 实例化对象
const ldh = new Star('刘德华', 18)
// 调用原型对象身上的 sayName 方法
ldh.sayName()
const my = new Star('马云', 20)
my.sayName()
// 验证是否解决浪费内存的问题
console.log(ldh.sayName === my.sayName)
原型对象-this
目标
能够说出原型对象里面的this指向谁
问题引入
问:构造函数里面的this指向谁?那么原型对象里面的this指向谁呢?
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script>
function Person(uname, age) {
// 构造函数里面的 this 指向 实例化对象
this.uname = uname
this.age = age
}
// 给实例化对象添加的方法 最好是添加到 构造函数的原型对象身上
// 原型对象里面的 this 指向 实例化对象
let that
Person.prototype.sayHello = function () {
// console.log(this)
that = this
}
// 实例化对象
const ldh = new Person('刘德华', 58)
ldh.sayHello()
console.log(ldh === that)
// console.log(this)
</script>
</body>
</html>
原型对象里面的this指向实例化的对象
案例-给数组扩展方法
目标
能够使用原型对象给数组扩展方法
需求
给数组扩展求最大值方法和求和方法
比如: 以前学过
constarr = [1,2,3]
arr.reverse() 结果是 [3,2,1]
扩展完毕之后:
arr.sum() 返回的结果是 6
示例代码
const arr = [1,2,3]
// 给数组扩展求最大值的方法
Array.prototype.max = () => Math.max(...this)
console.log(arr.max())
// 给数组扩展求和的方法
Array.prototype.sum = () => {
return this.reduce((sum,item)=> sum+item,0)
}
console.log(arr.sum())
proto属性
目标
能够知道__proto__属性的作用
问题引入
问:构造函数可以创建实例对象,构造函数还有一个原型对象,一些公共的属性或者方法放到这个原型对象身上
但是 为啥实例对象可以访问原型对象里面的属性和方法呢?
实例化对象 只能访问 实例成员
构造函数 只能访问 静态成员
实例化对象身上有 __proto__ 属性 指向 构造函数的原型对象
:::warning 注意
- proto 是JS非标准属性
[[prototype]]和proto意义相同- 用来表明当前实例对象指向哪个原型对象prototype
- proto对象原型里面也有一个 constructor属性,指向创建该实例对象的构造函数
constructor属性
目标
能够说出constructor属性的作用
讲解
constructor在哪里? 每个原型对象里面都有个constructor 属性(constructor 构造函数)
作用:该属性指向该原型对象的构造函数, 简单理解,就是指向我的爸爸,我是有爸爸的孩子 使用场景:
- 如果有多个对象的方法,我们可以给原型对象采取对象形式赋值.
- 但是这样就会覆盖构造函数原型对象原来的内容,这样修改后的原型对象 constructor 就不再指向当前构造函数了
- 此时,我们可以在修改后的原型对象中,添加一个 constructor 指向原来的构造函数。
function Star(uname, age) {
this.uname = uname
this.age = age
}
Star.prototype = {
constructor: Person,
sayHello: function () {
console.log('hello')
},
sayName: function () {
console.log('小马哥月薪过万')
}
}
const ldh = new Person('刘德华', 18)
构造函数-实例化对象-原型对象三角关系
目标
能够画出构造函数-实例化对象-原型对象三角恋关系图
讲解
总结
- 构造函数身上有一个
prototype属性 指向 构造函数的原型对象 - 构造函数的原型对象身上 有一个
constructor属性 指向 其构造函数 - 实例化对象身上有一个
__proto__属性指向构造函数的原型对象 - 实例化对象的
__proto__的constructor指向构造函数本身
继承
目标
能够说出什么是继承
什么是继承
继承:让一个对象拥有另一个对象的属性和方法
// 1. 继承 属性
// 2. 继承 方法
function Person(uname,age) {
this.uname = uname
this.age = age
}
function Son(uname,age,gender) {
this.uname = uname
this.age = age
this.gender = gender
}
call方法
目标
能够所用call方法改变函数里面的this指向
问题导入
问:全局作用域下函数里面的this指向哪个对象?
window对象
可以使用call方法改变里面的this指向
基本语法
fun.call(thisArg, arg1, arg2, ...)
作用:使用 call 方法调用函数,同时指定被调用函数中 this 的值
参数说明:
- thisArg:在 fun 函数运行时指定的 this 值
- arg1,arg2....:传递函数的其他参数
// 语法:函数名.call(对象, 参数1,参数2...)
// 作用:可以改变函数里面的 this 指向 会调用函数 ,可以将参数传递到 函数里面
const obj = {
uname: 'zs'
}
function f(x, y) {
console.log(this)
console.log(x, y)
}
f.call(obj, 10, 20)
## 借用父构造函数继承属性
### 目标
能够写出借用构造函数实现继承父对象属性
### 讲解
**核心原理**:通过`call()`方法把父构造函数的 `this`指向 子构造函数的`this`,这样就可以实现子对象继承父构造函数的属性
// 父构造函数 function Father(uname, age) { this.uname = uname this.age = age } // 子构造函数 需要继承 Father构造函数的 属性 function Son(uname, age, game) { // 借用 父 构造函数 来实现继承 属性 // 调用 Father 函数 不会使用 new 关键字调用 Father Father.call(this, uname, age) this.game = game } // 实例化对象 const s1 = new Son('吴所谓', 10, '王者荣耀') console.log(s1)
## 利用原型继承继承方法
### 目标
能够利用原型继承实现继承方法
### 讲解
重温一下构造函数、原型和实例的关系:
每个构造函数都有一个原型对象,原型对象有一个属性指回构造函数,而实例有一个内部指针指向原型对象。
如果原型对象是另一个构造函数的实例对象呢?
// 原型链继承 主要是继承 方法 // 父构造函数 function Father() { } Father.prototype.sayHello = function () { console.log('hello') } // 子构造函数 function Son() { } // 继承 父构造函数的方法 // 将 父构造函数的实例化对象 赋值给 子构造函数的原型对象 即可 Son.prototype = new Father() Son.prototype.game = function () { console.log('上王者') } const s1 = new Son() s1.sayHello() s1.game() // // 实例化 父构造函数 const p1 = new Father() p1.sayHello()
## 利用原型链继承方法问题
### 目标
能够知道利用原型链继承方法问题并解决问题
### 讲解
当我们将一个构造函数的实例化对象赋值给另外一个构造函数的原型对象了,该构造函数的原型对象的`constructor`属性是否还指向本身的构造函数吗?
如何解决呢?
- 可以手动的将该构造函数的原型对象的`constructor`属性 指向 该构造函数
// 原型链继承 主要是继承 方法 // 父构造函数 function Father() { } Father.prototype.sayHello = function () { console.log('hello') } // 子构造函数 function Son() { } // 继承 父构造函数的方法 // 将 父构造函数的实例化对象 赋值给 子构造函数的原型对象 即可 Son.prototype = new Father() Son.prototype.constructor = Son Son.prototype.game = function () { console.log('上王者') } const s1 = new Son() s1.sayHello() s1.game() // // 实例化 父构造函数 const p1 = new Father() p1.sayHello()
## 组合继承
### 目标
能够使用`组合继承`实现继承属性与方法
### 讲解
借用父构造函数只能实现继承属性
利用原型链继承只能实现继承方法
可以将两个组合一起就形成可以继承属性与方法
// 组合继承 = 借用构造函数继承 + 原型链继承 继承了父构造函数的属性与方法 // 借用构造函数继承 继承 父构造函数的属性 // 原型链继承 继承 父构造函数的方法 function Father(uname, age) { this.uname = uname this.age = age } Father.prototype.sayHello = function () { console.log('hello') } function Son(uname, age) { // 使用借用构造函数实现继承属性 Father.call(this, uname, age) } Son.prototype = new Father() // 让 构造函数的原型对象的 constructor 指回 Son 构造函数 Son.prototype.constructor = Son // 实例化 Son 构造函数 const s1 = new Son('吴所谓', 18) console.log(s1) s1.sayHello()
## 原型链
### 目标
能够说出原型链的作用
### 什么是原型链
**概念**:
基于原型对象的继承使得不同构造函数的原型对象关联在一起,并且这种关联的关系是一种链状的结构,我们将原型对象的链状结构关系称为**原型链 (** 面试爱问 **)**
原型链研究的是 对象属性的 查找机制。
理解起来,并不是太难。
- 实例对象 有原型对象
- 原型对象也是对象,那也有原型对象..............
- 这样就形成了多层继承了
![]() 原型链的作用:
0. 当访问一个对象的属性(包括方法)时,首先查找这个对象自身有没有该属性。
0. 如果没有就查找它的原型(也就是 **proto**指向的 prototype 原型对象)
0. 如果还没有就查找原型对象的原型(Object的原型对象)
0. 依此类推一直找到 Object 为止(null)
0. **proto**对象原型的意义就在于为对象成员查找机制提供一个方向,或者说一条路线