JS-面向对象

104 阅读8分钟

一、认识面向对象

面向对象是什么?

并不是一个语法,也不是一个新的语言,它是用JS完成需求的一种思想

我们一开始学习的解决问题的方式,统称为面向过程

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

问题:每一个功能之间会互相影响

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

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

拿一个功能举例:我想要吃一碗面

面向过程

  • 1.准备面粉
  • 2.准备水
  • 3.和面
  • 4.切面
  • 5.烧水
  • 6.煮面
  • 7.吃面

面向对象

  • 1.找一个面馆
  • 2.下单
  • 3.吃面

假如我们没有所谓的“面馆”,那么我们直接开一个面馆

创建完成之后的“面馆”,除了你能使用,其他人也能使用,起到一个多次复用的效果

轮播图:

面向过程

  • 1.const banner = ···
  • 2.const imgBox = ···
  • 3.const index = ···
  • 4.const timer = ···
  • 5.function copyEle (){内部处理的是假图,这个函数依赖imgBox这个变量}
  • 6.function setFocus (){内部处理的是焦点,依赖imgBox,focus}
  • 7.function···

面向过程,创建第二个轮播图

  • 1.const banner2 = ···
  • 2.const imgBox2 = ···
  • 3.const index2 = ···

面向对象

  • 1.找一个“机器”,能够帮我们创建一个轮播图
  • 2.发现没有这个“机器”
  • 3.我们创建一个“机器”
  • 4.“机器”创建完成之后,就能给我们创建一个轮播图
  • 5.这个“机器”的特点就是能够批量创建

注意点:面向对象我们关注的就是对象,因为需要批量创建,所以我们创建对象的方式就要和以前不同

二、创建对象的方式

1.字面量的方式

    let obj = {
      name: 'QF001',
      age: 18
    }

这个方式不合适,不利于批量创建

2.内置构造函数

这个方式不合适,不利于批量创建

let obj = new Object()

3.工厂函数的方式

其实就是创建一个函数,函数内部可以创建一个对象,我们把这种函数叫做工厂函数

    // 3.工厂函数的方式
    function createObj(num) {
      // 3.1手动创建一个对象
      const obj = {}
      
      // 3.2手动给对象上添加一些属性
      obj.name = 'QF001' //添加一个固定的字符串,每次创建对象他的属性都是 'QF001'
      // obj.age = 18
      obj.age = num //利用形参给对象的属性赋值一个变量,这样每次创建对象时都可以修改这个属性的值

      // 3.3手动给对象返回
      return obj
    }

    let obj1 = createObj(18)
    let obj2 = createObj(66)
    console.log(obj1)
    console.log(obj2)

三、自定义构造函数

1.什么是构造函数

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

2.构造函数的书写

  • 1.构造函数的函数名首字母大写(建议,非强制),目的就是为了和普通函数做一个区分

  • 2.构造函数内不要写return

    • 如果return的是一个基本数据类型,写了也没用

    • 如果return的是一个引用数据类型,写了就会导致构造函数没用

  • 3.构造函数调用时,必须和new关键字连用

    • 如果不连用,也能调用,但是构造函数就没用了
  • 4.构造函数内部的this

    • 当一个函数和new关键字连用的时候,那我们说这个函数是构造函数

    • 然后这个函数内部的this指向本次调用被自动创建出来的那个对象1

  • 5.构造函数不能使用箭头函数

    • 因为箭头函数内部没有this

因为构造函数自动创建出来的对象可以通过this来访问,所以我们需要向这个对象上添加属性的时候,可以通过this来添加

    function Person(name, age) {
      // 因为构造函数自动创建出来的对象可以通过this来访问,所以我们需要向这个对象上添加属性的时候,可以通过this来添加
      this.name = name
      this.age = age
      this.a = '我是随便添加的一个属性a,我是固定的内容'
    }
    const p1 = new Person('QF666', 18)
    console.log(p1)
    const p2 = new Person('QF001', 666)
    console.log(p2)

3.构造函数的缺点

构造函数内部,如果有这个引用数据类型,比如函数

