6.ECMAScript 对象

1.ECMAScript 面向对象技术
(1)面向对象术语

1)对象
ECMA-262 把对象(object)定义为“属性的无序集合,每个属性存放一个原始值、对象或函数”。严格来说,这意味着对象是无特定顺序的值的数组。
尽管 ECMAScript 如此定义对象,但它更通用的定义是基于代码的名词(人、地点或事物)的表示。

2)类
每个对象都由类定义,可以把类看做对象的配方。类不仅要定义对象的接口(interface)(开发者访问的属性和方法),还要定义对象的内部工作(使属性和方法发挥作用的代码)。编译器和解释程序都根据类的说明构建对象。

3)实例
程序使用类创建对象时,生成的对象叫作类的实例(instance)。对类生成的对象的个数的唯一限制来自于运行代码的机器的物理内存。每个实例的行为相同,但实例处理一组独立的数据。由类创建对象实例的过程叫做实例化(instantiation)。
ECMAScript 并没有正式的类。相反,ECMA-262 把对象定义描述为对象的配方。这是 ECMAScript 逻辑上的一种折中方案,因为对象定义实际上是对象自身。即使类并不真正存在,我们也把对象定义叫做类,因为大多数开发者对此术语更熟悉,而且从功能上说,两者是等价的。

(2)面向对象语言的要求
一种面向对象语言需要向开发者提供四种基本能力:
    封装 - 把相关的信息(无论数据或方法)存储在对象中的能力
    聚集 - 把一个对象存储在另一个对象内的能力
    继承 - 由另一个类(或多个类)得来类的属性和方法的能力
    多态 - 编写能以多种方法运行的函数或方法的能力
ECMAScript 支持这些要求,因此可被是看做面向对象的。

(3)对象的构成
在 ECMAScript 中,对象由特性(attribute)构成,特性可以是原始值,也可以是引用值。如果特性存放的是函数,它将被看作对象的方法(method),否则该特性被看作对象的属性(property)。



2.ECMAScript 对象应用
(1)声明和实例化
对象的创建方式是用关键字 new 后面跟上实例化的类的名字
var oObject = new Object();
var oStringObject = new String();

(2)对象引用
在 ECMAScript 中,不能访问对象的物理表示,只能访问对象的引用。每次创建对象,存储在变量中的都是该对象的引用,而不是对象本身。

(3)对象废除
ECMAScript 拥有无用存储单元收集程序(garbage collection routine),意味着不必专门销毁对象来释放内存。
当再没有对对象的引用时,称该对象被废除(dereference)了。
运行无用存储单元收集程序时,所有废除的对象都被销毁。每当函数执行完它的代码,无用存储单元收集程序都会运行,释放所有的局部变量。
还有在一些其他不可预知的情况下,无用存储单元收集程序也会运行。
把对象的所有引用都设置为 null,可以强制性地废除对象。例如:
var oObject = new Object;
// do something with the object here
oObject = null;
注意:废除对象的所有引用时要当心。如果一个对象有两个或更多引用,则要正确废除该对象,必须将其所有引用都设置为 null。

(4)早绑定和晚绑定
所谓绑定(binding),即把对象的接口与对象实例结合在一起的方法。

1)早绑定(early binding)
是指在实例化对象之前定义它的属性和方法,这样编译器或解释程序就能够提前转换机器代码。
在 Java 和 Visual Basic 这样的语言中,有了早绑定,就可以在开发环境中使用 IntelliSense(即给开发者提供对象中属性和方法列表的功能)。
ECMAScript 不是强类型语言,所以不支持早绑定。

2)晚绑定(late binding)
晚绑定(late binding)指的是编译器或解释程序在运行前,不知道对象的类型。使用晚绑定,无需检查对象的类型,只需检查对象是否支持属性和方法即可。
ECMAScript 中的所有变量都采用晚绑定方法。这样就允许执行大量的对象操作,而无任何惩罚。


3.ECMAScript 对象类型
在 ECMAScript 中,所有对象并非同等创建的。
一般来说,可以创建并使用的对象有三种:本地对象、内置对象和宿主对象。
(1)本地对象
ECMA-262 把本地对象(native object)定义为“独立于宿主环境的 ECMAScript 实现提供的对象”。
简单来说,本地对象就是 ECMA-262 定义的类(引用类型)。它们包括:
    Object
    Function
    Array //属性方法详见基础部分对象
    String //属性方法详见基础部分对象
    Boolean
    Number
    Date //属性方法详见基础部分对象
    RegExp
    Error
    EvalError
    RangeError
    ReferenceError
    SyntaxError
    TypeError
    URIError

