What is the difference between prototype and __proto__ in javascript?

b. We can attach them to an object, as its properties

-The easiest way to do this is modifying the empty object, like:


this way we can call them like:

and now the object is like:

-The other way to attach a property to an object is using the of that object that can be find in any JavaScript object with the name of , and I have tried to explain it a bit on the summary part. So we could get the similar result by doing:

But this way what we actually are doing is modifying the , because whenever we create a JavaScript object using literals (), it gets created based on , which means it gets attached to the newly created object as an attribute named , so if we change it, as we have done on our previous code snippet, all the JavaScript objects would get changed, not a good practice. So what could be the better practice now:

and now other objects are in peace, but it still doesn’t seem to be a good practice. So we have still one more solutions, but to use this solution we should get back to that line of code where object got created () then change it like:

what it does is creating a new JavaScript and attach the to the attribute. So to make sure you can do:

But the tricky point here is you have access to all the properties defined in on the first level of the object(read the summary part for more detail).

as you see using any of these two way would exactly point to the object.

Значение «this»

В приведённом выше примере может возникнуть интересный вопрос: каково значение внутри ? Куда записаны свойства и : в или в ?

Ответ прост: прототипы никак не влияют на .

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

Таким образом, вызов сеттера в качестве использует , а не .

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

Например, здесь представляет собой «хранилище методов», и использует его.

Вызов устанавливает для объекта :

Картинка с результатом:

Если бы у нас были другие объекты, такие как , и т.д., унаследованные от , они также получили бы доступ к методам . Но при вызове каждого метода будет соответствовать объекту (перед точкой), на котором происходит вызов, а не . Поэтому, когда мы записываем данные в , они сохраняются в этих объектах.

В результате методы являются общими, а состояние объекта — нет.

Наследование прототипов

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

В конце цепочки прототипов находится Object.prototype. Все объекты наследуют свойства и методы Object. Любая попытка поиска за пределами цепочки приводит к null.

В нашем примере x – пустой объект, который наследуется от Object. x может использовать любое свойство или метод, которые имеет Object, например toString().

Эта цепочка прототипов состоит из всего одной ссылки (x -> Object). Это понятно потому, что если вы попытаетесь связать два свойства ], получится null.

Давайте рассмотрим другой тип объекта. Если у вас есть опыт работы с массивами JavaScript, вы знаете, что у них много встроенных методов (таких как pop() и push()). У вас есть доступ к этим методам при создании нового массива потому, что любой массив, который вы создаете, имеет доступ к свойствам и методам Array.prototype.

Создайте новый массив:

Помните, что создать его можно также с помощью конструктора массива: let y = new Array().

Если посмотреть на ] нового массива y, вы увидите, что он имеет больше свойств и методов, чем объект x. Он унаследовал все это от Array.prototype.

Вы увидите свойство constructor в прототипе, для которого задано значение Array(). Свойство constructor возвращает функцию-конструктор объекта, которая является механизмом для построения объектов из функций.

Теперь можно объединить два прототипа, так как в этом случае цепочка прототипов будет длиннее. Он выглядит так: y-> Array -> Object.


Эта цепочка теперь относится к Object.prototype. Можно проверить внутренний ] на свойство prototype функции конструктора, чтобы увидеть, что они ссылаются на одно и то же.

Также для этого можно использовать свойство isPrototypeOf():

Можно использовать оператор instanceof, чтобы проверить, появляется ли свойство prototype конструктора в пределах цепочки прототипов объекта.

Итак, все объекты JavaScript имеют скрытое внутреннее свойство ] (которое можно определить с помощью __proto__ в некоторых браузерах). Объекты могут быть расширены и наследуют свойства и методы от ] их конструктора.

Прототипы складываются в цепочки, и каждый дополнительный объект наследует все по этой цепочке. Цепочка заканчивается на Object.prototype.

Что такое прототип

Прототип — это объект, который по умолчанию связан со всеми функциями и объектами в JavaScript, где свойство prototype функции доступно и модифицируемо, а свойство prototype объекта (он же атрибут) не отображается.

Каждая функция включает объект-прототип по умолчанию.

Объект-прототип — это специальный тип объекта, к которому могут быть добавлены дополнительные свойства, которые будут общими для всех экземпляров его функции конструктора.

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

