前言:上一篇文章js基础进阶2-1 面向对象(类与对象介绍)对类与对象的一些基本概念进行了描述,接下来进入类的创建与使用。
由于js中并没有明确定义类,实现类的方式有很多种,本篇文章仅选取几个典型的做法进行说明。如果想要了解更多实现方式,请移步w3cschool,本篇文章亦是由此参考而作。
原始的方式
var oCar = new Object;
oCar.color = "blue";
oCar.doors = 4;
oCar.mpg = 25;
oCar.showColor = function() {
alert(this.color);
};
缺点:这样的方式缺点很明显,无法创建多个类似的实例,实际上这样的方式并没有构成一个类,这只是一个单体对象。
构造函数方式
function Car(sColor,iDoors,iMpg) {
this.color = sColor;
this.doors = iDoors;
this.mpg = iMpg;
this.showColor = function() {
alert(this.color);
};
}
var oCar1 = new Car("red",4,23);
var oCar2 = new Car("blue",3,25);
优点:通过构造函数创建了一个类,使用new关键字进行实例化,而且可以给函数传递配置参数。
缺点:其实笔者最开始学习js面向对象的时候,一直是这样的写法,但是这样的写法有一个很大的弊端,那就是构造函数会重复生成函数,这是对内存不必要的浪费。
原型方式
function Car() {
}
Car.prototype.color = "blue";
Car.prototype.doors = 4;
Car.prototype.mpg = 25;
Car.prototype.showColor = function() {
alert(this.color);
};
var oCar1 = new Car();
var oCar2 = new Car();
优点:原型链prototype上的属性或方法只会被创建一次,故而没有了构造函数的缺点。
缺点:不能通过给构造函数传递参数来初始化属性的值
混合的构造函数/原型方式
function Car(sColor,iDoors,iMpg) {
this.color = sColor;
this.doors = iDoors;
this.mpg = iMpg;
this.drivers = new Array("Mike","John");
}
Car.prototype.showColor = function() {
alert(this.color);
};
var oCar1 = new Car("red",4,23);
var oCar2 = new Car("blue",3,25);
oCar1.drivers.push("Bill");
alert(oCar1.drivers); //输出 "Mike,John,Bill"
alert(oCar2.drivers); //输出 "Mike,John"
优点:将原型方式和构造函数方式进行结合,解决了两者的缺点。这种方式是目前比较主流的创建类的方式。
缺点:方法在视觉上被暴露在类定义的外面。对于习惯使用其他语言的开发者来说,使用混合的构造函数/原型方式感觉不那么和谐。毕竟,定义类时,大多数面向对象语言都对属性和方法进行了视觉上的封装。请看下面的 Java 类:
class Car {
public String color = "blue";
public int doors = 4;
public int mpg = 25;
public Car(String color, int doors, int mpg) {
this.color = color;
this.doors = doors;
this.mpg = mpg;
}
public void showColor() {
System.out.println(color);
}
}
动态原型方法
function Car(sColor,iDoors,iMpg) {
this.color = sColor;
this.doors = iDoors;
this.mpg = iMpg;
this.drivers = new Array("Mike","John");
if (typeof Car._initialized == "undefined") {
Car.prototype.showColor = function() {
alert(this.color);
};
Car._initialized = true;
}
}
优点:该方法使用标志(_initialized)来判断是否已给原型赋予了任何方法。该方法只创建并赋值一次。这样就解决了构造函数方式中的缺点,而且也将方法封装到了类的内部。
对象的销毁
CMAScript 拥有无用存储单元收集程序(garbage collection routine),意味着不必专门销毁对象来释放内存。当再没有对对象的引用时,称该对象被废除(dereference)了。运行无用存储单元收集程序时,所有废除的对象都被销毁。每当函数执行完它的代码,无用存储单元收集程序都会运行,释放所有的局部变量,还有在一些其他不可预知的情况下,无用存储单元收集程序也会运行。
把对象的所有引用都设置为 null,可以强制性地废除对象。例如:
var oObject = new Object;
// do something with the object here
oObject = null;
当变量 oObject 设置为 null 后,对第一个创建的对象的引用就不存在了。这意味着下次运行无用存储单元收集程序时,该对象将被销毁。
每用完一个对象后,就将其废除,来释放内存,这是个好习惯。这样还确保不再使用已经不能访问的对象,从而防止程序设计错误的出现。此外,旧的浏览器(如 IE/MAC)没有全面的无用存储单元收集程序,所以在卸载页面时,对象可能不能被正确销毁。废除对象和它的所有特性是确保内存使用正确的最好方法。
注意:废除对象的所有引用时要当心。如果一个对象有两个或更多引用,则要正确废除该对象,必须将其所有引用都设置为 null。
结语:上述几种创建类的方式是目前的主要做法,至于选择哪种,一般都会是混合的构造函数/原型方式、动态原型方法,不过笔者最常用的是前者,因为笔者认为大可不必在意这一丁点的视觉效果。