在多次调用构造函数时,每一次都会重新创建一个函数,必然会造成这个内存空间的浪费

    // 原版构造函数(有小问题)
    function Person(name, age) {
      this.name = name
      this.age = age
      this.a = '我是随便添加的一个属性a,我是固定的内容'
      this.fn = function () {
        console.log('我是fn函数')
      }
    }


    const p1 = new Person('QF666', 18)
    /**
     * 第一次调用Person构造函数
     *    自动创建一个对象
     *        1.添加一个属性name,值为形参name
     *        2.添加一个属性age,值为形参age
     *        3.添加一个属性a,值为一个固定的字符串
     *        4.添加一个属性fn,值为一个函数,此时的函数是我们在这个函数内部定义的一个函数,假设地址为GD001
     *    自动返回一个对象
    */
    console.log(p1)
    p1.fn()

    const p2 = new Person('QF001', 99)
    const p3 = new Person('QF003', 66)
    /**
     * 第二次调用Person构造函数
     *    自动创建一个对象
     *        1.添加一个属性name,值为形参name
     *        2.添加一个属性age,值为形参age
     *        3.添加一个属性a,值为一个固定的字符串
     *        4.添加一个属性fn,值为一个函数,此时的函数是我们在这个函数内部定义的一个函数,假设地址为GD002
     *    自动返回一个对象
    */
    p2.fn()
    console.log('验证两个对象内部的fn函数是否为同一个内存地址:', p1.fn === p2.fn, p1.fn === p3.fn) 
    //false  false

更新版构造函数

    // 更新版构造函数
    function winFn() {
      console.log('我是winFn函数')
    }
    function Person(name, age) {
      this.name = name
      this.age = age
      this.a = '我是随便添加的一个属性a,我是固定的内容'
      this.fn = winFn
    }
    const p1 = new Person('QF666', 18)
    const p2 = new Person('QF001', 99)
    p1.fn()
    p2.fn()
    console.log('验证两个对象内部的fn函数是否为同一个内存地址:', p1.fn === p2.fn)
    //true true

四、原型(原型空间/原型对象)

1.什么是原型

  • 每一个函数天生拥有一个属性 prototype ,他的属性值是一个对象,我们通常把这个对象叫做这个函数的原型(空间|对象)

  • 原型这个对象中有一个属性叫做 constructor ,这个属性表示的是:当前这个原型是哪个函数的原型

  • 每一个对象天生拥有一个属性 __proto__ (前后都是两个下划线),这个属性指向自己构造函数的原型

    function Person() { }
    // 向 Person函数的原型(对象)上添加一个属性age,值为18
    Person.prototype.abc = '我是添加到Person函数的原型上的一个字符串,你可以通过这个Person创建出来的对象 身上的 __proto__ 查看到'
    Person.prototype.age = 18
    Person.prototype.name = '千锋'
    Person.prototype.fn = function () {
      console.log('我是添加在Person函数的原型对象上的一个函数')
    }
    console.log(Person.prototype)

    let p1 = new Person()
    /**
     * 使用 new 结合 Person() 这个过程叫做构造函数的实例化,最终会得到一个对象
     *    在我们当前案例中,是将这个对象放在变量p1里边
     * 
     *    所以有些开发管这个p1叫做实例化对象,实例对象,本质上依然是一个对象
    */
    // console.log(p1.__proto__)
    console.log(p1.__proto__ === Person.prototype)
    /**
     * * __proto__ 属性指向自己构造函数的原型
     * 
     *    因为p1这个对象的构造函数时Person
     * 
     *    那么也就是说, p1.__proto__ 实际指向的就是Person 这个函数的原型
    */
  • 使用 new 结合 Person() 这个过程叫做构造函数的实例化,最终会得到一个对象

  • 在我们当前案例中,是将这个对象放在变量p1里边

  • 所以有些开发管这个p1叫做实例化对象,实例对象,本质上依然是一个对象

  • 因为p1这个对象的构造函数是Person那么也就是说, p1.__proto__ 实际指向的就是Person 这个函数的原型

2.原型的作用

把构造函数中公共的方法提取出来,放在原型中

为什么?-- 构造函数的原型上的方法或者属性,在每一个实例化对象中都能正常访问

3.对象的访问规则(面试题)

  • 访问对象的某一个属性时,会先在对象本身去查找
    • 找到就直接使用
    • 如果没有找到,那么回去对象的 __proto__ 中查找
      • 找到就使用
      • 如果没有找到,那么回去这个对象(原型)的 __proto__ 查找
      • 直到查找到 JS 的顶层对象 Object.prototype,如果还没找到,就不再向上查找
    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__)

    console.log(p1)
    console.log(p1.fn)
    p1.fn()
    console.log(p1.abc) //undefined

五、扩展内置构造函数

    new Object()
    new Array()
    new RegExp()
    new String()

JS 内的数据结构类

  • 在 JS 中,任何一个数组,他的构造函数都是 Array

  • 在 JS 中,任何一个对象,他的构造函数都是 Object

数组举例:Array.prototype

需求:在数组上扩展一个方法 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()