JS数据类型之Class

之前只使用ES5的特性来模拟类似有类(Class)的行为。ES6引入了class关键字具有正式定义类的能力。ES6表面上看起来可以支持正式的面向对象编程,但实际上使用的仍是原型和构造函数的概念。

类的定义

定义类的方式

定义类有两种主要方式:类声明和类表达式。

// 类声明
class Person {

}

// 类表达式
var Animal = class {}
  • 类的定义不能提升(也可以这样理解:类提升了,但是在定义之前存放在暂存性死区中,不能使用)
  • 函数 VS 类
函数
定义方式 两种
1、函数声明
2、函数表达式
两种
1、类声明
2、类表达式
表达式定义 提升,在赋值之前不能使用 提升,在赋值之前不能使用
声明定义 提升,在任何地方都可以使用 不提升,在声明之后可以使用
声明定义的作用域 函数声明不受作用域限制 类声明受块作用域限制
  • 类表达式中的名称是可选的。这个名称只能在类表达式作用域中使用。
// 空类
class Foo {}

// 有构造函数
class Bar {
  constructor() {
  }
}

// 有设置函数或获取函数
class Baz {
  get a() {
    return "a"
  }
}

// 有静态方法
class Qux {
  static myQux() {
  }
}

类的构成

类可以包含构造函数方法原型方法实例方法获取函数设置函数静态方法。但是不必要的。空的类定义同样有效。默认情况下,类定义中的代码都是在严格模式下进行的。

类构造函数

constructor关键字用于在类定义块内部创建类的构造函数。方法名constructor会告诉解释器在使用new操作符创建类的实例时,应该调用此方法。

实例化

使用new操作符实例化类时,相当于使用new调用其的构造函数。构造流程图如下

new执行过程.png

// 默认返回
class Person {
    constructor(name) {
        this.name = name;
    }
}

// 设置返回
class Animal {
    constructor(type) {
        this.type = type
        return {}
    }
}

let person = new Person("张三");

let monkey = new Animal("猴子");

console.log(person instanceof Person) // true

console.log(monkey instanceof Animal) // false

console.dir(person)
console.dir(monkey)
注意事项
  • constructor构造函数不是必需的。不定义构造函数时相当于将构造函数定义为空函数。
  • 类构造函数与构造函数的区别:调用类的构造必须使用new操作符,而普通构造函数可以使用new,也可以不使用new。
  • 类可以认为是特殊的构造函数。typeof操作符检测一个类时,返回的结果为function。同时类中有[[Prototype]]属性,此属性中的constructor属性指向自身。与构造函数相同。
构造函数
使用方式 必需使用new操作符 可以使用new操作符,也可以直接调用
instanceof 效果相同 效果相同
typeof 效果相同(function) 效果相同(function)

实例成员

每次通过new调用类标识时,都会执行类的构造函数。在构造函数的内部,可以为新创建的实例(this)添加自有属性。

class Person {
    constructor(name) {
        this.name = name;
        this.friends = ["1", "2", "3"];
        this.sayName = () => {
            console.log(this.name)
        }
    }
}

let person1 = new Person("张三");
person1.friends.push("4")
let person2 = new Person("李四");

console.log(person1.friends) // ["1", "2", "3", "4"]
console.log(person2.friends) // ["1", "2", "3"]
console.log(person1.sayName == person2.sayName) // false
注意事项
  • 每个实例都有一个唯一的成员对象,所有的成员不会在原型上共享。
  • 实例成员定义在类中。

原型方法

类定义语法把在类块中定义的方法叫做原型方法。

let sym = Symbol("now")
class Person {
    constructor(name) {
        this.name = name;
        this.friends = ["1", "2", "3"];
        
    }
    sayName() {
        console.log(this.name)
    }
        [sym]() {
          console.log("计算属性定义的方法")
        }
}

let person1 = new Person("张三");
person1.friends.push("4")
let person2 = new Person("李四");

