Skip to content
On this page

关于 JS 的类

类的内部工作机制就是原型操作

js
class User {
	constructor(name) {
		this.name = name
	}
	show() {}
}
console.dir(User) // show 方法自动放到了原型上
console.log(User === User.prototype.constructor) // true
let obj1 = new User('he')
console.dir(obj1)
console.log(Object.getOwnPropertyNames(obj1)) // ['name']
console.log('----------------------')
function Person(name) {
	this.name = name
}
Person.prototype.show = function() {}
console.dir(Person)
console.log(Person === Person.prototype.constructor) // true
let obj2 = new Person('llo')
console.dir(obj2)
console.log(Object.getOwnPropertyNames(obj2)) // ['name']

注: class 其实是语法糖

class 里的方法为什么不能遍历

js
function User() {}
User.prototype.show = function() {}
let obj1 = new User()
for (const key in obj1) {
	console.log(key) // show
}
// 很明显 show 方法的特征是允许遍历的,为什么?
console.log(
	JSON.stringify(
	  Object.getOwnPropertyDescriptor(User.prototype, 'show'),
	  null,
	  2
	)
)
/* 打印结果
	{
	  "writable": true,
	  "enumerable": true, // 说明 show 属性可允许遍历的
	  "configurable": true
	}
*/

如何避免 show 被遍历?在 for 循环里添加 hasOwnProperty(key) 来判断。

js
function User(name) {
	this.name = name
}
User.prototype.show = function() {}
let obj1 = new User('he')
for (const key in obj1) {
	if(obj1.hasOwnProperty(key) ) {
		console.log(key) // name
	}
}

为什么在 class 声明的方法不能遍历?

js
class User {
	constructor(name) {
		this.name = name
	}
	show() {}
}
console.dir(User) // show 方法在原型上
console.log(
	JSON.stringify(
	  Object.getOwnPropertyDescriptor(User.prototype, 'show'),
	  null,
	  2
	)
)
/* 打印结果
	{
	  "writable": true,
	  "enumerable": false, // 说明 show 属性不可遍历的
	  "configurable": true
	}
*/
for (const key in obj1) {
	console.log(key) // name , 没有打印出 show
}

// 主要原型是 不希望在遍历中去查找原型上的方法

class 比构造函数具有严格模式

js
function User() {}
User.prototype.show = function() {
	function test() {
		console.log(this)
	}
	test()
}
(new User()).show() // this -> window,如果使用 'use strict' 下 this -> undefined 

class Person {
	show() {
		function test() {
			console.log(this)
		}
		test()
	}
}
(new Person()).show() // this -> undefined

静态方法

js
function User() {}
User.show = function() {}

class Person {
	static show() {}
}

class User {
	constructor(name) {
		this.name = name
	}
	static create(name) {
		// return new User(name)
		return new this(name) // 效果同上面注释
	}
}
console.log(User.create('he'))

// 使用场景
const data = [
	{id: 10001, name: '语文'},
	{id: 10002, name: '数学'},
	{id: 10003, name: '英语'},
]
class Lesson {
	constructor(data) {
		this.model = data
	}
	get name() {
		return this.model.name
	}
	static createBatch(data) {
		return data.map(item => {
			return new this(item)
		})
	}
	static getClassById(data, id) {
		if(Array.isArray(data)) {
			return data.find(item => item.model.id === id)
		}
		return null
	}
}

let lessons = Lesson.createBatch(data)
console.log(lessons) // [Lesson, Lesson, Lesson]
console.log(Lesson.getClassById(lessons, 10001).name) // 语文'

在类中使用访问器

js
class Request {
	constructor(url) {
		this._host = url
	}
	set host(url) {
		if(!/^https?:\/\//i.test(url)) {
			throw new Error("url 不合规")
		}
		this._host = url
	}
	get host() {
		return this._host
	}
}
let r = new Request('https://www.google.com')
// r.host = 'hello' // Error
r.host = 'https://www.youtube.com'
console.log(r.host) // https://www.youtube.com
r._host = '123' // 这里可以任意修改,如何避免

_host 是使用命名规则保护属性,是人为规定的一种方式,但是总会出现不按照规定的开发人员,如何避免?

