轻松学习JavaScript十二:JavaScript基于面向对象之创建对象(二)

四原型方式

我们创建的每个函数都有一个通过prototype(原型)属性,这个属性是一个对象,它的用途是包含可以由特定类型

的所有实例共享的属性和方法。逻辑上可以这么理解:prototypt通过条用构造函数而创建的那个对象的原型对象。使

用原型的好处就是可以让所有对象实例共享它所包含的属性和方法。也就是说,不必在构造函数中定义对象信息,而

是直接将这些信息添加到原型中。

原型方式利用了对象的prototype 属性,可以把它看成创建新对象所依赖的原型。这里,首先用空构造函数来设置

函数名。然后所有的属性和方法都被直接赋予prototype属性。我重写了前面的例子,代码如下:

function Car() { };
//将所有的属性的方法都赋予prototype属性
Car.prototype.color = "blue";
Car.prototype.doors = 4;
Car.prototype.mpg = 25;
Car.prototype.showColor = function() {
     return this.color;
};
var Car1 = new Car();
var Car2 = new Car();
document.write(Car1.showColor()+"<br/>");//输出:blue
document.write(Car2.showColor());//输出:blue

在这段代码中,首先定义构造函数Car(),其中无任何代码。接下来的几行代码,通过给Car的 prototype 属性添加

属性去定义Car对象的属性。调用new Car()时,原型的所有属性都被立即赋予要创建的对象,意味着所有Car实例存

放的都是指向 showColor() 函数的指针。从语义上讲,所有属性看起来都属于一个对象,因此解决了前面的工厂方式

和构造函数方式存在的问题。

此外,使用这种方式,还能用 instanceof 运算符检查给定变量指向的对象的类型:

<span style="font-size:18px;">document.write(Car1 instanceof Car);	//输出:true</span>

原型方式看起来是个不错的解决方案。遗憾的是,它并不尽如人意。首先,这个构造函数没有参数。使用原型方

式,不能通过给构造函数传递参数来初始化属性的值,因为Car1和Car2的color属性都等于 "blue",doors属性都等于

4,mpg属性都等于25。这意味着必须在对象创建后才能改变属性的默认值,这点很令人讨厌,但还没完。真正的问

题出现在属性指向的是对象,而不是函数时。函数共享不会造成问题,但对象却很少被多个实例共享。请思考下面的

例子:

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() {
      return this.color;
};
var Car1 = new Car();
var Car2 = new Car();
Car1.drivers.push("Bill");
document.write(Car1.drivers+"<br/>");//输出:Mike,John,Bill
document.write(Car2.drivers);//输出 :Mike,John,Bill

上面的代码中,属性drivers是指向Array对象的指针,该数组中包含两个名"Mike"和 "John"。由于 drivers是引用

值,Car的两个实例都指向同一个数组。这意味着给Car1.drivers添加值 "Bill",在 Car2.drivers 中也能看到。输出这两

个指针中的任何一个,结果都是显示字符串 "Mike,John,Bill"。由于创建对象时有这么多问题,你一定会想,是否有种

合理的创建对象的方法呢?答案是有,需要联合使用构造函数和原型方式。

五混合的构造函数/原型方式(推荐使用)

混合使用构造函数方式和原型方式,就可像用其他程序设计语言一样创建对象。这种概念非常简单,即用构造函

数定义对象的所有非函数属性,用原型方式定义对象的函数属性(方法)。结果是,所有函数都只创建一次,而每个

对象都具有自己的对象属性实例。我们重写了前面的例子,代码如下:

function Car(Color,Doors,Mpg) {
  this.color = Color;
  this.doors = Doors;
  this.mpg = Mpg;
  this.drivers = new Array("Mike","John");
};
Car.prototype.showColor = function() {
         return this.color;
};
var Car1 = new Car("red",4,23);
var Car2 = new Car("blue",3,25);
Car1.drivers.push("Bill");
document.write(Car1.drivers+"<br/>");//输出:Mike,John,Bill
documnet.write(Car2.drivers);//输出:Mike,John

现在就更像创建一般对象了。所有的非函数属性都在构造函数中创建,意味着又能够用构造函数的参数赋予属性

默认值了。因为只创建showColor()函数的一个实例,所以没有内存浪费。此外,给Car1的drivers数组添加 "Bill" 值,

