面向对象

66 阅读8分钟

认识面向对象

    面向对象并不是一个语法, 也并不是一个新的语言, 他是用 JS 完成需求的一种 思想
    我们一开始学习的解决问题的方式,统称为面向过程
    
    面向过程:
            注重的是过程,每一步事无巨细,全都有我们的代码从上往下,依次完成
            问题:
                1.每个功能之间相互影响
                2.拿轮播图举例: 如果有多个, 那么这个代码 需要重复书写多次
    面向对象:
            注重的是得到一个对象, 这个对象就是我们的需求,比如说这个对象可以是一个轮播图,这个对象也可以是一个 分页
     举例:
         拿一个功能: 我想吃碗面
        面向过程:
                1.准备面粉
                2.准备水
                3.和面
                4.切面
                5.烧水
                6.煮面
                7.吃面
         面向对象:
                1.找一个面馆
                2.下单
                3.吃面
                假如 我们没有所谓的 "面馆", 那么我们直接开一个面馆
                创建完成之后的"面馆", 除了你能使用, 其他人也能使用, 起到一个 多次复用的效果

创建对象

创建对象的方式
    1.字面量的方式(此方式不合适,不利于批量创建)
        例: let obj = {
              name: 'qwer',
              age: 23
            }
            
    2.内置构造函数(此方式不合适,不利于批量创建)
        例: let obj = new Object()
        
    3.工厂函数的方式
        其实就是 创建一个函数, 但是函数内部可以创建一个对象, 我们把这种函数叫做工厂函数
        例: 
            function createObj(num) {
                  // 3.1手动创建一个对象
                  const obj = {}

                  // 3.2手动给对象添加一些属性
                  obj.name = 'qwer'  //添加一个 固定的字符串, 每次创建对象他的属性都是 'qwer'
                  obj.age = num   //利用形参给对象的属性赋值一个 '变量',这样每次创建对象时都可以修改这个属性的值
                  // console.log(obj)
                  // 3.3手动给对象返回
                  return obj
             }
  let obj1 = createObj(23)
  

自定义构造函数

什么是构造函数
    本质上就是一个普通函数,如果在调用的时候, 前面加上一个关键字 new , 那么我们把这种函数叫做 构造函数
工厂函数:
    function createObj() {
               // 3.1手动创建一个对象
               // 3.2手动给对象添加一些属性
               // 3.3手动给对象返回
             }
构造函数:
     function CreateObj1() {
               // 1.自动创建一个对象
               // 2.手动给对象添加一些属性
               // 3.自动返回对象
             }
构造函数的书写!!!
      1.构造函数的函数名首字母 大写(非强制,建议大写),目的是为了和普通函数做一个区分
      2.构造函数内,不要写 return
          如果 return 的是一个 基本数据类型, 写了也没用
          如果 return 的是一个 引用数据类型, 写了就会导致构造函数失败
      3.构造函数调用时, 必须和 new 关键字使用,如果不连用,也能调用,但是失去了构造函数的功能
!!!   4.构造函数内部的 this !!!
          当一个函数 和 new 关键字连用的时候, 那么我们说这个函数 是构造函数, 然后这个函数内部的 this 指向本次调用被自动创建出来的那个对象
      5.构造函数不能使用 箭头函数
          因为箭头函数内部没有 this
      例: 
          function Fn () {
                //因为 构造函数自动创建出来的对象可以通过 this 来访问, 所以我们需要向这个对象上添加属性的时候, 可以通过 this 来添加
                this.name = name
                this.age = age
                this.a = '我是随便添加的一个属性 a, 我是固定的内容'
          }
          const p1 = new Person('eddie', 18)
          console.log(p1)

构造函数的缺点

构造函数内部 如果有这个引用数据类型, 比如函数
    在多次调用构造函数时,每一次都会重新创建一个函数, 必然会造成内存空间浪费
原版构造函数(会出现浪费内存的情况)
    function Fn1 (name, age) {
        this.name = name
        this.age = age
        this.a = '我是随便添加的一个属性 a, 我是固定的内容'
        this.fn = function () {
            console.log('我是 fn 函数')
        }
    }
    const p1 = new Fn1('彭于晏', 41)
    p1.Fn1
    const p2 = new Fn1('秦霄贤'24)
    p2.Fn1
    console.log('验证两个对象内部的 fn 函数是否为同一个内存地址: ', p1.Fn1 === p2.Fn1, p1.Fn1 === p3.Fn1)  //false
     
 优化版 构造函数
     function WinFn () {
         console.log('我是 fn 函数') 
     }
     function Fn1 () {
         this.name = name
         this.age = age
         this.a = '我是随便添加的一个属性 a, 我是固定的内容'
         this.fn = WinFn
     }
     const p1 = new Fn1('彭于晏', 41)
     p1.Fn1
     const p2 = new Fn1('秦霄贤'24)
     p2.Fn1
     console.log('验证两个对象内部的 fn 函数是否为同一个内存地址: ', p1.Fn1 === p2.Fn1, p1.Fn1 === p3.Fn1) //true
     

原型

别名: 原型空间 / 原型对象
什么是原型
    => 每一个函数 天生拥有一个属性 prototype,他的属性值是一个 对象
    => 我们通常把这个对象叫做 这个函数的原型(空间|对象)
    => 原型这个对象中 有一个属性 叫做 constructor,这个属性表示的是 当前这个 原型对象 是那个  函数的原型
    
