先简单说明下类的出现和本质,ES6提供了更接近面向对象语言的写法,引入了Class(类)这个概念,作为对象的模板。通过class关键字,可以定义类。 但是要注意,ES6的class可以看作只是一个语法糖,它的绝大部分功能,ES5都可以做到,新的class写法只是让对象原型的写法更加清晰、更像面向对象编程的语法而已。
一、基本语法
//es6之前类实现
//Person.js
function Person(name,msg){
this.name = name;
this.msg= msg;
}
Person.prototype.toString = function (){
return (`${this.name} ${this.msg}`);
}
export {Person};
//index.js
import {Person} from './Person';
let p=new Person('哥哥','俺也要')
console.log(p.toString()) // 哥哥 俺也要
//es6后class用法
//Person.js
class Person{
// 构造
constructor(name,msg){
this.name = name;
this.msg = msg;
}
toString(){
return (`${this.name} ${this.msg}`);
}
}
export {Person};
//index.js
import {Person} from './Person';
let p=new Person('哥哥','俺也要')
console.log(p.toString()) // 哥哥 俺也要
/*在es6之前我们使用构造函数的prototype属性来添加方法,prototype在ES6的“类”上面继续存在。
事实上,类的所有方法都定义在类的prototype属性上面,我们在定义类的时候也可以通过这个方式添加方法。*/
//index.js
console.log(person.constructor === Person.prototype.constructor);//true
//index.js
Person.prototype = {
getName(){
return '哥哥';
},
getMsg(){
return '俺也要';
}
};
/*我们都知道Object.assign方法可以给对象Person动态的增加方法,而Person.prototype = {}则是覆盖对象的
方法,或者在初始化的时候添加方法。*/
//index.js
Object.assign(Person.prototype,{
getAge(){
console.log('12');
},
getClass(){
console.log('3');
}
});
二、原型链上
/*Object.keys(obj),返回一个数组,数组里是该obj可被枚举的所有属性
Object.getOwnPropertyNames(obj),返回一个数组,数组里是该obj上所有的实例属性*/
//在类内部定义的方法,ES6中它是不可枚举的,这一点与ES5的行为不一致,ES5是可以枚举的。
//ES5
console.log(Object.keys(Person.prototype));//["toString", "getAge", "getClass"]
console.log(Object.getOwnPropertyNames(Person.prototype));//["constructor", "toString", "getAge", "getClass"]
//ES6
console.log(Object.keys(Person.prototype));//["getAge", "getAge"]
console.log(Object.getOwnPropertyNames(Person.prototype));//["constructor", "toString", "getAge", "getClass"]
//_proto_
console.log(person.hasOwnProperty('name'));//true
console.log(person.hasOwnProperty('msg'));//true
console.log(person.hasOwnProperty('toString'));//false
console.log(person.__proto__.hasOwnProperty('toString'));//true
//从上面可以看出name,msg是定义在this对象上,toString定义在类上的
/*我们知道类的所有实例共享一个原型对象,所有Person的实例它们的原型都是Person.prototype,
所以每个实例的__proto__属性是相等的。也就是说,可以通过实例的__proto__属性为Class添加方法。*/
let person1 = new Person('老李','你真是个人才');
let person2 = new Person('老张','我也觉得');
person1.__proto__.getText = function (){
return "这是新增的一条信息";
};
console.log(person1.getText ());//这是新增的一条信息
console.log(person2.getText ());//这是新增的一条信息
/*class不存在变量提升,需要先定义再使用,因为ES6不会把类的声明提升到代码头部,但是ES5就不一样,
ES5存在变量提升,可以先使用,然后再定义。*/
//正确
new A();
function A(){
}//ES5可以先使用再定义,存在变量提升
//错误
new B();
class B{
}//B is not a constructor
//ES6不能先使用再定义,不存在变量提升
/*类相当于实例的原型,所有在类中定义的方法,都会被实例继承。
如果在一个方法前,加上static关键字,就表示该方法不会被实例继承,而是直接通过类来调用,这就称为“静态方法”。*/
//在静态方法包含this关键字,这个this指的是类,而不是实例,虽然类的静态方法不可被实例所继承,但是却可以被子类继承。
class Say {
static fa() {
this.fa_s();
}
static fa_s() {
console.log('hello');
}
fa_s() {
console.log('world');
}
}
class Tell extends Say{
fa_t() {
console.log('world');
}
}
Tell.fa()//hello
Tell.fa_s()//hello
Tell.fa_t()//Uncaught TypeError: Tell.fa_t is not a function
let t=new Tell
t.fa()//Uncaught TypeError: t.fa is not a function
t.fa_s()//world
t.fa_t()//world
Say.fa()//hello
Say.fa_s()//hello
let s=new Say
s.fa()//s.fa is not a function
s.fa_s() // world
//子类继承父类静态方法
class Say {
static fa() {
this.fa_s();
}
static fa_s() {
console.log('hello');
}
}
class Tell extends Say{
fa_t() {
console.log('world');
}
}
Tell.fa()//hello
console.log(Object.getOwnPropertyNames(Say.prototype))// ["constructor","fa_s"]
console.log(Object.getOwnPropertyNames(Tell.prototype))//["constructor","fa_t"]
三、继承
super这个关键字,既可以当作函数使用,也可以当作对象使用。在这两种情况下,它的用法完全不同.
第一种情况,super作为函数调用时,代表父类的构造函数。ES6 要求,子类的构造函数必须执行一次super函数。
第二种情况,super作为对象时,在普通方法中,指向父类的原型对象;在静态方法中,指向父类。
class A {
}
class B extends A {
}
B.__proto__ === A // true
B.prototype.__proto__ === A.prototype // true
//子类的_prop_属性,表示构造函数的继承,总是指向父类。
//子类prototype属性的_prop_属性,表示方法的继承,总是指向父类的prototype属性。