如何有效的保护属性

js
// 1. 使用 Symbol
const HOST = Symbol()
class Common {
	[HOST] = 'https://www.google.com'
}
class User extends Common {
	
	constructor(name) {
	    super()
		this.name = name
	}
	set host(url) {
		if(!/^https?:\/\//i.test(url)) {
			throw new Error("url 不合规")
		}
		this[HOST] = url
	}
	get host() {
		return this[HOST]
	}
}
let obj1 = new User('he')
console.log(obj1[Symbol()]) // undefined
obj1.host = 'https://www.youtube.com'
console.log(obj1.host) // https://www.youtube.com
obj1[HOST] = '111' // 这样使用可以访问并且可以修改,咋办?模块化
console.log(obj1.host) // 111

// 2. 使用 WeakMap
const host = new WeakMap()
class User {
	constructor(name) {
		this.name = name
		host.set(this, 'https://www.google.com')
	}
	set host(url) {
		if(!/^https?:\/\//i.test(url)) {
			throw new Error("url 不合规")
		}
		host.set(this, url)
	}
	get host() {
		return host.get(this)
	}
}
let obj2 = new User('llo')
console.log(obj2.host) // https://www.google.com
host.set(obj2, 'abc') // 但是可以修改,咋办?模块化,将 host “隐藏”,把 User 导出
console.log(obj2.host) // abc

obj2.host = 'https://www.youtube.com' // 合法使用
console.log(obj2.host) // https://www.youtube.com

class 私有属性

私有属性只能在当前的 class 内部使用。

js
class User {
	[[sex]] = '保密'
	constructor(name) {
		this.name = name
	}
	setSex(value = '保密') {
		// 先校验 value 是否合规
		this.#check()
		this.#sex = value
	}
	// [[check]]() { // 不同浏览器会报不同的错
	// 	 return true
	// }
	[[check]] = () => { 
		return true
	}
	
}
let obj1 = new User('tj')
// obj1.#sex = '男' // 报错
obj1.setSex('')

class 的属性继承

js
function User(name) {
	this.name = name
}
function Admin(name) {
	User.call(this, name)
}
Admin.prototype = Object.create(User.prototype)
Admin.prototype.show = function() {}
console.dir(Admin) 
console.log(Admin.prototype === User) // false
console.log(Admin.__proto__ === User) // false
console.log(Admin.prototype.__proto__ === User.prototype)//true
//-----------------------------------------------//
class User {
	static age = 1
	constructor(name) {
		this.name = name
	}
}
class Admin extends User {
	constructor(name) {
		super(name)
	}
}
console.dir(Admin)
console.log(Admin.age) // 1 ,说明 extends 可以继承父类的静态属性
console.log(Admin.prototype === User) // false
console.log(Admin.__proto__ === User) // true
console.log(Admin.prototype.__proto__ === User.prototype)//true

class 的方法继承

js
function User(name) {}
User.prototype.show = function() {
	console.log('show')
}
function Admin(name) {}
Admin.prototype = Object.create(User.prototype)
console.dir(Admin) // 会有 Admin.prototype.__proto__.show,Admin 继承了 show 方法
//-----------------------------------------------//
class User {
	show() {
		console.log('show')
	}
}
class Admin extends User {}
console.dir(Admin) // 会有 Admin.prototype.__proto__.show,Admin 继承了 show 方法
let obj = new Admin()
console.log(obj.__proto__ === Admin.prototype) // true
console.log(obj.__proto__.__proto__ === User.prototype) //true

super 原理

js
let parent = {
	name: 'p',
	show() {
		console.log('parent.show', this.name)
	}
}
let ch = {
	__proto__: parent,
	name: 'c',
	show() {
		// this.__proto__.show() // 写法有问题,this 的指向问题
		this.__proto__.show.call(this) 
		console.log('ch.show', this.name)
	}
}
ch.show()

注: super 本质上需要将 this 指向继承父类的子类(调用父类的 constructor ,并将 constructor 中的 this 指向子类),这样子类就能使用自己的属性和方法,而不是使用父类的属性和方法。 MDN 文档是这么描述的:super 关键字用于访问和调用一个对象的父对象上的函数。 看看 babel 实现类的继承

