Додаткові приклади для Object.defineProperty

Ця стаття надає додаткові приклади для Object.defineProperty().

Використання двійкових прапорів замість дескриптора властивості

Якщо вам необхідно визначити велику кількість властивостей методом Object.defineProperty(), ви можете використовувати один і той самий дескриптор для кожної властивості, перевизначаючи його час від часу через двійкові прапори.

var oDesc = {};
function setProp (oObj, nMask, sKey, vVal_fGet, fSet) {
  if (nMask & 8) {
    // дескриптор доступу
    if (vVal_fGet) {
      oDesc.get = vVal_fGet;
    } else {
      delete oDesc.get;
    }
    if (fSet) {
      oDesc.set = fSet;
    } else {
      delete oDesc.set;
    }
    delete oDesc.value;
    delete oDesc.writable;
  } else {
    // дескриптор даних
    if (arguments.length > 3) {
      oDesc.value = vVal_fGet;
    } else {
      delete oDesc.value;
    }
    oDesc.writable = Boolean(nMask & 4);
    delete oDesc.get;
    delete oDesc.set;
  }
  oDesc.enumerable = Boolean(nMask & 1);
  oDesc.configurable = Boolean(nMask & 2);
  Object.defineProperty(oObj, sKey, oDesc);
  return oObj;
}

/*
* :: функція setProp ::
*
* nMask є бітовою маскою:
*  flag 0x1: властивість є доступною для переліку,
*  flag 0x2: властивість є доступною для налаштування,
*  flag 0x4: властивість є доступною для запису,
*  flag 0x8: властивість є дескриптором доступу.
* oObj - об'єкт, на якому ми визначаємо властивість;
* sKey - ім'я властивості, яка визначається чи модифікується;
* vVal_fGet - значення, що призначається дескриптору даних чи функції-гетеру
* для присвоєння дескриптору доступу (в залежності від бітової маски);
* fSet - функція-сетер для присвоєння дескриптору доступу;
*
* Можливі значення бітової маски:
*
*  0  : дескриптор даних тільки для читання - недоступний для налаштування та переліку (0000).
*  1  : дескриптор даних тільки для читання - недоступний для налаштування, доступний для переліку (0001).
*  2  : дескриптор даних тільки для читання - доступний для налаштування, недоступний для переліку (0010).
*  3  : дескриптор даних тільки для читання - доступний для налаштування та переліку (0011).
*  4  : дескриптор даних, доступний для запису - недоступний для налаштування та переліку (0100).
*  5  : дескриптор даних, доступний для запису - недоступний для налаштування, доступний для переліку (0101).
*  6  : дескриптор даних, доступний для запису - доступний для налаштування, недоступний для переліку (0110).
*  7  : дескриптор даних, доступний для запису - доступний для налаштування та переліку (0111).
*  8  : дескриптор доступу - недоступний для налаштування та переліку (1000).
*  9  : дескриптор доступу - недоступний для налаштування, доступний для переліку (1001).
*  10 : дескриптор доступу - доступний для налаштування, недоступний для переліку (1010).
*  11 : дескриптор доступу - доступний для налаштування та переліку (1011).
*
*  Заувага: Якщо прапор 0x8 встановлений у "дескриптор доступу", прапор 0x4 (доступний для запису)
*  ігноруватиметься. Якщо ні, то аргумент fSet ігноруватиметься.
*/

// створення нового порожнього об'єкта
var myObj = {};

// додавання дескриптора даних, доступного для запису - недоступний для налаштування та переліку
setProp(myObj, 4, 'myNumber', 25);

// додавання дескриптора даних тільки для читання - недоступний для налаштування, доступний для переліку
setProp(myObj, 1, 'myString', 'Привіт!');

// додавання дескриптора доступу - недоступний для налаштування, доступний для переліку
setProp(myObj, 9, 'myArray', function() {
  for (var iBit = 0, iFlag = 1, aBoolArr = [false];
    iFlag < this.myNumber + 1 || (this.myNumber & iFlag);
    iFlag = iFlag << 1
  ) {
    aBoolArr[iBit++] = Boolean(this.myNumber & iFlag);
  }
  return aBoolArr;
}, function(aNewMask) {
  for (var nNew = 0, iBit = 0; iBit < aNewMask.length; iBit++) {
    nNew |= Boolean(aNewMask[iBit]) << iBit;
  }
  this.myNumber = nNew;
});