(2)内置对象
ECMA-262 把内置对象(built-in object)定义为“由 ECMAScript 实现提供的、独立于宿主环境的所有对象,在 ECMAScript 程序开始执行时出现”。
这意味着开发者不必明确实例化内置对象,它已被实例化了。
ECMA-262 只定义了两个内置对象,即 Global 和 Math (它们也是本地对象,根据定义,每个内置对象都是本地对象)。

1)Global 对象
全局对象是预定义的对象,作为 JavaScript 的全局函数和全局属性的占位符。
通过使用全局对象,可以访问所有其他所有预定义的对象、函数和属性。全局对象不是任何对象的属性,所以它没有名称。

顶层函数(全局函数)
函数                                                      描述  
decodeURI()             解码某个编码的 URI。
decodeURIComponent()    解码一个编码的 URI 组件。  
encodeURI()             把字符串编码为 URI。  
encodeURIComponent()    把字符串编码为 URI 组件。  
escape()                对字符串进行编码。
eval()                  计算 JavaScript 字符串,并把它作为脚本代码来执行。  
getClass()              返回一个 JavaObject 的 JavaClass。         
isFinite()              检查某个值是否为有穷大的数。
isNaN()                 检查某个值是否是数字。
Number()                把对象的值转换为数字。  
parseFloat()            解析一个字符串并返回一个浮点数。
parseInt()              解析一个字符串并返回一个整数。   
String()                把对象的值转换为字符串。    
unescape()              对由 escape() 编码的字符串进行解码。  

顶层属性(全局属性)
方法                      描述  
Infinity    代表正的无穷大的数值。    
java        代表 java.* 包层级的一个 JavaPackage。        
NaN         指示某个值是不是数字值。
Packages    根 JavaPackage 对象。        
undefined   指示未定义的值。   

2)Math 对象
Math 对象用于执行数学任务。

Math 对象属性
属性            描述
E       返回算术常量 e,即自然对数的底数(约等于2.718)。
LN2     返回 2 的自然对数(约等于0.693)。   
LN10    返回 10 的自然对数(约等于2.302)。
LOG2E   返回以 2 为底的 e 的对数(约等于 1.414)。
LOG10E  返回以 10 为底的 e 的对数(约等于0.434)。   
PI      返回圆周率(约等于3.14159)。
SQRT1_2 返回返回 2 的平方根的倒数(约等于 0.707)。
SQRT2   返回 2 的平方根(约等于 1.414)。

Math 对象方法
方法                      描述
abs(x)      返回数的绝对值。  
acos(x)     返回数的反余弦值。  
asin(x)     返回数的反正弦值。
atan(x)     以介于 -PI/2 与 PI/2 弧度之间的数值来返回 x 的反正切值。   
atan2(y,x)  返回从 x 轴到点 (x,y) 的角度(介于 -PI/2 与 PI/2 弧度之间)。  
ceil(x)     对数进行上舍入。   
cos(x)      返回数的余弦。  
exp(x)      返回 e 的指数。
floor(x)    对数进行下舍入。
log(x)      返回数的自然对数(底为e)。
max(x,y)    返回 x 和 y 中的最高值。     
min(x,y)    返回 x 和 y 中的最低值。   
pow(x,y)    返回 x 的 y 次幂。   
random()    返回 0 ~ 1 之间的随机数。    
round(x)    把数四舍五入为最接近的整数。
sin(x)      返回数的正弦。  
sqrt(x)     返回数的平方根。  
tan(x)      返回角的正切。  
toSource()  返回该对象的源代码。  
valueOf()   返回 Math 对象的原始值。  


(3)宿主对象
所有非本地对象都是宿主对象(host object),即由 ECMAScript 实现的宿主环境提供的对象。
所有 BOM 和 DOM 对象都是宿主对象。



4.ECMAScript 对象作用域
作用域指的是变量的适用范围。
(1)公用、私有和受保护作用域
1)概念
在传统的面向对象程序设计中,主要关注于公用和私有作用域。

2)ECMAScript 只有公用作用域
对 ECMAScript 讨论上面这些作用域几乎毫无意义,因为 ECMAScript 中只存在一种作用域 - 公用作用域。
ECMAScript 中的所有对象的所有属性和方法都是公用的。

