面向对象-小白上手

55 阅读8分钟

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

    1. 字面量的方式
    • 这个方式不合适,不利于批量创建
    let obj = {
      name: 'QF001',
      age: 18
    }
    1. 内置构造函数
    • 这个方式不合适,不利于批量创建
    let obj = new Object()
    let obj2 = new Object()
    let obj3 = new Object()
    1. 工厂函数的方式
    • 其实就是 创建一个函数,但是函数内部可以创建一个对象,我们把这种函数叫做工厂函数
    function createObj(num) {
      // 3.1 手动创建一个对象
      const obj = {}
      // 3.2 手动给对象上添加一些属性
      obj.name = 'QF001'
      obj.age = num
      // 3.3 手动给对象返回
      return obj
    }

    let obj1 = createObj(18)
    console.log(obj1) //{name: 'QF001', age: 18}
    let obj2 = createObj(66)
    console.log(obj2) //{name: 'QF001', age: 66}

自定义构造函数的方式

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

  • 工厂函数和自定义函数的区别
    // 工厂函数
    function createObj(num) {
      // 1.手动创建一个对象
      const obj = {}
      // 2.手动给对象上添加一些属性
      obj.name = 'QF001'
      obj.age = num
      // 3.手动给对象返回
      return obj
    }
    // 构造函数
    function createObj2(num) {
      // 1.自动创建一个对象
      // 2.手动给对象上添加一些属性
      this.name = 'QF001'
      this.age = num
      // 3.自动返回对象
    }

自定义构造函数的书写

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

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

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

构造函数的缺点

构造函数内部 如果有这个引用数据类型,比如函数,在多次调用构造函数时,每一次都会重新创建一个函数,必然会造成这个内存空间的浪费

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

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

    console.log('验证两个对象内部的 fn 函数是否为同一个内存地址:', p1.fn === p2.fn) //false
  • 更新版构造函数
    function winFn() {
      console.log('我是fn函数')
    }
    function Person(name, age) {
      this.name = name
      this.age = age
      this.a = '我是随便添加的一个属性,我是指定内容'
      this.fn = winFn
    }
    const p1 = new Person('QF666', 18)
    /**
     * 第一次调用Person 构造函数
     *    自动创建一个对象
     *      1. 添加一个属性 name, 值为形参 name
     *      2. 添加一个属性 age, 值为形参 age
     *      3. 添加一个属性a,值为一个固定的字符串
     *      4. 添加一个属性fn,值为一个函数,此时的函数是我们在这个函数内部定义的一个函数,假设地址是QF001
     *    自动返回一个对象
    * */
    p1.fn() //我是fn函数

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

    console.log('验证两个对象内部的 fn 函数是否为同一个内存地址:', p1.fn === p2.fn) //true

原型 (请熟读并背诵全文)

* 原型空间    原型对象
  • 什么是原型:
    • 每一个函数天生拥有一个属性 prototype,它的属性是一个对象,我们通常把这个对象叫做 这个函数的原型(空间|对象)
      • 原型这个对象中 有一个属性 叫做constructor,这个属性表示的是当前这个 原型 是那个 函数的原型
    • 每一个对象 天生拥有一个属性__proto__(前后都是两个下划线),这个属性指向 自己构造函数的原型
    function Person() { }
    // 向 Person 函数原型(对象)上 添加一个属性age, 值为18
    Person.prototype.age = 18
    Person.prototype.name = 'qf'
    Person.prototype.fn = function () {
      console.log('我是添加在Person函数的原型对象上的一个函数')
    }
    console.log(Person.prototype) 
    //{age: 18, name: 'qf', fn: ƒ, constructor: ƒ}
    let p1 = new Person()
    /**
     * 使用new结合Person() 这个过程叫做构造函数的实例化,最终会得到一个对象
     *    在我们当前案例中,是将这个对象放在变量p1里边
     * 
     *    所以有些开发管这个p1叫做实例化对象,实例对象,本质上依然是一个对象
     * 
     * */ 
    //console.log(p1.__proto__) 
    //{age: 18, name: 'qf', fn: ƒ, constructor: ƒ}
    console.log(p1.__proto__ === Person.prototype) //true
    /**
     * __proto__属性指向自己构造函数的原型
     *    因为 p1 这个对象的构造函数 是Person
     *    那么也就是说,p1.__proto__实际指向就是Person这个函数的原型
     * */ 
  • 原型的作用:
    • 把构造函数中 公共方法 提取出来,放在原型中
    • 为什么要这样做?
      • 构造函数的原型上的方法或者属性,在每一个实例化对象中都能正常访问
    function Person(name, age) { 
      this.name = name
      this.age = age
    }
    Person.prototype.age = 18
    Person.prototype.name = 'qf'
    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() //我是添加在Person函数的原型对象上的一个函数
    console.log(p1.abc) //undefined

原型练习题

    // 了解
    // const fn = new Function('console.lang(123')
    // fn() //123

    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