person1[sym]() // 计算属性定义的方法
console.log(person1.friends) // ["1", "2", "3", "4"]
console.log(person2.friends) // ["1", "2", "3"]
console.log(person1.sayName == person2.sayName) // true

注意事项

1、在类块中定义的普通方法会定义在类的原型中
2、定义类方法的时候,可以使用字符串、符号或计算的值作为键,即可以使用计算属性。

静态方法

在类上定义静态方法,这些方法通常用于执行不特定于实例的操作,也不要求存在类的实例。

// 默认返回
class Person {
    constructor(name) {
        this.name = name;
        this.friends = ["1", "2", "3"];
        this.sayName = () => {
            console.log(this.name)
        }
    }
    show() {
        console.log(this)
    }
    static getPrototype() {
        return this;
    }
    static getNow() {
        console.log("getNow")
    }
}

let person1 = new Person("张三");

console.dir(person1)
图片.png
注意事项

1、定义的静态方法会定义在类原型中的constructor对象中。
2、类中定义的静态方法无法使用实例进行访问。

访问器:获取函数、设置函数

class Person {
    constructor(name) {
        this.name_ = name;
        this.friends = ["1", "2", "3"];
        this.sayName = () => {
            console.log(this.name)
        }
    }
    show() {
        console.log(this)
    }
    get name() {
        return this.name_
    }
    set name(newName) {
        this.name_ = newName;
    }
}

let person1 = new Person("张三");

console.dir(person1.name) // 张三
person1.name = "李四"
console.dir(person1.name) // 李四

非函数原型和类成员

类定义并不显式支持在原型或类上添加成员数据,但在类定义的外部,可以手动添加:

class Person {
    constructor(name) {
        this.name = name;
        this.friends = ["1", "2", "3"];
    }
    type  = "type" // 自动到实例中
    // 定义在原型中
    getNow() {
        
    }
    // 定义在原型的constructor中
    static getTHIS() {
        console.log(this)
    }
    // 定义在原型的constructor中
    static type2 = "type2"
}
Person.type3 = "type3" // 添加到原型的constructor中
Person.prototype.type4 = "type4" // 添加到原型中
let person1 = new Person("张三");
图片.png
注意事项

1、类中定义的静态方法或属性只能通过类名.进行访问,无法使用实例进行访问。
2、定义类时,不能在类的原型上直接定义属性,但是可以在定义好类之后操作原型。

迭代器和生成器

class Person {
    constructor() {
        this.name = "abc";
        this.nickname = ["a", "b", "c"]
    }
    * getIterator() {
        yield 1;
        yield 2;
        yield 3;
    }
    // 定义迭代器,返回一个迭代器; 返回可迭代对象时,需要使用生成器函数
    [Symbol.iterator]() {
        return this.nickname.entries();
    }
}
let person1 = new Person("张三");
for (let value of person1) {
    console.log(value)
}
console.log("-----------")
let iterator = person1.getIterator();
console.log(iterator.next().value) // 1
console.log(iterator.next().value) // 2
console.log(iterator.next().value) // 3
console.log(iterator.next().value) // undefined
注意事项

1、定义迭代器时,定义普通函数,返回一个迭代器;或 定义生成器函数,返回一个可迭代对象。

类的继承

类的继承是ES6新语法,但是底层使用的仍然是原型链

入门

  • ES6支持单继承。使用关键字extends。继承内容内容是任何拥有[[Constructor]]和原型的对象。即可以继承类和一个普通的构造函数。
  • 派生类通过原型链访问类和原型上定义的方法,同时this表示当前的实例。
class Vehicle {
    identProto(id) {
        console.log(id, this)
    }

    static identClass(id) {
        console.log(id, this)
    }
}

class Bus extends Vehicle {

}

let bus = new Bus();
console.log(bus instanceof Vehicle) // true
console.log(bus instanceof Bus) // true
bus.identProto("bus") // bus Class Bus
Bus.identClass("Bus") // Bus Class Bus
Vehicle.identClass("Vehicle") // Vehicle Class Vehicle

function Person() {

}

