有些人一分开就是一辈子
我与她一个职业,我与她不同的城市。
前言
本篇大部分的篇幅都是用来讲类的使用方法的。
建议先去看看原型原型链,js的组合寄生试继承。
他们是一脉下来的,学完了上述这些看class就会得心应手。
作文讲究的是一个凤头,猪肚,豹尾,所以精华全在开头和结尾
没办法现在很多人也像语文阅卷老师一样浮躁(虽然写的确实不带劲)
什么是类?
讲什么是类前我们说回构造函数,他是生成实例对象的传统方法
function Point(x, y) {
this.x = x;
this.y = y;
}
Point.prototype.toString = function () {
return '(' + this.x + ', ' + this.y + ')';
};
var p = new Point(1, 2);
其实等同于
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
toString() {
return '(' + this.x + ', ' + this.y + ')';
}
}
var p = new Point(1, 2);
那这么说class类本质是函数喽?那如何验证呢?
很简单,我们看他符不符合构造函数所构造的原型与原型链。
typeof Point // "function"
Point === Point.prototype.constructor // true
p.__proto__ == Point.prototype // true
p.toString == p.__proto__.toSting == Point.prototype.toString //true
//类的方法都是放在了类的原型上
p.constructor === Point.prototype.constructor // true
//Object.assign()方法可以很方便地一次向类添加多个方法。
Object.assign(Point.prototype, {
toString(){},
toValue(){}
});
constructor
一个类必须有constructor()方法,如果没有显式定义,一个空的constructor()方法会被默认添加,且在通过new命令生成对象实例时,自动调用该方法。
我们先回顾一下实例化的过程也就是new关键词做了什么?
下面是一段仿写的代码
// 仿写该函数接收的第一个参数位函数
function objectFactory() {
// 创建新对象
var obj = new Object(),
// 取出第一个参数也就是函数
Constructor = [].shift.call(arguments);
// 将新对象的__proto__指向函数的原型
obj.__proto__ = Constructor.prototype;
// 改变函数内部的this指向,使this指向新对象
var ret = Constructor.apply(obj, arguments);
// 返回新对象
return typeof ret === 'object' ? ret : obj;
};
而constructor对应的就是函数本身,也就是上面的Constructor。
如何验证?
对于上述的代码来说我们要想破坏实例的原型结构,我们只需要让ret(函数执行的返回值)是一个不是obj的新对象就可以了。
class同理
class Foo {
constructor() {
return Object.create(null);
}
}
new Foo() instanceof Foo //false
静态方法static
什么是静态方法?
我们只需要在类的方法前加上一个static,那他就变成了一个静态方法。
但同时加上这个关键字就意味着,该方法不会被实例继承(所以我们不能通过实例调用)
class Foo {
static classMethod() {
return 'hello';
}
}
Foo.classMethod() // 'hello'
var foo = new Foo();
foo.classMethod()
// TypeError: foo.classMethod is not a function
关键词super
在构造函数中使用时,super关键字将单独出现,并且必须在使用this关键字之前使用。super关键字也可以用来调用父对象上的函数。
class Polygon {
constructor(height, width) {
this.name = 'Rectangle';
this.height = height;
this.width = width;
}
sayName() {
console.log('Hi, I am a ', this.name + '.');
}
}
class Square extends Polygon {
constructor(length) {
this.height; // 这样直接 this.heigh t会报错:ReferenceError,因为 super 需要先被调用!
// 这里,它调用父类的构造函数的,
// 作为Polygon 的 height, width
super(length, length);
// 注意: 在派生的类中, 在你可以使用'this'之前, 必须先调用super()。
this.name = 'Square';
// 同时super关键词还可以调用父类的方法
console.log(super.sayName());
}
}
继承extends
Class 可以通过extends关键字实现继承
class ColorPoint extends Point {
constructor(x, y, color) {
super(x, y); // 调用父类的constructor(x, y)
this.color = color;
}
toString() {
return this.color + ' ' + super.toString(); // 调用父类的toString()
}
}
let colorPoint = new ColorPoint
上面代码定义了一个ColorPoint类,该类通过extends关键字,继承了Point类的所有属性和方法。
注意super
子类必须在constructor方法中调用super方法,否则新建实例时会报错。
这是因为子类如果想要继承父类自然就要继承他的属性,super关键词的本质就是运行父类的constructor函数,以此对子类添加父类的属性。
具体实现是在我们let colorPoint = new ColorPoint()此时class ColorPoint中的constructor的this指向为实例对象colorPoint,当执行super调用父亲的constructor的时候,就自然的为实例colorPoint添加了父类的属性。
最后父类的静态方法也会被子类继承
class A {
static hello() {
console.log('hello world');
}
}
class B extends A {
}
B.hello() // hello world
基本说完了class的一些核心用法,现在我们看下面的代码。
function Parent (name) {
this.name = name;
this.colors = ['red', 'blue', 'green'];
}
Parent.prototype.getName = function () {
console.log(this.name)
}
function Child (name, age) {
Parent.call(this, name);
this.age = age;
}
// 关键的三步
var F = function () {};
F.prototype = Parent.prototype;
Child.prototype = new F();
var child1 = new Child('kevin', '18');
console.log(child1);
这是一段组合寄生试继承的代码。
这种继承的本质就是通过call改变this指向,执行父类函数,使得子类实例本身有父类的属性。
然后通过原型链让子类的原型作为父类函数的实例,进而通过原型链找到父类原型上的方法
class继承的本质上就是组合寄生试继承
我们看一下 colorPoint,不难发现
- colorPoint中有父类的属性。
- colorPoint.proto.proto == Point.prototype
这两点与组合寄生试继承是一样的。唯一的不同是他们在子类实例上添加父类属性的方法。
对于组合寄生试继承的实质是先创造子类的实例对象this,然后再将父类的方法添加到this上面Parent.apply(this)。
class 的继承机制的实质是先将父类实例对象的属性和方法,加到this上面(所以必须先调用super方法),然后再用子类的构造函数修改this。
当然最终的目的是一样的。