深入解析 Class 的继承

本文深入探讨了ES6中Class的继承问题,特别是如何正确继承原生构造函数如Date。文章分析了原生构造函数的特殊性,即this无法绑定,并对比了ES5与ES6继承的差异。通过实例展示了如何在ES6中继承Date对象,强调了正确实现继承的关键在于理解原型链的工作原理。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

深入解析 ES6 的继承

学习,就是不断推翻原有知识体系,跳出去建构更庞大的体系的一个过程。期间越是觉得迷惑不解,越是说明你掌握了新的东西。

今天看到一篇文章,博主遇到了如何继承 Date 对象的问题。博主很优秀,讲了好几个如何实现例子,但是文章看的我很迷糊。我觉得重点不是如何去实现,而是去明白产生问题的根源,所以有了这篇博客,从我认为可以理解的角度讲一讲。

之前写了一篇博客讲了如何用 es5 实现 Class 的继承,有兴趣可以看看 ES6 Class 的原生写法

一、问题产生背景

首先,我们要知道在 ES5 中,原生构造函数是无法继承的。

ECMAScript 的原生构造函数大致有下面这些:

Boolean()
Number()
String()
Array()
Date()
Function()
RegExp()
Error()
Object()

然而 ES6 却允许使用 extends 继承原生构造函数定义子类。

二、问题的根源

ES5 与 ES6 在继承上是有区别,而又通过原生构造函数的某个特殊性质,导致他们显现不同的状态。

所以我们要先弄清楚原生构造函数的特殊性,再看继承的不同表现

(1)原生构造函数特殊性: this 无法绑定

原生构造函数无法强制绑定 this

例子:

var e = {};
Object.getOwnPropertyNames(e)
//输出:[]
Array.call(e)
Object.getOwnPropertyNames(e)		
//输出:[]

(2)ES5 与 ES6 继承的区别

1. ES5 继承

由于原生构造函数 this 无法强制绑定,我们看看 ES5 继承产生的问题

例子:

function MyArray() {
  Array.apply(this, arguments);	//发生问题
}

MyArray.prototype = Object.create(Array.prototype, {
  constructor: {
    value: MyArray,
    writable: true,
    configurable: true,
    enumerable: true
  }
});

var colors = new MyArray();
colors[0] = "red";
colors[1] = "blue";
//color: [0: "red", 1: "blue"]

colors.length = 0;
//color: [0: "red", 1: "blue"]

Array 构造函数有一个内部属性[[DefineOwnProperty]],用来定义新属性时,更新length属性,这个内部属性无法在子类获取,导致子类的length属性行为不正常。

所以原生构造函数的实例方法只能由该构造函数的实例调用

2.ES6 继承

我们再看看 ES6 的例子:

class MyArrayES6 extends Array {
	constructor() {
		super();	
	}
}

var colors = new MyArrayES6();
colors[0] = "red";
colors[1] = "blue";
//color: [0: "red", 1: "blue"]

colors.length = 0;
//color: []

那 ES6 的继承为什么有没问题呢?

原来,ES5 的继承,实质是先创造子类的实例对象 this,然后再将父类的方法添加到 this 上面(Array.apply)。

ES6 是先新建父类的实例对象 this(所以必须先调用super方法),然后再用子类的构造函数修饰 this,使得父类的所有行为都可以继承

三、如何继承 Date 对象

好了,既然问题的根源我们知道了,那解决方法就出来了。

1.ES6 extends 继承
class MyDate extends Date{
	constructor(){ super() }
}

注意:如果是用 ES6 写法然后 Babel 打包,转化后还是变成 ES5 的组合寄生式继承写法,会报错

2.模拟 ES6 继承

先创建父类的实例,使得该实例的 this 可以访问内部属性;然后修改该实例的 _proto_ 指向子类的 prototype,形成继承的效果。

继承构造函数 Array 例子:

function MyArray(){
	var _a = new Array()
	Object.setPrototypeOf(_a,MyArray.prototype)
	return _a
}
Object.setPrototypeOf(MyArray.prototype,Array.prototype)

var a = new MyArray();
a = ['red','blue'];
a.length = 1; // color:['red']

继承构造函数 Data 例子:

function MyDate(){
	var _d = new(Function.prototype.bind.apply(Date, [Date].concat(Array.prototype.slice.call(arguments))))();
	Object.setPrototypeOf(_d,MyDate.prototype)
	return _d
}
MyDate.prototype = Object.create(Date.prototype)
Object.defineProperty(MyDate.prototype,'constructor',{
	enumerable:'false',
	value:MyDate
})

var d = new MyDate()
d.getDate();	//可以调用实例方法

四、总结

1.JS 原型继承的经典写法是为了兼容 ES 3 不能修改对象 proto,要继承原生对象内置功能,应该按 ES 6 的思路写 ES 5 继承。

2.JS 并没有真正的类,都是生成特定对象的函数;这里的继承也不像其他语言上复制继承,而是靠原型链的关联特性达到继承效果。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值