Paglalarawan ng Class o Object ng ECMAScript

Ang paggamit ng predefinidong bagay ay bahagi lamang ng kakayahan ng oopisyal na wika, ang tunay na kalakasan nito ay ang kakayahan gumawa ng sariling klase at bagay.

Ang ECMAScript ay may maraming paraan para gumawa ng bagay o klase.

Ang paraan ng pabrika

Ang orihinal na paraan

Bilang ang mga katangian ng bagay ay maaaring ayosin dinamikong matapos magawa ang bagay, maraming mga tagapagpabulang kung saan ay isinulat ang katulad ng sumusunod na kodigo noong una na inilunsad ang JavaScript:

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

TIY

Sa itaas na code, nilikha ang bagay na car. Pagkatapos, ito ay naka-set ng ilang attribute: ang kanyang kulay ay asul, may apat na pinto, at maaaring magtala ng 25 milya bawat galon ng gasolina. Ang huling attribute ay isang pointer na patungo sa function, na nangangahulugan na ang attribute na ito ay isang method. Pagkatapos ng pagpapatupad ng ganitong code, maaaring gamitin ang bagay na car.

Gayunman, may isang problema dito, na kailangan ng paglikha ng maraming insatansya ng car.

Solyusyon: Paraan ng Factory

Para malutas ang problema na ito, nilikha ng mga developer ang mga factory function na nagpapalabas at nagbibigay ng mga bagay na may tiyak na uri ng bagay.

Halimbawa, ang function na createCar() ay maaaring gamitin upang isangkayin ang mga operasyon ng paglikha ng bagay na car na naiulat sa itaas:

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

TIY

Dito, ang lahat ng code sa unang halimbawa ay kasama sa function na createCar(). Gayundin, mayroon pang isang dagdag na code, na ibabalik ang bagay na car (oTempCar) bilang halaga ng function. Ang pagtawag sa function, ay maglikha ng bagong bagay, at magbibigay ng lahat ng kinakailangang attribute, at kopyahin ang car na naiulat namin sa nakaraang parte. Sa pamamagitan ng ganitong paraan, maaari naming madaling gumawa ng dalawang bersyon ng bagay na car (oCar1 at oCar2), na may ganap na kaparehong attribute.

Ipagbigay ng argumento sa function

Maaari nating baguhin ang function na createCar(), upang ipasa ang mga default na halaga ng mga attribute, sa halip na magbigay ng default na halaga ng mga attribute:

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();		// Output "red"
oCar2.showColor();		// Output "blue"

TIY

Magdagdag ng argumento sa function na createCar(), upang magbigay ng halaga ng mga attribute na color, doors at mpg ng bagay na car na ito. Ito ay nagbibigay-daan sa dalawang bagay na may magkaparehong attribute, ngunit may magkakaibang halaga ng attribute.

Tinutukoy ang mga paraan ng bagay sa labas ng factory function

Kahit na ang ECMAScript ay laging nagiging mas formalye, ang paraan ng paglikha ng mga bagay ay napapabayaan, at ang kanilang pagtutulad ay patuloy na pinaghihinalaan. Ilang ay dahil sa dahilan ng kahulugan (walang tila mas napakahusay na gamit ang constructor function na may new operator), at iba ay dahil sa dahilan ng kalakip. Ang dahilang kalakip ay ang paglikha ng mga bagay gamit ang ganitong paraan. Sa nakaraang halimbawa, bawat pagtawag sa function na createCar(), kailangan lumikha ng bagong function na showColor(), na nangangahulugan na ang bawat bagay ay may sariling version ng showColor(). Gayunpaman, ang bawat bagay ay nakikilala ng isang function.

Mayroong mga developer na naglalagay ng method ng object sa labas ng factory function, at ipinapunta ng attribute ang method na ito, upang maiwasan ang problema na ito:

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();		// Output "red"
oCar2.showColor();		// Output "blue"

TIY

Sa pagsusulat ng code na ito, inilagay sa unang lugar ang function na showColor() bago ang function na createCar(). Sa loob ng createCar(), inilagay ang pointer ng object na patungo sa naunang existing na function na showColor(). Sa pakikitungo ng function, ito ay naglutas ng problema ng pagpapakilala ng function object ng mga ulit; ngunit masyadong hindi parang method ng object ang function na ito.

Ang lahat ng mga problemang ito ay nagpalakas ngNagpalakas ng developerng pagkakaroon ng construction function.

Paraan ng construction function

