类的本质是函数
class Father {}
console.dir(Father)
function Parent() {}
console.dir(Parent)
// Father 和 Parent 不管从结构还是原型,几乎一模一样,所以类的本质是函数(尤其指构造函数),类只是一种语法糖。
console.log(typeof Father) // function
成员变量(属性)
class Father {
constructor(a){
this.a = a
this.b = 'b'
}
}
console.dir(Father) // 没有 a 和 b 属性
function Parent(a){
this.a = a
this.b = 'b'
}
console.dir(Parent) // 没有 a 和 b 属性
console.log(new Father('爸爸')) // 有 a 和 b 属性
console.log(new Parent('父亲')) // 有 a 和 b 属性
// 类的成员变量(属性)通过 constructor 方法初始化,跟构造函数的属性几乎一样。
// 类的成员变量是属于对象的,不属于类。
成员方法
class Father {
show(){
console.log('show...')
}
}
console.dir(Father) // show 方法在 Father.prototype下
function Parent(){}
Parent.prototype.show = function(){
console.log('show...')
}
console.dir(Parent) // show 方法在 Parent.prototype下
// 类的成员方法跟构造函数原型上的方法几乎一样
// 不同点:
// 1、类的成员方法只有一个 __proto__ 原型,而构造函数的方法还是跟普通函数一样,拥有两个原型(prototype 和 __proto__)。
// 2、类的成员方法是不可遍历的,方法的 enumerable 特征为 false,而构造函数的方法正好相反。
每个对象拥有独立的成员变量(属性),原理同构造函数。
每个对象拥有公共的成员方法,原理同构造函数的原型方法。
静态属性
class Father {
static a = 'a'
}
console.dir(Father) // 有 a 属性
function Parent(a){}
Parent.a = 'a'
console.dir(Parent) // 有 a 属性
console.log(new Father()) // 没有 a 属性
console.log(new Parent()) // 没有 a 属性
// 静态属性属于类本身,不属于对象。
静态方法
class Father {
static show(){
console.log('show...')
}
}
console.dir(Father) // 有 show 方法
function Parent(){}
Parent.show = function(){
console.log('show...')
}
console.dir(Parent) // 有 show 方法
console.dir(new Father()) // 没有 show 方法
console.dir(new Parent()) // 没有 show 方法
// 静态方法属于类本身,不属于对象。
静态方法的应用
class User {
constructor(name, age, sex) {
this.name = name
this.age = age
this.sex = sex
}
static create(...args) { // 用展开运算符,接收任意多个参数
return new this(...args) // 返回 new User(...args),此处可用 this 代指 User
}
}
const user = User.create('张三', 11, '男') // 静态方法创建对象
console.log(user)
访问器
不使用访问器
class User {
setEmail(email){
if (email) {
console.log(`email is ${email}`)
} else {
throw new Error('email is error')
}
}
}
const user = new User()
user.setEmail('aa@qq.com') // 普通的成员方法,一个在 __proto__ 下的普通函数。
使用访问器
class User {
set email(email){
if (email) {
console.log(`email is ${email}`)
} else {
throw new Error('email is error')
}
}
}
console.dir(User) // 在 prototype 上有一个 email 方法,本质就是普通的成员方法
const user = new User()
console.log(user) // user 对象的原型 __proto__ 上有一个 email 方法,user 可以调用这个方法
user.email = 'aa@qq.com' // 以设置属性的方式使用 email 方法
console.log(user) // user 对象上并没有增加 email 属性,说明虽然是以属性设置的方式,但本质上还是方法调用
使用访问器需要避免属性和方法重名
class User {
constructor(email) {
this.email = email
}
set email(email) {
if (email) {
this.email = email
} else {
throw new Error('email is error')
}
}
}
const user = new User()
user.email = 'aa@qq.com'
// 当属性和方法重名时,一方面在调用 user.email 时,js 引擎无法分辨这个 email 是属性还是方法,另一方面,在调用 email 方法时,如果设置成功,那么就相当于执行了 this.email = email,那么这里又会触发访问器,进入无限循环。
// 一般采用 data 属性来封装普通属性,类似 vue 和小程序的 data 属性。
class User {
constructor() {
this.data = {
email: ''
}
}
set email(email) {
if (email) {
this.data.email = email
} else {
throw new Error('email is error')
}
}
}
const user = new User()
user.email = 'aa@qq.com'
console.log(user)
可以用 get 来获取
class User {
constructor() {
this.data = {
email: ''
}
}
set email(email) {
if (email) {
this.data.email = email
} else {
throw new Error('email is error')
}
}
get email() {
return this.data.email
}
}
const user = new User()
user.email = 'aa@qq.com'
console.log(user.email) // aa@qq.com
属性保护
命名规则保护属性
class Website {
_site = 'https://www.baidu.com'
}
// 使用约定俗成的命名规则,在变量前面加下划线,说明这是一个私有属性。
// 没有实质性的保护效果,不推荐
使用 Symbol 定义 protected 属性
const HOST = Symbol('site') // 利用 Symbol 定义变量的不重复特性,定义一个不重复的变量
class Website {
constructor(a) {
this.data = {
a: a,
[HOST]: 'https://www.baidu.com' // 初始化,用中括号括起来的意思是,解析 HOST 变量所存储的字符串,以这个字符串作为属性名
}
}
// 由于 Symbol 定义的变量不能直接拿来访问,因此对象必须通过访问器来访问 protected 属性
set host(host) {
this.data[HOST] = host
}
get host() {
return this.data[HOST]
}
}
console.dir(Website)
const website = new Website('aaa')
website.host = 'https://www.qq.com'
console.log(website.host) // https://www.qq.com
// 当需要多个 protected 属性时
const protecteds = Symbol('protecteds')
class Website {
constructor(a) {
this.data = {
a: a,
}
this[protecteds] = {
host: 'https://www.baidu.com'
}
}
set host(host) {
this[protecteds].host = host
}
get host() {
return this[protecteds].host
}
}
const website = new Website('aaa')
website.host = 'https://www.qq.com'
console.log(website)
使用 WeakMap 定义 protected 属性
// 需要用到模块化,不好演示
private 私有属性
class Website {
#password = '123456'
set password(password) {
this.#password = password
}
get password() {
return this.#password
}
}
const website = new Website()
console.log(website)
website.password = '000000'
console.log(website.password) // 000000
console.log(website.#password) // Uncaught SyntaxError: Private field '#password' must be declared in an enclosing class // 私有属性错误
类的继承
class Father {
}
class Son extends Father {
}
console.dir(Son)
// 打印发现,类的继承其实就是原型链继承,Father 挂载在 Son 的 prototype 上。
super
class Father {
show() {
console.log('show father')
}
}
class Son extends Father {
constructor() {
super() // 必须先调用父类的构造函数,否则会报派生类错误
super.show() // 调用父类的 show 方法
this.show() // 调用自己的 show 方法
}
show() {
console.log('show son')
}
}
new Son()