представлять
Эта глава является второй главой о объектно-ориентированной реализации Ecmascript. В первой главе мы обсуждаем сравнение между введением и Cemascript. Если вы не читали первую главу, прежде чем продолжить эту главу, я настоятельно рекомендую вам сначала прочитать первую главу, потому что эта глава слишком длинная (стр. 35).
Оригинальный английский текст: http://dmitrysoshnikov.com/ecmascript/chapter-7-2-oop-ecmascript-implementation/
Примечание. Поскольку статья слишком длинная, ошибки неизбежны, и они постоянно исправляются.
Во введении мы распространяемся на Ecmascript. Теперь, когда мы знаем, что это реализация ООП, давайте точно определим:
Кода -копия выглядит следующим образом:
Ecmascript-это объектно-ориентированный язык программирования, поддерживающий делегирование наследования на основе прототипов.
Ecmascript-это объектно-ориентированный язык, который поддерживает делегированное наследование на основе прототипа.
Мы проанализируем самые основные типы данных. Первое, что нам нужно понять, это то, что Ecmascript использует примитивные значения и объекты для различения сущностей. Следовательно, «в JavaScript все является объектом», упомянутым в некоторых статьях, неправильно (не совсем правильно), а примитивное значение - это некоторые типы данных, которые мы собираемся обсудить здесь.
Тип данных
Хотя Ecmascript-это динамический язык слабого типа, который может динамически конвертировать типы, он все еще имеет типы данных. Другими словами, объект должен принадлежать реальному типу.
В стандартной спецификации определены 9 типов данных, но только 6 доступны в программах Ecmascript. Они: неопределенные, нулевые, логические, строку, номер и объект.
Доступ к другим типам можно получить только на уровне реализации (эти типы не могут использоваться объектами ECMASCRIPT) и используются для спецификаций для объяснения некоторого рабочего поведения и сохранения промежуточных значений. Эти 3 типа: ссылка, список и завершение.
Следовательно, ссылка используется для объяснения таких операторов, как Delete, TypeOF и это, и содержит базовый объект и имя атрибута; Список описывает поведение списка параметров (когда новые выражения и вызовы функций); Завершение используется для объяснения перерыва поведения, продолжения, возврата и выбросов заявлений.
Примитивный тип значения
Оглядываясь назад на типы данных, используемые в программах Ecmascript в 6, первые 5 являются примитивными типами значений, включая неопределенные, нулевые, логические, строку, число и объект.
Пример исходного типа значения:
Кода -копия выглядит следующим образом:
var a = не определен;
var b = null;
var c = true;
var d = 'test';
var e = 10;
Эти значения реализованы непосредственно на нижнем уровне, они не являются объектами, поэтому нет прототипа, нет конструктора.
Дядя Примечание. Хотя эти собственные значения похожи на те, которые мы обычно используем (логическое, строка, число, объект), но не одно и то же. Следовательно, результаты TypeOF (true) и typeOF (логическое) различны, потому что результат типа (логический) является функцией, поэтому функции логических, строковых и числа имеют прототипы (также будут упомянуты следующие атрибуты чтения и записи).
Если вы хотите знать, какой тип данных лучший, лучше всего использовать TypeOF. Есть пример, который необходимо отметить. Если вы используете TypeOF, чтобы судить тип нуля, результатом является объект. Почему? Потому что тип нуля определяется как нулевый.
Кода -копия выглядит следующим образом:
предупреждение (тип нуля); // "объект"
Причина отображения «объекта» заключается в том, что спецификация предусматривает это: возврат «объект» для строкового значения TypeOf нулевого значения.
Спецификация не представляет объяснения этого, но Брендан Эйх (изобретатель JavaScript) заметил, что NULL в основном используется, чтобы объекты появлялись относительно неопределенного, например, установление объекта для нулевой ссылки. Тем не менее, в некоторых документах есть некоторое раздражение, чтобы приписать его к ошибке и поместить ошибку в список ошибок, в котором Брендан Эйх также участвовал в обсуждении. Результатом является то, что если вы отпустите его, вы должны установить результат типа NULL для объекта (хотя стандарт 262-3 определяет тип нуля как нулевой, а 262-5 изменил стандарт на NULL в качестве объекта).
Тип объекта
Затем тип объекта (не следует путать с конструктором объекта, теперь обсуждается только абстрактный тип) является единственным типом данных, который описывает объект Ecmascript.
Объект-это неупорядоченная коллекция пар ключевых значений.
Объект-это неупорядоченная коллекция, содержащая пары ключевых значений
Ключевое значение объекта называется свойством, а свойство представляет собой контейнер исходного значения и других объектов. Если значение свойства является функцией, мы называем это методом.
Например:
Кода -копия выглядит следующим образом:
var x = {// объект "x" имеет 3 свойства: a, b, c
A: 10, // исходное значение
b: {z: 100}, // объект "b" имеет атрибут z
C: function () {// function (метод)
Alert ('Метод X.C');
}
};
оповещение (xa); // 10
оповещение (xb); // [объект объекта]
предупреждение (xbz); // 100
xc (); // 'Метод X.C'
Динамика
Как мы отмечали в главе 17, объекты в ES полностью динамичны. Это означает, что когда программа выполняется, мы можем добавлять, изменять или удалять свойства объекта по желанию.
Например:
Кода -копия выглядит следующим образом:
var foo = {x: 10};
// Добавить новые атрибуты
foo.y = 20;
console.log (foo); // {x: 10, y: 20}
// Измените значение свойства на функцию
foo.x = function () {
console.log ('foo.x');
};
foo.x (); // 'foo.x'
// Удалить атрибуты
Удалить foo.x;
console.log (foo); // {y: 20}
Некоторые свойства не могут быть изменены - (свойства только для чтения, удаленные свойства или невыразимые свойства). Мы объясним это в характеристиках атрибута позже.
Кроме того, спецификация ES5 предусматривает, что статические объекты не могут расширять новые атрибуты, а их страницы свойств не могут быть удалены или изменены. Это так называемые замороженные объекты, которые можно получить, применяя метод объекта. Freeze (O).
Кода -копия выглядит следующим образом:
var foo = {x: 10};
// заморозить объект
Object.freeze (foo);
console.log (object.ifrozen (foo)); // истинный
// не может быть изменен
foo.x = 100;
// не может расширить
foo.y = 200;
// нельзя удалить
Удалить foo.x;
console.log (foo); // {x: 10}
В спецификации ES5 метод object.preventextensions (o) также используется для предотвращения расширений, или метод object.defineproperty (o) используется для определения свойств:
Кода -копия выглядит следующим образом:
var foo = {x: 10};
Object.defineproperty (foo, "y", {
Значение: 20,
Записывается: ложь, // только читать
настраивается: false // не настраивается
});
// не может быть изменен
foo.y = 200;
// нельзя удалить
Удалить foo.y; // ЛОЖЬ
// расширение профилактики и контроля
Object.preventextensions (foo);
console.log (object.isextensible (foo)); // ЛОЖЬ
// не может добавить новые атрибуты
foo.z = 30;
console.log (foo); {x: 10, y: 20}
Встроенные объекты, собственные объекты и объекты хоста
Необходимо отметить, что спецификация также различает эти встроенные объекты, объекты элементов и объекты хоста.
Встроенные объекты и объекты элементов определяются и реализуются спецификацией ECMASCRIPT, а разница между ними тривиальна. Все объекты реализации ECMASCRIPT являются собственными объектами (некоторые из которых являются встроенными объектами, некоторые создаются, когда программа выполняется, например, определенные пользователи объекты). Встроенный объект представляет собой подмножество нативного объекта и встроен в ECMASCRIPT до начала программы (например, Parseint, Match и т. Д.). Все объекты хоста предоставляются в среде хоста, обычно браузером, и могут включать, например, окно, оповещение и т. Д.
Обратите внимание, что объект хоста может быть реализован самим ES и полностью соответствует нормативной семантике. В связи с этим их можно назвать объектами «нативного хоста» (как можно скорее), но норма не определяет концепцию объектов «нативного хоста».
Логические, строковые и номера объекты
Кроме того, спецификация также определяет некоторые нативные классы специальных оберток, эти объекты:
1. логический объект
2. Строковой объект
3. Цифровые объекты
Эти объекты создаются соответствующим встроенным конструктором и содержат собственные значения в качестве внутренних свойств. Эти объекты могут быть преобразованы для сохранения исходного значения и наоборот.
Кода -копия выглядит следующим образом:
var c = new Boolean (true);
var d = new String ('test');
var e = новое число (10);
// конвертируется в исходное значение
// Использование функций без нового ключевого слова
с = логический (c);
d = string (d);
e = число (e);
// переконвертировать в объект
с = объект (c);
d = объект (d);
e = объект (e);
Кроме того, существуют объекты, созданные специальными встроенными конструкторами: функция (конструктор объекта функции), массив (конструктор массива), REGEXP (конструктор регулярного выражения), математика (математический модуль), дата (конструктор даты) и т. Д. Эти объекты также являются значениями типов объектов. Их различия управляются внутренними свойствами. Мы обсудим эти вещи ниже.
Буквальный
Для значений трех объектов: объект, массив и регулярное выражение, они имеют сокращенные идентификаторы, называемые инициализатором объекта, инициализатор массива и инициализатор регулярного выражения:
Кода -копия выглядит следующим образом:
// эквивалентно новому массиву (1, 2, 3);
// или array = new Array ();
// массив [0] = 1;
// массив [1] = 2;
// массив [2] = 3;
var array = [1, 2, 3];
// эквивалентно
// var object = new Object ();
// object.a = 1;
// object.b = 2;
// object.c = 3;
var object = {a: 1, b: 2, c: 3};
// эквивалентно новому regexp ("^// d+$", "g")
var re =/^/d+$/g;
Обратите внимание, что если приведенные выше три объекта переназначены в новый тип, последующая семантика реализации используется в соответствии с новым типом. Например, в текущем носорог и старой версии Spidermonkey 1.7 объекты будут созданы успешно с конструктором нового ключевого слова, но семантика некоторых реализаций (текущий паук/TracemonKey) не может измениться после изменения типа.
Кода -копия выглядит следующим образом:
var getClass = object.prototype.toString;
Объект = номер;
var foo = новый объект;
Alert ([foo, getClass.call (foo)]); // 0, "[номер объекта]"
var bar = {};
// Rhino, Spidermonkey 1.7- 0, "[номер объекта]"
// другие: все еще "[объект объекта]", "[объект объекта]"
оповещение ([bar, getClass.call (bar)]);
// массив имеет такой же эффект
Array = номер;
foo = новый массив;
Alert ([foo, getClass.call (foo)]); // 0, "[номер объекта]"
bar = [];
// Rhino, Spidermonkey 1.7- 0, "[номер объекта]"
// другие: все еще "", "[объект объекта]"
оповещение ([bar, getClass.call (bar)]);
// но для режима, семантика литералов не изменена. семантика буквального
// не изменяется во всех протестированных реализациях
Regexp = число;
foo = new Regexp;
Alert ([foo, getClass.call (foo)]); // 0, "[номер объекта]"
bar = /(?!) /g;
оповещение ([bar, getClass.call (bar)]); ///(?!)/g, "[Объект regexp]"
Литералы корзины и объекты regexp
Обратите внимание, что в следующих двух примерах, в спецификации третьего издания, семантика регулярных выражений эквивалентна. Литералы REGEXP существуют только в одном предложении и создаются на стадии анализа. Тем не менее, конструктор Regexp создает новый объект, поэтому это может вызвать некоторые проблемы, такие как значение Lastindex, неверно во время тестирования:
Кода -копия выглядит следующим образом:
для (var k = 0; k <4; k ++) {
var re = /ecma /g;
предупреждение (re.lastindex); // 0, 4, 0, 4
Alert (re.test ("Ecmascript")); // true, неверно, верно, ложно
}
// Сравнивать
для (var k = 0; k <4; k ++) {
var re = new Regexp ("ecma", "g");
предупреждение (re.lastindex); // 0, 0, 0, 0
Alert (re.test ("Ecmascript")); // правда, правда, правда, правда
}
Примечание. Однако эти вопросы были исправлены в спецификации ES в 5 -м издании. Независимо от того, основаны ли они на литералах или конструкторах, они создают новые объекты.
Ассоциативный массив
Различные статические обсуждения текста, объекты JavaScript (часто создаваемые с помощью инициализатора объекта {}), называются хэш -таблицами и хэш -таблицами или другими простыми названиями: хэш (концепции в Ruby или Perl), управляющие массивы (концепции в PHP), словаря (концепции в Python) и т. Д.
Существуют только такие термины, главным образом потому, что их структуры похожи, то есть с использованием паров «ключа» для хранения объектов, что полностью соответствует структуре данных, определяемой теорией «Ассоциации» или «Хэш-таблица». Кроме того, на уровне реализации обычно используются типы данных о хеш -таблице.
Однако, хотя концепция описана в терминах, это на самом деле ошибка. С точки зрения Ecmascript: Ecmascript имеет только один объект, тип и его подтип, который ничем не отличается от «значения ключей» к хранилищу, поэтому в этом нет особой концепции. Потому что внутренние свойства любого объекта могут храниться в виде клавишных паров:
Кода -копия выглядит следующим образом:
var a = {x: 10};
a ['y'] = 20;
AZ = 30;
var b = новый номер (1);
bx = 10;
по = 20;
b ['z'] = 30;
var c = новая функция ('');
cx = 10;
Cy = 20;
c ['z'] = 30;
// и т. д., подтип любого объекта «подтип»
Кроме того, поскольку объекты могут быть пустыми в Ecmascript, концепция «хэша» здесь также неверна:
Кода -копия выглядит следующим образом:
Object.Prototype.x = 10;
var a = {}; // Создать пустой "хэш"
оповещение (a ["x"]); // 10, но не пусто
предупреждение (A.ToString); // функция
a ["y"] = 20; // добавить новую пару клавиш в "хэш"
предупреждение (["y"]); // 20
Object.Prototype.y = 20; // Добавить атрибуты прототипа
удалить ["y"]; // Удалить
предупреждение (["y"]); // но здесь ключ и значение все еще стоят 20
Обратите внимание, что стандарт ES5 позволяет нам создавать непрототипированные объекты (реализованные с использованием метода Object.Create (null)). С этой точки зрения, такие объекты можно назвать хэш -таблицами:
Кода -копия выглядит следующим образом:
var ahashtable = object.create (null);
console.log (ahashtable.tostring); // Неопределенный
Кроме того, некоторые свойства имеют конкретные методы Getter/Setter, поэтому это также может привести к путанице по поводу этой концепции:
Кода -копия выглядит следующим образом:
var a = new String ("foo");
a ['длина'] = 10;
предупреждение (a ['длина']); // 3
Однако, даже если «хэш» может иметь «прототип» (например, класс, который делегирует хэш -объекты в Ruby или Python), этот термин не является правильным в Ecmascript, поскольку нет семантических разниц между двумя обозначениями (т. Е. Dot Entation AB и [«B»] нотация).
Концепция «атрибута свойства» в Ecmascript не семантически отделена от «ключа», индекса массива и методов. Здесь все чтение атрибутов и написание объектов должно следовать единому правилу: проверьте цепочку прототипа.
В следующем примере Ruby мы можем увидеть семантическую разницу:
Кода -копия выглядит следующим образом:
a = {}
A.Class # HASH
A.Length # 0
# Новая пара "Value"
a ['длина'] = 10;
# Семантически, атрибут или метод, к которому доступны с точкой, а не ключом
A.Length # 1
# Индексатор обращается к ключу в хэш
A ['длина'] # 10
# Это похоже на динамическое объявление хэш -класса на существующем объекте
# Затем объявите новый атрибут или метод
класс хеш
def Z.
100
конец
конец
# Новые свойства доступны
AZ # 100
# Но не "ключ"
['z'] # nil
Стандарт ECMA-262-3 не определяет концепцию «хэша» (и аналогично). Однако, если есть такая структурная теория, объект, названный в честь этого.
Преобразование объекта
Чтобы преобразовать объект в примитивное значение, вы можете использовать метод значения. Как мы уже говорили, когда конструктор функции называется функцией (для некоторых типов), но если вы не используете новое ключевое слово, вы преобразуете объект в исходное значение, оно эквивалентно неявному значению метода вызова:
Кода -копия выглядит следующим образом:
var a = новое число (1);
var primitivea = number (a); // неявное «значение» вызовы
var alsoprimitivea = a.valueof (); // Явный вызов
тревога([
typeof a, // "объект"
тип примитива, // "номер"
Typeof alsoprimitivea // "номер"
]);
Этот метод позволяет объектам участвовать в различных операциях, таких как:
Кода -копия выглядит следующим образом:
var a = новое число (1);
var b = новый номер (2);
блюд (A + B); // 3
// даже
var c = {
x: 10,
y: 20,
valueof: function () {
вернуть это.x + this.y;
}
};
var d = {
x: 30,
Y: 40,
// та же функция, что и значение C
Значение: C.Valueof
};
предупреждение (C + D); // 100
Значение значения по умолчанию изменится в соответствии с типом объекта (если не переопределено). Для некоторых объектов это возвращает это - например: object.prototype.valueof (), а также рассчитанное значение: date.prototype.valueof () Возвращает дату и время:
Кода -копия выглядит следующим образом:
var a = {};
alert (a.valueof () === a); // true, "valueof" возвращает это
var d = new Date ();
оповещение (D.Valueof ()); // время
Alert (D.Valueof () === D.GetTime ()); // истинный
Кроме того, объект имеет более примитивное представление - строковое дисплей. Этот метод ToString надежен и используется автоматически в некоторых операциях:
Кода -копия выглядит следующим образом:
var a = {
valueof: function () {
возврат 100;
},
toString: function () {
вернуть '__test';
}
};
// В этой операции метод ToString автоматически вызывается
предупреждение (а); // "__тест"
// но здесь метод значения () вызывается
предупреждение (A + 10); // 110
// однако, после удаления значения
// toString можно снова вызвать автоматически
Удалить A.Valueof;
предупреждение (A + 10); // "_test10"
Метод ToString, определенный на Object.Prototype, имеет особое значение, и он возвращает внутреннее значение атрибута [[class]], которое мы обсудим ниже.
По сравнению с конвертированием в исходные значения (топримитивные), существует также спецификация преобразования (TOOBject) для преобразования значений в типы объектов.
Явным методом является использование встроенного конструктора объекта в качестве функции для вызова toobject (что-то вроде использования нового ключевого слова в порядке):
Кода -копия выглядит следующим образом:
var n = объект (1); // [номер объекта]
var s = object ('test'); // [Строка объекта]
// некоторые сходства также возможны с новым оператором
var B = новый объект (true); // [объект логический]]
// Если вы применяете новый объект параметра, вы создаете простой объект
var o = new Object (); // [объект объекта]
// Если параметр является существующим объектом
// Результат творения - просто вернуть объект
var a = [];
оповещение (a === Новый объект (A)); // истинный
оповещение (A === Object (a)); // истинный
Что касается вызова встроенных конструкторов, не существует общих правил для использования или не применения нового оператора, в зависимости от конструктора. Например, массив или функция использует конструктор нового оператора или простой функции, которая не использует новый оператор для получения того же результата:
Кода -копия выглядит следующим образом:
var a = массив (1, 2, 3); // [Массив объектов]
var b = новый массив (1, 2, 3); // [Массив объектов]
var c = [1, 2, 3]; // [Массив объектов]
var d = function (''); // [функция объекта]
var e = новая функция (''); // [функция объекта]
Когда некоторые операторы используются, есть также некоторые дисплеи и неявные преобразования:
Кода -копия выглядит следующим образом:
var a = 1;
var b = 2;
// Скрытый
var c = a + b; // 3, номер
var d = a + b + '5' // "35", строка
// явный
var e = '10'; // "10", строка
var f = +e; // 10, номер
var g = parseint (e, 10); // 10, номер
// и т. д
Свойства атрибутов
Все свойства могут иметь много атрибутов.
1. {readonly} - игнорировать операцию записи назначения значений атрибутам, но атрибуты только для чтения могут быть изменены поведение среды хоста, то есть они не являются «постоянными значениями»;
2. {dontenum}-атрибут не может быть перечислен в течение ... In Loop
3. {DontDelete}-поведение оператора удаления игнорируется (то есть его нельзя удалить);
4. {внутренний} - внутренний атрибут, без имени (используется только на уровне реализации), такие атрибуты не могут быть доступны в Ecmascript.
Обратите внимание, что в es5 {readonly}, {dontenum} и {DontDelete} переименованы в [[[записывается]], [[перечисляет]] и [[настраивается]], и эти свойства могут управляться вручную через Object.DefineProperty или аналогичные методы.
Кода -копия выглядит следующим образом:
var foo = {};
Object.defineproperty (foo, "x", {
Значение: 10,
Записывается: true, // т.е. {readonly} = false
перечисляется: false, // т.е. {dontenum} = true
настраивается: true // т.е.
});
console.log (foo.x); // 10
// Получить атрибуты через описание
var desc = object.getownpropertydescriptor (foo, "x");
console.log (desc. enumerable); // ЛОЖЬ
console.log (desc.writable); // истинный
// и т. д
Внутренние свойства и методы
Объекты также могут иметь внутренние свойства (часть уровня реализации), и программы Ecmascript не могут получить доступ напрямую (но ниже мы увидим, что некоторые реализации позволяют доступ к некоторым таким свойствам). Эти свойства доступны через вложенные скобки [[]]. Давайте посмотрим на некоторые из этих свойств. Описание этих свойств можно найти в спецификации.
Каждый объект должен реализовать следующие внутренние свойства и методы:
1. [[Прототип]] - прототип объекта (который будет подробно описан ниже)
2. [[[class]] - представление строкового объекта (например, массив объектов, объект функции, функция и т. Д.); используется для различения объектов
3. [[get]]-метод получения значений атрибутов
4. [[put]]-метод установки значений атрибутов
5. [[[canput]] - проверьте, является ли атрибут для записи
6. [[[hasproperty]]-проверьте, есть ли у объекта это свойство
7. [[Удалить]] - удалить это свойство из объекта
8. [[[defaultValue]] Возвращает исходное значение объекта для (вызов метода значения, и некоторые объекты могут выбросить исключение TypeError).
Значение внутреннего свойства [[класс]] может быть получено косвенно через метод object.prototype.tostring (), который должен вернуть следующую строку: "[объект" + [[class]] + "]". Например:
Кода -копия выглядит следующим образом:
var getClass = object.prototype.toString;
getClass.call ({}); // [объект объекта]
getClass.call ([]); // [Массив объектов]
getClass.call (новый номер (1)); // [номер объекта]
// и т. д
Эта функция обычно используется для проверки объектов, но в спецификации [[[класс]] объекта хоста может быть любым значением, включая значение атрибута [[класс]] встроенного объекта, поэтому теоретически оно не может быть на 100% точным. Например, свойство [[класс]] метода document.childnodes.item (...) возвращает «строку» в IE, но возвращаемая «функция» в других реализациях действительно.
Кода -копия выглядит следующим образом:
// в IE - "String", в другой - "функции"
Alert (getClass.call (document.childnodes.item));
Конструктор
Таким образом, как мы упоминали выше, объекты в Ecmascript создаются с помощью так называемых конструкторов.
Конструктор - это функция, которая создает и инициализирует недавно созданный объект.
Конструктор - это функция, которая создает и инициализирует вновь созданные объекты.
Создание объекта (распределение памяти) является обязанностью внутреннего метода конструктора [[Construct]]. Поведение этого внутреннего метода определяется, и все конструкторы используют этот метод для распределения памяти для новых объектов.
Инициализация управляется, вызывая функцию вверх и вниз по новым объектам, который отвечает внутренним методом конструктора [[Call]].
Обратите внимание, что код пользователя можно получить только на этапе инициализации, хотя мы можем вернуть различные объекты на этапе инициализации (игнорируя объекты TIHS, созданные на первом этапе):
Кода -копия выглядит следующим образом:
функция a () {
// Обновление вновь созданных объектов
this.x = 10;
// но возврат - это другой объект
вернуть [1, 2, 3];
}
var a = new a ();
console.log (ax, a); Неопределенный, [1, 2, 3]
Ссылаясь на функции главы 15 - раздел алгоритма для создания функций, мы видим, что функция является нативным объектом, включая свойства [[Construct]]] и [[call]]], а также прототип отображаемого прототипа - прототип будущего объекта (примечание: NativeBject является соглашением для нативного объекта, используемого в Pseudo -Code ниже).
Кода -копия выглядит следующим образом:
F = new NativeObject ();
F. [[Class]] = "функция"
.... // другие атрибуты
F. [[[Call]] = <ссылка на функцию> // сама функция
F.
.... // другие атрибуты
// Прототип объектов, созданный F -конструктором
__objectPrototype = {};
__ObjectPrototype.constructor = f // {dontenum}
F.Prototype = __ObjectPrototype
[[Call]]] является основным способом различения объектов, кроме атрибута [[класс]] (который эквивалентен «функции» здесь), поэтому внутренний атрибут [[call]] объекта называется функцией. Если такой объект использует оператор TypeOF, он возвращает «функцию». Однако это в основном связано с местными объектами. В некоторых случаях реализация использует тип для получения значения по -разному, например, эффект Window.alert (...) в т.е.
Кода -копия выглядит следующим образом:
// IE Browser - "Object", "Object", другие браузеры - "функция", "функция"
alert (object.prototype.tostring.call (window.alert));
Alert (typeof window.alert); // "Объект"
Внутренний метод [[Construct]] активируется с использованием конструктора с новым оператором, как мы уже говорили, отвечает за распределение памяти и создание объектов. Если параметров нет, скобки для вызова конструктора также могут быть опущены:
Кода -копия выглядит следующим образом:
Функция a (x) {// Конструктор A
this.x = x || 10;
}
// Если вы не проходите параметры, кронштейны также могут быть опущены
var a = new a; // или новый a ();
оповещение (AX); // 10
// Явно передаю параметр x
var b = new a (20);
предупреждение (BX); // 20
Мы также знаем, что SHIS в конструкторе (этап инициализации) устанавливается на недавно созданный объект.
Давайте изучим алгоритм создания объекта.
Алгоритм создания объекта
Поведение внутреннего метода [[конструкция]] может быть описано следующим образом:
Кода -копия выглядит следующим образом:
F.
O = new NativeObject ();
// свойство [[класс]] установлено на «Объект»
O. [[Class]] = "Объект"
// Получить объект g при ссылке на F.Prototype
var __objectPrototype = f.prototype;
// Если __ObjectPrototype является объектом, то:
O. [[Прототип]] = __ObjectPrototype
// В противном случае:
O. [[Прототип]] = Object.Prototype;
// ЗДЕСЬ О. [[Прототип]] - прототип объекта объекта
// f. [[[Call]] применяется, когда инициализируется недавно созданный объект
// Установить это как недавно созданный объект o
// параметры такие же, как и начальные параметры в F
R = f. это === o;
// здесь r - возвращаемое значение [[Call]]
// в JS, как это:
// r = f.apply (o, initialParameters);
// Если r - объект
возврат р
// В противном случае
возврат o
Обратите внимание на две основные функции:
1. Во -первых, прототип вновь созданного объекта получается из свойства прототипа функции в данный момент (это означает, что свойства прототипа двух созданных объектов, созданных одним и тем же конструктором, могут отличаться, потому что свойства прототипа функции также могут быть различными).
2. Во -вторых, как мы упоминали выше, если [[call]] возвращает объект, когда объект инициализируется, это именно результат, используемый для всего нового оператора:
Кода -копия выглядит следующим образом:
Функция a () {}
A.Prototype.x = 10;
var a = new a ();
оповещение (AX); // 10 Получите его из прототипа
// Установить свойство .Prototype на новый объект
// Зачем явно объявлено.
A.prototype = {
конструктор: a,
Y: 100
};
var b = new a ();
// объект "B" имеет новые свойства
предупреждение (BX); // неопределенный
предупреждение (by); // 100 получить из прототипа
// но прототип объекта А все еще может получить исходный результат
оповещение (AX); // 10 - получить из прототипа
функция b () {
this.x = 10;
вернуть новый массив ();
}
// Если конструктор «B» не возвращает (или возвращает это)
// затем можно использовать этот объект, но следующая ситуация возвращает массив
var b = new b ();
предупреждение (BX); // неопределенный
alert (object.prototype.tostring.call (b)); // [Массив объектов]
Давайте узнаем больше о прототипе
прототип
Каждый объект имеет прототип (кроме некоторых системных объектов). Прототип связи осуществляется с помощью внутреннего, неявного и косвенного доступа к [[прототип]] свойства прототипа. Прототип может быть объектом или нулевым значением.
Конструктор свойств
Приведенный выше пример имеет две важные знания. Первый рассказывает о свойстве прототипа свойства конструктора функции. В алгоритме создания функции мы знаем, что свойство конструктора устанавливается как свойство прототипа функции на этапе создания функции. Значение свойства конструктора является важной ссылкой на саму функцию:
Кода -копия выглядит следующим образом:
Функция a () {}
var a = new a ();
предупреждение (A.Constructor); // Функция a () {}, по делегированию
оповещение (A.Constructor === A); // истинный
Обычно в этом случае существует недоразумение: неправильно строить свойство как вновь созданный объект, но, как мы видим, это свойство принадлежит прототипу и обращается к объекту через наследство.
Унаследовав экземпляр атрибута конструктора, вы можете косвенно получить ссылку на объект прототипа:
Кода -копия выглядит следующим образом:
Функция a () {}
A.Prototype.x = новый номер (10);
var a = new a ();
оповещение (a.constructor.prototype); // [объект объекта]
оповещение (AX); // 10, по прототипу
// тот же эффект, что и. [[Прототип]]. X
Alert (a.constructor.prototype.x); // 10
Alert (a.constructor.prototype.x === ax); // истинный
Но обратите внимание, что конструктор и свойства прототипа функции могут быть пересмотрены после создания объекта. В этом случае объект теряет механизм, упомянутый выше. Если вы редактируете прототип прототипа элемента через атрибут прототипа функции (добавить новые объекты или изменить существующие объекты), вы увидите недавно добавленный атрибут в экземпляре.
Однако, если мы полностью изменим свойство прототипа функции (присваивая новый объект), ссылка на исходный конструктор теряется, поскольку объект, который мы создаем, не включает свойство конструктора:
Кода -копия выглядит следующим образом:
Функция a () {}
A.prototype = {
x: 10
};
var a = new a ();
оповещение (AX); // 10
оповещение (A.Constructor === A); // ЛОЖЬ!
Следовательно, прототип ссылок на функции необходимо восстановить вручную:
Кода -копия выглядит следующим образом:
Функция a () {}
A.prototype = {
конструктор: a,
x: 10
};
var a = new a ();
оповещение (AX); // 10
оповещение (A.Constructor === A); // истинный
Обратите внимание, что, хотя атрибут конструктора восстанавливается вручную, по сравнению с исходным отсутствующим прототипом, функция {dontenum} исчезает, то есть оператор для ... в цикле в A.Prototype больше не поддерживается, но в спецификации 5 -го издания [[[перечисляемая]]] функция обеспечивает способность контролировать подчиненное состояние с помощью [[невыполненного]] функции.
Кода -копия выглядит следующим образом:
var foo = {x: 10};
Object.defineproperty (foo, "y", {
Значение: 20,
Перечисление: false // aka {dontenum} = true
});
console.log (foo.x, foo.y); // 10, 20
для (var k in foo) {
console.log (k); // только "x"
}
var xdesc = object.getOwnPropertyDescriptor (foo, "x");
var ydesc = object.getOwnPropertyDescriptor (foo, "y");
console.log (
xdesc.Enumerable, // true
ydesc.Enumerable // false
);
Явный прототип и неявные [[прототип]] свойства
Как правило, неверно явно ссылаться на прототип объекта через свойство прототипа функции. Он относится к тому же объекту, [[прототип]] свойство объекта:
а. [[Прототип]] ---> Прототип <---- А.
Кроме того, значение [[прототип]] экземпляра действительно получено на свойство прототипа конструктора.
Однако отправка атрибута прототипа не повлияет на созданный прототип (он будет влиять только на атрибут прототипа изменений конструктора), то есть в недавно созданный объект имеет новый прототип, и созданный объект все еще будет ссылаться на исходный старый прототип (этот прототип больше не может быть изменен).
Кода -копия выглядит следующим образом:
// ситуация перед изменением прототипа A.Prototype
а. [[Прототип]] ---> Прототип <---- А.
// после модификации
A.Prototype ---> Новый прототип // Новый объект будет иметь этот прототип
а.
Например:
Кода -копия выглядит следующим образом:
Функция a () {}
A.Prototype.x = 10;
var a = new a ();
оповещение (AX); // 10
A.prototype = {
конструктор: a,
x: 20
Y: 30
};
// объект A является значением, полученным из прототипа сырой нефти посредством неявного [[прототипа]] ссылки
оповещение (AX); // 10
Блюдо (AY) // не определено
var b = new a ();
// но новый объект - это значение, полученное из нового прототипа
предупреждение (BX); // 20
предупреждение (от) // 30
Следовательно, для некоторых статей неправильно сказать, что «динамическая модификация прототипов повлияет на все объекты, которые имеют новые прототипы», а новый прототип вступает в силу только на недавно созданных объектах после изменения прототипа.
Основное правило здесь: прототип объекта создается при создании объекта, и после этого не может быть изменен в новый объект. Если на один и тот же объект все еще упоминается, на него можно ссылаться через явный прототип конструктора. После создания объекта свойства прототипа могут быть добавлены или изменены только.
Нестандартные атрибуты __proto__
Однако некоторые реализации (такие как Spidermonkey) предоставляют нестандартные __proto__ явные свойства для ссылки на прототип объекта:
Кода -копия выглядит следующим образом:
Функция a () {}
A.Prototype.x = 10;
var a = new a ();
оповещение (AX); // 10
var __newPrototype = {
конструктор: a,
x: 20,
Y: 30
};
// Ссылка на новый объект
A.Prototype = __newPrototype;
var b = new a ();
предупреждение (BX); // 20
предупреждение (by); // 30
// "a"对象使用的依然是旧的原型
alert(ax); // 10
alert(ay); // неопределенный
// 显式修改原型
a.__proto__ = __newPrototype;
// 现在"а"对象引用的是新对象
alert(ax); // 20
alert(ay); // 30
注意,ES5提供了Object.getPrototypeOf(O)方法,该方法直接返回对象的[[Prototype]]属性――实例的初始原型。 然而,和__proto__相比,它只是getter,它不允许set值。
Кода -копия выглядит следующим образом:
var foo = {};
Object.getPrototypeOf(foo) == Object.prototype; // истинный
对象独立于构造函数
因为实例的原型独立于构造函数和构造函数的prototype属性,构造函数完成了自己的主要工作(创建对象)以后可以删除。原型对象通过引用[[Prototype]]属性继续存在:
Кода -копия выглядит следующим образом:
function A() {}
A.prototype.x = 10;
var a = new A();
alert(ax); // 10
// 设置A为null - 显示引用构造函数
A = null;
// 但如果.constructor属性没有改变的话,
// 依然可以通过它创建对象
var b = new a.constructor();
alert(bx); // 10
// 隐式的引用也删除掉
delete a.constructor.prototype.constructor;
delete b.constructor.prototype.constructor;
// 通过A的构造函数再也不能创建对象了
// 但这2个对象依然有自己的原型
alert(ax); // 10
alert(bx); // 10
instanceof操作符的特性
我们是通过构造函数的prototype属性来显示引用原型的,这和instanceof操作符有关。该操作符是和原型链一起工作的,而不是构造函数,考虑到这一点,当检测对象的时候往往会有误解:
Кода -копия выглядит следующим образом:
if (foo instanceof Foo) {
...
}
这不是用来检测对象foo是否是用Foo构造函数创建的,所有instanceof运算符只需要一个对象属性――foo.[[Prototype]],在原型链中从Foo.prototype开始检查其是否存在。instanceof运算符是通过构造函数里的内部方法[[HasInstance]]来激活的。
让我们来看看这个例子:
Кода -копия выглядит следующим образом:
function A() {}
A.prototype.x = 10;
var a = new A();
alert(ax); // 10
alert(a instanceof A); // истинный
// 如果设置原型为null
A.prototype = null;
// ..."a"依然可以通过a.[[Prototype]]访问原型
alert(ax); // 10
// 不过,instanceof操作符不能再正常使用了
// 因为它是从构造函数的prototype属性来实现的
alert(a instanceof A); // 错误,A.prototype不是对象
另一方面,可以由构造函数来创建对象,但如果对象的[[Prototype]]属性和构造函数的prototype属性的值设置的是一样的话,instanceof检查的时候会返回true:
Кода -копия выглядит следующим образом:
function B() {}
var b = new B();
alert(b instanceof B); // истинный
function C() {}
var __proto = {
constructor: C
};
C.prototype = __proto;
b.__proto__ = __proto;
alert(b instanceof C); // истинный
alert(b instanceof B); // ЛОЖЬ
原型可以存放方法并共享属性
大部分程序里使用原型是用来存储对象的方法、默认状态和共享对象的属性。
事实上,对象可以拥有自己的状态,但方法通常是一样的。 因此,为了内存优化,方法通常是在原型里定义的。 这意味着,这个构造函数创建的所有实例都可以共享找个方法。
Кода -копия выглядит следующим образом:
function A(x) {
this.x = x || 100;
}
A.prototype = (function () {
// 初始化上下文
// 使用额外的对象
var _someSharedVar = 500;
function _someHelper() {
alert('internal helper: ' + _someSharedVar);
}
function method1() {
alert('method1: ' + this.x);
}
function method2() {
alert('method2: ' + this.x);
_someHelper();
}
// 原型自身
возвращаться {
constructor: A,
method1: method1,
method2: method2
};
}) ();
var a = new A(10);
var b = new A(20);
a.method1(); // method1: 10
a.method2(); // method2: 10, internal helper: 500
b.method1(); // method1: 20
b.method2(); // method2: 20, internal helper: 500
// 2个对象使用的是原型里相同的方法
alert(a.method1 === b.method1); // истинный
alert(a.method2 === b.method2); // истинный
读写属性
正如我们提到,读取和写入属性值是通过内部的[[Get]]和[[Put]]方法。这些内部方法是通过属性访问器激活的:点标记法或者索引标记法:
Кода -копия выглядит следующим образом:
// Писать
foo.bar = 10; // 调用了[[Put]]
console.log(foo.bar); // 10, 调用了[[Get]]
console.log(foo['bar']); // 效果一样
让我们用伪代码来看一下这些方法是如何工作的:
[[Get]]方法
[[Get]]也会从原型链中查询属性,所以通过对象也可以访问原型中的属性。
O.[[Get]](P):
Кода -копия выглядит следующим образом:
// 如果是自己的属性,就返回
if (O.hasOwnProperty(P)) {
return OP;
}
// 否则,继续分析原型
var __proto = O.[[Prototype]];
// 如果原型是null,返回undefined
// 这是可能的:最顶层Object.prototype.[[Prototype]]是null
if (__proto === null) {
return undefined;
}
// 否则,对原型链递归调用[[Get]],在各层的原型中查找属性
// 直到原型为null
return __proto.[[Get]](P)
请注意,因为[[Get]]在如下情况也会返回undefined:
Кода -копия выглядит следующим образом:
if (window.someObject) {
...
}
这里,在window里没有找到someObject属性,然后会在原型里找,原型的原型里找,以此类推,如果都找不到,按照定义就返回undefined。
注意:in操作符也可以负责查找属性(也会查找原型链):
Кода -копия выглядит следующим образом:
if ('someObject' in window) {
...
}
这有助于避免一些特殊问题:比如即便someObject存在,在someObject等于false的时候,第一轮检测就通不过。
[[Put]]方法
[[Put]]方法可以创建、更新对象自身的属性,并且掩盖原型里的同名属性。
O.[[Put]](P, V):
Кода -копия выглядит следующим образом:
// 如果不能给属性写值,就退出
if (!O.[[CanPut]](P)) {
возвращаться;
}
// 如果对象没有自身的属性,就创建它
// 所有的attributes特性都是false
if (!O.hasOwnProperty(P)) {
createNewProperty(O, P, attributes: {
ReadOnly: false,
DontEnum: false,
DontDelete: false,
Internal: false
});
}
// 如果属性存在就设置值,但不改变attributes特性
OP = V
возвращаться;
Например:
Кода -копия выглядит следующим образом:
Object.prototype.x = 100;
var foo = {};
console.log(foo.x); // 100, 继承属性
foo.x = 10; // [[Помещать]]
console.log(foo.x); // 10, 自身属性
delete foo.x;
console.log(foo.x); // 重新是100,继承属性
请注意,不能掩盖原型里的只读属性,赋值结果将忽略,这是由内部方法[[CanPut]]控制的。
// 例如,属性length是只读的,我们来掩盖一下length试试
function SuperString() {
/* ничего */
}
SuperString.prototype = new String("abc");
var foo = new SuperString();
console.log(foo.length); // 3, "abc"的长度
// 尝试掩盖
foo.length = 5;
console.log(foo.length); // 依然是3
但在ES5的严格模式下,如果掩盖只读属性的话,会保存TypeError错误。
属性访问器
内部方法[[Get]]和[[Put]]在ECMAScript里是通过点符号或者索引法来激活的,如果属性标示符是合法的名字的话,可以通过“.”来访问,而索引方运行动态定义名称。
Кода -копия выглядит следующим образом:
var a = {testProperty: 10};
alert(a.testProperty); // 10, 点
alert(a['testProperty']); // 10, 索引
var propertyName = 'Property';
alert(a['test' + propertyName]); // 10, 动态属性通过索引的方式
这里有一个非常重要的特性――属性访问器总是使用ToObject规范来对待“.”左边的值。这种隐式转化和这句“在JavaScript中一切都是对象”有关系,(然而,当我们已经知道了,JavaScript里不是所有的值都是对象)。
如果对原始值进行属性访问器取值,访问之前会先对原始值进行对象包装(包括原始值),然后通过包装的对象进行访问属性,属性访问以后,包装对象就会被删除。
Например:
Кода -копия выглядит следующим образом:
var a = 10; // 原始值
// 但是可以访问方法(就像对象一样)
alert(a.toString()); // "10"
// 此外,我们可以在a上创建一个心属性
a.test = 100; // 好像是没问题的
// 但,[[Get]]方法没有返回该属性的值,返回的却是undefined
alert(a.test); // неопределенный
那么,为什么整个例子里的原始值可以访问toString方法,而不能访问新创建的test属性呢?
答案很简单:
首先,正如我们所说,使用属性访问器以后,它已经不是原始值了,而是一个包装过的中间对象(整个例子是使用new Number(a)),而toString方法这时候是通过原型链查找到的:
Кода -копия выглядит следующим образом:
// 执行a.toString()的原理:
1. wrapper = new Number(a);
2. wrapper.toString(); // "10"
3. delete wrapper;
接下来,[[Put]]方法创建新属性时候,也是通过包装装的对象进行的:
Кода -копия выглядит следующим образом:
// 执行a.test = 100的原理:
1. wrapper = new Number(a);
2. wrapper.test = 100;
3. delete wrapper;
我们看到,在第3步的时候,包装的对象以及删除了,随着新创建的属性页被删除了――删除包装对象本身。
然后使用[[Get]]获取test值的时候,再一次创建了包装对象,但这时候包装的对象已经没有test属性了,所以返回的是undefined:
Кода -копия выглядит следующим образом:
// 执行a.test的原理:
1. wrapper = new Number(a);
2. wrapper.test; // неопределенный
这种方式解释了原始值的读取方式,另外,任何原始值如果经常用在访问属性的话,时间效率考虑,都是直接用一个对象替代它;与此相反,如果不经常访问,或者只是用于计算的话,到可以保留这种形式。
наследовать
我们知道,ECMAScript是使用基于原型的委托式继承。链和原型在原型链里已经提到过了。其实,所有委托的实现和原型链的查找分析都浓缩到[[Get]]方法了。
如果你完全理解[[Get]]方法,那JavaScript中的继承这个问题将不解自答了。
经常在论坛上谈论JavaScript中的继承时,我都是用一行代码来展示,事实上,我们不需要创建任何对象或函数,因为该语言已经是基于继承的了,代码如下:
Кода -копия выглядит следующим образом:
alert(1..toString()); // "1"
我们已经知道了[[Get]]方法和属性访问器的原理了,我们来看看都发生了什么:
1.首先,从原始值1,通过new Number(1)创建包装对象
2.然后toString方法是从这个包装对象上继承得到的
为什么是继承的? 因为在ECMAScript中的对象可以有自己的属性,包装对象在这种情况下没有toString方法。 因此它是从原理里继承的,即Number.prototype。
注意有个微妙的地方,在上面的例子中的两个点不是一个错误。第一点是代表小数部分,第二个才是一个属性访问器:
Кода -копия выглядит следующим образом:
1.toString(); // 语法错误!
(1).toString(); // ХОРОШО
1..toString(); // ХОРОШО
1['toString'](); // ХОРОШО
原型链
让我们展示如何为用户定义对象创建原型链,非常简单:
Кода -копия выглядит следующим образом:
function A() {
alert('A.[[Call]] activated');
this.x = 10;
}
A.prototype.y = 20;
var a = new A();
alert([ax, ay]); // 10 (自身), 20 (继承)
function B() {}
// 最近的原型链方式就是设置对象的原型为另外一个新对象
B.prototype = new A();
// 修复原型的constructor属性,否则的话是A了
B.prototype.constructor = B;
var b = new B();
alert([bx, by]); // 10, 20, 2个都是继承的
// [[Get]] bx:
// bx (no) -->
// b.[[Prototype]].x (yes) - 10
// [[Get]] by
// by (no) -->
// b.[[Prototype]].y (no) -->
// b.[[Prototype]].[[Prototype]].y (yes) - 20
// where b.[[Prototype]] === B.prototype,
// and b.[[Prototype]].[[Prototype]] === A.prototype
这种方法有两个特性:
首先,B.prototype将包含x属性。乍一看这可能不对,你可能会想x属性是在A里定义的并且B构造函数也是这样期望的。尽管原型继承正常情况是没问题的,但B构造函数有时候可能不需要x属性,与基于class的继承相比,所有的属性都复制到后代子类里了。
尽管如此,如果有需要(模拟基于类的继承)将x属性赋给B构造函数创建的对象上,有一些方法,我们后来来展示其中一种方式。
其次,这不是一个特征而是缺点――子类原型创建的时候,构造函数的代码也执行了,我们可以看到消息"A.[[Call]] activated"显示了两次――当用A构造函数创建对象赋给B.prototype属性的时候,另外一场是a对象创建自身的时候!
下面的例子比较关键,在父类的构造函数抛出的异常:可能实际对象创建的时候需要检查吧,但很明显,同样的case,也就是就是使用这些父对象作为原型的时候就会出错。
Кода -копия выглядит следующим образом:
function A(param) {
if (!param) {
throw 'Param required';
}
this.param = param;
}
A.prototype.x = 10;
var a = new A(20);
alert([ax, a.param]); // 10, 20
function B() {}
B.prototype = new A(); // Ошибка
此外,在父类的构造函数有太多代码的话也是一种缺点。
解决这些“功能”和问题,程序员使用原型链的标准模式(下面展示),主要目的就是在中间包装构造函数的创建,这些包装构造函数的链里包含需要的原型。
Кода -копия выглядит следующим образом:
function A() {
alert('A.[[Call]] activated');
this.x = 10;
}
A.prototype.y = 20;
var a = new A();
alert([ax, ay]); // 10 (自身), 20 (集成)
function B() {
// 或者使用A.apply(this, arguments)
B.superproto.constructor.apply(this, arguments);
}
// 继承:通过空的中间构造函数将原型连在一起
var F = function () {};
F.prototype = A.prototype; // Цитировать
B.prototype = new F();
B.superproto = A.prototype; // 显示引用到另外一个原型上, "sugar"
// 修复原型的constructor属性,否则的就是A了
B.prototype.constructor = B;
var b = new B();
alert([bx, by]); // 10 (自身), 20 (集成)
注意,我们在b实例上创建了自己的x属性,通过B.superproto.constructor调用父构造函数来引用新创建对象的上下文。
我们也修复了父构造函数在创建子原型的时候不需要的调用,此时,消息"A.[[Call]] activated"在需要的时候才会显示。
为了在原型链里重复相同的行为(中间构造函数创建,设置superproto,恢复原始构造函数),下面的模板可以封装成一个非常方面的工具函数,其目的是连接原型的时候不是根据构造函数的实际名称。
Кода -копия выглядит следующим образом:
function inherit(child, parent) {
var F = function () {};
F.prototype = parent.prototype
child.prototype = new F();
child.prototype.constructor = child;
child.superproto = parent.prototype;
Возвращение ребенка;
}
因此,继承:
Кода -копия выглядит следующим образом:
function A() {}
A.prototype.x = 10;
function B() {}
inherit(B, A); // 连接原型
var b = new B();
alert(bx); // 10, 在A.prototype查找到
也有很多语法形式(包装而成),但所有的语法行都是为了减少上述代码里的行为。
例如,如果我们把中间的构造函数放到外面,就可以优化前面的代码(因此,只有一个函数被创建),然后重用它:
Кода -копия выглядит следующим образом:
var inherit = (function(){
function F() {}
return function (child, parent) {
F.prototype = parent.prototype;
child.prototype = new F;
child.prototype.constructor = child;
child.superproto = parent.prototype;
Возвращение ребенка;
};
}) ();
由于对象的真实原型是[[Prototype]]属性,这意味着F.prototype可以很容易修改和重用,因为通过new F创建的child.prototype可以从child.prototype的当前值里获取[[Prototype]]:
Кода -копия выглядит следующим образом:
function A() {}
A.prototype.x = 10;
function B() {}
inherit(B, A);
B.prototype.y = 20;
B.prototype.foo = function () {
alert("B#foo");
};
var b = new B();
alert(bx); // 10, 在A.prototype里查到
function C() {}
inherit(C, B);
// 使用"superproto"语法糖
// 调用父原型的同名方法
C.ptototype.foo = function () {
C.superproto.foo.call(this);
alert("C#foo");
};
var c = new C();
alert([cx, cy]); // 10, 20
c.foo(); // B#foo, C#foo
注意,ES5为原型链标准化了这个工具函数,那就是Object.create方法。ES3可以使用以下方式实现:
Кода -копия выглядит следующим образом:
Object.create ||
Object.create = function (parent, properties) {
function F() {}
F.prototype = parent;
var child = new F;
for (var k in properties) {
child[k] = properties[k].value;
}
Возвращение ребенка;
}
// 用法
var foo = {x: 10};
var bar = Object.create(foo, {y: {value: 20}});
console.log(bar.x, bar.y); // 10, 20
此外,所有模仿现在基于类的经典继承方式都是根据这个原则实现的,现在可以看到,它实际上不是基于类的继承,而是连接原型的一个很方便的代码重用。
в заключение
本章内容已经很充分和详细了,希望这些资料对你有用,并且消除你对ECMAScript的疑问,如果你有任何问题,请留言,我们一起讨论。