Ang paglikha ng constructor ay tulad ng madaling gumawa ng factory function. Unang hakbang, pumili ng pangalan ng klase, o pangalan ng constructor. Ayon sa tradisyon, ang unang titik ng pangalan na ito ay may malaking titik upang magiba sa pangalan ng variable na may maliliit na titik. Maliban sa ito, ang constructor ay kahawig sa factory function. Isipin natin ang sumusunod na halimbawa:

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

TIY

Nakakapagpaliwanag sa iyo ang pagkakaiba ng code at ang paraan ng gawaan. Una, walang nilikha ng object sa loob ng constructor, gamit ang keyword na this. Kapag gumagamit ng operator na new sa constructor, nilikha muna ng object bago ipatupad ang unang linya ng code, at kailangan lang ang this upang ma-access ang object. Pagkatapos, maaring i-assign sa attribute ng this, sa mga kasalukuyang kundisyon ay ang balaang return ng constructor (hindi kailangan malinaw na gamitin ang operator na return).

Ngayon, kapag gumagamit ng operator new at pangalan ng klase Car, mas katulad na ito sa paglikha ng pangkaraniwang object sa ECMAScript.

Maaring tanungin mo, ang problema sa pagpamamahala ng function ng ito ay magkakaroon ng kapareho ng unang paraan? Oo.

Katulad ng factory function, ang constructor ay magpapakabit ng function, at magbibigay ng isang hiwalay na bersyon ng function para sa bawat bagay. Gayunpaman, katulad ng factory function, maari mo ring gumamit ng panlabas na function upang magsulat ng constructor, gayon din, ito ay walang kahulugan sa semantiko. Ito ay ang kapakinabangan ng paraan ng prototype na ito.

Ang paraan ng prototype

Ang paraan ng prototype ay gumagamit ng attribute ng object, maaring itong isiping prototype na pinagmumulan ng bagay na gagawin sa paglikha ng bagay.

Dito, unang gamitin ang walang-laman na constructor upang itakda ang pangalan ng klase. Ang lahat ng attribute at method ay ipinapahintulot sa prototype attribute. Nagpapaiba ang aming halimbawa sa sumusunod, ang code na ito:

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

TIY

Sa kasunduan na ito, unang nilagay ang constructor (Car), walang kahit anong code. Ang mga sumunod na linya ng code, ay nagdaragdag ng mga attribute sa prototype ng Car upang tuluyan ang mga attribute ng Car object. Kapag tinawag ang new Car(), ang lahat ng attribute ng prototype ay maibigay sa bagay na gagawin, na nangangahulugan na lahat ng instance ng Car ay magkakaroon ng pointer na patungo sa function na showColor(). Sa semantiko, ang lahat ng attribute ay mukhang kabilang sa isang bagay, kaya nagsasagot ito sa problema ng dalawang paraan na ito.

Bilang karagdagan, sa paraan na ito, maari mo ring gumamit ng operator instanceof upang suriin ang uri ng object na tumutukoy ng variable. Kaya, ang sumusunod na code ay maglalabas TRUE:

alert(oCar1 instanceof Car);	// Maglalabas "true"

Ang problema ng paraan ng prototype

Ang paraan ng prototype ay mukhang magandang solusyon. Hindi naman nasisiyahan ang aming asal.

Unang-ungang, ang constructor na ito ay walang argumento. Sa paraan ng prototype, hindi mo magagamit na ipasa ang argumento sa constructor upang inilalagay ang halaga ng attribute, dahil ang attribute ng color ng Car1 at Car2 ay magkapareho na "blue", ang attribute ng doors ay magkapareho na 4, at ang attribute ng mpg ay magkapareho na 25. Ito ay nangangahulugan na dapat baguhin ang default na halaga ng attribute pagkatapos lumikha ang bagay, ito ay lubos na nakakainis, ngunit wala pang tapos. Ang tunay na problema ay dumating kapag ang attribute ay tumutukoy sa bagay, hindi sa function. Ang function sharing ay hindi magiging problema, ngunit ang object ay bihira na sa pagiging shared ng maraming instance. Isipin mo ang sumusunod na halimbawa:

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);	// lumalabas "Mike,John,Bill"
alert(oCar2.drivers);	// lumalabas "Mike,John,Bill"

TIY

Sa code na ito, ang attribute na drivers ay isang pointer na patungo sa Array object, na may dalawang pangalan na "Mike" at "John". Dahil ang drivers ay isang reference value, ang dalawang instance ng Car ay patungo sa parehong array. Ibig sabihin na ang pagdagdag ng halaga na "Bill" sa oCar1.drivers, makikita din ito sa oCar2.drivers. Lumabas ang string "Mike,John,Bill" kung sakaling ipakita ang alinman sa dalawang pointer.