// додавання дескриптора даних, доступного для запису (значення undefined) - доступний для налаштування та переліку
setProp(myObj, 7, 'myUndefined');

// додавання дескриптора доступу (тільки гетер) - доступний для налаштування та переліку
setProp(myObj, 11, 'myDate', function() { return new Date(); });

// додавання дескриптора доступу (тільки сетер) - недоступний для налаштування та переліку
setProp(myObj, 8, 'myAlert', null, function(sTxt) { alert(sTxt); });

myObj.myAlert = myObj.myDate.toLocaleString() + '\n\n' + myObj.myString +
  '\nЧисло ' + myObj.myNumber + ' представляє наступну бітову маску: ' +
  myObj.myArray.join(', ') + '.';

// список перелічуваних властивостей
var sList = "Ось перелічувані властивості об'єкта myObj:\n";
for (var sProp in myObj) {
  sList += '\nmyObj.' + sProp + ' => ' + myObj[sProp] + ';'
}

alert(sList);

Створення нового невбудованого методу Object.setProperty()

Те саме можна зробити з дескриптором, отриманим через анонімний конструктор, та користувацьким методом Object на ім'я setProperty():

// створення нового методу Object на ім'я Object.setProperty()

new (function() {
  var oDesc = this;
  oDesc.constructor = Object;
  Object.setProperty = function(oObj, nMask, sKey, vVal_fGet, fSet) {
    if (nMask & 8) {
      // дескриптор доступу
      if (vVal_fGet) {
        oDesc.get = vVal_fGet;
      } else {
        delete oDesc.get;
      }
      if (fSet) {
        oDesc.set = fSet;
      } else {
        delete oDesc.set;
      }
      delete oDesc.value;
      delete oDesc.writable;
    } else {
      // дескриптор даних
      if (arguments.length > 3) {
        oDesc.value = vVal_fGet;
      } else {
        delete oDesc.value;
      }
      oDesc.writable = Boolean(nMask & 4);
      delete oDesc.get;
      delete oDesc.set;
    }
    oDesc.enumerable = Boolean(nMask & 1);
    oDesc.configurable = Boolean(nMask & 2);
    Object.defineProperty(oObj, sKey, oDesc);
    return oObj;
  };
})();

// створення нового порожнього об'єкта
var myObj = {};

// додавання дескриптора даних, доступного дня запису - недоступний для налаштування та переліку
Object.setProperty(myObj, 4, 'myNumber', 25);

// додавання дескриптора даних тільки для читання - недоступний для налаштування, доступний для переліку
Object.setProperty(myObj, 1, 'myString', 'Привіт!');

// і т.д, і т.п.
Заувага: Метод Object.setProperty() також може бути запропонований як новий вбудований метод JavaScript (див. ECMAScript bug 335).

Синтаксис

Object.setProperty(obj, bitmask, prop[, value/getter[, setter]])

Параметри

obj
Об'єкт, на якому визначається властивість.
bitmask
Бітова маска дескриптора (дивіться нижче).
prop
Ім'я властивості, яка визначається чи модифікується.
value/getter
Необов'язковий. Значення, що призначається дескриптору даних чи функції-гетеру для присвоєння дескриптору доступу (в залежності від бітової маски).
setter
Необов'язковий. Функція-сетер для присвоєння дескриптору доступу. Якщо прапор 0x8 встановлений у дескриптор даних, цей аргумент ігноруватиметься.

Опис

Не вбудований метод Object.setProperty() працює як вбудований метод Object.defineProperty(), за винятком об'єкта-дескриптора, який замінений бітовою маскою. Аргумент bitmask має наступну структуру:

flag 0x1
Властивість доступна для переліку.
flag 0x2
Властивість доступна для налаштування.
flag 0x4
Властивість доступна для запису.
flag 0x8
Властивість є дескриптором доступу.