多重继承 super 的强悍之处

js
let common = {
	show() {
		console.log('common.show', this.name)
	}
}
let parent = {
	__proto__: common,
	name: 'p',
	show() {
		super.show()
		console.log('parent.show', this.name)
	}
}
let ch = {
	__proto__: parent,
	name: 'c',
	show() {
		super.show() // 这里使用时,函数不能用 function 关键字声明
		console.log('ch.show', this.name)
	}
}
ch.show()

为什么子类 constructor 内要调用 super

前提:子类声明了 constructor 函数。必须要调用 super ,为什么? 这是基于 ES6 的继承机制(先将父类实例对象的属性和方法,加到 this 上面(所以必须先调用 super 方法),然后再用子类的构造函数修改 this )。换句话说:这是因为子类自己的 this 对象,必须先通过父类的构造函数完成塑造,得到与父类同样的实例属性和方法,然后再对其进行加工,加上子类自己的实例属性和方法。如果不调用 super 方法,子类就得不到 this 对象。

使用 super 访问父类的方法

js
class Parent {
	check() {
		return true
	}
}
class Ch {
	validate() {
		return !super.check()
	}
}

静态继承原理

js
function User() {}
User.legs = 2
User.getArms = function() {
	return 2
}
console.dir(User)
// User 其实也是个对象,当然就有 __proto__ 
function Admin() {}
Admin.__proto__ = User
console.dir(Admin)
Admin.getArms() // 2
Admin.legs // 2
//------------------------------------//
class User {
	static legs = 2
	static getArms() {
		return 2
	}
}
class Admin extends User {}
console.dir(Admin)
Admin.getArms() // 2
Admin.legs // 2

使用 instanceof 检测对象实现

instanceof 运算符用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上。 语法: object instanceof constructor

js
function Parent() {}
function Child() {}
Child.prototype = Object.create(Parent.prototype)
let ch = new Child()
console.log(ch instanceof Parent) // true
console.log(ch instanceof Child) // true
console.log(ch.__proto__ === Child.prototype) // true
console.log(Child.prototype.__proto__ === Parent.prototype) // true
console.log(ch.__proto__.__proto__ === Child.prototype.__proto__) // true

实现 instanceof 函数

js
function checkPrototype(obj, constructor) {
	if(!obj.__proto__) return false
	if(obj.__proto__ === constructor.prototype) return true
	return checkPrototype(obj.__proto__, constructor)
}

isPrototypeOf 检测继承关系

isPrototypeOf() 方法用于测试一个对象是否存在于另一个对象的原型链上。 语法: prototypeObj.isPrototypeOf(object)

js
let a = {}
let b = {
	__proto__: a
}
let c = {
	__proto__: b
}
console.log(a.isPrototypeOf(b)) // true
console.log(a.isPrototypeOf(c)) // true

class User {}
class Admin extends User {}
let admin = new Admin()
console.log(User.prototype.isPrototypeOf(admin)) // true
console.log(Admin.prototype.isPrototypeOf(admin)) // true

注意: 没有 instanceof 方便

内置类继承的原型实现

js
function Arr(...args) {
	args.forEach(item => this.push(item))
	this.first = function() {
		return this[0]
	}
}
Arr.prototype = Object.create(Array.prototype)
let arr = new Arr(1,2,3)
console.log(arr) // [1, 2, 3, first: f]
console.log(arr.first()) // 1

使用继承增强内置类

js
class Arr extends Array {
	constructor(...args) {
		super(...args)
	}
	first() {
		return this[0]
	}
	remove(v) {
		let pos = this.findIndex(item => item === v)
		this.splice(pos, 1)
	}
}
let arr = new Arr(1,2,3)
console.log(arr) // [1, 2, 3]
console.log(arr.first()) // 1

mixin 混合模式使用技巧

js
let Tool = {
	max(key) {}
}
let Shared = {
	count('name') {}
}
class Classes {
	constrctor(lessons) {
		this.lessons = lessons
	}
}
const data = [{name: 'ES'}]
Object.assign(Classes.prototype, Tool, Shared)
console.dir(Classes)
let obj = new Classes(data)
console.log(obj.max('name'))

Released under the MIT License.