面向对象

80 阅读9分钟

一.面向对象是什么 ?

  • => 并不是一个语法, 也并不是一个新的语言, 他是用js完成 需求 的一种思想
  • => 我们一开始学习的解决问题的方式 统称为 面向过程
面向过程 :
  • 注重的是过程, 每一步 事无巨细, 全都有我们代码从上往下
  • 问题 : 每一个功能之间会 互相影响
    • 拿轮播图举例, 如果有多个, 那么这个代码 需要重复的写多次
面向对象 :
  • 注重的是得到一个对象, 这个对象就是我们的需要, 比如说这个对象可以是一个轮播图,也可以是一个分页

二.创建对象的方式 ?

  • 字面量方式 : 不利于批量创建
// 1.字面量方式, 不利于批量创建
        let obj = {
            name : 'qwe',
            age :18
        }
        let obj1 = {
            name : 'qwe',
            age :18
        }
        let obj2 = {
            name : 'qwe',
            age :18
        }

  • 内置构造函数 : 不利于批量创建
 //  2. 内置构造函数
        let obj3 = new Object()
        let obj4 = new Object()
        let obj5 = new Object()
  • 工厂函数的方式(开发者思考出来的一种方式) - 其实就是创建一个函数, 但是函数内部可以创建一个对象, 我们把这种函数叫做工厂函数
/ 3. 工厂函数的方式
        function creatObj(num){
            // 3.1 手动创建一个方式
            const obj = {}
            // 3.2 手动给对象上添加 一些属性
            obj.name = 'qwe' // 添加一个固定的字符串, 每次创建对象的属性值都是 'qwe'
            obj.age = 20  // 写法一 (写死值)
            obj.age = num  // 写法二 (可以通过参数), 利用形参多给对象的属性赋值一个'变量',
            // 3.3 手动给对象返回
            return obj
        }
        let objA = creatObj(22)
        console.log(objA);
        let objB = creatObj(23)
        console.log(objB);

三.构造函数的书写 :

  • 自定义构造函数 ( 构造函数 )

什么是构造函数 ?

  • 本质上就是一个 普通函数
    • => 通过在调用的时候, 前边加一个关键字 new, 那么我们把这种函数叫做构造函数

构造函数的书写 :

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

面试题 : 构造函数有什么注意点 ?

    1. 函数名 首字母大写
    2. 不用return
    3. 调用时, 和 new 关键字 (new 函数名)
    4. 通过this 指向访问该对象属性
  • 工厂函数方式 ( 手动 )
        function creatObj(num){
            // 3.1 手动创建一个方式
            const obj = {}
            // 3.2 手动给对象上添加 一些属性
            obj.name = 'qwe'
            obj.age = num  
            // 3.3 手动给对象返回
            return obj
        }
  • 构造函数方式
        function creatObj1(num){
            // 1. 自动创建一个对象 (通过this访问)
            // 2. 手动给对象添加一些属性
            this.name = 'qwe'
            this.age = num  
            // 3. 自动返回对象
        }
  • 构造函数书写
         // 构造函数的书写
         function Person(name,age){
            // 因为构造函数自动创建出来的对象可以通过 this 来访问, 所以我们需要想这个对象添加属性的时候, 可以通过 this 来添加
            this.name = name
            this.age = age
            this.a = '我是随便添加的一个属性a, 我是固定的内容'
         }
         const p1 = new Person('zxc',18)
         console.log(p1);
         const p2 = new Person('qaz',26)
         console.log(p2);

四.构造函数的缺点 ?

  • 构造函数内部 如果有引用数据类型
    • 在很多次调用构造函数时, 每一次都会重新创建一个函数, 会造成内存空间浪费
        function Person(name,age){
            // 因为构造函数自动创建出来的对象可以通过 this 来访问, 所以我们需要想这个对象添加属性的时候, 可以通过 this 来添加
            this.name = name
            this.age = age
            this.a = '我是随便添加的一个属性a, 我是固定的内容'
            this.fn = function(){
                console.log('我是 fn 函数');
            }
         }
         const p1 = new Person('asd',22)
         const p2 = new Person('qwe',24)
         console.log('两个对象内部的函数 是否为同一个地址' ,p1.fn === p2.fn); // flase 不是同一个地址
  • 如何处理 ?
      • 在构造函数外, 封装一个函数, 在构造函数内调用
         function winfn(){
            console.log('我是 fn 函数');
         }
         function Person(name,age){
            // 因为构造函数自动创建出来的对象可以通过 this 来访问, 所以我们需要想这个对象添加属性的时候, 可以通过 this 来添加
            this.name = name
            this.age = age
            this.a = '我是随便添加的一个属性a, 我是固定的内容'
            this.fn = winfn
         }
         const p1 = new Person('asd',22)
         const p2 = new Person('qwe',24)
         console.log('两个对象内部的函数 是否为同一个地址' ,p1.fn === p2.fn);  // true 共用同一个地址

