身后有阴影,那是因为面向太阳
ES6中的class可以看作一个语法糖,es5其实都是可以做到的,新的class语法使其更符合面向对象编程而已
区别
- 类必须使用new调用,否则会报错。ES5的构造函数是可以当做普通函数使用的
- 类的内部所有定义的方法,都是不可枚举的
- 类的静态方法也可以被子类继承
- 可以继承原生的构造函数
- ES5是先新建子类的实例对象this,再将父类的属性添加到子类上,由于父类的内部属性无法获取,导致无法继承原生的构造函数
- ES6允许继承原生构造函数定义子类,因为ES6是先新建父类的实例对象this,然后再用子类的构造函数修饰this,使得父类的所有行为都可以继承
使用ES5模拟实现ES6的class
new操作符检查函数
类必须使用new调用,否则会报错
function _checkType(obj, constructor) {
if (obj instanceof constructor) {
throw new TypeError('Cannot call a class as a function')
}
}
内部方法不可枚举
function definePorperties (target, descriptions) {
for (let descriptor of descriptions) {
descriptor.enumerable = descriptor.enumerable || false
descriptor.configurable = true
if ('value' in descriptor) {
descriptor.writable = true
}
Object.defineProperty(target, descriptor.key, descriptor)
}
}
/**
*
* @param {*} constructor 表示对应的constructor对象
* @param {*} protoDesc class内部定义的方法
* @param {*} staticDesc class内部定义的静态方法
*/
function _creatClass (constructor, protoDesc, staticDesc) {
protoDesc && definePorperties(constructor.prototype, protoDesc)
staticDesc && definePorperties(constructor, staticDesc)
return constructor
}
真正的创建class
function _checkType(obj, constructor) {
if (!(obj instanceof constructor)) {
throw new TypeError('Cannot call a class as a function')
}
}
function definePorperties (target, descriptions) {
for (let descriptor of descriptions) {
descriptor.enumerable = descriptor.enumerable || false
descriptor.configurable = true
if ('value' in descriptor) {
descriptor.writable = true
}
Object.defineProperty(target, descriptor.key, descriptor)
}
}
/**
*
* @param {*} constructor 表示对应的constructor对象
* @param {*} protoDesc class内部定义的方法
* @param {*} staticDesc class内部定义的静态方法
*/
function _creatClass (constructor, protoDesc, staticDesc) {
protoDesc && definePorperties(constructor.prototype, protoDesc)
staticDesc && definePorperties(constructor, staticDesc)
return constructor
}
const Foo = function () {
function Foo(name) {
_checkType(this, Foo) // 检查是否是new调用
this.name = name
}
_creatClass(Foo, [ // class内部定义的方法
{
key: 'say',
value: function () {
console.log(this.name);
}
}
], [
{
key: 'say',
value: function () {
console.log('static say');
console.log(this.name);
}
}
])
return Foo
}()
这个时候直接调用Foo()
使用new 操作符之后
下面截图可以看出say方法是不可枚举的
下图则可以看出静态方法say也是不可枚举的
实现继承
- 类的静态方法也可被子类继承
function _inherits(subClass, superClass) {
if (typeof superClass !== 'function' && superClass !== null) {
throw new TypeError('父类必须是函数且不为null')
}
subClass.prototype = Object.create(superClass && superClass.prototype, {
constructor: {
value: subClass,
enumerable: false,
writable: true,
configurable: true
}
})
if (superClass) {
Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass._proto_ = superClass
}
}
使用父类的实例对象this
// 返回父类的this,若为null,返回自身
function _possibleConstructorReturn(self, call) {
if (!self) {
throw new ReferenceError('this has not initialised - super() has not been called ')
}
return call && (typeof call === 'object' || typeof call === 'function') ? call : self
}
创建子类class
const Child = function (_parent) {
_inherits(child, _parent) // 继承父类原型上的属性和静态方法的继承
function Child(name, age) {
_checkType(this, Child)
// 先使用父类的实例对象this,再返回
const _this = _possibleConstructorReturn(this, (Child._proto_ || Object.getPrototypeOf(Child)).call(this, name))
_this.age = age
return _this
}
return Child
}(Foo)
执行截图,证明继承成功了
完整代码
function _checkType(obj, constructor) {
if (!(obj instanceof constructor)) {
throw new TypeError('Cannot call a class as a function')
}
}
function definePorperties (target, descriptions) {
for (let descriptor of descriptions) {
descriptor.enumerable = descriptor.enumerable || false
descriptor.configurable = true
if ('value' in descriptor) {
descriptor.writable = true
}
Object.defineProperty(target, descriptor.key, descriptor)
}
}
/**
*
* @param {*} constructor 表示对应的constructor对象
* @param {*} protoDesc class内部定义的方法
* @param {*} staticDesc class内部定义的静态方法
*/
function _creatClass (constructor, protoDesc, staticDesc) {
protoDesc && definePorperties(constructor.prototype, protoDesc)
staticDesc && definePorperties(constructor, staticDesc)
return constructor
}
const Foo = function () {
function Foo(name) {
_checkType(this, Foo) // 检查是否是new调用
this.name = name
}
_creatClass(Foo, [ // class内部定义的方法
{
key: 'say',
value: function () {
console.log(this.name);
}
}
], [
{
key: 'say',
value: function () {
console.log('static say');
console.log(this.name);
}
}
])
return Foo
}()
function _inherits(subClass, superClass) {
if (typeof superClass !== 'function' && superClass !== null) {
throw new TypeError('父类必须是函数且不为null')
}
subClass.prototype = Object.create(superClass && superClass.prototype, {
constructor: {
value: subClass,
enumerable: false,
writable: true,
configurable: true
}
})
if (superClass) {
Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass._proto_ = superClass
}
}
// 返回父类的this,若为null,返回自身
function _possibleConstructorReturn(self, call) {
if (!self) {
throw new ReferenceError('this has not initialised - super() has not been called ')
}
return call && (typeof call === 'object' || typeof call === 'function') ? call : self
}
const Child = function (_parent) {
_inherits(Child, _parent) // 继承父类原型上的属性和静态方法的继承
function Child(name, age) {
_checkType(this, Child)
// 先使用父类的实例对象this,再返回
const _this = _possibleConstructorReturn(this, (Child._proto_ || Object.getPrototypeOf(Child)).call(this, name))
_this.age = age
return _this
}
return Child
}(Foo)
class中子类调用super()的区别
super这个关键字,既可以当作函数使用,也可以当作对象使用。在这两种情况下,它的用法完全不同。
super作为函数调用时,代表父类的构造函数。ES6 要求,子类的构造函数必须执行super函数。子类没有写constructor方法,js引擎默认,帮你执行constructor(){ super() }
super作为对象时,在普通方法中,指向父类的原型对象;在静态方法中,指向父类。
这里需要注意,由于super指向父类的原型对象,所以定义在父类实例上的方法或属性,是无法通过super调用的。
ES6在继承中强制要求,必须在子类调用super,因为子类的this是由父类得来的。