ECMAScript အသုံးပြု

ပြင်ဆင်ချက် အရာဝတ္တု အသုံးပြုခြင်း သည် ရှိသော ရှိသော ဝီကီစိတ် စကားလုံး အချို့ အရေးအချင်း တစ်ခု ဖြစ်သည်၊ သို့သော် အခြား အရေးအချင်း သည် ကိုယ်ပိုင် ကျွမ်းကျွမ်းသော အမျိုးမျိုးသော အက်ဥ်စတာ နှင့် အရာဝတ္တု ဖန်တီး ဖို့ အရေးအချင်း ဖြစ်သည်。

ECMAScript တွင် အရာဝတ္တု ပေါ်လွင်းခြင်း သို့မဟုတ် အမျိုးမျိုးသော အက်ဥ်စတာ အုပ်စု များ ပါဝင်သည်。

ကျောက်ဆောင် စကားလုံး

အစိတ်စိတ် စကားလုံး

အရာဝတ္တု၏ အချက်အလက်များ အစားထိုးခြင်း အခွင့်အရေး အရ အရာဝတ္တု ဖန်တီး ပြီးနောက် စမ်းသပ် ပြီး များစွာ သုံးစွဲသူများ လောက် အောက်ပါ အုပ်စု စကားလုံး များ ကို ကြိုးစား သုံးစွဲကြသည်:

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

TIY

在上面的代码中,创建对象 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();

TIY

在这里,第一个例子中的所有代码都包含在 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"

TIY

给 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"

TIY

အထက်တွင် ပြန်လည်ဖျင်းပြခြင်းတွင် အခြားအက်ဥပဒေကို ဖန်တီးပြီး အခြားအက်ဥပဒေကို အသုံးပြုပြီး ဖြစ်သည်။ အခြားအက်ဥပဒေကို အသုံးပြုပြီး အခြားအက်ဥပဒေကို အသုံးပြုပြီး ဖြစ်သည်။ အခြားအက်ဥပဒေကို အသုံးပြုပြီး အခြားအက်ဥပဒေကို အသုံးပြုပြီး ဖြစ်သည်။

အဆိုပါအခြေခံအချက်များ အတွက်သတ္တုကြီးသမုတ္တရာရှင်ဖြစ်ပေါ်လာခြင်း

ကုမ္ပဏီအက်ဥပဒေပုံစံ

ကုမ္ပဏီအက်ဥပဒေကို ဖန်တီးပြီး ပုံစံအက်ဥပဒေကဲ့သို့ လွယ်ကူပါသည်။ ပထမဆုံး အဆက်အဖြစ် အမျိုးအစားအမည် ရွေးချယ်ပါ၏။ အဆိုပါအမည် အစိတ်အပိုင်း အများအားဖြင့် အောက်ဆုံးအစိတ် အောက်ချိုးပြီး သုံးပါ၏။ အဆိုပါအခြေခံအချက် အပါအဝင် အခြားအမည် မပြောင်းလဲခြင်း မရှိပေ။ အဆိုပါအခြေခံအချက် အပါအဝင် အခြားအမည် မပြောင်းလဲခြင်း မရှိပေ။

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

အထက်တွင်ပြောဆိုတာကို သင်းတော်အား ရှာဖွေဖော်ပြပါ၏။ ပထမဆုံး ကုမ္ပဏီအက်ဥပဒေမှ အပြီးတွင် အရာကို ဖန်တီးခြင်း မရှိပေ။ သို့သော်လည်း ၄င်း ကို this ကိုသုံးပြီး အသုံးပြုပါ၏။ new ပုံစံအားသုံး၍ ကုမ္ပဏီအက်ဥပဒေကို ဖန်တီးပြီး၊ ပထမဆုံး အုပ်ချုပ်ချက် အလုပ်လုပ်ခင်၊ အရာကို ဖန်တီးပြီး၊ this ကိုသုံးနိုင်ပေသည်။ နောက်မှ သင်းတော်ကို this အခြေခံအချက် သုံး၍ ချီးမြှင့်ပါ၏။ ပုံစံအားသုံး၍ အရာကို ဖန်တီးပြီး၊ သင်းတော်ကို အားရိုက်ချီးမြှင့်ပါ၏။ အခြားမူ အားလုံး ပြောင်းလဲခြင်း မရှိပေ။

现在,用 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();

TIY

在这段代码中,首先定义构造函数(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);	//ထုတ်ပ�န် "Mike,John,Bill"
alert(oCar2.drivers);	//输出 "Mike,John,Bill"

TIY

上面的代码中,属性 drivers 是指向 Array 对象的指针,该数组中包含两个名字 "Mike" 和 "John"。由于 drivers 是引用值,Car 的两个实例都指向同一个数组。这意味着给 oCar1.drivers 添加值 "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"

TIY

