面向对象
1. 认识面向对象
面向对象是什么?
他并不是一个语法, 也并不是一个新的语言, 他是用 JS 完成需求 的一种思想 我们一开始学习的解问题的方式, 统称为面向过程
面向过程:
注重的是过程, 每一步事无巨细, 全都有我们的代码从上往下 依次完成
问题:
-
每一个功能之间会互相影响
-
拿轮播图举例, 如果有多个, 那么这个代码 需要重复的写多次
面向对象:
注重的是得到一个对象, 这个对象就是我们的需求, 比如说这个对象可以是一个轮播图, 这个对象也可以是一个 分页
- 找个 '机器', 能够帮助我们创建一个 轮播图
- 发现没有这个 '机器'
- 我们创建一个 '机器'
- 机器创建完成之后, 就能给我们 创建一个 轮播图
- 这个 '机器' 的特定 就是 能够批量创建 注意点: 面向对象我们关注的就是 对象, 因为需要批量创建, 所以我们创建对象的方式就要和以前不同
2. 创建对象的方式
创建对象的方式 (需要能够方便我们批量创建对象)
- 字面量的方式(这个方式不合适, 不利于批量创建)
- 内置构造函数(这个方式不合适, 不利于批量创建)
- 工厂函数的方式 其实就是 创建一个函数, 但是函数内部可以创建一个对象, 我们把这种函数叫做工厂函数
// 1. 字面量的方式
let obj = {
name: 'QF001',
age: 18
}
// 2. 内置构造函数
let obj = new Object()
// 3. 工厂函数
function createObj(num) {
// 3.1 手动创建一个对象
const obj = {}
// 3.2 手动给对象上添加一些属性
obj.name = 'QF001' // 添加一个 固定的字符串, 每次创建对象他的属性都是 ''QF001
obj.age = num // 利用形参给对象的属性赋值一个 '变量', 这样每次创建对象时都可以修改这个属性的值
// 3.3 手动给对象返回
return obj
}
let obj1 = createObj(18)
let obj2 = createObj(66)
console.log(obj1)
console.log(obj2)
3. 构造函数的书写
什么是构造函数
本质上就是一个普通函数, 如果再调用的时候, 前边加上一个关键字 new
构造函数的书写
- 构造函数的函数名首字母 大写 (建议, 非强制), 目的就是为了和普通函数做一个区分
- 构造函数内 不要写 return
- 如果 return 的是一个 基本数据类型, 写了也没用
- 如果 return 的是一个 引用数据类型, 写了就会导致构造函数没用
- 构造函数调用时, 必须和 new 关键字连用
- 如果不连用, 也能调用, 但是构造函数就没用了
- 构造函数内部的 this
- 当一个函数 和 new 关键字连用的时候, 那么我们说这个函数是 构造函数
- 然后这个函数内部的 this 指向本次调用被自动创建出来的那个对象
- 构造函数不能使用 箭头函数
- 因为箭头函数内部没有 this
面试题: 构造函数有什么注意点?
// 构造函数的书写
function Person(name, age) {
// 因为 构造函数自动创建出来的对象可以通过 this 来访问, 所以我们需要向这个对象上添加属性的时候, 可以通过 this 来添加
this.name = name
this.age = age
this.a = '我是随便添加的一个属性 a, 我是固定的内容'
// return '我是一个 没有意义的字符串'
// return {
// t: '当你看到我的时候, 说明构造函数失效了'
// }
}
const p1 = new Person('QF666', 18)
console.log(p1)
const p2 = new Person('QF001', 666)
console.log(p2)
4. 构造函数的缺点
- 构造函数内部 如果有这个引用数据类型, 比如函数
- 在多次调用构造函数时, 每一次都会重新创建一个函数, 必然会造成这个内存空间的浪费
// 原版构造函数 (有小问题)
function Person(name, age) {
this.name = name
this.age = age
this.a = '我是随便添加的一个属性 a, 我是固定的内容'
this.fn = function () {
console.log('我是 fn 函数')
}
}
function winFn() {
console.log('我是 fn 函数')
}
function Person(name, age) {
this.name = name
this.age = age
this.a = '我是随便添加的一个属性 a, 我是固定的内容'
this.fn = winFn
}
const p1 = new Person('QF666', 18)
p1.fn()
const p2 = new Person('QF001', 99)
const p3 = new Person('QF001', 99)
p2.fn()
// 原版函数中两个输出都为 false, 处理后的函数两个输出都是 true
console.log('验证两个对象内部的 fn 函数是否为同一个内存地址: ', p1.fn === p2.fn, p1.fn === p3.fn)
5. 原型(又名: 原型空间 原型对象)
什么是原型?
- => 每一个函数 天生拥有一个属性 prototype, 他的属性值是一个 对象;
- => 我们通常把这个对象叫做 这个函数的原型(空间|对象)
- => 原型这个对象中 有一个属性 叫做 constructor, 这个属性表示的是: 当前这个 原型 是那个 函数的原型
如何使用原型中的属性?
- => 每一个对象 天生拥有一个属性 proto (前后都是两个下划线)
- => 这个属性指向 自己构造函数 的原型
function Person() { }
// 向 Person 函数的原型(对象)上 添加一个属性 age, 值为 18
Person.prototype.age = 18
Person.prototype.name = '千锋'
Person.prototype.fn = function () {
console.log('我是添加在 Person 函数的原型对象上 的一个函数')
}
Person.prototype.abc = '我是添加到 Person 函数的原型上的一个字符串, 你可以通过 Person 创建出来的对象上的 __proto__ 查看'
let p1 = new Person()
/**
* 使用 new 结合 Person() 这个过程叫做 构造函数的实例化, 最终会得到一个对象
* 在我们当前案例中, 是将这个对象放在 变量 p1 里边
*
* 所以有些开发管这个 p1 叫做 实例化对象, 实例对象, 本质上依然是一个对象
*/
// console.log(p1.__proto__)
console.log(p1.__proto__ === Person.prototype)
/**
* __proto__ 属性指向自己构造函数的原型
*
* 因为 p1 这个对象的构造函数 是 Person
*
* 那么也就是说, p1.__proto__ 实际指向的就是 Person 这个函数的原型
*/
6. 原型下
原型的作用:
把构造函数中 公共方法 提取出来, 放在原型中
为什么要这样做?
构造函数的原型上的方法或者属性, 在每一个实例化对象中都能正常访问
function Person(name, age) {
this.name = name
this.age = age
}
Person.prototype.fn = function () {
console.log('我是添加在 Person 函数的原型对象上 的一个函数')
}
let p1 = new Person('QF001', 18)
console.log(p1.__proto__)
let p2 = new Person('QF002', 28)
console.log(p2.__proto__)
/**
* 对象的访问规则
* 访问对象的某一个属性时, 会先在对象本身去查找, 找到就直接使用, 如果没有找到
*
* 那么会去 对象的 __proto__ 中查找, 找到就使用, 如果没有找到
*
* 那么会去这个对象(原型) 的 __proto__ 查找
*
* 直到查找到 JS 的顶层对象 Object.prototype, 如果还没找到 就不在向上查找
*/
console.log(p1)
console.log(p1.fn)
p1.fn()
console.log(p1.abc) // undefined
7. 扩展内置构造函数
扩展内置构造函数
- new Object()
- new Array()
- new RegExp()
- new String() js 内的数据结构类
- 在 JS 中, 任何一个数组, 他的构造函数都是 Array
- 在 JS 中, 任何一个对象, 他的构造函数都是 Object
- 拿 数组举例:
Array.prototype
/**
* 需求: 在数组上扩展一个方法, 要求, 所有数组都能使用
*/
Array.prototype.abc = function () {
console.log('我是扩展在 Array 构造函数的原型上的一个函数')
}
let arr = [1, 2, 3, 4, 5]
console.log(arr.__proto__)
arr.abc()
/**
* 需求: 在 数组上扩展一个方法 getMax, 作用是求出数组中的最大值, 并且要求所有数组都能使用
*/
let arr = [1, 2, 3, 4, 5]
let arr2 = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
Array.prototype.getMax = function () {
// console.log(this)
// 函数的 this 指向是 他的调用者, 所以我们可以通过 this, 拿到数组中的值
let max = this[0]
for (let i = 1; i < this.length; i++) {
if (this[i] > max) {
max = this[i]
}
}
console.log(max)
}
arr.getMax()
arr2.getMax()
8. 课堂练习
// const fn = new Function('console.log(123)')
// fn()
function Person(name, age) {
this.name = name
this.age = age
}
Person.prototype.sayHi = function () {
console.log('你好~~~')
}
const p1 = new Person('QF001', 18)
/**
* 1. p1 的 __proto__ 指向谁
* __proto__ 指向自己构造函数的原型
* 所以 相当于 指向了 Person.prototype
*
* p1.__proto__ === Person.prototype
*
* 2. Person 的 __proto__ 指向谁
* Person 是一个构造函数, 构造函数本质上就是一个函数而已
* 在 JS 中, 只要是一个函数, 那么就属于构造函数 Function 的实例化对象
* __proto__ 指向自己构造函数的原型
* 所以相当于 指向了 Function.prototype
*
* Person.__proto__ === Function.prototype
*
* 3. Person.prototype 的 __proto__ 指向谁
* Person.prototype 是 Person 构造函数的原型, 本质上就是一个对象而已
* 在 JS 中, 只要是一个对象, 那么就属于构造函数 Object 的实例化对象
* __proto__ 指向自己构造函数的原型
* 所以相当于指向了 Object.prototype
*
* Person.prototype.__proto__ === Object.prototype
*
* 4. Function 的 __proto__ 指向谁
* Function 是一个构造函数, 构造函数本质上就是一个函数而已
* 在 JS 中, 只要是一个函数, 那么就属于构造函数 Function 的实例化对象
* __proto__ 指向自己构造函数的原型
* 所以相当于 指向了 Function.prototype
*
* Function.__proto__ === Function.prototype
*
* 5. Function.prototype 的 __proto__ 指向谁
* Function.prototype 是 Function 构造函数的原型, 本质上就是一个对象而已
* 在 JS 中, 只要是一个对象, 那么就属于构造函数 Object 的实例化对象
* __proto__ 指向自己构造函数的原型
* 所以相当于指向了 Object.prototype
*
* Function.prototype.__proto__ === Object.prototype
*
* 6. Object 的 __proto__ 指向谁
* Object 是一个构造函数, 构造函数本质上就是一个函数而已
* 在 JS 中, 只要是一个函数, 那么就属于构造函数 Function 的实例化对象
* __proto__ 指向自己构造函数的原型
* 所以相当于 指向了 Function.prototype
*
* Object.__proto__ === Function.prototype
*
* 7. Object.prototype 的 __proto__ 指向谁
* 因为 Object.prototype 是 JS 的顶层对象, 在往上就没有了, 所以这个值为 null
* console.log(Object.prototype.__proto__) null
*/
console.log(p1.__proto__ === Person.prototype)
console.log(Person.__proto__ === Function.prototype)
console.log(Person.prototype.__proto__ === Object.prototype)
console.log(Function.__proto__ === Function.prototype)
console.log(Function.prototype.__proto__ === Object.prototype)
console.log(Object.__proto__ === Function.prototype)
console.log(Object.prototype.__proto__)