ES5面向对象
使用传统的构造函数。通过call和apply将父类的constructor赋值到子类上,再对父类的原型进行遍历赋值到子类原型上。
function Person(name){
this.name = name;
}
Person.prototype.showName = function(){
console.log(this.name);
}
Person.prototype.reName = function(newName){
this.name = newName;
}
function Chinese(){
Person.call(this);
}
ES6面向对象
1. ES6 Class语法
为什么ES6要提出Class?
其实ES6并没有真正的将js变成了面向对象的语言,而是提供了新的一套语法糖,让开发者更像在写面向对象语言。
其次,在ES5中虽然也能够写继承,但是行为与内置对象表现并不一致,ES6的继承是进一步向内置对象靠齐。
ES6 Class语法说明:
- 声明的类,其实还是构造函数constructor。如果不写constructor将会默认添加一个constructor。
- 不能重复声明,会报错,相当于声明一个变量与let、const类似。
- 与ES5的构造函数行为一致,但原型里的方法不可以枚举。
Object.keys(Person.prototype)
- 共享属性写在constructor里,实例私有。
- 共享方法直接写在类里,会自动放到prototype中。
- 私有属性只能在class块之外,使用点操作符添加,可以被子类继承。
- 私有方法,在共享方法前添加
static
关键字,可以被子类继承。
class Person {
constructor(name) {
this.name = name; // 共享属性,为实例私有
}
// 共享方法,其实跟Person.prototype是一样的,只是一个语法糖而已,实例通过继承得到,不可以枚举。
showName() {
console.log(this.name);
}
static goHome(){ // 私有方法,这里面的this是指向Class的,而不是实例
console.log(`回${this.address}啦~`)
}
}
Person.address = '自贡'; // 私有属性
test:
Person.address // 自贡
Person.goHome(); // 回自贡啦~
const liz = new Person('liz');
liz.address // undefined
liz.goHome(); // liz.goHome is not a function
2. 什么是new.target
new.target
指向当前正在执行的函数,用于检查此类必须通过继承才能使用。
class Person{
constructor(){
if(new.target === Person){
throw error('Person不允许实例化,请继承后再使用');
}
}
}
3. Class的get与set
对公共属性的值进行便捷的获取和设置
class Person {
constructor() {
}
get address(){
return '自贡'
}
set address(value){
console.log(`你设置了地址${value}`);
}
}
const liz = new Person('liz');
console.log(liz.address) ; // 自贡
liz.address = '成都'; // 你设置了地址成都
4. ES6 的继承
使用extends关键字进行继承。
使用super引入父类的构造函数,并将this指向子类,Father.prototype.constructor.call(this)
。
这是因为子类自己的this对象,必须先通过父类的构造函数完成塑造,得到与父类同样的实例属性和方法,然后再对其进行加工,加上子类自己的实例属性和方法。如果不调用super方法,子类就得不到this对象。
如果父类构造函数有参数,还需要在super中传递进去。
子类依旧可以调用父类的私有方法
class Chinese extends Person{
constructor(...arg){ // 收集参数
super(...arg); // 解构参数
}
}
5. super的使用
super方法
只能在子类的构造函数中调用,引入父类的构造函数,并将this指向子类,等同于Father.prototype.constructor.call(this)
。
注: new.target
指向当前正在执行的函数。
class Person {
constructor() {
console.log(new.target.name);
}
}
class Chinese extends Person {
constructor() {
super();
}
}
new Person() // Person
new Chinese () // Chinese
super对象
-
在普通函数中调用
super指向父类的原型对象,只能调用原型里的东西。
如果调用的是方法,方法里的this指向子类的实例,如果在子类找不到此属性将会进行原型链搜索。
如果使用super去操作属性,此时super指向子类实例。这里实际实验与书本有误。class Person { constructor() { this.x = '父类的共享属性x'; } showX() { console.log('父类的共享方法,且弹出', this.x); } } Person.x = '父类的私有属性x'; Person.prototype.x = '父类的原型属性x'; class Chinese extends Person { constructor() { super(); this.x = '子类的共享属性x'; super.showX(); // 父类的共享方法,且弹出 子类的共享属性x => 子类的原型属性x => 父类的共享属性x => 父类的原型属性x console.log(super.x); // 父类的原型属性x super.x = 'super就是this,指向子类实例'; super.showX(); // 父类的共享方法,且弹出 super就是this,指向子类实例 } } Chinese.x = '子类的私有属性x'; Chinese.prototype.x = '子类的原型属性x'; new Chinese();
-
在私有方法中调用
super
将指向父类,而不是父类的原型对象。方法里的this也是子类,而不是子类实例。class Person { constructor() { this.x = '父类的共享属性x'; } static showX() { console.log('父类的私有方法,且弹出', this.x); } } Person.x = '父类的私有属性x'; Person.prototype.x = '父类的原型属性x'; class Chinese extends Person { constructor() { super(); this.x = '子类的共享属性x'; } static showX() { console.log('子类的私有方法,且弹出', this.x);// 子类的私有方法,且弹出 子类的私有属性x => 父类的私有属性x super.showX(); // 父类的私有方法,且弹出 子类的私有属性x => 父类的私有属性x } } Chinese.x = '子类的私有属性x'; Chinese.prototype.x = '子类的原型属性x'; Chinese.showX();
ES6里__proto__
指向
ES6里为了规范化 __proto__
的使用,提出了Object.setPrototypeOf(Son.prototype, Father.prototype)用于内部继承。
导致了:
- 子类的
__proto__
指向父类,而不是Function.prototype
。 - 子类prototype属性的
__proto__
表示方法的继承,指向父类的prototype。
class Person{}
class Chinese extends Person{
constructor(){
super()
}
}
console.log(Chinese.prototype.__proto__ === Person.prototype) // true
console.log(Chinese.__proto__ === Person); // true
console.log(Chinese.__proto__ === Function.prototype) // false