ఇక్మాస్క్రిప్ట్ క్లాస్ లేదా ఆబ్జెక్ట్ డిఫైన్
- ముందు పేజీ ఆబ్జెక్ట్ యొక్క రంగం
- తరువాత పేజీ ఆబ్జెక్ట్ను సవరించండి
使用预定义对象只是面向对象语言的能力的一部分,它真正强大之处在于能够创建自己专用的类和对象。
ECMAScript 拥有很多创建对象或类的方法。
工厂方式
原始的方式
因为对象的属性可以在对象创建后动态定义,所有许多开发者都在 JavaScript 最初引入时编写类似下面的代码:
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 属性赋值。这使两个对象具有相同的属性,却有不同的属性值。
在工厂函数外定义对象的方法
ఎక్మాస్క్రిప్ట్ మరింత రూఢీకరణగా మారుతున్నప్పటికీ, ఆబ్జెక్ట్లను సృష్టించడానికి ఉపయోగించే విధానాన్ని విస్మరించబడింది. కొంతమంది అర్థపూరక కారణాల కారణంగా (ఇది new కాల్స్ కాల్స్ విధానంతో పోల్చితే అధికారికంగా కనిపించదు), కొంతమంది పనితీరు కారణాల కారణంగా. పనితీరు కారణం ఈ విధానంతో ఆబ్జెక్ట్లను సృష్టించడానికి అవసరం ఉంటుంది. ముంది ఉదాహరణలో, క్రియాలు క్రియాలు కాల్స్ చేయడంలో ప్రతిసారి కొత్త క్రియాలను సృష్టించాలి, అంటే ప్రతి ఆబ్జెక్ట్కు తన సొంత 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() 函数的指针。从功能上讲,这样解决了重复创建函数对象的问题;但是从语义上讲,该函数不太像是对象的方法。
所有这些问题都引发了开发者定义的构造函数的出现。
构造函数方式
创建构造函数就像创建工厂函数一样容易。第一步选择类名,即构造函数的名字。根据惯例,这个名字的首字母大写,以使它与首字母通常是小写的变量名分开。除了这点不同,构造函数看起来很像工厂函数。请考虑下面的例子:
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);
下面为您解释上面的代码与工厂方式的差别。首先在构造函数内没有创建对象,而是使用 this 关键字。使用 new 运算符构造函数时,在执行第一行代码前先创建一个对象,只有用 this 才能访问该对象。然后可以直接赋予 this 属性,默认情况下是构造函数的返回值(不必明确使用 return 运算符)。
现在,用 new 运算符和类名 Car 创建对象,就更像 ECMAScript 中一般对象的创建方式了。
你也许会问,这种方式在管理函数方面是否存在于前一种方式相同的问题呢?是的。
就像工厂函数,构造函数会重复生成函数,为每个对象都创建独立的函数版本。不过,与工厂函数相似,也可以用外部函数重写构造函数,同样地,这么做语义上无任何意义。这正是下面要讲的原型方式的优势所在。
原型方式
该方式利用了对象的 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 ఇన్స్టాన్స్లు షోకలర్() ఫంక్షన్కు పరిపత్తిని కలిగి ఉంటాయి. సిమాన్సిక్గా, అన్ని లక్షణాలు ఒక వస్తువుకు చెందినట్లు కనిపిస్తాయి, అందువల్ల ముంది రెండు విధానాలు యొక్క సమస్యలను పరిష్కరించింది.
ఇలాగే, ఈ విధంగా, instanceof ఆపరేటర్ ద్వారా పరిమితమైన వస్తువు యొక్క రకాన్ని తనిఖీ చేయవచ్చు. అందువల్ల, క్రింది కోడ్ ట్రూ అవుతుంది:
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"
పై కోడ్ లో, అంశం drivers ఆరే వస్తువులోని పద్ధతి పొందించబడింది, అనేకందుకు ఆరేయలో "Mike" మరియు "John" పేర్లు ఉన్నాయి. కారణం కాస్ట్రక్చర్ లేదా పద్ధతి పొందించబడినందున, Car యొక్క రెండు ఇన్స్టాన్సులు ఒకే ఆరేయానికి సూచిస్తాయి. ఈ అరేయలో "Bill" విలువను జోడించినప్పుడు, oCar2.drivers లో కూడా చూడవచ్చు. ఈ రెండు పద్ధతులను ప్రదర్శించినప్పుడు, "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"
ఇప్పుడు అన్ని ఫంక్షన్ లేని అంశాలు కాస్ట్రక్చర్ ఫంక్షన్ లో సృష్టించబడినందున, అనేకందుకు కాస్ట్రక్చర్ ఫంక్షన్ పారామిటర్లను వాడి అంశాలకు డిఫాల్ట్ విలువలను కట్టించవచ్చు. కారణం కాస్ట్రక్చర్ ఫంక్షన్ నుండి showColor() ఫంక్షన్ యొక్క ఒకే ఇన్స్టాన్స్ మాత్రమే సృష్టించబడుతుంది కాబట్టి మెమొరీ వ్యర్థింపు లేదు. మరియు oCar1.drivers అరేయలో "Bill" విలువను జోడించినప్పుడు, oCar2 అరేయాన్ని ప్రభావితం చేయదు కాబట్టి, ఈ అరేయలను ప్రదర్శించినప్పుడు, oCar1.drivers "Mike,John,Bill" అని ప్రదర్శిస్తుంది, మరియు oCar2.drivers "Mike,John" అని ప్రదర్శిస్తుంది. ప్రాటోటైప్ విధానాన్ని వాడినందున, instanceof ఆపరేటర్ ను వాడి పద్ధతిని పరిశీలించవచ్చు.
ఈ విధానం ఇస్క్రియప్టిక్ స్క్రిప్టింగ్ లో ప్రధాన విధానం, ఇతర విధానాలకు లక్షణాలు కలిగి ఉంది, కానీ అవి అవినీతికరమైన ప్రభావాలు లేవు. అయితే, కొన్ని డెవలపర్లు ఈ విధానాన్ని పరిపూర్ణంగా భావించడం లేదు.
డైనమిక్ ప్రాటోటైప్ మెథడ్స్
ఇతర భాషలను ఉపయోగించే డెవలపర్లకు కాంబైన్డ్ కన్స్ట్రక్టర్/ప్రాటోటైప్ విధానం సహకరించకపోతుంది. కాబట్టి, క్లాస్ నిర్వచించటంలో అత్యంత ఆప్టికల్ ప్యాక్షన్ అనేక ఆప్టికల్ ప్యాక్షన్స్ ఉన్నాయి. దాదాపు ఈ జావా క్లాస్ ను పరిగణించండి:
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 క్లాస్ ఉంది:
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; } }
直到检查 typeof Car._initialized 是否等于 "undefined" 之前,这个构造函数都未发生变化。这行代码是动态原型方法中最重要的部分。如果这个值未定义,构造函数将用原型方式继续定义对象的方法,然后把 Car._initialized 设置为 true。如果这个值定义了(它的值为 true 时,typeof 的值为 Boolean),那么就不再创建该方法。简而言之,该方法使用标志(_initialized)来判断是否已给原型赋予了任何方法。该方法只创建并赋值一次,传统的 OOP 开发者会高兴地发现,这段代码看起来更像其他语言中的类定义了。
混合工厂方式
这种方式通常是在不能应用前一种方式时的变通方法。它的目的是创建假构造函数,只返回另一种对象的新实例。
这段代码看起来与工厂函数非常相似:
function Car() { var oTempCar = new Object; oTempCar.color = "blue"; oTempCar.doors = 4; oTempCar.mpg = 25; oTempCar.showColor = function() { alert(this.color); }; return oTempCar; }
与经典方式不同,这种方式使用 new 运算符,使它看起来像真正的构造函数:
var car = new Car();
由于在 Car() 构造函数内部调用了 new 运算符,所以将忽略第二个 new 运算符(位于构造函数之外),在构造函数内部创建的对象被传递回变量 car。
ఈ విధానం క్లాసిక్ విధానంతో సమానంగా ఆధారిక మెట్హడ్స్ లోని సమస్యలను కలిగి ఉంటుంది. అందువల్ల, ఎందుకంటే మాత్రమే ఈ విధానాన్ని ఉపయోగించకుండా ఉండండి.
ఏ విధానాన్ని ఉపయోగించాలి
ముందు చెప్పబడినట్లు, ప్రస్తుతం అత్యంత విస్తరించబడినది హైబ్రిడ్ కన్స్ట్రక్టర్/ప్రాటోటైప్ విధానం. పాటుపోయే అడ్జుక్ట్ మెథడ్స్ కూడా అధికంగా వినియోగించబడతాయి, ఇవి కన్స్ట్రక్టర్/ప్రాటోటైప్ విధానంతో సమానంగా పని చేస్తాయి. ఈ రెండు విధానాలలో ఏదైనా ఒకటిని ఎంచుకోవచ్చు. కానీ క్లాసిక్ కన్స్ట్రక్టర్ లేదా ప్రాటోటైప్ విధానాన్ని ప్రత్యేకంగా ఉపయోగించకుండా ఉండండి, ఎందుకంటే ఇది కోడ్కు సమస్యలను ప్రవేశపెడతుంది.
ఉదాహరణ
దీనికి ఆసక్తి కలిగించే ఒక విషయం దీనిని సమస్యలను పరిష్కరించే విధానం. ECMAScript లో అత్యంత సాధారణమైన ఒక సమస్య స్ట్రింగ్ కనెక్షన్ పరిమాణం. ఇతర భాషలతో సమానంగా, ECMAScript యొక్క స్ట్రింగ్స్ అనివార్యంగా అమాయకం, అంటే వాటి విలువలను మార్చలేము. దయచేసి క్రింది కోడ్ని పరిగణించండి:
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();
సిగ్నల్బ్యూట్రింగ్బ్యూఫర్ క్లాస్ మరియు పారంపరిక స్ట్రింగ్ కనెక్షన్ పనితీరును పరీక్షించడానికి క్రింది కోడ్ ను వాడవచ్చు:
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");
ఈ కోడ్ స్ట్రింగ్ కనెక్షన్ను రెండు పరీక్షలు చేస్తుంది, ఒకటి కూడాను జోడించడం చేస్తుంది, మరొకటి StringBuffer క్లాస్ వాడటం చేస్తుంది. ప్రతి కార్యకలాపంలో స్ట్రింగ్లను పదిహేను వేల సంఖ్యలు కనెక్షన్ చేస్తాయి. డేట్ విలువలు d1 మరియు d2 యొక్క కార్యకలాపం పూర్తి అయ్యే సమయాన్ని నిర్ణయించడానికి ఉపయోగిస్తాయి. డేట్ అబ్జెక్ట్ను సృష్టించటం సమయంలో కాలం లేకపోతే, ప్రస్తుత తేదీ మరియు సమయాన్ని అబ్జెక్ట్కు ఇవ్వబడుతుంది. కనెక్షన్ కార్యకలాపం ఎంత సమయం పడుతుందో నిర్ణయించడానికి, డేట్లను మిగిలిన మిల్లీసెకన్లుగా తీసుకోవడం చేస్తారు. ఈ పద్ధతి జావాస్క్రిప్ట్ పనితీరును మాపడానికి సాధారణ పద్ధతి. ఈ పరీక్ష ఫలితాలు, StringBuffer క్లాస్ మరియు జోడించడం చేస్తున్న కర్ణనిష్టతను పోల్చడానికి సహాయపడతాయి.
- ముందు పేజీ ఆబ్జెక్ట్ యొక్క రంగం
- తరువాత పేజీ ఆబ్జెక్ట్ను సవరించండి