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 相关文章推荐
javascript实现unicode和字符的互相转换
Jul 18 Javascript
jquery 学习笔记一
Apr 07 Javascript
JS维吉尼亚密码算法实现代码
Nov 09 Javascript
js下拉菜单语言选项简单实现
Sep 23 Javascript
对js中回调函数的一些看法
Aug 29 Javascript
EsLint入门学习教程
Feb 17 Javascript
Vue.js开发环境快速搭建教程
Mar 17 Javascript
Web前端框架Angular4.0.0 正式版发布
Mar 28 Javascript
vue-cli之router基本使用方法详解
Oct 17 Javascript
让axios发送表单请求形式的键值对post数据的实例
Aug 11 Javascript
layUI实现前端分页和后端分页
Jul 27 Javascript
jquery检测上传文件大小示例
Apr 26 jQuery
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
如何使用Linux的Crontab定时执行PHP脚本的方法
2011/12/19 PHP
php中simplexml_load_string使用实例分享
2014/02/13 PHP
destoon安装出现Internal Server Error的解决方法
2014/06/21 PHP
php实现的统计字数函数定义与使用示例
2017/07/26 PHP
保证JavaScript和Asp、Php等后端程序间传值编码统一
2009/04/17 Javascript
Pro JavaScript Techniques学习笔记
2010/12/28 Javascript
jquery插件制作 自增长输入框实现代码
2012/08/17 jQuery
javascript小数四舍五入多种方法实现
2012/12/23 Javascript
JavaScript代码编写中各种各样的坑和填坑方法
2014/06/06 Javascript
js阻止事件追加的具体实现
2014/10/15 Javascript
jQuery选择器用法实例详解
2015/12/17 Javascript
你所未知的3种Node.js代码优化方式
2016/02/25 Javascript
老司机带你解读jQuery插件开发流程
2016/05/16 Javascript
Node.js返回JSONP详解
2016/05/18 Javascript
微信小程序 两种为对象属性赋值的方式详解
2017/02/23 Javascript
node koa2实现上传图片并且同步上传到七牛云存储
2017/07/31 Javascript
纯js代码生成可搜索选择下拉列表的实例
2018/01/11 Javascript
Vue隐藏显示、只读实例代码
2018/07/18 Javascript
vue中使用elementUI组件手动上传图片功能
2019/12/13 Javascript
解决vue动态路由异步加载import组件,加载不到module的问题
2020/07/26 Javascript
Python写的PHPMyAdmin暴力破解工具代码
2014/08/06 Python
Python获取文件ssdeep值的方法
2014/10/05 Python
Python内置模块ConfigParser实现配置读写功能的方法
2018/02/12 Python
python奇偶行分开存储实现代码
2018/03/19 Python
Python利用ORM控制MongoDB(MongoEngine)的步骤全纪录
2018/09/13 Python
numpy给array增加维度np.newaxis的实例
2018/11/01 Python
Pycharm之快速定位到某行快捷键的方法
2019/01/20 Python
python3实现钉钉消息推送的方法示例
2019/03/14 Python
python树的同构学习笔记
2019/09/14 Python
python多线程案例之多任务copy文件完整实例
2019/10/29 Python
python读取图像矩阵文件并转换为向量实例
2020/06/18 Python
学生请假条
2014/04/11 职场文书
2014年销售内勤工作总结
2014/12/01 职场文书
2015国庆节宣传语
2015/07/14 职场文书
超市主管竞聘书
2015/09/15 职场文书
pytorch显存一直变大的解决方案
2021/04/08 Python