不会影响到Car2的数组,所以输出这些数组的值时,Car1.drivers 显示的是 "Mike,John,Bill",而 Car2.drivers 显示的

是 "Mike,John"。因为使用了原型方式,所以仍然能利用 instanceof运算符来判断对象的类型。

这种方式是ECMAScript采用的主要方式,它具有其他方式的特性,却没有他们的副作用。不过,有些开发者仍觉

得这种方法不够完美。

六动态原型方式

对于习惯使用其他语言的开发者来说,使用混合的构造函数/原型方式感觉不那么和谐。毕竟,定义类时,大多数

面向对象语言都对属性和方法进行了视觉上的封装。请考虑下面的 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);
  }
}

Java很好地打包了Car类的所有属性和方法,因此看见这段代码就知道它要实现什么功能,它定义了一个对象的

信息。批评混合的构造函数/原型方式的人认为,在构造函数内部找属性,在其外部找方法的做法不合逻辑。因此,他

们设计了动态原型方法,以提供更友好的编码风格。

动态原型方法的基本想法与混合的构造函数/原型方式相同,即在构造函数内定义非函数属性,而函数属性则利用

原型属性定义。唯一的区别是赋予对象方法的位置。下面是用动态原型方法重写的Car:

function Car(Color,Doors,Mpg) {
  this.color = Color;
  this.doors = Doors;
  this.mpg = Mpg;
  this.drivers = new Array("Mike","John");
  //如果Car对象中的_initialized为undefined,表明还没有为Car的原型添加方法
  if (typeof Car._initialized == "undefined") {
         Car.prototype.showColor = function() {
                return this.color;
         };
         Car._initialized = true; //设置为true,不必再为prototype添加方法
  }
}
var Car1 = new Car("red",4,23);//生成一个Car对象
var Car2 = new Car("blue",3,25);
Car1.drivers.push("Bill");//向Car1对象实例的drivers属性添加一个元素
document.write(Car1.drivers+"<br/>");//输出:Mike,John,Bill
document.write(Car2.drivers);//输出:Mike,John

直到检查typeof Car._initialize是否等于"undefined"之前,这个构造函数都未发生变化。这行代码是动态原型方法

中最重要的部分。如果这个值未定义,构造函数将用原型方式继续定义对象的方法,然后把 Car._initialized设置为

true。如果这个值定义了(它的值为 true时,typeof 的值为Boolean),那么就不再创建该方法。简而言之,该方法使用

标志(_initialized)来判断是否已给原型赋予了任何方法。该方法只创建并赋值一次,传统的 OOP开发者会高兴地发

现,这段代码看起来更像其他语言中的类定义了。

我们应该采用哪种方式呢?

如前所述,目前使用最广泛的是混合的构造函数/原型方式。此外,动态原型方式也很流行,在功能上与构造函

数/原型方式等价。可以采用这两种方式中的任何一种。不过不要单独使用经典的构造函数或原型方式,因为这样会给

代码引入问题。总之JS是基于面向对象的一门客户端脚本语言,我们在学习它的面向对象技术的时候要的留意JS与

其他严谨性高的程序语言的不同。也要正确使用JS创建对象的合理的方式,推荐使用构造函数与原型方式的混合方式

创建对象实例。这样可以避免许多不必要的麻烦。

时间: 2024-11-25 04:24:15

轻松学习JavaScript十二:JavaScript基于面向对象之创建对象(二)的相关文章

JavaScript学习总结(十四)——JavaScript编写类的扩展方法

