Наши проекты:
Журнал · Discuz!ML · Wiki · DRKB · Помощь проекту |
||
ПРАВИЛА | FAQ | Помощь | Поиск | Участники | Календарь | Избранное | RSS |
[3.144.253.161] |
|
Сообщ.
#1
,
|
|
|
Написал свою версию имитации класса , но работает странно, если не выполнять вызов _super в конструкторе JavaDrocher, то вызов mark.getName() возвращает "Gregory"! Откуда тут Gregory? Как оно сюда попало? Должно undefined!
Где я накосячил? Object.extends = function _extends(props) { var _super = this.prototype; var proto = new this(); for (const key in props) { if (props.hasOwnProperty(key)) { proto[key] = function (...args) { this._super = _super[key]; return props[key](...args); }; } } function Class(...args) { this.__constructor && this.__constructor(...args); } Class.prototype = proto; Class.extends = _extends; return Class; }; var Person = Object.extends({ __constructor(name) { this.name = name; }, getName() { return this.name; } }); var JavaDrocher = Person.extends({ __constructor(name, grade) { // this._super(name); //<-- отключаем вызов _super this.grade = grade; }, getGrade() { return this.grade; } }); // tests var gregory = new Person('Gregory'); var mark = new JavaDrocher('Mark', 10); console.log(gregory.getName()); // Gregory console.log(mark.getName()); // Gregory <-- должно undefined! console.log(mark.getGrade()); // 10 |
Сообщ.
#2
,
|
|
|
Нашел баг, все дело было в контексте я вызывал методы внутри extends в не правильном контексте. Заменил простой вызов на вызов через call(this, ...args) теперь все пучком
Object.extends = function _extends(props) { var _super = this.prototype; var proto = new this(); for (const key in props) { if (props.hasOwnProperty(key)) { proto[key] = function (...args) { this._super = _super[key]; // return props[key](...args); //<-- ошибка: вызов в неправильном контексте return props[key].call(this, ...args); //<-- вызов в нужном контексте }; } } function Class(...args) { this.__constructor && this.__constructor(...args); } Class.prototype = proto; Class.extends = _extends; return Class; }; |
Сообщ.
#3
,
|
|
|
- Маста, а можно без call?
Да! Для этого надо просто скопировать функции в правильный контекст Object.extends = function _extends(props) { var _super = this.prototype; var proto = new this(); for (const key in props) { if (props.hasOwnProperty(key)) { proto[key] = function (...args) { this._super = _super[key]; this[key] = props[key]; //<-- копируем функции в контекст return this[key](...args); //<-- вызываем }; } } function Class(...args) { this.__constructor && this.__constructor(...args); } Class.prototype = proto; Class.extends = _extends; return Class; }; |
Сообщ.
#4
,
|
|
|
Обнаружен косяк , конструктор класса в случае наследования вызывается два раза!
Первый раз когда собственно определяется класс JavaDrocher var JavaDrocher = Person.extends({ __constructor(name, grade) { this._super(name); this.grade = grade; }, getGrade() { return this.grade; } }); Ну а второй раз как полагается при создании объекта mark тут вопросов нет. Исследуя код обнаружил место где это происходит. Вот этот кусок Object.extends = function _extends(props) { var _super = this.prototype; var proto = new this(); //<-- тут! ... Решение такое надо определять момент определения класса и момент создания объекта. Например можно определить флажок isCreatingObject и окружить им оператор создания объекта proto: (function () { var isCreatingObject; Object.extends = function _extends(props) { var _super = this.prototype; isCreatingObject = false; //<-- раз var proto = new this(); isCreatingObject = true; //<-- и два Object.keys(props).forEach(key => { proto[key] = function (...args) { this._super = _super[key]; return props[key].call(this, ...args); }; }); function Class(...args) { isCreatingObject && this.__constructor && this.__constructor(...args); //<-- и три } Class.prototype = proto; Class.extends = _extends; return Class; }; })(); поскольку данный флажок проверяется также после определения класса в момент создания объекта, его необходимо вынести за пределы extends, но поскольку мы не хотим его делать глобальной, юзаем iife. Все теперь __constructor вызывается один раз при создании объекта var mark = new JavaDrocher('Mark', 10); |
Сообщ.
#5
,
|
|
|
Есть еще одна оптимизация кода смотрите на след кусок кода
... Object.keys(props).forEach(key => { proto[key] = function (...args) { this._super = _super[key]; //<-- создается ссылка на метод из прототипа return props[key].call(this, ...args); }; }); ... тут при обходе каждый раз создается новая функция, которая подменяет оригинальную. Казалось бы ничего страшного, но если посмотреть на содержимое этой функции, то вы увидите, что единственое что она делает это выставляет ссылку на метод из прототипа. Спрашивается а зачем нам нужна ссылка на метод из прототипа, если мы ее не используем. Так в моем примере эта ссылка юзается только в методе __constructor var JavaDrocher = Person.extends({ __constructor(name, grade) { this._super(name); //<-- вот она this.grade = grade; }, getGrade() { // а тут _super не юзается return this.grade; } }); Делаем след оптимизацию, она не такая очевидная: ... Object.keys(props).forEach(key => { proto[key] = /_super/.test(props[key]) ? function (...args) { this._super = _super[key]; return props[key].call(this, ...args); } : props[key]; }); ... Данный код требует комментария Строка /_super/.test(props[key]) тестирует тело метода на наличие в нем слова _super. Как это работает? Дело в том вызов test(props[key]) приводит к test(props[key].toString()), который возвращает содержимое метода в виде строки и таким образом мы имеем возможность проверить наличие обращения к _super. Если так то строим новый метод, если нет то просто ссылаемся на оригинальный метод. Тут еще остались косяки, но пока этого достаточно для общего развития Добавлено - Маста, а почему вы юзаете переменную _super: ... Object.extends = function _extends(props) { var _super = this.prototype; //<-- тут isCreatingObject = false; var proto = new this(); isCreatingObject = true; Object.keys(props).forEach(key => { proto[key] = /_super/.test(props[key]) ? function (...args) { this._super = _super[key]; //<-- тут return props[key].call(this, ...args); } : props[key]; }); ... Новичок, это не просто переменная это замыкание, поскольку к ней мы обращаемся из другой функции. Это необходимо чтобы коректно определить контекст вызова метода из прототипа. - Как это? Смотри если ты напишешь просто this._super = this.prototype[key], то this.prototype[key] в момент вызова метода не будет указывать на метод прототипа, поскольку this будет ссылаться на созданный объект, а не функцию-конструктор. - Маста как же сразу не просек Спокойно Новичок! Голова нам еще пригодиться! |
Сообщ.
#6
,
|
|
|
а где:
class A extends B { const A = 'aaa'; private a; protected b; public c; } ? |
Сообщ.
#7
,
|
|
|
Цитата K313 @ а где: class A extends B { const A = 'aaa'; private a; protected b; public c; } ? Цели написать полный аналог классов ESNext не было, цель была поупражняться с JS ну еще раз вспомнить прототипы, замыкания. К тому же лично я не юзаю эти дела, не прижилось в моей практике. |
Сообщ.
#8
,
|
|
|
- Маста, а почему в вашем коде юзается то стрелочный синтаксис то обычный?
- с вашего позволения я убрал улучшения, чтобы код стал компактнее Object.extends = function _extends(props) { var _super = this.prototype; var proto = new this(); Object.keys(props).forEach(key => { //<-- стрелочная proto[key] = function (...args) { //<-- обычная this._super = _super[key]; return props[key].call(this, ...args); }; }); function Class(...args) { this.__constructor && this.__constructor(...args); } Class.prototype = proto; Class.extends = _extends; return Class; }; var Person = Object.extends({ __constructor(name) { this.name = name; }, getName() { return this.name; } }); var gregory = new Person('Gregory'); console.log(gregory.getName()); Хороший вопрос Новичок Суть в контексте. Как ты наверное знаешь доступ к контексту выполняется через this. Функция extends имеет несколько зон обращения к контексту (я обозначил их комментариями): Object.extends = function _extends(props) { var _super = this.prototype; var proto = new this(); //<-- 1 Object.keys(props).forEach(key => { proto[key] = function (...args) { this._super = _super[key]; //<-- 2 return props[key].call(this, ...args); }; }); function Class(...args) { this.__constructor && this.__constructor(...args); //<-- 3 } Class.prototype = proto; Class.extends = _extends; return Class; }; var Person = Object.extends({ __constructor(name) { this.name = name; //<-- 4 }, getName() { return this.name; } }); Так вот в первом случае контекстом будет Object, во втором - proto, в третьем - зависит от того как будет вызван Class, простой вызов Class() определит в качестве контекста объект Window или global, если же через new Class(), то это будет новый объект. В четвертом - также зависит от того как будут вызваны методы анонимного объекта, мы уже говорили об этом в предыдущих постах, если ты забыл я напомню. Посмотри на код сразу после комментария 2: props[key].call(this, ...args) тут как раз идет вызов методов из нашего анонимного объекта, как видишь я вызываю их через call с передачей контекста this, который как я уже написал является объектом proto. - Понятно Маста! Но как это относится к стрелочной или обычной нотации функции? Новичок дело в том что контекст в функции определяемой через function, если она не принадлежит объекту всегда является глобальный объект Window или global. Если же функция объявлена через стрелочную нотацию контекст внутри нее не меняется и является тем же что и в зоне ее определения, или (как возможно ты слышал) ее называют лексической областью видимости. Т.е. в нашем случае это будет функция Object. На самом деле в данном случае это не имеет значения, т.к. внутри нашей стрелочной функции мы не юзаем конткест, поэтому ее можно заменить на обычную нотацию. А вот если заменить на стрелочную функцию, функцию которая присваивается объекту proto (см коммент 2), то контекст будет тем же что и в зоне ее определения, т.е. функция Object, что как ты понимаешь приведет к некорреткной работе кода вызова методов из анонимного объекта. Вот поэтому я юзал обычную нотацию в случае функции присваиваемой proto, а в случае колбэка forEach по привычке юзал стрелочную нотацию. - ВАУ МАСТА! На это мне потребуется время, чтобы я все перевалил - СПАСИБО! Я перечитаю пост несколько раз |
Сообщ.
#9
,
|
|
|
- Маста, я тут снова перечитал ваши посты и не понел откуда таки в примере поста #1 появляется Gregory?
Новичок ты меня таки поймал Давай исследуем откуда этот Gregory взялся. В том примере у нас идет привязка к анонимному объекту, что передается в качестве параметра в extends, таким образом он и будет нашим контекстом при вызове __constructor: var Person = Object.extends({ __constructor(name) { this.name = name; }, getName() { return this.name; } }); var gregory = new Person('Gregory'); //<-- вызов __constructor в контексте анонимного объекта это приведет к появлению свойства name со значением "Gregory" в этом объекте, я визуализирую состояние объекта: { name: "Gregory", //<-- новое свойство name __constructor(name) { this.name = name; }, getName() { return this.name; } } Далее идет вызов Person.extends, который приводит к тому что JavaDrocher "наследует" свойства Person, который как ты помнишь из-за неправильного контекста ссылается на наш анонимный объект, вместо Person.prototype: var JavaDrocher = Person.extends({ __constructor(name, grade) { this.grade = grade; }, getGrade() { return this.grade; } }); таким образом вызов mark.getName var mark = new JavaDrocher('Mark', 10); mark.getName(); приведет к обращению getName из нашего анонимного объекта, в котором свойство name уже определен со значением "Gregory", оно и будет возвращено. Вроде все верно отследил - Маста дайте мне время я это переварю Ок Новичок! |
Сообщ.
#10
,
|
|
|
Решил проблему двух вызовов конструкторов через копирование прототипа, вместо введения переменной isCreatingObject, также заюзал Object.defineProperty для восстановления связи прототипа с конструктором и заключил все в условие, согласно форме написания полифила :
if (!Object.extends) { Object.extends = function _extends(props) { var _super = this.prototype; var proto = Object.create(this.prototype); //<-- копируем this.prototype Object.keys(props).forEach(key => { proto[key] = /_super/.test(props[key]) ? function (...args) { this._super = _super[key]; return props[key].call(this, ...args); } : props[key]; }); function Class(...args) { this.__constructor && this.__constructor(...args); } Class.prototype = proto; Object.defineProperty(Class.prototype, 'constructor', { enumerable: false, value: Class }); Class.extends = _extends; return Class; }; } |
Сообщ.
#11
,
|
|
|
Цитата Cfon @ - Понятно Маста! Но как это относится к стрелочной или обычной нотации функции? Новичок дело в том что контекст в функции определяемой через function, если она не принадлежит объекту всегда является глобальный объект Window или global. Если же функция объявлена через стрелочную нотацию контекст внутри нее не меняется и является тем же что и в зоне ее определения, или (как возможно ты слышал) ее называют лексической областью видимости. Т.е. в нашем случае это будет функция Object. На самом деле в данном случае это не имеет значения, т.к. внутри нашей стрелочной функции мы не юзаем конткест, поэтому ее можно заменить на обычную нотацию. А вот если заменить на стрелочную функцию, функцию которая присваивается объекту proto (см коммент 2), то контекст будет тем же что и в зоне ее определения, т.е. функция Object, что как ты понимаешь приведет к некорреткной работе кода вызова методов из анонимного объекта. Вот поэтому я юзал обычную нотацию в случае функции присваиваемой proto, а в случае колбэка forEach по привычке юзал стрелочную нотацию. Другое более понятное объяснение заключается в том, что каждое объявление функции неявно включает ссылку на контект this, значение которого зависит от того как была вызвана функция. В случае же со стрелкой this не определяется и как следствие юзается this внешней функции, если конечно она не является также стрелкой. |
Сообщ.
#12
,
|
|
|
Цитата Cfon @ также заюзал Object.defineProperty для восстановления связи прототипа с конструктором Можно сразу определить constructor в proto, Object.defineProperty уже не требуется: ... Object.extends = function _extends(props) { var _super = this.prototype; var proto = Object.create(this.prototype, { constructor: { //<-- определяем constructor в proto value: Class } }); ... |