es6 Class语法糖 , 它甜了什么?
如果你还不熟悉 es5 中几种继承方式 , 建议你可以回顾一下高程 或者 我的上一篇文章 js中继承
本文基于此es5的继承方式来讲解 .
继承什么 ?
-
子类实例继承父类实例的属性和方法
-
原型链继承
-
构造函数作为对象, 构造函数的属性, 即静态方法继承
回顾继承方法
寄生组合式继承是结合多种继承方法 的最完美的继承方法 .
function extend(subClass,superClass){
var prototype = object(superClass.prototype);//创建对象
prototype.constructor = subClass;//增强对象
subClass.prototype = prototype;//指定对象
}
function Father(name){
this.name = name;
}
Father.prototype.sayName = function(){
alert(this.name);
};
function Son(name,age){
Father.call(this,name);//继承实例属性,第一次调用Father()
this.age = age;
}
//Son.prototype = new Father();
//不创建新的父类实例而是用extend完成
extend(Son,Father);
要点
Father.call(this,name)如同在子类混入父类的内容 , 完成了实例上属性的继承extend(Son,Father)在创建不执行父类构造函数的情况下, 完成了原型链的继承
Class的甜
-
子类构造器继承时 , 必须先调用
super()(下文会详细分析 , 此时的super为 父类原型上的constructor) , 否则报错 .super中的this指向子类的实例 , 聪明的你应该发现了 , 它就是在做Father.call(this,name)做的事情
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
}
class ColorPoint extends Point {
constructor(x, y, color) {
super(x, y);
this.color = color;
}
}
-
自动完成原型链继承
class A { getName(){ console.log('xing'); } } class B extend A { } let me = new B(); me.getName(); // xing -
自动完成了静态方法的继承
//父类的静态方法,也会被子类继承。 class A { static hello() { console.log('hello world'); } } class B extends A { } B.hello() // hello world两条原型链
class不但完成了原型链上的继承 , 也完成了对类静态方法的继承 . 实际上 , 他们都是通过原型链来完成的 .
- 子类的
__proto__属性,表示构造函数的继承,总是指向父类。 - 子类
prototype属性的__proto__属性,表示实例的继承,总是指向父类的prototype属性。
- 子类的
//如同通过以下代码实现
class A {
}
class B {
}
// B 的实例继承 A 的实例
Object.setPrototypeOf(B.prototype, A.prototype);
// B 继承 A 的静态属性
Object.setPrototypeOf(B, A);
const b = new B();
Object.setPrototypeOf是es6 对象的新api, 可以为对象设置原型 , 如同执行以下代码
Object.setPrototypeOf = function (obj, proto) {
obj.__proto__ = proto;
return obj;
}
es5中的extend函数是生成一个原型是父类的原型对象的对象 , 替换了原来子类的原型对象 .
es6的方法Object.setPrototypeOf就是对原来子类的原型对象进行操作 , 使子类的原型对象的原型是父类的原型对象
所以他们完成的功能是一样 , 只不过Object.setPrototypeOf在原本对象上操作的方式更加优雅 .
super
摘自阮一峰老师 es6入门
我们先了解super的规则,class中会涉及
es6中新增了一个 super关键字 , 在对象的方法中 , super指向对象的原型
const proto = {
foo: 'hello'
};
const obj = {
foo: 'world',
find() {
return super.foo;//super是动态的 执行寻找当前原型
}
};
Object.setPrototypeOf(obj, proto);
obj.find() // "hello"
注意,super关键字表示原型对象时,只能用在对象的方法之中,用在其他地方都会报错。
// 报错
const obj = {
foo: super.foo
}
// 报错
const obj = {
foo: () => super.foo
}
// 报错
const obj = {
foo: function () {
return super.foo
}
}
上面三种super的用法都会报错,因为对于 JavaScript 引擎来说,这里的super都没有用在对象的方法之中。第一种写法是super用在属性里面,第二种和第三种写法是super用在一个函数里面,然后赋值给foo属性。目前,只有对象方法的简写法可以让 JavaScript 引擎确认,定义的是对象的方法。
JavaScript 引擎内部,super.foo等同于Object.getPrototypeOf(this).foo(属性)或Object.getPrototypeOf(this).foo.call(this)(方法)。
const proto = {
x: 'hello',
foo() {
console.log(this.x);
},
};
const obj = {
x: 'world',
foo() {
super.foo();
}
}
Object.setPrototypeOf(obj, proto);
obj.foo() // "world"
上面代码中,super.foo指向原型对象proto的foo方法,但是绑定的this却还是当前对象obj,因此输出的就是world。
const proto = {
};
const obj = {
setNum() {
super.num = 1
}
};
Object.setPrototypeOf(obj, proto);
obj.setNum();
console.log(proto.num,obj.num);//undefined 1
上面代码中, 我们setNum 方法中给super 的num属性 , 但是并非我们所预设的情况 , super指向的是 obj 或者说它在 setNum函数中等于this
//接上面代码
const obj2 = {
};
obj.setNum.call(obj2);
console.log(obj2);//{num: 1}
我们可以看到 , 改变了 setNum的this指向时 , 给对象的属性赋值时的super也随之改变 , 说明此时的super指向的就是this .
class中的super
super在class中有两种形式 . 一种是做对象 , 一种是函数 . 必须明确指定是哪种 , 否则会报错 .
class中的super的行为完全符合super基本规则:
super.foo = xx 等同于 this.foo = xx .
super.foo等同于
Object.getPrototypeOf(this).foo(属性)
Object.getPrototypeOf(this).foo.call(this)(方法) ;
-
constructor(构造函数)中的super- super必须在函数
constructor函数内部最前面调用一次super()(否则报错) , 因为继承时的constructor中的this是由super()后才生成返回的 , 此时super指向父类的构造函数 , 且this被绑定为子类实例 . 一般来说 , super作为函数只在此出现 ,在其他地方用作方法会报错 . - 作对象时同类方法中
super.
- super必须在函数
-
类方法中的super
-
类方法实际是在原型对象上 , 所以
super指向的是父类的原型对象; -
调用
super上方法时 ,this指向调用者class A { a(){ console.log(this); } } class B extends A{ m(){ super.a(); } } B.prototype.m() // B.prototype对象 let b = new B(); b.m();// b对象这里说明 ,
super上的方法中的this指向调用者 -
对
super上属性赋值时 super等于this
class A { constructor() { this.x = 1; } } class B extends A { constructor() { super(); this.x = 2; super.x = 3; console.log(super.x); // undefined console.log(this.x); // 3 } } let b = new B();上面代码中,
super.x赋值为3,这时等同于对this.x赋值为3。而当读取super.x的时候,读的是A.prototype.x,所以返回undefined。 -
-
类静态方法中的super
- 静态方法实际是在子类对象上 , 所以
super指向的是父类对象; - super上方法中的
this指向当前子类 - 赋值时指向super等于this , 一般来说 也是子类 .
- 静态方法实际是在子类对象上 , 所以
通过 super 的三种操作 , 我们可以看出 , 它与原本原型链上的读取赋值操作非常相似 , 设计者非常聪明 , 通过对super的限制(或说延伸) , 达到到了与原型链行为统一的效果 .
如果你记不住几种方式, 牢牢记住super的基本行为 ,再去推断 .