JS面向对象编程——ES6 中class的继承用法详解


Posted in Javascript onMarch 03, 2020

本文实例讲述了 ES6 中class的继承用法。分享给大家供大家参考,具体如下:

JS是一种基于对象的语言,要实现面向对象,写法跟传统的面向对象有很大的差异。ES6引入了Class语法糖,使得JS的继承更像面向对象语言的写法。

此篇博客,分为:基本介绍、Vue使用案例

基本介绍

Class可以通过extends关键字实现继承,这比ES5的通过修改原型链实现继承,要清晰和方便很多;

class Father {
 }
class Son extends Father {
}

代码定义了一个Son 类,该类通过extends关键字,继承了Father类的所有属性和方法,但是由于没有部署任何代码,所以这两个类完全一样,等于复制了一个Father类。

class Son extends Father {
     constructor (name,age,city) {
        super(name,age);//调用父类的constructor(name,age);
        this.city = city;
      }
 
      toString () { 
         return this.city+ " " +super.toString();//调用父类的toString()
      }
}

constructor方法和toString方法之中,都出现了super关键字,他在这里表示父类的构造函数,用来新建父类的this对象;

子类必须在constructor方法中调用super方法,否则新建实例时会报错,这是因为子类没有自己的this对象,而是继承父类的this对象,然后对其进行加工,如果不调用super方法,子类就得不到this对象;

class Father {  }
 
class Son extends Father {
     constructor(){ }
}
let s = new Son();
//referenceError : this is not defined

Son继承了父类Fatherm,但是他的构造函数没有调用super方法,这导致新建实例时报错;
ES5的继承,实质是先创造子类的实例对象this,然后再将父类的方法添加到this上(Parent.apply(this)),ES6的继承机制完全不同,实质是先创造父类的实例对象this(所以必须先调用super方法),然后再用子类的构造函数修改this;
如果子类没有定义constructor方法,这个方法会默认添加,也就是说,不管有没有显式定义,任何一个子类都有constructor方法。

class Son extends Father {
}
 
//等同于
class Son extends Parent {
    constructor(...args) {
    super(...args);
   }
}

另一个需要注意的是:在子类的构造函数中,只有调用super之后,才能使用this关键字,否则会报错。这是因为子类实例的构建,是基于对父类实例加工,只有super方法才能返回父类实例;

class Father {
   constructor (x,y) {
      this.x= x;
      this.y = y;
    }
}
 
class Son extends Father {
   constructor (x, y, color) {
       this.color =color ;//ReferenceError : this is not defined
      super(x,y);
       this.color = color;//正确
      }
}
 
let s = new Son(25,8,"green");
s instanceof Son //true 
s instanceof Father //true

子类的constructor方法没有调用super之前,就使用this关键字,结果报错,而放在super方法之后就是正确的;

Object.getPrototypeOf()方法用来从子类上获取父类

Object.getPrototypeOf( Son ) ===Father
//true
//因此可以用这个方法判断,一个类是否继承了另一类

super 关键字
super这个关键字,既可以当作函数使用,也可以当作对象使用,
(1)第一情况是:super当作函数调用时,代表父类的构造函数,ES6要求,子类的构造函数必须执行一个super函数;

class Father { }
 
class Son extends Father {
    constructor () {
          super();
       }
}
//子类Son的构造函数之中的super(),代表调用父类的构造函数。这是必须的,否则 JavaScript 引擎会报错。

super虽然代表了父类Father的构造函数,但是返回的是子类Son的实例,即super内部的this指向的是Son,因此super()在这里相当于Father.constructor.call(this);
而且作为函数时,super()只能用在子类的构造函数中,在其他地方会报错;

class A {
     constructor (){
        console.log(new.target.name);
      }
 }
 
class B extends A {
   constructor () {
      super();
      }
 }
  new A()//A
 new B()//B

new.target指向当前正在执行的函数,在super()执行时,他指向的是子类B的构造函数,而不是父类A的构造函数,super()内部的this指向的是B;

(2)第二种情况,super作为对象时,在普通方法中,指向父类的原型对象,在静态方法中,指向父类;

class Father{
   getName ( ) {
     return "MGT360124";
   }
}
class Son extends Father {
    constructor () {
    super();
    console.log(super.getName() ) //“MGT360124”
    }
}
let s = new Son();

