JavaScript——面向对象

107 阅读8分钟

面向对象

1. 认识面向对象

面向对象是什么?

他并不是一个语法, 也并不是一个新的语言, 他是用 JS 完成需求 的一种思想 我们一开始学习的解问题的方式, 统称为面向过程

面向过程:

注重的是过程, 每一步事无巨细, 全都有我们的代码从上往下 依次完成

问题:

  1. 每一个功能之间会互相影响

  2. 拿轮播图举例, 如果有多个, 那么这个代码 需要重复的写多次

面向对象:

注重的是得到一个对象, 这个对象就是我们的需求, 比如说这个对象可以是一个轮播图, 这个对象也可以是一个 分页

  1. 找个 '机器', 能够帮助我们创建一个 轮播图
  2. 发现没有这个 '机器'
  3. 我们创建一个 '机器'
  4. 机器创建完成之后, 就能给我们 创建一个 轮播图
  5. 这个 '机器' 的特定 就是 能够批量创建 注意点: 面向对象我们关注的就是 对象, 因为需要批量创建, 所以我们创建对象的方式就要和以前不同

2. 创建对象的方式

创建对象的方式 (需要能够方便我们批量创建对象)

  1. 字面量的方式(这个方式不合适, 不利于批量创建)
  2. 内置构造函数(这个方式不合适, 不利于批量创建)
  3. 工厂函数的方式 其实就是 创建一个函数, 但是函数内部可以创建一个对象, 我们把这种函数叫做工厂函数
// 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

构造函数的书写

  1. 构造函数的函数名首字母 大写 (建议, 非强制), 目的就是为了和普通函数做一个区分
  2. 构造函数内 不要写 return
  • 如果 return 的是一个 基本数据类型, 写了也没用
  • 如果 return 的是一个 引用数据类型, 写了就会导致构造函数没用
  1. 构造函数调用时, 必须和 new 关键字连用
  • 如果不连用, 也能调用, 但是构造函数就没用了
  1. 构造函数内部的 this
  • 当一个函数 和 new 关键字连用的时候, 那么我们说这个函数是 构造函数
  • 然后这个函数内部的 this 指向本次调用被自动创建出来的那个对象
  1. 构造函数不能使用 箭头函数
  • 因为箭头函数内部没有 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__)