出现的目的
什么是面向对象编程
ES6提供了更接近传统语言的写法,引入了 Class(类)这个概念,作为对象的模板。通过class关键字,可以定义类。基本上,ES6的class可以看作只是一个语法糖,它的绝大部分功能,ES5都可以做到,新的 class 写法只是让对象原型的写法更加清晰、更像面向对象编程的语法而已。
- 按照前面的构造函数形式创建类,不仅仅和编写普通的函数过于相似,而且代码并不容易理解。在ES6 ( ECMAScript2015)新的标准中使用了class关键字来直接定义类;
- 但是类本质上依然是构造函数、原型链的语法糖而已;
- 所以前面的构造函数、原型链更有利于理解类的概念和继承关系;
1、类定义
声明
特点:// 类声明// 注意:类名需要首字母大写。class Demo {// 构造函数 定义属性constructor() {this.name = '类的名称'}// 定义方法fn () {console.log('定义的方法1');}}const demo = new Demo();demo.fn1()// 类表达式const demo = class {};
1、函数声明可以提升(在定义前提前使用),但类定义不能
2、函数受函数作用域限制,而类受块作用域限制类和构造函数的异同
```javascript class Person { constructor() { } } // function Person () {
// } console.log(Person.prototype); // Object console.log(Person.prototype.proto); // Object console.log(Person.prototype.constructor); // class Person {…} console.log(typeof Person); // function const p = new Person(); console.log(p.proto == Person.prototype); // true
发现,和构造函数是一样的,因此class只是构造函数的语法糖<a name="Zhk8w"></a>## 2、构造函数 constructor<a name="LFm2M"></a>### 构造函数 *constructor 关键字用于在类定义块内部创建类的构造函数,告诉解释器在使用 new 操作符创建类的新实例时,应该调用这个函数- 当我们通过new关键字操作类的时候,会调用这个constructor函数,并且执行如下操作︰- 在内存中创建一个新的对象(空对象)- 这个对象内部的[[prototype]]属性会被赋值为该类的prototype属性- 构造函数内部的this,会指向创建出来的新对象- 执行构造函数的内部代码(函数体代码)- 如果构造函数没有返回非空对象,则返回创建出来的新对象这个属性不是必须的<br />下面代码演示了,一般情况通过函数创建对象,以及通过class的方式创建对象,对比,实际上两个方式是一模一样的。```javascript// ============ 一般的函数 ============function Person1(name,age){// 1、定义属性this.name = namethis.age = age}// 2、定义方法Person1.prototype.running = function(){console.log(this.name + "正在跑步")}var p1 = new Person1("张三",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("张三",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], 1234); // instance 1234
立刻实例化
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');}}const p = new Person();p.locate(); // instancePerson.prototype.locate() // prototypeconsole.log(p.otherName); // human
访问器 get set *
可以给一个属性设置get和set方法,表示这个属性被读取时(get)执行什么,以及被设置(set)时执行什么。
类似于给对象的属性设置特性,可以查看:
JS原生对象
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("张三",18)p2.city = "重庆" // 输出:重新设置了我所在的城市
4、继承
继承可以帮助我们将重复的代码和逻辑抽取到父类中,子类只需要直接继承过来使用即可。
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调用父类的构造函数!
super的使用位置有三个:子类的构造函数、实例方法、静态方法;
class Person {constructor(name, age) {this.name = name;this.age = age}}// Student 称之为子类 (派生类)// 直接继承,this会报错class Student extends Person {constructor(name, age, sno) {this.name = namethis.age = agethis.sno = sno}}let stu = new Student('why', 18, 111)// Must call super constructor in derived class before accessing 'this' or returning from derived constructor
引用错误,必须在子类里,访问子类的this之前,通过super( )调用父类的构造方法;或者在return 之前,通过super( )调用父类的构造方法
正确写法
class Person {constructor(name, age) {this.name = name;this.age = age}}// Student 称之为子类 (派生类)// 直接继承,this会报错class Student extends Person {constructor(name, age, sno) {super(name,age)this.sno = sno}}let stu = new Student('why', 18, 111)
类的模块化
文件1:
class a {// 类a 的属性和方法}export {a}
文件2:
import {a} from '路径/文件1.js'let b = new a()
因此可以把类专门写在一个js文件中,通过es6模块化的方式引入到业务文件中,直接new 创建实例来使用,避免业务文件臃肿,也利于其他业务复用。
babel转换成ES5代码
有的工具如webpack的babel,会把ES6的类转成ES5代码,为了可以适配IE10和更低的浏览器
这里多写了一个函数 _classCallCheck,目的是检查这个类class,不能让他可以直接被调用 Person( ),调用的话如果this不是她自己的实例(直接调用this就是window),就会抛出错误。除去则函数,可以看到class 就是构造函数的简写
