[ES6系列-01]Class:面向对象的“新仇旧恨”
[原创]CoderPower
大家好,这里是码路工人有力量,我是码路工人,你们是力量。
这是公众号(码路工人有力量)开通后的第二篇,写得还是有待改进吧。
这次准备写一个关于ES6基础的短文系列,努力尽快更完。
欢迎关注分享,一起学习提高吧。
QRCode/微信订阅号二维码
今天主要聊聊JS中的面向对象即类的使用,先来看看ES5中的传统实践,再对比ES6中的便利优雅,面向未来又不忘历史。
1. ES5中的类与继承
1.1 function 是函数,也是类
先来一段例子代码
/* eg.1 * class example in ES5 */ // Class Definition function Person(name, gender) { this.name = name; this.gender = gender; Person.PCount++; } // Method Definition Person.prototype.greeting = function(){ console.log("Hi! I am " + this.name+ "."); } // JS also has static methods and properties. Person.PCount = 0; Person.GetPCount = function() { console.log("Static method called. PCount = " + Person.PCount); } // Instance var p1 = new Person("Tom", "male"); p1.greeting(); // Hi! I am Tom. var p2 = new Person("Jerry", "male"); p1.greeting(); // Hi! I am Jerry. Person.GetPCount(); // Static method called. PCount = 2
-
Person 构造函数
Person
在这里既是类定义(类名),又是构造器(构造函数)。对于CSharper或者Javaer,这多新鲜,类跟构造函数竟然是同一个东西。(所以到了ES6,写起来就舒服多了,从这里也能看出开发语言的相互学习与特性趋同趋势)构造函数:通过
new 类名
来创建一个函数的对象
实例:接收到 new 构造函数 创建出来的对象就是实例。 -
prototype 原型
关于
prototype
即原型,用与实现对象的属性继承。有很多优秀的文章介绍原型链的,这里不再展开,或以后再做单独总结。其中的关系如下:
- 构造函数.prototype === 原型
- 实例.__proto__ === 原型
- 原型.constructor === 构造函数
-
JS类也可以有静态函数和静态属性
上面也提到了,这里说的构造器就是类名。
构造器.属性名 = xxx
构造器.方法名 = foo(){}通过 构造器.属性名/方法名() 来调用。在 eg.1 中实现一个实例自动计数器。
1.2 实现好继承还需要抱团才行(组合继承)
ES5中对继承的支持比较弱,或者说面向对象支持比较弱,继承要绕弯子来实现。
继承的实现方式有:
-
通过构造函数继承
就是在 new 的时候,构造函数里用父类的构造函数调用 call/apply 来传递子类这个对象改变 this 指向。
原理上讲,就是用 call/apply,更改了父类构造函数里 this 的指向,实现了冒充继承。 -
通过原型链继承
将子类的 prototype 属性 指向一个父类的新对象:
Child.prototype = new Parent(); -
混合继承(即用构造函数又用原型链)
前两种分别有弊端所有混合才成了最佳实践。Talk is cheap, show you the code.
/* eg.2 * extension example in ES5 */ // Child Class Definition function Student(name, gender, schoolName) { Person.apply(this, arguments); this.school = schoolName; } Student.prototype = Person.prototype; // or: // Student.prototype = Object.create(Person); Student.prototype.constructor = Student; var s1 = new Student("Emily", "female", "Non-Famouse University"); s1.greeting(); // Hi! I am Emily Person.GetPCount.apply(s1) // Static method called. PCount = 3 s1.GetPCount(); // Uncaught TypeError: s1.GetPCount is not a function
在上面的 eg.2 中,注释里记录了运行结果,大家也可以把代码copy到chrome的Console里回车执行一下来查看。
很明显可以看到,Person 的属性和方法被继承了,但是静态属性和静态方法却没有,不能直接调用,不过可以绕路调用也就是仍然用Person,再用apply改变this指向子类。可以发现计数器确实增加了,也就是实例化子类时,父类里面有正常调用。
2. ES6中的类与继承
既然是ES6系列,ES5自然不是本文重点。回顾完了接下来我们看看ES6。
ES6中的类与继承主要概括为四个关键字:
- class
- constructor
- extends
- super
- 除以上之外的补充:static 关键字
2.1 终于有了属于JS自己的Class
/* eg.3 * class example in ES6 */ // Class Definition class Person { constructor(name, gender) { this.name = name; this.gender = gender; Person.PCount++; } // Methods Definition greeting() { console.log("Hello! I am " + this.name + "."); } foo() { console.log("bar"); } // mock static property static get PCount() { return Person.Count; } static set PCount(value) { Person.Count = value; } // static method static GetPCount() { console.log("Static method called in ES6. PCount = " + Person.PCount); } } // static property Person.Count = 0 // Instance const p1 = new Person("Petter", "male"); p1.greeting(); // Hello! I am Petter. const p2 = new Person("Ben", "male"); p2.greeting(); // Hello! I am Ben. Person.GetPCount(); // Static method called in ES6. PCount = 2
-
世界大同:class 关键字
与C#和java里采用了相同的定义方式:class关键字声明一个类。
类成员方法的定义,有了简便写法,即
方法名(){方法体}
,后面继续定义方法的时候也不需要加逗号之类的分隔。 -
构造函数:constructor 关键字
这样的写法看起来就舒服多了,类是类,构造函数是构造函数。
类里的构造函数是必须存在的,如果没有显式指定,那么就会生成一个默认的构造函数(如下所示)。怎么样,听起来多么熟悉的味道,对于CShaper/Javaer。
... constructor() {} ...
-
静态方法:static 关键字
增加了static关键字,可以在类内部用来声明静态方法。
静态方法的调用与之前相同,直接
类名.静态方法()
不能用static在类里定义一个静态属性,但可以用 static get/set 来模拟操作属性。
2.1 继承变得很简单
/* eg.4 * extension example in ES6 */ // Child Class Definition class Student extends Person { constructor(name, gender, schoolName) { super(name, gender); // must call [super] before use [this] this.school = schoolName; } greeting() { console.log("Hello! I am " + this.name + " and I am studying in " + this.school + "."); } } const s1 = new Student("Lily", "female", "Non-Famouse College"); s1.greeting(); // Hello! I am Lily and I am studying in Non-Famouse College. Person.GetPCount.apply(s1); // Static method called in ES6. PCount = 3 s1.GetPCount(); // Uncaught TypeError: s1.GetPCount is not a function
-
继承:extends 关键字
ES6中的类与继承其实内部还是用的prototype与constructor实现的,只是增加了语法糖,写起来就简单多了,看起来也是清晰多了。
在 eg.4 中,我们重写了父类 Person 的 greeting 方法,所以在调用时打印内容不再是父类里的内容。
-
调用/访问父类:super 关键字
通过 super 关键字,可以访问父类的属性或方法。比如在上面的 eg.4 中,重写的 greeting 方法还可以写成:
greeting() { super.greeting(); // Hello! I am Lily. console.log("Hello! I am " + this.name + " and I am studying in " + this.school + "."); }
需要注意的一点是,在子类的构造函数里,若要给属性赋值(这就用到了 this),必须要先调用 super,也就是先走父类的构造函数初始化,创建一个空对象(也就是咱们用到的 this),否则报错。
- 补充(要实例化的类):new.target 关键字
用 new.target 能区分要实例化的是哪个类,比如在实例化子类时,new.target.name 就是子类名字。
放开 class Person 的 constructor 的 console.log(new.target.name); 这句,可以确认到以下打印结果:new Person() // Person new Student() // Student
读到这里,对于 JavaScript 里的类与继承就能一笑泯恩仇了吧。
希望能有帮助,下篇再见。
作者:码路工人
公众号:码路工人有力量(Code-Power)
欢迎关注个人微信公众号 Coder-Power
一起学习提高吧~
![](https://gitee.com/Coding-Worker/picture/raw/master/2021-1-5/1609860559027-qrcode_for_gh_e1903e0c25a7_258.jpg)
posted on 2019-06-10 16:28 CoderMonkey 阅读(297) 评论(0) 编辑 收藏 举报