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代码小结
Oct 14 Javascript
Javascript中产生固定结果的函数优化技巧
Jan 16 Javascript
js实现浏览器的各种菜单命令比如打印、查看源文件等等
Oct 24 Javascript
JS上传图片前实现图片预览效果的方法
Mar 02 Javascript
js实现Select列表内容自动滚动效果代码
Aug 20 Javascript
JavaScript操作XML/HTML比较常用的对象属性集锦
Oct 30 Javascript
基于JavaScript实现类似于百度学术高级检索功能
Mar 02 Javascript
前端面试题及答案整理(二)
Aug 26 Javascript
利用n 升级工具升级Node.js版本及在mac环境下的坑
Feb 15 Javascript
微信小程序中setInterval的使用方法
Sep 29 Javascript
vue引入js数字小键盘的实现代码
May 14 Javascript
一篇文章告诉你如何实现Vue前端分页和后端分页
Feb 18 Vue.js
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
关于在php.ini中添加extension=php_mysqli.dll指令的说明
2007/06/14 PHP
php去除字符串中空字符的常用方法小结
2015/03/17 PHP
jquery select(列表)的操作(取值/赋值)
2011/03/16 Javascript
AeroWindow 基于JQuery的弹出窗口插件
2011/06/27 Javascript
javascript高级程序设计第二版第十二章事件要点总结(常用的跨浏览器检测方法)
2012/08/22 Javascript
jQuery Ajax提交表单查询获得数据实例代码
2012/09/19 Javascript
js实现屏蔽默认快捷键调用自定义事件示例
2013/06/18 Javascript
jQuery中ajax的使用与缓存问题的解决方法
2013/12/19 Javascript
jquery 删除字符串最后一个字符的方法解析
2014/02/11 Javascript
实例代码讲解jquery easyui动态tab页
2015/11/17 Javascript
理解javascript中try...catch...finally
2015/12/25 Javascript
laypage分页控件使用实例详解
2016/05/19 Javascript
防止重复发送 Ajax 请求
2017/02/15 Javascript
字太多用...代替的方法(两种)
2017/03/15 Javascript
简单谈谈关于Angular Cli打包的事
2017/09/05 Javascript
浅谈react-router HashRouter和BrowserRouter的使用
2017/12/29 Javascript
vue实现微信分享功能
2018/11/28 Javascript
Nodejs异步流程框架async的方法
2019/06/07 NodeJs
使用grappelli为django admin后台添加模板
2014/11/18 Python
python 全局变量的import机制介绍
2017/09/07 Python
python监控进程脚本
2018/04/12 Python
Python中的groupby分组功能的实例代码
2018/07/11 Python
动态设置django的model field的默认值操作步骤
2020/03/30 Python
白宫黑市官网:White House Black Market
2016/11/17 全球购物
美国校园市场:OCM
2017/06/08 全球购物
空字符串(“”)和null的区别
2012/11/13 面试题
2013年大学生的自我鉴定
2013/10/24 职场文书
教师试用期自我鉴定
2014/02/12 职场文书
团队队名口号大全
2014/06/06 职场文书
党员干部群众路线教育实践活动个人对照检查材料
2014/09/23 职场文书
硕士毕业论文导师评语
2014/12/31 职场文书
中学生勤俭节约倡议书
2015/04/29 职场文书
小学总务工作总结
2015/08/13 职场文书
干货!开幕词的写作方法
2019/04/02 职场文书
JavaScript实现简单图片切换
2021/04/29 Javascript
pnpm对npm及yarn降维打击详解
2022/08/05 Javascript