==这篇文章将会让你了解class的基本使用,文章的代码尤为重要==
什么是Class
class是ES6提出的一种概念,通过class关键字来定义类,他其实是创建构造函数的一种语法糖,通过class来创建,使语法更加明了
通过基类创建
基类是使用'class'关键字定义的意思
class Person {
constructor (name) {
// 给类的实例添加一个name属性
this.name = name;
}
// 在prototype中添加say方法
say () {
console.log(this) // Person实例
console.log('我的名字叫遥近')
}
}
// 上面的代码其实等价于此
function Person (name) {
this.name = name
}
Person.prototype.say = function () {
console.log('我的名字叫遥近')
}
constructor不是必须要写的,如果您需要为实例添加属性和方法,那么就需要,但是constructor又是必须的,哪怕你没有写上,它也会默认添加constructor
基类的默认constructor
constructor() {}
通过类表达式创建
通过类表达式创建,可以是命名的也可以是匿名的
==创建一个匿名的类==
let Person = class {
who () {
return '遥近'
}
}
let yaojin = new Person()
console.log(yaojin.who()) // 遥近
==创建一个命名类==
let Iphone = class phone {
say () {
console.dir(phone)
console.log(phone.name) // phone
}
}
let iphone11 = new Iphone()
iphone11.say()
通过命名类或者基类创建,都可以在==内部==使用类本身
可以看到 console.dir(phone) 可以获取到类本身,而通过匿名创建,没有名称无法读取类自身
==通过类表达式,可以写出立即执行的 Class==
let Iphone = new class phone{
constructor (name) {
this.name = name
}
}('iphone11')
console.log(Iphone.name) // iphone11
创建class的静态方法
在class中定义的方法都会在原型上,如果在一个方法前加上 ==static== 关键字,那么该方法就不会给继承,==只能通过类自身来调用,静态方法中的this指向类本身==
class Phone{
static money () {
console.log(9999)
}
}
Phone.money() // 9999
let iphone11 = new Phone()
iphone11.money() // 报错了
创建class的静态属性
跟静态方法一样,静态属性只能 ==类自身使用== 其实==es6并不支持静态属性==, 因为类也是对象,其实我们只是在这个对象上添加了一个prop属性,勉强可以说是静态属性,可是如果你将方法一的prop属性名改成name,其实它并不会改变类自身的name,打印的时候还是原来的name值
而方法二仅仅只是草案, 通过方法二定义的可以改变自身的name值,是真正意义上的静态属性
// 方法一
class Phone{
}
Phone.prop = 'iphone11'
console.log(Phone.prop) // iphone11
// // 方法二
class Phone {
static name = 'iphone11'
}
let iphone11 = new Phone()
console.log(Phone.name) // iphone11
console.log(iphone11.name) // undefined
简单的定义实例属性
如果想给实例添加一个属性,我们一般会在constructor中进行定义,还有更加简单的办法,直接写在class的==最顶部(更加规范)==
class Phone{
name = 'iphone11'
}
let iphone11 = new Phone()
console.log(iphone11.name) // iphone11
当然这种办法,仅适合不是动态的设置实例的属性的时候,否则的话还是使用constructor吧
创建私有方法
所谓私有方法是只能在==类内部使用的方法==,外部无法调用,可是ES6并不提供,如果我们想实现这样的需求,那么就需要自己模拟实现
==通过改变this指向模拟==
class Iphone {
price (num) {
buy.call(this, num)
}
}
function buy (num) {
return this.money = num // 给实例对象添加了一个money属性
}
let iphone11 = new Iphone()
iphone11.price(9999)
console.log(iphone11.money) // 9999
Class继承(派生类)
Class 可以通过extends关键字实现继承
class MobilePhone {
constructor(price) {
this.price = price
}
call () {
console.log('开始通信')
}
}
class Iphone extends MobilePhone {
constructor (price, name) {
super(price) // 调用父类constructor(price)
this.name = name
}
call () {
super.call()
}
}
let iphone11 = new Iphone(9999)
iphone11.call() // 开始通信
console.log(iphone11.price)
==需要注意的是,实现继承子类必须在constructor中调用super方法才可以使用this==, 这是因为你要继承父类的属性和方法,如果你没有调用该方法,就会报错
我们之前也说了,constructor是必须的,你可以不显示定义,如果你没有显示定义,它也会默认添加
继承(派生类)默认的constructor
constructor(...args) {
super(...args);
}
通过继承的创建的实例对象, 都是两个类的实例
console.log(iphone11 instanceof Iphone) // true
console.log(iphone11 instanceof MobilePhone) // true
另外, 子类还可以继承父类的静态方法和静态属性
class MobilePhone {
static number = 1
static call () {
console.log('开始通信')
}
}
class Iphone extends MobilePhone {
}
Iphone.call()
console.log(Iphone.number)
super的使用
super即可当函数使用也可以当对象使用
作为函数使用只能在constructor中使用,在其他地方会报错,并且其this指向子类的构造函数 ==也就是说当super当函数使用的使用,其实等价于 父类.prototype.constructor.call(this)==
当作为对象使用的时候,在普通方法中,指向父类的原型对象;在静态方法中,指向父类。
普通方法
class MobilePhone {
call () {
console.log(this) // Iphone实例
console.log('开始通信')
}
}
class Iphone extends MobilePhone {
constructor () {
super()
this.money = 9999
super.money = 1 // 此时super为this
console.log(super.money) // undefined
console.log(this.money)
}
inherit () {
super.call() // 普通方法中
}
}
console.log(Iphone.prototype)
let iphone11 = new Iphone()
iphone11.inherit() // 开始通信,调用了父的方法
- 当在子类的普通方法中调用super,此时方法中的this指向当前子类的实例
- 当对某个属性进行赋值的时候,super就是this
- 其他情况super为父类的原型对象,可以看到当用super.money读取的时候,是undefined,因为此时是读取父类的prototype,显然是没有money属性
静态方法
class MobilePhone {
static call () {
console.log('我是手机')
}
}
class Iphone extends MobilePhone {
static call () {
super.call() // MobilePhone.call
}
}
Iphone.call() // 我是手机
当在静态方法中使用super,此时super为父类
如何模拟实现继承?
首先我们要明确一个关系如下:
class MobilePhone {
}
class Iphone extends MobilePhone {
}
Iphone.__proto__ === MobilePhone
Iphone.prototype.__proto__ === MobilePhone.prototype
这样的结果是通过这样进行实现的
class MobilePhone {
}
class Iphone {
}
// Iphone 的实例继承 MobilePhone 的实例
Object.setPrototypeOf(Iphone.prototype, MobilePhone.prototype);
// Iphone 的实例继承 MobilePhone 的静态属性
Object.setPrototypeOf(Iphone, MobilePhone);
什么是setPrototypeOf ? setPrototypeOf 的实现
Object.setPrototypeOf = function (obj, proto) {
obj.__proto__ = proto;
return obj;
}
extends后面可以跟多种类型
了解到时如何继承,那么我们就可以知道extends不仅仅可以继承class定义的类,只要是函数那么就可以继承, 因为函数拥有prototype属性,当然==Function.prototype除外==
例如我创建的class想继承Object原型上的方法
class Iphone extends Array {
}
Iphone.__proto__ === Array
Iphone.prototype.__proto__ === Array.prototype
应用场景 现在有个业务需求,设置病人的体检项目,病人有默认的体检项目有抽血、内外科检查,可是根据不同的情况又会添加不一样的体检项目,此时我们就可以封装一个class了
class Patient extends Array {
constructor () {
super()
this.checkList = ['抽血', '内外科检查']
}
addProject (name) {
this.checkList.push(name)
}
}
let xiaogao = new Patient()
xiaogao.addProject('手术')
console.log(xiaogao.checkList) // ["抽血", "内外科检查", "手术"]
Mixin模式的实现
Mixin 指的是多个对象合成一个新的对象,这样你定义的Class就可以拥有多种继承对象
function mix(...mixins) {
class Mix {
constructor() {
for (let mixin of mixins) {
copyProperties(this, new mixin()); // 拷贝实例属性
}
}
}
for (let mixin of mixins) {
copyProperties(Mix, mixin); // 拷贝静态属性
copyProperties(Mix.prototype, mixin.prototype); // 拷贝原型属性
}
return Mix;
}
function copyProperties(target, source) {
// 遍历所有构造函数,返回构造函数的自身属性
for (let key of Reflect.ownKeys(source)) {
// 排除一些没有的属性
if ( key !== 'constructor'
&& key !== 'prototype'
&& key !== 'name'
) {
// 获取对象的属性描述符
let desc = Object.getOwnPropertyDescriptor(source, key);
// 给对象上定义一个新的属性
Object.defineProperty(target, key, desc);
}
}
}
class DistributedEdit extends mix(Array, String) {
}
new target
ES6 为new命令引入了一个new.target属性
function Person(name) {
if (new.target !== undefined) {
this.name = name;
} else {
throw new Error('必须使用 new 命令生成实例');
}
}
var person = new Person('张三'); // 正确
var notAPerson = Person.call(person, '张三'); // 报错
细节知识
==constructor默认返回实例对象,但是可以指定返回一个新的对象==
class Foo {
constructor() {
return Object.create(null);
}
}
new Foo() instanceof Foo // false
上面代码中,constructor函数返回一个全新的对象,结果导致实例对象不是Foo类的实例
==类的属性名,可以采用表达式。==
let methodName = 'getArea';
class Square {
constructor(length) {
// ...
}
[methodName]() {
// ...
}
}
==类不存在变量提升==
new Foo(); // ReferenceError
class Foo {}