Как и зачем мне писать класс, расширяющий null?

синтаксис классов JavaScript, добавленный в ES6, по-видимому, делает допустимым расширить null:

class foo extends null {}

Некоторые гуглы показывают, что на ES Discuss было предложено считать такие заявления ошибкой; однако другие комментаторы утверждали, что их следует оставить законными на том основании, что

кто-то может захотеть создать класс с прототипом {__proto__: null}

и эта сторона спора в конечном итоге возобладала.

Я не могу понять этот гипотетический вариант использования. Во-первых, несмотря на то, что объявление такого класса допустимо, кажется, что создание экземпляра класса, объявленного таким образом, недопустимо. Попытка создать экземпляр класса foo сверху в Node.js или Chrome дает мне удивительно глупую ошибку

TypeError: function is not a function

делая то же самое в Firefox, я получаю

TypeError: function () {
} is not a constructor

Это не помогает определить конструктор класса, как показано в текущем примере функции MDN; если я попытаюсь создать экземпляр этого класса:

class bar extends null {
  constructor(){}
}

затем Chrome/Node скажите мне:

ReferenceError: this is not defined

и Firefox говорит мне:

ReferenceError: |this| used uninitialized in bar class constructor

Что это за безумие? Почему эти классы с нулевым расширением не могут быть созданы? И учитывая, что они не создаются, почему возможность их создания намеренно оставлена ​​в спецификации, и почему какой-то автор MDN считает, что это достаточно примечательный, чтобы задокументировать? Какой возможный вариант использования этой функции?


person Mark Amery    schedule 16.12.2016    source источник
comment
Пусть constructor вернет Object.create(null) в качестве замены. Очевидно, это позволяет избежать автоматического вызова super. jsfiddle.net/w7y2mbcd Но тогда я думаю, что на самом деле это не член этого класса.   -  person    schedule 16.12.2016
comment
Может быть, вернуть Object.create(bar.prototype). jsfiddle.net/w7y2mbcd/1   -  person    schedule 16.12.2016
comment
@squint nice - ваш второй комментарий предоставляет способ фактического создания экземпляра класса с нулевым расширением, что приводит к объекту, у которого нет ни одного из методов из Object.prototype, таких как .toString() или .isPrototypeOf(). Это больше, чем я понял, хотя я все еще не совсем уверен, зачем вам нужен такой объект.   -  person Mark Amery    schedule 16.12.2016
comment
Я думаю, что иногда людям просто нужен чистый объект. Это было более важно до того, как у нас появился Map, поэтому они использовали Object.create(null) в качестве универсальной карты, которая может содержать произвольные свойства, неизвестные заранее, чтобы можно было гарантировать, что поиск любого свойства происходит в результате взаимодействия с объектом, а не наследование по умолчанию. Сейчас это кажется менее важным, но опять же, вероятно, здесь и там есть несколько вариантов использования. Хотя не знаю, что они из себя представляют.   -  person    schedule 16.12.2016
comment
ИМО, было бы лучше не прыгать через обручи; следует просто избегать вызова super, если нет родительского конструктора. Но тогда я действительно не думал об этом.   -  person    schedule 16.12.2016
comment
@squint, вы пару раз ссылались на неявный вызов super в этой ветке комментариев, но не объясняли его подробно (возможно, предполагая, что ваша аудитория знает больше о классах ES6, чем я лично). Является ли этот неявный вызов причиной ошибок при попытке создать экземпляр bar из моего вопроса? При каких обстоятельствах происходит этот неявный вызов и почему?   -  person Mark Amery    schedule 16.12.2016
comment
Я мало знаком с этим синтаксисом, но я понимаю, что когда вы используете синтаксис class, родительский конструктор автоматически вызывается для объекта или что, если вы пытаетесь сослаться на this, вы должны сначала вручную вызвать super()... но я могу некоторые детали перепутаны, поэтому вам лучше поискать другие ресурсы для лучшего объяснения. Но да, я думаю, что это, вероятно, причина ошибок, хотя я могу сильно ошибаться в этом.   -  person    schedule 16.12.2016
comment
FWIW, spec говорит, что extends может быть за которым следует любое LeftHandSideExpression, что в основном означает все, что можно поставить слева от =. Это включает в себя такие вещи, как foo() и null. Теперь ни null = 42;, ни foo() = 42 не имеют смысла, но они допустимы и вызовут ошибку времени выполнения. OTOH, class Foo extends bar() {} имеет смысл. edit: Э-э, foo() = 42 и null = 42 кажутся ранними ошибками, которые почти как синтаксические ошибки :-/   -  person Felix Kling    schedule 16.12.2016
comment
У меня та же теория, что и у Феликса, это разрешено не потому, что это полезно, а потому, что это делает язык однородным и простым. Под простым я подразумеваю, что документы читают любое выражение LeftHandSideExpression вместо любого выражения LeftHandSideExpression, кроме null или т. д.   -  person givanse    schedule 17.12.2016


Ответы (2)


EDIT (2021): TC39, в котором указано, что JavaScript до сих пор не решил, как именно это должно работать. Это должно произойти, прежде чем браузеры смогут последовательно его реализовать. Вы можете следить за последними усилиями здесь.


Исходный ответ:

Создание таких классов должно работать; В Chrome и Firefox просто есть ошибки. Вот Chrome, вот Firefox. Он отлично работает в Safari (по крайней мере, на мастере).

Раньше в спецификации была ошибка, которая делала невозможным их создание, но сейчас было исправлено на некоторое время. (Есть все еще связанный, но это не то, что вы видите.)

Вариант использования примерно такой же, как у Object.create(null). Иногда вам нужно что-то, что не наследуется от Object.prototype.

person Bakkot    schedule 17.12.2016
comment
Допустим, я хочу подключить все прототипы объектов, НО тот, который я создаю с помощью extend null или Object.create(null)? - person Stefan Rein; 19.12.2016
comment
Похоже, что спецификация, касающаяся extends null, изменена, и Из-за этого Chrome не исправляет эту ошибку. - person Decade Moon; 19.02.2017

Чтобы ответить на вторую часть:

Я не могу понять этот гипотетический вариант использования.

Таким образом, ваш объект не будет иметь Object.prototype в своей цепочке прототипов.

class Hash extends null {}
var o = {};
var hash = new Hash;
o["foo"] = 5;
hash["foo"] = 5;
// both are used as hash maps (or use `Map`).
hash["toString"]; // undefined
o["toString"]; // function

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

Это общий шаблон для Object.create(null), и он распространен во многих базах кода, включая такие крупные, как NodeJS.

person Benjamin Gruenbaum    schedule 20.12.2016