基础知识

为了和其他语言继承形态一致,JS提供了class 关键词用于模拟传统的class ,但底层实现机制依然是原型继承。

class 只是语法糖为了让类的声明与继承更加简洁清晰。

声明定义

可以使用类声明和赋值表达式定义类,推荐使用类声明来定义类

// 声明式
class User {}

// 赋值
const User = class {}

类方法间不需要逗号

class User {
    get() {}
    set() {}
}

构造函数

使用 constructor 构造函数传递参数,下例中show为构造函数方法,getName为原型方法

constructor 会在 new 时自动执行

class User {
    constructor(name) {
        this.name = name
        this.show = function() {}
    }

    getName() {
        return this.name
    }
}

const m = new User('mkimq')
console.log(m)

构造函数用于传递对象的初始参数,但不是必须定义的,如果不设置系统会设置如下类型

  • 子构造器中调用完super 后才可以使用 this
  • 至于 super 的概念会在后面讲到
constructor(...args) {
    super(...args)
}

原理分析

  • 类其实是函数
  • 在类中定义的方法也保存在函数原型中
class User {
	constructor(name) {
		this.name = name
	}

	show() {
		console.log(this.name)
	}
}
function User(name) {
	this.name = name
}

User.prototype.show = function() {
	console.log(this.name)
}

属性定义

在class中定义的属性为每个new出的对象独立创建,下面定义了site与name两个对象属性

class User {
	site = 'mkimq'

	constructor(name) {
		this.name = name
	}

	show() {
		console.log(this.site + ':' + this.name)
	}
}

const m = new User('mankeung')

m.show()

函数差异

class 是使用函数声明类的语法糖,但也有些区别

class 中定义的方法不能枚举

class User {
	constructor(name) {
		this.name = name
	}

	show() {
		console.log(this.name)
	}
}

const m = new User('mkimq')

// 不会枚举出show属性
for (const key in m) {
	console.log(key)
}

function Fn(name) {
	this.name = name
}

Fn.prototype.show = function() {
	console.log(this.name)
}

const f = new Fn('f')

for (const key in f) {
	console.log(key)
}

严格模式

class 默认使用strict 严格模式执行

class User {
	constructor(name) {
		this.name = name
	}

	show() {
		function test() {
			//严格模式下输出 undefined
			console.log(this)
		}
		test()
	}
}

const m = new User('mkimq')
m.show()

function Fn(name) {
	this.name = name
}

Fn.prototype.show = function() {
	function test() {
		// 非严格模式输出 Window
		console.log(this)
	}
	test()
}

const f = new Fn('f')
f.show()

静态访问

静态属性

静态属性即为类设置属性,而不是为生成的对象设置,下面是原理实现

function User() {}

User.site = 'mkimq'
console.log(User)

const m = new User()
console.log(m.site)
console.log(User.site)

在class中为属性添加static关键字即声明为静态属性

  • 可以把为所有对象使用的值定义为静态属性
class User {
	static site = 'mkimq'

	show() {
		return User.site
	}
}

const m = new User()
console.log(m.show())
console.log(User.site)

静态方法

指通过类访问不能使用对象访问的方法,比如系统的Math.round()就是静态方法

  • 一般来讲方法不需要对象属性参与计算就可以定义为静态方法

下面是静态方法实现原理

function User() {
	this.show = function() {
		console.log('this is a object function')
	}
}

User.show = function() {
	console.log('this is static method')
}

const m = new User()
m.show()
User.show()

在class内声明的方法前使用static定义的方法即是静态方法

class User {
	show() {
		console.log('this is a object function')
	}

	static show() {
		console.log('this is static method')
	}
}

const m = new User()
m.show()
User.show()

访问器

使用访问器可以对对象的属性进行访问控制,下面是使用访问器对私有属性进行管理。

语法介绍

使用访问器可以管控属性,有效的防止属性随意修改

  • 访问器就是在函数前加上 get/set修饰,操作属性时不需要加函数的扩号,直接用函数名
class User {
	constructor(name) {
		this.data = { name }
	}

	get name() {
		return this.data.name
	}

	set name(value) {
		if (value.trim() == '') throw new Error('invalid params')

		this.data.name = value
	}
}

const m = new User('mkimq')

m.name = 'MKIMQ'

console.log(m.name)

访问控制

设置对象的私有属性有多种方式

public

public 指不受保护的属性,在类的内部与外部都可以访问到

class User {
	site = 'mkimq'

	constructor(name) {
		this.name = name
	}
}

const m = new User('mankeung')

