JS 面向对象

74 阅读7分钟

1.概念

  • 面向对象是什么?

    • 他并不是一个语法,也并不是一个新的语言,是用 JS 完成需求 的一种思想
  • 面向过程:

    • 注重的是过程,每一步事无巨细,全都由我们的代码从上往下,依次完成
    • 问题:
      1. 每一个功能之间会互相影响
      2. 拿轮播图举例,如果有多个,那么这个代码需要重复写多次
  • 面向对象:

    • 注重的是得到一个对象,这个对象就是我们的需求,比如说这个对象可以是一个轮播图,这个对象也可以是一个分页
  • 注意点: 面向对象我们关注的就是 对象,因为需要批量创建,所以我们创建对象的方式就要和以前不同

创建对象的方式

1.字面量的方式

  • 这个方式不利于批量创建
    let obj = {
      name: 'QF001',
      age: 18
    }

2.内置构造函数

  • 这个方式不利于批量创建
    let obj = new Object()
    let obj2 = new Object()

3.工厂函数的方式

  • 其实就是创建一个函数,但是函数内部可以创建一个对象,我们把这种函数叫做工厂函数
    function createObj (num){
      //3.1 手动创建一个对象
      const obj = {}

      //3.2 手动给对象上添加一些属性
      obj.name = '001' // 添加一个 固定的字符串,每次创建对象它的属性 都是 '001'
      obj.age = num // 利用形参给对象的属性赋值一个 '变量', 这样每次创建对象时都可以修改这个属性的值

      //3.3 手动给对象返回
      return obj
    }
    let obj1 = createObj(18)
    let obj2 = createObj(66)
    console.log(obj1)
    console.log(obj2)

4.自定义构造函数的方式

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

构造函数的缺点

  • 构造函数内部如果有引用数据类型,比如函数;在多次调用构造函数时,每一次都会重新创建一个函数,必然会造成这个内存空间的浪费
function Person() {
    this.name = 'Jack'
    this.age = 18
    this.sayHi = function () {
        console.log('hello word')
    }
}
var o1 = new Person()
var o2 = new Person()
  • 第一次 new 的时候, Person 这个函数要执行一遍, 执行一遍就会创造一个新的函数, 并且把函数地址赋值给 this.sayHi
  • 第二次 new 的时候, Person 这个函数要执行一遍, 执行一遍就会创造一个新的函数, 并且把函数地址赋值给 this.sayHi
  • 执行一遍就会创造一个新的函数, 并且把函数地址赋值给 this.sayHi
  • 这样写的话, 我们两个对象内的 sayHi 函数就是一个代码一模一样, 功能一模一样
  • 但却占用了两个内存空间
  • 也就是说 o1.sayHi 是一个地址, o2.sayHi 是一个地址
  • 所以我们执行 console.log(o1.sayHi === o2.sayHi) 的到的结果是 false
  • 缺点: 一模一样的函数出现了两次, 占用了两个空间地址
  • 要想解决的话, 就需要一个东西, 叫做原型

原型

  • 什么是原型?

    1.每一个函数天生拥有一个属性,叫 prototype,它的属性值是一个对象

    2.我们通常把这个对象叫做 这个函数的 原型

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

  • 如何使用原型中的属性?

    每一个对象 天生拥有一个属性__proto__(前后都是两个下划线)

    这个属性指向自己构造函数的原型

function Person () {}
//向 Person 函数的原型(对象)上 添加一些属性
Person.prototype.abc = '我是添加到 Person 函数的原型上的一个字符串,你可以通过这个 Person 创建出来的对象 身上的 __proto__查看到'
Person.prototype.age = 18
Person.prototype.name = 'gg'
Person.prototype.fn = function () {
    console.log('我是添加在 Person 函数的原型对象上的一个函数')
}
let p1 = new Person()
/**
*  使用 new 结合 Person() 这个过程叫做 构造函数的实例化,最终会得到一个对象
*  在我们当前案例中,是将这个对象放在 变量p1 里面
*  所以有些开发管这个 p1 叫做 实例化对象,实例对象,本质上依然是一个对象
*/
console.log(p1.__proto__ === Person.prototype)
//__proto__属性指向自己构造函数的原型
//因为 p1 这个对象的构造函数是 Person,所以p1.proto__实际指向的是 Person这个函数的原型

原型的作用

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

对象的访问规则

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

案例

  • 需求: 在 数组 上扩展一个方法 getMax,作用是求出数组中的最大值,并且要求所有数组都能使用
let arr = [1,2,3,4,5]
let arr2 = [6,7,8,9,10]
Array.prototype.getMax = function () {
    //函数的 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()

__proto__指向

function Person(name,age){
    this.name = name
    this.age = age
}
Person.prototype.sayHi = function(){
console.log('你好~~~')}
const p1 = new Person('gg',18)

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
  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