ES6学习15章:Class

Class基本语法

ES6引入了Class(类)的概念,作为对象的模板。通过class关键字,可以定义类。基本上,ES6的class可以看作只是一个语法糖,它的绝大部分功能,ES5都可以做到,新的Class写法只是让对象原型的写法更加清晰、更像面向对象编程的语法而已)

# ES5写法
function Point(x,y){
    this.x = x;
    this.y = y;
}
Point.prototype.toString = function(){
    return '('+this.x+','+this.y+')'
}
var p = new Point(1,2)
# ES6写法
class Point{
    constructor(x,y){
        this.x=x;
        this.y=y;
    }
    toString(){
        return '('+this.x+','+this.y+')'
    }
}
var p6= new Point(1,2)

如上,ES6的写法,Point类中,constructor代表原构造函数部分,而其他方法,则默认挂在实例的原型上。

class Point{
    //...
}
typeof Point //'function'
Point === Point.prototype.constructor  //true
  • 如上:类的数据类型就是函数,类本身就指向构造函数。构造函数的prototype属性,在ES6的类上面继续存在。事实上,类的所有方法都定义在prototype属性上面的。
class Point(){
    constructor(){
        // ...
    }
    toString(){
        // ...
    }
    toValue(){
        // ...
    }
}
# 等同于
Point.prototype={
    toString(){},
    toValue(){}
}

# constructor一致。在类实例上调用方法,实际就是调用原型上的方法
class B {}
let b = new B();
b.constructor === B.prototype.constructor // true

# 类的方法新增
class Point{
    constructor(){//....}
}
Object.assign(Point.prototype,{
    toString(){},
    toValue(){}
})

# prototype对象的constructor属性,直接指向“类”本身,这与ES5的行为是一致的。
# 类的内部定义的方法是不可枚举
class Point{
    constructor(x,y){
        // ...
    }
    toString(){// ...}
}
Object.keys(Point.prototype)  // []
Object.getOwnPropertyNames(Point.prototype)  // ["constructor","toString"]

constructor方法

constructor方法是类默认方法,通过new命令生成对象实例时,自动调用该方法。一个类必须有constructor方法,如果没有显式定义,一个空的constructor方法会被默认添加。
- constructor方法默认返回实例对象(即this),完全可以指定返回另一个对象
- 类的构造函数,必须使用new调用,否则会报错

class Point{
    constructor(){
        return Object.create(null)
    }
}
var foo = new Point()
foo instanceof Point //false,因为返回了一个新对象,而不再是默认的this

var bar  = Point()  //报错,必须用new调用

注意事项

1. 不存在变量提升
new Foo()  //ReferenceError
class Foo{//...}
2. class表达式

Class也可以使用表达式的形式定义

const MyClass = class Me{  // 这里的类名是MyClass,而Me只是在类内部定义的,指代当前类
    getClassName(){
        return Me.name;
    }
}
let inst = new MyClass();
inst.getClassName()  //Me(类自带的name属性,Me指当前类)
Me.name //ReferenceError:Me is not defined(因为Me只是类内部定义的,外部拿不到)

# 立即执行Class
let person = new class {
    constructor(name){
        this.name = name;
    }
    sayName(){
        console.log(this.name)
    }
}('张三')
person.sayName() //张三
3. 私有方法

私有方法是常见需求,但ES6不提供,只能通过变通方法模拟实现。

# 方法一(在class外部定义)
class Point{
    foo(baz){
        bar.call(this,baz)
    }
}
function bar(baz){
    return this.snaf = baz;
}

# 方法二(利用Symblo值的唯一性,外界无法调用)
const bar = Symbol('bar')
const snaf = Symbol('snaf')
export default subclassFactory({
    // 公有方法
    foo(baz){
        this[bar](baz)
    }
    // 私有方法
    [bar](baz){
        return this[snaf]=baz;
    }
})
4. 严格模式

类和模块的内部,默认就是严格模式,所以不需要使用use strict指定运行模式。(ES6实际上把整个语言升级到了严格模式)

5. name属性

ES6的class只是ES5的构造函数的一层包装,所以函数的许多我都被class继承,包括name属性。
- name属性总是返回紧跟在class关键字后的类名

class Point{}
Point.name //Point

Class的继承