子类Son中的super.p()就是将super当作一个对象使用,这时,super在普通方法中,指向Father.prototype,所以super.getName()就相当于Father.prototype.getName();//"MGT360124",由于super指向父类的原型对象,所以定义在父类实例上的方法或者属性,是无法通过super调用的;

class Father {
   constructor () {
       this.p =2
     }
}
 
class Son extends Father {
     get m ( ) {
          return super.p;
     }
     getValue ( ) {
          return super.a;
      }
}
let s = new Son();
s.m
//undefined

p是父类Father实例的属性,super.p就引用不到它

如果属性定义在父类的原型对象上,super就可以取到。

class A {}
A.prototype.x = 2;
 
class B extends A {
 constructor() {
  super();
  console.log(super.x) // 2
 }
}
 
let b = new B();

属性x是定义在A.prototype上面的,所以super.x可以取到它的值。

ES6 规定,通过super调用父类的方法时,super会绑定子类的this。

class Father {
   constructor () {
      this.x =1;//这个this指向的是Father对象的实例
   }
   print () {
      console.log(this.x);
   }
}
 
class Son extends Father {
   constructor () {
       super();
        this.x = 2;//这个this指向的是Son对象的实例
   }
     m() {
      super.print();    
     }
}
let s = new Son();
s.m();
//2

super.print()虽然调用的是Father.prototype.print(),但是Father.prototype.print()会绑定子类Son的this,导致输出的是2,而不是1,也就是说,实际上执行的是 super.print.call(this)。

如果super作为对象,用在静态方法中,这时super将指向父类,而不是父类的原型对象;

class Parent {
      static myMethod (msg) {
           console.log("static",msg);
        }
      myMethod (msg) {
          console.log("instance" ,msg);
        }
}
 
class Child extends Parent {
     static myMethod(msg) {
        super.myMethod(msg);
     }
      myMethod (msg) {
      super.myMethod(msg);
      }
 }
 
Child.myMethod(1);
//static 1
var child = new Child();
child.myMethod(2);
//instance 2

super在静态方法之中指向父类,在普通方法之中指向父类的原型对象。
注意,使用super的时候,必须显式指定是作为函数、还是作为对象使用,否则会报错。
类的prototype属性和proto属性
大多数浏览器的ES5实现之中,每一个对象都有proto属性,指向对应的构造函数的prototype属性,class作为构造函数的语法糖,同时有prototype属性和proto属性,因此同时存在两条继承链;
(1)子类的proto属性,表示构造函数的继承,总是指向父类;
(2)子类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()方法的实现:

Object.setPrototypeOf = function (obj, proto) {
obj.__proto__ = proto;
  return obj ;
}

因此

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关键字后面可以跟很多类型的值;

class B extends A{
}

只要A有一个prototype属性的函数,就能被B继承,由于函数都有prototype属性(除了Function.prototype函数),因此A可以使任意函数,下面三种情况:
(1)子类继承Object类

class A extends Object {
}
A.__proto__ === Object //true;
A.prototype.__proto__ === Object.prototype //true

这种情况就是 : A就是构造函数Object的复制,A的实例就是Object的实例
(2)不存在任何继承

class A {
}
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

//定义类
class Person{ 
	// 构造 
	constructor(x,y){ 
		this.x = x; 
		this.y = y; 
	} 
 
  //定义在类中的方法不需要添加function
	toString(){ 
		return (this.x + "的年龄是" +this.y+"岁"); 
	} 
} 
export {
	Person
};

test.vue

<template>
	<div>
		<p id="testJs"></p>
	</div>
</template>
<script>
import {Person} from './testClass.js'; 
export default {  
	data() {
		return {
		}
	},
	mounted(){
		let text=document.getElementById("testJs");
		//使用new的方式得到一个实例对象
		let person = new Person('张三',12); 
		text.innerHTML=person.toString();//张三的年龄是12岁
		console.log(typeof Person);//function 
	}
}
</script>

感兴趣的朋友可以使用在线HTML/CSS/JavaScript代码运行工具:http://tools.3water.com/code/HtmlJsRun测试上述代码运行效果。

希望本文所述对大家JavaScript程序设计有所帮助。

