七十二、JavaScript——面向对象简介
面向对象编程(OOP)
1. 程序是干嘛的
- 程序是现实世界的抽象(照片就是对人的抽象)
2. 对象是干嘛的?
- 一个事物抽象到程序后就变成了对象
- 在程序的试接中,一切皆对象
- 一个事物通常由两个部分组成,数据和功能
- 一个对象由两部分组成:属性和方法
- 静态的就是属性,动态就是方法
- 事物的数据到了对象中,体现为属性
- 事物的功能到了对象中,体现为方法
<script>
const five = {
name: "张三",
sleep(){
console.log(this.name+"在睡觉")
}
}
</script>
七十三、JavaScript——类的简介
使用Object创建对象的问题
1. 无法区分不同类型的对象
2. 不方便批量创建对象
在JS中可以通过类(class)来解决这个问题
1. 类是对象模板,可以将对象中的属性和方法直接定义在类中
定义后,就可以直接通过类来创建对象
2. 通过同一个类创建的对象,我们称之为同类对象,
可以使用onstanceof来检查一个对象是否由某个类创建的
语法:
class 类名 { } 类名要使用大驼峰命名 class Person {}
const 类名 = class { }
// Person类专门用来创建人的对象
class Person {
}
const p1 = new Person()
//打印p1
console.log(p1)
// 利用instanceof检查p1是否由Person创建
console.log(p1 instanceof Person)
七十四、JavaScript——属性
实例属性:
new出来的p1和p2都是实例
访问实例属性 实例.属性名(p1.name)
类属性:
使用static声明的属性,是静态属性(类属性)
类中加了static的属性就是类属性
访问类属性 类.属性名(Person.test)
<script>
/*
类是创建对象的模板,要创建第一件事就是定义类
*/
class Person {
/*
类的代码块,默认就是严格模式
类的代码是用来设计对象的属性的,不是什么代码都能写
*/
name // Person的实例属性name,new出来的p1和p2都是实例
age // Person的实例属性age
static test = "test是静态属性" // 使用static声明的属性,是静态属性(类属性),只能通过 类.属性去访问 例 Person.test
}
const p1 = new Person()
console.log(p1)
const p2 = new Person()
console.log(p2)
</script>
七十五、JavaScript——方法
实例方法:
new出来的p1是实例
访问实例方法 实例.属性方法名(p1.sayHello())
类属性:
使用static声明的方法,是静态方法(类方法)
类中加了static的方法就是类方法
静态方法中,this指向的是当前类
访问类属性 类.方法名(Person.test())
<script>
class Person {
name = "张三";
// 添加方法的一种方式
sayHello = function() {
}
// 添加方法的另一种方式(实例方法)
sayHello() {
// 这里的this指的是实例对象,这里的实例对象是p1,
console.log("大家好,我是" + this.name)
}
// 静态方法,使用static声明的方法,只能通过 类.方法 调用
// 静态方法中,this指向的是当前类
static test() {
console.log("我是静态方法", this)
}
}
const p1 =new Person()
p1.sayHello()
</script>
七十六、JavaScript——构造函数
<script>
// class Person {
// // 当我们在类中直接指定实例属性的值时,意味着我们创建的所有对象的属性都是这个值
// name = "张三"
// age = 12
// gender = "男"
// sayHello() {
// console.log(this.name)
// }
// }
// // 创建一个Person实例
// const p1 = new Person()
class Person{
name
age
gender
// 在类中可以添加一个特殊的方法constructor
// 该方法我们称之为构造函数 (构造方法)
// 构造函数会在我们调用类创建对象时执行,new的时候执行
constructor(name, age, gender){
// 在构造函数中,为实例属性进行赋值
// 在构造函数中,this表示当前所创建的对象
this.name = name
this.age = age
this.gender = gender
}
}
// 直接在创建对象时就进行赋值,因为有构造函数
const p1 = new Person("张三", 18, "男")
const p2 = new Person("李四", 11, "女")
</script>
七十七、JavaScript——封装
面向对象的特点
封装、继承和多态
1.封装
- 对象就是一个用来存储不同属性的容器
- 对象不仅赋值属性,还要 赋值数据安全
- 直接添加到对象中的属性,并不安全,因为他们可以被任意的修改
- 如何确保数据的安全
1. 私有化数据
- 将需要的保护的数据设置为私有,只能在内部使用
- 私有化数据需要先声明再使用
- 实例使用#开头就变成了私有属性,私有属性只能在类内部访问
2. 提供setter和getter方法来开放对数据的操作
- 属性设置私有, 通过getter和setter方法操作属性带来的好处
1. 可以控制属性的读写权限
2. 可以在方法中对属性的值进行验证
- 封装主要用来保证数据的安全
- 实现封装的方式
1. 属性私有化 : 属性名前加#
2. 通过getter和setter方法来操作属性
get 属性名() {
return this.#属性名
}
set 属性名(参数){
this.#属性 = 参数
}
使用gettert和setter推荐使用第二种写法:
class Person{
// 封装属性前线声明
#name
#age
#gender
constructor(name, age, gender){
this.#name = name
this.#age = age
this.#gender = gender
}
// 第一种写法:获取私有属性的方法
getName() {
return this.#name
}
// 第一种写法:修改私有属性
setName(name) {
this.#name = name
}
// 另一种写法(推荐使用)
get age() {
return this.#age
}
// 另一种写法(推荐使用)
set age(age){
this.#age = age
}
}
const p1 = new Person("张三", 19, "男")
// 第一种写法的修改
p1.setName("李四")
// 第一种写法的获取
console.log(p1.getName())
// 第二种写法的修改(推荐使用)
p1.age = 11
// 第二种写法的获取(不需要加括号)(推荐使用)
console.log(p1.age)
</script>
七十八、JavaScript——多态
<script>
class Person {
constructor(name) {
this.name = name
}
}
class Dog{
constructor(name){
this.name = name
}
}
const dog1 = new Dog("旺财")
const p1 = new Person("小李")
/*
定义一个函数,这个函数将接收一个对象作为参数,它可以输出hello并打印对象的name属性
多态
- 在JS种不会检查参数的类型,所以这就意味着任何数据都可以作为参数传递
- 要带调用某个函数,无需指定的类型,只要对象满足某些条件即可
- 多态为我们提供了灵活性
*/
function sayHello(obj){
console.log("Hello" + obj.name)
}
// dog1对象和p1对象的类中都有name的属性,所以可以成功调用
sayHello(dog1)
</script>
七十九、JavaScript——继承
继承
- 可以通过extends关键字来完成继承
- 当一个类继承另一个类时,就相当于将另一个类中的代码复制到了当前类中(简单理解)
- 继承发生时,被继承的类i称为 父类(超类),继承的类称为 子类
<script>
/*
继承
- 可以通过extends关键字来完成继承
- 当一个类继承另一个类时,就相当于将另一个类中的代码复制到了当前类中(简单理解)
- 继承发生时,被继承的类i称为 父类(超类),继承的类称为 子类
*/
class Animal{
constructor(name){
this.name = name
}
sayHello() {
console.log("动物在叫")
}
}
class Dog extends Animal{
}
class Cat extends Animal{
}
const dog = new Dog("旺财")
const cat = new Cat("小喵")
dog.sayHello()
cat.sayHello()
console.log(dog)
</script>
八十、JavaScript——继承
<script>
/*
继承
- 通过继承可以在不修改一个类的情况下对其进行拓展
- OCP原则
- 程序应该对修改关闭,对扩展开放
*/
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
}
// 在方法中调用父类的方法 super.父类方法名
sayHello() {
// 调用父类的sayHello
super.sayHello() // 在方法中可以使用 super来调用父类的方法
console.log("喵喵喵")
}
}
const dog = new Dog("旺财")
const cat = new Cat("小喵", 67)
dog.sayHello()
cat.sayHello()
console.log(dog)
console.log(cat)
</script>
八十一、JavaScript——对象的结构
对象中存储属性的区域实际有两个
1. 对象自身
- 直接通过对象添加的属性,位于对象自身中
- 在类中通过 x = y 的形式添加的属性,位于对象自身中
2. 神秘位置:(prototype)
- 对象中还有一些内容,会存储到其他对象里(原型对象)
- 在对象中会有一个属性用来存储原型对象,这个属性叫__proto__
- 原型对象也负责为对象存储属性
当我们访问对象中的属性时,会优先访问对象自身的属性
对象自身不包含该属性时,才会去原型对象中寻找
- 会添加到原型对象的情况
1. 在类中通过xxx( ){ }方式添加的方法,位于原型中
2. 主动向原型中添加属性和方法,
<script>
/*
对象中存储属性的区域实际有两个
1. 对象自身
- 直接通过对象添加的属性,位于对象自身中
- 在类中通过 x = y 的形式添加的属性,位于对象自身中
2. 神秘位置:(prototype)
- 对象中还有一些内容,会存储到其他对象里(原型对象)
- 在对象中会有一个属性用来存储原型对象,这个属性叫_proto_
- 原型对象也负责为对象存储属性
当我们访问对象中的属性时,会优先访问对象自身的属性
对象自身不包含该属性时,才会去原型对象中寻找
- 会添加到原型对象的情况
1. 在类中通过xxx( ){ }方式添加的方法,位于原型中
2. 主动向原型中添加属性和方法,
*/
class Person{
name = "孙悟空"
// 原型对象的sayHello
sayHello(){
console.log("Hello,我是"+ this.name)
}
}
const p = new Person()
// p对象中的sayHello
p.sayHello = "hello"
// 线访问对象自身,再访问原型对象,最后访问父类
console.log(p.sayHello)
</script>
八十二、JavaScript——原型
访问一个对象的原型对象
1. 对象.__proto__
2. JS内置的访问原型对象: Object.getPrototypeOf(对象名)
原型对象中的数据
1. 对象中的数据(属性,方法等)
2. constructor (对象的构造函数)
注意:
原型对象也有原型,这样就构成了一条原型链,根据对象的复杂程度不同,原型链的长度也不同
p对象的原型链:p对象 -- 原型 -- 原型 -- null
原型链:
- 读取对象属性时,会优先对象自身属性
如果对象中有,则使用,没有就去对象的原型中找
如果原型中有,则使用,没有则去原型的原型中寻找
直到找到Object对象的原型,Object的原型没有原型(为null)
如果依然没有找到,找到obj.__proto__还没有,则返回undefined
- 作用域链,是找变量链
- 原型链,是用来找属性的,找不到会返回undefined
<script>
class Person{
name = "张三"
age = 18
sayHello(){
console.log("我是" + this.name)
}
}
/*
访问一个对象的原型对象
1. 对象.__proto__
2. JS内置的访问原型对象: Object.getPrototypeOf(对象名)
原型对象中的数据
1. 对象中的数据(属性,方法等)
2. constructor (对象的构造函数)
注意:
原型对象也有原型,这样就构成了一条原型链,根据对象的复杂程度不同,原型链的长度也不同
p对象的原型链:p对象 -- 原型 -- 原型 -- null
原型链:
- 读取对象属性时,会优先对象自身属性
如果对象中有,则使用,没有就去对象的原型中找
如果原型中有,则使用,没有则去原型的原型中寻找
直到找到Object对象的原型,Object的原型没有原型(为null)
如果依然没有找到,找到obj.__proto__还没有,则返回undefined
- 作用域链,时找变量链
- 原型链,是用来找属性的,找不到会返回undefined
*/
const p = new Person()
// 访问原型对象
console.log(p.__proto__)
</script>
八十三、JavaScript——原型的作用
所有的同类型对象他们的原型对象都是同一个
也就意味着同类型的原型链是一样的
原型的作用:
原型就相当于是一个公共的区域,可以被所有该类实例访问
这样我们只需要创建一个属性,都可以被所有实例访问
JS中继承就是通过原型来实现的
当继承时,子类的原型就是父类的实例
在对象中,有些值是对象独有的,像属性(name,age,gender) 每个对象都应该有自己的值
但是有些值对于每一个对象来说都是一样的,像各种方法,对于这些一样的值没必要重复的创建
<script>
class Person{
name = "张三"
age = 18
sayHello(){
console.log("我是" + this.name)
}
}
const p = new Person()
const p2 = new Person()
/*
所有的同类型对象他们的原型对象都是同一个
也就意味着同类型的原型链是一样的
原型的作用:
原型就相当于是一个公共的区域,可以被所有该类实例访问
这样我们只需要创建一个属性,都可以被所有实例访问
JS中继承就是通过原型来实现的
当继承时,子类的原型就是父类的实例
在对象中,有些值是对象独有的,像属性(name,age,gender) 每个对象都应该有自己的值
但是有些值对于每一个对象来说都是一样的,像各种方法,对于这些一样的值没必要重复的创建
*/
// 结果为true ,所有的同类型对象他们的原型对象都是同一个
console.log(p.__proto__ === p2.__proto__)
class Animal{
}
class Cat extends Animal{
}
const cat = new Cat()
// 当继承时,子类的原型就是父类的实例
// cat的原型是父类Animal实例, 父类Animal的原型object,object的原型为Object,Object原型为null
console.log(cat)
console.log("cat的原型" + cat.__proto__)
console.log("父类Animal的原型" + cat.__proto__.__proto__)
console.log("object的原型为" + cat.__proto__.__proto__.__proto__)
console.log("Object原型为" + cat.__proto__.__proto__.__proto__.__proto__)
</script>
八十四、JavaScript——修改原型
大部分情况下,我们不需要修改原型对象
注意:
千万不要通过类的实例去修改原型
1. 通过一个实例去影响所有的同类对象,这么做不合适
2. 修改原型得先创建实例,麻烦
3. 危险
除了通过__proto__能访问对象的原型外,
还可以通过类的prototype属性,来访问实例的对象: 类.prototype
修改原型,最好还是通过类去修改
好处:
1. 已修改就是修改所有实例的原型
2. 无需创建实例即可完成对类的修改
原则:
1. 原型尽量不要修改
2. 要改也不要通过实例对象修改
3. 通过 类.prototype 去修改
4. 最好不要直接给prototype去赋值 如:Person.prototype = { }
<script>
/*
大部分情况下,我们不需要修改原型对象
注意:
千万不要通过类的实例去修改原型
1. 通过一个实例去影响所有的同类对象,这么做不合适
2. 修改原型得先创建实例,麻烦
3. 危险
除了通过__proto__能访问对象的原型外,
还可以通过类的prototype属性,来访问实例的对象: 类.prototype
修改原型,最好还是通过类去修改
好处:
1. 已修改就是修改所有实例的原型
2. 无需创建实例即可完成对类的修改
原则:
1. 原型尽量不要修改
2. 要改也不要通过实例对象修改
3. 通过 类.prototype 去修改
4. 最好不要直接给prototype去赋值 如:Person.prototype = { }
*/
class Person{
name = "张三"
age = 18
sayHello(){
console.log("我是" + this.name)
}
}
const p = new Person()
const p2 = new Person()
// 通过对象修改原型,向原型中添加方法,修改后所有的同类实例都能访问该方法
p.__proto__.run = () => {
console.log("我在跑")
}
p.__proto__ = new Dog() // 直接为对象赋值了一个新的原型,不要这么做
console.log(Person.prototype) // 访问Person实例的原型对象
p.run()
p2.run()
</script>
八十五、JavaScript——instanceof和hasOwn
instanceof 用来检查一个对象是否是一个类的实例
- instanceof 检查的是对象的原型链上是否有该类实例
只要原型链上有该类实例,就会返回true
-
dog -> Animal的实例 -> Object实例 -> Object原型
- Object是所有对象的原型,所以任何和对象和Object进行instanceof运算都会返回true
in
- 使用in运算符检查属性时,无论属性在对象自身还是在原型中,都会返回true
对象.hasOwnProperty(属性名)(不推荐使用)
- 用来检查一个对象的自yong身是否含有某个属性
Object.hasOwn(对象,属性名) (推荐使用)
- 用来检查一个对象的自yong身是否含有某个属性
<script>
class Animal{
}
class Dog extends Animal{
}
const dog = new Dog()
/*
instanceof 用来检查一个对象是否是一个类的实例
- instanceof 检查的是对象的原型链上是否有该类实例
只要原型链上有该类实例,就会返回true
-
dog -> Animal的实例 -> Object实例 -> Object原型
- Object是所有对象的原型,所以任何和对象和Object进行instanceof运算都会返回true
*/
// 通过instanceof检查dog是否是Dog类的实例
console.log(dog instanceof Dog) // true
console.log(dog instanceof Animal) // true
console.log(dog instanceof Object) // true
// 读取实例原型的两种方式
const obj = new Object()
//方式一:通过对象访问Object原型
console.log(obj.__proto__)
//方式二:直接通过类访问Object原型
console.log(Object.prototype)
class Person{
name = "张三"
age = 18
sayHello(){
console.log("我是" + this.name)
}
}
const p = new Person()
/*
in
- 使用in运算符检查属性时,无论属性在对象自身还是在原型中,都会返回true
对象.hasOwnProperty(属性名)(不推荐使用)
- 用来检查一个对象的自yong身是否含有某个属性
Object.hasOwn(对象,属性名) (推荐使用)
- 用来检查一个对象的自yong身是否含有某个属性
*/
// 检查 p 对象中是否含有 name 这个属性
console.log("name" in p)
// 检查 p 对象中是否含有 sayHello 这个属性
console.log(p.hasOwnProperty("sayHello"))
</script>
八十七、JavaScript——new运算符
new运算符是创建对象时要使用的运算符
-new的解释 https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Operators/new
- 当使用new去调用一个函数时,这个函数会作为构造函数调用
使用new调用函数时,会发生这些事
1. 创建一个普通的JS对象(Object对象 { }),为了方便,称其为新对象
2. 将构造函数的prototype属性设置为新对象的原型
3. 使用实参来执行构造函数,并且将新对象设置为函数中的this
4. 如果构造函数返回的是一个非原始值,则该值回作为new运算符的返回值返回
如果构造函数的返回值是一个原始值或者没有指定返回值
则新的对象会作为返回值返回
八十八、JavaScript——对象总结
面向对象本质就是,编写代码时所有的操作都是通过对象来进行的
面向对象的编程的步骤:
1. 找对象
2. 搞对象
学习对象:
1. 明确对象代表什么,有什么用
2. 如何获取到这个对象
3. 如何使用这个对象(对象中的属性方法)
对象的分类:
内建对象
- 由ES标准所定义的对象
- 比如:Object Function String Number。。。
宿主对象
- 由浏览器提供的对象
- BOM DOM
自定义对象
- 由开发人员自己创建的对象