  • class之间可以通过extends关键字实现继承,这比ES5通过修改原型链的方式实现继承要清晰和方便。
class ColorPoint extends Point{
    constructor(x,y,color){
        super(x,y)  //调用父类的constructor(x,y)
        this.color = color
    }
    toStrin(){
        return this.color + ' ' +super.toString(); //调用父类的toString()
    }
}

如上:
1. super关键字:表示父类的构造函数,用来新建父类的this对象
2. 子类必须在constructor 方法中调用super方法,否则新建实例时会报错。(因为子类没有自己的this对象,而是继承父类的this对象,然后对其进行加工。如果不调用super方法,子类就得不到this对象)
3. this的使用: 在子类的构造函数中,只有调用了super之后,才能使用this关键字,否则会报错。

类的prototype属性和proto属性

大多数浏览器的ES5实现之中,每一个对象都有proto属性,指向对应的构造函数的prototype属性。Class作为构造函数的语法糖,同时有prototype属性和proto属性,因此同时存在两条继承链。(注意,两条下划线__)
1. 子类的proto属性,表示构造函数的继承,总是指向父类。
2. 子类的prototype属性的proto属性,表示方法的继承,总是指向父类的prototype属性

   class A{}
   class B extends A {}
   console.log(B.__proto__ === A)  // true
   console.log(B.prototype.__proto__ == A.prototype)  // true
Extends 的继承目标
  • extends关键字后面可以跟多种类型的值
class B extends A {}
  • 上面代码中A,只要是一个有prototype属性的函数,就能被B继承。(由于函数都有prototype属性,因此A可以是任意函数)
  • Object.getPrototypeOf() 可以从子类上获取父类。(因此可以用些方法判断一个类是否继承了另一个类)
Object.getPrototypeOf(ColorPoint) === Point // true
super关键字

super 两种用法,含义不同
1. 作为函数调用时(即super(…args)),super代表父类的构造函数。
2. 作为对象调用时(即super.prop或super.method()),super代表父类。(此时,super既可以引用父类的实例的属性和方法,也可以引用父类的静态方法)

实例的proto属性

子类的原型的原型指向父类的原型。通过子类的proto.proto属性可以修改父类实例的行为

var p1 = new Point(2,3)
var p2 = new ColorPoint(2,3,'red')
p1.__proto__ === p2.__proto__.__proto__  // true
p2.__proto__ === p1.__proto__ // false
# 子类通过原型修改父类
p2.__proto__.__proto__.printName = function(){
    console.log('Ha')
}
p1.printName() //'Ha'
原生构造函数的继承

原生构造函数:语言内置的构造函数,通常用来生成数据结构。ECMAScript原生构造函数有如下:
- Boolean()
- Number()
- String()
- Array()
- Date()
- Function()
- RegExp()
- Error()
- Object()
在ES6之前,原生构造函数是无法继承的。因为子类无法获得原生构造函数的内部属性。ES6允许继承原生构造函数定义子类,因为ES6先新建父类的实例对象this,然后再用子类的构造函数修饰this,使得父类的所有行为都可以继承。

class MyArray extends Array{
    constructor(...args){
        super(...args)
    }
}
var arr = new MyArray();
arr[0] = 12;
arr.length // 1
arr.length = 0;
arr[0]  //undefined

可以在原生数据结构的基础上,定义自己的数据结构。

Class的取值函数(getter)和存值函数(setter)

与ES5一样,在Class内部可以使用get和set关键字,对某个属性设置存值函数和取值函数,拦截该属性的存取行为

class MyClass{
    constructor(){
        // ...
    }
    get prop(){
        return 'getter'
    }
    set prop(value){
        console.log('setter' + value)
    }
}
let inst = new MyClass()
inst.prop = 123
// setter 123
inst.prop  //'getter'
Class的Generator函数

在某个方法之前加上星号(*),就表示该方法是一个Generator函数

class Foo(){
    constructor(...args){
        this.args = args
    }
    * [Symbol.iterator](){
        for(let arg of this.args){
            yield arg;
        }
    }
}
for(let x of new Foo('He','Wo')){
    console.log(x)
}

上面代码中,Foo类的Symbol.iterator方法前有一个星号,表示该方法是一个Genrator函数。Symbol.iterator方法返回一个Foo类的默认遍历器,for…of循环会自动调用这个遍历器。

Class的静态方法

在一个方法前,加上static关键字,表示该方法不会被实例继承,而是直接通过类来调用,这就称为“静态方法”

class Foo{
    static classMethod(){
        return 'hello';
    }
}
Foo.classMethod() //'hello'
var foo = new Foo()
foo.classMethod() //TypeError:foo.classMethod is not a function
  • 如上,Foo类的classMethod方法前有static表示此为静态方法,不会被实例继承,只能通过Foo类调用。

父类的静态方法可以被子类继承

class Foo{
    static test(){
        return 'hello'
    }
}
class Bar extends Foo{}
Bar.test() //hello

静态方法可以在super对象上调用

class Foo{
    static test(){
        return 'hello'
    }
}
class Bar extends Foo{
    static test(){
       return super.test() + 'world'
    }
}
Bar.test() //hello world
Class的静态属性和实例属性

静态属性指的是Class本身的属性,而不是实例对象(this)的属性

# 有效写法
class Foo{}
Foo.prop = 1 //Foo.prop为静态属性
console.log(Foo.prop) //1
# 无效写法
class Foo{
    prop:2
    static prop:2
}
Foo.prop //undefined
  • 上面无效写法是因为ES6明确规定,Class内部只有静态方法,没有静态属性
    ES7中有一个静态属性的提案,目前Babel转码器支持
# 实例属性 —— 如下,myprop是实例的属性,可以在实例上读取这个属性
class MyClass{
    myProp = 42;
    constructor(){
        console.log(this.myProp) //42
    }
}
# 实例属性在ES6中写法
class Myclass{
    constructor(){
        this.myProp = 42
    }
}

# 类的静态属性
class MyClass{
    static myStaticProp = 42
    constructor(){
        console.log(MyClass.myProp) //42
    }
}
new.target属性

new是从构造函数生成实例的命令。ES6为new命令引入了一个new.target属性,返回new命令作用于的那个构造函数。如果构造函数不是通过new命令调用的,new.target会返回undefined。

function Person(name){
    if(new.target!==undefined){
        this.name = name;
    }else{
        throw new Error('必须使用new 生成实例')
    }
}
var person = new Person('张三') //正确
var notAperson = Person.call(person,'张三') //报错

Mixin模式的实现

Mixin模式指的是,将多个类的接口“混入”(mix in)另一个类。实现如下:

function mix(...mixinx){
    class Mix{}
    for(let mixin of mixins){
        copyProperties(Mix,minxins)
        copyProperties(Mix.prototype,mixin.prototype)
    }
}
function copyProperties(target,source){
    for(let key of Reflext.ownKeys(sourct)){
        if(key !=='constructor'
        && key != 'prototype'
        && key !== 'name'){
            let desc = Object.getOwnPropertyDescriptor(source,key)
            Object.defineProperty(target,key,desc)
        }
    }
}

class DistributedEdit extends mix(Loggable,Serializable){}
  • 上面代码的mix函数,可以将多个对象合成为一个类。使用的时候,只要继承这个类即可
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Sophie_U

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值