JS面向对象编程——ES6 中class的继承用法详解
本文实例讲述了ES6中class的继承用法。分享给大家供大家参考,具体如下:
JS是一种基于对象的语言,要实现面向对象,写法跟传统的面向对象有很大的差异。ES6引入了Class语法糖,使得JS的继承更像面向对象语言的写法。
此篇博客,分为:基本介绍、Vue使用案例
基本介绍
Class可以通过extends关键字实现继承,这比ES5的通过修改原型链实现继承,要清晰和方便很多;
classFather{ } classSonextendsFather{ }
代码定义了一个Son类,该类通过extends关键字,继承了Father类的所有属性和方法,但是由于没有部署任何代码,所以这两个类完全一样,等于复制了一个Father类。
classSonextendsFather{ constructor(name,age,city){ super(name,age);//调用父类的constructor(name,age); this.city=city; } toString(){ returnthis.city+""+super.toString();//调用父类的toString() } }
constructor方法和toString方法之中,都出现了super关键字,他在这里表示父类的构造函数,用来新建父类的this对象;
子类必须在constructor方法中调用super方法,否则新建实例时会报错,这是因为子类没有自己的this对象,而是继承父类的this对象,然后对其进行加工,如果不调用super方法,子类就得不到this对象;
classFather{} classSonextendsFather{ constructor(){} } lets=newSon(); //referenceError:thisisnotdefined
Son继承了父类Fatherm,但是他的构造函数没有调用super方法,这导致新建实例时报错;
ES5的继承,实质是先创造子类的实例对象this,然后再将父类的方法添加到this上(Parent.apply(this)),ES6的继承机制完全不同,实质是先创造父类的实例对象this(所以必须先调用super方法),然后再用子类的构造函数修改this;
如果子类没有定义constructor方法,这个方法会默认添加,也就是说,不管有没有显式定义,任何一个子类都有constructor方法。
classSonextendsFather{ } //等同于 classSonextendsParent{ constructor(...args){ super(...args); } }
另一个需要注意的是:在子类的构造函数中,只有调用super之后,才能使用this关键字,否则会报错。这是因为子类实例的构建,是基于对父类实例加工,只有super方法才能返回父类实例;
classFather{ constructor(x,y){ this.x=x; this.y=y; } } classSonextendsFather{ constructor(x,y,color){ this.color=color;//ReferenceError:thisisnotdefined super(x,y); this.color=color;//正确 } } lets=newSon(25,8,"green"); sinstanceofSon//true sinstanceofFather//true
子类的constructor方法没有调用super之前,就使用this关键字,结果报错,而放在super方法之后就是正确的;
Object.getPrototypeOf()方法用来从子类上获取父类
Object.getPrototypeOf(Son)===Father //true //因此可以用这个方法判断,一个类是否继承了另一类
super关键字
super这个关键字,既可以当作函数使用,也可以当作对象使用,
(1)第一情况是:super当作函数调用时,代表父类的构造函数,ES6要求,子类的构造函数必须执行一个super函数;
classFather{} classSonextendsFather{ constructor(){ super(); } } //子类Son的构造函数之中的super(),代表调用父类的构造函数。这是必须的,否则JavaScript引擎会报错。
super虽然代表了父类Father的构造函数,但是返回的是子类Son的实例,即super内部的this指向的是Son,因此super()在这里相当于Father.constructor.call(this);
而且作为函数时,super()只能用在子类的构造函数中,在其他地方会报错;
classA{ constructor(){ console.log(new.target.name); } } classBextendsA{ constructor(){ super(); } } newA()//A newB()//B
new.target指向当前正在执行的函数,在super()执行时,他指向的是子类B的构造函数,而不是父类A的构造函数,super()内部的this指向的是B;
(2)第二种情况,super作为对象时,在普通方法中,指向父类的原型对象,在静态方法中,指向父类;
classFather{ getName(){ return"MGT360124"; } } classSonextendsFather{ constructor(){ super(); console.log(super.getName())//“MGT360124” } } lets=newSon();
子类Son中的super.p()就是将super当作一个对象使用,这时,super在普通方法中,指向Father.prototype,所以super.getName()就相当于Father.prototype.getName();//"MGT360124",由于super指向父类的原型对象,所以定义在父类实例上的方法或者属性,是无法通过super调用的;
classFather{ constructor(){ this.p=2 } } classSonextendsFather{ getm(){ returnsuper.p; } getValue(){ returnsuper.a; } } lets=newSon(); s.m //undefined
p是父类Father实例的属性,super.p就引用不到它
如果属性定义在父类的原型对象上,super就可以取到。
classA{} A.prototype.x=2; classBextendsA{ constructor(){ super(); console.log(super.x)//2 } } letb=newB();
属性x是定义在A.prototype上面的,所以super.x可以取到它的值。
ES6规定,通过super调用父类的方法时,super会绑定子类的this。
classFather{ constructor(){ this.x=1;//这个this指向的是Father对象的实例 } print(){ console.log(this.x); } } classSonextendsFather{ constructor(){ super(); this.x=2;//这个this指向的是Son对象的实例 } m(){ super.print(); } } lets=newSon(); s.m(); //2
super.print()虽然调用的是Father.prototype.print(),但是Father.prototype.print()会绑定子类Son的this,导致输出的是2,而不是1,也就是说,实际上执行的是super.print.call(this)。
如果super作为对象,用在静态方法中,这时super将指向父类,而不是父类的原型对象;
classParent{ staticmyMethod(msg){ console.log("static",msg); } myMethod(msg){ console.log("instance",msg); } } classChildextendsParent{ staticmyMethod(msg){ super.myMethod(msg); } myMethod(msg){ super.myMethod(msg); } } Child.myMethod(1); //static1 varchild=newChild(); child.myMethod(2); //instance2
super在静态方法之中指向父类,在普通方法之中指向父类的原型对象。
注意,使用super的时候,必须显式指定是作为函数、还是作为对象使用,否则会报错。
类的prototype属性和proto属性
大多数浏览器的ES5实现之中,每一个对象都有proto属性,指向对应的构造函数的prototype属性,class作为构造函数的语法糖,同时有prototype属性和proto属性,因此同时存在两条继承链;
(1)子类的proto属性,表示构造函数的继承,总是指向父类;
(2)子类prototype属性的proto属性,表示方法的继承,总是指向父类的prototype属性;
classA{ } classB{ } //B的实例继承A的实例 Object.setPrototypeOf(B.prototype,A.prototype); //B的实例继承A的静态属性 Object.setPrototypeOf(B,A); constb=newB();
《对象的扩展》一章中Object.setPrototypeOf()方法的实现:
Object.setPrototypeOf=function(obj,proto){ obj.__proto__=proto; returnobj; }
因此
Object.setPrototypeOf(B.prototype,A.prototype); //等同于 B.prototype.__proto__=A.prototype; Object.setPrototypeOf(B,A); //等同于 B.__proto__=A;
这两条继承链,可以理解为:作为一个对象,子类B的原型(proto属性)是父类(A);作为一个构造函数,子类B的原型对象(prototype属性)是父类的原型对象(prototype)的实例;
extends的继承目标
extends关键字后面可以跟很多类型的值;
classBextendsA{ }
只要A有一个prototype属性的函数,就能被B继承,由于函数都有prototype属性(除了Function.prototype函数),因此A可以使任意函数,下面三种情况:
(1)子类继承Object类
classAextendsObject{ } A.__proto__===Object//true; A.prototype.__proto__===Object.prototype//true
这种情况就是:A就是构造函数Object的复制,A的实例就是Object的实例
(2)不存在任何继承
classA{ } A.__proto__===Function.prototype//true A.prototype.__proto__=Object.prototype//true
这种情况是:A作为一个基类(不存在任何继承),就是一个普通的函数,所以直接继承Function.prototype。但是A调用后返回一个空对象(即Object实例),所以A.prototype.proto指向构造函数(Object)的prototype属性;
实例的proto属性
子类实例的proto属性的proto属性,指向父类实例的proto属性。也就是说,子类的原型的原型,是父类的原型。
原生构造函数的继承
原生构造函数是指语言内置的构造函数,通常用来生成数据结构。
Boolean() Number() String() Array() Date() Function() RegExp() Error() Object()
extends关键字不仅可以用来继承类,还可以用来继承原生的构造函数。因此可以在原生数据结构的基础上,定义自己的数据结构。
vue使用
testClass.js
//定义类 classPerson{ //构造 constructor(x,y){ this.x=x; this.y=y; } //定义在类中的方法不需要添加function toString(){ return(this.x+"的年龄是"+this.y+"岁"); } } export{ Person };
test.vue
感兴趣的朋友可以使用在线HTML/CSS/JavaScript代码运行工具:http://tools.jb51.net/code/HtmlJsRun测试上述代码运行效果。
更多关于JavaScript相关内容感兴趣的读者可查看本站专题:《javascript面向对象入门教程》、《JavaScript错误与调试技巧总结》、《JavaScript数据结构与算法技巧总结》、《JavaScript遍历算法与技巧总结》及《JavaScript数学运算用法总结》
希望本文所述对大家JavaScript程序设计有所帮助。
声明:本文内容来源于网络,版权归原作者所有,内容由互联网用户自发贡献自行上传,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任。如果您发现有涉嫌版权的内容,欢迎发送邮件至:czq8825#qq.com(发邮件时,请将#更换为@)进行举报,并提供相关证据,一经查实,本站将立刻删除涉嫌侵权内容。