class Man extends Person {

}

let man = new Man();
console.log(man instanceof Person) // true
console.log(man instanceof Man) // true

super、HomeObject、构造函数

super

派生类的方法通过super关键字引用他们的原型。仅局限于派生类的构造函数、实例方法和静态方法。

子类有构造函数

class Vehicle {
    constructor(type = "car", product_time, width, height) {
        this.type = type;
        this.product_time = product_time;
        this.width = width;
        this.height = height;
    }
    identProto(id) {
        console.log(id, this)
    }

    static identClass(id) {
        console.log(id, this)
    }
}

// Bus 显示定义构造函数
class Bus extends Vehicle {
    constructor(...arg) {
        super("bus", ...arg);
        this.param = arg;
    }
    static func() {
        console.log("静态方法")
    }
}

// Car 未定义构造函数
class Car extends Vehicle {
}

let bus = new Bus(1999, 6, 20);
let car = new Car(2000, 3, 6);
console.dir(bus)
console.dir(car)
图片.png
[[HomeObject]]

ES6给类的构造函数和静态方法添加了内部特性[[HomeObject]],这个特性是一个指针,指向定义该方法的对象。此指针是自动赋值的,而且只能在JavaScript引擎内部使用。

注意事项

1、super只能在派生类构造函数和静态方法中使用
2、不能单独使用super关键字,要么使用它调用构造函数,要么使用它引用静态方法。
3、调用super()会调用父类的构造函数,并将返回的实例赋值给this。同时也可通过super()进行参数的传递。
4、派生类没有定义构造函数时,实例化的时候会默认的构造函数,只调用父类的构造函数;派生类定义构造函数时,要么调用super()之后在进行其他操作,要么直接return一个对象。

继承内置类型

js支持继承内置类型,这样新创建的类就继承了内置对象的相关方法。

class SubArray extends Array {
  static arr() {
      
  }
}
let sub = new SubArray(...[1,2,3,4])
console.dir(sub)
注意事项

1、使用派生类类型调用父类的方法同时此方法有返回对象,返回的对象也属于派生类。

抽象基类

抽象基类,可供其他类继承,但本身不会被实例化。(类似java中的接口)。

  • 原理:new.target保存了通过new关键字调用的类或函数。可以通过这个字段进行判断是否是抽象基类。
class Vehicle {
  constructor(type = "car",product_time, width, height) {
    if(new.target == Vehicle) {
      throw new Error("Vehicle 不能实例化")
    }
    this.type = type;
    this.width = width;
    this.height = height;
  }
}

class Bus extends Vehicle {
  constructor(...param) {
    super("bus", ...param);
    this.param = param
  }
}

let bus = new Bus(1999, 6, 20);
console.dir(bus)
let vehicle = new Vehicle(200, 3, 6);
图片.png

多继承

虽然ES6不支持多继承,但是可以通过现有的特性可以轻松地模拟此行为。

原理:

extends关键字支持在类表达中使用,通过箭头函数实现类的继承。

class Vehicle {
    vehicle() {
        console.log("vehicle")
    }
}
let FooMixin = (Superclass) => class extends Superclass {
    foo() {
        console.log("foo")
    }
}
let BarMixin = (Superclass) => class extends Superclass {
    bar() {
        console.log("bar")
    }
}
let BazMixin = (Superclass) => class extends Superclass {
    baz() {
        console.log("baz")
    }
}

class Bus extends FooMixin(BarMixin(BazMixin(Vehicle))) {
    
}

let bus = new Bus();
bus.vehicle(); // vehicle
bus.foo(); // foo
bus.bar(); // bar
bus.baz(); // baz
注意事项

1、Object.assign()是混合对象,将对象的属性进行混合;而不是方法。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,539评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,911评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,337评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,723评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,795评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,762评论 1 294
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,742评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,508评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,954评论 1 308
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,247评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,404评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,104评论 5 340
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,736评论 3 324
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,352评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,557评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,371评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,292评论 2 352

推荐阅读更多精彩内容