vtils:小巧实用的 JavaScript 工具类库
source link: https://www.tuicool.com/articles/QjyiMvu
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
小巧实用的 JavaScript 工具类库。
https://fjc0k.github.io/vtils/
特性
- 源于日常项目实践,更实用
- 使用 TypeScript 编写,类型友好
- 支持摇树优化(Tree Shaking),只引入使用到的工具
- 浏览器、Node、小程序多端兼容
安装
# yarn yarn add vtils # or, npm npm i vtils --save
使用
在线体验: https://stackblitz.com/edit/vtils
import { inBrowser, shuffle } from 'vtils' if (inBrowser()) { alert('您在浏览器中...') } alert(shuffle([1, 2, 3, 4]))
工具列表
:package: 工具函数
:bulb: assign
分配来源对象的可枚举属性到目标对象上。
来源对象的应用规则是从左到右,随后的下一个对象的属性会覆盖上一个对象的属性。
assign( {}, { x: 1 }, { y: 2 }, { x: 5, z: 9 }, ) // => { x: 5, y: 2, z: 9 }
:bulb: base64Decode
返回 base64
解码后的字符串。
base64Decode('dnRpbHM=') // => vtils base64Decode('5Lit5Zu9') // => 中国 base64Decode('8J+RqOKAjfCfkrs=') // => :man::computer:
:bulb: base64Encode
返回 base64
编码后的字符串。
base64Encode('vtils') // => dnRpbHM= base64Encode('中国') // => 5Lit5Zu9 base64Encode(':man::computer:') // => 8J+RqOKAjfCfkrs=
:bulb: base64UrlDecode
返回 base64url
解码后的字符串。
base64UrlDecode('dnRpbHM') // => vtils base64UrlDecode('5Lit5Zu9') // => 中国 base64UrlDecode('8J-RqOKAjfCfkrs') // => :man::computer:
:bulb: base64UrlEncode
返回 base64url
编码后的字符串。
base64UrlEncode('vtils') // => dnRpbHM base64UrlEncode('中国') // => 5Lit5Zu9 base64UrlEncode(':man::computer:') // => 8J-RqOKAjfCfkrs
:bulb: castArray
如果 value
是数组,直接返回;如果 value
不是数组,返回 [value]
。
castArray([123, 456]) // => [123, 456] castArray(123) // => [123] castArray('hello') // => ['hello'] castArray(null) // => [null]
:bulb: chunk
将 arr
拆分成多个 size
长度的区块,并将它们组合成一个新数组返回。
如果 arr
无法等分,且设置了 filler
函数,剩余的元素将被 filler
函数的返回值填充。
const arr = [1, 2, 3, 4, 5, 6] chunk(arr, 2) // => [[1, 2], [3, 4], [5, 6]] chunk(arr, 3) // => [[1, 2, 3], [4, 5, 6]] chunk(arr, 4) // => [[1, 2, 3, 4], [5, 6]] chunk(arr, 4, index => index) // => [[1, 2, 3, 4], [5, 6, 0, 1]]
:bulb: clamp
返回限制在最小值和最大值之间的值。
clamp(50, 0, 100) // => 50 clamp(50, 0, 50) // => 50 clamp(50, 0, 49) // => 49 clamp(50, 51, 100) // => 51
:bulb: createURIQuery
创建 URI 查询字符串。
createURIQuery({ x: 1, y: 'z' }) // => x=1&y=z
:bulb: debounce
创建一个去抖函数,将触发频繁的事件合并成一次执行。
该函数被调用后,计时 wait
毫秒后调用 fn
函数。若在 wait
毫秒内该函数再次被调用,则重新开始计时。
一个应用场景:监听输入框的 input
事件发起网络请求。
document.querySelector('#input').oninput = debounce( e => { console.log(e.target.value) }, 500, )
:bulb: defaultTo
检查 value
是否是 null
、 undefined
、 NaN
,是则返回 defaultValue
,否则返回 value
。
defaultTo(1, 2) // => 1 defaultTo(NaN, 2) // => 2 defaultTo(null, 2) // => 2 defaultTo(undefined, 2) // => 2
:bulb: endsWith
检查 str
是否以 needle
结尾。
endsWith('hello', 'llo') // => true endsWith('hello', 'he') // => false
:bulb: fill
使用 value
来填充(替换) arr
,从 start
位置开始, 到 end
位置结束(但不包括 end
位置)。
fill(Array(5), () => 1) // => [1, 1, 1, 1, 1] fill(Array(3), (value, index) => index) // => [0, 1, 2]
:bulb: flexible
移动端屏幕适配。
:bulb: forOwn
遍历对象的可枚举属性。若遍历函数返回 false
,遍历会提前退出。
注:基于你传入的 obj
,遍历函数中 key
的类型可能为 number
,但在运行时, key
始终为 string
,因此,你应该始终把 key
当作 string
处理。(为什么会这样? https://github.com/microsoft/TypeScript/pull/12253#issuecomment-263132208)
forOwn( { x: '1', y: 2 }, (value, key) => { console.log(key, value) } )
:bulb: getGlobal
获取全局对象。
// 浏览器中 getGlobal() // => window // Node 中 getGlobal() // => global
:bulb: getType
检测 value
的类型。
getType(1) // => 'Number' getType(true) // => 'Boolean' getType([]) // => 'Array' getType(/hello/) // => 'RegExp'
:bulb: groupBy
根据 iteratee
返回的值对 data
进行分组。
groupBy( [ { type: 1, name: '石头' }, { type: 3, name: '花生' }, { type: 2, name: '鲸鱼' }, { type: 1, name: '树木' }, { type: 2, name: '鲨鱼' }, ], item => item.type, ) // => { // => 1: [ // => { type: 1, name: '石头' }, // => { type: 1, name: '树木' }, // => ], // => 2: [ // => { type: 2, name: '鲸鱼' }, // => { type: 2, name: '鲨鱼' }, // => ], // => 3: [ // => { type: 3, name: '花生' }, // => ], // => }
:bulb: has
检查 key
是否是对象 obj
自身的属性。
const obj = { x: 1, 2: 'y' } has(obj, 'x') // => true has(obj, 2) // => true has(obj, 'toString') // => false
:bulb: ii
立即调用函数并返回其返回值。
注: ii = immediately invoke
ii(() => 1) // => 1
:bulb: inAndroid
检查是否在 Android
设备中。
// Android 设备中 inAndroid() // => true inAndroid( () => console.log('你在 Android 设备中'), )
:bulb: inBrowser
检查是否在浏览器环境中。
// 浏览器中 inBrowser() // => true inBrowser( () => console.log('你在浏览器中'), )
:bulb: inIOS
检查是否在 iOS
设备中。
// iOS 设备中 inIOS() // => true inIOS( () => console.log('你在 iOS 设备中'), )
:bulb: inNode
检查是否在 Node
环境中。
// Node 中 inNode() // => true inNode( () => console.log('你在 Node 中'), )
:bulb: inRange
检查 value
是否在某区间内。
// 2 是否在区间 (0, 2) 内 inRange(2, 0, 2, InRangeIntervalType.open) // => false // 2 是否在区间 [0, 2] 内 inRange(2, 0, 2, InRangeIntervalType.closed) // => true // 2 是否在区间 [0, 2) 内 inRange(2, 0, 2, InRangeIntervalType.leftClosedRightOpen) // => false // 2 是否在区间 (0, 2] 内 inRange(2, 0, 2, InRangeIntervalType.leftOpenRightClosed) // => true
:bulb: inWechatMiniProgram
检查是否在微信小程序环境中。
// 微信小程序中 inWechatMiniProgram() // => true inWechatMiniProgram( () => console.log('你在微信小程序中'), )
:bulb: inWechatWebview
检查是否在微信浏览器环境中。
// 微信浏览器中 inWechatWebview() // => true inWechatWebview( () => console.log('你在微信浏览器中'), )
:bulb: includes
检索值 value
是否在数组 arr
中。
includes([1, 2, 3], 1) // => true includes([NaN, 2, 3], NaN) // => true includes([1, 2, 3], 4) // => false
检索可枚举属性值 value
是否在对象 obj
中。
includes({ x: 1, y: 2 }, 1) // => true includes({ x: 1, y: 2 }, 3) // => false
检索值 value
是否在字符串 str
中。
includes('hello', 'h') // => true includes('hello', 'll') // => true includes('hello', '123') // => false
:bulb: isArguments
检查 value
是否是一个 arguments
对象。
function myFunction() { console.log(isArguments(arguments)) // true }
:bulb: isArray
检查 value
是否是一个数组。
isArray(['x']) // => true isArray('x') // => false
:bulb: isBoolean
检查 value
是否是一个布尔值。
isBoolean(true) // => true isBoolean(false) // => true isBoolean('true') // => false
:bulb: isChineseIDCardNumber
检查 value
是否是合法的中国大陆居民 18
位身份证号码。
isChineseIDCardNumber('123456') // => false
:bulb: isDate
检查 value
是否是一个日期。
isDate(new Date()) // => true
:bulb: isEmail
检查 value
是否是一个邮件地址。
isEmail('[email protected]') // => true isEmail('hello@foo') // => false
:bulb: isEmpty
检查 value
是否是空值,包括: undefined
、 null
、 ''
、 false
、 true
、 []
、 {}
。
isEmpty(undefined) // => true isEmpty(null) // => true isEmpty('') // => true isEmpty(false) // => true isEmpty(true) // => true isEmpty([]) // => true isEmpty({}) // => true
:bulb: isEqualArray
检查给定的数组的各项是否相等。
isEqualArray([1], [1]) // => true isEqualArray([1], [5]) // => false
:bulb: isFinite
检查 value
是否是原始有限数值。
isFinite(1) // => true isFinite(Infinity) // => false
:bulb: isFunction
检查 value
是否是一个函数。
isFunction(() => {}) // => true isFunction(2000) // => false
:bulb: isHan
检查 value
是否全是汉字。
isHan('hello') // => false isHan('嗨咯') // => true
:bulb: isInteger
检查 value
是否是一个整数。
isInteger(1) // => true isInteger(1.2) // => false isInteger(-1) // => true
:bulb: isNaN
检查 value
是否是 NaN
。
isNaN(NaN) // => true isNaN(2) // => false
:bulb: isNegativeInteger
检查 value
是否是一个负整数。
isNegativeInteger(-1) // => true isNegativeInteger(1) // => false
:bulb: isNil
检查 value
是否是 null
或 undefined
。
isNil(null) // => true isNil(undefined) // => true
:bulb: isNull
检查 value
是否是 null
。
isNull(null) // => true
:bulb: isNumber
检查 value
是否是一个数字。
注: NaN
不被认为是数字。
isNumber(1) // => true isNumber(0.1) // => true isNumber(NaN) // => false
:bulb: isNumeric
检查 value
是否是一个数值。
注: Infinity
、 -Infinity
、 NaN
不被认为是数值。
isNumeric(1) // => true isNumeric('1') // => true
:bulb: isObject
检查 value
是否是一个对象。
isObject({}) // => true isObject(() => {}) // => true isObject(null) // => false
:bulb: isPlainObject
检查 value
是否是一个普通对象。
isPlainObject({}) // => true isPlainObject(Object.create(null)) // => true isPlainObject(() => {}) // => false
:bulb: isPositiveInteger
检查 value
是否是一个正整数。
isPositiveInteger(1) // => true isPositiveInteger(-1) // => false
:bulb: isPossibleChineseMobilePhoneNumber
检测 number
是否可能是中国的手机号码。
isPossibleChineseMobilePhoneNumber(18000030000) // => true isPossibleChineseMobilePhoneNumber(10086) // => false
:bulb: isPossibleChineseName
检测 value
是否可能是中国人的姓名,支持少数名族姓名中间的 ·
号。
isPossibleChineseName('鲁') // => false isPossibleChineseName('鲁迅') // => true isPossibleChineseName('买买提·吐尔逊') // => true
:bulb: isPromiseLike
检查 value
是否像 Promise
。
isPromiseLike(Promise.resolve()) // => true
:bulb: isRegExp
检查 value
是否是一个正则对象。
isRegExp(/hello/) // => true isRegExp(new RegExp('hello')) // => true
:bulb: isString
检查 value
是否是一个字符串。
isString('') // => true isString('hello') // => true
:bulb: isUndefined
检查 value
是否等于 undefined
。
isUndefined(undefined) // => true isUndefined(void 0) // => true
:bulb: isUrl
检查 value
是否是一个有效的网址,仅支持 http
、 https
协议,支持 IP
域名。
isUrl('http://foo.bar') // => true isUrl('https://foo.bar/home') // => true
:bulb: jestExpectEqual
这是一个 jest 测试辅助函数,等同于 expect(actual).toEqual(expected)
,只不过是加上了类型。
:bulb: keyBy
根据 iteratee
返回的键对 data
进行分组,但只保留最后一个结果。
keyBy( [ { type: 1, name: '石头' }, { type: 3, name: '花生' }, { type: 2, name: '鲸鱼' }, { type: 1, name: '树木' }, { type: 2, name: '鲨鱼' }, ], item => item.type, ) // => { // => 1: { type: 1, name: '树木' }, // => 2: { type: 2, name: '鲨鱼' }, // => 3: { type: 3, name: '花生' }, // => }
:bulb: keys
返回 obj
的可枚举属性组成的数组。
注:基于你传入的 obj
,返回的 key
的类型可能为 number
,但在运行时, key
始终为 string
,因此,你应该始终把 key
当作 string
处理。(为什么会这样? https://github.com/microsoft/TypeScript/pull/12253#issuecomment-263132208)
keys({ x: 1, 2: 'y' }) // => ['x', '2'] 或 ['2', 'x']
:bulb: last
返回数组 arr
的最后一项。
last([1, 2, 3]) // => 3
:bulb: loadResource
加载图片、代码、样式等资源。
loadResource([ 'https://foo.bar/all.js', 'https://foo.bar/all.css', 'https://foo.bar/logo.png', { type: LoadResourceUrlType.js, path: 'https://s1.foo.bar/js/full', alternatePath: 'https://s2.foo.bar/js/full', }, ]).then(() => { // 资源加载完成后的操作 })
:bulb: mapValues
映射对象的可枚举属性值为一个新的值。
mapValues( { x: 1, y: 2 }, value => value + 10, ) // => { x: 11, y: 12 }
:bulb: memoize
函数结果缓存。
let i = 0 const fn = memoize(() => i++) fn() // => 0 fn() // => 0
:bulb: noop
无操作函数。
noop() // => undefined
:bulb: omit
创建一个从 obj
中剔除选中的可枚举属性的对象。
omit({ x: 1, y: 2 }, ['x']) // => { y: 2 }
:bulb: orderBy
允许指定一个或多个规则对数据进行排序。
orderBy( ['x', 'xyz', 'xy'], { iteratee: item => item.length, type: OrderByRuleType.desc, }, ) // => ['xyz', 'xy', 'x']
:bulb: padEnd
在 str
右侧填充字符。
padEnd('姓名', 4, '*') // => 姓名**
:bulb: padStart
在 str
左侧填充字符。
padStart('姓名', 4, '*') // => **姓名
:bulb: parallel
并行执行任务, 同步任务
、 异步任务
皆可。
parallel([ () => 1, async () => 'hello', ]).then(res => { // => [1, 'hello'] })
:bulb: parseCSSValue
解析 CSS
值的数值和单位。
parseCSSValue('12px') // => { value: 12, unit: 'px' } parseCSSValue(12) // => { value: 12, unit: 'px' } parseCSSValue('12%') // => { value: 12, unit: '%' }
:bulb: pick
创建一个从 obj
中选中的可枚举属性的对象。
pick({ x: 1, y: 2 }, ['x']) // => { x: 1 }
:bulb: placeKitten
给定大小获取占位猫咪图片,图片来自: https://placekitten.com/
placeKitten(100) // => https://placekitten.com/100/100
给定宽高获取占位猫咪图片,图片来自: https://placekitten.com/
placeKitten(100, 200) // => https://placekitten.com/100/200
:bulb: pluck
将数据中每一项的迭代值组合成一个数组返回。
pluck( [{ id: 1, name: 'Jay' }, { id: 2, name: 'Lily' }], item => item.name, ) // => ['Jay', 'Lily']
将数据中每一项的迭代值组合成一个对象返回。
pluck( [{ id: 1, name: 'Jay' }, { id: 2, name: 'Lily' }], item => item.name, item => item.id, ) // => { 1: 'Jay', 2: 'Lily' }
:bulb: randomString
生成一个随机字符串。
randomString() // => m481rnmse1m
:bulb: range
创建一个包含从 start
到 end
,但不包含 end
本身范围数字的数组。
range(0, 5) // => [0, 1, 2, 3, 4] range(0, -5, -1) // => [0, -1, -2, -3, -4]
:bulb: repeat
重复 n
次给定字符串。
repeat('a', 5) // => aaaaa
:bulb: round
对传入的数字按给定的精度四舍五入后返回。
round(3.456) // => 3 round(3.456, 1) // => 3.5 round(3.456, 2) // => 3.46 round(345, -2) // => 300
:bulb: roundDown
对传入的数字按给定的精度向下取值后返回。
roundDown(3.456) // => 3 roundDown(3.456, 1) // => 3.4 roundDown(3.456, 2) // => 3.45 roundDown(345, -2) // => 300
:bulb: roundUp
对传入的数字按给定的精度向上取值后返回。
roundUp(3.456) // => 4 roundUp(3.456, 1) // => 3.5 roundUp(3.456, 2) // => 3.46 roundUp(345, -2) // => 400
:bulb: safeGet
:bulb: sample
从数组中随机获取一个元素。
sample([1, 2, 3]) // => 1 或 2 或 3
从对象中随机获取一个可枚举属性的值。
sample({ x: 1, y: 2, z: 3 }) // => 1 或 2 或 3
:bulb: sequential
顺序执行任务, 同步任务
、 异步任务
皆可。
sequential([ () => 1, async () => 'hello', ]).then(res => { // => [1, 'hello'] })
:bulb: shuffle
打乱一个数组。
shuffle([1, 2]) // => [1, 2] 或 [2, 1]
:bulb: startsWith
检查 str
是否以 needle
开头。
startsWith('hello', 'he') // => true startsWith('hello', 'llo') // => false
:bulb: sum
计算传入值的总和。
sum([1, 2, 3]) // => 6
:bulb: sumBy
根据 iteratee
返回的结果计算传入值的总和。
sumBy( [ { count: 1 }, { count: 2 }, { count: 3 }, ], item => item.count, ) // => 6
:bulb: throttle
创建一个节流函数,给函数设置固定的执行速率。
- 该函数首次被调用时,会立即调用
fn
函数,并记录首次调用时间。- 该函数第二次被调用时:
- 如果该次调用时间在首次调用时间的
wait
区间内,timer = setTimeout(操作, 时间差)
;- 该函数再次被调用时:
wait
- 该函数再次被调用时:
- 否则,清除首次调用时间,回到第一步。
- 如果该次调用时间在首次调用时间的
- 该函数第二次被调用时:
一个应用场景:监听窗口的 resize
事件响应相关操作。
window.addEventListener( 'resize', throttle( () => console.log('窗口大小改变后的操作'), 1000, ), )
:bulb: times
调用函数 n
次,将每次的调用结果存进数组并返回。
times(4, () => { // 这里将会执行 4 次 })
:bulb: tryGet
尝试执行 accessor
返回值,若其报错,返回默认值 defaultValue
。
const obj = { x: 1 } tryGet(() => obj.x, 2) // => 1 tryGet(() => obj.x.y, 2) // => 2
尝试执行 accessor
返回值,若其报错,返回 undefined
。
const obj = { x: 1 } tryGet(() => obj.x) // => 1 tryGet(() => obj.x.y) // => undefined
:bulb: unique
将给定的数组去重后返回。
unique([1, 2, 1, 3]) // => [1, 2, 3]
:bulb: values
返回 obj
自身可枚举属性值组成的数组。
values({ x: 1, 2: 'y' }) // => [1, 'y'] 或 ['y', 1]
:bulb: wait
等待一段时间。
wait(1000).then(() => { // 等待 1000 毫秒后执行 })
:package: 工具类
:bulb: Disposer
资源释放器。
const disposer = new Disposer() const timer = setInterval( () => console.log('ok'), 1000, ) disposer.add(() => clearInterval(timer)) document.querySelector('#stop').onclick = () => { disposer.dispose() }
:bulb: EasyStorage
:bulb: EasyStorageAdapter
:bulb: EasyStorageAdapterBrowser
:bulb: EasyStorageAdapterMemory
:bulb: EasyStorageAdapterWeapp
微信小程序 Storage
适配器。
由于微信小程序的 wx.getStorageSync
方法对于不存在的项目会返回 空字符串
,导致无法判断项目是否存在,因此,该适配器对存储的内容做了一层封装,以保证相关操作的结果可确定。
:bulb: EasyStorageDriverBrowserLocalStorage
:bulb: EasyStorageDriverBrowserSessionStorage
:bulb: EasyValidator
数据对象验证器。
interface Data { name: string, phoneNumber: string, pass1: string, pass2: string, } const ev = new EasyValidator<Data>([ { key: 'name', type: 'chineseName', message: '请输入真实姓名', }, { key: 'phoneNumber', type: 'chineseMobilePhoneNumber', message: '请输入正确的手机号码', }, { key: 'phoneNumber', test: async ({ phoneNumber }, { updateMessage }) => { const result = await checkPhoneNumberAsync(phoneNumber) if (!result.valid) { updateMessage(result.message) return false } }, message: '请输入正确的手机号码' }, { key: 'pass1', test: ({ pass1 }) => pass1.length > 6, message: '密码应大于6位', }, { key: 'pass2', test: ({ pass1, pass2 }) => pass2 === pass1, message: '两次密码应一致', }, ]) ev.validate({ name: '方一一', phoneNumber: '18087030070', pass1: '1234567', pass2: '12345678' }).then(res => { // => { valid: false, unvalidRules: [{ key: 'pass2', test: ({ pass1, pass2 }) => pass2 === pass1, message: '两次密码应一致' }] } })
:bulb: EventBus
事件巴士,管理事件的发布与订阅。
const bus = new EventBus<{ success: () => void, error: (message: string) => void, }>() const unbindSuccessListener = bus.on('success', () => { console.log('成功啦') }) const unbindErrorListener = bus.once('error', message => { console.error(message) }) bus.emit('success') bus.emit('error', '出错啦') unbindSuccessListener() bus.off('error')
:bulb: Wechat
对微信 JSSDK 的封装。
const wechat = new Wechat() getWechatConfigAsync().then(config => { wechat.config(config) }) wechat.updateShareData({ title: '分享标题', desc: '分享描述', link: '分享链接', imgUrl: '缩略图地址', }) wechat.invoke('scanQRCode').then(res => { // => API 调用结果 })
:package: 工具类型
:bulb: AnyFunction
任意函数类型。
:bulb: AnyObject
任意对象类型。
:bulb: AsyncOrSync
// before type X = PromiseLike<string> | string // after type X = AsyncOrSync<string>
:bulb: Brand
名义化类型。
type User = { id: Brand<number, User>, name: string } type Post = { id: Brand<number, Post>, title: string } type UserIdIsNumber = User['id'] extends number ? true: false // => true type PostIdIsNumber = Post['id'] extends number ? true: false // => true type PostIdIsNotUserId = Post['id'] extends User['id'] ? false : true // => true
:bulb: Defined
从 T
中排除 undefined
类型。
interface User { gender?: 'male' | 'female', } // before type UserGender = Exclude<User['gender'], undefined> // after type UserGender = Defined<User['gender']>
:bulb: If
条件类型。
type X = 'x' // before type IsX = X extends 'x' ? true : false // after type IsX = If<X extends 'x', true, false>
:bulb: IsNever
检查 T
是否是 never
类型。
type X = never // before type XIsNever = [X] extends [never] ? true : false // after type XIsNever = IsNever<X>
:bulb: LiteralUnion
字面量联合类型。
// before: China, American 将得不到类型提示 type Country = 'China' | 'American' | string // after: China, American 将得到类型提示 type Country = LiteralUnion<'China' | 'American', string>
:bulb: Merge
合并两个类型,后一个类型的定义将覆盖前一个类型的定义。
type X = Merge< { x: number, y: number }, { x: string, z: string } > // => { x: string, y: number, z: string }
:bulb: Omit
从接口 T
中去除指定的属性。
type X = Omit< { x: number, y: string, z: boolean }, 'x' | 'z' > // => { y: string }
:bulb: OneOrMore
// before type X = number | number[] // after type X = OneOrMore<number>
:bulb: ValueOf
返回接口 T
属性值的类型。
type V = ValueOf<{ x: number, y: string, z: boolean }> // => number | string | boolean
许可
MIT :copyright: Jay Fong
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK