面向对象

76 阅读6分钟

面向对象

什么是面向对象?

首先大家要明白,面向对象并不是一个语法,也不是一个新的语言,

它是 JS 为完成需求的一种思想

  1. 在我们最开始接触 JS 时,我们解决问题的方式都是面向过程的,按步骤书写代码,

    这种方式注重的是过程,每一个步骤都书写详细,输入的代码都是按顺序完成

    但是这种方式也是存在问题的:

       (1).每一个实现的功能相互影响
       (2).同样的功能,当同时存在多个同样功能时,实现JS的代码需要书写多遍
    
  2. 但是面向对象就不一样了,面向对象注重结果,也就是得到一个对象,而这个对象就是我们的需求;

  3. 我们以吃面为例看看两者的区别

       (1).面向过程:
           需要准备面粉,准备水,和面,切面,热水,煮面,吃面
           可以看出来面向过程步骤虽然详细但是繁琐
       (2).面向对象:
           找一个面馆,下单,吃面,
           可以看出来,面向对象就是将繁琐的步骤以面馆代替,
           而我们只需通过下单去调用就可以实现我们吃面的需求
           
           
    

创建对象的方式

  1. 字面量方式;
  • 这种方式不适用批量创建对象
let obj = {
      name: 'qwe',
      age:18
     }
  1. 内置构造函数的方式:
  • 这种方式不适用批量创建对象
    let obj1 = new Object()
  1. 工厂函数方式:
  • 其实就是创建一个函数,但是函数内部可以创建对象,这种函数叫做工厂函数
function createObj(Number){
      //3.1 手动创建对象
      const obj = {}
      //3.2给对象添加属性
      obj.name = 'qwe'//添加一个固定的字符串,每次创建对象它的属性都是 'qwe'
      obj.age = Number// 利用形参给对象赋值变量,这样每次创建对象都可以修改这个属性的值  
      //3.3 手动给对象返回
      return obj
    }
    let obj1 = createObj(18)
    let obj2 = createObj(250)
    console.log(obj1)
    console.log(obj2)

构造函数的方式

首先我们需要明白;所谓的构造函数,其本质上就是一个函数, 但是如果在调用时,前面加上关键字new,我们通常称之为构造函数

接下来我们从书写方式上看看构造函数工厂函数有什么不同点

1. 工厂函数

   function createObj(Number){
      //3.1 手动创建对象
      //3.2给对象添加属性
      //3.3 手动给对象返回
    }

2. 构造函数:

function createObj(Number){
      //4.1 自动创建对象          // <---  通过 this 访问
      //4.2 手动给对象添加属性
      //4.3 自动返回对象
    }

*** 示例**

function  Person(name,age){
      //因为构造函数自动创建的对象通过 this 访问,
      //所以添加属性时可以通过this 添加
      this.a = '我是随便添加的属性,固定的内容'
      this.name = name
      this.age = age
    }
    const p1 = new Person('qwe',18)
    console.log(p1) 

但是在我们书写构造函数时,也有一些需要我们注意的

  • 1.构造函数的函数名首字母大写
    • (建议,非强制,目的就是为了和普通函数进行区分)
  • 2.构造函数内不要写 return
    • 如果 return 是一个基本数据类型,写了也没用
    • 如果 return 是一个引用数据类型,写了会导致构造函数失效
  • 3.构造函数调用时,必须 个 new 关键字连用
    • 如果不练用,也可以调用,但是构造函数会失效
  • 4.构造函数内部的 this 指向
    • 当一个函数和 new 连用时,那么这个函数是构造函数
    • 然后内部的 this 指向本次调用被自动创建的对象
  • 5.构造函数不能使用 箭头函数
    • 因为 箭头函数 内部没有 this

构造函数的缺点

  • 内部如果有引用数据类型的,比如函数
    • 在每次调用函数时,每一次都会重新创建一个函数,必然会造成空间的浪费
    function  Person(name,age){
          this.a = '我是随便添加的属性,固定的内容'
          this.name = name
          this.age = age
          this.fn = function(){  
        }
    }
    const p1 = new Person('qwe',18)
    const p2 = new Person('asd',22)
    /**
     * 第一次调用Person构造函数+
     *    自动创建一个对象
     *        1.添加一个屈性name,值为形参name
     *        2.添加一个属性age,值为形参age
     *        3.添加一个屈性a,值为一个囵定的字符串
     *        4.添加一个属性fn,值为一个函数,业时的函数是我们在这个函数内部定义的一个函数,假设地址为coe1
     *     自动返回一个对象
    */
    p1.fn() 
    /**
     * 第二次调用Person构造函数+
     *    自动创建一个对象
     *        1.添加一个屈性name,值为形参name
     *        2.添加一个属性age,值为形参age
     *        3.添加一个屈性a,值为一个囵定的字符串
     *        4.添加一个属性fn,值为一个函数,业时的函数是我们在这个函数内部定义的一个函数,假设地址为coe2
     *     自动返回一个对象
    */
    p2.fn()
  • 对于这种问题,我们可以采取下面的方法解决
 function winFn(){
      console.log('我是fn函数')
    }
 function  Person(name,age){
      this.a = '我是随便添加的属性,固定的内容'
      this.name = name
      this.age = age
      this.fn =  winFn
    }
    const p1 = new Person('qwe',18)
    const p2 = new Person('asd',22)
    console.log(p1)
    console.log(p2)

原型(很重要)

  • 又叫 : 原型空间,原型对象

什么是原型

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

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

     *  每一个对象天生拥有一个属性  __proto__  (前后各有两个下划线)
              这个属性指向自己构造函数的原型
              
function Person(){}
    //向 Person 函数的原型上 添加一个属性 age ,值为18
    Person.prototype.age = 18
    //向 Person 函数的原型上 添加一个属性 fn ,值为一个函数
Person.prototype.fn = function(){
      console.log('我是再原型上添加的fn函数')
    }

    console.log(Person.prototype)
    // 函数的属性,打印  {constructor: ƒ} => constructor : ƒ Person()   表示函数原型
    console.log(Person)  
    // Person(){}  会打印出来函数
    
    let p1 = new Person()
    console.log(p1)//空对象  Person{}
    /**
     * 使用 new 结合 Person() ,这个过程叫做 构造函数实例化,最终会的到一个对象
     *      再我们当前案例中,是将这个对象放在变量 p1 里面
     * 
     *    所以有些开发把 p1 叫做 实例化对象,实例对象,
     *        但本质上依然是一个对象
    */

    console.log(p1.__proto__)
    //{constructor: ƒ} => constructor : ƒ Person()   表示函数原型
    /**
     * __proto__   这个属性指向自己构造函数的原型
     * 
     *    因为 p1 这个对象的构造函数 是 Person()
     *    所以打印 Person() 的原型   p1.__proto__  指向的就是 Person() 的原型
    */

原型的作用:

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

  • 为什么这么做

    • 构造函数的原型上的方法或者属性。在每一个实例化对象中都能正常访问
  • 对象的访问规则:

    • 访问对象的缪一个属性时,会先在对象本身去查找,
      
    •  找到就直接使用,如果没有找到
      
    •  就会去对象的 __proto__ 中去找,找到就使用
      
    •  如果还没有找到,那么会去这个对象(原型)的 __proto__ 查找
      
    •  知道查找到 JS 的顶层对象 Object.prototype 
      
    •  如果还找不到,就不再向上查找了
      
  • 代码示例如下:

    function Person(name, age, exe) {
          this.name = name
          this.age = age
          this.exe = 1
        }

    Person.prototype.fn = function () {
          console.log('我是再原型上添加的fn函数')
        }

    let p1 = new Person('qwe', 10)
    console.log(p1)      // Person {name: 'qwe', age: 10, exe: 1}
    console.log(p1.age)  // 10 => 现在自身找,自身存在age,直接使用
    console.log(p1.exe)  // 1 => 自身没有exe,去Person,找到使用
    console.log(p1.max)  // undefined =>按规则查找,没有找到,输出undefined

原型经典练习题

        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)   //true       
        console.log(Person.__proto__ === Function.prototype)   //true  
        console.log(Person.prototype.__proto__ === Object.prototype)  //true
        console.log(Function.__proto__ === Function.prototype)  //true
        console.log(Function.prototype.__proto__ === Object.prototype)  //true
        console.log(Object.__proto__ === Function.prototype)  //true
        console.log(Object.prototype.__proto__)   //null

ES6

模板字符串

在ES5中要么是单引导包裹要么是双引号包裹

在ES6 中推出的模板字符串是使用 反引号 包亵的

   和之前的区别
     1.反引号能够换行,但是单引号双引号不行
     2.内部如果书写的有 ${} 然后在内部可以识别出变量

展开字符串

在ES6 推出了展开运算符 ... , 其作用主要有以下几个方面:

1.展开数组

let arr = [1, 2, 3]

console.log(arr)// [1, 2, 3]
console.log(...arr)//1 2 3

2.合并数组

let arr2 = [100, 200, 300, ...arr]
console.log(arr2) // [100, 200, 300, 1, 2, 3]

3.展开对象

let obj = {
  a: 1,
  b: 2
}
let obj2 = {
  ...obj,
  c: 3,
  d: 4
}
console.log(obj2) //{a: 1, b: 2, c: 3, d: 4}

4.函数传参

function fn(a, b, c) {
  console.log('我是传参实现的函数')
}
// fn(arr[0], arr[1], arr[2],)//普通传参方式
fn(...arr)     

对象简写语法

  1. 当对象的 key 与 value 相同时 , 并且 value 是变量时, 那么其中一个可以不写
  2. 函数是可以简写
let name = '游戏'

let obj3 = {
  // name: '电脑',
  // name: name,
  name,
  fn: function () {
    console.log('我是函数')
  },

  fn1() {
    console.log('我是简化写法的函数')
  }
}
console.log(obj3)//{name: '游戏', fn: ƒ, fn1: ƒ}
obj3.fn()//我是函数
obj3.fn1()//我是简化写法的函数