07_JavaScript的面向对象

115 阅读12分钟

JavaScript的面向对象

对象类型是一种存储键值对(key-value)的复杂的数据类型

基本数据类型可以存储一些简单的值,但是现实世界的事物抽象成程序时,往往比较复杂

这个时候,我们需要一种新的类型将这些特性和行为组织在一起,这种类型就是对象类型

对象类型可以使用{...}来创建的复杂类型,里面包含的是键值对(“key: value”)

键值对可以是属性和方法(在对象中的函数称之为方法)

其中key是字符串(也叫做属性名property name ,ES6之后也可以是Symbol类型,后续学习);

其中value可以是任意类型,包括基本数据类型、函数类型、对象类型等;

    /*
      两个术语: 函数/方法
         函数(function): 如果在JavaScript代码中通过function默认定义一个结构, 称之为是函数.
         方法(method): 如果将一个函数放到对象中, 作为对象的一个属性, 那么将这个函数称之为方法.
    */
    function foo() {
    }

    // key: 字符串类型, 但是在定义对象的属性名时, 大部分情况下引号都是可以省略的
    // 对象的多个键值对之间使用逗号进行分割
    var person = {
      // key: value
      // name是属性名
      // wjl是属性值
      name: "wjl",
      // 对象的属性名可以是字符串或symbol类型的值
      // 如果key是字符串类型,且是合法的js变量对应的字符串的时候,key的引号一般会省略
      age: 18,
      height: 1.88,
      // 但是如果key的类型是字符串,且key不是一个合法的js变量的时候,key的引号不可以省略
      'my friend': 'kobe',
      "my friend": {
        name: "kobe",
        age: 30
      },
      // 对象的值可以是任何的合法数据类型
      // 如果对应的属性值是一个函数(function)的时候,我们就称这种函数为方法(method)
      run: function() {
        console.log("running")
      },
      eat: function() {
        console.log("eat foods")
      },
      study: function() {
        console.log("studying")
      }
    }

1.创建对象和使用对象

  1. 对象字面量(Object Literal)
   // 1.对象字面量
    var obj1 = {
      name: "wjl"
    }
  1. new Object+动态添加属性

    // 2.new Object()
    // Object构造函数
    var obj2 = new Object()
    obj2.name = "kobe"
  1. new 其他类 (可以是JS内置的类也可以是用户自定义的类)
    function Person() {}
    var obj3 = new Person()

2. 对象的常见操作

    // 1.定义了一个对象
    var info = {
      name: "why",
      age: 18,
      friend: {
        name: "kobe",
        age: 30
      },
      running: function() {
        console.log("running~")
      }
    }
    // 2.访问对象中的属性
    // 访问属性
    // 如果访问一个对象上没有定义的属性的时候
    // 那么获取到的属性值就是undefined

    // console.log(info.name)
    // console.log(info.friend.name)
    // info.running()

    // 3.修改对象中的属性
    // info.age = 25
    // info.running = function() {
    //   alert("I am running~")
    // }
    // console.log(info.age)
    // info.running()

    // 4.添加对象中的属性
    info.height = 1.88
    info.studying = function() {
      console.log("I am studying~")
    }
    console.log(info)

    // 5.删除对象中的属性
    // delete关键字(操作符)
    delete info.age
    delete info.height
    console.log(info)

2.1访问对象点语法和中括号语法

方括号在对象中的使用,使我们在定义或者操作属性时更加的灵活

  let address = Symbol('address')
    var message = 'Hello World'
    var obj = {
      // 在对象中,如果key的类型不是字符串或Symbol类型值的时候
      // js会尽可能将key所对应的值转换为字符串类型 如 1 -> '1', true -> 'true'
      name: "why",
      "my friend": "kobe",
      [message]: '你好, 世界',
      // 计算属性: 属性值使用方括号进行包裹
      // 方括号中的值 可以是变量, js表达式 或 普通变量
      [address]: 'shanghai',
      "eating something": function() {
        console.log("eating~")
      }
    }
    // 也可以使用中括号方式去访问对象的属性
    // 此时这个属性名可以任意,可以是变量,合法的js变量对应的字符串,或是其它
    console.log(obj["my friend"]) // 不合法js变量对应的字符串
    console.log(obj[message]) // 普通变量
    console.log(obj[address]) // symbol类型的变量
    console.log(obj.name)
    console.log(obj["name"])// 合法js变量对应的字符串

    // obj["eating something"]()
    var eatKey = "eating something"
    obj[eatKey]()