五. 原型 ( 原型空间 / 原型对象)

什么是原型 ?

  • 每一个函数天生拥有一个 属性 prototype(原型), 他的属性是一个对象
    • 我们通常把这个对象叫做 这个函数的原型(空间|对象)
    • 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 这个函数的原型
        */

原型作用 ?

  • 就是把构造函数中 公共方法 提取出来, 放在原型中
  • 为什么这样做 ?
    • 构造函数的原型上的方法或属性, 在每一个实例化对象中 都能正常访问

对象访问规则 ( 面试题 ) ?

访问对象的某一个属性, 会在对象本身去查找, 找到直接使用

  • 如果没有找到, 那么就回去 对象的 proto 中查找, 找到直接使用
  • 如果还没有找到, 那么就会去这个对象(原型) 的 proto 查找
  • 一直查找到 js 顶级对象 Object.proytype, 如果还没找到, 不在向上查找, 就返回undefined

六. 扩展内置构造函数

  • 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]
        Array.prototype.getMax = function () {
            // console.log(this);
            // 函数的 this 指向他的调用者, 所以我们可以通过 this 拿到数组中能够的值
            let max = this[0]
            for (let i = 1; i < this.length; i++) {  // i = 0 就是第一个自己, 没必要和自己比
                if (this[i] > max) {
                    max = this[i]
                }
            }
            console.log(max);
        }
        arr.getMax()
        arr2.getMax()

七. 课堂练习

        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 指向谁
    • p1.proto === Person.prototype
      • ptoto 指向自己构造函数的原型
    1. Person 的 proto 指向谁
    • Person.proto === Function.prototype
      • Person 是一个构造函数, 构造函数本质就是一个 函数而已
      • 在 js 中, 只要是一个函数, 那么就属于构造函数 Function 的实例化对象
      • proto 指向了 自己构造函数的原型, 所以相当于 指向了 Function.prototype
    1. Person.prototype 的 proto 指向谁
    • Preson.prototppe.proto === Object.prototype
      • Person.prototype 是 Person 的构造函数原型, 本质上就是一个 对象而已
      • 在 js 中, 只要是一个对象, 那么就属于构造函数 Object 的实例对象
      • proto 指向自己构造函数的原型, 所以相当于 指向了 Object.prototype
    1. Function 的 proto 指向谁
    • Function.proto === Function.protype
      • function 是一个构造函数, 构造函数本质上是一个 函数而已
      • 在 js 中, 只要是一个函数, 那么就属于构造函数 Function 的实例化对象
      • proto 指向自己构造函数的原型, 就相当于 指向了 Function.prototype
    1. Function.prototype 的 proto 指向谁
    • Function.prototype.protype === Object.prototype
      • Function.prototype 是 Function 构造函数的原型, 本质上就是一个对象而已
      • 在 js 中, 只要是一个对象, 那么就属于构造函数 Object 的实例化对象
      • proto 指向自己构造函数的原型, 所以相当于 指向了 Object.prototype
    1. Object 的 proto 指向谁
    • Object.proto === Function.prototype
      • Object 是一个构造函数, 构造函数本质上就是一个 函数而已
      • 在 js 中, 只要是一个函数, 那么就属于构造函数 Function 的实例化对象
      • proto 指向自己构造函数原型, 所以相当于 指向了 Function.prototype
    1. Object.prototype 的 proto 指向谁
    • Object.proto.proto === null
      • 因为 Object.prototype 是 js 的顶级对象, 再往上就没有了, 所以这个值为 null

八. 选项卡案例 - 面向对象版

分析 :

  • 面向对象 就是寻找一个机器,这个机器能够帮助我们批量生产 选项卡
  • 目前来看没有这个机器, 那么我们就需要 自己创建一个 机器
  • 思考 :
    • 这个机器需要什么 ?
      • 需要获取元素
      • 给我们获取的元素添加 对应的事件