06面向对象

面向对象

简介

面向对象编程(OOP)

  1. 程序是干嘛的?

    程序是对现实世界的抽象(列如: 照片就是对人的抽象)

  2. 对象是干嘛的?

    一个事物抽象到程序中后就变成了对象, 在程序中一切皆对象

    一个事物通常有两部分组成: 数据和功能

    一个对象有两部分组成: 属性和方法

面向对象编程:

程序中的所有操作都是通过对象来完成

做任何事之前都要先找到它的对象, 然后通过对象来完成各种操作

使用object创建对象的问题:

  1. 无法区分出不同类型的对象
  2. 不方便批量创建对像

JS中可以通过类(class)来解决这个问题:

  1. 类是对象的模板, 可以将对象中的属性和方法直接定义在类中, 定义后就可以直接通过类来创建对象
  2. 通过同一个类创建的对象, 称为同类对象

可以使用instanceof来检查一个对象是否有某个类创建, 如果某个对象是由某个类创建的, 则称该对象是这个类的实例

//创建类
class Person {
    
}
class Dog {
    
}
//调用构造函数创建对象
const p1 = new Person
const p2 = new Person
const d1 = new Dog
const d2 = new Dog
//使用instanceof检查是否为同类对象
console.log(p1 instanceof Person) //true
console.log(d2 instanceof Person) //false

类的属性

class Person {
    //实例属性
    name = "孙悟空" //Person的实例属性name, 实例属性只能通过实例访问-->p1.name p2.age
    age = 18
    //静态属性
    static test = "test为静态属性"
    static hh = "静态属性" 
    //使用static声明的的属性为静态属性(类属性)只能通过类去访问 Person.test Person.hh
}
const p1 = new Person()
const p2 = new Person()
console.log(p1, p2)

类是创建对象的模块, 要创建的第一件事就是定义类

类的代码块, 默认为严格模式, 类的代码块只能用来设置对象的属性, 不能什么代码都写

类的方法

class Person {
    name = "猪八戒"
    //实例方法
    sayHello() {
        //实例方法, 通过类的实例(p1)调用, this指向当前实例
        console.log("我是猪八戒" + this.name)
    }
    //静态方法
   static test() {
       //静态方法, 通过类调用, this指向当前的类
       console.log("text静态方法")
   }
}
//类的实例赋值给 p1
const p1 = new Person()
p1.sayHello() //类实例调用, 实例方法
Person.test() //类调用, 静态方法

类的方法调用与类的属性调用方式一样,

实例属性(方法)通过类实例来调用

静态属性(方法)直接通过类调用

构造函数

class Person {
    //在类中可以添加一个特殊的方法(constructor), 该方法我们称为构造函数(构造方法), 构造函数会在调用类创建对象时执行
    constructor(name, age, gender) {
        this.name = name
        this.age = age
        this.gender = gender
    }
} 
const p1 = new Person("孙悟空", 18, "男")// cosntructor执行
const p2 = new Person("猪八戒", 28, "男")
const p3 = new Person("白骨精", 38, "女")
console.log(p1)
console.log(p2)
console.log(p3)

构造函数会在我们调用类创建对象时执行

可以在构造函数中, 为实例属性进行赋值, this 指向当前所创建的对象

封装