2.2对象定义案例

    // 定义商品对象
    var product = {
      name: "鞋子",
      desc: "鞋子非常棒!!!",
      price: 99,
      brand: "nike"
    }
    
    // 定义手机对象
    var phone = {
      name: "iPhone 13 Pro Max",
      desc: "对iPhone的描述信息",
      price: 888,
      callPhone: function(phoneNum) {
        console.log("打电话给某人:", phoneNum)
      },
      playGame: function(gameName) {
        console.log("玩游戏:", gameName)
      }
    }

    // 定义用户对象
    var user = {
      id: 1111111,
      account: "coderwjl",
      nickname: "coderwjl",
      password: "xxx123",
      avatarURL: "图片地址",
      role: {
        id: 110,
        name: "管理员",
        createTime: "2033-03-03"
      }
    }

3.遍历

对象的遍历(迭代):表示获取对象中所有的属性和方法 Object.keys() 方法会返回一个由一个给定对象的自身可枚举属性组成的数组;

注意: 以下方式进行遍历只能遍历自身的可枚举属性不可枚举属性和定义在原型中的属性和方法是无法获取的

方法名功能
Object.key()给定对象的自身可枚举属性key组成的数组
Object.values()给定对象的自身可枚举属性value组成的数组
Object.entries()给定对象的自身可枚举属性key和value组成的二维数组

    var info = {
      name: "wjl",
      age: 18,
      height: 1.88
    }

    // console.log(Object.keys(info))    // =>['name', 'age', 'height']
    // console.log(Object.values(info))  // => ['wjl', 18, 1.88]
    // console.log(Object.entries(info)) //=> [ [ 'name', 'wjl' ], [ 'age', 18 ], [ 'height', ' 1.88' ] ]
    
    // 对对象进行遍历
    // 1.普通for循环
    var infoKeys = Object.keys(info)
    for (var i = 0; i < infoKeys.length; i++) {
      var key = infoKeys[i]
      var value = info[key]
      console.log(`key: ${key}, value: ${value}`)
    }

    // 2.for..in..: 遍历对象
    for (var key in info) {
      var value = info[key]
      console.log(`key: ${key}, value: ${value}`)
    }

    // 对象不支持:  for..of..: 默认是不能遍历对象
    // for (var foo of info) {
    // }

4.堆内存和栈内存

程序的运行是需要加载到内存中来执行的,我们可以将内存划分为两个区域:栈内存和堆内存

  • 原始类型占据的空间是在栈内存中分配的
  • 对象类型占据的空间是在堆内存中分配的

Snipaste_2022-11-15_10-42-30.png

Snipaste_2022-11-15_11-01-18.png

5.值类型和引用类型

原始类型的保存方式:在变量中保存的是值本身, 所以原始类型也被称之为值类型

Snipaste_2022-11-15_11-05-38.png

对象类型的保存方式:在变量中保存的是对象的“引用”(指针,地址),所以对象类型也被称之为引用类型

Snipaste_2022-11-15_11-05-59.png

1.在JS中,每定义一个对象,就会在堆中新建一个内存空间来进行存储

const obj1 = {}
const obj2 = {}
console.log(obj1 === obj2) // => false

Snipaste_2022-11-15_11-19-27.png

2.现象二: 引用的赋值

    // 2.现象二: 引用的赋值
    var info = {0
      name: "wjl",
      friend: {
        name: "kobe"
      }
    }

    var friend = info.friend
    friend.name = "james"
    console.log(info.friend.name) // james

Snipaste_2022-11-17_11-02-24.png

3.现象三: 值传递和引用传递的区别

    // 3.现象三: 值传递和引用传递的区别
    function foo(a) {
      a = 200
    }
    var num = 100
    foo(num)
    console.log(num) // 100

Snipaste_2022-11-17_11-10-31.png

4.现象四: 引用传递, 但是在函数中创建了一个新对象, 没有对传入对象进行修改

    // 4.现象四: 引用传递, 但是在函数中创建了一个新对象, 没有对传入对象进行修改
    function foo(a) {
      a = {
        name: "why"
      }
    
    }
    var obj = {
      name: "obj" 
    }
    foo(obj)
    console.log(obj)

Snipaste_2022-11-17_11-16-04.png

