[[Инженер JavaScript — 10]]

Object.defineProperty:

Мы можем определить свойство, которое может быть свойством дескриптора данных, свойством дескриптора доступа или символом, используя Object.defineProperty.

const obj = { 
  a: 500
};
// Define a data descriptor property on obj with name 'b' and the below descriptors
Object.defineProperty(obj, 'b', {
  value: 100,
  writable: true, 
  enumerable: true,
  configurable: true
});

// Define getters and setters on obj with name 'distance' and the below descriptors
Object.defineProperty(obj, 'distance', { 
  get: function () { 
    return Math.sqrt(this.a * this.a + this.b * this.b);
  }, 
  set: function (val) { 
    const [x, y] = val; 
    this.a = x;
    this.b = y;
  }
});

// obj now contains a, b, distance getter, distance setter
console.log(obj); 
console.log(obj.distance); // this is obj, implicit binding
obj.distance = [ 150, 405]; // this is obj, implicit binding
console.log(obj.distance); // this is obj, implicit binding

Кто-то может спросить, что свойства могут быть добавлены непосредственно к объекту во время создания с использованием синтаксиса литерала объекта, как показано ниже, тогда зачем нам нужна служебная функция, такая как Object.defineProperty?

const obj = { 
  a: 500,
  b: 100,
  get distance() {
    return Math.sqrt(this.a * this.a + this.b * this.b);
  },
  set distance(val) {
    const [x, y] = val; 
    this.a = x;
    this.b = y;
  }
};

console.log(obj); 
console.log(obj.distance); // this is obj, implicit binding
obj.distance = [ 10, 40]; // this is obj, implicit binding
console.log(obj.distance); // this is obj, implicit binding

Поскольку объекты JavaScript обеспечивают гибкость динамического добавления свойств к объектам, необходимо иметь утилиту для добавления любого типа свойств после того, как объект был создан и используется.

Теперь некоторые могут возразить, что можно напрямую назначать свойства объекту, используя алгоритм [[Set]]. Действительно ли нам нужен Object.defineProperty?

const obj = { 
  a: 500
};
obj.b = 100; 
// directly used [[Set]] algorithm to add a data descriptor property

// Define getters and setters on obj with name 'distance' and the below descriptors
Object.defineProperty(obj, 'distance', { 
  get: function () { 
    return Math.sqrt(this.a * this.a + this.b * this.b);
  }, 
  set: function (val) { 
    const [x, y] = val; 
    this.a = x;
    this.b = y;
  }
});

// obj now contains a, b, distance getter, distance setter
console.log(obj); 
console.log(obj.distance); // this is obj, implicit binding
obj.distance = [ 150, 405]; // this is obj, implicit binding
console.log(obj.distance); // this is obj, implicit binding

Можно добавить свойство дескриптора данных к существующему объекту с помощью алгоритма [[Set]], но необходимо определить свойство получения или установки для существующего объекта с помощью Object.defineProperty.

Даже использование алгоритма [[Set]] для определения свойства может работать не во всех случаях.
Пример 1: Приведенная ниже программа выдает ошибку, у нас есть объект pqr, свойство x которого помечено как доступное для записи: false. Когда объект obj наследуется от pqr, теперь инструкция obj.x = 321 завершается ошибкой, так как JavaScript замечает, что первое вхождение свойства x в цепочке [[Prototype]] объекта obj (включая obj) доступно только для чтения. Механизм [[Set]] должен соблюдать ограничение и, следовательно, либо молча выйдет из строя, либо выдаст ошибку в зависимости от режима работы.

'use strict';
const pqr = {};
Object.defineProperty(pqr, 'x', {
  writable: false,
  value: 1000
});

const obj = { a: 500 };
Object.setPrototypeOf(obj, pqr);
obj.x = 321;

Точно так же, когда выполняется obj.x = 321, первое появление в цепочке [[Prototype]] obj является функцией установки, затем механизм [[Set]] просто вызовет функцию установки с этим контекстом, указывающим на объект obj. Приведенная ниже программа не добавляет свойство x к obj, вместо этого она использует функцию установки и в конечном итоге добавляет свойство _val к ​​obj.

'use strict';

