推广 热搜: csgo  vue  angelababy  2023  gps  新车  htc  落地  app  p2p 

JavaScript定义类或对象

   2023-06-24 网络整理佚名1840
核心提示:拥有很多创建对象或类的方法。有些开发者在工厂函数外定义对象的方法,然后通过属性指向该方法,从而避免这个问题:结果是,所有函数都只创建一次,而每个对象都具有自己的对象属性实例。毕竟,定义类时,大多数面向对象语言都对属性和方法进行了视觉上的封装。Car类的所有属性和方法,因此看见这段代码就知道它要实现什么功能,它定义了一个对象的信息。

本来想写一篇关于如何定义类或者对象生成的文章,但是仔细比较之后,还是用高级教程来讲解比较合理,所以直接把文章转发到这里了。

更多内容请参见: 。

注:核心。

使用预定义的对象只是面向对象语言的一部分; 它的真正威力在于能够创建您自己的专门类和对象。

有许多创建对象或类的方法。

工厂方式 原始方式

由于对象属性可以在对象创建后动态定义,因此许多开发人员在首次引入对象属性时会编写如下代码:

var oCar = new Object;
oCar.color = "blue";
oCar.doors = 4;
oCar.mpg = 25;
oCar.showColor = function() {
  alert(this.color);
}; 

DIY

在上面的代码中,创建了对象 car。 然后赋予它一些属性:它是蓝色的,有四扇门,每加仑可行驶 25 英里。 最后一个属性实际上是一个指向函数的指针,这意味着该属性是一个方法。 执行这段代码后,对象car就可以使用了。

不过,这里有一个问题,就是可能需要创建多个car实例。

解决方案:工厂方法

为了解决这个问题,开发人员创建了工厂函数 ( ),用于创建并返回特定类型的对象。

例如,()可用于封装前面列出的创建汽车对象的操作:

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(); 

DIY

在这里,第一个示例中的所有代码都包含在 () 函数中。 此外,还有一行额外的代码返回 car () 作为函数值。 调用此函数将创建一个新对象,为其提供所有必要的属性,并复制我们之前解释过的汽车对象。 因此,通过这种方法,我们可以轻松创建具有完全相同属性的汽车对象的两个版本(oCar1 和 oCar2)。

将参数传递给函数

我们还可以修改 () 函数,将每个属性的默认值传递给它,而不是简单地给属性一个默认值:

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);
var oCar2 = createCar("blue",3,25);
oCar1.showColor();		//输出 "red"
oCar2.showColor();		//输出 "blue" 

DIY

通过向 () 函数添加参数,可以为要创建的汽车对象的 color、doors 和 mpg 属性赋值。 这使得两个对象具有相同的属性但属性值不同。

在工厂函数之外定义对象的方法

尽管日益形式化,但创建对象的方法却被忽视了,其规范化到今天仍然是反对的。 部分是出于语义原因(它看起来不像将 new 运算符与构造函数一起使用那么正式),部分是出于功能原因。 功能原因是必须以这种方式创建对象的方法。 在前面的示例中,每次调用 () 时,都会创建 new (),这意味着每个对象都有自己的 () 版本。 事实上,每个对象共享相同的功能。

有些开发者通过在工厂函数之外定义对象的方法,然后通过属性指向该方法来避免这个问题:

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;
}
var oCar1 = createCar("red",4,23);
var oCar2 = createCar("blue",3,25);
oCar1.showColor();		//输出 "red"
oCar2.showColor();		//输出 "blue" 

DIY

在上面重写的代码中,() 是在 () 之前定义的。 在 () 内部,为对象分配一个指向现有 () 函数的指针。 从功能上来说,这解决了重复创建函数对象的问题; 但从语义上讲,该函数看起来不像对象的方法。

所有这些问题导致了开发人员定义的构造函数的出现。

构造方法

创建构造函数就像创建工厂函数一样简单。 第一步是选择类名,即构造函数的名称。 按照约定,该名称的首字母大写,以区别于首字母通常为小写的变量名称。 除了这个区别之外,构造函数看起来很像工厂函数。 考虑以下示例:

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); 

DIY

下面解释一下上面代码和工厂方法的区别。 首先,对象不是在构造函数内部创建的,而是使用了this关键字。 使用new运算符构造函数时,在执行第一行代码之前就创建了一个对象,并且只能用this来访问该对象。 然后你可以直接分配这个属性,默认情况下它是构造函数的返回值(不必显式使用运算符)。