5.现象五: 引用传递, 但是对传入的对象进行修改

    // 5.现象五: 引用传递, 但是对传入的对象进行修改
    function foo(a) {
      a.name = "why"
    }

    var obj = {
      name: "obj"
    }
    foo(obj)
    console.log(obj)

Snipaste_2022-11-17_11-37-09.png

6.类和对象的思维方式

在开发中经常需要创建一系列的相似的对象,如果我们一个个都通过字面量方式手动进行创建,必然会十分的麻烦,而且存在大量的重复性代码

    // 一系列的学生对象
    // 重复代码的复用: for/函数
    var stu1 = {
      name: "why",
      age: 18,
      height: 1.88,
      running: function() {
        console.log("running~")
      }
    }
    var stu2 = {
      name: "kobe",
      age: 30,
      height: 1.98,
      running: function() {
        console.log("running~")
      }
    }
    var stu3 = {
      name: "james",
      age: 25,
      height: 2.05,
      running: function() {
        console.log("running~")
      }
    }
    
    // for循环
    // for (var i = 0; i < 3; i++) {
    //   var stu = {
    //     name: "why",
    //     age: 18,
    //     height: 1.88,
    //     running: function() {

    //     }
    //   }
    // }

7 创建对象的方案 – 工厂函数

为了我们可以便于我们批量创建相似对象,我们可以使用工厂函数

我们可以封装一个函数,这个函数用于帮助我们创建一个对象,我们只需要重复调用这个函数即可;

工厂模式其实是一种常见的设计模式;

但工厂函数存在一个比较大的问题,工厂函数内部其实是使用new Obejct()来创建我们对于的实例对象

所以使用工厂函数返回的对象的类型都是Obejct类型

    // 工厂函数(工厂生产student对象) -> 一种设计模式
    // 通过工厂设计模式, 自己来定义了一个这样的函数
    // 工厂函数创建
    function createStudent(name, age, height) {
      var stu = {}
      stu.name = name
      stu.age = age
      stu.height = height
      stu.running = function() {
        console.log("running~")
      }
      return stu
    }
    // stu1 和 stu2  和  stu3的类型是Object
    var stu1 = createStudent("why", 18, 1.88)
    var stu2 = createStudent("kobe", 30, 1.98)
    var stu3 = createStudent("james", 25, 2.05)
    console.log(stu1)
    console.log(stu2)
    console.log(stu3)

但是我们更希望的时候可以细分不同实例对象的类型,例如学生实例都是Student类型的,水果实例都是Fruit类型的

为此JavaScript为我们提供了一种更为简便的方式去批量创建相似对象,那就是构造函数

8构造函数

我们先理解什么是构造函数?

  • 构造函数也称之为构造器(constructor),通常是我们在创建对象时会调用的函数

  • 在其他面向的编程语言里面,构造函数是存在于类中的一个方法,称之为构造方法

  • 但是JavaScript中的构造函数有点不太一样,构造函数扮演了其他语言中类的角色

也就是在JavaScript中,构造函数其实就是其它编程语言中的类

  • 在ES5之前,我们都是通过function来声明一个构造函数(类)的,之后通过new关键字来对其进行调用

  • 在ES6之后,JavaScript可以像别的语言一样,通过class来声明一个类

    // JavaScript已经默认提供给了我们可以更加符合JavaScript思维方式(面向对象的思维方式)的一种创建对象的规则
    // 在函数中的this一般指向某一个对象
    /*
      如果一个函数被new操作符调用
        1.创建出来一个新的空对象
        2.让this指向这个空对象
        3.执行函数体的代码块
        4.如果没有明确的返回一个非空对象, 那么this指向的对象会自动返回
    */
    
    function coder(name, age, height) {
      this.name = name
      this.age = age
      this.height = height
      this.running = function() {
        console.log("running~")
      }
    }

    // 在函数调用的前面加 new 关键字(操作符)
    // stu1 和 stu2 的类型是 coder
    var stu1 = new coder("why", 18, 1.88)
    var stu2 = new coder("kobe", 30, 1.98)
    console.log(stu1, stu2)

9. 类和对象的关系

现实生活中往往是根据一份描述/一个模板来描述一个个实体对象

编程语言也是一样, 也必须先有一份描述, 在这份描述中说明将来创建出来的对象有哪些属性(成员变量)和行为(成员方法)