function Student() {
  this.name = 'John';
  this.gender = 'M';
}

Student.prototype.age = 15;

var studObj1 = new Student();
alert(studObj1.age); // Результат: 15

var studObj2 = new Student();
alert(studObj2.age); // Результат: 15

Каждый объект, который создается с использованием буквального синтаксиса или синтаксиса конструктора с ключевым словом new, включает свойство __proto__, которое указывает на объект-прототип функции, создавшей этот объект.

Вы можете отлаживать и видеть свойство prototype объекта или функции в инструменте разработчика Chrome или Firefox. Рассмотрим следующий пример.

function Student() {
  this.name = 'John';
  this.gender = 'M';
}

var studObj = new Student();

console.log(Student.prototype); // Результат: object
console.log(studObj.prototype); // Результат: undefined
console.log(studObj.__proto__); // Результат: object

console.log(typeof Student.prototype); // Результат: object
console.log(typeof studObj.__proto__); // Результат: object

console.log(Student.prototype === studObj.__proto__ ); // Результат: true

Как видно из приведенного выше примера, к свойству prototype функции можно получить доступ с помощью <имя-функции> .prototype. Однако объект (экземпляр) не предоставляет свойство prototype, вместо этого вы можете получить к нему доступ, используя __proto__.

Изменение встроенных прототипов

Встроенные прототипы можно изменять. Например, если добавить метод к , метод становится доступен для всех строк:

В течение процесса разработки у нас могут возникнуть идеи о новых встроенных методах, которые нам хотелось бы иметь, и искушение добавить их во встроенные прототипы. Это плохая идея.

Важно:

Прототипы глобальны, поэтому очень легко могут возникнуть конфликты. Если две библиотеки добавляют метод , то одна из них перепишет метод другой.

Так что, в общем, изменение встроенных прототипов считается плохой идеей.

В современном программировании есть только один случай, в котором одобряется изменение встроенных прототипов. Это создание полифилов.

Полифил – это термин, который означает эмуляцию метода, который существует в спецификации JavaScript, но ещё не поддерживается текущим движком JavaScript.

Тогда мы можем реализовать его сами и добавить во встроенный прототип.

Например:

The .prototype property:

This is a property that is only found on functions. Using a very simple function:

The property holds an object that will be assigned to when you do . You can easily examine this:

One of the most important s is that of the function. This prototype holds the prototypical object that all chains contain. On it, all the available methods for new objects are defined:

Now, since is an object, it has a property. When you don’t make any assignments to , the ‘s points to the prototypical object (). This is automatically performed anytime you create a new function.

This way, any time you do the prototype chain is set up for you, you get everything defined on and everything defined on :

When you do make assignments to all you are doing is extending the prototype chain to include another object. It’s like an insertion in a singly linked list.

This basically alters the chain allowing properties that are defined on the object assigned to to be seen by any object created by the function.

Поведение примитивных типов данных

В JavaScript строки, числа и булевые переменные:

String, Number, Boolean

относятся к примитивным типам и, строго говоря, не являются объектами. Однако, для них также можно вызывать методы базового объекта, например:

console.log("Hello World".toString());

Конечно, эта запись не имеет особого смысла, т.к. она просто возвратит ту же самую строку, но она показывает, что строка, литерал, здесь превращается в объект. Как такое может быть? В действительности, все очень просто. Если виртуальная машина JavaScript «видит», что к литералу идет обращение как к объекту, то она временно создает объект соответствующего типа, для этого объекта вызывает указанный метод и, затем, временный объект уничтожается. В частности, строка обертывается в объект типа String и мы можем для литерала вызвать, например, метод toUpperCase:

console.log("Hello World".toUpperCase());

Раз это так, то для любого такого объекта, в нашем случае строки, можно менять и добавлять новые свойства и методы, используя объект prototype. Как мы отмечали на предыдущем занятии, если добавить в prototype свойство (или переопределить существующее), то новый созданный объект будет его содержать. Например, мы легко можем переопределить метод toUpperCase:

String.prototype.toUpperCase = function() {
         return this;
}

В результате, он попросту вернет исходную строку. Или, можно добавить новый метод:

String.prototype.len = function() {
         return this.length;
}
 
console.log("Hello World".len());

И так далее. Однако, этим функционалом следует пользоваться с большой осмотрительностью, т.к. если в разных программных модулях будут переопределяться одинаковые методы, но с разной логикой работы, то в основной программе работать будет тот, что импортируется в последнюю очередь. Это может привести к непредвиденным ошибкам. В современном программировании есть только один случай, в котором одобряется изменение встроенных прототипов. Это создание полифилов.

Полифил – это эмуляция метода, который существует в спецификации JavaScript, но ещё не поддерживается текущим движком JavaScript.

Во всех остальных случаях лучше не прибегать к этому механизму.

«Простейший» объект

Как мы знаем, объекты можно использовать как ассоциативные массивы для хранения пар ключ/значение.

…Но если мы попробуем хранить созданные пользователями ключи (например, словари с пользовательским вводом), мы можем заметить интересный сбой: все ключи работают как ожидается, за исключением .

Посмотрите на пример:

Если пользователь введёт , присвоение проигнорируется!

И это не должно удивлять нас. Свойство особенное: оно должно быть либо объектом, либо , а строка не может стать прототипом.

Но мы не намеревались реализовывать такое поведение, не так ли? Мы хотим хранить пары ключ/значение, и ключ с именем не был сохранён надлежащим образом. Так что это ошибка!

Конкретно в этом примере последствия не так ужасны, но если мы присваиваем объектные значения, то прототип и в самом деле может быть изменён. В результате дальнейшее выполнение пойдёт совершенно непредсказуемым образом.

Что хуже всего – разработчики не задумываются о такой возможности совсем. Это делает такие ошибки сложным для отлавливания или даже превращает их в уязвимости, особенно когда JavaScript используется на сервере.

Неожиданные вещи могут случаться также при присвоении свойства , которое по умолчанию функция, и других свойств, которые тоже на самом деле являются встроенными методами.

Как же избежать проблемы?

Во-первых, мы можем переключиться на использование коллекции , и тогда всё будет в порядке.

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

Свойство – не обычное, а аксессор, заданный в :

Так что при чтении или установке вызывается соответствующий геттер/сеттер из прототипа , и именно он устанавливает/получает свойство .

Как было сказано в начале этой секции учебника, – это способ доступа к свойству , это не само свойство .

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

создаёт пустой объект без прототипа ( будет ):

Таким образом не будет унаследованного геттера/сеттера для . Теперь это свойство обрабатывается как обычное свойство, и приведённый выше пример работает правильно.

Мы можем назвать такой объект «простейшим» или «чистым словарным объектом», потому что он ещё проще, чем обычные объекты .

Недостаток в том, что у таких объектов не будет встроенных методов объекта, таких как :

…Но обычно это нормально для ассоциативных массивов.

Обратите внимание, что большая часть методов, связанных с объектами, имеют вид. К примеру,

Подобные методы не находятся в прототипе, так что они продолжат работать для таких объектов:

Шаблон «Прототип»

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

Вместо того, чтобы указывать свойства и методы в конструкторе, их можно указать непосредственно прототипу:

function User() {}
User.prototype.name = "Homer";
User.prototype.age = "33";
User.prototype.sayHi = function () {
  console.log("Hello!");
};

let user = new User();
console.log(user.name);

По умолчанию все прототипы имеют только свойство , содержащее ссылку на функцию, к которой оно относится:

function foo() {}
console.log(foo.prototype.constructor === foo);   // true

Все остальные методы и свойства наследуются от типа . Когда с помощью конструктора создаётся новый объект, в нём определяется внутренний указатель (ссылка) на прототип конструктора. Доступ к этому указателю можно получить с помощью метода :

function foo() {}
let obj = new foo();
console.log(Object.getPrototypeOf(obj) === foo.prototype);   // true

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


Если в объект добавить свойство с именем, как у свойства прототипа, то у объекта будет создано собственное свойство, в этом случае при следующем чтении свойства будет использоваться свойство объекта, а не прототипа.

Чтобы писать меньше кода, можно перезаписать прототип литералом объекта, содержащим все свойства и методы:

function User() {}
User.prototype = {
  name: "Homer",
  age: 33,
  sayHi: function () {
    console.log("Hello!");
  }
};

// Восстановление свойства constructor
Object.defineProperty(User.prototype, "constructor", {
  enumerable: false,
  value: User
});

В этом примере свойству присваивается новый объект, созданный с помощью литерала. Он полностью заменяет собою объект, предлагаемый по умолчанию. Результат получается таким же, как и в предыдущем примере, за одним исключением: свойство больше не указывает на функцию . Явное добавление свойства со значением в литерал объекта решает эту проблему. Свойство у встроенных объектов по умолчанию неперечислимо, поэтому для его добавления использовался метод .

Прототип proto

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

Свойство доступно во всех браузерах, кроме IE10-, а в более старых IE оно, конечно же, тоже есть, но напрямую к нему не обратиться, требуются чуть более сложные способы, которые мы рассмотрим позднее.

Пример кода (кроме IE10-):

  1. Первый здесь работает очевидным образом – он выводит свойство объекта .
  2. Второй хочет вывести , ищет его в самом объекте , не находит – и продолжает поиск в объекте , то есть, в данном случае, в .

Иллюстрация происходящего при чтении (поиск идёт снизу вверх):

Объект, на который указывает ссылка , называется «прототипом». В данном случае получилось, что является прототипом для .

Также говорят, что объект «прототипно наследует» от .

Обратим внимание – прототип используется исключительно при чтении. Запись значения, например, или удаление – работает напрямую с объектом

В примере ниже мы записываем свойство в сам , после чего перестаёт брать его у прототипа, а берёт уже из самого объекта:

Другими словами, прототип – это «резервное хранилище свойств и методов» объекта, автоматически используемое при поиске.

У объекта, который является , может быть свой , у того – свой, и так далее. При этом свойства будут искаться по цепочке.

Ссылка proto в спецификации

Если вы будете читать спецификацию ECMAScript – свойство обозначено в ней как .

Двойные квадратные скобки здесь важны, чтобы не перепутать его с совсем другим свойством, которое называется , и которое мы рассмотрим позже.

Флаги свойств

Помимо значения , свойства объекта имеют три специальных атрибута (так называемые «флаги»).

  • – если , свойство можно изменить, иначе оно только для чтения.
  • – если , свойство перечисляется в циклах, в противном случае циклы его игнорируют.
  • – если , свойство можно удалить, а эти атрибуты можно изменять, иначе этого делать нельзя.

Мы ещё не встречали эти атрибуты, потому что обычно они скрыты. Когда мы создаём свойство «обычным способом», все они имеют значение . Но мы можем изменить их в любое время.

Сначала посмотрим, как получить их текущие значения.

Метод Object.getOwnPropertyDescriptor позволяет получить полную информацию о свойстве.

Его синтаксис:

Объект, из которого мы получаем информацию.
Имя свойства.

Возвращаемое значение – это объект, так называемый «дескриптор свойства»: он содержит значение свойства и все его флаги.

Например:

Чтобы изменить флаги, мы можем использовать метод Object.defineProperty.

Его синтаксис:

,
Объект и его свойство, для которого нужно применить дескриптор.
Применяемый дескриптор.

Если свойство существует, обновит его флаги. В противном случае метод создаёт новое свойство с указанным значением и флагами; если какой-либо флаг не указан явно, ему присваивается значение .

Например, здесь создаётся свойство , все флаги которого имеют значение :

Сравните это с предыдущим примером, в котором мы создали свойство «обычным способом»: в этот раз все флаги имеют значение . Если это не то, что нам нужно, надо присвоить им значения в параметре .

Теперь давайте рассмотрим на примерах, что нам даёт использование флагов.

Методы create, getPrototypeOf и setPrototypeOf

На сегодняшний день свойство __proto__ считается устаревшим и формально поддерживается только в браузерной среде. Хотя, почти все остальные среды по-прежнему позволяют им пользоваться. Ему на смену пришли новые методы объекта Object:

  • Object.create(proto, ) – используется для создания нового объекта с указанием базового (proto) и необязательным набором дополнительных дескрипторов свойств – descriptors;

  • Object.getPrototypeOf(obj) – возвращает ссылку на базовый объект, либо null, если его нет;

  • Object.setPrototypeOf(obj, proto) – назначает базовый объект proto для уже существующего объекта obj.