const pqr = {};
Object.defineProperty(pqr, 'x', {
  get: function () {
    return this._val; // [[Get]] used
  },
  set: function (val) {
    this._val = 5 * val; // [[Set]] used
  }
});

const obj = { a: 500 };
Object.setPrototypeOf(obj, pqr);

obj.x = 321; // [[Set]] used // calls setter function, this is obj
console.log(obj.x); // [[Get]] used, output: 1605

Решение. Если вы не хотите использовать унаследованное свойство getter и setter, а вместо этого хотите определить свойство дескриптора данных для объекта obj, используйте свойство Object.defineProperty. Обратите внимание, что после того, как свойство дескриптора данных x определен в obj, теперь первое вхождение obj в цепочке [[Prototype]] (включая obj) также изменится.

'use strict';
const pqr = {};
Object.defineProperty(pqr, 'x', {
  get: function () {
    return this._val; // [[Get]] used
  },
  set: function (val) {
    this._val = 5 * val; // [[Set]] used
  }
});

const obj = { a: 500 };
Object.setPrototypeOf(obj, pqr);

Object.defineProperty(obj, 'x', {
  value: 0,
  writable: true, 
  enumerable: true,
  configurable: true
});

obj.x = 321; // [[Set]] used, 
// overwrites the data descriptor property that was defined on obj

console.log(obj.x); // [[Get]] used, output: 321

Object.defineProperties:

Он используется для определения нескольких свойств с дескрипторами объекта.

Использование:

const obj = {};
Object.defineProperties(obj, { 
   b: { 
       enumerable: true,
       writable: true, 
       value: 500,
       configurable: false
   },
   c: {
       enumerable: true,
       writable: true, 
       value: 780,
       configurable: false
   }
});

Пример реализации:

Object.defineProperties = function (target, propertiesConfiguration) {
  for(const [propertyName, descriptors] of Object.entires(
              propertiesConfiguration)) {
    Object.defineProperty(target, propertyName, descriptors);
  } 
  return target;
}

Object.create:

Object.create возвращает новый объект, [[Prototype]] которого связан с объектом, переданным в качестве первого аргумента.

const pqr = { a: 10 };
const obj = Object.create(pqr); // obj is an empty object connected to pqr

Если вы хотите добавить свойства к созданному объекту, определите их во втором аргументе вместе с его дескрипторами.

const pqr = { a: 10 };
const obj = Object.create(pqr, { 
   b: { 
       enumerable: true,
       writable: true, 
       value: 500,
       configurable: false
   },
   c: {
       enumerable: true,
       writable: true, 
       value: 780,
       configurable: false
   }
});

Пример реализации Object.create:

Object.create = function (objToConnect, propertiesConfiguration) {
  const result = {};
  Object.setPrototypeOf(result, objToConnect);
  Object.defineProperties(result, propertiesConfiguration);
}

Object.assign:

Просто перебирает все собственные перечисляемые свойства из нескольких исходных объектов слева направо, затем использует алгоритм [[Set]] для целевого объекта и алгоритм [[Get]] для исходного объекта. Изменяет целевой объект, переданный в качестве аргумента.

const sourceA = { 
  a: 10,
  b: 20,
  get z() { 
    return 530;
  }
};

const sourceB = { a: 140, x: 30, y: 50 };
const target = Object.assign({}, sourceA, sourceB);
console.log(target); // { a: 140, b: 20, x: 30, y: 50, z: 530 };

Пример реализации Object.assign:

Object.assign = function (target, ...sources) {
  for(const sourceObj of sources) {
    // own enumerable properties using Object.entries
    for(const [key, value] of Object.entries(sourceObj)) {
        target[key] = value;
    }
    // own enumerable symbol
    for(const symbol of Object.getOwnPropertySymbols(sourceObj)) {
       if(sourceObj.propertyIsEnumerable(symbol)) { 
         target[symbol] = sourceObj[symbol];
       }
    }
  }
  
  return target;
}

Object.keys:

Возвращает все собственные перечисляемые свойства (имена свойств дескриптора данных и дескриптора доступа) для заданного объекта. Обратите внимание, что объекты могут динамически изменяться в JavaScript путем добавления/удаления свойств.

const object1 = {
  b: 120,
  c: false,
  get x() { 
     return this.b;
  }
};