现在,使用 new 运算符和类名 Car 创建对象更像是通常创建对象的方式。

您可能会问,这种方式在管理功能方面是否与之前的方式存在同样的问题? 是的。

与工厂函数一样,构造函数重新生成函数,为每个对象创建该函数的单独版本。 然而,与工厂函数类似,构造函数也可以被外部函数覆盖,同样,这样做没有语义意义。 这就是下面要讨论的原型方法的优点所在。

原型

该方法利用了对象的属性,可以将对象视为创建新对象的原型。

在这里,类名首先使用空构造函数设置。 然后,所有属性和方法都直接分配给属性。 我们重写前面的例子,代码如下:

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(); 

DIY

在此代码中,首先定义构造函数 (Car),无需任何代码。 接下来的几行代码通过向 Car 的属性添加属性来定义 Car 对象的属性。 当调用 new Car() 时,原型的所有属性立即分配给要创建的对象,这意味着所有 Car 实例都存储指向 () 函数的指针。 从语义上讲,所有属性似乎都属于一个对象,从而解决了前两种方法的问题。

此外,通过这种方式,可以使用运算符来检查给定变量所指向的对象的类型。 因此,以下代码将输出 TRUE:

alert(oCar1 instanceof Car);	//输出 "true" 

原型设计问题

原型设计方法看起来是一个很好的解决方案。 不幸的是,它并不完全是应有的样子。

首先,这个构造函数没有参数。 使用原型方法,无法通过向构造函数传递参数来初始化属性的值,因为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() {
  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" 

DIY

在上面的代码中,属性是一个指向包含两个名字“Mike”和“John”的Array对象的指针。 由于它们是引用值,因此 Car 的两个实例都指向同一个数组。 这意味着将值“Bill”添加到 oCar1。 在 oCar2 中也可以看到。 打印这两个指针中的任何一个都会产生字符串“Mike,John,Bill”。

面对这么多的对象创建问题,你一定想知道,有没有一种合理的方法来创建对象呢? 答案是肯定的,你需要一起使用构造函数和原型。

混合构造函数/原型方法

结合构造函数和原型,可以像任何其他编程语言一样创建对象。 这个概念很简单,就是用构造函数定义对象的所有非函数属性,用原型定义对象的函数属性(方法)。 结果是所有函数都被创建一次,而每个对象都有自己的对象属性实例。

我们重写前面的例子,代码如下:

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" 

DIY

现在它更像是创建一个普通的对象。 所有非函数属性都是在构造函数中创建的,这意味着可以使用构造函数参数再次为属性分配默认值。 由于只创建了 the() 函数的一个实例,因此不会浪费任何内存。 另外,将“Bill”值添加到oCar1数组中不会影响oCar2数组,因此在输出这些数组的值时,oCar1. 显示“Mike,John,Bill”,而 oCar2. 显示“迈克,约翰”。 因为使用了原型方法,所以仍然可以使用运算符来判断对象的类型。

这种方法是采用的主要方法,它具有其他方法的特性,但没有副作用。 然而,一些开发人员仍然认为这种方法并不完美。

动态原型法

对于习惯其他语言的开发人员来说,使用混合构造函数/原型方法感觉不太和谐。 毕竟,大多数面向对象语言在定义类时都会直观地封装属性和方法。 考虑以下 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(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;
  }
} 

DIY

该构造函数在检查 Car. 等于“”。 这行代码是动态原型方法中最重要的部分。 如果这个值未定义,构造函数会继续以原型的方式定义对象的方法,然后设置Car。 为真。 如果定义了该值(当其值为 true 时, 的值为 is),则永远不会创建该方法。 简而言之,该方法使用 flags() 来判断原型是否已被赋予任何方法。 该方法仅创建和分配一次,传统的 OOP 开发人员会很高兴地发现这段代码看起来更像其他语言中的类定义。

混合工厂方法

当以前的方法无法应用时,此方法通常是一种解决方法。 它的目的是创建仅返回另一种对象的新实例的假构造函数。

这段代码看起来与工厂函数非常相似:

function Car() {
  var oTempCar = new Object;
  oTempCar.color = "blue";
  oTempCar.doors = 4;
  oTempCar.mpg = 25;
  oTempCar.showColor = function() {
    alert(this.color);
  };
  return oTempCar;
} 