Dahil may maraming problema sa paggawa ng bagay, masasabi mo, mayo ba ring makatuwirang paraan sa paggawa ng bagay? Ang sagot ay mayo, kailangan gamitin ang constructor at prototype na paraan.

Hymayang constructor/prototype na paraan

Kasama ang paggamit ng constructor at prototype na paraan, maaaring gumawa ng mga bagay tulad ng ibang wika ng programadik. Ang konsepto na ito ay napakasimple, ibig sabihin gumamit ng constructor upang magbigay ng lahat ng hindi function na attribute ng bagay, at gumamit ng prototype na paraan upang magbigay ng function na attribute (method) ng bagay. Ang resulta, lahat ng function ay binubuo lamang isang beses, at ang bawat bagay ay may sariling instance ng attribute ng bagay.

Nagpalit naming ng eksempyo sa nakaraan, ang code ay tulad nito:

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);	// lumalabas "Mike,John,Bill"
alert(oCar2.drivers);	// lumalabas "Mike,John"

TIY

Ngayon masama'y parang gumagawa ng pangkaraniwang bagay. Lahat ng hindi function na attribute ay ginawa sa constructor, ibig sabihin masasagawa na maaaring gamitin ang mga argumento ng constructor upang magbigay ng default na halaga ng mga attribute. Dahil binubuo lamang ng isang instance ng function na showColor(), walang pagkakalugi ng memory. Gayundin, ang pagdagdag ng halaga na "Bill" sa array ng drivers ng oCar1 ay hindi apektuhin ang array ng oCar2, kaya't ang lumalabas na halaga ng mga array ay "Mike,John,Bill" para sa oCar1.drivers at "Mike,John" para sa oCar2.drivers. Dahil sa paggamit ng prototype na paraan, maari pa ring gamitin ang operator na instanceof upang matukoy ang uri ng mga bagay.

Ang paraan na ito ay pangunahing ginagamit ng ECMAScript, ito ay may mga katangian ng iba pang paraan, ngunit walang kanilang mga epekto. Gayunman, mayroon pang ilang nag-aalaga ng kodigo na nangangamba na ang paraan na ito ay hindi ganap na ganap.

Dinamikong pangmaliwak na pamamaraan

Para sa mga nag-aalaga ng iba pang wika na nag-aalaga ng pinagsamang konstraktor/halimbawa na pangmaliwak na paraan, ito ay hindi maganda. Sa katunayan, kapag tinatayo ng klase, karamihan sa mga orientadong bagay na object na wika ay nagbibigay ng panlabas na pagsasanggalang sa mga katangian at mga paraan. Isipin mo ang klase ng Java sa ibaba:

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 ay mahusay na nagpack ng lahat ng mga katangian at mga paraan ng klase ng Car, kaya alam mo na ano ang ginagawa ng kodigo na ito, ito ay naglalarawan ng impormasyon ng isang bagay. Sinasabi ng mga kritiko ng pinagsamang konstraktor/halimbawa na pangmaliwak na paraan na ang paghahanap ng mga katangian sa loob ng konstraktor at sa labas ng mga paraan ay hindi makatotohanan. Kaya nila dinisenyo ang dinamikong pangmaliwak na pamamaraan upang magbigay ng mas magandang pamamaraan ng pagsusulat ng kodigo.

Dinamikong pangmaliwak na pamamaraan ng pangunahing ideya ay katulad sa pinagsamang konstraktor/halimbawa na pangmaliwak na paraan, saan naglalarawan ng mga hindi function na katangian sa loob ng konstraktor, at ang function na katangian ay ginagamit ang katangian ng halimbawa. Ang tanging pagkakaiba ay ang posisyon ng pagbibigay ng mga paraan sa mga bagay. Ito ay ang klase ng Car na muling nilikha gamit ang dinamikong pangmaliwak na pamamaraan:

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

TIY

Ang constructor na ito ay hindi nagbago hanggang sa suriin ang typeof Car._initialized ay magiging "undefined" bago. Ang linya ng code na ito ay ang pinakamahalagang bahagi ng dynamic prototype method. Kung ang halaga ng ito ay wala (ang halaga nito ay true kung ang halaga nito ay Boolean), ang constructor ay magpatuloy sa pagpapatibay ng mga method ng object sa paraan ng prototype, at mag-set ng Car._initialized sa true. Kung ang halaga nito ay nabanggit (ang halaga nito ay true kung ang halaga nito ay Boolean), hindi na ito maglilikha ng paraan. Sa madaling sabi, ang method na ito ay gumagamit ng tanda (ang _initialized) upang makita kung naibigay na ang anumang method sa prototype. Ang method na ito ay binubuo at binibigay lamang sa unang pagkakataon, na ang mga developer na gumagamit ng tradisyonal na OOP ay magiging masaya na ang code na ito ay parang paglilingkod ng class sa ibang wika.