// defined a non enumerable property on object1
Object.defineProperty(object1, 'z' , {
  enumerable: false,
  writable: true,
  configurable: false,
  value: 2000
});

console.log(Object.keys(object1)); // Output: ['b', 'c', 'x']

Пример реализации:

Object.entries = function(obj) {
  const ownProperties = Object.getOwnPropertyNames(obj);
  const result = [];

  for(const propertyName of ownProperties) {
    if(obj.propertyIsEnumerable(propertyName)) {
        // own enumerable property
        result.push(propertyName);
    }
  }
  return result;
}

Object.values:

Возвращает значения всех собственных перечислимых свойств (свойство дескриптора данных и дескриптора доступа), используя алгоритм [[Get]] для данного объекта. Обратите внимание, что объекты могут динамически изменяться в JavaScript.

const object1 = {
  b: 120,
  c: false,
  get x() { 
     return this.b;
  }
};

console.log(Object.values(object1)); // Output: [120, false, 120]

Пример реализации:

Object.entries = function(obj) {
  const ownProperties = Object.getOwnPropertyNames(obj);
  const result = [];
  for(const propertyName of ownProperties) {
    if(obj.propertyIsEnumerable(propertyName)) {
        // own enumerable property
        result.push(obj[propertyName]);
    }
  }
  return result;
}

Object.entries:

Возвращает массив массивов, каждый внутренний массив содержит 2 элемента, имя собственного перечисляемого свойства и значение соответствующего свойства, полученное с помощью алгоритма [[Get]] на объекте.

const object1 = {
  b: 120,
  c: false,
  get x() { 
     return this.b;
  }
};

console.log(Object.entries(object1)); 
// Output: [['b', 120], ['c', false], ['x', 120]]

Пример реализации:

Object.entries = function(obj) {
  const ownProperties = Object.getOwnPropertyNames(obj);
  const result = [];
  for(const propertyName of ownProperties) {
    if(obj.propertyIsEnumerable(propertyName)) {
        // own enumerable propertyName
        result.push([ propertyName, obj[propertyName] ]);
    }
  }
  return result;
}

Идея ограничения свойств объекта:

Если нам нужен некоторый контроль над динамической природой объектов в JavaScript, мы можем использовать некоторые функции, предоставляемые JavaScript.

Чтобы избежать добавления каких-либо дополнительных свойств к существующему объекту, мы можем использовать Object.preventExtensions. Кроме того, [[Prototype]] объекта нельзя изменить после вызова preventExtensions для объекта.

'use strict';
const obj = { a: 200, b: 500 };
Object.preventExtensions(obj);
obj.a = 10; // changing the value of existing property is allowed
obj.b = 20;
// obj.x = 500; // throws an error since adding a new property is not allowed
console.log(obj); // { a:10, b:20 }

delete obj.a; // delete operation on existing property is allowed

// obj.a = 600;  // throws an error, 
// cannot add property a after deleting since addition of properties prohibited
console.log(obj);

Таким образом, мы успешно обеспечили невозможность добавления нового свойства к объекту с помощью операции.

В чем польза Object.seal?
Целью уплотнения является обеспечение того, чтобы никакое свойство никогда не могло быть добавлено или удалено из объекта. На самом деле печать также гарантирует, что дескрипторы свойства не могут быть переопределены по отношению к объекту. Все, что мы можем сделать, это работать с фиксированным количеством свойств, определенных для объекта.

Чтобы гарантировать невозможность добавления свойств: Object.seal внутренне вызывает Object.preventExtensions
. Чтобы гарантировать, что свойства никогда не будут удалены из объекта или переопределены для объекта: каждое свойство в объекте помечается флагом дескриптора configurable: false.

'use strict';
const obj = { a: 200, b: 500 };
Object.seal(obj);
obj.a = 100; // using the existing own enumerable properties
obj.b = 300;
console.log(obj);

// delete obj.a; // throws an error, property is not configurable
// obj.x = 500; // throws an error, cannot add a new property on obj

// error, cannot redefine an existing property
// Object.defineProperty(obj, 'a', {
//   get: function() {
//     return this._val;
//   },
//   set: function(val) {
//     this._val = 7 * val;
//   }
// });

Object.freeze:

Каков последний уровень неизменности, который может быть достигнут в отношении объекта JavaScript?

