本系列更多文章,可以查看专栏 JS学习笔记
引用类型不是类!!!
类定义的代码块,默认开启严格模式,但是此语句可以省略
使用 class
创建类对象是一种 语法糖
,指计算机语言中添加的新语法,但对语言的功能没影响。同时,将在下文介绍旧的类创建方法。
class Person {
"use strict" // 可以省略
属性...
方法...
}
const p = new Person(); // 最好使用const声明,必须使用new来创建class类对象
用class创建类,相当于创建了一个类模板,后续可以使用此方式创建多个此类的实例化对象
const p1 = new Person();
const p2 = new Person();
...
类中的属性可以只声明,不赋初值
// Person类相当于一个类模板,可以使用此类创建多个Person类实例
class Person {
name;
age = "18";
static school = "加里敦";
}
- (1)没有使用static关键字添加的属性
- (2)通过实例化的类对象访问的属性(实例对象.属性名)
输出类实例时,可以直接看到的属性,即上述Person
类中的name
、age
属性
console.log(new Person()); // Person {name: undefined, age: '18'}
- (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
- (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大'}
方法同样也有实例方法、类方法和私有方法,与上面的属性方式类似
- (1)通过函数表达式方式创建的方法
- (2)可以使用实例对象调用的方法
- (1)通过
static 函数名(){}
创建的方法,static可以省略- (2)只能通过类来调用的方法
- (3)可以被继承
- (1)通过在
实例方法
或者静态方法
前面添加#
,来设置为私有方法- (2)只能在类内访问的方法
class Test {
// 实例方法,通过函数表达式方式创建的方法
sayHello = function () {
alert("你好");
};
// 类 / 静态方法,使用函数名(){}创建的函数,可以省略static不写
static sayGoodbye() {
alert("再见");
}
}
console.log(new Test()); // Test {sayHello: ƒ}
直接使用 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);
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}
不使用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)设置私有属性、私有方法,提高安全性
- (2)使用getter、setter方式操作对象的值,提高安全性
以下多态、继承的特点,只是JavaScript看起来支持正式的面向对象的编程,但本质上和真正的面向对象的语言不一样!!!
- (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); // 我叫 修勾
通过 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();
部分内容参考JS DOM(文档对象类型)
(1)内建对象: ES标准定义的对象(原始数据类型和引用数据类型)
(2)宿主对象: 浏览器提供的对象(BOM、DOM)
BOM
的核心为 window
对象Document
对象是DOM
树中所有节点的根节点,document
对象也属于 window
对象。当网页加载时,浏览器就会自动创建当前页面的文档对象模型(3)自定义对象: 由开发人员创建的对象
更多内容,可以参考此篇文章学习《JavaScript深入之从原型到原型链》
向类中添加的 静态属性、静态方法 ,无法通过查看实例对象,直接观察到——是因为,每个创建的类都有一个私有属性 __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());
私有方法定义在类对象中,静态方法无法通过实例对象查看,普通方法会定义在类原型对象上。
方式1: 对象.__proto__
方式2:Object.getPrototypeOf(类对象)
- 对象中的数据(部分属性和方法)
- 对象的默认构造函数(自己未重新定义时)
所有类的原型链:类对象 --> 原型 -->原型的原型 --> … --> Object --> null(原型链的长度不一定,去绝对类的复杂程度)
原型对象也有原型对象,不断嵌套直到Object对象的原型对象为null
类似于作用域链,调用类中变量时,沿作用域链向上寻找调用的变量; 调用类中方法时,沿作用域链向上寻找调用的方法
- (1)可以被类所有实例访问的区域,可以将公共属性(方法)存储到原型中
- (2)JS中的继承通过原型对象来实现,将子类的原型设置为父类的实例,例如
子类.prototype = new 父类()
- (3)将公共属性(方法)存储在原型中,可以节约内存【实例属性(方法),每创建一个实例,需要单独存储】
切记:
- (1)不要通过实例修改原型!!【使用
实例.__proto__
修改后,所有实例均会得到一个修改后的原型】- (2)不要为实例创建一个新原型!!【会造成该实例具有特殊性】
- (3)推荐修改原型的方式:
类.prototype = xxx
【好处:无需创建实例,一次修改所有实例原型均会修改】
(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
共同点:
均可以用来检查一个属性是否存在于某个对象中
区别点:
(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
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改变属性是否可枚举)- 包括基本包装类型
Boolean
、Number
和String
(特殊的引用数据类型)的 原型属性
注:for … in …遍历的也是可枚举属性
部分内容参考《ECMAScript 6 入门》《JavaScript权威指南》《JavaScript高级程序设计》,如有错误,欢迎评论区指正。