JS学习笔记(七)class类、对象的方法、面向对象(封装、多态、继承)、原型

发布于:2023-01-16 ⋅ 阅读:(461) ⋅ 点赞:(0)

JS学习笔记(七)

本系列更多文章,可以查看专栏 JS学习笔记



一、类

引用类型不是类!!!

1. 使用class创建类对象(ES6)

类定义的代码块,默认开启严格模式,但是此语句可以省略

使用 class 创建类对象是一种 语法糖 ,指计算机语言中添加的新语法,但对语言的功能没影响。同时,将在下文介绍旧的类创建方法。

class Person {
	"use strict" // 可以省略
	属性...
	方法...
}
const p = new Person(); // 最好使用const声明,必须使用new来创建class类对象

用class创建类,相当于创建了一个类模板,后续可以使用此方式创建多个此类的实例化对象

const p1 = new Person(); 
const p2 = new Person(); 
		...

2. 属性

类中的属性可以只声明,不赋初值

// Person类相当于一个类模板,可以使用此类创建多个Person类实例
class Person {
	// 添加实例属性,可以直接用实例对象访问此属性
	name;
	age = "18";
	// 添加类 / 静态属性,只能使用类直接访问此属性
	static school = "加里敦";
}
// 实例化Person类对象
const p = new Person();
// 获取实例属性
console.log(p.name);
// 获取类 / 静态属性
console.log(Person.school);

(1)实例属性

  • (1)没有使用static关键字添加的属性
  • (2)通过实例化的类对象访问的属性(实例对象.属性名)

输出类实例时,可以直接看到的属性

console.log(p);

在这里插入图片描述
看不到 school 属性,只能看到实例属性


(2)类 / 静态属性(static)

  • (1)使用static关键字添加的属性
  • (2)只能通过类直接使用的属性(类名.属性名)
console.log(Person);

在这里插入图片描述
可以在类中,看到 school 属性