3)建议性的解决方法:
由于缺少私有作用域,开发者确定了一个规约,说明哪些属性和方法应该被看做私有的。这种规约规定在属性前后加下划线:
obj._color_ = "blue";
有些开发者还喜欢用单下划线说明私有成员,例如:obj._color。

(2)静态作用域
静态作用域定义的属性和方法任何时候都能从同一位置访问。在 Java 中,类可具有属性和方法,无需实例化该类的对象,即可访问这些属性和方法。
严格来说,ECMAScript 并没有静态作用域。不过,它可以给构造函数提供属性和方法。
案例:
sayHello.alternate = function() {
  alert("hi");
}
sayHello.alternate();   //输出 "hi"
这里,方法 alternate() 实际上是函数 sayHello 的方法。即使如此,alternate() 也是 sayHello() 公用作用域中的方法,而不是静态方法。

(3)关键字 this
1)this 的功能
在 ECMAScript 中,要掌握的最重要的概念之一是关键字 this 的用法,它用在对象的方法中。
关键字 this 总是指向调用该方法的对象。注意是指向对象。

2)使用 this 的原因
为什么使用 this 呢?
因为在实例化对象时,总是不能确定开发者会使用什么样的变量名。使用 this,即可在任何多个地方重用同一个函数。



5.ECMAScript 定义类或对象
使用预定义对象只是面向对象语言的能力的一部分,它真正强大之处在于能够创建自己专用的类和对象。
ECMAScript 拥有很多创建对象或类的方法。
(1)工厂方式
原始的方式
因为对象的属性可以在对象创建后动态定义,所有许多开发者都在 JavaScript 最初引入时编写类似下面的代码:
var oCar = new Object;
oCar.color = "blue";
oCar.doors = 4;
oCar.mpg = 25;
oCar.showColor = function() {
  alert(this.color);
};
这里有一个问题,就是可能需要创建多个 car 的实例。

(2)解决方案:工厂方式
在方法中new一个object,为该object添加属性和方法,最后返回该object
1)为函数传递参数
function createCar(sColor,iDoors,iMpg) {
  var oTempCar = new Object;
  oTempCar.color = sColor;
  oTempCar.doors = iDoors;
  oTempCar.mpg = iMpg;
  oTempCar.showColor = function() {
    alert(this.color);
  };
  return oTempCar;
}
var oCar1 = createCar("red",4,23);
oCar1.showColor();      //输出 "red"

2)在工厂函数外定义对象的方法
function showColor() {
  alert(this.color);
}
function createCar(sColor,iDoors,iMpg) {
  var oTempCar = new Object;
  oTempCar.color = sColor;
  oTempCar.doors = iDoors;
  oTempCar.mpg = iMpg;
  oTempCar.showColor = showColor;
  return oTempCar;
}

(3)构造函数方式
构造函数方法不在方法不中创建object,在调用构造方法的时候通过new来创建一个object。所以方法里就没有return object。
方法体里通过this关键字设置属性和方法。
function Car(sColor,iDoors,iMpg) {
  this.color = sColor;
  this.doors = iDoors;
  this.mpg = iMpg;
  this.showColor = function() {
    return this.color;
  };
}
var oCar1 = new Car("red",4,23);
document.write(oCar1.showColor());
构造函数方法和工厂方法区别
首先在构造函数内没有创建对象,而是使用 this 关键字。
使用 new 运算符构造函数时,在执行第一行代码前先创建一个对象,只有用 this 才能访问该对象。
然后可以直接赋予 this 属性,默认情况下是构造函数的返回值(不必明确使用 return 运算符)。
缺点:就像工厂函数,构造函数会重复生成函数,为每个对象都创建独立的函数版本。这是浪费内存的原因。

(4)原型方式
该方式利用了对象的 prototype 属性,可以把它看成创建新对象所依赖的原型。
1)首先用空构造函数来设置类名。
function Car() {

}
2)然后所有的属性和方法都被直接赋予 prototype 属性。
Car.prototype.color = "blue";
Car.prototype.doors = 4;
Car.prototype.mpg = 25;
Car.prototype.showColor = function() {
  document.write(this.color);
};
var oCar1 = new Car();
oCar1.showColor();
此外,使用这种方式,还能用 instanceof 运算符检查给定变量指向的对象的类型。因此,下面的代码将输出 TRUE:
alert(oCar1 instanceof Car);    //输出 "true"
原型方式的问题:
首先,这个构造函数没有参数。
真正的问题出现在属性指向的是对象,而不是函数时。函数共享不会造成问题,但对象却很少被多个实例共享。
结论:原型方法会导致所有创建的引用指向同一个对象,也就是说修改其中一个对象的属性,相当于全部修改了。

