一、简介
function Point(x,y){
this.x=x;
this.y=y;
}
Point.prototype.toString=function (){
console.log(`输入当前坐标,${this.x},${this.y}`);
}
var point=new Point(10,20);
console.log(point);
point.toString();
- ES6 提供了更接近传统语言的写法,引入了 Class(类)这个概念,作为对象的模板。通过class关键字,可以定义类。
- 基本上,ES6 的class可以看作只是一个语法糖,它的绝大部分功能,ES5 都可以做到,新的class写法只是让对象原型的写法更加清晰、更像面向对象编程的语法而已。
- 类在被使用的时候需要实例化new产生一个对象
- 使用类调用里面的静态方法或者属性,new产生对象调用非静态之类的方法或者属性就可以调用了
class Point{
constructor(x,y){
this.x=x;
this.y=y;
}
toString(){
console.log(`输入当前坐标,${this.x},${this.y}`);
}
}
let point=new Point(1,2);
console.log(point);
point.toString();
- 上面代码定义了一个“类”,可以看到里面有一个constructor()方法,这就是构造方法,而this关键字则代表实例对象。这种新的 Class 写法,本质上与本章开头的 ES5 的构造函数Point是一致的。
- Point类除了构造方法,还定义了一个toString()方法。注意,定义toString()方法的时候,前面不需要加上function这个关键字,直接把函数定义放进去了就可以了。另外,方法与方法之间不需要逗号分隔,加了会报错。
console.log(typeof Point);
console.log(Point === Point.prototype.constructor);
- 类的数据类型就是函数,类本身就指向构造函数。
- 使用的时候,也是直接对类使用new命令,跟构造函数的用法完全一致。
V构造函数的prototype属性,在 ES6 的“类”上面继续存在。事实上,类的所有方法都定义在类的prototype属性上面。
class Point{
constructor(){
}
toString(){
}
toValue(){
}
}
Point.prototype={
constructor(){},
toString(){},
toValue(){},
};
- 上面代码中,constructor()、toString()、toValue()这三个方法,其实都是定义在Point.prototype上面。
- 因此,在类的实例上面调用方法,其实就是调用原型上的方法。
class B{}
const b=new B();
console.log(b.constructor === B.prototype.constructor);
- b是B类的实例,它的constructor()方法就是B类原型的constructor()方法。
- 由于类的方法都定义在prototype对象上面,所以类的新方法可以添加在prototype对象上面。Object.assign()方法可以很方便地一次向类添加多个方法。
class Point{
constructor(){
}
}
Object.assign(Point.prototype,{
toString(){},
toValue(){}
});
- prototype对象的constructor()属性,直接指向“类”的本身
console.log(Point.prototype.constructor === Point);//true
二、不可枚举性
- 类的内部所有定义的方法,都是不可枚举的。
- 对象的属性或者方法通过一些方法可以获取(可枚举的属性或者方法),不能获取不可枚举的属性或方法。
class fun{
constructor(x,y){
this.x=x;
this.y=y;
}
toString(){
console.log(`当前坐标${this.x},${this.y}`);
}
}
var f=new fun(1,2);
console.log(Object.keys(f));
三、constructor 方法
- constructor()方法是类的默认方法,通过new命令生成对象实例时,自动调用该方法。一个类必须有constructor()方法,如果没有显式定义,一个空的constructor()方法会被默认添加。
class Point{
}
class Point{
constructor(){}
}
- constructor()方法默认返回实例对象(即this),完全可以指定返回另外一个对象。
class Foo{
constructor(){
return Object.create(null);
}
}
console.log(new Foo() instanceof Foo);
console.log(Object.create(null));
- 上面代码中,constructor()函数返回一个全新的对象,结果导致实例对象不是Foo类的实例。
- 类必须使用new调用,否则会报错。这是它跟普通构造函数的一个主要区别,后者不用new也可以执行。
console.log(Foo());
四、类的实例
- 生成类的实例的写法,与 ES5 一样,也是使用new命令。前面说过,如果忘记加上new,像函数那样调用Class,将会报错。
- 与 ES5 一样,实例的属性除非显式定义在其本身(即定义在this对象上),否则都是定义在原型上(即定义在class上)。
class Point{
constructor(x,y){
this.x=x;
this.y=y;
}
toString(){
return '('+this.x+', '+this.y+')';
}
}
var point=new Point(2,3);
console.log(point.toString());
console.log(point.hasOwnProperty('x'));
console.log(point.hasOwnProperty('y'));
console.log(point.hasOwnProperty('toString'));
console.log(point.__proto__.hasOwnProperty('toString'));
- 上面代码中,x和y都是实例对象point自身的属性(因为定义在this对象上),所以hasOwnProperty()方法返回true,而toString()是原型对象的属性(因为定义在Point类上),所以hasOwnProperty()方法返回false。与ES5一样。
- 与 ES5 一样,类的所有实例共享一个原型对象。
var p1=new Point(2,3);
var p2=new Point(3,3);
console.log(p1.__proto__ === p2.__proto__);
- 上面代码中,p1和p2都是Point的实例,它们的原型都是Point.prototype,所以__proto__属性是相等的。
- 这也意味着,可以通过实例的__proto__属性为“类”添加方法。
var p1=new Point(2,3);
var p2=new Point(3,2);
p1.__proto__.Name=function (){
return 'zzz';
};
console.log(p1.Name());
console.log(p2.Name());
var p3=new Point(4,2);
console.log(p3.Name());
- 上面代码在p1的原型上添加了一个Name()方法,由于p1的原型就是p2的原型,因此p2也可以调用这个方法。而且,此后新建的实例p3也可以调用这个方法。这意味着,使用实例的__proto__属性改写原型,必须相当谨慎,不推荐使用,因为这会改变“类”的原始定义,影响到所有实例。
五、取值函数(getter)和存值函数(setter)
class Student{
constructor(){
}
get allName(){
return this.name;
}
set allName(name){
this.name=name;
}
}
let stu=new Student();
stu.allName='zzz';
console.log(stu.allName);
console.log(stu);
class Element{
constructor(element){
this.element=element;
}
get html(){
return this.element.innerHTML;
}
set html(str){
this.element.innerHTML=str;
}
}
let btn=document.querySelector("#btn");
let ele=new Element(btn);
ele.html="zzz";
console.log(ele.html);
六、属性表达式
let proto="getname";
class People{
constructor(name){
this.name=name;
}
[proto](){
console.log(this.name);
}
}
let p=new People("zzz");
console.log(p);
p.getname();
p[proto]();
七、Class 表达式
let car=new Car{
constructor(name){
this.name=name;
}
}("宝马");
console.log(car);
console.log(car.name);
- 上面代码使用表达式定义了一个类。需要注意的是,这个类的名字是Car,但是Car只在 Class 的内部可用,指代当前类。在 Class 外部,这个类只能用car引用。
八、静态方法
- 类相当于实例的原型,所有在类中定义的方法,都会被实例继承。如果在一个方法前,加上static关键字,就表示该方法不会被实例继承,而是直接通过类来调用,这就称为“静态方法”。
class Book{
name;
price;
author;
static type;
constructor(name,price,author){
this.name=name;
this.price=price;
this.author=author;
}
toString(){
return `${this.name}书${this.price}元`;
}
static getType(){
return `当前属于${Book.type}`;
}
}
let book=new Book('水浒传',99.9,'吴承恩');
console.log(book);
console.log(book.toString());
Book.type="书";
console.log(Book.type);
console.log(Book.getType());
- 如果在实例上调用静态方法,会抛出一个错误,表示不存在该方法。
- 注意,如果静态方法包含this关键字,这个this指的是类,而不是实例。
- 父类的静态方法,可以被子类继承。
class Foo{
static Method(){
return 'hello'
}
}
class Bar extends Foo{
}
console.log(Bar.Method());
class Foo{
static Method(){
return 'hello'
}
}
class Bar extends Foo{
static Can(){
return super.Method()+',too';
}
}
console.log(Bar.Can());
九、静态属性
- 静态属性指的是 Class 本身的属性,即Class.propName,而不是定义在实例对象(this)上的属性。
class Foo{
}
Foo.prop=1;
class Foo{
static prop=1;
}
十、私有方法和私有属性
- 私有方法和私有属性,是只能在类的内部访问的方法和属性,外部不能访问。这是常见需求,有利于代码的封装,但 ES6 不提供,只能通过变通方法模拟实现。
- 一种做法是在命名上加以区别。
class Animal{
constructor(){
}
getAge(){
}
_setColor(){
}
}
let cat=new Animal();
console.log(cat);
- _setColor()方法前面的下划线,表示这是一个只限于内部使用的私有方法。但是,这种命名是不保险的,在类的外部,还是可以调用到这个方法。
- 另一种方法就是索性将私有方法移出类,因为类内部的所有方法都是对外可见的。
- 还有一种方法是利用Symbol值的唯一性,将私有方法的名字命名为一个Symbol值。
十一、注意点
1. 严格模式
- 类和模块的内部,默认就是严格模式,所以不需要使用use strict指定运行模式。只要你的代码写在类或模块之中,就只有严格模式可用。考虑到未来所有的代码,其实都是运行在模块之中,所以 ES6 实际上把整个语言升级到了严格模式。
2. 不存在提升
3. name 属性
- 由于本质上,ES6 的类只是 ES5 的构造函数的一层包装,所以函数的许多特性都被Class继承,包括name属性。
class Point{}
console.log(Point.name);
- name属性总是返回紧跟在class关键字后面的类名。
4. this 的指向
- 类的方法内部如果含有this,它默认指向类的实例。