上一篇文章里我介绍了一下面向对象编程的概念,在最后终于喜出望外看到了ES6提供了类的概念了。那这个类如何去用,是这篇文章的主题。ES6给我们提供了一个class关键字。这个关键字跟以前的var let const很像,它们都是用做声明的,而class就是用来声明一个类的。

语法

class name [extends]{   //extends是用来继承的,可选参数
    //class body
};

注意

  • class不能重复声明(与let、const一样)
  • 类的本质还是一个构造函数
class Div{  //类
	constructor(x,y){   //构造函数
		this.x=x;	//共享属性,放在constructor里
		this.y=y;
	}//注意这里是没有逗号的
	move(){	//共享方法,这里相当于在Div.prototye上添加方法
		console.log('动起来');
	}
}
console.dir(Div);   //在控制台里看一下与ES5的面向对象程序有什么不同

ES5里面的面向对象,所谓的“类”与构造函数其实是一个东西,也就是双重角色。而到了ES6里面真正的类与构造函数现在是分离的,通过上面的代码可以看出来,这种写法正是面向对象的正统写法。同时,我们在控制台里看到这个对象与ES5的对象区别仅在于显示的名字上多了一个class关键字,如下图:

下面我要详细的对比一下ES5ES6的面向对象有什么区别,以及用这种方式写出来的对象与ECMAScript的内置对象有什么区别,这样做的目的能让你清晰的明白面向对象编程究竟是一种什么样的形式。

1、与ES5对比

const [div1,div2]=[new Div(10,20),new Div(15,20)];	//这两个对象是为了对比他们身上的原型
div1.z=30;	//给实例添加一个私有属性

console.log(
	typeof Div,	//function 构造函数(虽说是类,但实质还是构造函数)
	Div.prototype.constructor===Div,    //true 类本质还是构造函数(披着羊皮的狼)
	
	//Object.getPrototypeOf方法是用来取对象身上的原型,用它代替__proto__
	Object.getPrototypeOf(div1)===Div.prototype,    //true 实例的原型就是构造函数的原型
	Object.getPrototypeOf(div1)===Object.getPrototypeOf(div2),  //true 两个实例的原型都一样,指向构造函数的原型对象
	
	div1 instanceof Div,		//true div是它的实例
	div1.constructor===Div,	    //true 实例的构造函数就是类
	
	/*
	 * 方法说明
	 *  Object.getOwnPropertyNames()这个方法是用来获取对象身上的所有属性名
	 *  hasOwnProperty()用来判断某个属性是对象自身的(true),还是继承自原型对象的(false)
	 *  Object.keys()返回对象所有可枚举(遍历)的属性名
	 */
	Object.getOwnPropertyNames(div1),//["x", "y", "z"] 实例自己的属性
	div1.hasOwnProperty('x'),	    //true 实例的属性
	div1.hasOwnProperty('move'),	//false 这个方法是继承而来的
	Object.keys(Div.prototype)	    //[] 对象身上的方法都是不可枚举的
);

//ES5定义的对象,身上的方法是可以枚举的
function Car(){}
Car.prototype.drive=function(){
	console.log('窜的老快了');
}
console.log(Object.keys(Car.prototype));  //["drive"] 所有方法都是可枚举的

从上面的代码得出以下的结论

  1. 类的本质还是构造函数,其实class就是个语法糖,它的内部还是个构造函数
  2. class声明的对象与ES5声明的对象实质上一样
  3. class声明的对象,它身上的方法都不能被枚举

2、与内置对象对比

const [d1,d2]=[new Date(),new Date()];  //声明两个内置对象实例
d1.x=10,d1.y=20,d1.z=30;	//给实例添加三个私有属性

console.log(
	typeof Date,    //function
	Date.prototype.constructor===Date,	//true
	Object.getPrototypeOf(d1)===Date.prototype, //true
	Object.getPrototypeOf(d1)===Object.getPrototypeOf(d1),  //true
	d1 instanceof Date, //true
	d1.constructor===Date,  //true
	Object.getOwnPropertyNames(d1), //["x", "y", "z"]
	d1.hasOwnProperty('x'),  //true
	d1.hasOwnProperty('getDate'),   //false 这个方法是继承于Date对象的
	Object.keys(Date.prototype),	//内置对象身上的方法都是不可枚举的
);

从上面的代码得出以下的结论

  1. 自定义对象就是我们声明的一个类似于内置对象的对象
  2. JavaScript的面向对象编程,实质是把某个功能写成一个对象,并且这个对象是在模仿内置对象

添加属性与方法

class声明的对象同样允许小伙伴们任性的添加属性与方法,包括共享与私有的。

  • 共享属性放在constructor里,共享方法放在大括号内
  • 私有属性放在类身上,私有方法放在大括号内同时前面要加static关键字
  • 私有方法里this指向类本身,其它方法里的this指向实例对象
class Bear{
    constructor(){
        this.name='熊大';   //共享属性(放在constructor里)
    }
    sleep(){	//共享方法(直接放在大括号里)
		this.name='熊二';	//this指向实例,所以在这里给this添加属性还是实例的属性
		console.log(`${this.name}爱睡觉`);
	}
	static gohome(){	//私有方法
		//类会默认添加一个name属性,值为class后面的那个单词
		console.log(`${this.name}的家在森林`);  //这里的this并不会指向实例,而是指向类
	}
}

//共享属性与方法
const b1=new Bear();
console.log(b1.name);	//熊大	
b1.sleep();	//熊大爱睡觉
console.log(b1.name);	//熊二  sleep里重新定义了name属性,所以在这就被改了

//私有属性与方法
Bear.age=5;		//在外面添加私有属性
console.log(b1.age);	//undefined 实例不具备
Bear.gohome();	        //Bear的家在森林
//b1.goHome();	        //报错,它是私有方法

下一篇文章会详细介绍class里的继承。