Mixed factory approach

Ang paraan na ito ay pangalawang opsyon kapag hindi mo maaaring gamitin ang unang paraan. Ang layunin nito ay gumawa ng paliwanag na constructor, na ibibigay lamang ang bagong instance ng ibang object.

Ang code na ito ay parang mukhang mukhang factory function:

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

TIY

Tulad ng klasikong paraan, ang paraan na ito ay gumagamit ng new operator, na gawin ito katulad ng tunay na constructor:

var car = new Car();

Dahil ang new operator ay tinawagan sa loob ng constructor function na Car(), ang ikalawang new operator (nasa labas ng constructor) ay itututulan, at ang object na binuo sa loob ng constructor ay ipapasa sa variable na car.

Ang paraan na ito ay may katulad na problema sa pangangalaga ng mga method ng object sa loob ng klasikong paraan. Matinding inaanyayahan: Huwag gumamit ng paraan na ito maliban sa kung mayroon kang walang dahilan.

Anong paraan ang gagamitin

Tulad ng nabanggit, ang pinakamalawak na ginagamit ngayon ay ang mixed constructor/protoype approach. Sa karagdagan, ang dynamic primitive methods ay lubos na kasinayaan, na katumbas sa function ng constructor/protoype approach. Maaaring gamitin ang alinman sa dalawa na paraan. Subalit huwag gumamit ng klasikong constructor o prototype approach nang mag-isa, dahil ito ay magdudulot ng problema sa code.

Isang halimbawa

Isang kagiliw-giliw na katangian ng mga bagay na ginagamit nila upang lutasin ang problema. Ang pinakakaraniwang problema sa ECMAScript ay ang pagkakabit ng performance ng string concatenation. Katulad ng ibang wika, ang mga string sa ECMAScript ay hindi nagbabago, ibig sabihin ang kanilang halaga ay hindi mababago. Isipin ang sumusunod na code:

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

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

  1. 创建存储 "hello " 的字符串。
  2. 创建存储 "world" 的字符串。
  3. 创建存储连接结果的字符串。
  4. 将 str 的当前内容复制到结果中。
  5. 将 "world" 复制到结果中。
  6. 更新 str,使其指向结果。

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

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

这样,无论数组中引入多少字符串都不成问题,因为只在调用 join() 方法时才会进行连接操作。此时,执行步骤如下:

  1. 创建存储结果的字符串
  2. 将每个字符串复制到结果中的合适位置

尽管这种解决方案不错,但还有更好的方法。问题是,这段代码无法准确表达其意图。为了使其更容易理解,可以使用 StringBuffer 类封装该功能:

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

注意此代码的 strings 属性,它原本是私有属性。它包含两个方法,即 append() 和 toString() 方法。append() 方法有一个参数,它将此参数附加到字符串数组中,toString() 方法调用数组的 join 方法,返回真正连接成的字符串。要使用 StringBuffer 对象连接一组字符串,可以使用以下代码:

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

TIY

Maaaring gamitin ang sumusunod na kodigo upang subukan ang pagganap ng klase na StringBuffer at ang tradisyonal na paraan ng pagkakasambong ng string:

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("<br />Concatenation with StringBuffer: ")
 + (d2.getTime() - d1.getTime()) + " milliseconds");

TIY

Ang ganitong kodigo ay gumagawa ng dalawang pagsusuri sa pagkakasambong ng string, ang una ay gamit ang plus, ang ikalawa ay gamit ang klase na StringBuffer. Ang bawat operasyon ay nagkakasambong ng 10000 na string. Ang mga halaga ng petsa na d1 at d2 ay ginagamit para sa pagtitingnan kung anong oras ang kinakailangan para sa operasyon. Mangyaring pagsintaan, kapag ang Date object ay nilikha at walang parametro, ang petsa at oras na pinagbigay sa object ay ang kasalukuyang petsa at oras. Upang makalkula kung gaano katagal ang operasyon, puwedeng i-minus ang mga pangalang-milis ng petsa (sa pamamagitan ng getTime() method na ibinabalik). Ito ay isang karaniwang paraan para sa pagtitingnan ng pagganap ng JavaScript. Ang resulta ng pagsusuri ay makakatulong sa iyo upang paghahambing ang kahusayan ng paggamit ng klase na StringBuffer at ng plus.