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

JS学习笔记(七)

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


文章目录

  • JS学习笔记(七)
  • 一、类
    • 1. 使用class创建类对象(ES6)
    • 2. 属性
      • (1)实例属性
      • (2)类 / 静态属性(static)
      • (3)私有属性(#)
    • 3. 方法
      • (1)实例方法
      • (2)类 / 静态方法
      • (3)私有方法
      • (4)get、set方法(ES)
      • (5)构造函数 `constructor(){}`
    • 4. 使用function创建类对象(旧语法)
  • 二、面向对象编程
    • 1. 封装 —— 安全性
    • 2. 多态 —— 灵活性
    • 3. 继承 —— 扩展性
    • 4. 对象的分类
  • 三、原型
    • 1. 原型对象(prototype)
      • (1)原型对象简介
      • (2)访问原型对象
      • (3)原型对象的结构
      • (4)原型链
    • 2. 原型的作用
    • 3. 修改原型
    • 4. instanceof 运算符
    • 5. in运算符 、hasOwnProperty()方法、hasOwn()方法
    • 6. getOwnPropertyNames()和keys()方法
  • 结尾


一、类

引用类型不是类!!!

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 = "加里敦";
}

(1)实例属性

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

输出类实例时,可以直接看到的属性,即上述Person类中的nameage属性

console.log(new Person()); // Person {name: undefined, age: '18'}

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

  • (1)使用static关键字添加的属性
  • (2)只能通过类直接使用的属性(类名.属性名)
  • (3)可以被继承
console.log(new Person()); // Person {name: undefined, age: '18'}
console.log(Person.school); // 加里敦

// 定义一个类继承Person,需要使用extends,并在构造函数中使用super()
class Student extends Person {
	constructor() {
		super();
	}
	studentId = "0000001";
}
console.log(new Student()); //Student {name: undefined, age: '18', studentId: '0000001'}
console.log(Student.school); // 加里敦
// 使用Object.hasOwnProperty()可以判断某个属性是否为对象本身所拥有,不会沿着其原型链去查找
// 因此使用此方法,可以判断某个属性是否是继承的属性,还是自身拥有的属性
console.log(Student.hasOwnProperty("school")); // false
console.log(Person.hasOwnProperty("school")); // true

(3)私有属性(#)

  • (1)使用 # 修饰的属性
  • (2)可以先定义,后赋值;也可以定义的同时,进行赋值
  • (3)类实例使用getter方法来获取私有变量的值,使用setter方法来修改私有变量的值
  • (4)不借助getter和setter方法,仅支持在类内部进行访问

注:ES6新增的get set关键字替代本小节书写的getter和setter方法,具体使用方法见下一节 “方法” 中的内容

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大'}

3. 方法

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

(1)实例方法

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

(2)类 / 静态方法

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

(3)私有方法

  • (1)通过在 实例方法 或者 静态方法 前面添加 # ,来设置为私有方法
  • (2)只能在类内访问的方法
class Test {
// 实例方法,通过函数表达式方式创建的方法
	sayHello = function () {
		alert("你好");
	};
	// 类 / 静态方法,使用函数名(){}创建的函数,可以省略static不写
	static sayGoodbye() {
		alert("再见");
	}
}
console.log(new Test()); // Test {sayHello: ƒ}

(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); // Test {name: '李四', age: 19}
console.log(t2); // Test {name: '张三', age: 18}

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 对象。当网页加载时,浏览器就会自动创建当前页面的文档对象模型
    JS学习笔记(七)class类、对象的方法、面向对象(封装、多态、继承)、原型_第1张图片

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


三、原型

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

1. 原型对象(prototype)

(1)原型对象简介

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

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

	static sayBye() {
		console.log("byebye!");
	}

	#sayYes() {
		console.log("yes!");
	}
}
console.log(new Person());

私有方法定义在类对象中,静态方法无法通过实例对象查看,普通方法会定义在类原型对象上。
JS学习笔记(七)class类、对象的方法、面向对象(封装、多态、继承)、原型_第2张图片

(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")); // true
console.log(person.hasOwnProperty("sex")); // false
// hasOwn()方法
console.log(Object.hasOwn(Person, "sex")); // true
console.log(Object.hasOwn(person, "sex")); // false

6. getOwnPropertyNames()和keys()方法

Object.getOwnPropertyNames(对象名): 获取对象自身拥有的所有属性名(包括不可枚举属性,但不包括Symbol类型属性),并返回一个数组。
Object.keys(对象名): 获取对象自身拥有的可枚举属性,并返回一个数组。

// Object.getOwnPropertyNames(对象)
console.log(Object.getOwnPropertyNames(Person)); // ['length', 'name', 'prototype', 'sex']
console.log(Object.getOwnPropertyNames(new Person())); // ['name']
// Object.keys(对象)
console.log(Object.keys(Person)); // ['sex']
console.log(Object.keys(new Person())); // ['name']
  • 可枚举属性: 属性的 enumerable: true
  • 不可枚举属性: 属性的 enumerable: false
    • 包括使用 Object.defineProperty(对象,属性名,{value:xx;...}) 添加的属性(此方法可以通过修改enumerable改变属性是否可枚举)
    • 包括基本包装类型 BooleanNumberString(特殊的引用数据类型)的 原型属性

注:for … in …遍历的也是可枚举属性


结尾

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

你可能感兴趣的:(JavaScript,javascript,学习,前端)