(5)混合的构造函数/原型方式
联合使用构造函数和原型方式,就可像用其他程序设计语言一样创建对象。
这种概念非常简单,即用构造函数定义对象的所有非函数属性,用原型方式定义对象的函数属性(方法)。
结果是,所有函数都只创建一次,而每个对象都具有自己的对象属性实例。

1)用构造函数定义对象的所有非函数属性
function Car(sColor,iDoors,iMpg) {
  this.color = sColor;
  this.doors = iDoors;
  this.mpg = iMpg;
  this.drivers = new Array("Mike","John");
}
2)用原型方式定义对象的函数属性(方法)。
Car.prototype.showColor = function() {
  alert(this.color);
};
3)测试:
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");
  // 关键判断 只有在第一次new对象的时候该方法会被初始化
  if (typeof Car._initialized == "undefined") {
    Car.prototype.showColor = function() {
      alert(this.color);
    };
    // 关键判断 在第一次new对象之后添加标示位,来标记对象的方法已经被初始化。
    Car._initialized = true;
  }
}
这段代码看起来更像其他语言中的类定义了,比混合的构造函数/原型方式在可读性上有了提高。

(7)采用哪种方式
如前所述,目前使用最广泛的是混合的构造函数/原型方式。
此外,动态原始方法也很流行,在功能上与构造函数/原型方式等价。
可以采用这两种方式中的任何一种。不过不要单独使用经典的构造函数或原型方式,因为这样会给代码引入问题。



6.ECMAScript 修改对象
通过使用 ECMAScript,不仅可以创建对象,还可以修改已有对象的行为。
prototype 属性不仅可以定义构造函数的属性和方法,还可以为本地对象添加属性和方法。

(1)创建新方法
通过已有的方法创建新方法
可以用 prototype 属性为任何已有的类定义新方法,就像处理自己的类一样。
案例:为Number添加toHexString方法
Number.prototype.toHexString = function() {
  return this.toString(16);
};
测试:
var iNum = 15;
document.write(iNum.toHexString());      //输出 "F"
由于数字 15 等于十六进制中的 F,因此输出 "F"。

(2)重命名已有方法
我们还可以为已有的方法命名更易懂的名称。
案例:可以给 Array 类添加两个方法 enqueue() 和 dequeue(),只让它们反复调用已有的 push() 和 shift() 方法即可:
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;
}
该方法 indexOf() 与 String 类的同名方法保持一致,在数组中检索每个项,直到发现与传进来的项相同的项目为止。
如果找到相同的项,则返回该项的位置,否则,返回 -1
测试:有了这种定义,我们可以编写下面的代码:
var aColors = new Array("red","green","blue");
alert(aColors.indexOf("green"));    //输出 "1"

(3)为本地对象添加新方法
如果想给 ECMAScript 中每个本地对象添加新方法,必须在 Object 对象的 prototype 属性上定义它。
,所有本地对象都继承了 Object 对象,所以对 Object 对象做任何改变,都会反应在所有本地对象上。
案例:如果想添加一个用警告输出对象的当前值的方法,可以采用下面的代码:
Object.prototype.showValue = function () {
  alert(this.valueOf());
};
var str = "hello";
var iNum = 25;
str.showValue();        //输出 "hello"
iNum.showValue();       //输出 "25"

(4)重定义已有方法
也可重定义已有的方法,不过指向的原始函数怎么了呢?
它将被无用存储单元回收程序回收,因为它被完全废弃了。没有能够恢复原始函数的方法。
所以在覆盖原始方法前,比较安全的做法是存储它的指针,以便以后的使用。
案例:修改了本地方法,如 toString()
// 保存原方法的指针
Function.prototype.originalToString = Function.prototype.toString;
// 修改该方法
Function.prototype.toString = function() {
  if (this.originalToString().length > 100) {
    return "Function too long to display.";
  } else {
    return this.originalToString();
  }
};

(5)极晚绑定(Very Late Binding)
new之后再绑定方法
注意:不建议使用极晚绑定方法,因为很难对其跟踪和记录。不过,还是应该了解这种可能。
var o = new Object();
Object.prototype.sayHi = function () {
  alert("hi");
};
o.sayHi();

你可能感兴趣的:(ECMAScript)