对象的创建和销毁都在 JavaScript 执行过程中发生。把对象的所有引用都设置为 null,可以强制性地废除对象。
在 ECMAScript 中,所有对象并非同等创建的。
一、对象类型
一般来说,可以创建并使用的对象有三种:本地对象、内置对象和宿主对象。
1. 本地对象
本地对象就是 ECMA-262 定义的类(引用类型)。它们包括:
- Object
- Function
- Array
- String
- Boolean
- Number
- Date
- RegExp
- Error
- EvalError
- RangeError
- ReferenceError
- SyntaxError
- TypeError
- URIError
2. 内置对象
ECMA-262 只定义了两个内置对象,即 Global 和 Math (它们也是本地对象,根据定义,每个内置对象都是本地对象)。
3. 宿主对象
所有非本地对象都是宿主对象(host object),即由 ECMAScript 实现的宿主环境提供的对象。
所有 BOM 和 DOM 对象都是宿主对象。
二、对象作用域
作用域指的是变量的适用范围。
在传统的面向对象程序设计中,主要关注于公用和私有作用域。公用作用域中的对象属性可以从对象外部访问,即开发者创建对象的实例后,就可使用它的公用属性。而私有作用域中的属性只能在对象内部访问,即对于外部世界来说,这些属性并不存在。这意味着如果类定义了私有属性和方法,则它的子类也不能访问这些属性和方法。
受保护作用域也是用于定义私有的属性和方法,只是这些属性和方法还能被其子类访问。
- ECMAScript 只有公用作用域
- 建议性的解决方法:开发者确定了一个规约,在属性前后加下划线,告诉其他开发者,应该把该属性看作私有的。
- ECMAScript 没有静态作用域
1. 关键字this
在 ECMAScript 中,要掌握的最重要的概念之一是关键字 this 的用法,它用在对象的方法中。关键字 this 总是指向调用该方法的对象。
var oCar = new Object; oCar.color = "red"; oCar.showColor = function() { alert(this.color); }; oCar.showColor(); //输出 "red"
在上面的代码中,关键字 this 用在对象的 showColor() 方法中。在此环境中,this 等于 oCar。等同于:
var oCar = new Object; oCar.color = "red"; oCar.showColor = function() { alert(oCar.color); }; oCar.showColor(); //输出 "red"
使用this的原因:因为在实例化对象时,总是不能确定开发者会使用什么样的变量名。使用 this,即可在任何多个地方重用同一个函数。
三、定义类或对象
1. 原始方式
var oCar = new Object; oCar.color = "blue"; oCar.doors = 4; oCar.mpg = 25; oCar.showColor = function() { alert(this.color); };
缺点:如果需要多个,就要创建多个对象。
2. 工厂方式
为了解决多个对象的问题,可以创建一个返回对象的工厂方法。
function createCar() { var oTempCar = new Object; oTempCar.color = "blue"; oTempCar.doors = 4; oTempCar.mpg = 25; oTempCar.showColor = function() { alert(this.color); }; return oTempCar; } var oCar1 = createCar(); var oCar2 = createCar();
缺点:功能上解决了重复创建函数对象的问题;语义上,该函数不太像是对象的方法。
3. 构造函数方式
创建构造函数就像创建工厂函数一样容易。第一步选择类名,即构造函数的名字。根据惯例,这个名字的首字母大写,以使它与首字母通常是小写的变量名分开。除了这点不同,构造函数看起来很像工厂函数。
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);
4. 原型方式
该方式利用了对象的 prototype 属性,可以把它看成创建新对象所依赖的原型。
首先用空构造函数来设置类名。然后所有的属性和方法都被直接赋予 prototype 属性。
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();
我们再看下面这个例子:
function Car() { } Car.prototype.color = "blue"; Car.prototype.doors = 4; Car.prototype.mpg = 25; Car.prototype.drivers = new Array("Mike","John"); Car.prototype.showColor = function() { alert(this.color); }; var oCar1 = new Car(); var oCar2 = new Car(); oCar1.drivers.push("Bill"); alert(oCar1.drivers); //输出 "Mike,John,Bill" alert(oCar2.drivers); //输出 "Mike,John,Bill"
属性 drivers 是指向 Array 对象的指针,该数组中包含两个名字 "Mike" 和 "John"。由于 drivers 是引用值,Car 的两个实例都指向同一个数组。这意味着给 oCar1.drivers 添加值 "Bill",在 oCar2.drivers 中也能看到。输出这两个指针中的任何一个,结果都是显示字符串 "Mike,John,Bill"。
5. 混合的构造函数/原型方式
联合使用构造函数和原型方式,就可像用其他程序设计语言一样创建对象。这种概念非常简单,即用构造函数定义对象的所有非函数属性,用原型方式定义对象的函数属性(方法)。结果是,所有函数都只创建一次,而每个对象都具有自己的对象属性实例。
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"
这种方式是 ECMAScript 采用的主要方式,它具有其他方式的特性,却没有他们的副作用。
6. 动态原型方法
动态原型方法的基本想法与混合的构造函数/原型方式相同,即在构造函数内定义非函数属性,而函数属性则利用原型属性定义。
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; } }
四、修改对象
prototype 属性不仅可以定义构造函数的属性和方法,还可以为本地对象添加属性和方法。
1. 创建新方法
(1)通过已有的方法创建新方法
可以用 prototype 属性为任何已有的类定义新方法,就像处理自己的类一样。
Number.prototype.toHexString = function() { return this.toString(16); };
关键字 this 指向 Number 的实例,因此可完全访问 Number 的所有方法。有了这段代码,可实现下面的操作:
var iNum = 15; alert(iNum.toHexString()); //输出 "F"
(2)重命名已有的方法
为已有的方法命名更易懂的名称。
例如,可以给 Array 类添加两个方法 enqueue() 和 dequeue(),只让它们反复调用已有的 push() 和 shift() 方法即可:
Array.prototype.enqueue = function(vItem) { this.push(vItem); }; Array.prototype.dequeue = function() { return this.shift(); };
(3)添加与已有方法无关的方法
Array.prototype.indexOf = function (vItem) { for (var i=0; i<this.length; i++) { if (vItem == this[i]) { return i; } } return -1; }
(4)为本地添加新方法
如果想给 ECMAScript 中每个本地对象添加新方法,必须在 Object 对象的 prototype 属性上定义它。所有本地对象都继承了 Object 对象,所以对 Object 对象做任何改变,都会反应在所有本地对象上。
Object.prototype.showValue = function () { alert(this.valueOf()); }; var str = "hello"; var iNum = 25; str.showValue(); //输出 "hello" iNum.showValue(); //输出 "25"
2. 重定义已有的方法
Function.prototype.toString = function() { return "Function code hidden"; }
function sayHi() { alert("hi"); } alert(sayHi.toString()); //输出 "Function code hidden"