ကျွန်ုပ်သည် ပုံစံအရင်းတို့ကို ဖန်တီးကြောင်း အသိအမှတ်ပြုသည်။ ပုံစံတို့၏ သင်္ကန်သင်းမပါဘဲဖြစ်သော အချက်အလက်များ အား ဖန်တီးသည့် အသုံးပြုသည်ဟု ထင်မြင်သည်။ ဤအားဖြင့် showColor() သင်္ကန်သင်း၏ တစ်ခုတည်းသော အကျယ်အဝန်းကို ဖန်တီးလိုက်သည်ဟု ထင်မြင်သည်။ ထို့ပြင် oCar1.drivers အသင်းအဖွဲ့တွင် "Bill" အသုံးပြုသည်မှာ oCar2 အသင်းအဖွဲ့အား အခြေအားဖြင့် မပါဝင်သည်။ ထို့ကြောင်းဖြင့် oCar1.drivers အသင်းအဖွဲ့၏ အကျယ်အဝန်းကို ထုတ်ပြန်ချင်းကို ထိုအချက်အလက်ကို ထုတ်ပြန်လိုက်သည်။ oCar1.drivers အသင်းအဖွဲ့၏ အကျယ်အဝန်းကို ထုတ်ပြန်လိုက်သည်၏ အချက်အလက်ကို "Mike,John,Bill" ဟု ထုတ်ပြန်လိုက်ပြီး oCar2.drivers အသင်းအဖွဲ့၏ အကျယ်အဝန်းကို ထုတ်ပြန်လိုက်သည်၏ အချက်အလက်ကို "Mike,John" ဟု ထုတ်ပ�န်လိုက်ပြီး။ အဆိုပါ အကျယ်အဝန်းကို အသုံးပြုသည်မှာ ပုံစံအဖွဲ့၏ အချက်အလက်များ အား ထုတ်ပြန်ရန် ဖြစ်သည်။

这种方式是 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(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

直到检查 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;
}

TIY

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

var car = new Car();

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

အဆိုပါ အချက်အလက် အရ အသုံးပြုနိုင်သော အချက်အလက်

အဆိုပါ အချက်အလက် အရ အသုံးပြုနိုင်သော အချက်အလက်

အဆိုပါ အချက်အလက် အရ အသုံးပြုနေသေးသည် သည် အသုံးပြုနေသေးသည် သို့မဟုတ် အခြား အဆိုပါ အချက်အလက် အရ ပြုပြင်ခြင်း နဲ့ အတူတူ အလုပ်လုပ်ခြင်း ဖြစ်သည်။ အဆိုပါ အချက်အလက် အရ အသုံးပြုနိုင်သော အချက်အလက် အချို့ အရ အသုံးပြုနိုင်သည်။ သို့သော် အဆိုပါ အချက်အလက် အရ သီးခြား အသုံးပြုနိုင်ခြင်း မရှိ။ အဆိုပါ အချက်အလက် အရ သီးခြား အသုံးပြုခြင်း သည် အခြေခံ အဆိုပါ အချက်အလက် အရ အခြေအနေ ပါ အခြေအနေ ဖြစ်သည်။

အမှတ်

အားကြောင်းအရ ပြုပြင်ခြင်းအကျိုးသက်ရှိသော အချက်တစ်ခု သည် သူတို့ဖြင့် ပြုပြင်ခြင်း ဖြစ်သည်။ ECMAScript တွင် အပြင်းအထန် ရှိသော ပြုပြင်ခြင်း တစ်ခု သည် စကားလုံး ချို့ကွဲခြင်း၏ ပြုပြင်မှု၏ ပမာဏအား ဖြစ်သည်။ အခြား ဘာသာစကား နဲ့အတူတူ အမေရိကန်အက္ခရာများ တွင် စကားလုံး မည်သည့် အခြေအနေမှ ပြုပြင်နိုင်ခြင်း မရှိ။ အောက်ပါ ကြော်ငြာသံကြားကို ကျွန်ုပ် သဘောပေါက်စေခြင်း

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

ထို့ကြောင့် အစုအဖွဲ့ တွင် အသုံးပြုသော စကားလုံး အများဆုံး ဖြစ်လျှင် အခြေအနေ မပြတ်မတရား ဖြစ်ပေ။ အခြေအနေ ကို ပြုပြင်ရန် အောက်ပါ အဆင့်ခံ အဆောင်ရွက် ကို အသုံးပြုပါ:

  1. အကျိုးသား အချက် ကို ကိုင်တွယ်ပေးရန် စကားလုံး ကို ဖွဲ့စည်းပါ
  2. ရှိသော စကားလုံး ကို အကျိုးသား အချက်တွင် အခြေအနေအတိုင်း ပြုပြင်ပေးပါ

ဒါက လုပ်ဆောင်နည်းဖြင့် အကောင်းမွန်သည် သော်လည်း အခြား ကောင်းမွန်သော လုပ်ဆောင်နည်း ရှိသည်။ ဒါက လုပ်ဆောင်နည်းသည် အကျိုးသား၏ အဆိုချက် ကို အချစ်ပြောင်းစေသည်။ အမှန်တကယ် အကျိုးသားကို ကူးစက်စေရန် 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();

TIY

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

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

TIY

这段代码对字符串连接进行两个测试,第一个使用加号,第二个使用 StringBuffer 类。每个操作都连接 10000 个字符串。日期值 d1 和 d2 用于判断完成操作需要的时间。请注意,创建 Date 对象时,如果没有参数,赋予对象的是当前的日期和时间。要计算连接操作历经多少时间,把日期的毫秒表示(用 getTime() 方法的返回值)相减即可。这是衡量 JavaScript 性能的常见方法。该测试的结果可以帮助您比较使用 StringBuffer 类与使用加号的效率差异。