Добро пожаловать в мир современного фронтенда и базы программирования на канале Kobezzza. Здесь вы найдете интересные и актуальные материалы, подготовленные автором Андреем Кобецем, ведущим разработчиком с 20-летним опытом в области программирования. Андрей делится своими знаниями и опытом, помогая аудитории расширять свои навыки и углублять знания в сфере IT. На канале Kobezzza вы не найдете рекламы, здесь ценится качественный контент и информация, которая действительно будет полезна каждому, кто интересуется программированием. Если у вас есть вопросы или предложения о сотрудничестве, обращайтесь к @kobezzza. Присоединяйтесь к нам и станьте частью сообщества, где развитие и обмен знаниями - важнейшие ценности. Добро пожаловать на канал Kobezzza!
14 Feb, 12:36
14 Feb, 09:43
14 Feb, 09:10
13 Feb, 09:54
_
.const s = symbolGenerator();
console.log(s1.foo);
// ...
console.log(s1.bar);
const foo = Symbol('foo');
const bar = Symbol('bar');
console.log(foo);
// ...
console.log(bar);
13 Feb, 09:54
const myObject = {a: 42};
const proxy = new Proxy(myObject, {
// Перегружаем чтение свойства объекта
get(target, key) {
// Тут пишем нашу логику
}
})
11 Feb, 12:21
10 Feb, 11:15
07 Feb, 08:22
06 Feb, 15:21
attr
использовался исключительно с псевдо-элементами для генерации доп. разметки, чаще в кейсах различных тултипов.<style>
.tooltip::after {
content: attr(data-tooltip);
display: none;
background-color: yellow;
padding: 5px;
border: 1px solid black;
position: absolute;
margin-top: 10px;
}
.tooltip:hover::after {
display: block;
}
</style>
<div class="tooltip" data-tooltip="Это подсказка!">Наведи на меня</div>
<style>
[data-color] {
color: attr(data-color type(<color>), red);
}
</style>
<div data-color="blue">Какой-то текст</div>
data-color
с помощью новой функции type
как значение свойства color
.attr
мы задаем значение по умолчанию.06 Feb, 09:09
31 Jan, 07:40
30 Jan, 09:13
29 Jan, 11:21
29 Jan, 08:18
28 Jan, 13:58
28 Jan, 13:58
24 Jan, 10:01
24 Jan, 10:00
22 Jan, 08:40
21 Jan, 10:26
20 Jan, 10:09
17 Jan, 08:11
RegExp.escape('foo.bar') === "\\x66oo\\.bar"
f
преобразовался в форму с указанием кода символа (это нужно, чтобы конкатенация таких экранированных строк всегда работала как ожидается). Ну и универсальный символьный класс .
также был экранирован.// true, ведь . ознает любой символ кроме перевода строки
new RegExp(['foo', 'bar', 'bla'].join('.')).test('foo$bar#bla');
// false, все символы распознаются "как есть"
new RegExp(RegExp.escape(['foo', 'bar', 'bla'].join('.'))).test('foo$bar#bla');
// Позиция найдена, ведь . ознает любой символ кроме перевода строки
'helloworld'.search('.');
// Позиция не найдена
'helloworld'.search(RegExp.escape('.'));
17 Jan, 07:50
16 Jan, 09:45
13 Jan, 12:06
export const storage = (() => {
const db = indexedDB.open('myApp', 1);
db.addEventListener('upgradeneeded', (e) => {
const db = e.target.result;
// Создаем в БД 2 хранилища
if (e.oldVersion === 0) {
// Говорим, что ИД должен браться из поля _id
db.createObjectStore('users', {keyPath: '_id'});
db.createObjectStore('images');
}
});
return result(db);
})();
// Небольшой хелпер для удобства работы с Promise
export function result(target) {
return new Promise((resolve, reject) => {
const success = (e) => {
resolve(e.target.result);
target.removeEventListener('error', error);
}
const error = (e) => {
reject(e.target.error);
target.removeEventListener('success', success);
};
target.addEventListener('success', success, {once: true});
target.addEventListener('error', error, {once: true});
})
}
import * as db from './core/storage';
async function saveSomeData() {
const
imageName = 'image.jpeg',
image = await (await fetch(`//example.com/${imageName}`)).blob();
// Открываем транзакцию на запись в 2 хранилища БД
const t = (await db.storage).transaction(['users', 'images'], 'readwrite');
// Просто передаем объект JS (причем ключом же является одно из его же поле _id)
t.objectStore('users').put({_id: 0, name: 'Bob', age: 22});
// Просто передаем Blob, а вторым параметром ключ
t.objectStore('images').put(image, imageName);
}
async function getSomeData() {
// Для чтения из users откроем транзакцию на чтение
const t = (await db.storage).transaction('users');
// get вернет значение по ключу, getAll вернет массив значений и т.д.
console.log(await db.result(t.objectStore('users').get(0)));
}
13 Jan, 12:06
10 Jan, 08:46
09 Jan, 12:37
09 Jan, 10:52
31 Dec, 12:03
31 Dec, 11:12
29 Dec, 11:27
26 Dec, 12:56
26 Dec, 09:57
// '1 час, 46 минут 40 секунд'
new Intl.DurationFormat('ru-RU', {style: 'long'}).format({hours: 1, minutes: 46, seconds: 40});
23 Dec, 14:34
function toBase64(str) {
return new TextEncoder().encode(str).toBase64();
}
function fromBase64(str) {
return new TextDecoder().decode(Uint8Array.fromBase64(str));
}
23 Dec, 14:23
fromBase64
, fromHex
, toBase64
, toHex
, setFromBase64
, setFromHex
и это очень круто, т.к. раньше приходилось такой код писать руками.window.atob
и window.btoa
, которые мы использовали для конвертации строк в формат base64. Почему кривые? Ну, они поддерживают только ASCII диапазон, а значит, например, преобразовать кириллические символы у нас не получится. Но теперь это не проблема.function toBase64(str) {
// Наши строки закодированы в UTF-16, поэтому делаем на их основе Uint16Array и извлекаем буфер
const buf = new Uint16Array(
str.split('').map((s) => s.charCodeAt(0))
).buffer;
// Смотрим на буфер как на поток байт и кодируем в base64 строку
return new Uint8Array(buf).toBase64();
}
function fromBase64(str) {
// Декодируем строку в байты
const buf = Uint8Array.fromBase64(str).buffer;
// Делаем из Uint16Array обратно строку
return new Uint16Array(buf).reduce((res, code) => res + String.fromCharCode(code), '');
}
19 Dec, 12:20
19 Dec, 09:54
navigator.sendBeacon
.document.addEventListener('visibilitychange', function logData() {
if (document.visibilityState === 'hidden') {
navigator.sendBeacon('/log', analyticsData);
}
});
sendBeacon
уж слишком примитивен: у вас есть URL, данные и вы можете использовать только POST запросы. Даже header запроса тут не установить. И вот теперь у нас появилась такая возможность используя стандартное Fetch API (правда поддержка только в новейших версиях браузеров, например, в FF только с версии 133).fetch('/log', {
method: 'POST',
body: analyticsData,
keepalive: true
});
sendBeacon
так и в keepalive
есть ограничение на размер отправляемых данных: они не могут превышать 64кб. Но я считаю, что такое изменение в API не может не радовать. По моему опыту проведения собеседования, половина разработчиков даже не знает о наличии navigator.sendBeacon
. 17 Dec, 08:19
as
, который позволяет удобно делать переименование ключей в объектном типе.type Getters<Type> = {
[Property in keyof Type as `get${Capitalize<string & Property>}`]: () => Type[Property]
};
interface Person {
name: string;
age: number;
location: string;
}
// type LazyPerson = {
// getName: () => string;
// getAge: () => number;
// getLocation: () => string;
// }
type LazyPerson = Getters<Person>;
13 Dec, 05:58
12 Dec, 11:37
11 Dec, 07:30
10 Dec, 13:02
10 Dec, 05:23
03 Dec, 06:51
29 Nov, 12:50
29 Nov, 07:44
29 Nov, 06:30
29 Nov, 06:12
28 Nov, 12:03
28 Nov, 06:56
27 Nov, 15:13
27 Nov, 08:52
26 Nov, 16:02
26 Nov, 10:09
25 Nov, 13:45
25 Nov, 09:34
21 Nov, 12:22
href="javascript:void(0)"
. Явно не то, что я ожидал увидеть.href="javascript:void(0)"
будут препятствовать внедрению CSP заголовков на вашем сервисе.<button>
. Использовать href="#"
тоже не надо, т.к. это также нарушение доступности и семантики. 21 Nov, 11:01
const AppRequest = Request.useCredential.header('Content-Type', 'application/json');
const UserRequest = AppRequest.url('/user');
// Создам PUT запрос для создания пользователя
UserRequest.put({name: 'Bob', age: 42}).then(console.log);
Request
, причем UserRequest
за основу использует AppRequest
в котором описаны базовые настройки запросов нашего приложения. А сам запрос уже создается используя терминальный метод put
.const MyStorage = KVStorage.newBucket();
// Где-то в вашем коде
MyStorage.set('key1', data1);
// Где-то в вашем коде
MyStorage.set('key2', data2);
// Где-то в вашем коде
MyStorage.set('key3', data3);
// Создаем объект хранилища, а также серелизуем данные из бакета и сохраняем "за раз"
const dataStorage = MyStorage.create(localStorage, 'myStore');
class MyRequest {
static _state = {};
static url(url) {
const newState = {...this._state, url};
return class extends this { static _state = newState; }
}
static method(method) {
const newState = {...this._state, method};
return class extends this { static _state = newState; }
}
static create(params) {
return new MyRequest({...this._state, ...params});
}
}
MyRequest.url('/example').method('POST').create({body: 'my-data'});
20 Nov, 11:54
import { loadSession } from 'core/init/dependencies/load-session';
import { loadedHydratedPage } from 'core/init/dependencies/loaded-hydrated-page';
import { whenDOMLoaded } from 'core/init/dependencies/when-dom-loaded';
import { dependency } from 'core/init/dependencies/helpers';
export default {
loadSession,
// Этот модуль должен инициализироваться после loadSession
loadedHydratedPage: dependency(loadedHydratedPage, 'loadSession'),
// Этот модуль должен инициализироваться после всех модулей
whenDOMLoaded: dependency(whenDOMLoaded, '*')
};
Promise.all(createDependencyIterator(dependencies).map(([_, {init}]) => init(appState)));
18 Nov, 11:47
<select>
<button>
selected option: <selectedcontent></selectedcontent>
</button>
<option>one</option>
<option>two</option>
</select>
<style>
select, ::picker(select) {
appearance: base-select;
}
</style>
16 Nov, 14:24
16 Nov, 11:45
15 Nov, 18:09
13 Nov, 14:45
12 Nov, 11:27
07 Nov, 10:22
function foo()
42
end
function foo(a:: Int32)
a + Int32(10)
end
function foo(a:: Int64)
a + 100
end
# 42
foo()
# Int32(20)
foo(Int32(10))
# 200
foo(100)
struct User
name:: String;
age:: UInt32;
end
function getName(user:: User)
user.name
end
function getAge(user:: User)
user.age
end
bob = User("Bob", 42)
getName(bob)
07 Nov, 10:22
06 Nov, 09:57
05 Nov, 11:19
01 Nov, 19:06
01 Nov, 16:11
01 Nov, 13:33
31 Oct, 08:38
30 Oct, 19:28
30 Oct, 19:23
30 Oct, 15:20
30 Oct, 09:35
30 Oct, 06:04
29 Oct, 07:58
25 Oct, 10:39
24 Oct, 10:04
@component({functional: true})
class bInput extends iBlock {
@prop(String)
value: string = '';
@field((ctx) => ctx.sync.link('value'))
valueStore: string;
submit() {
// ...
}
}
Object.getOwnPropertyNames
, что также не бесплатно.@defaultValue
.@component({functional: true})
class bInput extends iBlock {
@prop(String)
value: string = '';
@field()
valueStore: string = this.sync.link('value');
}
@component({functional: true})
class bInput extends iBlock {
@defaultValue('')
@prop(String)
value: string = '';
@defaultValue(function () { return this.sync.link('value'); })
@field()
valueStore: string = this.sync.link('value');
}
@method
@component({functional: true})
class bInput extends iBlock {
submit() {
// ...
}
get foo() {
// ...
}
}
@component({functional: true})
class bInput extends iBlock {
@method('method')
submit() {
// ...
}
@method('accessor')
get foo() {
// ...
}
}
24 Oct, 10:04
22 Oct, 14:57
22 Oct, 11:57
22 Oct, 10:48
21 Oct, 11:29
21 Oct, 11:15
18 Oct, 10:23
17 Oct, 13:09
17 Oct, 09:58
--experimental-strip-types
и --experimental-transform-types
для поддержки TS.require
теперь можно использовать для загрузки ESM модулей в синхронном режиме без необходимости использовать top await import
.16 Oct, 15:45
16 Oct, 09:12
15 Oct, 09:39
14 Oct, 11:50
11 Oct, 13:55
10 Oct, 14:08
09 Oct, 17:19
09 Oct, 08:19
08 Oct, 10:25
07 Oct, 14:41
07 Oct, 12:07
fn main() {
let x = 2.0;
// Вызов метода через точку
let y = x.powi(3);
println!("2^3 = {}", y);
// Вызов метода через явную квалификацию
let z = f64::powi(x, 3);
println!("2^3 = {}", z);
}
trait Double {
fn double(self) -> Self;
}
impl Double for i32 {
fn double(self) -> i32 {
self * 2
}
}
fn main() {
let x: i32 = 5;
// Выглядит как новый метод, но это сахар для Double::double(x)
let result = x.double();
println!("Удвоенное значение {} равно {}", x, result);
}
07 Oct, 12:07
+
, -
, .
, []
- это тоже функции из различных характеристик, а значит мы можем их явно реализовывать для любого своего типа. Говоря проще: в Rust есть перегрузка операторов. Причем работает она абсолютно прозрачно.04 Oct, 16:39
03 Oct, 12:52
02 Oct, 19:08