Классы
Классы в JavaScript были введены в ECMAScript 2015 и представляют собой синтаксический сахар над существующим в JavaScript механизмом прототипного наследования. Синтаксис классов не вводит новую объектно-ориентированную модель, а предоставляет более простой и понятный способ создания объектов и организации наследования.
Определение классов
На самом деле классы — это «специальные функции», поэтому точно также, как вы определяете функции (function expressions и function declarations), вы можете определять и классы с помощью: class declarations и class expressions.
Объявление класса
Первый способ определения класса — class declaration (объявление класса). Для этого необходимо воспользоваться ключевым словом class и указать имя класса (в примере — «Rectangle»).
class Rectangle constructor(height, width) this.height = height; this.width = width; > >
Подъём (hoisting)
Разница между объявлением функции (function declaration) и объявлением класса (class declaration) в том, что объявление функции совершает подъём (hoisting), в то время как объявление класса — нет. Поэтому вначале необходимо объявить ваш класс и только затем работать с ним, а код же вроде следующего сгенерирует исключение типа ReferenceError :
var p = new Rectangle(); // ReferenceError class Rectangle >
Выражение класса
Второй способ определения класса — class expression (выражение класса). Можно создавать именованные и безымянные выражения. В первом случае имя выражения класса находится в локальной области видимости класса и может быть получено через свойства самого класса, а не его экземпляра.
// безымянный var Rectangle = class constructor(height, width) this.height = height; this.width = width; > >; console.log(Rectangle.name); // отобразится: "Rectangle" // именованный var Rectangle = class Rectangle2 constructor(height, width) this.height = height; this.width = width; > >; console.log(Rectangle.name); // отобразится: "Rectangle2"
Примечание: выражения класса подвержены тем же проблемам с подъёмом (hoisting), что и объявления класса.
Тело класса и задание методов
Тело класса — это часть кода, заключённая в фигурные скобки <> . Здесь вы можете объявлять члены класса, такие как методы и конструктор.
Строгий режим
Тела объявлений классов и выражений классов выполняются в строгом режиме (strict mode).
Constructor
Метод constructor — специальный метод, необходимый для создания и инициализации объектов, созданных, с помощью класса. В классе может быть только один метод с именем constructor . Исключение типа SyntaxError будет выброшено, если класс содержит более одного вхождения метода constructor .
Ключевое слово super можно использовать в методе constructor для вызова конструктора родительского класса.
Методы прототипа
class Rectangle constructor(height, width) this.height = height; this.width = width; > get area() return this.calcArea(); > calcArea() return this.height * this.width; > > const square = new Rectangle(10, 10); console.log(square.area); // 100
Статические методы и свойства
Ключевое слово static , определяет статический метод или свойства для класса. Статические методы и свойства вызываются без инстанцирования (en-US) их класса, и не могут быть вызваны у экземпляров (instance) класса. Статические методы, часто используются для создания служебных функций для приложения, в то время как статические свойства полезны для кеширования в рамках класса, фиксированной конфигурации или любых других целей, не связанных с реплецированием данных между экземплярами.
class Point constructor(x, y) this.x = x; this.y = y; > static displayName = "Точка"; static distance(a, b) const dx = a.x - b.x; const dy = a.y - b.y; return Math.hypot(dx, dy); > > const p1 = new Point(5, 5); const p2 = new Point(10, 10); p1.displayName; //undefined p1.distance; //undefined p2.displayName; //undefined p2.distance; //undefined console.log(Point.displayName); // "Точка" console.log(Point.distance(p1, p2)); // 7.0710678118654755
Привязка this в прототипных и статических методах
Когда статический или прототипный метод вызывается без привязки к this объекта (или когда this является типом boolean, string, number, undefined, null), тогда this будет иметь значение undefined внутри вызываемой функции. Поведение будет таким же даже без директивы «use strict» , потому что код внутри тела класса всегда выполняется в строгом режиме.
class Animal speak() return this; > static eat() return this; > > let obj = new Animal(); obj.speak(); // объект Animal let speak = obj.speak; speak(); // undefined Animal.eat(); // класс Animal let eat = Animal.eat; eat(); // undefined
Если мы напишем этот же код используя классы основанные на функциях, тогда произойдёт автоупаковка основанная на значении this, в течение которого функция была вызвана. В строгом режиме автоупаковка не произойдёт — значение this останется прежним.
function Animal() > Animal.prototype.speak = function () return this; >; Animal.eat = function () return this; >; let obj = new Animal(); let speak = obj.speak; speak(); // глобальный объект (нестрогий режим) let eat = Animal.eat; eat(); // глобальный объект (нестрогий режим)
Свойства экземпляра
Свойства экземпляра должны быть определены в методе класса:
class Rectangle < constructor(height, width) < this.height = height; this.width = width; >>
Статические (class-side) свойства и свойства прототипа должны быть определены за рамками тела класса:
Rectangle.staticWidth = 20; Rectangle.prototype.prototypeWidth = 25;
Определение полей
Предупреждение: Публичные и приватные поля — это экспериментальная особенность (stage 3), предложенная комитетом TC39 по стандартам языка Javascript. Поддержка браузерами ограничена, но это нововведение может быть использовано на моменте сборки, используя к примеру Babel.
Публичные поля
Используя Javascript синтаксис определения полей, приведённый выше пример может быть изменён следующим образом:
class Rectangle < height = 0; width; constructor(height, width) < this.height = height; this.width = width; >>
Как видно из примера, поля могут быть объявлены как со начальным значением, так и без него.
Более подробно об этом написано в публичные поля класса.
Приватные поля
Предыдущий пример может быть изменён следующим образом, используя приватные поля:
class Rectangle < #height = 0; #width; constructor(height, width) < this.#height = height; this.#width = width; >>
Приватные поля могут быть изменены или прочитаны только в рамках класса и не могут быть вызваны извне. Определяя вещи, которые не видны за пределами класса, вы гарантируете, что пользователи ваших классов не могут зависеть от внутренних компонентов, которые могут изменить версию на версию.
Примечание: Приватные поля могут быть объявлены только заранее в объявлении поля.
Приватные поля не могут быть созданы позже путём присваивания им значения, в отличии от обычных свойств.
Более подробно об этом написано в Приватные поля класса.
Наследование классов с помощью extends
Ключевое слово extends используется в объявлениях классов и выражениях классов для создания класса, дочернего относительно другого класса.
class Animal constructor(name) this.name = name; > speak() console.log(`$this.name> издаёт звук.`); > > class Dog extends Animal constructor(name) super(name); // вызывает конструктор super класса и передаёт параметр name > speak() console.log(`$this.name> лает.`); > > let d = new Dog("Митци"); d.speak(); // Митци лает
Если в подклассе присутствует конструктор, он должен сначала вызвать super, прежде чем использовать this.
Аналогичным образом можно расширять традиционные, основанные на функциях «классы»:
function Animal(name) this.name = name; > Animal.prototype.speak = function () console.log(`$this.name> издаёт звук.`); >; class Dog extends Animal speak() console.log(`$this.name> лает.`); > > let d = new Dog("Митци"); d.speak(); // Митци лает // Для аналогичных методов дочерний метод имеет приоритет над родительским.
Обратите внимание, что классы не могут расширять обычные (non-constructible) объекты. Если вам необходимо создать наследование от обычного объекта, в качестве замены можно использовать Object.setPrototypeOf() :
var Animal = speak() console.log(`$this.name> издаёт звук.`); >, >; class Dog constructor(name) this.name = name; > > // Если вы этого не сделаете, вы получите ошибку TypeError при вызове speak. Object.setPrototypeOf(Dog.prototype, Animal); let d = new Dog("Митци"); d.speak(); // Митци издаёт звук.
Species
Допустим, вам хотелось бы возвращать объекты типа Array в вашем производном от массива классе MyArray . Паттерн species позволяет вам переопределять конструкторы по умолчанию.
Например, при использовании таких методов, как map() , который возвращает конструктор по умолчанию, вам хотелось бы, чтобы они возвращали родительский объект Array вместо объекта MyArray . Символ Symbol.species позволяет это реализовать:
class MyArray extends Array // Изменить species на родительский конструктор Array static get [Symbol.species]() return Array; > > var a = new MyArray(1, 2, 3); var mapped = a.map((x) => x * x); console.log(mapped instanceof MyArray); // false console.log(mapped instanceof Array); // true
Обращение к родительскому классу с помощью super
Ключевое слово super используется для вызова функций на родителе объекта.
class Cat constructor(name) this.name = name; > speak() console.log(`$this.name> издаёт звук.`); > > class Lion extends Cat speak() super.speak(); console.log(`$this.name> рычит.`); > > let l = new Lion("Фаззи"); l.speak(); // Фаззи издаёт звук. // Фаззи рычит.
Mix-ins
Абстрактные подклассы, или mix-ins, — это шаблоны для классов. У класса в ECMAScript может быть только один родительский класс, поэтому множественное наследование (к примеру, от tooling classes) невозможно. Функциональность должен предоставлять родительский класс.
Для реализации mix-ins в ECMAScript можно использовать функцию, которая в качестве аргумента принимает родительский класс, а возвращает подкласс, его расширяющий:
var calculatorMixin = (Base) => class extends Base calc() > >; var randomizerMixin = (Base) => class extends Base randomize() > >;
Класс, использующий такие mix-ins, можно описать следующим образом:
class Foo > class Bar extends calculatorMixin(randomizerMixin(Foo)) >
Спецификации
| Specification |
|---|
| ECMAScript Language Specification # sec-class-definitions |
Совместимость с браузерами
BCD tables only load in the browser
Повторное определение класа
Класс не может быть переопределён. Попытка этого приведёт к SyntaxError .
Если мы запускаете код в веб браузере, к примеру в Firefox Web Console (Tools > Web Developer > Web Console) и вы используете (‘Run’) определение класса с одним и тем же именем дважды, вы получите SyntaxError: redeclaration of let ClassName; . (Обсуждение по ошибке можно посмотреть в баг 1428672.) Chrome Developer Tools возвращает сообщение типа Uncaught SyntaxError: Identifier ‘ClassName’ has already been declared at :1:1 .
Смотрите также
- Функции
- Определение классов
- Выражение классов
- Публичные поля класса
- Приватные поля класса
- super
- Статья в блоге: «ES6 In Depth: Classes»
- Fields and public/private class properties proposal (stage 3)
Found a content problem with this page?
- Edit the page on GitHub.
- Report the content issue.
- View the source on GitHub.
This page was last modified on 7 авг. 2023 г. by MDN contributors.
Your blueprint for a better internet.
Styled Components — идеальная стилизация React-приложения
Данная статья будет полезна новичкам и, возможно, старичкам. Эта реализация является чисто субъективной и может вам не понравиться (жду вас в комментах). Для понимания материала требуются базовые навыки работы с React и TypeScript.
Введение
Styled Components — одно из популярных решений написания кода методом CSS in JS. Гибкое, простое и, главное, идеально вписывается в архитектуру React приложения.
CSS in JS — описание стилей в JavaScript файлах.
Преимущества:
- Никаких больше className. Возможность передавать классы никуда не пропадает, но их использование опционально и бессмысленно, теперь мы можем прописывать все стили внутри стилизованных компонент, и классы будут генерироваться автоматически.
- Простая динамическая стилизация. Не нужно больше писать тернарные операторы и жонглировать className внутри компоненты, теперь все эти проблемы решаются благодаря прокидыванию пропсов внутрь стилизованных компонент.
- Теперь это JS. Так как теперь стили пишутся в экосистеме JavaScript, это упрощает навигацию по проекту и даёт различные возможности написания кода.
- StylisJSпод капотом. Данный препроцессор поддерживает:
4.1. Ссылки на родителя &, который часто используют в SCSS.
4.2. Минификация — уменьшение размера исходного кода.
4.3. Tree Shaking — удаление мёртвого кода.
4.4. Вендорные префиксы — приставка к свойству CSS, обеспечивающая поддержку браузерами, в которых определённая функция ещё не внедрена на постоянной основе.
За последние пол года Styled Components стал моим любимчиком, и теперь я стараюсь использовать его в каждом возможном проекте. В этой статье хочу выделить мои лучшие практики и поделиться приобретенным опытом.
Установка
Создадим приложение с помощью CRA с TypeScript.
npx create-react-app my-app --template typescript # or yarn create react-app my-app --template typescript
TypeScript будет отличным помощником в написании стилизованных компонент и даст нам больше контроля и понимания в коде. Все дальнейшие примеры будут описываться в связке с TypeScript.
Установим Styled Components и типы для него.
npm i styled-components @types/styled-components # or yarn add styled-components @types/styled-components
Установим расширение vscode-styled-components для подсветки и подсказок в VSCode.
Создадим следующую файловую структуру в корне src. Чуть позже мы остановимся на каждом из этих файлов.
--src/ ---styles/ ----animations.ts ----components.ts ----global.ts ----theme.ts
Основы
Базовый пример
Посмотрим на простую реализацию Styled Components.
// Какая-тоКомпонента.tsx import styled from 'styled-components' // Создаем стилизованную компоненту // Присваиваем ей функцию styled.[название тега] // Приписываем шаблонную строку и внутри пишем CSS стили const Container = styled.div` background-color: #2b2b2b; border-radius: 5px; ` const Title = styled.h1` font-weight: 300; ` const Text = styled.p` font-size: 12px ` // Используем эти компоненты внутри нашего JSX! export const SimpleComponent = () => ( Styled Component Some text )
Зависимости (properties/пропсы)
Чтобы сделать нашу стилизованную компоненту зависимой от значений, нужно передать атрибутом необходимые параметры.
В случае с TypeScript для описания дополнительных свойств нужно определить тип.
Обобщённый тип (обобщение, дженерик) позволяет резервировать место для типа, который будет заменён на конкретный, переданный пользователем в треугольных скобках .
// Какая-тоКомпонента.tsx import styled from "styled-components"; // Компонента будет ждать на вход // атрибут bg с любым строковым значением const Container = styled.div>` // Чтобы получить доступ к зависимостям, // внутри шаблонных строк воспользуемся строковой интерполяцией `$` // Где вызовем функцию у которой есть параметр props background-color: $ props.bg>; ` // Если тип занимает много места, // то будет лучше вынести его в отельный интерфейс interface TitleProps < weight: 200 | 300 | 400 | 500 | 600 | 700 >const Title = styled.h1` // Для лучшей читаемости - деструктурируем props, // задаем дефолтное значение если это необходимо font-weight: $<(< weight = 400 >) => weight>; ` interface TextProps < primary: boolean >const Text = styled.p` color: $<(< primary >) => primary ? '#424242' : '4b4b4b'>; ` export const SimpleComponentWithProps = () => ( >Styled Component Some Text )
Атрибуты
Мы также имеем доступ к атрибутам, когда создаём стилизованную компоненту. Метод attrs позволяет преобразовывать пропсы стилизованной компоненты перед их использованием.
// Какая-тоКомпонента.tsx import styled from "styled-components"; interface TextInputProps < size: number; >const TextInput = styled.input.attrs((props) => ( < // Статичные свойства // Мы можем задать им значение type: "text", onFocus: () =>console.log("Focused"), // Динамическая зависимость // Можем изменить её перд отправкой в стили size: (props.size || 4) + "px", >))` padding: $<(< size >) => size>; margin: $<(< size >) => size>; `; export const SimpleComponentWithAttrs = () => />;
Лучше не использовать без необходимости статичные свойства внутри стилизованной компоненты, так как зачастую эти свойства задаются в JSX. Просто знайте об этой фиче.
Наследование стилей
Стилизованные компоненты могут наследовать стили других компонент, что помогает избежать дублирования кода.
// Какая-тоКомпонента.tsx import styled from 'styled-components' const Text = styled.div` font-size: 12px; ` // TomatoText наследует те же стили и тег, что и Text const TomatoText = styled(Text)` color: "#FF6347"; ` export const SimpleComponentWithExtending = () => ( <> Simple Text Tomato Text >)
CSS-фрагмент
Важной особенностью является передача фрагментов стилей внутрь стилизованной компоненты, данный подход упрощает написание кода и предотвращает повторяемые элементы стилей.
// Какая-тоКомпонента.tsx import styled, < css >from 'styled-components' // Создаем css фрагмент const fontStyles = css` font-size: 12px; line-height: 14px; font-weight: 700; ` const Text1 = styled.h1` color: blue; // Дополняем стили Text1 фрагментом fontStyles $ ` const Text2 = styled.p` color: blueviolet; $ ` export const SimpleComponent = () => ( <> Some Text Another some text >)
Глобальные стили
Как и подобает любому веб-приложению, добавим нашему проекту главный файл со стилями. Откроем global.ts и с помощью функции createGlobalStyle сформируем компонент с глобальными стилями.
// global.ts import < createGlobalStyle >from 'styled-components' export default createGlobalStyle` * < . >*::before, *::after < . >body < . >`
Далее добавим его в приложение.
// App.tsx import < Routing >from 'routing' // Импортируем глобальные стили import GlobalStyles from 'styles/global' const App = () => < return ( <> < // Добавляем его как тег > >) > export default App
Тема
Как насчёт сделать тему приложения одним большим источником истины в котором мы будем хранить палитру, размеры, медиа запросы и прочие свойства, связанные с компонентами?
В файле theme.ts объявим переменную со всеми необходимыми свойствами.
// theme.ts export const baseTheme = < colors: < primary: '#7986cb', secondary: '#2b2b2b', success: '#4caf50', danger: '#f44336 ', bg: '#E5E4E8', font: '#19191B', >, media: < extraLarge: '(max-width: 1140px)', large: '(max-width: 960px)', medium: '(max-width: 720px)', small: '(max-width: 540px)', >, // in px sizes: < header: < height: 56 >, container: < width: 1200 >, footer: < height: 128 >, modal: < width: 540 >, >, // in ms durations: < ms300: 300, >, // z-index order: < header: 50, modal: 100, >, >
Данный подход избавляет нас от магических чисел, и даёт возможность применять конкретные значения в стилях и компонентах.
Магические числа в коде — одно из олицетворений зла и лени у программиста. Это целочисленная константа, которая встречается в коде, смысл которой сложно понять.
C этого момента мы уже просто можем импортировать эту константу в необходимых местах и использовать её.
// Какая-тоКомпонента.tsx import styled from 'styled-components' import < baseTheme >from 'styles/theme' const StyledHeader = styled.header` background-color: $; height: $px; z-index: $; ` export const Header = () => Title
Можно передавать тему внутрь стилизованных компонент при помощи ThemeProvider, чтобы постоянно не импортировать наш baseTheme.
// App.tsx import < ThemeProvider >from 'styled-components' import < Routing >from 'routing' import GlobalStyles from 'styles/global' // Импортируем тему import < baseTheme >from 'styles/theme' const App = () => < return ( > ) > export default App
Теперь в любой стилизованной компоненте, которая находится внутри провайдера, мы имеем доступ к baseTheme.
// Какая-тоКомпонента.tsx import styled from 'styled-components' const StyledHeader = styled.header` // Получаем значение темы внутри стрелочной функции, // где деструктурируем props background-color: $<(< theme >) => theme.colors.secondary>; height: $<(< theme >) => theme.sizes.header.height>px; z-index: $<(< theme >) => theme.order.header>; ` export const Header = () => Title
У этой реализации есть одна небольшая проблема — редактор кода не даёт подсказок при написании свойств theme. Для её решения нам нужно типизировать тему и расширить интерфейс DefaultTheme.
В корне src создаём директорию interfaces с файлом styled.ts, где опишем каждое свойство темы.
// styled.ts export interface ITheme < colors: < primary: string secondary: string success: string danger: string bg: string, font: string, >media: < extraLarge: string large: string medium: string small: string >sizes: < header: < height: number >container: < width: number >footer: < height: number >modal: < width: number >> durations: < ms300: number >order: < header: number modal: number >, >
После этого создаём в корне src файл styled.d.ts, где расширяем интерфейс стандартной темы с помощью нашего типа.
// styled.d.ts import 'styled-components'; import < ITheme >from 'interfaces/styled'; declare module 'styled-components' < export interface DefaultTheme extends ITheme <>>
d.ts файлы — описывают форму сторонней библиотеки и позволяют компилятору TypeScript знать, как обращаться с этим сторонним кодом.
Не забудем дописать интерфейс ITheme нашей baseTheme, чтобы все её значения были корректны.
// theme.ts import < ITheme >from 'interfaces/styled' export const baseTheme: ITheme = < // . >
Готово!
Если что, мы можем брать этот интерфейс прямо из styled-components, если это будет необходимо.
import < DefaultTheme >from 'styled-components'
Динамическая тема
Если мы хотим создать динамическую тему, например для переключения светлой темы на тёмную — то нам потребуется уже знакомый ThemeProvider и любой стейт менеджер для инициализации и контроля темы.
Для начала создадим enum для определения типа нашей темы в папке interfaces.
// styled.ts export enum ThemeEnum < light = "light", dark = "dark" >export interface ITheme < // . >
Enum — это конструкция, состоящая из набора именованных констант, называемого списком перечисления и определяемого такими примитивными типами, как number и string.
Дополним DefualtTheme в d.ts файле.
// styled.d.ts import 'styled-components'; import < ITheme, ThemeEnum >from 'interfaces/styled'; declare module 'styled-components' < export interface DefaultTheme extends ITheme < type: ThemeEnum >>
Создадим тёмную и светлую тему на основе baseTheme. Цвета bg и font — динамические, они будут изменяться при переключении темы.
// theme.ts import < DefaultTheme >from 'styled-components' import < ITheme, ThemeEnum >from 'interfaces/styled' // ITheme - используется для статичной темы const baseTheme: ITheme = < // . >// DefaultTheme - используется для динамических тем export const lightTheme: DefaultTheme = < . baseTheme, type: ThemeEnum.light, colors: < . baseTheme.colors, bg: '#E5E4E8', font: '#19191B', >, > export const darkTheme: DefaultTheme = < . baseTheme, type: ThemeEnum.dark, colors: < . baseTheme.colors, bg: '#19191B', font: '#E5E4E8', >, >
Ниже приведён пример инициализации и переключения темы в MobX.
// ui.ts - одно из хранилищ MobX import < makeAutoObservable >from 'mobx' import < DefaultTheme >from 'styled-components' import < ThemeEnum >from 'interfaces/styled' import < darkTheme, lightTheme >from 'styles/theme' export class UIStore < theme: DefaultTheme = lightTheme constructor() < makeAutoObservable(this) >get isLightTheme() < return this.theme.type === ThemeEnum.light >// Переключатель темы toggleTheme() < this.theme = this.isLightTheme ? darkTheme : lightTheme >>
Передаём тему из стейт менеджера в ThemeProvider.
// App.tsx . return ( > ) .
После этого реализовываем механизм переключения темы, присваиваем динамические цвета в нужных местах и по желанию можно добавить плавность анимации.
Микрокомпоненты
Это простые переиспользуемые стилизованные компоненты, состоящие из одного узла, которые часто встречаются в приложении. Это могут быть заголовки, тексты, контейнеры, иконки и другие.
Перейдём в файл components.ts и создадим несколько подобных компонент.
// components.ts // Пример вертикального разделителя interface DividerProps < height?: number heightMob?: number >export const Divider = styled.div` height: $<(< height = 8 >) => height>px; // Медиа запрос @media $<(< theme >) => theme.media.large> < height: $<(< heightMob = 4 >) => heightMob>px; > ` // Пример заголовков разного уровня interface TitleProps < weight?: 200 | 300 | 400 | 500 | 600 | 700 >export const Title1 = styled.h1` font-size: 24px; font-weight: $<(< weight = 700 >) => weight>; ` export const Title2 = styled.h2` font-size: 18px; font-weight: $<(< weight = 700 >) => weight>; `
Приведу пример использования.
// Какая-тоКомпонента.tsx import < Divider, Title1, Title2 >from 'styles/components' export SimpleComponent = () => ( Some title H1 /> >Some title H2 )
Анимации
Для этого в Styled Components есть специальная функция keyframes внутрь которой мы передаём ключевые кадры. Их написание полностью схоже с аналогичным в CSS. Все анимации можно записывать в отдельный файл, так как теперь мы можем хранить значения в переменной.
// animations.ts import < keyframes >from 'styled-components' export const spin = keyframes` 0% < transform: rotate(0deg); >100% < transform: rotate(360deg); >`
Применим её к одной из микрокомпонент.
// components.ts import styled, < css >from 'styled-components' // Импортируем компоненту FontAwesomeIcon и её пропсы, // на базе которой мы расширим интерфейс передаваемых данных import < FontAwesomeIcon, FontAwesomeIconProps, >from '@fortawesome/react-fontawesome' // Импорт ключевых кадров import < spin >from './animations' interface FAIconProps extends FontAwesomeIconProps < // Временный атрибут $animated?: boolean // не будет передан в FontAwesomeIcon >export const FAIcon = styled(FontAwesomeIcon)` $<(< $animated >) => $animated ? css` animation: $ 4s infinite linear; ` : css` animation: none; `> `
Временный атрибут — обозначается префиксом $. Предотвращает дальнейшее прокидывание пропса в стилизуемый компонент.
Пример использования микрокомпоненты FAIcon.
// Какая-тоКомпонента.tsx import < faCog >from '@fortawesome/free-solid-svg-icons' import < faReact >from '@fortawesome/free-brands-svg-icons' import < FAIcon >from 'styles/components' export const SimpleComponent = () => ( <> color="#a8324a" $animated/> color="#3265a8"/> >)
Импорты
Совместное размещение стилизованных компонент с вашими фактическими компонентами упрощает файловую структуру вашего проекта. И для улучшения читаемости кода — перенесём стили после основного кода.
// Какая-тоКомпонента.tsx import styled from 'styled-components' // Импорт микрокомпоненты import < Title1 >from 'styles/components' export const Header = () => ( Some Title! ) const StyledHeader = styled.header` background-color: $<(< theme >) => theme.colors.secondary>; padding: 0 16px; `
Если же компонента разрослась до более крупных масштабов, то будет правильней вынести стили в отдельный файл styles.ts рядом с компонентой.
// Какая-тоКомпонента.tsx import < faSun >from '@fortawesome/free-regular-svg-icons' import < StyledHeader, HeaderTitle >from './styles' import < Title1, SupText, FAIcon >from 'styles/components' import < Button >from 'components/Button' export const Header = () => ( >Plankton React + Mobx + SC )
Ещё одна хорошая практика — это компактный импорт стилизованных компонент.
// Какая-тоКомпонента.tsx import < faSun >from '@fortawesome/free-regular-svg-icons' // Импортируем всё из файла styles. import * as S from './styles' import * as C from 'styles/components' import < Button >from 'components/Button' export const Header = () => ( >Plankton React + Mobx + SC )
Такой подход даёт нам несколько преимуществ:
- Не засоряем код лишними импортами.
- Логическое разделение. У каждой стилизованной компоненты теперь есть своя приставка, что упрощается ориентирование в коде. В моём случае:
Без приставки — обычные компоненты.
S — Стили для нашей фактической компоненты.
C — Микрокомпоненты. - Коллизия названий. Часто основную обёртку обзываю StyledЧто-тоТам, теперь мы спокойно можем писать S.Что-тоТам. Это связано с повторяющимся названием родительской компоненты и стилизованной обертки.
Вариации
При создании компоненты мы часто пытаемся интерпретировать её в разных видах, сохраняя основной функционал, будь-то кнопки, текстовые поля, карточки и другие.
Разберём эту практику на примере кнопки, которая может быть разных размеров.
// Где-тоВКакой-тоКомпоненте.tsx import < Button >from 'components/Button' . .
// Button.tsx import < PropsWithChildren >from 'react' import * as S from './styles' // Размеры кнопок export enum ButtonSize < xs = 'xs', sm = 'sm', md = 'md', lg = 'lg', >// Интерфейс входящих зависимостей export interface ButtonProps < size?: ButtonSize >const ButtonComponent = (< children, // Так как size опциональный, зададим ему значение по умолчанию size = ButtonSize.md, >: PropsWithChildren) => < return ( > ) > // Присваиваем Enum ButtonSize компоненте ButtonComponent.size = ButtonSize export const Button = ButtonComponent
Я не буду сильно заострять внимание на реализации компоненты, так как это другая тема. Здесь стоит понимать, что значение size может задаваться только в рамках enum ButtonSize.
Перейдём к стилям.
// styles.tsx import styled, < css >from 'styled-components' // Импортируем enum import < ButtonSize >from '.' interface ButtonProps < size: ButtonSize >export const Button = styled.button` // . $<(< size >) => size === ButtonSize.lg ? css` height:48px; font-size: 18px; ` : size === ButtonSize.md ? css` height: 40px; font-size: 16px; ` : size === ButtonSize.sm ? css` height: 32px; font-size: 14px; ` : css` height: 24px; font-size: 12px; ` > `
Думаю вы согласитесь, что это не самое красивое решение. Поэтому предлагаю такую альтернативу: избавимся от тернарных операторов и создадим объект sizes из которого просто будем брать необходимое значение по ключу.
Получим следующий результат.
// styles.tsx import styled, < css >from 'styled-components' import < ButtonSize >from '.' import < StyledVariants >from 'interfaces/styled' interface ButtonProps < size: ButtonSize >export const Button = styled.button` // . $<(< size >) => sizes[size]> ` const sizes: StyledVariants =
Необходимо типизировать данный объект, чтобы быть уверенными в задаваемых свойствах. Дополним наш файл с интерфейсами.
// styled.ts // тип css фрагмента import < FlattenSimpleInterpolation >from 'styled-components' // E - элемент enum export type StyledVariants =
Что если вариация зависит не от одного значения, а от двух или более? Я добавлю в компонент кнопки еще две зависимости variant и color.
// Button.tsx . export enum ButtonVariant < solid = 'solid', outline = 'outline', ghost = 'ghost', >export enum ButtonColor < primary = 'primary', secondary = 'secondary', success = 'success', danger = 'danger', >.
Для работы с несколькими параметрами нам потребуется создать конструкцию switch case, которая будет возвращать один из CSS-фрагментов определенной вариации и цвета.
// styles.tsx import styled, < css >from 'styled-components' import < ButtonVariant, ButtonColor >from '.' interface ButtonProps < variant: ButtonVariant color: ButtonColor >export const Button = styled.button` // . $<(< variant = ButtonVariant.solid, color = ButtonColor.primary, theme >) => < const themeColor = theme.colors[color] switch (variant) < case ButtonVariant.solid: return css` background-color: $; ` case ButtonVariant.outline: return css` background-color: transparent; color: $; border: 1px solid $; ` case ButtonVariant.ghost: return css` background-color: transparent; color: $; border: none; box-shadow: rgba(0, 0, 0, 0.12) 0px 1px 3px; ` > >> `
Бонус
polished
Хорошее дополнение для Styled Components о котором вы должны знать. Этот пакет даёт массу новых возможностей, например затемнять и осветлять цвета, переводить hex в rgb, делать элемент прозрачным с привязкой к определённому цвету и многое другое.
Оставим эту библиотеку с закосом на сиквел.
Цветные скобки
В процессе использования Styled Components, я столкнулся с одной неприятной багой плагина Bracket Pair Colorizer. Так как мы пишем стили внутри шаблонных строк, то зачастую скобки разных уровней неправильно подсвечиваются. Благо решение есть, причем очень свежее (на данный момент), с недавним обновлением VSCode ввёл свои цветные скобки, и как пишут сами разработчики, их реализация в 10 000 раз быстрее плагина.
Нужно просто добавить следующий параметр в настройки вашего редактора:
"editor.bracketPairColorization.enabled": true
getTransitions
Хочу поделиться своим хелпером — эта функция упростит написание транзишенов, особенно в тех местах, где мы хотим реализовать смену темы.
import < css >from 'styled-components' export const getTransition = ( duration: number, property: string[] | string = ['background-color', 'color'], animation = 'ease' ) => css` transition-property: $; transition-duration: $ms; transition-timing-function: $; `
Пример использования в стилизованной компоненте.
const Something = styled.div` $<(< theme >) => getTransition(theme.durations.ms300, [ 'background-color', 'border-color', 'color', ])> `
Больше примеров
В данном репозитории собраны готовые компоненты, статьи, видео, проекты, созданные на базе Styled Components, и многое-многое другое. Советую посмотреть.
Заключение
Надеюсь я смог донести уникальность и гибкость написания CSS-кода с помощью Styled Components. Как по мне, это идеальный инструмент для React проектов, где мы можем позволить себе инновации, уникальные подходы и массу вариативности!
Если есть желание посмотреть на эти практики в деле, то вот исходники и демо проекта.
Работа с объектами в JavaScript: теория и практика
В этой статье я хочу по возможности полно и последовательно рассказать о том, что такое объект в JavaScript, каковы его возможности, какие взаимоотношения могут строиться между объектами и какие способы «родного» наследования из этого вытекают, как это все влияет на производительность и что вообще со всем этим делать 🙂
В статье НЕ будет ни слова про: эмуляцию традиционной класс-объектной парадигмы, синтаксический сахар, обертки и фреймворки.
Сложность материала будет нарастать от начала к концу статьи, так что для профи первые части могут показаться скучными и банальными, но дальше будет намного интереснее 🙂
Объекты в JavaScript
Во многих статьях встречается фраза «В JavaScript — всё объект». Технически это не совсем верно, однако производит должное впечатление на новичков 🙂
Действительно, многое в языке является объектом, и даже то, что объектом не является, может обладать некоторыми его возможностями.
Важно понимать, что слово «объект» употребляется здесь не в смысле «объект некоторого класса». Объект в JavaScript — это в первую очередь просто коллекция свойств (если кому проще, может называть это ассоциативным массивом или списком), состоящая из пар ключ-значение. Причем ключом может быть только строка (даже у элементов массива), а вот значением — любой тип данных из перечисленных ниже.
Итак, в JavaScript есть 6 базовых типов данных — это Undefined (обозначающий отсутствие значения), Null, Boolean (булев тип), String (строка), Number (число) и Object (объект).
При этом первые 5 являются примитивными типами данных, а Object — нет. Кроме того, условно можно считать, что у типа Object есть «подтипы»: массив (Array), функция (Function), регулярное выражение (RegExp) и другие.
Это несколько упрощенное описание, но на практике обычно достаточное.
Кроме того, примитивные типы String, Number и Boolean определенным образом связаны с не-примитивными «подтипами» Object: String, Number и Boolean соответственно.
Это означает, что строку ‘Hello, world’, например, можно создать и как примитивное значение, и как объект типа String.
Если вкратце, то это сделано для того, чтобы программист мог и в работе с примитивными значениями использовать методы и свойства, как будто это объекты. А подробнее об этом можно будет прочитать в соответствующем разделе данной статьи.
Работа по ссылке
Ссылка — это средство доступа к объекту под различными именами. Работа с любыми объектами ведется исключительно по ссылке.
Продемонстрируем это на примере:
test= function ()
//Создадим функцию (а функция, как мы помним, является полноправным объектом) и сделаем переменную test ссылкой на нее
test_link=test; //test_link теперь тоже ссылается на нашу функцию
test(); //Hello!
test_link(); //Hello!
* This source code was highlighted with Source Code Highlighter .
Как мы видим, и первая ссылка, и вторая дают один и тот же результат.
Необходимо осознать, что у нас нет никакой функции с именем test, и что переменная test не является какой-то «главной» или «основной» ссылкой, а «test_link» — второстепенной.
Наша функция, как и любой другой объект — просто область в памяти, и все ссылки на эту область абсолютно равнозначны. Более того, объект может вообще не иметь ссылок — в таком случае он называется анонимным, и может быть использован только непосредственно сразу после создания (например, передан в функцию), иначе доступ к нему получить будет невозможно и в скором времени он будет уничтожен сборщиком мусора (garbage collection), который и занимается тем, что удаляет объекты без ссылок.
Посмотрим, почему так важно это понимать:
test= //Создаем объект со свойством prop
test_link=test; //Создаем еще одну ссылку на этот объект
alert(test.prop); //sometext
alert(test_link.prop); //sometext
//Изменяем свойство объекта
test_link.prop= ‘newtext’ ;
alert(test.prop); //newtext
alert(test_link.prop); //newtext
/*Можно было бы сказать, что свойство изменилось и там и тут — но это не так.
Объект-то один. Так что свойство изменилось в нем один раз, а ссылки просто продолжают указывать туда, куда и указывают. */
//Добавляем новое свойство и удаляем старое
test.new_prop= ‘hello’ ;
delete test.prop;
alert(test_link.prop); //undefined — такого свойства больше нет
alert(test_link.new_prop); //hello — что и следовало ожидать
//Удаляем ссылку
delete test;
alert(test.new_prop);
/*В этом месте скрипт выкинет ошибку, потому что test уже не существует, и test.new_prop не существует тем более */
alert(test_link.new_prop); //hello
/* а вот тут все в порядке, ведь мы удалили не сам объект, а лишь ссылку на него. Теперь на наш объект указывает единственная ссылка test_link */
//Создаем новый объект
test=test_link; //Сперва снова создадим ссылку test
test_link= //А вот и новый объект
alert(test_link.prop); //sometext
alert(test.prop); //undefined
/* Cоздание нового объекта разрывает ссылочную связь, и теперь test и test_link указывают на разные объекты.
Фактически, это равносильно удалению ссылки test_link и созданию ее заново, но уже указывающей на другой объект */
alert(test.new_prop); //hello — теперь test содержит ссылку на наш самый первый объект
* This source code was highlighted with Source Code Highlighter .
Такое поведение объектов часто вызывает массу вопросов у начинающих разработчиков, так что надеюсь данный текст внесет некоторую ясность. Если же мы хотим создать действительно новую, независимую копию объекта, а не ссылку — то единственный способ сделать это — создать новый объект и скопировать туда требуемые свойства.
Также стоит отметить, что работа с объектами по ссылке, помимо вышеперечисленных забавных эффектов дает также значительную экономию памяти, что немаловажно при широком использовании одного объекта в различных местах программы.
Примитивные значения
Как я упоминал выше, типы данных String и Number могут быть как объектами, так и примитивными значениями.
obj= new String( ‘hello’ ); //Создаем строку как объект
simple= ‘hello’ ; //Создаем примитивное значение
alert(obj); //hello
alert(simple); //hello — пока все предсказуемо
alert(obj.length); //6 — у объекта типа String есть свойство length, хранящее длину строки
alert(simple.length); //6
/* Хотя simple — не объект, мы можем обращаться к тому же набору свойств, что и у объекта типа String. Это довольно удобно */
obj.prop= ‘text’ ;
simple.prop= ‘text’ ;
alert(obj.prop); //text — раз obj обычный объект, то мы можем запросто придать ему еще одно свойство
alert(simple.prop); //undefined — а вот simple не объект, и этот номер у нас не пройдет
* This source code was highlighted with Source Code Highlighter .
Все то же самое справедливо и для типа Number, и для Boolean (ну, кроме того, что в них нет свойства length, а есть ряд других замечательных свойств).
Использование строк и чисел как объектов не несет в себе никакой практической пользы, т.к. примитивные значения удобнее в работе, но сохраняют при этом весь необходимый функционал. Тем не менее, для полноты картины необходимо понимать этот механизм.
Не стоит путать использование примитивных значений с использованием литералов — например, независимо от того, создаем мы массив как «test=new Array()» или как «test=[]», в результате все равно будет один и тот же объект. Никаких примитивных значений мы не получим.
Создание и использование объектов
Итак, в отличии от языков, где реализована класс-объектная парадигма, нам не нужно создавать сначала класс, чтобы потом создать объект класса. Мы можем сразу создать объект, что и сделаем в следующем примере:
test= simple_property: ‘Hello’ ,
object_property: user_1: ‘Петя’ ,
user_2: ‘Вася’
>,
function_property: function (user) alert( this .simple_property + ‘, ‘ + this .object_property[user]);
>
>
test.function_property( ‘user_1’ ); //Hello, Петя.
* This source code was highlighted with Source Code Highlighter .
Перед нами объект test, имеющий 3 свойства, названия которых, как я надеюсь, говорят сами за себя. Больше всего нас в нем интересует свойство function_property, содержащее функцию. Такую функцию можно назвать методом объекта.
В нашей функции дважды используется ключевое слово this, которое является указателем (т.е. ссылкой) на объект, из которого вызывается функция. Таким образом, this.simple_property=test.simple_property=’Hello’, а this.object_property[user]=test.object_property[user]=’Петя’.
Необходимо четко осознавать, this всегда указывает именно на объект, из которого вызвана функция, а не на объект, к которому она принадлежит. Хотя в данном примере это один и тот же объект, это не всегда так.
test.function_property( ‘user_1’ ); //Hello, Петя.
test2= new Object(); //Еще одна форма создания нового объекта, аналогичная test2=<>
test.function_property.call(test2, ‘user_1’ ); //ошибка
/* Метод call позволяет вызвать функцию от имени другого объекта. В данном случае, мы вызываем метод function_property объекта test, и его this указывает уже не на объект test, а на объект test2. А т.к. в нем нет свойства object_property, то при попытке получить this.object_property[user]скрипт выдаст ошибку */
//попробуем исправить ситуацию
test2.simple_property= ‘Good day’ ;
test2.object_property=test.object_property; //В данном случае воспользуемся указанием объекта по ссылке, чтобы не дублировать код
test.function_property.call(test2, ‘user_1’ ); //Good day, Петя.
* This source code was highlighted with Source Code Highlighter .
Из примера также должно быть видно, что нет четких этапов создания и использования объекта. Объект может быть как угодно модифицирован в любое время — до, после и даже во время использования. Это тоже важное отличие от «традиционного» ООП.
Конструктор
В примере выше мы создавали 2 объекта, обладающих некой схожестью. И там и там имелись свойства simple_property и object_property. Очевидно, что при написании реального кода также нередко встает задача создания одинаковых или просто похожих объектов. И разумеется, мы не должны каждый такой объект создавать вручную.
На помощь нам придет конструктор. Конструктор в JavaScript — это не часть класса (потому что здесь нет классов), а просто самостоятельная функция. Самая обычная функция.
make_me= function (_name) alert( ‘меня запустили’ );
this .name=_name;
this .show_name= function ()
>
child= new make_me( ‘Вася’ ); //меня запустили
/* Давайте разберемся, что здесь происходит. Интерпретатор видит оператор new и проверяет, что находится справа от него. Т.к. make_me — это функция, и она может быть использована в качестве контруктора, то создается новый объект в памяти и запускается на выполнение функция make_me, причем ее this указывает как раз на этот новый объект. Далее этому объекту добавляется свойство name, которому присваивается значение из аргумента _name, и метод show_name. Также (не знаю в какой именно момент, но это и не важно) переменная child начинает указывать на наш новенький, только что рожденный объект */
alert(child.name); //Вася
child.show_name(); //Вася
child2= new make_me( ‘Петя’ );
child2.show_name(); //Петя
child2.show_name= function ()
child2.show_name(); //Не буду говорить свое имя
child.show_name(); //Вася — дети никак не влияют друг на друга
* This source code was highlighted with Source Code Highlighter .
Также можно сравнить конструктора с отцом — он порождает ребенка, наделяя его определенными качествами, но сразу после создания ребенок становится полностью независим от родителя и может стать очень непохожим на своих братьев.
Если мы вспомним про описание типов данных в начале статьи, то становится понятно, что Object и его подтипы (Function, Array и другие) — это на самом деле конструкторы, придающие создаваемому объекту возможности функции, массива и т.д.
Итак, это уже намного лучше. У нас теперь есть возможность создавать объекты по некоторому образцу. Однако, не все еще хорошо. Во-первых, каждый созданный нами объект и все его свойства и методы занимают отдельное место в памяти, хотя во многом они повторяются. Во-вторых, как быть, если мы хотим сохранить связь между родителем и ребенком, и иметь возможность менять все дочерние объекты разом. На помощь нам придет прототип.
Прототип
Как у каждого ребенка есть отец и мать (хотя бы в биологическом смысле), также они есть и у каждого объекта в JavaScript. И если отец, как мы определелись, работает конструктором, то мать — это как раз прототип. Посмотрим, как это происходит:
make_me= function (_name) alert( ‘меня запустили’ );
this .name=_name;
this .show_name= function ()
>
/*
Видя ключевое слово function, интерпретатор проверяет код справа от него, и т.к. все ок — создает новый объект в памяти, который одновременно является нашей функцией. Затем, автоматически (без участия программиста) для этой функции создается свойство prototype, ссылающееся на пустой объект. Если бы мы это делали вручную, это выглядело бы как make_me.prototype=new Object();
Затем, данному объекту (на который указывает свойство prototype) также автоматически добавляется свойство constructor, указывающее обратно на функцию. Получается такая вот циклическая ссылка.
Теперь этот объект, который можно описать как — и есть прототип функции.
*/
alert( typeof make_me.prototype); //Object — действительно, объект
alert( typeof make_me.prototype.constructor); //Function — это наша функция
alert(make_me.prototype.constructor === make_me); //true
make_me.prototype.set_name= function (_name) < this .name=_name;>//Добавляем в прототип функции make_me новый метод
child= new make_me( ‘Вася’ ); //меня запустили
/* Теперь помимо всего того, что описано в предыдущем примере, дополнительно в объекте child создается скрытое свойство [[Prototype]], которое указывает на тот же объект, что и make_me.prototype. Т.к. свойство скрыто, мы не можем ни просмотреть его значение, ни изменить его — однако оно играет важную роль в дальнейшей работе */
alert(child.name); //Вася
child.show_name(); //Вася
child.set_name( ‘Коля’ );
/* Сначала, интерпретатор ищет метод set_name в объекте child. Так как его там нет, он продолжает поиск в свойстве child.[[Prototype]], находит его там и запускает. */
child.show_name(); //Коля — теперь Васю зовут Коля 🙂
make_me.prototype.show_name2= function ()
child2= new make_me( ‘Петя’ );
child2.show_name2(); //Привет, Петя
child.show_name2(); //Привет, Коля — изменения в прототипе влияют не только на вновь созданные объекты, но и на все старые
child2.show_name2= function ()
child2.show_name2(); //Не буду говорить свое имя — т.к. у нас теперь есть собственный метод show_name2, то он и вызывается, и поиск в прототипе не происходит
child.show_name2(); //Привет, Коля — здесь все по прежнему
make_me.prototype= //Попробуем пересоздать прототип заново
alert(child.prop); //undefined
child.show_name2(); //Привет, Коля
/* Если вспомнить, что такое работа по ссылке, то все понятно. Пересоздание прототипа рвет связь, и теперь свойство [[Prototype]] у объектов child и child2 указывают на один объект (который раньше был прототипом функции make_me), а свойство make_me.prototype — на другой объект, который является новым прототипом функции make_me */
child3= new make_me( ‘Олег’ );
alert(child3.prop); //hello — что и следовало ожидать
* This source code was highlighted with Source Code Highlighter .
Как видно из примера, пока отец сохраняет верность матери (т.е. пока протип функции остается прежним), все дети зависят от матери и чутко реагируют на все изменения в ней. Однако, стоит только родителям развестись (конструктор меняет прототип на другой) — дети тут же разбегаются кто куда и больше связи с ними нет.
Немного о терминологии
До тех пор, пока первичная связь между конструктором и прототипом не разорвана, мы можем наблюдать следующую картину:
make_me= function (_name) alert( ‘меня запустили’ );
this .name=_name;
this .show_name= function ()
>
make_me.prototype.set_name= function (_name) < this .name=_name;>
child= new make_me( ‘Вася’ );
alert( typeof make_me.prototype); //object — у функции есть свойство prototype
alert( typeof child.prototype); //undefined — у созданного объекта НЕТ свойства prototype
alert(child.constructor.prototype === make_me.prototype); //true — зато у объекта есть свойство constructor, которое указывает на функцию-конструктор make_me, у которой, в свою очередь, есть свойство prototype
* This source code was highlighted with Source Code Highlighter .
Как я заметил после чтения многочисленных форумов на эту тему, основные проблемы возникают у людей, когда они путают свойство prototype у функции и скрытое свойство [[Prototype]] у объекта, созданного с помощью этой функции.
Оба этих свойства являются ссылкой на один и тот же объект (до тех пор, пока первичная связь прототипа с конструктором не нарушена), но это тем не менее разные свойства, с разными именами, одно из них доступно для программиста, а другое нет.
Необходимо всегда четко понимать, что если речь идет о прототипе конструктора — то это всегда свойство prototype, а если о прототипе созданного объекта — то это скрытое свойство [[Prototype]].
Наследование
Теперь мы знаем, что у каждого объекта есть скрытая ссылка на прототип, а каждый прототип — это обычный объект.
Наиболее чуткие читатели уже уловили запах рекурсии 🙂
Действительно, т.к. прототип — это обычный объект, то и он в свою очередь имеет ссылку на свой прототип, и так далее. Таким образом реализуется иерархия прототипов.
bird= function () <> //Это конструктор птички
bird.prototype.cry= function ()//Птичка умеет кричать
bird.prototype.fly= function ()//и летать
duck= function () <>
duck.prototype= new bird();
duck.prototype.cry= function ()
duck.prototype.constructor=duck; //Принудительно устанавливаем свойство prototype.constructor в duck, т.к. иначе оно будет ссылаться на bird
billy = new duck(); //Билли — это наша утка
billy.fly(); //Я лечу! — Билли может летать, потому что он птица
billy.cry(); //Кря-кря! — Билли кричит кря-кря, потому что он утка
* This source code was highlighted with Source Code Highlighter .
Так можно реализовывать иерархию любого уровня вложенности.
Задача на звездочку
Теперь, раз уж мы столько знаем обо всем этом, давайте попробуем разобраться, сколько всего происходит в этих трех строчках
make_me= function () <>
child= new make_me();
alert(child.toString()); //выводит [object]* This source code was highlighted with Source Code Highlighter .
В первой строке мы создаем новую функцию и переменную make_me, которая указывает на эту функцию. При этом создается прототип функции, make_me.prototype, в котором содержится свойство constructor, указывающее на make_me.
Но это далеко не все 🙂
Т.к. функция make_me — это тоже объект, то он в свою очередь имеет папу и маму, т.е. конструктор и прототип. Его конструктор — это родная функция языка Function(), а прототип — объект, содержащий в себе методы call, apply и т.д. — именно благодаря этому прототипу мы и можем пользоваться этими методами в любой функции. Таким образом, у функции make_me появляется свойство [[Prototype]], указывающее на Function.prototype.
В свою очередь, прототип конструктора Function — тоже объект, конструктором которого является (сюрприз!) Object (т.е. Function.prototype.[[Prototype]].constructor===Object), а прототипом — объект, содержащий стандартные свойства и методы объекта, такие как toString, hasOwnProperty и другие (другими словами — Function.prototype.[[Prototype]][‘hasOwnProperty’] — это как раз тот самый метод, которым мы можем пользоваться во всех производных объектах — причем это именно собственной метод данного объекта, а не наследованный). Вот таким вот интересным образом мы обнаруживаем, что все виды объектов являются производными от Object.
Можем ли мы продолжить дальше? Оказывается, нет. Object.prototype именно потому и содержит базовые свойства объекта, что не имеет собственного прототипа. Object.prototype.[[Prototype]]=null; В этом месте путешествие по цепочке прототипов в поиске свойства или метода прекращается.
Еще один интересный факт — конструктором Object является Function. Т.е. Object.[[Prototype]].constructor===Function.
Налицо еще одна циклическая ссылка — конструктор Object это Function, а конструктор Function.prototype — это Object.
Вернемся к нашему примеру. Как создается функция мы уже поняли, теперь перейдем ко второй строке. Там мы создаем объект child, конструктором которого является функция make_me, а прототипом — make_me.prototype.
Ну и в третей строчке мы видим, как интепретатор поднимается по цепочке, от child к child.[[Prototype]] (он же make_me.prototype), затем к child.[[Prototype]].[[Prototype]] (он же Object.prototype), и уже там находит метод toString, который и запускает на выполнение.
Примеси
Может показаться, что наследование через прототипы — единственный способ, возможный в JavaScript. Это не так.
Мы имеем дело с очень гибким языком, который предоставляет не столько правила, сколько возможности.
Например, при желании мы можем вообще не использовать прототипы, а программировать с помощью концепции примесей. Для этого нам пригодятся наши старые добрые друзья — конструкторы.
//Это конструктор человека
man= function () this .live= function ()//Человек умеет жить
this .walk= function ()//Человек умеет ходить
>
//Это конструктор поэта
poet= function () this .kill= function ()
this .live= function ()
>
vladimir= new man(); //Владимир — человек
vladimir.live(); //Я живу — он жив
vladimir.walk(); //Я иду — он ходит
poet.call(vladimir); //Выполняем конструктор poet для объекта vladimir
vladimir.kill(); //Поэт убил человека
vladimir.live(); //Я мертв
//А теперь фокус
man.call(vladimir);
vladimir.live(); //Я живу
* This source code was highlighted with Source Code Highlighter .
Что мы видим в данном примере? Во-первых, это возможность наследования от нескольких объектов, не находящихся в одной иерархии. В примере их 2, но может быть сколько угодно.
Во-вторых, это отсутствие какой-либо иерархии вообще. Переопределение свойств и методов определяется исключительно поряком вызова конструкторов.
В-третьих, это возможность еще более динамически менять объект, причем именно отдельный объект, а не всех потомков, как при изменении прототипа.
Upd: Замыкания и приватные свойства
Чтобы не раздувать эту и без того немаленькую статью, даю ссылку на пост Замыкания в JavaScript, где про это довольно подробно написано.
Что теперь со всем этим делать
Как я уже говорил выше, и произвольное изменение отдельных объектов, и использование конструкторов, и примеси, и гибкость прототипов — это лишь инструменты, возможности, которые позволяют программисту создать как ужасный, так и прекрасный во всех отношениях код. Важно лишь понимать, какие задачи мы решаем, какими средствами, какие цели достигаем и какую цену платим за это.
Причем вопрос о цене довольно нетривиален, особенно если мы говорим о разработке под браузер Internet Explorer 6 и 7 версий.
1. Память — тут все просто. Во всех браузерах наследование на прототипах отнимает в разы меньше памяти, чем при создании методов через конструкторы. Причем, чем больше методов и свойств у нас есть, тем больше разница. Однако, стоит помнить, что если у нас не тысяча одинаковых объектов а всего лишь один, то расходы памяти в любом случае будут небольшими, т.к. здесь стоит учитывать другие факторы.
2. Процессорное время — здесь основные тонкости связанны именно с браузерами от Microsoft.
С одной стороны, объекты, где методы и свойства создаются через конструктор — могут создаваться в разы (в некоторых случаях в десятки и сотни раз) медленнее, чем через прототип. Чем больше методов — тем медленнее. Так что если у вас в IE замирает на несколько секунд во время инициализации скрипта — есть повод копать в эту сторону.
С другой стороны, собственные методы объекта (созданные через конструктор) могут выполняется немного быстрее, чем прототипные. В случае, если позарез необходимо ускорить именно выполнение какого-то метода в этом браузере, то нужно это учесть. Имейте ввиду, ускоряется именно вызов метода (т.е. поиск его в объекте), а не его выполнение. Так что если сам метод у вас выполняется секунду, то особого увеличения быстродействия вы не заметите.
В других браузерах подобных проблем наблюдается, там время создания объектов и вызова их методов примерно одинаково для обоих подходов.
P.S. Обычно в статьях подобного рода автор предлагает некую обертку, либо пытающуюся реализовать класс-объектное наследование на базе прототипного, либо просто синтаксический сахар для прототипного наследования. Я не делаю этого намеренно, т.к. считаю, что человек, понявший смысл данной статьи, способен сам для себя написать любую обертку, и еще много интересных вещей 🙂
Как изменить класс элемента с помощью JavaScript?
«Атрибут class в основном используется для указания на класс в таблице стилей. Однако он также может использоваться JavaScript (через HTML DOM) для внесения изменений в элементы HTML с указанным классом». — w3schools.com/tags/att_standard_class.asp
Triynko 07 апр. 2011, в 18:11
element.setAttribute(name, value); Замените name на class . Замените value тем именем, которое вы дали классу, заключенным в кавычки. Это позволяет избежать необходимости удалять текущий класс и добавлять другой. Этот пример jsFiddle показывает полный рабочий код.
Alan Wells 18 май 2014, в 04:59
Для изменения класса элемента HTML с помощью onClick используйте этот код: и в разделе javascript: function addNewClass(elem) < elem.className="newClass"; >Онлайн
Iman Bahrampour 22 авг. 2017, в 04:13
@Triynko — эта ссылка на w3schools изменилась, похоже, в сентябре 2012 года. Вот эта страница на Archive.org от 12 сентября 2012 года: HTML-класс Attribute-w3schools . Вот ссылка на страницу замены на w3schools.com: HTML-класс Attribute-w3schools .
Kevin Fegan 12 окт. 2018, в 17:46
Показать ещё 2 комментария
Поделиться:
32 ответа
Лучший ответ
Современные методы HTML5 для изменения классов
Современные браузеры добавили classList, который предоставляет методы, облегчающие манипулирование классами без необходимости в библиотеке:
document.getElementById("MyElement").classList.add('MyClass'); document.getElementById("MyElement").classList.remove('MyClass'); if ( document.getElementById("MyElement").classList.contains('MyClass') ) document.getElementById("MyElement").classList.toggle('MyClass');
К сожалению, они не работают в Internet Explorer до v10, хотя есть прокладка, чтобы добавить поддержку для него в IE8 и IE9, доступной от этой страницы. Это, тем не менее, все больше и больше поддерживается.
Простое кросс-браузерное решение
Стандартный метод JavaScript для выбора элемента использует document.getElementById(«Id») , что и используется в следующих примерах: вы можете, конечно, получать элементы по-другому, и в правильной ситуации может просто использовать this вместо этого, вдаваясь в подробности, это выходит за рамки ответа.
Чтобы изменить все классы для элемента:
Чтобы заменить все существующие классы одним или несколькими новыми классами, задайте атрибут className:
document.getElementById("MyElement").className = "MyClass";
(Вы можете использовать список с разделителями пробелов для применения нескольких классов.)
Чтобы добавить дополнительный класс к элементу:
Чтобы добавить класс к элементу, не удаляя/не влияя на существующие значения, добавьте пробел и новое имя класса, например:
document.getElementById("MyElement").className += " MyClass";
Чтобы удалить класс из элемента:
Чтобы удалить один класс для элемента, не затрагивая другие потенциальные классы, требуется простая замена регулярного выражения:
document.getElementById("MyElement").className = document.getElementById("MyElement").className.replace ( /(?:^|\s)MyClass(?!\S)/g , '' ) /* Code wrapped for readability - above is all one statement */
Объяснение этого регулярного выражения выглядит следующим образом:
(?:^|\s) # Match the start of the string, or any single whitespace character MyClass # The literal text for the classname to remove (?!\S) # Negative lookahead to verify the above is the whole classname # Ensures there is no non-space character following # (i.e. must be end of string or a space)
Флаг g сообщает, что замена повторяется по мере необходимости, если имя класса добавлено несколько раз.
Чтобы проверить, уже ли класс применяется к элементу:
Такое же регулярное выражение, используемое выше для удаления класса, также может использоваться в качестве проверки того, существует ли конкретный класс:
if ( document.getElementById("MyElement").className.match(/(?:^|\s)MyClass(?!\S)/) )
Присвоение этих действий событиям onclick:
Хотя можно писать JavaScript непосредственно внутри атрибутов событий HTML (например, onclick=»this.className+=’ MyClass'» ), это поведение не рекомендуется. В более крупных приложениях более удобный код достигается путем разделения HTML-разметки на логику взаимодействия JavaScript.
Первым шагом к достижению этого является создание функции и вызов функции в атрибуте onclick, например:
.
(Не требуется иметь этот код в тегах скриптов, это просто для краткости примера, и в том числе JavaScript в отдельном файле может быть более подходящим.)
Второй шаг — переместить событие onclick из HTML и в JavaScript, например, с помощью addEventListener
function changeClass() < // Code examples from above >window.onload = function() .
(Обратите внимание, что часть window.onload требуется, чтобы содержимое этой функции выполнялось после завершения загрузки HTML — без этого, MyElement может не существовать при вызове кода JavaScript, чтобы строка не удалась.)
Структуры JavaScript и библиотеки
Вышеприведенный код находится в стандартном JavaScript, однако распространенной практикой является использование инфраструктуры или библиотеки для упрощения общих задач, а также использование исправленных ошибок и крайних случаев, которые вы, возможно, не придумали при написании кода.
В то время как некоторые люди считают излишним добавить среду размером 50 КБ для простого изменения класса, если вы выполняете какую-либо значительную работу JavaScript или что-то, что может иметь необычное поведение в кросс-браузере, стоит рассмотреть.
(Очень грубо, библиотека представляет собой набор инструментов, предназначенных для конкретной задачи, в то время как структура обычно содержит несколько библиотек и выполняет полный набор обязанностей.)
Приведенные выше примеры были воспроизведены ниже, используя jQuery, возможно, наиболее часто используемую библиотеку JavaScript (хотя есть и другие, заслуживающие изучения).
(Обратите внимание, что $ здесь является объектом jQuery.)
Изменение классов с помощью jQuery:
$('#MyElement').addClass('MyClass'); $('#MyElement').removeClass('MyClass'); if ( $('#MyElement').hasClass('MyClass') )
Кроме того, jQuery предоставляет ярлык для добавления класса, если он не применяется, или удаление класса, который делает:
$('#MyElement').toggleClass('MyClass');
Назначение функции событию клика с помощью jQuery:
$('#MyElement').click(changeClass);
или, не требуя идентификатора:
$(':button:contains(My Button)').click(changeClass);
Peter Boughton 12 окт. 2008, в 22:27
Поделиться
Отличный ответ Питер. Один вопрос . почему лучше делать с JQuery, чем с Javascript? JQuery — это здорово, но если это все, что вам нужно сделать — что оправдывает включение всего массива JQuery вместо нескольких строк JavaScript?
mattstuehler 15 май 2011, в 15:32
@mattstuehler 1) фраза «еще лучше х» часто означает «еще лучше (вы можете) х». 2) Чтобы понять суть дела, jQuery разработан для помощи в доступе к DOM и манипулировании им, и очень часто, если вам нужно делать подобные вещи, когда вам придется делать это повсеместно.
Barry 24 май 2011, в 16:46
Имейте в виду, что в случае добавления имени класса перед именем класса, которое вы хотите добавить, ДОЛЖНО быть пустое пространство.
didxga 13 июль 2011, в 07:07
если вы добавляете класс, удаляется ли предыдущий класс, или вам придется делать это вручную?
Gabor Magyar 27 июль 2011, в 19:22
Предыдущие занятия останутся. Вам придется удалить другие классы вручную, если вы хотите это сделать.
Micharch54 03 авг. 2011, в 19:15
Одна ошибка в этом решении: когда вы нажимаете на кнопку несколько раз, она добавляет класс «MyClass» к элементу несколько раз, а не проверяет, существует ли он уже. Таким образом, вы можете получить атрибут класса HTML, который выглядит примерно так: class=»button MyClass MyClass MyClass»
Web_Designer 13 сен. 2011, в 16:28
Я думаю, что решение этого было бы: if ( ! document.getElementById(«MyElement»).className.match(/(?:^|\s)MyClass(?!\S)/) ) или с помощью jQuery: if ( ! $j(‘#MyElement’).hasClass(‘MyClass’) ) (хотя я подозреваю, что addClass не будет добавлять дубликаты?)
Peter Boughton 14 сен. 2011, в 00:14
Если вы пытаетесь удалить класс «myClass» и у вас есть класс «prefix-myClass», то регулярное выражение, которое вы указали выше для удаления класса, оставит вас с «prefix-» в вашем className: O
jinglesthula 15 сен. 2011, в 05:26
Вау, три года и 183 возражения, и никто не заметил этого до сих пор. Благодаря jinglesthula, я исправил регулярное выражение, чтобы оно не могло некорректно удалять части имен классов. // Полагаю, это хороший пример того, почему стоит использовать Framework (например, jQuery) — подобные ошибки быстрее обнаруживаются и исправляются и не требуют изменений в обычном коде.
Peter Boughton 15 сен. 2011, в 17:09
Я сделал несколько небольших правок, чтобы улучшить ответ — надеюсь, люди согласятся, что они улучшения? Если есть что-то еще, что я пропустил, дайте мне знать (или просто отредактируйте ответ самостоятельно).
Peter Boughton 15 сен. 2011, в 17:24
@PeterBoughton Не уверен, если вы считаете, что это улучшение, но мне нравится: (‘ ‘+document.getElementById(«MyElement»).className+’ ‘).replace( ‘ ‘+MyClass+’ ‘ , » )
Paulpro 21 дек. 2011, в 09:38
@ PaulP.RO вы сворачиваете окружающие пробелы и регулярное выражение в ноль, что объединит строки имен окружающих классов (если таковые имеются) вместе. Я думаю, что elem.className = elem.className.replace (‘+ classname +’ ‘,’ ‘) — это то, что мы хотим. Спасибо!
Cris Stringfellow 04 июнь 2012, в 10:40
Я использую аналогичные методы, но с регулярным выражением ag (general). причина как раз и заключается в проблеме, описанной @Web_Designer выше: если пользователь щелкает несколько раз, вы получаете несколько экземпляров одного и того же класса, и все экземпляры должны быть удалены для удаления класса.
Christophe 15 авг. 2012, в 00:06
Импорт целой библиотеки для выполнения одной простой задачи — это именно то, почему в Интернете так много дрейфа. Поэтому я не согласен с тем, что использование JQuery является «лучшим» решением.
b1nary.atr0phy 03 окт. 2012, в 01:01
Я только что сделал существенные дополнения к этому ответу — основная масса такая же, но я был бы признателен за отзыв о том, есть ли ошибки или места для дальнейшего улучшения.
Peter Boughton 12 дек. 2012, в 18:24
При создании регулярного выражения с переменной для имени класса мне пришлось искать синтаксис: var regExp = new RegExp(‘(?:^|\\s)’ + classToAddOrRemove + ‘(?!\\S)’, ‘g’);
Sprockincat 30 янв. 2013, в 16:44
Здесь не хватает одной важной детали — различия между фреймворком и библиотекой. jQuery — это библиотека, а не фреймворк. Просто для безопасности примера, например, фреймворк будет angularJs.
brunoais 08 апр. 2013, в 07:23
(Вы можете указать разделенный пробелами список элементов.) Вы должны иметь в виду список классов для назначения, а не элементы для выбора.
DrJonOsterman 02 авг. 2013, в 21:23
Действительно отличный ответ, который полностью не отвечает на вопрос ОП: как вы меняете значение существующего класса?
Peter Flynn 06 янв. 2014, в 10:13
Если он не отвечает на вопрос ОП, почему они отметили это как решение? Ваш вопрос, кажется, другой (возможно, вы хотите изменить свойства / объявления CSS, связанные с именем класса? Не уверен, что браузеры позволяют это) — почему бы не уточнить, что вам нужно, в новом вопросе, который объясняет, что именно вы хотите / пытаетесь добиться?
Peter Boughton 06 янв. 2014, в 16:37
Что касается точки «Добавить дополнительный класс к элементу» (2-й в списке), я считаю, что лучше всего взять текущий el.className , строку concat с » class-to-add» и, наконец, вызвать .trim() в строке перед обновлением целевого элемента. Тем не менее, я не думаю, что CSS действительно заботится о \s , отдельные классы элемента в любом случае определяются разделенным пробелами списком.
ZaLiTHkA 22 янв. 2015, в 09:26
хех: var a=[];e.className=((a=e.className.split(» «)).splice(a.indexOf(oldClass),1,newClass)&&a).join(» «).trim(); : D
Duncan 06 март 2015, в 19:04
Можете ли вы переставить вещи? По моему мнению classList с шимом для поддержки браузера должен быть наверху.
Marcel Burkhard 02 июнь 2015, в 13:04
было бы здорово, если бы что-то вроде этого работало тоже, чтобы удалить имя класса: document.getElementById(«MyElement»).className -= » MyClass»;
Nick Humphrey 29 июль 2015, в 08:53
поэтому ответом будет просто $ (‘# MyElement’). toggleClass (‘MyClass’); спасибо, у меня работает
shady sherif 05 окт. 2015, в 14:15
Привет. Мне было интересно, что вы скажете об использовании границ слов вместо (?: \ S) в начале и (?! \ S) в конце вашего регулярного выражения. Комментарии? document.getElementById(«MyElement»).className.replace( /\bMyClass\b/g , » ) . Кажется, у меня никогда не было причин использовать множество сокращений для регулярных выражений, но кажется, что это тоже может сработать. Хотя \ b имеет ограничения на то, что он считает символом слова, и вы никогда не знаете, что кто-то может использовать в качестве имени класса.
Anthony Rutledge 07 нояб. 2015, в 09:57
\ b совпадает с дефисом
Peter Boughton 07 нояб. 2015, в 14:23
Существуют более быстрые методы, чем удаление класса с использованием RegExp, особенно если вы собираетесь превратить его в полизаполнение. См. Jsperf.com/removeclass-methods
thdoan 24 нояб. 2015, в 08:48
Код в « Чтобы удалить класс из элемента»: и « Проверить», если класс уже применен к элементу: разделы отсутствуют ; с конца строки. Для этого есть причина? Было бы также неплохо, если бы в разделе «Удалить класс из элемента» была заметка, указывающая, что происходит с пробелами. В противном случае это очень хороший ответ.
Trisped 21 март 2016, в 18:38
Я бы предпочел (^start(\s+start)*\s*)|((?:\s+)start(?!\S+)) удалить слово start из списка, разделенного пробелами.
Andreas Dyballa 24 май 2016, в 12:18
Angular: angular.element (document.getElementById («MyElement»)). RemoveClass («имя-класса»); angular.element (document.getElementById ( «MyElement»)). addClass ( «имя-класса»)
Zymotik 02 июнь 2016, в 13:59
Привет, это очень хороший ответ. Я хочу изменить класс при некоторых условиях при загрузке html, а не о событии нажатия кнопки. пожалуйста, обратитесь к этому вопросу: stackoverflow.com/questions/38028756/…
pankaj 25 июнь 2016, в 14:13
Ваш метод удаления класса почти позаботится об удалении лишних пробелов, которые могут появиться после удаления элементов. Но может случиться так, что результат начнется с пробела. Если вы добавите .trim (), ваш метод удаления класса не будет содержать лишних пробелов.
rosell.dk 17 окт. 2016, в 20:42
Кроме того, вы можете безопасно удалить «?:» В начале RegEx так, чтобы он читал: new RegExp («(\\ s | ^)« + className + »(?! \\ S) », «г»)
rosell.dk 17 окт. 2016, в 20:45
Я создал обширный тест альтернатив для удаления имени класса. Ваше решение в тесте. Тест доступен здесь
rosell.dk 17 окт. 2016, в 20:48
@Zymotik Вы можете добавить другой отдельный ответ вместо редактирования чужого ответа. Это полезный фрагмент, но это должен быть собственный ответ.
TankorSmash 01 март 2017, в 17:32
Я использую github.com/toddmotto/apollo
Fahmi 16 март 2017, в 10:20
@PoornaSenaniGamage — Ваше редактирование говорит, что причиной было «улучшенное форматирование», интересно, как вы могли бы считать это «улучшением»? Это было хорошо до вашего редактирования, и хорошо, это просто «хорошо» после вашего редактирования. Вы просто заменили один стандартный метод форматирования кода, который предпочитает (ну, по крайней мере, выбрал) первоначальный автор, на другой стандартный метод форматирования кода, который вы предпочитаете. То, что ваше редактирование было одобрено, для меня загадка, так как мне кажется, что это неуместное изменение. Договорились, что это не крайне значимый вопрос. Кто-то еще, пожалуйста, дайте мне знать, если я вне линии.
Kevin Fegan 12 окт. 2018, в 19:27
Показать ещё 36 комментариев
Вы также можете просто сделать:
document.getElementById('id').classList.add('class'); document.getElementById('id').classList.remove('class');
И для переключения класса (удалите, если существует еще его добавить):
document.getElementById('id').classList.toggle('class');
Tyilo 05 авг. 2011, в 19:29
Поделиться
Я считаю, что это зависит от HTML5.
John 27 окт. 2011, в 17:16
Вам понадобится classList Eli Grey’s classList .
ELLIOTTCABLE 14 нояб. 2011, в 14:34
Стоит отметить, что это не работает в версиях IE менее 8 ..
Lloyd 24 янв. 2012, в 10:24
Mozilla Developer Network заявляет, что изначально не работает в Internet Explorer меньше 10. Я считаю, что это утверждение соответствует действительности в моем тестировании. По-видимому, для Internet Explorer 8-9 требуется прокладка Eli Grey . К сожалению, я не смог найти его на своем сайте (даже с поиском). Подкладка доступна по ссылке Mozilla.
doubleJ 29 окт. 2012, в 04:13
Функция не поддерживается несколько часов назад (добавлена в IE10 и Android 3)
andreszs 02 авг. 2014, в 23:13
Ajedi32 15 июнь 2015, в 15:56
atow «classList» частично поддерживается в IE10 +; нет поддержки Opera Mini; еще полная поддержка в остальных стандартных браузерах: caniuse.com/#search=classlist
Nick Humphrey 29 июль 2015, в 08:47
Я думаю, что подкладка Eli Grey можно найти здесь .
Wilf 06 сен. 2015, в 16:54
я люблю, как мало ты заставил меня читать
Squirrl 27 март 2016, в 18:43
Показать ещё 7 комментариев
В одном из моих старых проектов, которые не использовали jQuery, я создал следующие функции для добавления, удаления и проверки, если элемент имеет класс:
function hasClass(ele, cls) < return ele.className.match(new RegExp('(\\s|^)' + cls + '(\\s|$)')); >function addClass(ele, cls) < if (!hasClass(ele, cls)) ele.className += " " + cls; >function removeClass(ele, cls) < if (hasClass(ele, cls)) < var reg = new RegExp('(\\s|^)' + cls + '(\\s|$)'); ele.className = ele.className.replace(reg, ' '); >>
Так, например, если я хочу, чтобы onclick добавил некоторый класс, я могу использовать эту кнопку:
.
Теперь наверняка было бы лучше использовать jQuery.
Andrew Orsich 28 май 2011, в 08:13
Поделиться
Это отлично подходит для случаев, когда ваш клиент не позволяет вам использовать jQuery. (Потому что вы в конечном итоге почти построили свою собственную библиотеку.)
Mike 24 июль 2013, в 05:16
@Mike Если клиент не позволяет вам использовать jQuery, не могли бы вы просто пройтись и перестроить только те функции, которые вам нужны, в вашу собственную библиотеку?
kfrncs 18 нояб. 2013, в 05:04
@kfrncs Потому что мне вообще не нужен такой большой фреймворк. Для проекта, о котором я думал, единственными необходимыми мне функциями были 3 функции имени класса (has, add, remove) и функции cookie (has, add, remove). Все остальное было либо на заказ, либо изначально хорошо поддерживалось. Таким образом, все вместе было всего 150 строк до минимизации, включая комментарии.
Mike 18 нояб. 2013, в 19:17
Чувак, его 4 утра и большое спасибо. Vanilla JS — это то, что мы используем в моем проекте, и это спасло жизнь.
LessQuesar 16 дек. 2015, в 09:25
Это мое любимое решение для этого. Я использую это везде. Я считаю, что это самый элегантный способ добавления и удаления классов, когда в вашем проекте еще нет другого способа сделать это.
WebWanderer 17 окт. 2017, в 21:36
Следует отметить, что после использования addClass и removeClass addClass и того же элемента, className элемента будет содержать дополнительный пробел. Модифицирующая строка removeClass объекта removeClass должна быть обновлена до ele.className = ele.className.replace(reg, ‘ ‘).trim().replace(/\s<2,>/g, ‘ ‘); , Это удаляет конечные пробелы, оставшиеся после, и объединяет несколько пробелов в один пробел в className.2,>
WebWanderer 17 окт. 2017, в 21:45
Показать ещё 4 комментария
Вы можете использовать node.className так:
document.getElementById('foo').className = 'bar';
Это должно работать в IE5.5 и выше в соответствии с PPK.
Eric Wendelin 12 окт. 2008, в 21:00
Поделиться
это переписало бы все остальные классы объекта . так что это не так просто.
Eric Sebasta 09 окт. 2015, в 20:33
Ух ты, удивленный, здесь так много ответов о переполнении.
Travis J 05 янв. 2012, в 20:13
Поделиться
да, но ненавязчивый javascript — лучшая практика
Lloyd 24 янв. 2012, в 10:27
Я бы сказал, ненавязчивый javascript — ужасная практика для написания примера кода .
Gabe 13 апр. 2012, в 14:50
Я бы не согласился, потому что я думаю, что пример кода должен служить хорошим примером.
thomasrutter 29 март 2015, в 23:36
Хороший пример должен наставлять и зажигать воображение одновременно. Это не должно заменять мысль, но вдохновлять ее.
Anthony Rutledge 31 окт. 2015, в 04:01
Другие ответы не являются чрезмерными, они также сохраняют существующие классы на элементе.
gcampbell 04 июнь 2016, в 15:04
Показать ещё 3 комментария
Использование чистого кода JavaScript:
function hasClass(ele, cls) < return ele.className.match(new RegExp('(\\s|^)' + cls + '(\\s|$)')); >function addClass(ele, cls) < if (!this.hasClass(ele, cls)) ele.className += " " + cls; >function removeClass(ele, cls) < if (hasClass(ele, cls)) < var reg = new RegExp('(\\s|^)' + cls + '(\\s|$)'); ele.className = ele.className.replace(reg, ' '); >> function replaceClass(ele, oldClass, newClass) < if(hasClass(ele, oldClass))< removeClass(ele, oldClass); addClass(ele, newClass); >return; > function toggleClass(ele, cls1, cls2)< if(hasClass(ele, cls1))< replaceClass(ele, cls1, cls2); >else if(hasClass(ele, cls2))< replaceClass(ele, cls2, cls1); >else < addClass(ele, cls1); >>
PseudoNinja 20 сен. 2011, в 16:02
Поделиться
так же, как здесь: snipplr.com/view/3561/addclass-removeclass-hasclass
Jaider 20 апр. 2012, в 20:40
Это работает для меня:
function setCSS(eleID)
Gopal Krishna Ranjan 08 дек. 2011, в 10:14
Поделиться
Отличный ответ! Осталось добавить: установить для каждого CSS-имени класса селектор, чтобы указать стиль для группы элементов класса.
Roman Polen. 08 май 2012, в 11:19
Это работает для меня на FF, но когда я попытался использовать el.className = «newStyle»; это не сработало, почему?
Lukasz ‘Severiaan’ Grela 19 июль 2012, в 12:29
Вы можете использовать el.setAttribute(‘class’, newClass) или лучше el.className = newClass . Но не el.setAttribute(‘className’, newClass) .
Oriol 11 фев. 2015, в 15:54
Показать ещё 1 комментарий
Также вы можете расширить объект HTMLElement, чтобы добавить методы для добавления, удаления, переключения и проверки классов (gist):
HTMLElement = typeof(HTMLElement) != 'undefiend' ? HTMLElement : Element; HTMLElement.prototype.addClass = function(string) < if (!(string instanceof Array)) < string = string.split(' '); >for(var i = 0, len = string.length; i < len; ++i) < if (string[i] && !new RegExp('(\\s+|^)' + string[i] + '(\\s+|$)').test(this.className)) < this.className = this.className.trim() + ' ' + string[i]; >> > HTMLElement.prototype.removeClass = function(string) < if (!(string instanceof Array)) < string = string.split(' '); >for(var i = 0, len = string.length; i < len; ++i) < this.className = this.className.replace(new RegExp('(\\s+|^)' + string[i] + '(\\s+|$)'), ' ').trim(); >> HTMLElement.prototype.toggleClass = function(string) < if (string) < if (new RegExp('(\\s+|^)' + string + '(\\s+|$)').test(this.className)) < this.className = this.className.replace(new RegExp('(\\s+|^)' + string + '(\\s+|$)'), ' ').trim(); >else < this.className = this.className.trim() + ' ' + string; >> > HTMLElement.prototype.hasClass = function(string)
И тогда просто используйте вот так (по щелчку добавит или удалит класс):
document.getElementById('yourElementId').onclick = function()
moka 11 апр. 2013, в 10:33
Поделиться
это немного многословно . вот очень краткое решение . jsfiddle.net/jdniki/UaT3P
zero_cool 18 авг. 2014, в 19:28
Извините @Jackson_Sandland, но вы полностью упустили из виду то, что вообще не нужно использовать jQuery.
moka 21 авг. 2014, в 08:36
Чтобы добавить информацию из другой популярной структуры, Google Closures, см. класс dom/classes:
goog.dom.classes.add(element, var_args) goog.dom.classes.addRemove(element, classesToRemove, classesToAdd) goog.dom.classes.remove(element, var_args)
Один из вариантов выбора элемента — goog.dom.query с селектором CSS3:
var myElement = goog.dom.query("#MyElement")[0];
Ben Flynn 26 нояб. 2011, в 21:59
Поделиться
Несколько небольших заметок и настроек по предыдущим регулярным выражениям:
Вы хотите сделать это глобально, если список классов имеет имя класса более одного раза. И вы, вероятно, захотите снять пробелы с концов списка классов и преобразовать несколько пробелов в одно пространство, чтобы не допустить пробелов. Ни одна из этих проблем не должна быть проблемой, если только один код, совпадающий с именами классов, использует регулярное выражение ниже и удаляет имя перед его добавлением. Но. Ну, кто знает, кто может смириться с списком имен классов.
Это регулярное выражение нечувствительно к регистру, так что ошибки в именах классов могут отображаться до того, как код будет использоваться в браузере, который не заботится о случае в именах классов.
var s = "testing one four one two"; var cls = "one"; var rg = new RegExp("(^|\\s+)" + cls + "(\\s+|$)", 'ig'); alert("[" + s.replace(rg, ' ') + "]"); var cls = "test"; var rg = new RegExp("(^|\\s+)" + cls + "(\\s+|$)", 'ig'); alert("[" + s.replace(rg, ' ') + "]"); var cls = "testing"; var rg = new RegExp("(^|\\s+)" + cls + "(\\s+|$)", 'ig'); alert("[" + s.replace(rg, ' ') + "]"); var cls = "tWo"; var rg = new RegExp("(^|\\s+)" + cls + "(\\s+|$)", 'ig'); alert("[" + s.replace(rg, ' ') + "]");
Alex Robinson 02 май 2012, в 05:15
Поделиться
Измените класс CSS класса с помощью JavaScript в ASP.NET:
Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load If Not Page.IsPostBack Then lbSave.Attributes.Add("onmouseover", "this.className = 'LinkButtonStyle1'") lbSave.Attributes.Add("onmouseout", "this.className = 'LinkButtonStyle'") lbCancel.Attributes.Add("onmouseover", "this.className = 'LinkButtonStyle1'") lbCancel.Attributes.Add("onmouseout", "this.className = 'LinkButtonStyle'") End If End Sub
Hiren Kansara 28 май 2011, в 07:59
Поделиться
Я бы использовал jQuery и написал что-то вроде этого:
jQuery(function($) < $("#some-element").click(function() < $(this).toggleClass("clicked"); >); >);
Этот код добавляет функцию, вызываемую при нажатии элемента id some-element. Функция добавляет щелчок к атрибуту класса элемента, если он еще не является частью его, и удаляет его, если он там.
Да, вам нужно добавить ссылку на библиотеку jQuery на своей странице, чтобы использовать этот код, но по крайней мере вы можете быть уверены, что большинство функций в библиотеке будут работать практически во всех современных браузерах, и это сэкономит вы время внедряете свой собственный код, чтобы сделать то же самое.
shingokko 05 май 2012, в 03:50
Поделиться
Вы должны написать jQuery только один раз. jQuery(function($) < >); делает $ доступным внутри функции во всех случаях.
ThiefMaster 13 июнь 2012, в 14:38
document.getElementById("MyElement").className = document.getElementById("MyElement").className.replace(/\bMyClass\b/','')
document.getElementById("MyElement").className = document.getElementById("MyElement").className.replace('/\bMyClass\b/','');
Eric Bailey 08 дек. 2009, в 22:49
Поделиться
Неправильно. RegEx ограничен косыми чертами. Добавление кавычек приводит к сбою в IE, возвращая строку класса, который вы пытаетесь удалить, а не фактически удаляя класс.
Dylan 14 авг. 2011, в 21:46
Здесь toggleClass toggle/add/remove класс для элемента:
// If newState is provided add/remove theClass accordingly, otherwise toggle theClass function toggleClass(elem, theClass, newState) < var matchRegExp = new RegExp('(?:^|\\s)' + theClass + '(?!\\S)', 'g'); var add=(arguments.length>2 ? newState : (elem.className.match(matchRegExp) == null)); elem.className=elem.className.replace(matchRegExp, ''); // clear all if (add) elem.className += ' ' + theClass; >
также см. мой ответ здесь для динамического создания нового класса
kofifus 28 окт. 2015, в 07:30
Поделиться
Здесь моя версия, полностью работающая:
function addHTMLClass(item, classname) < var obj = item if (typeof item=="string") < obj = document.getElementById(item) >obj.className += " " + classname > function removeHTMLClass(item, classname) < var obj = item if (typeof item=="string") < obj = document.getElementById(item) >var classes = ""+obj.className while (classes.indexOf(classname)>-1) < classes = classes.replace (classname, "") >obj.className = classes >
alfred 17 окт. 2012, в 12:36
Поделиться
Это нарушит класс foobar если вы попытаетесь удалить класс foo . JS в атрибутах встроенного обработчика событий был нарушен перед редактированием. 4-летний принятый ответ намного лучше.
Quentin 17 окт. 2012, в 12:25
спасибо, я исправил это (не проблема префикса). это старый принятый ответ, в котором есть ошибка с регулярным выражением.
alfred 17 окт. 2012, в 12:27
В коде все еще есть проблема с foobar . Смотрите тест здесь
rosell.dk 17 окт. 2016, в 12:11
Показать ещё 1 комментарий
Изменить класс элемента в ванильном JavaScript с поддержкой IE6
Вы можете попробовать использовать свойство node attributes , чтобы поддерживать совместимость со старыми браузерами даже в IE6:
function getClassNode(element) < for (var i = element.attributes.length; i--;) if (element.attributes[i].nodeName === 'class') return element.attributes[i]; >function removeClass(classNode, className) < var index, classList = classNode.value.split(' '); if ((index = classList.indexOf(className)) >-1) < classList.splice(index, 1); classNode.value = classList.join(' '); >> function hasClass(classNode, className) < return classNode.value.indexOf(className) >-1; > function addClass(classNode, className) < if (!hasClass(classNode, className)) classNode.value += ' ' + className; >document.getElementById('message').addEventListener('click', function() < var classNode = getClassNode(this); var className = hasClass(classNode, 'red') && 'blue' || 'red'; removeClass(classNode, 'red'); removeClass(classNode, 'blue'); addClass(classNode, className); >)
.red < color: red; >.red:before < content: 'I am red! '; >.red:after < content: ' again'; >.blue < color: blue; >.blue:before
Click me
Eugene Tiurin 04 нояб. 2015, в 17:57
Поделиться
Я использую следующие ванильные функции JavaScript в своем коде. Они используют регулярные выражения и indexOf но не требуют цитирования специальных символов в регулярных выражениях.
function addClass(el, cn) < var c0 = (" " + el.className + " ").replace(/\s+/g, " "), c1 = (" " + cn + " ").replace(/\s+/g, " "); if (c0.indexOf(c1) < 0) < el.className = (c0 + c1).replace(/\s+/g, " ").replace(/^ | $/g, ""); >> function delClass(el, cn) < var c0 = (" " + el.className + " ").replace(/\s+/g, " "), c1 = (" " + cn + " ").replace(/\s+/g, " "); if (c0.indexOf(c1) >= 0) < el.className = c0.replace(c1, " ").replace(/\s+/g, " ").replace(/^ | $/g, ""); >>
Вы также можете использовать element.classList в современных браузерах.
Salman A 18 янв. 2014, в 10:22
Поделиться
Это можно сделать.
.red a < color:red; >.black a
function changeClass()
Fahad Uddin 03 июль 2016, в 15:38
Поделиться
Если вы хотите удалить класс, вы можете сделать:
ELEMENT.classList.remove("CLASS_NAME");
Чтобы добавить класс:
ELEMENT.classList.add('CLASS_NAME');
ferralucho 14 нояб. 2017, в 01:21
Поделиться
При нажатии кнопки вы можете попробовать:
заменить (oldClass, newClass)
Заменяет существующий класс новым классом.
var myDiv = document.getElementById('myElement'); myDiv.classList.contains('myCssClass'); myDiv.classList.replace("myCssClass", "myCssClass_new");
Kurkula 27 нояб. 2017, в 19:19
Поделиться
После длительного поиска я нашел лучшее решение для управления классами без document.getElementById()
var els = [].slice.apply(document.getElementsByClassName("no-js")); for (var i = 0; i < els.length; i++) < els[i].className = els[i].className.replace(/ *\bno-js\b/g, "js"); >
eapo 09 дек. 2016, в 23:56
Поделиться
Это хорошо, что я нашел полезным.
function classChangeFn() < document.getElementById("MyElement").className = ""; >window.onload = function()
Mohammed Javed 11 апр. 2016, в 12:26
Поделиться
Да, есть много способов сделать это. В синтаксисе ES6 мы можем легко достичь. Используйте этот код для переключения добавить и удалить класс.
const tabs=document.querySelectorAll('.menu li'); for(let tab of tabs) < tab.onclick=function()< let activetab=document.querySelectorAll('li.active'); activetab[0].classList.remove('active') tab.classList.add('active'); >>
body < padding:20px; font-family:sans-serif; >ul < margin:20px 0; list-style:none; >li < background:#dfdfdf; padding:10px; margin:6px 0; cursor:pointer; >li.active
Danish Khan 06 фев. 2019, в 14:28
Поделиться
API classList DOM:
Очень удобным способом добавления и удаления классов является DOM API classList . Этот API позволяет нам выбирать все классы определенного элемента DOM, чтобы изменить список, используя javascript. Например:
const el = document.getElementById("main"); console.log(el.classList);
В журнале мы можем наблюдать, что мы получаем объект не только с классами элемента, но также со многими вспомогательными методами и свойствами. Этот объект наследует от интерфейса DOMTokenList, интерфейса, который используется в DOM для представления набора разделенных пробелами токенов (например, классов).
Пример:
const el = document.getElementById('container'); function addClass () < el.classList.add('newclass'); >function replaceClass () < el.classList.replace('foo', 'newFoo'); >function removeClass ()
button < margin: 20px; >.foo < color: red; >.newFoo < color: blue; >.bar < background-color:powderblue; >.newclass
"Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas
Willem van der Veen 21 сен. 2018, в 10:44
Поделиться
Рассмотрим кнопку, которая будет элементом html. Пусть класс удален, чтобы быть button1, и добавляемый класс — button2, а id — кнопка.
function ClassChange ()
user8158111 20 фев. 2018, в 18:26
Поделиться
ОК, я думаю, что в этом случае вы должны использовать jQuery или написать свои собственные методы в чистом javascript, но я предпочитаю добавлять свои собственные методы, а не вводить все jQuery в мое приложение, если мне это не нужно по другим причинам.
Я хотел бы сделать что-то вроде ниже в качестве методов для моей инфраструктуры javascript, чтобы добавить несколько функциональных возможностей, которые обрабатывают добавление классов, удаление классов и т.д., аналогично jQuery, это полностью поддерживается в IE9 +, также мой код написан на ES6, поэтому вам нужно убедиться, что ваш браузер поддерживает его или вы используете что-то вроде babel, иначе вам нужно использовать синтаксис ES5 в вашем коде, также таким образом, мы находим элемент через идентификатор, что означает, что элементу должен быть выбран идентификатор
//simple javascript utils for class management in ES6 var classUtil = < addClass: (id, cl) =>< document.getElementById(id).classList.add(cl); >, removeClass: (id, cl) => < document.getElementById(id).classList.remove(cl); >, hasClass: (id, cl) => < return document.getElementById(id).classList.contains(cl); >, toggleClass: (id, cl) => < document.getElementById(id).classList.toggle(cl); >>
и вы можете просто вызвать их использование, как показано ниже, представьте, что ваш элемент имеет идентификатор «id» и класс «class», убедитесь, что вы передаете их как строку, вы можете использовать утилиту, как показано ниже:
classUtil.addClass('myId', 'myClass'); classUtil.removeClass('myId', 'myClass'); classUtil.hasClass('myId', 'myClass'); classUtil.toggleClass('myId', 'myClass');