Запретить добавление свойств [ предотвратить расширения ]
Запретить удаление свойств [ настраиваемый: false ]
Запретить переопределение свойств [ настраиваемый: false ]
Запретить изменение значения свойства/Запретить [[Set] ] механизм для свойства
[доступно для записи: false]

'use strict';
const obj = { a: 200, b: 500 };
Object.freeze(obj);
// obj.a = 100; // error, cannot change a value using [[Set]] algorithm
console.log(obj);

// delete obj.a; // throws an error, property is not configurable
// obj.x = 500; // throws an error, cannot add a new property on obj

// error, cannot redefine an existing property
// Object.defineProperty(obj, 'a', {
//   get: function() {
//     return this._val;
//   },
//   set: function(val) {
//     this._val = 7 * val;
//   }
// });

Неизменяемые объекты можно использовать для определения констант, перечислений и т. д.

Object.getPrototypeOf и Object.setPrototypeOf:

Object.getPrototypeOf используется для получения объекта, который является [[Prototype]] объекта. С другой стороны, Object.setPrototypeOf используется для изменения [[Prototype]] объекта. Последствия этих функций высоки, поскольку доступ к свойствам в объекте JavaScript поддерживается через механизмы цепочки [[Prototype]], такие как механизм [[Set]] и механизм [[Get]]. Таким образом, изменение ссылки [[Prototype]] объекта может привести к изменению цепочки связанных объектов. Обратите внимание, что если изменение [[Prototype]] объекта запрещено, setPrototypeOf выдаст ошибку.

const obj = {
  a: 10,
  b: 20
};

const pqr = {
  b: 20,
  c: 50,
  d: 600
};

const xyz = {
  c: 100,
  x: 500
};

Object.setPrototypeOf(obj, pqr);
console.log(obj);
console.log(obj.c);
console.log(obj.d);
obj.c = 100; // [[Set]] added a data descriptor property to obj 
console.log("obj ", obj);
console.log("pqr ", pqr);

pqr.d = 1000; // changes the data descriptor property d of pqr
console.log(obj.d);
console.log(obj.x);
Object.setPrototypeOf(pqr, xyz);
console.log(obj.x);
console.log(Object.getPrototypeOf(obj) === pqr); // true
console.log(Object.getPrototypeOf(pqr) === xyz); // true

Для чего полезен Object.prototype.isPrototypeOf?

Он отвечает на вопрос, наследуется ли объект A от объекта B. Он просматривает цепочку [[Prototype]] объекта A и ищет в ней объект B, поскольку наследование достигается через цепочку [[Prototype]].

const obj = {
  a: 10,
  b: 20
};

const pqr = {
  b: 20,
  c: 50,
  d: 600
};

const xyz = {
  c: 100,
  x: 500
};

Object.setPrototypeOf(obj, pqr);
Object.setPrototypeOf(pqr, xyz);
console.log(pqr.isPrototypeOf(obj)); // true, this will be pqr
console.log(xyz.isPrototypeOf(obj)); // true, this will be xyz

Пример реализации:

Object.prototype.isPrototypeOf = function (inheritingObj) {
  const obj = this;
  // check if inheritingObj inherits from obj, 
  // or check if obj lies in [[Prototype]] chain of inheritingObj
  let chainObj = Object.getPrototypeOf(inheritingObj);
  while(chainObj) {
    if(chainObj === obj) {
      return true;
    }
    chainObj = Object.getPrototypeOf(chainObj);
  }
  return false;
}

Object.hasOwn:

Возвращает значение, если существует собственное свойство с именем свойства или если для объекта существует свойство собственный символ. Не учитывает цепочку [[Prototype]].

const obj = { a: 50, b: 60 };
const pqr = { c: 100 };
conole.log(Object.hasOwn(obj, 'a')); // true
console.log(Object.hasOwn(pqr, 'c')); // true

Object.setPrototypeOf(obj, pqr);
console.log(obj.c); // 100
console.log(Object.hasOwn(obj, 'c')); // false

Object.getOwnPropertyNames:

Возвращает массив имен свойств, соответствующих собственному дескриптору данных и собственному дескриптору доступа объекта (включая неперечисляемые собственные свойства). Не включает символы. Не включает свойства из цепочки [[Prototype]].

