文章目录
1.class方式定义类
1.1 class的定义类结构
我们会发现,按照前面的构造函数形式创建类,不仅仅和编写普通的函数过于相似,而且代码并不容易理解。
- 在ES6(ECMAScript2015)新的标准中使用了class关键字来直接定义类;
- 但是类本质上依然是前面所讲的构造函数、原型链的语法糖而已;
- 所以学好了前面的构造函数、原型链更有利于我们理解类的概念和继承关系;
那么,如何使用class来定义一个类呢?
-
可以使用两种方式来声明类:类声明和类表达式;
// 方式一: 类声明 class Person { } // 创建实例对象 var p1 = new Person(); var p2 = new Person(); console.log(p1, p2); // Person {} Person {}
// 方式二: 类表达式(少见) var Student = class { } // 创建实例对象 var stu1 = new Student(); var stu2 = new Student(); console.log(stu1, stu2); // Student {} Student {}
注意: 类结构中定义多个内容, 不需要加逗号进行分隔
1.2 class类中构造函数
如果我们希望在创建对象的时候给类传递一些参数,这个时候应该如何做呢?
- 每个类都可以有一个自己的构造函数(方法),这个方法的名称是固定的constructor;
- 当我们通过new操作符,操作一个类的时候会调用这个类的构造函数constructor;
- 每个类只能有一个构造函数,如果包含多个构造函数,那么会抛出异常;
当我们通过new关键字操作类的时候,会调用这个constructor函数,并且执行如下操作:
- 在内存中创建一个新的对象(空对象);
- 这个对象内部的[[prototype]]属性会被赋值为该类的prototype属性;
- 构造函数内部的this,会指向创建出来的新对象;
- 执行构造函数的内部代码(函数体代码);
- 如果构造函数没有返回非空对象,则默认返回创建出来的新对象;
class Person {
// 类中的构造函数
// 当我们通过new关键字调用一个Person类时, 默认调用class中的constructor
constructor(name, age) {
this.name = name;
this.age = age;
}
}
var p1 = new Person("coder", 18);
console.log(p1.name, p1.age); // coder 18
// 实例的隐式原型会绑定Person的显式原型
console.log(p1.__proto__ === Person.prototype); // true
1.3 class类中实例方法
在上面我们定义的属性都是直接放到了this上,也就意味着它是放到了创建出来的新对象中:
- 在前面我们说过对于实例的方法,我们是希望放到原型上的,这样可以被多个实例来共享;
- 这个时候我们可以直接在类中定义;
class Person {
// 类中的构造函数
// 当我们通过new关键字调用一个Person类时, 默认调用class中的constructor
constructor(name, age) {
this.name = name;
this.age = age;
}
// 类中的实例方法
running() {
console.log("running");
}
eating() {
console.log("eating");
}
}
var p1 = new Person("coder", 18);
// 调用实例方法
p1.running();
p1.eating();
类中多个内容之间, 不需要逗号分隔
1.4 类和构造函数异同
这里有一段分别使用function和class定义类的代码:
// function定义类
function Person1(name, age) {
this.name = name;
this.age = age;
}
// 类的实例方法
Person1.prototype.running = function () {
console.log("Person1 running~");
};
Person1.prototype.eating = function () {
console.log("Person1 eating~");
};
// class定义类
class Person2 {
// 1.类的构造函数
constructor(name, age) {
this.name = name;
this.age = age;
}
// 2.类的实例方法
running() {
console.log("Person2 running~");
}
eating() {
console.log("Person2 eating~");
}
}
我们来研究一下类和构造函数的一些特性:
- 你会发现类和我们的构造函数的特性其实是一致的;
// 创建Person1的实例
var p1 = new Person1("coder", 18);
console.log(typeof Person1); // function
console.log(Person1.prototype); // {running: ƒ, eating: ƒ, constructor: ƒ}
console.log(Person1.prototype.constructor); // Person1
console.log(p1.__proto__ === Person1.prototype); // true
// 创建Person2的实例
var p2 = new Person2("kaisa", 19);
console.log(typeof Person2); // function
console.log(Person2.prototype); // {constructor: ƒ, running: ƒ, eating: ƒ}
console.log(Person2.prototype.constructor); // Person2
console.log(p2.__proto__ === Person2.prototype); // true
不同点: function定义的类可以作为普通函数去调用; class定义的类, 不能作为一个普通函数调用
// function定义的类可以作为普通函数调用
Person1("aaa", 11);
// class定义的类不可作为普通函数调用, 会报错
Person2("bbb", 22);
1.5 对象的访问器方法
我们之前讲对象的属性描述符时有讲过对象可以添加setter和getter函数的, 这里我们回顾一下:
- 我们对一些属性编写的get和set方法, 我们可以称之为getter方法和setter方法
方式一: 通过属性描述符定义访问器
var obj = {
// 程序员之间的约定: 以下划线开头的属性和方法, 是不在外界访问的
_name: "kaisa"
}
Object.defineProperty(obj, "name", {
set: function(value) {
this._name = value
},
get: function() {
return this._name
}
})
方式二: 之间在对象定义访问器(很少见,了解)
var obj = {
_name: "kaisa",
set name(value) {
this._name = value;
},
get name() {
return this._name;
},
};
1.6 class的访问器方法
那么类也是可以添加getter和setter函数
class Person {
constructor(name, age) {
this.name = name;
this._age = age;
}
set age(value) {
this._age = value;
}
get age() {
return this._age;
}
}
1.7 class类的静态方法
静态方法通常用于定义直接使用类来执行的方法,不需要有类的实例,回顾一下我们之前ES5定义类的静态方法(类方法)
function Person() {}
// 添加实例方法
Person.prototype.running = function() {}
// 添加静态方法(类方法)
Person.eating = function() {}
var p1 = new Person()
p1.running() // 调用实例方法
Person.eating() // 调用静态方法
ES6中class, 使用static关键字来定义静态方法(类方法):
class Person {
// 实例方法
eating() {}
running() {}
// 类方法
static studying() {}
}
var p1 = new Person();
p1.running(); // 调用实例方法
p1.eating(); // 调用实例方法
Person.studying(); // 调用类方法
注意: 实例方法的this指向实例对象, 静态方法的this指向类
2.ES6实现继承
2.1 extends实现继承
前面我们花了很大的篇幅讨论了在ES5中实现继承的方案,虽然最终实现了相对满意的继承机制,但是过程却依然是非常繁琐的。
- 在ES6中新增了使用extends关键字,可以方便的帮助我们实现继承
我们来写代码感受一下ES6中的继承吧
-
先定义一个Student类
class Student { constructor(name, age, id, score) { this.name = name; this.age = age; this.id = id; this.score = score; } running() { console.log(this.name + "running~"); } eating() { console.log(this.name + "eating~"); } studying() { console.log(this.name + "studying~"); } }
-
再定义一个Teacher类
class Teacher { constructor(name, age, title) { this.name = name; this.age = age; this.title = title; } running() { console.log(this.name + "running~"); } eating() { console.log(this.name + "eating~"); } teaching() { console.log(this.name + "teaching"); } }
-
我们发现两个类之间有许多重复的属性和方法, 我们可以定义一个父类, 使相同的属性和方法放在父类中
// 定义一个父类, 将相同的属性方法添加到父类中 class Person { constructor(name, age) { this.name = name; this.age = age; } running() { console.log(this.name + "running~"); } eating() { console.log(this.name + "eating~"); } }
-
子类再使用extends关键字, 可以快速实现继承
// 让Student继承自Person class Student extends Person { constructor(id, score) { // super关键字马上会讲到 这里是调用父类构造函数, 将属性传进去 super(name, age); this.id = id; this.score = score; } studying() { console.log(this.name + "studying~"); } } // 让Teacher继承自Person class Teacher extends Person { constructor(title) { // super关键字马上会讲到 这里是调用父类构造函数, 将属性传进去 super(name, age); this.title = title; } teaching() { console.log(this.name + "teaching~"); } }
-
这样我们就实现了子类Student和Teacher继承自Person,子类均可访问父类或自身的属性和方法
var stu1 = new Student("kaisa", 18, 11, 99); console.log(stu1); // Student {name: 'kaisa', age: 18, id: 11, score: 99} stu1.eating(); // kaisa eating~ stu1.studying(); // kaisa studying~ var tec1 = new Teacher("coder", 22, "数学"); console.log(tec1); // Teacher {name: 'coder', age: 22, title: '数学'} tec1.running(); // coder running~ tec1.teaching(); // coder teaching~
2.2 super关键字
Class为我们的方法中还提供了super关键字:
- 执行 super.method(…) 来调用一个父类方法
- 执行 super(…) 来调用一个父类constructor(只能在constructor中使用)
我们会发现在上面的代码中我使用了一个super关键字,这个super关键字有不同的使用方式:
- 注意:在子(派生)类的构造函数中使用this或者返回(return)默认对象之前,必须先通过super调用父类的构造函数!
- super的使用位置有三个:子类的构造函数、实例方法、静态方法(类方法);
位置一: 上面我们以及在子类构造函数中使用过了
位置二: 实例方法
class Animal {
running() {
console.log("running");
}
eating() {
console.log("eating");
}
}
class Dog extends Animal {
// 如果子类对继承过来父类的running方法不满足
// 重新实现父类方法也称之为, 父类方法的重写
running() {
// 比如父类的running方法只满足了我们一部分需求
console.log("未满足的一部分需求");
// 再通过super调用父类方法
super.running();
}
}
var dog = new Dog();
dog.running(); // 未满足的一部分需求 running
位置三: 静态方法
class Animal {
static sleep() {
console.log("睡觉");
}
}
class Dog extends Animal {
static sleep() {
console.log("趴着");
super.sleep();
}
}
// 子类的静态方法中调用父类的静态方法
Dog.sleep();
2.3 继承内置类
我们也可以让我们的类继承自内置类,比如Array:
-
我们可以继承自内置类之后, 对内置类进行一些扩展, 例如对Array中扩展一个获取数组中最后一个元素的方法
// 创建一个新的类, 继承自Array进行扩展 class MYArray extends Array { // 扩展一个方法: 获取数组的最后一个元素 lastItem() { return this[this.length - 1]; } // 可以使用get方法扩展成属性: 获取数组的第一个元素 get firstItem() { return this[0]; } } var arr = new MYArray(10, 20, 30, 40); console.log(arr.lastItem()); // 40 console.log(arr.firstItem); // 10
-
当然我们也可以直接对Array进行扩展
// 直接通过原型对象对Array进行扩展 Array.prototype.lastItem = function() { return this[this.length - 1] } var arr = new Array(10, 20, 30, 40); console.log(arr.lastItem()); // 40
3.类的混入(了解)
JavaScript的类只支持单继承:也就是只能有一个父类
class Animal {
running() {
console.log("running")
}
}
class Flyer {
flying() {
console.log("flying")
}
}
// Bird这个类想要同时继承Animal, Flyer在JavaScript中是不允许的
class Bird extends Animal, Flyer{}
那么在开发中我们我们需要在一个类中添加更多相似的功能时,应该如何来做呢?
- 这个时候我们可以使用混入(mixin);
// 传入一个类
function mixinAnimal(BaseClass) {
// 返回一个新创建的类继承自传入的那个类, 并在新创建的类中添加running方法
return class extends BaseClass {
running() {
console.log("running~");
}
};
}
// 传入一个类
function mixinFlyer(BaseClass) {
// 返回一个新创建的类继承自传入的那个类, 并在新创建的类中添加flying方法
return class extends BaseClass {
flying() {
console.log("flying");
}
};
}
class Bird {
eating() {
console.log("eating");
}
}
// 创建第一个新的类继承自Bird,并且添加方法running, 在创建第二个类,继承自第一个创建的类, 并且添加flying方法
// 再创建第三个类, 接收第二个创建的类, 此时第三个类以及有三个类的方法了
class newBird extends mixinFlyer(mixinAnimal(Bird)) {}
var bird = new newBird();
bird.running();
bird.flying();
bird.eating();
这种写法和react的高阶组件中的写法是非常相似的, 建议了解一下这种思想
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/120138.html