出现的目的
=============
1、类定义
声明 *
// 类声明class Person {}// 类表达式const Animal = class {};
特点:
1、函数声明可以提升(在定义前提前使用),但类定义不能
2、函数受函数作用域限制,而类受块作用域限制
类和构造函数的异同 *

true
发现,和构造函数是一样的,因此class只是构造函数的语法糖
==============
2、构造函数 constructor
构造函数 *
constructor 关键字用于在类定义块内部创建类的构造函数,告诉解释器在使用 new 操作符创建类的新实例时,应该调用这个函数
这个属性不是必须的
下面代码实现了,一般情况通过函数创建对象,以及通过class的方式创建对象,对比,实际上两个方式是一模一样的。
// ============ 一般的函数 ============function Person1(name,age){// 1、定义属性this.name = namethis.age = age}// 2、定义方法Person1.prototype.running = function(){console.log(this.name + "正在跑步")}var p1 = new Person1("yjl",18)p1.running()// ============ 类的构造方法 ============class Person2 {// 1、定义属性constructor(name,age) { // 类构造函数,这个名字是固定的,而且不能写多个;new 创建一个实例时,会执行里面的代码this.name = namethis.age = age}// 2、定义方法// 实际也是给原型链添加方法,和上面一样的running(){console.log(this.name + "正在跑步")}}var p2 = new Person2("yjl",18)p2.running()
new 操作符
类构造函数与构造函数的主要区别是,调用类构造函数必须使用 new 操作符。
普通构造函数如果不使用 new 调用,那么就会以全局的 this(通常是 window)作为内部对象。
调用类构造函数时如果忘了使用 new 则会抛出错误。
function Person() {}class Animal {}// 把 window 作为 this 来构建实例let p = Person();let a = Animal();// TypeError: class constructor Animal cannot be invoked without 'new'p.constructor();// TypeError: Class constructor Person cannot be invoked without 'new'// 使用对类构造函数的引用创建一个新实例let p2 = new p.constructor();
任意地方定义
// 类可以像函数一样在任何地方定义,比如在数组中let classList = [class {constructor(id) {this.id_ = id;console.log(`instance ${this.id_}`);}}];function createInstance(classDefinition, id) {return new classDefinition(id);}let foo = createInstance(classList[0], 3141); // instance 3141
立刻实例化
let p = new class Foo {constructor(x) {console.log(x);}}('bar'); // barconsole.log(p); // Foo {}
==============
3、实例
每次通过new调用类标识符时,都会执行类构造函数。
在这个函数内部,可以为新创建的实例(this)添加“自有”属性。
至于添加什么样的属性,则没有限制。
而且在每个实例的属性都不共享,都是属于他们自己的
属性和方法
class Person {constructor(name) {// 添加到 this 的所有内容都会存在于不同的实例上// 通过 实例.属性 访问this.name = name// 通过 实例.方法() 访问this.locate = () => console.log('instance');}// 在类块中定义的所有内容都会定义在类的原型上//共享属性// 1、通过 类名.prototype.属性 访问// 2、通过 实例名.__proto__.属性 访问otherName = 'human'//共享方法// 1、通过 类名.prototype.方法() 访问// 2、通过 实例名.__proto__.方法() 访问locate() {console.log('prototype');}}p.locate(); // instancePerson.prototype.locate() // prototype
访问器 get set *
可以给一个属性设置get和set方法,表示这个属性被读取时(get)执行什么,以及被设置(set)时执行什么。
类似于给对象的属性设置特性,可以查看: https://www.yuque.com/yejielin/mypn47/mkkfc6#y0Swp
class Person2 {constructor(name,age) {this.name = namethis.age = agethis._city = "深圳"}running(){console.log(this.name + "正在跑步")}// 访问器(可以拦截对象属性的读取和写入的操作)get city(){ // 访问这个属性时执行console.log("读取了我所在的城市")return this._city}set city(newCity){ // 重新设置这个属性时执行console.log("重新设置了我所在的城市")this._city = newCity}}var p2 = new Person2("yjl",18)p2.city = "广州" // 输出:重新设置了我所在的城市
类的静态属性方法 static *
类里面加上static,表示这个属性或方法是类私有的,只能通过 类名.属性 或 类名.方法 访问。
比如 Promise.resole( ) 、 Promise.reject( )、 Promise.all( )这些就是静态方法。
// 定义人类class Person {constructor(name,age) {// 添加到 this 的所有内容都会存在于不同的实例上this.locate = () => console.log('这个是实例上的');this.name = name;this.age = age;}// 定义在类的原型对象上// 1、通过 实例.属性 或 实例.方法() 访问和在类外部创建// 2、通过 类名.prototype.属性 或 类名.prototype.方法() 访问和在类外部创建locate1() {console.log('这个是原型上的');}// 定义在类本身上私有方法// 通过 类名.方法() 访问和在类外部创建static locate() {console.log('这个是类的静态/私有方法');}// 定义在类本身上私有属性// 通过 类名.属性 访问和在类外部创建static name = 'human'// 静态方法非常适合用于特定方法构造对象实例static create(name) {// 使用随机年龄创建并返回一个 Person 实例return new Person(name,Math.floor(Math.random()*100));}}let T = new Person('Tom');T.locate() // 这个是实例上的T.locate1() // 这个是原型上的Person.locate() // 这个是类的静态/私有方法// Person.locate1() // 报错Person.prototype.locate1() // 这个是原型上的console.log('Person.name:',Person.name) // Person.name: humanconsole.log('T.name:',T.name) // T.name: Tomlet J = Person.create('Jerry')console.log('J.age:',J.age) // 15 随机数
迭代器与生成器
// 类定义语法支持在原型和类本身上定义生成器方法:class Person {// 在原型上定义生成器方法*createNicknameIterator() {yield 'Jack';yield 'Jake';yield 'J-Dog';}// 在类上定义生成器方法static *createJobIterator() {yield 'Butcher';yield 'Baker';yield 'Candlestick maker';}}let jobIter = Person.createJobIterator();console.log(jobIter.next().value); // Butcherconsole.log(jobIter.next().value); // Bakerconsole.log(jobIter.next().value); // Candlestick makerlet p = new Person();let nicknameIter = p.createNicknameIterator();console.log(nicknameIter.next().value); // Jackconsole.log(nicknameIter.next().value); // Jakeconsole.log(nicknameIter.next().value); // J-Dog// 因为支持生成器方法,所以可以通过添加一个默认的迭代器,把类实例变成可迭代对象:class Person {constructor() {this.nicknames = ['Jack', 'Jake', 'J-Dog'];}*[Symbol.iterator]() {yield *this.nicknames.entries();}}let p = new Person();for (let [idx, nickname] of p) {console.log(nickname);}// Jack// Jake// J-Dog//也可以只返回迭代器实例:class Person {constructor() {this.nicknames = ['Jack', 'Jake', 'J-Dog'];}[Symbol.iterator]() {return this.nicknames.entries();}}let p = new Person();for (let [idx, nickname] of p) {console.log(nickname);}// Jack// Jake// J-Dog
类私有属性
这个类特有的属性,外部都无法进行访问


报错,对象的外部得不到私有属性
只有对象内部可以访问
==============
4、继承
面向对象编程里面说到ES5的继承
JS - 面向对象编程
ES6新增类的继承。
虽然类继承使用的是新语法,但背后依旧使用的是原型链
继承基础 extends
继承任何拥有[[Construct]]和原型的对象。
这意味着不仅可以继承一个类,也可以继承普通的构造函数
class Vehicle {}// 继承类class Bus extends Vehicle {// Bus类的属性和方法}// 或 let Bus = class extends Vehicle { // Bus类的属性和方法 }let b = new Bus();console.log(b instanceof Bus); // trueconsole.log(b instanceof Vehicle); // truefunction Person() {}// 继承普通构造函数class Engineer extends Person {// Engineer类的属性和方法}let e = new Engineer();console.log(e instanceof Engineer); // trueconsole.log(e instanceof Person); // true
继承父类的 this
指向调用相应方法的实例或者类
class Vehicle {identifyPrototype(id) {console.log(id, this);}static identifyClass(id) {console.log(id, this);}}class Bus extends Vehicle {}let v = new Vehicle();let b = new Bus();b.identifyPrototype('bus'); // bus, Bus {}v.identifyPrototype('vehicle'); // vehicle, Vehicle {}Bus.identifyClass('bus'); // bus, class Bus {}Vehicle.identifyClass('vehicle'); // vehicle, class Vehicle {}
引用父类super *



引用错误,必须在子类里,访问子类的this之前,通过super( )调用父类的构造方法;或者在return 之前,通过super( )调用父类的构造方法
(正确)
重新实现父类方法 *
如果对父类的方法不满意,可以在子类里面重新实现一个同名的。
因为父类的方法是在父类的原型对象里面,如果自己没有就去找父类的,如果自己有,就用自己的。
(1)直接重写

(2)引用父类方法再重写
当自己重写的方法,部分逻辑和父类的相同,也可以通过super来引用父类的方法,同时可以传入参数

(3)引用静态方法重写
同样是直接通过super就可以调用父类的静态方法

抽象基类
基类:就是被其他类继承的父类,或者父类的父类…
抽象:无法实例化
JS没有专门支持这种类的语法 ,但通过 new.target 也很容易实现,表示当前创建实例时的类名
// 抽象基类class Vehicle {constructor() {console.log(new.target);if (new.target === Vehicle) { // 当前创建实例时的类名throw new Error('Vehicle 不允许实例化');}// 可以要求派生类必须定义某个方法,这里用if做检测,没有就报错if (!this.foo) {throw new Error('继承的类必须有属性foo()');}console.log('success!');}}// 派生类class Bus extends Vehicle {}new Bus(); // class Bus {} 报错,检查到foo属性不存在,因此抛出异常new Vehicle(); // class Vehicle {} 报错,new.target === Vehicle,因此抛出异常
内置对象扩展
JS内部提供了很多内置对象,比如Array,String等。
如果我想给数组加一个我通用的方法,我可以直接给Array加,但是这样就会产生问题,可能会意外重写了原本内部已经有了的方法,这样可能会导致其他问题,而且后面维护报错时完全没办法查看是哪里的问题。
因此可以创建一个自己的类,继承内置对象,然后再扩展。
但是实际开发中这样操作比较少。Java等面向对象语言用的比较多。
继承内置对象
this指的是调用new 的那个对象

类混入(理解)
JS中,类只有一个父类。
如果说我想继承多个父类,JS中只能一个一个继承。
可以写一个函数,在函数中实现继承,然后返回出去
用的不是特别多。
class 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))) {}// 或者function mix(BaseClass, ...Mixins) {return Mixins.reduce((accumulator, current) => current(accumulator), BaseClass);}class Bus extends mix(Vehicle, FooMixin, BarMixin, BazMixin) {}let b = new Bus();b.foo(); // foob.bar(); // barb.baz(); // baz
很多 JavaScript 框架(特别是 React)已经抛弃混入模式,转向了组合模式(把方法提取到独立的类和辅助对象中,然后把它们组合起来,但不使用继承)。
这反映了那个众所周知的软件设计原则:“组合胜过继承(composition over inheritance)。”
这个设计原则被很多人遵循,在代码设计中能提供极大的灵活性。
==============
类的模块化
文件1:
class a {// 类a 的属性和方法}export {a}
文件2
import {a} from '路径/文件1.js'let b = new a()
因此可以把类专门写在一个js文件中,通过es6模块化的方式引入到业务文件中,直接new 创建实例来使用,避免业务文件臃肿,也利于其他业务复用。
模块化可以查看
JS-模块化
babel转换成ES5代码
有的工具如webpack的babel,会把ES6的类转成ES5代码,为了可以适配IE10和更低的浏览器
这里多写了一个函数 _classCallCheck,目的是检查这个类class,不能让他可以直接被调用 Person( ),调用的话如果this不是她自己的实例(直接调用this就是window),就会抛出错误。
除去则函数,可以看到class 就是构造函数的简写