class Person {
#address = "花果山" //实例使用 # 号开头就变成了私有属性
#name
#age
#gender
      constructor(name, age, gender) {
        this.#name = name
        this.#age = age
        this.#gender = gender
      }
      sayHello() {
        console.log(this.name, this.#address)
      }
      getName() {
        return this.#name = name
      }
      setName(name) {
        this.#name = name
      }
    // 控制数据的 读 权限
      //getAge() {
        //return this.#age = age
      //}
      setAge(age) {
          //对属性的值进行验证
        if(isNaN(age) && age >= 0) {
          this.#age = age
        }
      }
      //简便方式
      get gender() {
        console.log("getter(gender)执行了")
        return this.#gender
      }
      set gender(gender) {
        console.log("setter(gender)执行了")
        this.#gender = gender
      }
    }
const p1 = new Person("孙悟空", 18 ,"男")
p1.setAge(18)
p1.setName("猪八戒")
p1.gender = "女" //实际上在调用set方法
console.log(p1)
console.log(p1.gender) //p1.gender实际上在调用get方法

封装:
(装) 对象就是一个用来存储不同属性的容器
对象不仅负责属性的存储, 还要负责属性的安全
(封) 保证数据的安全:
- 私有化数据: 将需要保护的数据设置为私有, 只能在类的内部使用
- 提供 setter 和 getter 方法来开放对数据的操作
- 属性设置私有, 通过getter 和 setter方法操作属性的优点
- 控制数据的 读, 写 权限
- 可以在方法中对属性的值进行验证

多态


class Person {
  constructor(name) {
    this.name = name
  }
}
class Dog {
  constructor(name) {
    this.name = name
  }
}
class text {
  static name = '李四'
}
const dog = new Dog('旺财')
const person = new Person('张三')
console.log(dog, person)
//多态
function sayHello(obj) {
  console.log('hello' + obj.name)
}
sayHello(text)//静态属性也能访问

多态:

  • 在 js 中不会检查参数的类型, 因此任何数据都可以作为参数传递
  • 要调用某个函数, 无需指定类型, 只要对象满足某些条件即可
  • 多态为我们提供了灵活性

继承

class Animal {
  constructor(name) {
  this.name = name
  }
  sayHello() {
    console.log("动物再叫")
  }
}
//继承Animal
class Dog extends Animal {

}
class Cat extends Animal {

}
const dog = new Dog("旺财")
const cat = new Cat("汤姆")
dog.sayHello()
console.log(dog)
cat.sayHello()
console.log(cat)

继承:
-通过extends关键字可以完成继承
-顶一个类继承另一个类时, 相当于把被继承的类, 的代码复制到当前类(继承类)中
-继承发生时, 被继承的称为父类, 继承的类称为子类
-通过继承可以减少重复的代码, 并且可以在不修改一个类的前提下 对其进行改扩展
封装 --> 安全性
继承 --> 扩展性
多态 --> 灵活性

class Animal {
  constructor(name) {
    this.name = name
  }
  sayHello() {
    console.log("动物再叫")
  }
}
class Dog extends Animal {
//  在子类中, 可以通过创建同名方法来重写父类的方法
  sayHello() {
    console.log("汪汪汪")
  }
}
class Cat extends Animal {
//  重写构造函数
  constructor(name, age) {
    //重写父类构造函数时, 构造函数的第一行代码必须为 super()
  super(name) //调用父类的构造函数
    this.age = age
  }
sayHello() {
    //调用父类的sayHello()方法
  super.sayHello()
  console.log("喵喵喵")
}
}
const dog = new Dog("旺财")
const cat = new Cat("汤姆", 18)
dog.sayHello()
console.log(dog)
cat.sayHello()
console.log(cat)

通过继承可以在不修改一个类的情况下 对其进行扩展, 使用super关键字可以调用父类的方法

OCP原则:

程序应该对修改关闭, 对扩展开放

对象的结构

class Person {
  age = 18 //对象自身
  constructor(name) {
    this.name  = "张三" //对象自身
    this.gender = "男" //对象自身
  }
  //对象自身
  sayHello = () => {
    console.log("hello, 我是" + this.name)
  }
  // //原型对象
  // sayHello(name) {
  //   console.log("hello, 我是" + this.name)
  // }
}
const p = new Person()
p.address = "花果山" // 对象自身
p.sayHello = "hello" // 对象自身(原型对象中也有,但是优先访问对象自身)
console.log(p)
console.log(p.sayHello)

对象在内存中的存储位置

1.对象自身(优先级高, 优先访问)
直接通过对象所添加的属性, 位于对象自身中
在类中通过 属性名 = 属性值(函数, 对象)
2.原型对象(prototype)
-对象中还有一些内容, 会存储到其他的对象里(原型对象)
-在对象中会有一个属性来存储原型对象并,链接对象自身, 这个属性叫做 __proto__
-原型对象也负责对象的存储属性
-当我们访问对象中的属性时, 会优先访问自身的属性, 当该对象自身不包含该属性时, 则会去原型对象中寻找
-添加到原型对象的情况
-在类中通过xxx() {} 方法添加的方法, 为于原型中
-主动向原型中添加属性或者方法

原型链

class Person {
  name = "孙悟空"
  age = 18
  sayHello() {
    console.log("我是" + this.name)
  }
}
const p = new Person()
console.log(p)
//访问一个对象的原型
console.log(p.__proto__)
console.log(Object.getPrototypeOf(p))
console.log(p.constructor)
console.log(p.__proto__.__proto__.__proto__) //null

访问对象中的原型对象:
-对象.proto
-Object.getPrototypeOf(对象)
原型对象中的数据:
-对象中的数据(属性, 方法, 等)
-constructor (对象的构造函数)
注意:
-原型对象也有对象, 以此类推就形成了一条原型链, 根据对象的复杂程度不同, 原型链的长度也会不同

-原型链:
-读取对象属性时, 会优先使用对象自身属性, 如果对象自身没有则去原型对象中去寻找, 直到找不到返回null, 如果对象原型链中没有这个属性则返回 undefined

-作用域链:
-是找变量, 找不到会报错

-原型链:
-找属性, 找不到返回undefined

原型链的作用

class Person {
  name = "张三"
  age = 18
  sayHello() {
    console.log("我是" + this.name)
  }
}
const p = new Person()
const p2 = new Person()
//所有的同类型对象, 他们的原型对象都是同一个
console.log(p === p2) //false
console.log(p.__proto__ === p2.__proto__) 
//true, 虽然是两个不同的对象, 但他们都是通过Person()创建的, 因此他们的原型对象一样
console.log(p.sayHello) //原型对象
p.sayHello  = "hello" // 添加与原型对象不同的方法
console.log(p.sayHello)//自身对象
console.log(p2.sayHello)//原型对象
class Animal {

}
class Cat extends Animal {

}
class Tom extends Cat {

}

原形型的作用:
-原型相当于一个公共区域, 可以被所有这个类的实例 访问
-可以将一个类的实例中, 存放所有公共属性(方法)进行统一存储
-这样只需要创建一个属性, 即可被所有实例访问
在对象中有些值时对象独有的, 如属性(name, age, gender)每个对象都应该有自己的值
但有些值对于每个对象来说都是一样的, 象各种方法, 对于一样的值没必要重复创建
继承就是通过原型链来实现的, 当继承时, 子类的原型就是一个父类的实例

修改原型