如何使用原型中的属性
    => 每一个对象 天生拥有一个属性 __proto__(前后都是两个下划线)
    => 这个属性指向 自己的构造函数 的原型
总结: !!!!!!
    函数 的prototype属性 指向 自己的原型对象
    实例对象 的__proto__属性 指向 自己构造函数的原型对象
    原型对象 的constructor属性 指向 自己的构造函数
实例:
    function Fn () { }
    //向 Fn 函数的原型(对象)上 添加一些属性
    Fn.prototype.age = 23
    Fn.prototype.name = '秦霄贤'
    Fn.prototype.Fn1 = function () {
        console.log('我是添加在 Fn1 函数的原型对象上的 一个函数')
    }
    console.log(Fn.prototype)  //打印 Fn 函数的原型对象
    let p1 = new Fn1
        使用 new 结合 Fn1() 这个过程叫做 构造函数的实例化, 最终回得到一个对象
        在我们当前案例中, 是将这个对象放在 变量 p1 里面
        所以有些开发管这个 p1 叫做 实例化对象/实例对象,本质上依然是一个对象
    console.log(p1.__proto__) // p1对象的构造函数的原型
    console.log(p1.__proto__  === Fn1.prototype) //true
        __proto__属性指向自己构造函数的原型
            因为 p1 这个对象的构造函数 是 Fn1, 那么也就说,p1__proto__实际指向的就是 Fn1 这个函数的原型

原型链

    把构造函数中  公共方法 提取出来, 放在原型中
这么做的原因:
     构造函数的原型上的方法或者属性, 在每一个实例化对象中都能正常访问
对象的访问规则
     访问对象的某一个属性时,会先在对象本身去查找, 找到就直接使用, 如果没有找到
     那么会去对象的 __proto__ 中查找,找到就使用, 如果没有找到
     那么会去这个对象(原型) 的 __proto__ 查找
     直到查找到 JS 的顶层对象 Object.prototype, 如果还没找到,就不再向上查找
    

扩展内置构造函数

内置构造函数:
    new Object()
    new Array()
    new RegExp()
    new String()
JS 内的数据结构类型
    => 在 JS 中, 任何一个数组, 他的构造函数都是 Array
    => 在 JS 中, 任何一个对象, 他的构造函数都是 Object
    例: 
        用 数组 举例 ---Array.prototype
        需求:在数组上扩展一个方法 getMax ,作用是求出数组中最大值, 并且要求所有的数组都能使用
        
let arr1 = [1, 2, 5, 8, 9, 12]
ler arr2 = [12, 58, 78, 98, 21]
Array.prototype.getMax = function () {
    // 函数的 this 指向的是他的调用者, 所有我们可以通过 this,拿到数组的值
    let max = this[0]
    for (let i = 0;i < this.length; i++) {
        if(this[i] > max){
            max = this[i]
        }
    }
    return max
}
console.log(arr1.getMax())
console.log(arr2.getMax())

面向对象的逻辑问题

function Fn1(name, age) {
    this.name = name
    this.age = age
}
Fn1.prototype.sayHi = function(){
    console.log('Holle')
}
const p1 = new Fn1('abcd',23)
1.p1 的 __proto__指向谁
    __proto__指向自己的构造函数的原型
    所以 相当于 指向了 Fn1.prototype
    p1.__proto__=== Fn1.prototype
    
2.Fn1 的 __proto__指向谁
    Fn1 是一个构造函数,构造函数本质上就是一个函数而已
    在 JS 中, 只要是一个函数, 那么就属于构造函数 Function 的实例化对象
    __proto__指向自己构造函数的原型
    所以就相当于 指向了 Function.prototype
    Fn1.__proto__ === Function.prototype
    
3.Fn1.prototype 的 __proto__指向谁
    Fn1.prototypeFn1 构造函数的原型, 本质上就是一个对象而已
    在 JS 中,只要是一个对象,那么就属于构造函数 Object 的实例化对象
    __proto__指向自己的构造函数的原型, 所以就相当于指向了Object.prototype
   Fn1.prototype.__proto__ === Object.prototype
   
4.Function 的 __proto__ 指向谁
    Fnction 是一个构造函数, 构造函数本质上就是一个函数而已
    在 JS 中,只要是一个函数,那么就属于构造函数 Function的实例化对象
     __proto__指向自己构造函数的原型
     Function.__proto__ === Function.prototype
 
 5.Funtion.prototype.__proto__ 指向谁
     Function.prototypeFunction的原型对象,本质上就是一个对象而已
     在 JS 中,只要是一个对象,那么就属于构造函数 Object的实例化对象
     __proto__是指自己构造函数的原型对象
     Funtion.prototype.__proto__ === Object.prototype
  
 6.Object 的 __proto__指向谁
     在 JS 中, 只要是一个函数, 那么就属于构造函数 Function 的实例化对象
     __proto__ 指向自己构造函数的原型
     所以相当于 指向了 Function.prototype
     Object.__proto__ === Function.prototype
     
 7.Object.prototype 的 __proto__ 指向谁
     因为 Object.prototypeJS 的顶层对象, 再往上就没有了, 所以这个值为 null
     console.log(Object.prototype.__proto__)     null