Muabali wa Class au Muabali wa Kina cha KiECMAScript
- Mwakilishi wa kwanza Muundo wa muundo wa kifaa
- Mwakilishi wa kina Kuhariri muundo
Kutumia kiume kizitoa ni sehemu ya kina ya lugha ya kibinafsi, ina uwezo mkubwa sana kwa kuzingatia kikemia na kiume zilizotengenezwa kwa kuzingatia.
ECMAScript ina vifaa vya kuzingatia kiume au kikemia zaidi.
Mwendo wa kuzingatia wa kifaa
Mwendo wa kuzingatia wa kwanza
Kwa sababu ya kufanya kwamba ujumbe wa kiume unaweza kutengenezwa kwa sababu ya kuzingatia, wenginezaji wengi waliandika kwa sababu ya JavaScript ilianzishwa kama kifaa chini:
var oCar = new Object; oCar.color = "blue"; oCar.doors = 4; oCar.mpg = 25; oCar.showColor = function() { alert(this.color); };
在上面的代码中,创建对象 car。然后给它设置几个属性:它的颜色是蓝色,有四个门,每加仑油可以跑 25 英里。最后一个属性实际上是指向函数的指针,意味着该属性是个方法。执行这段代码后,就可以使用对象 car。
不过这里有一个问题,就是可能需要创建多个 car 的实例。
解决方案:工厂方式
要解决该问题,开发者创造了能创建并返回特定类型的对象的工厂函数(factory function)。
例如,函数 createCar() 可用于封装前面列出的创建 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();
在这里,第一个例子中的所有代码都包含在 createCar() 函数中。此外,还有一行额外的代码,返回 car 对象(oTempCar)作为函数值。调用此函数,将创建新对象,并赋予它所有必要的属性,复制出一个我们在前面说明过的 car 对象。因此,通过这种方法,我们可以很容易地创建 car 对象的两个版本(oCar1 和 oCar2),它们的属性完全一样。
为函数传递参数
我们还可以修改 createCar() 函数,给它传递各个属性的默认值,而不是简单地赋予属性默认值:
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"
给 createCar() 函数加上参数,即可为要创建的 car 对象的 color、doors 和 mpg 属性赋值。这使两个对象具有相同的属性,却有不同的属性值。
在工厂函数外定义对象的方法
虽然 ECMAScript 越来越正式化,但创建对象的方法却被置之不理,且其规范化至今还遭人反对。一部分是语义上的原因(它看起来不像使用带有构造函数 new 运算符那么正规),一部分是功能上的原因。功能原因在于用这种方式必须创建对象的方法。前面的例子中,每次调用函数 createCar(),都要创建新函数 showColor(),意味着每个对象都有自己的 showColor() 版本。而事实上,每个对象都共享同一个函数。
有些开发者在工厂函数外定义对象的方法,然后通过属性指向该方法,从而避免这个问题:
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"
在上面这段重写的代码中,在函数 createCar() 之前定义了函数 showColor()。在 createCar() 内部,赋予对象一个指向已经存在的 showColor() 函数的指针。从功能上讲,这样解决了重复创建函数对象的问题;但是从语义上讲,该函数不太像是对象的方法。
Mashaka haya yote yana kuzingatiaKumekua kwa mwanasoftikikuzaliwa kwa kifaa cha kizalizo cha kifaa.
Mwendo wa kifaa cha kizalizo cha kifaa
Kumekua kifaa cha kizalizo cha kifaa kama kumekua kifaa cha kizalizo cha kifaa kina mafanikio. Mwanzo unapokea jina la kikundi, au jina la kifaa cha kizalizo cha kifaa. Kwa kawaida jina hilo linatokea kuanzia herufi kubwa, ili kusaidia kuzingatia jina hilo kama jina la mambo ambao jina lake linatokea kuanzia herufi kichwa chake. Kwa sababu ya hii tu, kifaa cha kizalizo cha kifaa hupakana na kifaa cha kizalizo cha kifaa. Tuna mbinu hii:
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);
Hapa ni inaonyesha kipengele cha kipengele cha kifaa cha kizalizo cha kipengele. Kwanza kama hawakuwa kichukua kwa kifaa cha kizalizo cha kizalizo cha kifaa, bali kutumia herufi this. Kwa kichukua kifaa cha kizalizo cha kifaa kwa kifaa cha kizalizo cha kizalizo cha kifaa, kwanza kichukua kwa kifaa cha kizalizo cha kifaa kabla ya kufanya mabomu ya kwanza cha kichukua kifaa cha kizalizo cha kifaa, kwa sababu tu herufi this inaweza kupata kifaa cha kizalizo cha kifaa. Kisha inaweza kumpatia mambo ya this, kwa kawaida ni kumwambia kifaa cha kizalizo cha kifaa (hakuna hatua ya kumwambia kifaa cha kizalizo cha kifaa cha kifaa cha kizalizo cha kifaa).
现在,用 new 运算符和类名 Car 创建对象,就更像 ECMAScript 中一般对象的创建方式了。
你也许会问,这种方式在管理函数方面是否存在于前一种方式相同的问题呢?是的。
就像工厂函数,构造函数会重复生成函数,为每个对象都创建独立的函数版本。不过,与工厂函数相似,也可以用外部函数重写构造函数,同样地,这么做语义上无任何意义。这正是下面要讲的原型方式的优势所在。
原型方式
该方式利用了对象的 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();
在这段代码中,首先定义构造函数(Car),其中无任何代码。接下来的几行代码,通过给 Car 的 prototype 属性添加属性去定义 Car 对象的属性。调用 new Car() 时,原型的所有属性都被立即赋予要创建的对象,意味着所有 Car 实例存放的都是指向 showColor() 函数的指针。从语义上讲,所有属性看起来都属于一个对象,因此解决了前面两种方式存在的问题。
此外,使用这种方式,还能用 instanceof 运算符检查给定变量指向的对象的类型。因此,下面的代码将输出 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); //Inatoa "Mike,John,Bill" alert(oCar2.drivers); //Inatoa "Mike,John,Bill"
Kwenye maelezo ya juu, mawazo ya mashofa ni kibarua cha Array ya kifaa, iliyotengeneza mawazo "Mike" na "John". Kwa sababu mashofa ni kibarua cha matokeo, Car ya mbili inaonyesha msingi wa kina moja. Hii ina maana kwamba kuingiza thamani "Bill" katika oCar1.drivers, inawekwa katika oCar2.drivers. Kupata kibarua kwa kina yoyote, inatoa mawazo "Mike,John,Bill".
Kwa sababu kuna mabadi mengi ya kumekua kifaa, inaweza kuwa na mawili kwa kuwa na mtu wa kumekua kifaa wa kawaida? Jibu ni naye, inahitajika kuandikisha kifaa cha kuzungumza na tabaka la kuzingatia.
Mwongozo wa kifaa cha kuzungumza/kuzingatia
Kutumia kifaa cha kuzungumza na tabaka la kuzingatia, inaweza kutumia kama kila kitengo cha programu kinachotumia kumekua kifaa. Koncepti hii inayofanana kwa uharibifu, ni kwamba kumekua mawazo ya kifaa yote inayohusiana na maadili kwa kifaa cha kuzungumza, na kumekua maadili (mashairi) kwa tabaka la kuzingatia. Matokeo ni kwamba maelezo yote yanakaa ndani ya kina moja, na kila kifaa ina kina ya kifaa yake kwa kina.
Tunarekodi maelezo ya awali, kama iliyochaguliwa hapa:
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); //Inatoa "Mike,John,Bill" alert(oCar2.drivers); //Inatoa "Mike,John"
Sasa ni kama kumekuja kama kukuundika maelezo jingine ya kifaa. Mawazo yote ya kifaa inayohusiana na maadili hayafanyiwa katika kifaa cha kuzungumza, ina maana kwamba tena inaweza kutumia thamani ya kifaa cha kuzungumza kumtegea thamani za kuzingatia mawazo. Kwa sababu kinaundwa maelezo mmoja ya showColor() kwa kina, hana uharibifu wa mafungu ya heshima. Pia, kuingiza "Bill" katika orodha ya mashofa ya oCar1, haikuwa inafaa kwa oCar2, kwa sababu ya hivyo, kumtaarifu thamani ya orodha hizi, oCar1.drivers inaonyesha "Mike,John,Bill", na oCar2.drivers inaonyesha "Mike,John". Kwa sababu inatumiwa tabaka la kuzingatia, inaweza kutumia alama ya instanceof kuingia kwa aina ya kifaa.
这种方式是 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 类的所有属性和方法,因此看见这段代码就知道它要实现什么功能,它定义了一个对象的信息。批评混合的构造函数/原型方式的人认为,在构造函数内部找属性,在其外部找方法的做法不合逻辑。因此,他们设计了动态原型方法,以提供更友好的编码风格。
Ujumbe wa maadili ya method ya prototype wa kina ya habari inayofanana na sababu ya kina ya habari na prototype, bila kufikiria sababu ya kina ya habari inayotumika kwenye kina ya habari. Mfano wa kina ya habari ya Car inayorekwa kwa method ya prototype wa kina ya habari:
function Car(sColor,iDoors,iMpg) { this.color = sColor; this.doors = iDoors; this.mpg = iMpg; this.drivers = new Array("Mike","John"); ina kama (iaja Car._initialized niwa 'undefined') { Car.prototype.showColor = function() { alert(this.color); }; Car._initialized = true; } }
Kina cha ujenzi hii kinachotukia hadi typeof Car._initialized inaeleza "undefined" hauwezi kumwambia kina cha ujenzi hii inapungua. Tena, hii ni sehemu inayohusiana na kina cha kina cha kuzingatia cha kina cha kina cha kuzingatia. Kama hii inaeleza "undefined", kina cha ujenzi kinakufanya kina cha kina cha kuzingatia kwa njia ya kina cha kina cha kuzingatia kwa kina cha kina cha kuzingatia, kwa kina cha kina cha kuzingatia Car._initialized kinapokwa kwa kina cha kina cha kuzingatia. Kama hii inaeleza (inaadiliwa kwamba hii inaeleza true), hii inasababisha kina cha kina cha kuzingatia hauwezi kumwambia kina cha kina cha kuzingatia. Kwa ujumbe, hii inaonesha kwamba kina cha kina cha kuzingatia hauwezi kumwambia kina cha kina cha kuzingatia kama kina cha kina cha kuzingatia. Kina cha kina cha kuzingatia hauwezi kumwambia kina cha kina cha kuzingatia kama kina cha kina cha kuzingatia kama kina cha kina cha kuzingatia.
Kina cha kina cha kuzingatia cha kina cha kina cha kuzingatia
Hii ni njia inayotumika kama njia ya kina cha kina cha kuzingatia kwa sababu inasababisha kina cha kina cha kuzingatia inayotumika kwa kina cha kina cha kuzingatia. Matokeo yake ni kuzingatia kina cha kina cha kuzingatia kama kina cha kina cha kuzingatia kina cha kina cha kuzingatia.
Kina hiki kinapakana na kina cha kina cha kuzingatia:
function Car() { var oTempCar = new Object; oTempCar.color = "blue"; oTempCar.doors = 4; oTempCar.mpg = 25; oTempCar.showColor = function() { alert(this.color); }; return oTempCar; }
Kama kina cha kina cha kuzingatia hii inafikia kina cha kina cha kuzingatia, inaonesha kwamba hii ni kina cha kina cha kuzingatia kama kina cha kina cha kuzingatia:
var car = new Car();
Kwa sababu ya kwamba kina cha ujenzi Car() kinatokana na muhimu wa kina cha kina cha kuzingatia, kina cha kina cha kuzingatia ya pili (kina cha kina cha kuzingatia inayotumika kwenye kina cha kina cha kuzingatia) kinapotea, kina cha kina cha kuzingatia kinapokwa kwa kina cha kina cha kuzingatia kwenye kina cha kina cha kuzingatia kinapokwa kwa kina cha kina cha kuzingatia.
Hii ni njia inayotumika kwa sababu ya masuala yasiyotumika kama kina cha kina cha kuzingatia. Inaruhusiwa sana kwa sababu inasababisha matatizo. Inaruhusiwa sana kwa sababu inasababisha matatizo.
Jina la njia hii
Kama ilivyoandikwa, hivi karibuni inayotumika zaidi ni ujumbe wa ujenzi/kina cha kuzingatia na kina cha matumizi wa asili kwa uwezo. Pia, kina cha matumizi wa asili ya kina inaonesha uwezo linalotumika kama kina cha ujenzi/kina cha kuzingatia. Inaweza kutumia kila moja ya njia hizi. Hata hivyo, haikuwezi kutumia kina cha ujenzi au kina cha kuzingatia kwa njia ya kina cha kina cha kuzingatia, kwa sababu hii inaathirisha programu yako.
Mfano
Inasikitisho inayotumia kwa sababu ya uwezo wao wa kusaidia. Matokeo ya ECMAScript inayopakana kwa kawaida ni hisia ya kuelekea kwa vifaa vya matukio. Kama kawaida kwenye lugha zingine, matukio ya ECMAScript ni bila tabia, kwa maana ya kwamba upeo wao hauweza kubadilika. Tafadhali mtaarifu kwa kifaa chake:
var str = "hello "; str += "world";
实际上,这段代码在幕后执行的步骤如下:
- 创建存储 "hello " 的字符串。
- 创建存储 "world" 的字符串。
- 创建存储连接结果的字符串。
- 把 str 的当前内容复制到结果中。
- 把 "world" 复制到结果中。
- 更新 str,使它指向结果。
每次完成字符串连接都会执行步骤 2 到 6,使得这种操作非常消耗资源。如果重复这一过程几百次,甚至几千次,就会造成性能问题。解决方法是用 Array 对象存储字符串,然后用 join() 方法(参数是空字符串)创建最后的字符串。想象用下面的代码代替前面的代码:
var arr = new Array(); arr[0] = "hello "; arr[1] = "world"; var str = arr.join("");
这样,无论数组中引入多少字符串都不成问题,因为只在调用 join() 方法时才会发生连接操作。此时,执行的步骤如下:
- 创建存储结果的字符串
- 把每个字符串复制到结果中的合适位置
虽然这种解决方案很好,但还有更好的方法。问题是,这段代码不能确切反映出它的意图。要使它更容易理解,可以用 StringBuffer 类打包该功能:
function StringBuffer () { this._strings_ = new Array(); } StringBuffer.prototype.append = function(str) { this._strings_.push(str); }; StringBuffer.prototype.toString = function() { return this._strings_.join(""); };
这段代码首先要注意的是 strings 属性,本意是私有属性。它只有两个方法,即 append() 和 toString() 方法。append() 方法有一个参数,它把该参数附加到字符串数组中,toString() 方法调用数组的 join 方法,返回真正连接成的字符串。要用 StringBuffer 对象连接一组字符串,可以用下面的代码:
var buffer = new StringBuffer(); buffer.append("hello "); buffer.append("world"); var result = buffer.toString();
Inaweza kufanikia mafanikio ya kifaa cha StringBuffer na method ya kuingia wa maandiko kwa programu zifuatazo:
var d1 = new Date(); var str = ""; for (var i=0; i < 10000; i++) { str += "text"; } var d2 = new Date(); document.write("Kuingia mtandao na kichwa cha kuingia: ") + (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 />Kuingia mtandao na StringBuffer: ") + (d2.getTime() - d1.getTime()) + " milliseconds");
Hii programu inafanya mafanikio mbili ya kuingia mtandao wa maandiko, kawaida inatumia kichwa cha kuingia na kichwa cha StringBuffer. Harufu zote zinaingia 10000 wa maandiko. Tarehe za d1 na d2 zinatumiwa kumtambua wakati ambao inahitaji kufanyia mafanikio. Kupambana wakati ambao inahitaji kufanyia mafanikio wa kuingia mtandao, kufungua msingi wa tarehe wa milimali (kwa kutumia kipindi cha getTime()) kusadifu. Hii inaonekana kama sababu ya kumtambua uharibifu wa JavaScript. Matokeo ya mafanikio haya inaweza kusaidia kumtambua uharibifu wa kichwa cha StringBuffer na kichwa cha kuingia.
- Mwakilishi wa kwanza Muundo wa muundo wa kifaa
- Mwakilishi wa kina Kuhariri muundo