【ECMAScript5】对象

对象的创建和销毁都在 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"

 

posted @ 2020-01-13 15:46  codedot  阅读(199)  评论(0编辑  收藏  举报