const obj = { a: 50, b: 60 };
conole.log(Object.getOwnPropertyNames(obj)); // [ 'a', 'b' ]

оператор in в JavaScript:

Проверяет наличие свойства (включая неперечислимые свойства) в объекте obj или любом объекте в его цепочке [[Prototype]].

const obj = {
  a: 500
};

const pqr = {
  b: 1000  
}

// defined a non enumerable property on object1
Object.defineProperty(obj, 'z' , {
  enumerable: false,
  writable: true,
  configurable: false,
  value: 2000
});

Object.setPrototypeOf(obj, pqr);

console.log('a' in obj); // true
console.log('b' in obj); // true
console.log('z' in obj); // true

Цикл for .. in:

Перебирает все перечисляемые свойства объекта obj и всю его цепочку [[Prototype]].

const obj = {
  a: 20,
  b: 50
};

// non enumerable own property
Object.defineProperty(obj, 'c', {
  enumerable: false,
  value: 500,
  writable: false,
  configurable: true
});

const xyz = {
  x: 500,
  y: 1000
};

// non enumerable chain property
Object.defineProperty(xyz, 'z', {
  enumerable: false,
  value: 500,
  writable: false,
  configurable: true
});

Object.setPrototypeOf(obj, xyz);

for (const key in obj) {
  console.log("propertyName ", key); // a, b, x, y
  console.log("Property Value ", obj[key]);
}

Большинство свойств в Object.prototype помечены как перечисляемые: false, чтобы они не появлялись в цикле for .. in

Цикл for ..

Цикл for .. of используется для перебора всех значений, возвращаемых объектом итератора с использованием итерируемого протокола. В случае массивов значение по умолчанию состоит из всех элементов массива. Таким образом, for .. of можно использовать для перебора элементов массива по порядку.

Мы будем использовать цикл for .. вместе с Object.keys, Object.values ​​или Object.entries, поскольку обычно нас интересует цикл по массиву, содержащему собственные перечисляемые свойства объекта.
Обычно мы избегаем использования цикла for .. in , так как он заканчивается перебором перечислимых свойств объекта и всей его цепочки.

const arr = [1, 6, 10, 22];
for(const element of arr) {
  console.log(element);
}

Использование с Object.entries

const obj = {
  'Java': 'Gosling',
  'JavaScript': 'Eich',
  'C': 'Ritichie',
  'C++': 'Stroustrup'
}

for (const [ language, creator ] of Object.entries(obj)) {
  console.log("Language ", language, " Creator ", creator);
};

Типы в JavaScript:

JavaScript имеет фиксированное количество типов и не позволяет добавлять новые типы.

  1. тип номера: 12, 14,6
  2. логический тип: истина, ложь
  3. тип строки: «Hello World»
  4. тип символа: Symbol(‘a’)
  5. тип объекта: { a: 10 } используется для представления объектов
  6. Тип bigint: 9007199254740991n используется для представления большого целого числа.
  7. тип null: null используется для представления нулевого указателя.
  8. неопределенный тип: неопределенное значение, используемое для представления отсутствующих значений
  9. тип функции: классы и функции являются особым типом объектов и имеют свой собственный тип

оператор typeof:

console.log(typeof 12.4); // number
console.log(typeof null); // should have been null, shown as object, bug in JavaScript
console.log(typeof x); // undefined , no x is defined
console.log(typeof undefined); // undefined
console.log(typeof { a: 12 }); // object
const sym = Symbol('a');
console.log(typeof sym); // symbol
console.log(typeof 9007199254740991n); // bigint
console.log(typeof "Hello World"); // string
console.log(typeof true); // boolean
function showHello() { 
  console.log("Hello World");
}
console.log(typeof showHello); // function

Более того, примитивные типы также имеют типы-оболочки.

оператор instanceof в JavaScript:

Оператор ожидает объект в качестве левого операнда и функцию-конструктор в качестве правого операнда и определяет, наследуется ли объектом «прототип» функции-конструктора в нижнем регистре.

function Human(age, name) {
   this.age = age;
   this.name = name;
}

const prince = new Human(23, "Prince Jha");
console.log(prince instanceof Human);  // true
// checks Human.prototype.isPrototypeOf(prince);