(3)私有属性(#)

class Student {
	#school = "加里敦";
	// getter方法,获取私有变量的值
	getSchool() {
		return this.#school;
	}
	// setter方法,修改私有变量的值
	setSchool(school) {
		this.#school = school;
	}
}
const stu = new Student();

// 正确获取私有变量值的方法:使用类内部的方法,访问私有变量
console.log(stu.getSchool());   // 加里敦
// 错误获取私有变量值的方法
console.log(stu.school);    // undefined

// 正确修改私有变量值的方法:使用类内部的方法,访问私有变量
stu.setSchool("T大");
console.log(stu);   // Student {#school: 'T大'}
// 错误修改私有变量值的方法,
stu.school = "P大";
// 私有属性可以通过实例看到,但是不可以在类外部直接访问
console.log(stu);  // Student {school: 'P大', #school: 'T大'}

代码效果如下图所示:

在这里插入图片描述

  • (1)使用#添加的属性
  • (2)只能在类内部访问
  • (3)常使用getter、setter方式获取或修改私有变量的值

ES6新增的get set方法,见下一部分 “方法” 中的内容


3. 方法

方法同样也有实例方法、类方法和私有方法,与上面的属性方式类似

class Test {
	// 实例方法,通过函数表达式方式创建的方法
	sayHello = function () {
		alert("你好");
	};
	// 类 / 静态方法,使用函数名(){}创建的函数,可以省略static不写
	static sayGoodbye() {
		alert("再见");
	}
}
const t = new Test();
console.log(t);
console.log(Test);

(1)实例方法

  • (1)通过函数表达式方式创建的方法
  • (2)可以使用实例对象调用的方法

(2)类 / 静态方法

  • (1)通过 static 函数名(){} 创建的方法,static可以省略
  • (2)只能通过类来调用的方法

(3)私有方法

  • (1)通过在 实例方法 或者 静态方法 前面添加 # ,来设置为私有方法
  • (2)只能在类内访问的方法

(4)get、set方法(ES)

直接使用 get 属性名(){}set 属性名(){}方法替代自行添加的类似 setName(){}getName() 方法,可以在后续使用时使用 . 直接调用属性,而不用通过调用函数的方式间接操作属性

class Test {
	#name;
	set name(n) {
		this.#name = n;
	}

	get name() {
		return this.#name;
	}
}

const t = new Test();
// 优点:简化了后续操作属性的方式
t.name = "李四";
console.log(t.name);

(5)构造函数 constructor(){}

一个类只能有一个构造函数!!!

构造函数在类对象被创建时执行可以有参数。也可以无参数,通常不会设置返回值

  • 若无返回值,默认会返回一个新创建的对象
  • 若有返回值,则此构造函数实际功能会失效
class Test {
	name;
	age;
	
	constructor(name, age) {
		this.name = name;
		this.age = age;
	}
}
// 普通方式为属性赋值
const t1 = new Test();
t1.name = "李四";
t1.age = 19;
// 实例化对象的同时赋值
const t2 = new Test("张三", 18);
console.log(t1);
console.log(t2);

代码效果如下图所示:

在这里插入图片描述


4. 使用function创建类对象(旧语法)

不使用new创建对象,创建的则为普通函数,使用new会自动调用构造函数【需要先了解原型】

// 将旧语法创建类对象的代码,可以放入立即执行函数中
var 类名 = (function(){
	function 类名() {
		属性...
		方法...
	}
	// 不使用new,创建的则为普通函数,使用new会自动调用构造函数
	const 变量名 = new 类名()
	// 向类的原型中添加属性(方法)
	类名.prototype.属性名 = 属性值 或 方法
	// 添加静态属性
	类名.staticProperty = 属性值
	// 添加静态方法
	类名.staticMethod = 方法
	return 类名
})()

如果需要继承,可以参照以下模板,进行编写

var 父类 = (function(){
	// 相当于父类的构造函数
	function 父类([参数]){	
	}
	属性...
	方法...
	return 父类
})()

var 子类 = (function(){
	// 相当于子类的构造函数
	function 子类([参数]){	
	}
	属性...
	方法...
	// 设置子类继承父类
	子类.prototype =  new 父类()
	return 子类
})()

二、面向对象编程

面向对象语言的三大特征:封装、多态、继承
注:虽然从技术上讲,JavaScript是一门面向对象的语言;但它缺少面向对象编程语言所具备的某些基本结构,包括类和接口;
虽然ES6中引入了class关键字,使JavaScript具有正式定义类的能力,但是背后仍然是原型和好构造函数的概念,并没有实现继承和多态,所以JavaScript不是面向对象的语言!!!

1. 封装 —— 安全性

封装:将一个整体的多种属性、方法组合成一个类,后续便于用其它方式增加安全性。

  • (1)设置私有属性、私有方法,提高安全性
  • (2)使用getter、setter方式操作对象的值,提高安全性

以下多态、继承的特点,只是JavaScript看起来支持正式的面向对象的编程,但本质上和真正的面向对象的语言不一样!!!

2. 多态 —— 灵活性

  • (1)JS中不会检查参数的类型,传递实参时没有类型限制
  • (2)调用函数时,无需指定数据类型
// Person类
class Person {
	constructor(name) {
		this.name = name;
	}
}
// Animals类
class Animals {
	constructor(name) {
		this.name = name;
	}
}
// 函数声明
function sayHello(obj) {
	console.log("我叫", obj.name);
}
const p = new Person("王五");
const a = new Animals("修勾");
// 以下就是多态,不会检查参数的类型
sayHello(p);
sayHello(a);

代码效果如下图所示:
在这里插入图片描述

3. 继承 —— 扩展性

通过 extends 来实现一个类对另一个类的继承

  • (1)继承别的类的类叫子类,被继承的类叫父类(超类)
  • (2)提高代码复用率,减少代码量
  • (3)子类会继承父类的属性和方法
// 父类
class Animals {
	name;
	sayHello() {
		console.log("我是动物");
	}
}
// 以下为子类
class Cat extends Animals {}
class Pig extends Animals {}
// 创建子类对象
const cat = new Cat();
const pig = new Pig();
cat.sayHello(); // 我是动物
pig.sayHello(); // 我是动物

重写继承的父类的普通方法和构造函数

  • (1)重写父类普通方法: 直接在子类中,重新定义一个同名方法,重写后,会覆盖掉父类的同名方法
  • (2)重写父类构造函数: 直接在子类中,重新写一个构造函数,但是第一行必须为super()
class Animals {
	name;
	constructor(name) {
		this.name = name;
	}
	sayHello() {
		console.log("我是动物");
	}
}
// 以下为子类
class Cat extends Animals {
	// 重写父类构造函数
	constructor(name, age) {
		super();
		this.age = age;
	}
	// 重写父类普通方法
	sayHello() {
		console.log("我是猫猫");
	}
}
class Pig extends Animals {}
// 创建子类对象
const cat = new Cat("猫猫", 2);
cat.sayHello();

4. 对象的分类

部分内容参考JS DOM(文档对象类型)
(1)内建对象: ES标准定义的对象(原始数据类型和引用数据类型)
(2)宿主对象: 浏览器提供的对象(BOM、DOM)

  • BOM(浏览器对象模型): BOM 的核心为 window 对象
  • DOM(文档对象模型): Document 对象是DOM树中所有节点的根节点,document 对象也属于 window 对象。当网页加载时,浏览器就会自动创建当前页面的文档对象模型
    在这里插入图片描述

(3)自定义对象: 由开发人员创建的对象


三、原型

更多内容,可以参考此篇文章学习《JavaScript深入之从原型到原型链》

1. 原型对象(prototype)

(1)原型对象简介

向类中添加的 静态属性、静态方法 ,无法通过查看实例对象,直接观察到——是因为,每个创建的类都有一个私有属性 __proto__,用于存储原型对象

class Person {
	static name = "修勾";
	age = 18;
	sayHello() {
		console.log("我叫", this.name);
	}
}
const person = new Person();
console.log(person);

代码效果如下图所示:

在这里插入图片描述
绿色框的内容都存储在Person类的原型对象中

(2)访问原型对象

方式1: 对象.__proto__
方式2:Object.getPrototypeOf(类对象)

(3)原型对象的结构

  • 对象中的数据(部分属性和方法)
  • 对象的默认构造函数(自己未重新定义时)

(4)原型链

所有类的原型链:类对象 --> 原型 -->原型的原型 --> … --> Object --> null(原型链的长度不一定,去绝对类的复杂程度)

原型对象也有原型对象,不断嵌套直到Object对象的原型对象为null

类似于作用域链调用类中变量时,沿作用域链向上寻找调用的变量; 调用类中方法时,沿作用域链向上寻找调用的方法

2. 原型的作用

  • (1)可以被类所有实例访问的区域,可以将公共属性(方法)存储到原型中
  • (2)JS中的继承通过原型对象来实现,将子类的原型设置为父类的实例,例如 子类.prototype = new 父类()
  • (3)将公共属性(方法)存储在原型中,可以节约内存【实例属性(方法),每创建一个实例,需要单独存储】

3. 修改原型

切记:

  • (1)不要通过实例修改原型!!【使用 实例.__proto__ 修改后,所有实例均会得到一个修改后的原型】
  • (2)不要为实例创建一个新原型!!【会造成该实例具有特殊性】
  • (3)推荐修改原型的方式: 类.prototype = xxx 【好处:无需创建实例,一次修改所有实例原型均会修改】

4. instanceof 运算符

(1)作用:用于检查一个对象是否为某个类的实例
(2)原理:通过查看某一个对象的原型链,如果这个类在这个对象的原型链中,则 instanceof 的结果返回 true;否则,返回 false
(3)用法:实例对象 instanceof 类
(4)所有实例对象 instanceof Object 的结果,均为true

class Person {}
const person = new Person();
console.log(person instanceof Person);	// true
console.log(person instanceof Object);	// true

5. in运算符 、hasOwnProperty()方法、hasOwn()方法

共同点: 均可以用来检查一个属性是否存在于某个对象中
区别点:
(1)in: 检查某个属性时,无论属性是存在于对象自身,还是原型对象中,均会返回 true

"属性名" in 对象	// "name" in Person(可以为实例,也可以其他类对象,下同)

(2)hasOwnProperty: 检查某个属性时,若存在于对象自身中,会返回 true(不推荐使用)

对象.hasOwnProperty("属性名")	// Person.hasOwnProperty("name")

(3)hasOwn(ES6): 检查某个属性时,若存在于指定对象中,会返回 true

Object.hasOwn(对象,"属性名")	// Object.hasOwn(Person, "sex")

```javascript
class Person {
	name;
	static sex;
}
const person = new Person();
// in 运算符
console.log("name" in person);	// true
console.log(name in person);	// false,注意双引号
// hasOwnProperty()方法
console.log(Person.hasOwnProperty("sex"));	// Person类中有静态属性
console.log(person.hasOwnProperty("sex"));		// Person类中有静态属性
// hasOwn()方法
console.log(Object.hasOwn(Person, "sex"));	// Person类中有静态属性
console.log(Object.hasOwn(person, "sex"));	// Person类中有静态属性

代码效果如下图所示:

在这里插入图片描述
可以使用 Object.hasOwnPropertyNames(对象) 来获取对象自身拥有的所有属性名,并返回一个数组。


结尾

部分内容参考《ECMAScript 6 入门》《JavaScript权威指南》《JavaScript高级程序设计》,如有错误,欢迎评论区指正。