1.简介
JavaScript 语言中,传统的生成实例对象的方法是通过构造函数 。
function Person(name, age) {
this.name = name
this.age = age
}
Person.prototype.say = function () {
console.log('my name is ' + this.name + ', i am ' + this.age + ' years old')
}
var person = new Person('kobe', 30)
person.say() // my name is kobe, i am 30 years old
ES6 引入了 Class (类)这个概念,通过 class 关键字可以定义类,写法和其它传统语言类似,可以看作是一个语法糖,新的 class 写法只是让对象原型的写法更加清晰、更像面向对象编程的语法。
class Person {
contructor(name, age) {
this.name = name
this.age = age
}
say() {
console.log('my name is ' + this.name + ', i am ' + this.age + ' years old')
}
}
// 对类的使用和 ES5 中使用构造函数的方法一样,直接对类使用 new 命令即可。
var person = new Person('kobe', 30)
Person 类中的 constructor 方法其实就相当于 Person 构造函数
class Person {
// ...
}
Person = Person.prototype.constructor // true
构造函数的 prototype 属性在“类”中依然存在,事实上,类的所有方法都定义在类的prototype属性上面。
class Person1 {
contructor(name, age) {
// ...
}
say() {
// ,,,
}
jump() {
// ...
}
}
// 等同于
Person.prototype = {
contructor(name, age) {
// ...
},
say() {
// ,,,
},
jump() {
// ...
}
}
// 在类的实例上面调用方法,其实就是调用原型上的方法。
class B {}
let b = new B();
b.constructor === B.prototype.constructor // true
和 ES5 不同的是,类内部所定义的方法是不可枚举的。
class Person {
contructor(name, age) {
// ...
}
say() {
// ,,,
}
}
Object.keys(Person.prototype) // []
Object.getOwnPropertyNames(Person.prototype) // ['constructor', 'say']
var Person = function () {}
Person.prototype.say() {}
Object.keys(Person.prototype) // ['say']
Object.getOwnPropertyNames(Person.prototype) // ['constructor', 'say']
类的属性名,可以采用表达式
let methodName = 'getArea';
class Square {
constructor(length) {
// ...
}
[methodName]() {
// ...
}
}
2. 严格模式
类和模块的内部,默认就是严格模式。ES6 实际上把整个语言升级到了严格模式。
3. constructor
constructor方法是类的默认方法,通过new命令生成对象实例时,自动调用该方法。一个类必须有constructor方法,如果没有显式定义,一个空的constructor方法会被默认添加.
class Point {
}
// 等同于
class Point {
constructor() {}
}
constructor方法默认返回实例对象(即this),完全可以指定返回另外一个对象。
class Foo {
constructor() {
return Object.create(null)
}
}
new Foo() instanceOf Foo // false
类必须使用new调用,否则会报错。
4.类的实例对象
与 ES5 一样,所以实例对象共享一个原型对象。
var p1 = new Point(2,3);
var p2 = new Point(3,2);
p1.__proto__ === p2.__proto__
这也意味着,可以通过实例的 proto 属性为“类”添加方法
proto 并不是语言本身的特性,这是各大厂商具体实现时添加的私有属性,虽然目前很多现代浏览器的 JS 引擎中都提供了这个私有属性,但依旧不建议在生产中使用该属性,避免对环境产生依赖。
可以使用 Object.getPrototypeOf 方法代替
另外改写原型,必须相当谨慎,不推荐使用,因为这会改变“类”的原始定义,影响到所有实例。
var p1 = new Point(2,3);
var p2 = new Point(3,2);
p1.__proto__.printName = function () { return 'Oops' };
p1.printName() // "Oops"
p2.printName() // "Oops"
var p3 = new Point(4,2);
p3.printName() // "Oops"
5. Class 表达式
与函数一样,类也可以使用表达式的形式定义。
const MyClass = class Me{
getClassName() {
return Me.name
}
}
上面代码使用表达式定义了一个类。需要注意的是,这个类的名字是MyClass而不是Me,Me只在 Class 的内部代码可用,指代当前类, Me 也可以省略
let inst = new MyClass();
inst.getClassName() // Me
Me.name // ReferenceError: Me is not defined
利用 Class 表达式,可以写出立即执行的 Class
let person = new class {
constructor(name) {
this.name = name
}
sayName() {
console.log(this.name)
}
}('张三')
person.sayName()
6.不存在变量提升
类不存在变量提升(hoist),这一点与 ES5 完全不同。
new Foo(); // ReferenceError
class Foo {}
假如存在变量提升的话,在继承类的时候就会出现问题。
let Foo = class {}
class Bar extends Foo {}
上面的代码,因为 let 命令是没有变量提升的,加入 class 提升到前面的话,这个时候 Foo 类还没有定义,就会出错了。
7.私有方法和私有属性
ES6 不提供私有方法,只能通过变通方法模拟实现。
一种做法是在命名上加以区别。
只是这种做法也仅仅就是命名上的区别,在类的外部,仍然可以访问到私有方法。
class Widget {
// 公有方法
foo(baz) {
this._bar(baz)
}
// 私有方法
_bar(baz) {
return this.snaf = baz
}
}
另一种方法就是索性将私有方法移出模块,因为模块内部的所有方法都是对外可见的。
class Widget {
foo(baz) {
bar.call(this, baz)
}
}
function bar(baz) {
return this.snaf = baz
}
还有一种方法是利用Symbol值的唯一性,将私有方法的名字命名为一个Symbol值。 因为都是Symbol值,导致第三方无法获取到它们,因此达到了私有方法和私有属性的效果。
const bar = Symbol('bar')
const snaf = Symbol('snaf')
class Widget {
// 公有方法
foo(baz) {
this.[bar](baz)
}
// 私有方法
[bar](baz) {
return this.[snaf] = baz
}
}
ES6 同样也没有私有属性,目前,有一个提案,是在属性名前加 # 号表示。当然同样的也可以应用在方法上。
8. this 的指向
9. name 属性
继承了 ES5的很多特性, 包括 name 属性
class Point {}
Point.name // "Point"
10.Class 的取值函数(getter)和存值函数(setter)
与 ES5 一样,在“类”的内部可以使用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'
和 ES5 一致,存值函数和取值函数是设置在属性的 Descriptor 对象上的。
class CustomHTMLElement {
constructor(element) {
this.element = element;
}
get html() {
return this.element.innerHTML;
}
set html(value) {
this.element.innerHTML = value;
}
}
var descriptor = Object.getOwnPropertyDescriptor(
CustomHTMLElement.prototype, "html"
);
"get" in descriptor // true
"set" in descriptor // true
11.class 的 Generator 方法
如果某个方法之前加上星号,就表示该方法是一个 Generator 函数
class Foo {
constructor (...args) {
this.args = args
}
* [Symbol.iterator]() {
for (let arg of this.args) {
yield arg
}
}
}
for (let f of new Foo('Hello', 'World')) {
console.log(f);
}
// 'Hello'
// 'World'
12.Class 的静态方法
类相当于实例的原型,所有在类中定义的方法,都会被实例继承。如果在一个方法前,加上static关键字,就表示该方法不会被实例继承,而是直接通过类来调用,这就称为“静态方法”。
class Foo {
static classMethod () {
return 'hello'
}
}
Foo.classMethod()
var foo = new Foo() // 'hello'
foo.classMethod() // TypeError: foo.classMethod is not a function
注意,如果静态方法包含this关键字,这个this指的是类,而不是实例
class Foo {
static bar () {
return this.baz()
}
static baz () {
return 'hello'
}
baz () {
return 'world'
}
}
Foo.bar() // 'hello'
父类的静态方法,可以被子类继承。
class Foo {
static bar () {
return 'hello'
}
}
class Bar extends Foo {
}
Bar.bar() // 'hello'
静态方法也可以从 super 对象上调用的
class Foo {
static bar () {
return 'hello'
}
}
class Bar extends Foo {
static bar () {
return super.bar() + ',too'
}
}
Bar.bar()
13.Class 的静态属性和实例属性
静态属性指的是 Class 上的属性,即 Class.propName,而不是定义在实例对象(this)上的属性。
class MyClass {
}
MyClass.prop = 'hello'
MyClass.prop // 'hello'
上面的写法是 ES6 中唯一的写法,因为 ES6 规定 Class 内部只有静态方法,没有静态属性。
目前有一个静态属性的提案,对实例属性和静态属性都规定了新的写法。
- (1)类的实例属性
类的实例属性可以用等式,写入类的定义之中。
class MyClass {
myProp = 42
constructor() {
console.log(this.myProp); // 42
}
}
以前,我们定义实例属性,只能写在类的constructor方法里面,有了新的写法以后,可以不在constructor方法里面定义。
class ReactCounter extends React.Component{
constructor(props) {
super(props)
this.state = {
count: 0
}
}
}
// 等同于
class ReactCounter extends React.Component{
state = {
count: 0
}
}
// 对于在 constructor 里面已经定义的实例属性,新写法允许直接列出。
class ReactCounter extends React.Component{
state;
constructor(props) {
super(props)
this.state = {
count: 0
}
}
}
- (2)类的静态属性
类的静态属性只要在上面的实例属性写法前面,加上static关键字就可以了。
class MyClass {
static myStaticProp = 42;
constructor() {
console.log(MyClass.myStaticProp); // 42
}
}
14.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 命令生成实例');
}
}
// 另一种写法
function Person(name) {
if (new.target === Person) {
this.name = name;
} else {
throw new Error('必须使用 new 命令生成实例');
}
}
var person = new Person('张三'); // 正确
Class 内部调用new.target,返回当前 Class,同时,子类继承父类时,new.target会返回子类,所以可以写出不能独立使用、必须继承后才能使用的类
class Shape {
constructor() {
if (new.target === Shape) {
throw new Error('本类不能实例化')
}
}
}
class Rectangle extends Shape {
constructor (length, width) {
// ...
}
}
var x = new Shape(); // 报错
var y = new Rectangle(3, 4); // 正确
上面代码中,Shape类不能被实例化,只能用于继承。
注意,在函数外部,使用new.target会报错。