Javascript 相关文章推荐
js中apply方法的使用详细解析
Nov 04 Javascript
js实现select组件的选择输入过滤代码
Oct 14 Javascript
JavaScript编程学习技巧汇总
Feb 21 Javascript
JS实现图片平面旋转的方法
Mar 01 Javascript
Bootstrap编写一个同时适用于PC、平板、手机的登陆页面
Jun 30 Javascript
jQuery 插件封装的方法
Nov 16 Javascript
Vue2学习笔记之请求数据交互vue-resource
Feb 23 Javascript
详解vue项目的构建,打包,发布全过程
Nov 23 Javascript
vuejs项目打包之后的首屏加载优化及打包之后出现的问题
Apr 01 Javascript
Layui动态生成select下拉选择框不显示的解决方法
Sep 24 Javascript
javascript设计模式 ? 外观模式原理与用法实例分析
Apr 15 Javascript
vue video和vue-video-player实现视频铺满教程
Oct 30 Javascript
JS面向对象编程实现的拖拽功能案例详解
Mar 03 #Javascript
序列化模块json代码实例详解
Mar 03 #Javascript
JS常用排序方法实例代码解析
Mar 03 #Javascript
JS面向对象编程实现的Tab选项卡案例详解
Mar 03 #Javascript
JS面向对象编程基础篇(三) 继承操作实例详解
Mar 03 #Javascript
小程序接入腾讯位置服务的详细流程
Mar 03 #Javascript
vue.js this.$router.push获取不到params参数问题
Mar 03 #Javascript
You might like
Laravel5中防止XSS跨站攻击的方法
2016/10/10 PHP
PHP实现的多维数组排序算法分析
2018/02/10 PHP
PHP实现简单用户登录界面
2019/10/23 PHP
比较详细的关于javascript中void(0)的具体含义解释
2007/08/02 Javascript
使用jQuery实现的网页版的个人简历(可换肤)
2013/04/19 Javascript
js 赋值包含单引号双引号问题的解决方法
2014/02/26 Javascript
Javascript验证上传图片大小[前台处理]
2014/07/18 Javascript
JavaScript实现防止网页被嵌入Frame框架的代码分享
2014/12/29 Javascript
JS实现浏览器状态栏显示时间的方法
2015/10/27 Javascript
js实现完美兼容各大浏览器的人民币大小写相互转换
2015/10/29 Javascript
Bootstrap学习笔记之css样式设计(1)
2016/06/07 Javascript
JavaScript学习笔记整理_setTimeout的应用
2016/09/19 Javascript
jQuery动态创建元素以及追加节点的实现方法
2016/10/20 Javascript
JavaScript for循环 if判断语句(学习笔记)
2017/10/11 Javascript
JavaScript实现快速排序的方法分析
2018/01/10 Javascript
vue使用element-ui的el-input监听不了回车事件的解决方法
2018/01/12 Javascript
Vue 拦截器对token过期处理方法
2018/01/23 Javascript
小程序开发踩坑:页面窗口定位(相对于浏览器定位)(推荐)
2019/04/25 Javascript
rhythmbox中文名乱码问题解决方法
2008/09/06 Python
Android 兼容性问题:java.lang.UnsupportedOperationException解决办法
2017/03/19 Python
python+mongodb数据抓取详细介绍
2017/10/25 Python
Python切片操作深入详解
2018/07/27 Python
淘宝秒杀python脚本 扫码登录版
2019/09/19 Python
解决reload(sys)后print失效的问题
2020/04/25 Python
python输入中文的实例方法
2020/09/14 Python
使用CSS3制作饼状旋转载入效果的实例
2015/06/23 HTML / CSS
斐乐美国官方网站:FILA美国
2019/03/01 全球购物
美国在线医疗分销商:MedEx Supply
2020/02/04 全球购物
澳洲CFL商城:CHEMIST FOR LESS(中文)
2021/02/28 全球购物
房屋买卖协议书范本
2014/04/10 职场文书
堂吉诃德读书笔记
2015/06/30 职场文书
学生会宣传部竞选稿
2015/11/21 职场文书
子女赡养老人协议书
2016/03/23 职场文书
导游词之宿迁乾隆行宫
2019/10/15 职场文书
Golang实现AES对称加密的过程详解
2021/05/20 Golang
一文搞懂Python Sklearn库使用
2021/08/23 Python