ES6 类(Class)的继承(extends)和自定义存(setter)取值(getter)详解
来源:互联网 发布:我的世界暮色恶魂js 编辑:程序博客网 时间:2024/05/06 11:17
转载请注明预见才能遇见的博客:http://my.csdn.net/
原文地址:http://blog.csdn.net/pcaxb/article/details/53784309
ES6 类(Class)的继承(extends)和自定义存(setter)取值(getter)详解
ES6的Class之间可以通过extends关键字实现继承,这比ES5的通过修改原型链实现继承,要简单很多,这也是平常大多数面向对象语言的方式。1.类的super方法
子类必须在constructor方法中调用super方法,否则新建实例时会报错。如果子类在constructor方法中使用了this初始化实例属性,调用super方法必须放到this初始化实例属性的前面。这是因为子类没有自己的this对象,而是继承父类的this对象,然后对其进行加工。如果不调用super方法,子类就得不到this对象。
//ExtendStu.js//正确 构造constructor(name, age, height) { super(name,age); this.height = height;}//错误 构造constructor(name, age, height) { this.height = height; super(name,age); //SyntaxError: 'this' is not allowed before super()}ES5的继承,实质是先创造子类的实例对象this,然后再将父类的方法添加到this上面(Parent.apply(this))。ES6的继承机制完全不同,实质是先创造父类的实例对象this(所以必须先调用super方法),然后再用子类的构造函数修改this。如果子类没有定义constructor方法,这个方法会被默认添加。
(1)super作为函数时,指向父类的构造函数。super()只能用在子类的构造函数之中,用在其他地方就会报错。
注意:super虽然代表了父类ExtendStuParent的构造函数,但是返回的是子类ExtendStu的实例,即super内部的this指的是ExtendStu,因此super()在这里相当于ExtendStuParent.prototype.constructor.call(this)。
(2)super作为对象时,指向父类的原型对象。
toString(){ return "name:"+ super.getName() + " age:"+this.age + " height:"+this.height;}上面代码中,子类ExtendStu当中的super.getName(),就是将super当作一个对象使用。这时,super指向ExtendStuParent.prototype,所以super.getName()就相当于ExtendStuParent.prototype.getName()。
由于super指向父类的原型对象,所以定义在父类实例上的属性,是无法通过super调用的。
toString(){ console.log(super.age);//undefined return "name:"+ super.getName() + " age:"+this.age + " height:"+this.height;}age是父类的实例属性,子类通过是super不能调用
//ExtendStuParent.jsExtendStuParent.prototype.width = '30cm';//ExtendStu.jstoString(){ console.log(super.age);//undefined console.log(super.width);//30cm return "name:"+ super.getName() + " age:"+this.age + " height:"+this.height;}width定义在父类的原型对象上,所以width可以通过super取到。
ES6 规定,通过super调用父类的方法时,super会绑定子类的this。由于绑定子类的this,所以如果通过super对某个属性赋值,这时super就是this,赋值的属性会变成子类实例的属性。
// 构造constructor(name, age, height) { super(name,age); this.height = height; //父类和子类都有实例属性name,但是在toString方法调用getName的时候, //返回的是子类的的数据 this.name = 'LiSi 子类'; // this.color = 'black'; super.color = 'white'; console.log(super.color);//undefined console.log(this.color);//black}最后,由于对象总是继承其他对象的,所以可以在任意一个对象中,使用super关键字。
import ExtendStuParent from './ExtendStuParent';//父类import ExtendStu from './ExtendStu';//子类let extendStu = new ExtendStu('LiSi',12,'170cm');console.log(extendStu.toString());console.log(extendStu instanceof ExtendStuParent);//trueconsole.log(extendStu instanceof ExtendStu);//true说明;ExtendStu继承ExtendStuParent之后,ExtendStu创建的对象同时是ExtendStu和ExtendStuParent两个类的实例。
2.类的prototype属性和__proto__属性
Class作为构造函数的语法糖,同时有prototype属性和__proto__属性,因此同时存在两条继承链。
(1)子类的__proto__属性,表示构造函数的继承,总是指向父类。
(2)子类prototype属性的__proto__属性,表示方法的继承,总是指向父类的prototype属性。
console.log(ExtendStu.__proto__ === ExtendStuParent);//trueconsole.log(ExtendStu.prototype.__proto__ === ExtendStuParent.prototype);//true//继承的原理// ExtendStu的实例继承ExtendStuParent的实例Object.setPrototypeOf(ExtendStu.prototype, ExtendStuParent.prototype);// ExtendStu的实例继承ExtendStuParent的静态属性Object.setPrototypeOf(ExtendStu, ExtendStuParent);作为一个对象,子类(ExtendStu)的原型(__proto__属性)是父类(ExtendStuParent);作为一个构造函数,子类(ExtendStu)的原型(prototype属性)是父类的实例。
console.log(ExtendStuParent.__proto__ === Function.prototype);//trueconsole.log(ExtendStuParent.prototype.__proto__ === Object.prototype);//trueExtendStuParent作为一个基类(即不存在任何继承),就是一个普通函数,所以直接继承Funciton.prototype。但是,ExtendStuParent调用后返回一个空对象(即Object实例),所以ExtendStuParent.prototype.__proto__指向构造函数(Object)的prototype属性。
console.log(Object.getPrototypeOf(ExtendStu) === ExtendStuParent );//trueObject.getPrototypeOf方法可以用来从子类上获取父类。因此,可以使用这个方法判断,一个类是否继承了另一个类。
3.实例的__proto__属性
子类实例的__proto__属性的__proto__属性,指向父类实例的__proto__属性。也就是说,子类的原型的原型,是父类的原型。
class A{}class B extends A{}let a = new A();let b = new B();console.log(b.__proto__ === a.__proto__);//falseconsole.log(b.__proto__.__proto__ === a.__proto__);//true因此,通过子类实例的__proto__.__proto__属性,可以修改父类实例的行为。
b.__proto__.__proto__.getName = ()=>{ console.log('给父类添加一个函数');};b.getName();//给父类添加一个函数a.getName();//给父类添加一个函数说明:使用子类的实例给父类添加getName方法,由于B继承了A,所以B和A中都添加了getName方法。
4.原生构造函数的继承
原生构造函数是指语言内置的构造函数,通常用来生成数据结构。ECMAScript的原生构造函数大致有下面这些。
Boolean()
Number()
String()
Array()
Date()
Function()
RegExp()
Error()
Object()
以前,这些原生构造函数是无法继承的,比如,不能自己定义一个Array的子类。
function MyArray() { Array.apply(this, arguments);}MyArray.prototype = Object.create(Array.prototype, { constructor: { value: MyArray, writable: true, configurable: true, enumerable: true }});上面代码定义了一个继承Array的MyArray类。但是,这个类的行为与Array完全不一致。
var colors = new MyArray();colors[0] = "red";colors.length // 0colors.length = 0;colors[0] // "red"之所以会发生这种情况,是因为子类无法获得原生构造函数的内部属性,通过Array.apply()或者分配给原型对象都不行。原生构造函数会忽略apply方法传入的this,也就是说,原生构造函数的this无法绑定,导致拿不到内部属性。ES5是先新建子类的实例对象this,再将父类的属性添加到子类上,由于父类的内部属性无法获取,导致无法继承原生的构造函数。比如,Array构造函数有一个内部属性[[DefineOwnProperty]],用来定义新属性时,更新length属性,这个内部属性无法在子类获取,导致子类的length属性行为不正常。
ES6允许继承原生构造函数定义子类,因为ES6是先新建父类的实例对象this,然后再用子类的构造函数修饰this,使得父类的所有行为都可以继承。下面是一个继承Array的例子。
//ExtendsArray.jsclass ExtendsArray extends Array{}let extendsArray = new ExtendsArray();console.log("=====ExtendsArray=====");extendsArray[0] = '数据1';console.log(extendsArray.length);上面代码定义了一个ExtendsArray类,继承了Array构造函数,因此就可以从ExtendsArray生成数组的实例。这意味着,ES6可以自定义原生数据结构(比如Array、String等)的子类,这是ES5无法做到的。上面这个例子也说明,extends关键字不仅可以用来继承类,还可以用来继承原生的构造函数。因此可以在原生数据结构的基础上,定义自己的数据结构。
5.Class的取值函数(getter)和存值函数(setter)
取值函数(getter)和存值函数(setter)可以自定义赋值和取值行为。当一个属性只有getter没有setter的时候,我们是无法进行赋值操作的,第一次初始化也不行。如果把变量定义为私有的(定义在类的外面),就可以只使用getter不使用setter。
//GetSet.jslet data = {};class GetSet{ // 构造 constructor() { this.width = 10; this.height = 20; } get width(){ console.log('获取宽度'); return this._width; } set width(width){ console.log('设置宽度'); this._width = width; } //当一个属性只有getter没有setter的时候,我们是无法进行赋值操作的,第一次初始化也不行。 //bundle.js:8631 Uncaught TypeError: Cannot set property width of #<GetSet> which has only a getter get data(){ return data; } //如果把变量定义为私有的,就可以只使用getter不使用setter。}let getSet = new GetSet();console.log("=====GetSet=====");console.log(getSet.width);getSet.width = 100;console.log(getSet.width);console.log(getSet._width);console.log(getSet.data);//存值函数和取值函数是设置在属性的descriptor对象上的。var descriptor = Object.getOwnPropertyDescriptor( GetSet.prototype, "width");console.log("get" in descriptor);//trueconsole.log("set" in descriptor);//true如果把上面的get和set改成以下形式,不加下划线报Uncaught RangeError: Maximum call stack size exceeded错误。这是因为,在构造函数中执行this.name=name的时候,就会去调用set name,在set name方法中,我们又执行this.name = name,进行无限递归,最后导致栈溢出(RangeError)。
get width(){ console.log('获取宽度'); return this.width;}set width(width){ console.log('设置宽度'); this.width = width;}说明:以上width的getter和setter只是给width自定义存取值行为,开发者还是可以通过_width绕过getter和setter获取width的值。
6.new.target属性
new是从构造函数生成实例的命令。ES6为new命令引入了一个new.target属性,(在构造函数中)返回new命令作用于的那个构造函数。如果构造函数不是通过new命令调用的,new.target会返回undefined,因此这个属性可以用来确定构造函数是怎么调用的。
class Targettu{ // 构造 constructor() { if(new.target !== undefined){ this.height = 10; }else{ throw new Error('必须通过new命令创建对象'); } }}//或者(判断是不是通过new命令创建的对象下面这种方式也是可以的)class Targettu{ // 构造 constructor() { if(new.target === Targettu){ this.height = 10; }else{ throw new Error('必须通过new命令创建对象'); } }}let targettu = new Targettu();//Uncaught Error: 必须通过new命令创建对象//let targettuOne = Targettu.call(targettu);需要注意的是,子类继承父类时,new.target会返回子类。通过这个原理,可以写出不能独立使用、必须继承后才能使用的类。
// 构造constructor(name,age) { //用于写不能实例化的父类 if (new.target === ExtendStuParent) { throw new Error('ExtendStuParent不能实例化,只能继承使用。'); } this.name = name; this.age = age;}
7.Mixin模式的实现
Mixin模式指的是,将多个类的接口“混入”(mix in)另一个类。它在ES6的实现如下。
//MixinStu.jsexport default function mix(...mixins) { class Mix {} for (let mixin of mixins) { copyProperties(Mix, mixin); copyProperties(Mix.prototype, mixin.prototype); } return Mix;}function copyProperties(target, source) { for (let key of Reflect.ownKeys(source)) { if ( key !== "constructor" && key !== "prototype" && key !== "name" ) { let desc = Object.getOwnPropertyDescriptor(source, key); Object.defineProperty(target, key, desc); } }}上面代码的mix函数,可以将多个对象合成为一个类。使用的时候,只要继承这个类即可。
class MixinAll extends MixinStu(B,Serializable){ // 构造 constructor(x,y,z) { super(x,y); this.z = z; }}更多的资料:点击打开链接 点击打开链接
Class的继承(extends)和自定义存值(setter)取值(getter)的整个案例:
//ExtendStuParent.jsconst ExtendStuParent = class ExtendStuParent{ name;//定义name age;//定义age // 构造 constructor(name,age) { //用于写不能实例化的父类 if (new.target === ExtendStuParent) { throw new Error('ExtendStuParent不能实例化,只能继承使用。'); } this.name = name; this.age = age; } getName(){ return this.name; } getAge(){ return this.age; }};ExtendStuParent.prototype.width = '30cm';export default ExtendStuParent;//ExtendStu.jsimport ExtendStuParent from './ExtendStuParent';export default class ExtendStu extends ExtendStuParent{ // 构造 constructor(name, age, height) { super(name,age); this.height = height; //父类和子类都有实例属性name,但是在toString方法调用getName的时候, //返回的是子类的的数据 this.name = 'LiSi 子类'; // this.color = 'black'; super.color = 'white'; console.log(super.color);//undefined console.log(this.color);//black } toString(){ console.log(super.age);//undefined console.log(super.width);//30cm return "name:"+ super.getName() + " age:"+this.age + " height:"+this.height; }}
参考资料:点击打开链接
ES6 类(Class)的继承(extends)和自定义存(setter)取值(getter)详解
博客地址:http://blog.csdn.net/pcaxb/article/details/53784309
下载地址:http://download.csdn.net/detail/pcaxb/9717587
0 0
- ES6 类(Class)的继承(extends)和自定义存(setter)取值(getter)详解
- idea 自定义 getter和setter
- ES6 getter and setter
- Socket类的getter和setter方法
- Socket类的getter和setter方法
- lombok的@Getter和@Setter
- JS的getter和setter
- jQuery的getter和setter
- es6中类和extends的本质
- getter和setter方法原理详解
- 【ES6】class的继承
- Socket类的getter和setter方法(1)
- Socket类的getter和setter方法(2)
- 【java】类中使用getter和setter的优势
- 在【ES6】的【Class】中编写属性的取值函数【get】和存值函数【set】时的三点注意事项
- Ruby里getter和setter的写法
- Struts2的getter()和setter()方法
- Objective-C的setter和getter
- EventBus v 3.0 使用和源码分析
- android studio 2.2.3 ndk 添加 C 和 C++ 代码
- Android实用技巧.动画效果(二)
- windows下oracle的本地、远程导入导出
- 设计师谈配色 配色方案 WEB
- ES6 类(Class)的继承(extends)和自定义存(setter)取值(getter)详解
- 使用ajax提交文件流对象
- 网易蜂巢ubuntu16.04 安装Tomcat7
- 关于IOS中压缩图片
- feature friends and family right
- E2E dm8168 相关帖子整理
- Java获取文件大小
- Linux 死锁检测模块 Lockdep 简介——转自魅族内核团队,对死锁检测认识上升到新高度
- gedit 使用