其实就是将多个对象中共有的属性和方法进行抽取(抽象),形成一个对于相似对象的具体描述信息

而这份描述就被称之为类,所描述的对象一般就被称之为实例对象

比如水果fruits是一类事物的统称,苹果、橘子、葡萄等是具体的对象

比如人person是一类事物的统称,而Jim、Lucy、Lily、李雷、韩梅梅是具体的对象

Snipaste_2022-11-17_15-49-08.png

在JavaScript中的表示形式就是构造函数

  • 构造函数也是一个普通的函数,从表现形式来说,和千千万万个普通的函数没有任何区别
  • 如果这么一个普通的函数被使用new操作符来调用了,那么这个函数就称之为是一个构造函数

如果一个函数被使用new操作符调用了,那么它会执行如下操作:

  • 在内存中创建一个新的对象(空对象)
  • 这个对象内部的[[prototype]]属性(也就是__proto__属性)会被赋值为该构造函数的prototype属性
  • 构造函数内部的this,会指向创建出来的新对象
  • 执行函数的内部代码(函数体代码)
  • 如果构造函数没有返回非空对象,则返回创建出来的新对象
  // 创建一系列的对象
    // 构造函数的名称: 使用大驼峰
    function Person(name, age, height) {
      this.name = name
      this.age = age
      this.height = height
      this.running = function() {
        console.log("running~")
      }
    }
    var p1 = new Person('wjl',18,1.88)
    console.log(p1)
    
    
    // 平时创建普通的对象
    // new Object()
    var obj1 = {}
    var obj2 = new Object()
    var obj3 = new Person()

    // 普通函数: 使用小驼峰
    function sayHello() {

    }

补充

globalThis

 // 浏览器中存在一个全局对象object -> window
    // 作用一: 查找变量时, 最终会找到window头上
    // 作用二: 将一些浏览器全局提供给我们的变量/函数/对象, 放在window对象上面
    // 作用三(了解): 使用var定义的变量会被默认添加到window上面
    console.log(window)

    // 使用var定义变量
    var message = "Hello World"

    function foo() {
      // 自己的作用域
      // abc()
      // alert("Hello World")
      console.log(window.console === console)

      // 创建一个对象
      // var obj = new Object()
      console.log(window.Object === Object)

      // DOM
      console.log(document)

      // window.message
      console.log(window.message)
    }
    foo()

在JavaScript的宿主环境(宿主环境又叫运行环境)中,会提供一个全局对象

在浏览器中,这个对象就是window

在node中,这个对象就是global

在web worker中,这个对象就是self

所以ECMA提供了一个特殊的全局变量就在globalThis

该变量会根据JavaScript的不同宿主环境被映射成对应宿主环境下的全局对象

 全局对象的作用

  1. 在查找变量的时候,如果一直找不到,最终会在globalThis上进行查找

  2. 因为globalThis作为全局对象,可以用来被挂载全局变量,全局函数和全局对象

    例如: 在浏览器中console, alert, document等都会被挂载到全局对象window上

  3. 使用var定义的变量或隐式全局变量默认会被自动挂载到globalThis上 --- ES6开始已经不被推荐

globalThis作为一个对象,在这个对象中有一个特殊的属性叫做globalThis,指向其自身

所以我们可以使用 globalThis.globalThis.glpbalThis.... 进行无限调用

最终的结果都是指向globalThis自身

函数是一个特殊的对象

在JavaScript中,函数是一种特殊的可以执行的对象

所以当我们创建一个函数的时候,对应的函数代码会被存放到堆内存中

在执行的时候,会在栈中开辟对应的函数执行上下文,执行对应的函数


    // 定义原始类型的变量
    var name = "wjl"
    var age = 18

    // 定义对象类型的变量
    // 地址 - 指针 - 引用
    var obj = {} // 堆内存
    var foo = function() {} // 堆内存
    function bar() {} // 堆内存

    console.log(typeof obj) // object
    console.log(typeof foo) // function -> object

    // var stu = new Student() // stu是一个Student -> Person

    // 引申一些别的知识(了解)
    var info = {}
    info.name = "abc"

    function sayHello() {
    }
    sayHello.age = 18
    console.log(sayHello.age)

    function Dog() {
      
    }
    // 构造函数上(类上面)添加的函数, 称之为类方法
    Dog.running = function() {}
    Dog.running()