Например, используя объекты из предыдущего занятия:

let prop = {
         sp {x , y },
         ep {x 100, y 20},
         get coords() {
                   return this.sp.x, this.sp.y, this.ep.x, this.ep.y;
         },
         set coords(coords) {
                   this.sp.x = coords; this.sp.y = coords1;
                   this.ep.x = coords2; this.ep.y = coords3;
         }
};
 
function Rect() {
         this.name = "прямоугольник";
 
         this.draw = function() {
                   console.log("Рисование фигуры: "+this.name);
         }
 
         this.__proto__ = prop;
}

Перепишем объявление объекта Rect с использованием метода create:

let rect = Object.create(prop, {
         name {value "прямоугольник", writable true},
         draw {value function() {
                            console.log("Рисование фигуры: "+this.name);
                   }
         },
});
 
console.log( rect.coords );
rect.draw();

Чтобы получить ссылку на базовый класс, можно воспользоваться методом getPrototypeOf:

console.log( Object.getPrototypeOf(rect) === prop );

Наконец, для замены базового объекта на другой, выполним метод setPrototypeOf:

Object.setPrototypeOf( rect, {} );

Мы здесь указали пустой объект вместо прежнего prop и теперь свойство coords возвращает значение undefined.

JavaScript

JS Array concat() constructor copyWithin() entries() every() fill() filter() find() findIndex() forEach() from() includes() indexOf() isArray() join() keys() length lastIndexOf() map() pop() prototype push() reduce() reduceRight() reverse() shift() slice() some() sort() splice() toString() unshift() valueOf()

JS Boolean constructor prototype toString() valueOf()

JS Classes constructor() extends static super

JS Date constructor getDate() getDay() getFullYear() getHours() getMilliseconds() getMinutes() getMonth() getSeconds() getTime() getTimezoneOffset() getUTCDate() getUTCDay() getUTCFullYear() getUTCHours() getUTCMilliseconds() getUTCMinutes() getUTCMonth() getUTCSeconds() now() parse() prototype setDate() setFullYear() setHours() setMilliseconds() setMinutes() setMonth() setSeconds() setTime() setUTCDate() setUTCFullYear() setUTCHours() setUTCMilliseconds() setUTCMinutes() setUTCMonth() setUTCSeconds() toDateString() toISOString() toJSON() toLocaleDateString() toLocaleTimeString() toLocaleString() toString() toTimeString() toUTCString() UTC() valueOf()

JS Error name message

JS Global decodeURI() decodeURIComponent() encodeURI() encodeURIComponent() escape() eval() Infinity isFinite() isNaN() NaN Number() parseFloat() parseInt() String() undefined unescape()

JS JSON parse() stringify()

JS Math abs() acos() acosh() asin() asinh() atan() atan2() atanh() cbrt() ceil() cos() cosh() E exp() floor() LN2 LN10 log() LOG2E LOG10E max() min() PI pow() random() round() sin() sqrt() SQRT1_2 SQRT2 tan() tanh() trunc()

JS Number constructor isFinite() isInteger() isNaN() isSafeInteger() MAX_VALUE MIN_VALUE NEGATIVE_INFINITY NaN POSITIVE_INFINITY prototype toExponential() toFixed() toLocaleString() toPrecision() toString() valueOf()

JS OperatorsJS RegExp constructor compile() exec() g global i ignoreCase lastIndex m multiline n+ n* n? n{X} n{X,Y} n{X,} n$ ^n ?=n ?!n source test() toString() (x|y) . \w \W \d \D \s \S \b \B \0 \n \f \r \t \v \xxx \xdd \uxxxx

JS Statements break class continue debugger do…while for for…in for…of function if…else return switch throw try…catch var while

JS String charAt() charCodeAt() concat() constructor endsWith() fromCharCode() includes() indexOf() lastIndexOf() length localeCompare() match() prototype repeat() replace() search() slice() split() startsWith() substr() substring() toLocaleLowerCase() toLocaleUpperCase() toLowerCase() toString() toUpperCase() trim() valueOf()


С этим читают