Отже, бітова маска може мати такі можливі числові значення:

  • 0: Бітова маска відображає дескриптор даних тільки для читання - недоступний для налаштування та переліку (0000).
  • 1: Бітова маска відображає дескриптор даних тільки для читання - недоступний для налаштування, доступний для переліку (0001).
  • 2: Бітова маска відображає дескриптор даних тільки для читання - доступний для налаштування, недоступний для переліку (0010).
  • 3: Бітова маска відображає дескриптор даних тільки для читання - доступний для налаштування та переліку (0011).
  • 4: Бітова маска відображає дескриптор даних, доступний для запису - недоступний для налаштування та переліку (0100).
  • 5: Бітова маска відображає дескриптор даних, доступний для запису - недоступний для налаштування, доступний для переліку (0101).
  • 6: Бітова маска відображає дескриптор даних, доступний для запису - доступний для налаштування, недоступний для переліку (0110).
  • 7: Бітова маска відображає дескриптор даних, доступний для запису - доступний для налаштування та переліку (0111).
  • 8: Бітова маска відображає дескриптор доступу - недоступний для налаштування та переліку (1000).
  • 9: Бітова маска відображає дескриптор доступу - недоступний для налаштування, доступний для переліку (1001).
  • 10: Бітова маска відображає дескриптор доступу - доступний для налаштування, недоступний для переліку (1010).
  • 11: Бітова маска відображає дескриптор доступу - доступний для налаштування та переліку (1011).
Заувага: Якщо прапор 0x8 встановлений у дескриптор доступу, прапор 0x4 (доступний для запису) ігноруватиметься. Якщо ні, то аргумент setter ігноруватиметься.

Реалізація HTMLSelectElement.selectedIndex

Метод Object.defineProperty() також можна використовувати із вбудованими об'єктами. Наступний приклад демонструє, як реалізувати властивість selectedIndex інтерфейсу HTMLSelectElement у групах перемикачів.

<!doctype html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>Radio group selectedIndex example</title>
<script type="text/javascript">
Object.defineProperty(NodeList.prototype, 'selectedIndex', {
  get: function() {
    var nIndex = this.length - 1;
    while (nIndex > -1 && !this[nIndex].checked) {
      nIndex--;
    }
    return nIndex;
  },

  set: function(nNewIndex) {
    if (isNaN(nNewIndex)) {
      return;
    }
    var nOldIndex = this.selectedIndex;
    if (nOldIndex > -1) {
      this[nOldIndex].checked = false;
    }
    if (nNewIndex > -1) {
      this[nNewIndex].checked = true;
    }
  },

  enumerable: true,
  configurable: false
});

// спробуйте!
function checkForm() {
  var nSelectedIndex = document.myForm.myRadioGroup.selectedIndex;
  if (nSelectedIndex < 0) {
    alert('Оберіть предмет!!');
    return false;
  }
  alert('Вітаємо!! Ви обрали ' + document.myForm.myRadioGroup[nSelectedIndex].value + '.');
  return true;
}
</script>
</head>

<body>
  <form name="myForm" onsubmit="return(checkForm());">
    <fieldset><legend>Оберіть предмет</legend>
      <p><input type="radio" name="myRadioGroup" id="ourShirt" value="сорочка" /> <label for="ourShirt">сорочка</label><br />
      <input type="radio" name="myRadioGroup" id="ourPants" value="штани" /> <label for="ourPants">штани</label><br />
      <input type="radio" name="myRadioGroup" id="ourBelt" value="ремінь" /> <label for="ourBelt">ремінь</label><br />
      <input type="radio" name="myRadioGroup" id="ourShoes" value="взуття" /> <label for="ourShoes">взуття</label></p>
      <p><span style="cursor:pointer;text-decoration:underline;color:#0000ff;" onclick="document.myForm.myRadioGroup.selectedIndex=2;">Оберіть улюблений предмет ;-)</span></p>
      <p><input type="submit" value="Замовити!" />
    </fieldset>
  </form>
</body>
</html>