 class Person {
   name = "张三"
   age = 48
   sayHello(name) {
     console.log("我是" + this.name)
   }
 }
 class Dog {
 }
 const p = new Person()
 const p2 = new Person()
 //不建议使用
 // p.__proto__.run = () => {
 //   console.log("跑")
 // }
 // console.log(p.run(), p2.run())
 // //更不建议使用
 // // p.__proto__ = new Dog()
 // // console.log(p, p2)
 Person.prototype.fly = () => {
   console.log("飞")
 }
 // console.log(p, p2)
 Person.prototype = {}
 console.log(p.fly, p2)

一般情况下不需要修改原型
-注意:
-千万不要通过类的实例去修改原型
1.通过一个对象影响所有同类对象, 不合适
2.修改原型要先创建实例, 麻烦
3.危险
可以通过类的prototype属性, 来访问对象的原型, 也可以通过它来修改原型
-修改会对所有实例产生影响
-无需创建实例即可对类进行操作
-不建议直接给prototype直接赋值

instanceof, hasOwn

instanceof :

class Animal {}
class Dog extends Animal {}
const dog = new Dog()
console.log(dog.__proto__.__proto__.__proto__)
console.log(dog instanceof Dog) //true
console.log(dog instanceof Animal) //true
console.log(dog instanceof Object) //true

instanceof会检查一个对象是否是一个类的实例
-instanceof检查的是对象的原型链上是否有该类实例, 只要原型链上有就会返回true
-Object是所有对象的原型, 所以任何对象和Object进行instanceof运算都会返回true

hasOwn :

const obj = new Object()
console.log(obj.__proto__)
console.log(Object.prototype)

class Person {
  name = "张三"
  age = 18
  sayHello() {
    console.log("我是", this.name)
  }
}
const p = new Person()
//"属性名" in 对象
//  -使用in运算符检查属性时, 无论属性在原型中还是在对象自身中, 都会返回true, 除非没有这个属性
console.log("sayHello" in p) //true
//对象.hasOwnProperty("属性名"),(不推荐使用)
//  -用来检查一个属性是否在对象自身中
console.log(p.hasOwnProperty("sayHello")) //false
//Object.hasOwn(对象, "属性名")
console.log(Object.hasOwn(p, "sayHello")) //false

旧类

var Person = (function() {
  function  Person(name, age) {
    //在构造函数中, this就表示新建的对象
    this.name = "张三"
    this.age = 18
    //旧的类添加方法, 会直接添加到对象本身
    // this.sayHello = function() {
    //   console.log(this.name)
    // }
  }
//向函数中添加属性(方法)
  Person.prototype.sayHello = function() {
    console.log(this.name)
  }
//添加静态属性
  Person.staticProperty = "jjjj"
//添加静态方法
  Person.staticMethod = function(){
    console.log("张三")
  }
  return Person
})()
const p = new Person()
console.log(p)
console.log(Person)

var Cat = (function() {
  function Cat() {
  }
  //  继承Person
    Cat.prototype = new Person()
  return Cat
})()
var cat = new Cat()
console.log(cat)

在早期的 js 中, 直接通过函数在定义类