在?J?a?v?a?S?c?r?i?p?t?中?可以使?用?类的p?r?o?t?o?t?y?p?e属性来?扩?展?类的属?性?和?方?法,在实际开发当中,当JavaScript内置的那些类所提供的动态方法和动态属性不满足我们实际开发时,我们就可以通过"prototype"属性给自定义类添加方法和属性或者扩展原有的类中的方法和属性. 一.扩展JavaScript内置类,添加动态方法 语法格式: 类名.prototype.方法名 = function([param1],[param2],.

JavaScript学习总结(十六)——Javascript闭包(Closure)

原文地址: http://www.cnblogs.com/xdp-gacl/p/3703876.html 闭包(closure)是Javascript语言的一个难点,也是它的特色, 很多高级应用都要依靠闭包实现.很早就接触过闭包这个概念了,但是一直糊里糊涂的,没有能够弄明白JavaScript的闭包到底是什么,有什么用,今天 在网上看到了一篇讲JavaScript闭包的文章(原文链接), 讲得非常好,这下算是彻底明白了JavaScript的闭包到底是个神马东东以及闭包的用途了,在此写出来和大家分

[F#, Basic] 轻松学习系列 ( 20 ) ─ 循环设计 之 计数循环 (二)

Visual F# 在前一文介绍的是 for -- to -- 的语法,今天再介绍?? for -- downto -- 的语法啰! ★ 语法说明: 1: for ... downto ... 计数循环 - 语法说明 2: for = downto do 3: ? ? ★ 范例程序: 1: // Nobel Hsieh ( http://www.dotblogs.com.tw/nobel12 ) 2: open System;; 3:? 4: // for ... downto ... 计数循环

Linux学习(十九)软件安装与卸载(二)更换yum源

一.简介 系统自带的源数量有限,而且是国外的源,速度肯定不如国内的.而断网的时候,本地源就可以派得上用处.而RPMForge源是传说中规模最大的一个源.那么接下来我们就来分别配一下本地源,国内源,RPMForge源. 此外还可以安装一个扩展源.下面我们也会讲到. 二.本地源的配置 方法: (图1) 实验: vim Centos-DVD.repo [dvd] name= install dvd baseurl=file:///mnt enable=1 gpcheck=0 然后清楚缓存看看: [[e

SPRING学习(十九)--基于注解的AOP切面编程

上篇中介绍了基于XML配置的AOP切面编程,除了XML配置AOP切面编程外,还可以通过注解方式实现AOP切面编程,本篇通过一个小例子来介绍基于注解的AOP编程. 1.在spring中使用AOP变成,不止要导入spring-aop.jar,还需要导入spring-aspects.jar.aspectjweaver.jar和aopalliance.jar,但是aspectjweaver.jar被spring-aspects.jar依赖,aopalliance.jar被spring-aop.jar依赖

Java学习 (十四)、面向对象编程(六)抽象类

抽象的定义:抽象是把多个事物的共性的内容抽取出来,本质就是把我们关注的内容抽取出来.(比如:宝马,奔驰都属于汽车,汽车是抽象出来的概念); 抽象类:Java中可以定义没有方法体的方法,该方法由其子类来具体的实现.该没有方法体的方法我们称之为抽象方法,含有抽象方法的类我们称之为抽象类; 抽象方法特点: ①   只有方法头没有方法体的方法称为抽象方法: ②   抽象方法用abstract来修饰; ③   抽象方法代表一种不确定的操作或行为; ④   抽象方法不能被调用; 抽象类的特点: ①   定义

【转】JMeter学习(十八)JMeter测试Java(二)

实例: 服务为:将输入的两个参数通过IO存入文件: 1.打开MyEclipse,编写Java代码 服务: package test; import java.io.File; import java.io.PrintWriter; public class OutputService { public static void output(String filename, int a, int b) throws Exception { PrintWriter out = new PrintWr

JMeter学习(十八)JMeter测试Java(二)

实例: 服务为:将输入的两个参数通过IO存入文件: 1.打开MyEclipse,编写Java代码 服务: package test; import java.io.File; import java.io.PrintWriter; public class OutputService { public static void output(String filename, int a, int b) throws Exception { PrintWriter out = new PrintWr

西门子PLC学习笔记十八-(带参数FC编程二)

本篇仍是对带参数的FC编程的练习,本次实现功能: 一共有4台电机,每台电机都要求Y-△降压启动.启动时,按下启动按钮,M1电机启动,然后每隔10s启动一台,最后M1到M4四台电机全部启动.当按下停止按钮时,M4先停止,过10s后M3在停止,再过10s后M2停止,再过10sM1电机停止.同时任一台电机启动时,控制电源的接触器和Y形接法的接触器接通电源6s后,Y形接触器断开,1s后△接触器动作接通. 本需求程序实现代码如下: 1.符号表 2.功能块编写代码如下 3.主程序编写如下 本程序代码可在此下