在 ES5 中,我们经常使用方法或者对象去模拟类的使用,并基于原型实现继承,虽然可以实现功能,但是代码并不优雅,很多人还是倾向于用 class 来组织代码,很多类库、框架创造了自己的 API 来实现 class 的功能。
ES6 时代终于有了 class(类)语法,能让我们可以用更简明的语法实现继承,也使代码的可读性变得更高,同时为以后的 JavaScript 语言版本添加更多的面向对象特征打下基础。有了 ES6 的 class 以后妈妈再也不用担心我们的代码乱七八糟了,这简直是喜大普奔的事情。ok, 我们看看神奇的 class.
一、类的定义
1.1 ES5 模拟类定义
function Person(name , age) {
this.name = name;
this.age = age;
}
Person.prototype.say = function(){return '我叫' + this.name + ', 今年' + this.age + '岁';}
var p = new Person('大彬哥',18); // Person {name: "大彬哥", age: 18}
p.say() //"我叫大彬哥, 今年 18 岁"
使用 ES5 语法定义了一个 Person 类,该类有 name 和 age 两个属性和一个原型 say 方法。
这种写法跟传统的面向对象语言(比如 C++ 和 Java)差异很大。接下来我们看下 ES6 类的写法,这个就很接近于传统面向对象语言了。如果你想了解传统面向对象语言,这里是一个好切入点。
1.2 ES6 class 类定义
class Person {constructor( name , age) {
this.name = name;
this.age = age;
}
say() {return '我叫' + this.name + ', 今年' + this.age + '岁';}
}
var p = new Person('大彬哥',18); // Person {name: "大彬哥", age: 18}
p.say() //"我叫大彬哥, 今年 18 岁"
上面代码定义了一个同样的 Person 类,constructor 方法就是构造方法,而 this 关键字则代表实例对象,这更接近传统语言的写法。
注意:
虽然引入了 class 关键字,但 ES6 中并没有真的引入类这个概念,通过 class 定义的仍然是函数:
console.log(typeof Person); // 'function'
所以说,class 仅仅是通过更简单直观的语法去实现原型链继承。这种对语言功能没有影响、但是给程序员带来方便的新语法,被称为语法糖。
二、类的传参 constructor
在类的参数传递中我们用 constructor() 进行传参。传递参数后可以直接使用 this.xxx 进行调用。
class Person {constructor(a,b){
this.a=a;
this.b=b;
}
add(){return this.a + this.b;}
}
let p = new Person(18,30);
console.log(p.add()); // 48 (18+30)
我们用 constructor 来传递参数,然后用了一个 add 方法,把参数相加。这和以前我们的函数传参方法有些不一样,所以小伙伴们要注意转换下思维。
三、静态方法
在面向对象语言中,静态方法是指不需要实例化,可以通过类名直接调用的方法,但静态方法不会继承到类实例中,因此静态方法经常用来作为工具函数。比如我们经常用的 Math.random(),我们并不需要先 new 一个 Math 然后再去用,一是如果作者那么设计 JS 一来是没必要,二是用起来太繁琐。
在使用函数模拟类时,可以像下面这样定义静态方法:
function Person(name, sex) {}
Person.walk = function() {console.log('我会走路')
}
Person.walk(); // 我会走路
var person = new Person();
person.walk(); // TypeError
在 ES6 class 类定义中,可以使用 static 关键字定义:
class Person {constructor() {}
static walk(){console.log('我会走路')
}
}
Person.walk(); // 我会走路
var person = new Person();
person.walk(); // TypeError
static 关键字是 ES6 的另一个语法糖,static 使静态方法声明也成为了一个一等公民。
于此同时,静态方法也是可以从子类中的 super 对象上调用的。
class Person {constructor() {}
static walk(){return '我会走路'}
}
class People extends Person {static walk() {return super.walk() + ', 我还会跑步';
}
}
People.walk(); //"我会走路, 我还会跑步"
四、封装与继承
封装和继承,是面向对象编程三大核心特征中非常重要的两个,封装和继承在我们实际生活中也有非常多的应用。举个例子,你去驴肉火烧店去吃饭。
老板把驴肉面和火烧一起买,起名字叫“精英驴火套餐”, 这就是封装。
而进去以后跟老板说,老板给我来个“82 年的驴火套餐”这就是继承。当然了你不仅仅能继承,还能扩展自己的功能。比如你可以跟老板说,老板再给我加一个驴板肠。说的我都饿了,不过我们还是教编程的专栏,不是开店的专栏,我们继续,看看 ES6 里面怎么玩继承。
4.1 extends
旧的原型继承有时看起来让人非常头疼。
function Child(firstName, lastName, age) {Parent.call(this, firstName, lastName)
this.age = age
}
Child.prototype = Object.create(Parent.prototype)
Child.constructor = Child
ES6 中新的 extends 关键字解决了这个问题:
class Child extends Parent {}
上面代码定义了一个 Child 类,该类通过 extends 关键字,继承了 Parent 类的所有属性和方法。
由于没有在 Child 内部写任何代码,所以这两个类完全一样,等于复制了一个 Parent 类。
之后,我们在 Child 内部加上代码:
class Child extends Parent {constructor(firstName, lastName, age) {super(firstName, lastName)
// 调用父类的 constructor(firstName, lastName)
this.age = age
}
speak(){return this.age + ' ' + super.speak();
// 调用父类的 speak()}
}
使用简介的 extends 达到继承的目的,而非杂乱的 Object.create()、.proto、Object.setPrototypeOf(),这样能让我们更顺利的扩充功能。
4.2 super
super 这个关键字,既可以当作函数使用,也可以当作对象使用。在这两种情况下,它的用法完全不同。
(1)super 作为函数调用
代表父类的构造函数,ES6 中规定,子类的构造函数必须执行一次 super 函数。
class A {}
class B extends A {constructor() {super();
}
}
上面代码中,子类 B 的构造函数之中的 super(),代表调用父类的构造函数,这是必须的,否则 JavaScript 引擎会报错。
注意,super 虽然代表了父类 A 的构造函数,但是返回的是子类 B 的实例,即 super 内部的 this 指的是 B,因此 super() 在这里相当于 A.prototype.constructor.call(this)。
(2)super 作为对象时,指向父类的原型对象。
class A {p() {return 2;}
}
class B extends A {constructor() {super();
console.log(super.p()); // 2
}
}
let b = new B();
与 Java 一样,JavaScript 也使用 extends 关键字实现继承,子类中可以通过 super 关键字调用父类:
在 constructor 里面,super 的用法是 super()。它相当于一个函数,调用它等于调用父类的 constructor。
但在普通方法里面,super 的用法是 super.prop 或者 super.method(),它相当于一个指向对象的 [[Prototype]] 的属性。
4.3 getter(取值函数)、setter(存值函数)
与 ES5 一样,在“类”的内部可以使用 get 和 set 关键字,对某个属性设置存值函数和取值函数,拦截该属性的存取行为。
class Person {constructor() {}
get prop() {return 'getter';}
set prop(value) {console.log('setter:'+value);
}
}
let p = new Person();
p.prop = 666; // setter: 666
p.prop // 'getter'
五、总结
无论学什么知识,最重要也是最基础的,要实现思想上的转变, 目前大部分框架和库,都采用了面向对象方式编程。而且在工作中,要书写中型和大型的项目也经常使用面向对象方式编程,可能大家习惯了面向过程方式编程,其实面向对象方式编程一旦习惯了,会让我开发和思路更宽阔和易于开发项目。
从学习 javascript 基础开始的时候,我们就了解了 js 中的保留字,js 中并没有用到,但是将来可能会用到的未来关键字。这些保留字中就包括:class、extends、super。这些就是为将来在 js 中支持面向对象的类机制而预留的。
果不其然,现在 ES6 语法中使用到了这些保留字,这些保留字成功升级成了关键字,可见当时 javascript 的设计者还是很有前瞻眼光的。
通过这些新的关键字,使类成为了 JS 中一个新的一等公民。但是目前为止,这些关于类的新关键字仅仅是建立在旧的原型系统上的语法糖。这样做的原因是为了保证向后兼容性。也就是,旧代码可以在不做任何 hack 的情况下,与新代码同时运行。
不过,它使代码的可读性变得更高,并且为今后版本里更多面向对象的新特性打下了基础。