DIY

与经典方式不同,这种方式使用了 new 运算符,使其看起来像一个真正的构造函数:

var car = new Car(); 

由于 new 运算符是在 Car() 构造函数内部调用的,因此第二个 new 运算符(位于构造函数外部)将被忽略,构造函数内部创建的对象将被传递回变量 car。

这种方法与有关对象方法内部管理的经典方法存在相同的问题。 强烈建议:除非绝对必要,否则避免使用此方法。

哪一条路

如前所述,最广泛使用的一种是混合构造函数/原型方法。 此外,动态原始方法很流行,并且在功能上等同于构造函数/原型方法。 这两种方式都可以使用。 但不要单独使用经典的构造函数或原型方法,因为这会给代码带来问题。

例子

关于对象的有趣的事情之一是它们用于解决问题的方式。 最常见的问题之一是字符串连接的性能。 与其他语言类似,中的字符串是不可变的,即它们的值不能更改。 考虑以下代码:

var str = "hello ";
str += "world"; 

事实上,这段代码在幕后执行的步骤如下:

创建一个存储“hello”的字符串。 创建一个字符串来存储“world”。 创建一个字符串来存储连接的结果。 将 str 的当前内容复制到结果中。 将“世界”复制到结果中。 更新 str 使其指向结果。

每次完成字符串连接时都会执行步骤 2 到 6,这使得该操作非常消耗资源。 如果这个过程重复数百甚至数千次,可能会导致性能问题。 解决方案是使用 Array 对象来存储字符串,然后使用 join() 方法(使用空字符串参数)创建最终字符串。 想象一下用以下代码替换前面的代码:

var arr = new Array();
arr[0] = "hello ";
arr[1] = "world";
var str = arr.join(""); 

这样,数组中引入多少个字符串并不重要,因为连接操作仅在调用 join() 方法时发生。 此时,需要执行的步骤如下:

创建字符串来存储结果 将每个字符串复制到结果中的适当位置

虽然这个解决方案很好,但还有更好的方法。 问题是,这段代码并没有准确反映它的意图。 为了更容易理解,可以将功能封装成一个类:

function StringBuffer () {
  this._strings_ = new Array();
}
StringBuffer.prototype.append = function(str) {
  this._strings_.push(str);
};
StringBuffer.prototype.toString = function() {
  return this._strings_.join("");
}; 

这段代码中首先要注意的是属性,它是私有属性。 它只有两个方法,() 和 () 方法。 ()方法有一个参数,它将参数追加到字符串数组中,()方法调用数组的join方法,并返回实际连接的字符串。 要将一组字符串与一个对象连接起来,可以使用以下代码:

var buffer = new StringBuffer ();
buffer.append("hello ");
buffer.append("world");
var result = buffer.toString(); 

DIY

下面的代码可以用来测试对象和传统字符串连接方法的性能:

var d1 = new Date();
var str = "";
for (var i=0; i < 10000; i++) {
    str += "text";
}
var d2 = new Date();
document.write("Concatenation with plus: "
 + (d2.getTime() - d1.getTime()) + " milliseconds");
var buffer = new StringBuffer();
d1 = new Date();
for (var i=0; i < 10000; i++) {
    buffer.append("text");
}
var result = buffer.toString();
d2 = new Date();
document.write("
Concatenation with StringBuffer: " + (d2.getTime() - d1.getTime()) + " milliseconds"); 

DIY

此代码对字符串连接进行两次测试,第一个使用加号,第二个使用类。 每个操作连接 10000 个字符串。 日期值d1和d2用于确定操作需要何时完成。 请注意,创建 Date 对象时,如果没有参数,则为该对象赋予当前日期和时间。 要计算连接操作花费的时间,请相互减去日期的毫秒表示形式(由 () 方法返回)。 这是衡量性能的常用方法。 该测试的结果表明,与使用加号相比,使用类比可节省 50% - 66% 的时间。

 
反对 0举报 0 收藏 0 打赏 0评论 0
 
更多>同类资讯
推荐图文
推荐资讯
点击排行
网站首页  |  关于我们  |  联系方式  |  使用协议  |  版权隐私  |  网站地图  |  排名推广  |  广告服务  |  积分换礼  |  网站留言  |  RSS订阅  |  违规举报
Powered By DESTOON