  • 一个函数如果直接调用: xxx(), 那么就是一个普通的函数
  • 一个函数如果通过new调用 new xxx() 那么这个函数就是一个构造函数

new运算符

class Person {
  constructor(name) {
    // 3.使用实参来执行构造函数, 并且将新对象设置为函数中的this
    this.naem = naem
    //2.将构造函数的prototype属性设置为新对象的原型
    let newInstance = {}
    newInstance.__proto__ = Person.prototype
    //4.如果构造函数返回的是一个非原始值, 则该值会作为new运算符的返回值返回(千万不要这么做)
    //一旦指定了非原始值, 就会返回这个值, 就不会把新对象作为返回值返回
    return {name: "12"} //非原始值 永远会返回 {name: "12"}
    return 1 //原始值(五大基本数据类型: Undefined,Null,Boolean,Number,String)--原始值不能更改, 会返回新对象Person
  }

}
//1.创建一个普通的js对象(Object 对象 {}), 为了方便, 称为新对象
let p = new Person()
console.log(p)

当时用new去调用一个函数时, 这个函数将会作为构造函数调用

  1. 创建一个普通的js对象(Object 对象 {}), 为了方便, 称为新对象
  2. 将构造函数的prototype属性设置为新对象的原型
  3. 使用实参来执行构造函数, 并且将新对象设置为函数中的this
  4. 如果构造函数返回的是一个非原始值, 则该值会作为new运算符的返回值返回(千万不要这么做)
    如果构造函数的返回值是一个原始值, 或者不指定返回值, 则新对象会作为返回值返回, 通常不指定返回值

面向对象总结

面向对象, 即所有的操作都是通过对象来进行的

面向对象的编程步骤:

  1. 找对象
  2. 搞对象

学习对象:

  1. 明确这个对象代表这什么(如, window代表浏览器窗口对象, document代表文档对象), 与对象的具体作用, 操作这个对象能干些什么?
  2. 如何使用这个对象(对象中的属性和方法), 具体的操作步骤

对象的分类:

  1. 内建对象:
    • 有Es标准所定义的对象
    • 列如: String, Number, Boolean, Function, Object
  2. 宿主对象:
    • 由浏览器提供的对象
    • BOM, DOM
  3. 自定义对象:
    • 有开发人员自己定义
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值