console.log(m.site, m.name)

protected

protected是受保护的属性修释,不允许外部直接操作,但可以继承后在类内部访问,有以下几种方式定义

class User {
	_site = 'mkimq'

	constructor(name) {
		this.name = name
	}
}

// 继承时可以使用
class Admin extends User {
    show() {
        return this._site
    }
}

使用Symbol定义私有访问属性,即在外部通过查看对象结构无法获取的属性

const protecteds = Symbol()

class User {
    constructor() {
		this[protecteds] = {}
		this[protecteds].site = 'mkimq'
	}
}

WeakMap 是一组键/值对的集,下面利用WeakMap类型特性定义私有属性

const protecteds = new WeakMap()

class User {
    constructor() {
		protecteds.set(this, 'mkimq')
	}
}

private

private 指私有属性,只在当前类可以访问到,并且不允许继承使用

  • 为属性或方法名前加 # 为声明为私有属性
  • 私有属性只能在声明的类中使用
class User {
	#site = 'mkimq'

	#check() {}
}

属性保护

保护属性并使用访问器控制

class User {
	constructor(name) {
		this.data = { name }
	}

	get name() {
		return this.data.name
	}

	set name(value) {
		if (value.trim() == '') throw new Error('invalid params')
		this.data.name = name
	}
}

详解继承

属性继承

属性继承的原型如下

function User(name) {
	this.name = name
}

function Admin(name) {
	User.call(this, name)
}

const m = new Admin('mkimq')
console.log(m)

类构造函数

class User {
	constructor(name) {
		this.name = name
	}
}

class Admin extends User {
	constructor(name) {
		super(name)
	}
}

const m = new Admin('mkimq')
console.log(m)

方法继承

原生的继承主要是操作原型链,实现起来比较麻烦,使用 class 就要简单的多了。

  • 继承时必须在子类构造函数中调用 super() 执行父类构造函数
  • super.show() 执行父类方法
class User {
	constructor(name) {
		this.name = name
	}

	show() {
		return this.name
	}
}

class Admin extends User {
	constructor(name) {
		super(name)
	}

	run() {
		return super.show()
	}
}

const m = new Admin('mkimq')
console.log(m.run())

也可以使用 extends 继承表达式返回的类

super

  • 表示从当前原型中执行方法,
  • super 一直指向当前对象

super 只能在类或对象的方法中使用,而不能在函数中使用,下面将产生错误

constructor

  • super 指调父类引用,在构造函数constructor 中必须先调用super()
  • super() 指调用父类的构造函数
  • 必须在 constructor 函数里的this 调用前执行 super()

父类方法

使用super可以执行父类方法

super.show()

方法覆盖

子类存在父类同名方法时使用子类方法

静态继承

静态的属性和方法也是可以被继承使用的

对象检测

  1. instanceof
console.log(m instanceof User)
  1. isPrototypeOf
User.prototype.isPrototypeOf(m)

继承内置类

function Arr(...args) {
	args.forEach(item => this.push(item))

	this.first = function() {
		return this[0]
	}

	this.max = function() {
		return this.sort((a, b) => b -a)[0]
	}
}

Arr.prototype = Object.create(Array.prototype)

const arr = new Arr(1, 2, 3)

arr.forEach(item => {
	console.log(item)
})

console.log(arr.first())
class Arr extends Array {
	constructor(...args) {
		super(...args)
	}

	first() {
		return this[0]
	}

	add(value) {
		return this.push(value)
	}

	remove(value) {
		const pos = this.findIndex(curValue => {
			return curValue == value
		})

		this.splice(pos, 1)
	}
}

const m = new Arr(1, 2, 3)

console.log(m.length)
console.log(m.first())

m.add('mkimq')

console.log(m.join('-'))

mixin

JS不能实现多继承,如果要使用多个类的方法时可以使用mixin混合模式来完成。

  • mixin 类是一个包含许多供其它类使用的方法的类
  • mixin 类不用来继承做为其它类的父类
const Tool = {
	max(key) {
		return this.data.sort((a, b) => b[key] - a[key])[0]
	}
}

class Lesson {
	constructor(lession) {
		this.lession = lession
	}

	get data() {
		return this.lession
	}
}

Object.assign(Lesson.prototype, Tool)

const data = [
	{
		name: 'js',
		price: 100
	},
	{
		name: 'ts',
		price: 90
	},
	{
		name: 'node',
		price: 88
	},
]

const m = new Lesson(data)

console.log(m.max('price'))
贡献者: mankueng