Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(keys): Add keys to compatibility layer #814

Merged
merged 9 commits into from
Dec 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions docs/ja/reference/compat/object/keys.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# keys

::: info
この関数は互換性のために `es-toolkit/compat` からのみインポートできます。代替可能なネイティブ JavaScript API があるか、まだ十分に最適化されていないためです。

`es-toolkit/compat` からこの関数をインポートすると、[lodash と完全に同じように動作](../../../compatibility.md)します。
:::

`object` の列挙可能なプロパティ名を返します。

オブジェクトではない値はオブジェクトに変換されます。

## インターフェース

```typescript
function keys(object?: any): string[];
```

### パラメータ

- `object` (`object`): 問い合わせるオブジェクト。

### 戻り値

(`string[]`): プロパティ名の配列。

## 例

```typescript
function Foo() {
this.a = 1;
this.b = 2;
}
Foo.prototype.c = 3;
keys(new Foo()); // ['a', 'b'] (iteration order is not guaranteed)

keys('hi'); // ['0', '1']
keys([1, 2, 3]); // ['0', '1', '2']
keys({ a: 1, b: 2 }); // ['a', 'b']
```
40 changes: 40 additions & 0 deletions docs/ko/reference/compat/object/keys.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# keys

::: info
이 함수는 호환성을 위한 `es-toolkit/compat` 에서만 가져올 수 있어요. 대체할 수 있는 네이티브 JavaScript API가 있거나, 아직 충분히 최적화되지 않았기 때문이에요.

`es-toolkit/compat`에서 이 함수를 가져오면, [lodash와 완전히 똑같이 동작](../../../compatibility.md)해요.
:::

`object` 객체의 열거 가능한 프로퍼티 이름들을 반환해요.

객체가 아닌 값은 객체로 변환해요.

## 인터페이스

```typescript
function keys(object?: any): string[];
```

### 파라미터

- `object` (`object`): 프로퍼티 이름들을 구할 객체에요.

### 반환 값

(`string[]`): 프로퍼티 이름의 배열.

## 예시

```typescript
function Foo() {
this.a = 1;
this.b = 2;
}
Foo.prototype.c = 3;
keys(new Foo()); // ['a', 'b'] (iteration order is not guaranteed)

keys('hi'); // ['0', '1']
keys([1, 2, 3]); // ['0', '1', '2']
keys({ a: 1, b: 2 }); // ['a', 'b']
```
40 changes: 40 additions & 0 deletions docs/reference/compat/object/keys.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# keys

::: info
This function is only available in `es-toolkit/compat` for compatibility reasons. It either has alternative native JavaScript APIs or isn’t fully optimized yet.

When imported from `es-toolkit/compat`, it behaves exactly like lodash and provides the same functionalities, as detailed [here](../../../compatibility.md).
:::

Creates an array of the own enumerable property names of `object`.

Non-object values are coerced to objects.

## Signature

```typescript
function keys(object?: any): string[];
```

### Parameters

- `object` (`object`): The object to query.

### Returns

(`string[]`): Returns the array of property names.

## Examples

```typescript
function Foo() {
this.a = 1;
this.b = 2;
}
Foo.prototype.c = 3;
keys(new Foo()); // ['a', 'b'] (iteration order is not guaranteed)

keys('hi'); // ['0', '1']
keys([1, 2, 3]); // ['0', '1', '2']
keys({ a: 1, b: 2 }); // ['a', 'b']
```
40 changes: 40 additions & 0 deletions docs/zh_hans/reference/compat/object/keys.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# keys

::: info
出于兼容性原因,此函数仅在 `es-toolkit/compat` 中提供。它可能具有替代的原生 JavaScript API,或者尚未完全优化。

从 `es-toolkit/compat` 导入时,它的行为与 lodash 完全一致,并提供相同的功能,详情请见 [这里](../../../compatibility.md)。
:::

创建一个包含`object`自身可枚举属性名的数组。

非对象值将被强制转换为对象。

## 签名

```typescript
function keys(object?: any): string[];
```

### 参数

- `object` (`object`): 要查询的对象。

### 返回值

(`string[]`): 返回属性名数组。

## 示例

```typescript
function Foo() {
this.a = 1;
this.b = 2;
}
Foo.prototype.c = 3;
keys(new Foo()); // ['a', 'b'] (iteration order is not guaranteed)

keys('hi'); // ['0', '1']
keys([1, 2, 3]); // ['0', '1', '2']
keys({ a: 1, b: 2 }); // ['a', 'b']
```
1 change: 1 addition & 0 deletions src/compat/_internal/arrayProto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const arrayProto: any = Array.prototype;
4 changes: 2 additions & 2 deletions src/compat/_internal/isIndex.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
const IS_UNSIGNED_INTEGER = /^(?:0|[1-9]\d*)$/;

export function isIndex(value: PropertyKey): boolean {
export function isIndex(value: PropertyKey, length = Number.MAX_SAFE_INTEGER): boolean {
switch (typeof value) {
case 'number': {
return Number.isInteger(value) && value >= 0 && value < Number.MAX_SAFE_INTEGER;
return Number.isInteger(value) && value >= 0 && value < length;
}
case 'symbol': {
return false;
Expand Down
2 changes: 1 addition & 1 deletion src/compat/_internal/isPrototype.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
export function isPrototype(value: {}) {
const constructor = value.constructor;
const constructor = value?.constructor;
const prototype = typeof constructor === 'function' ? constructor.prototype : Object.prototype;

return value === prototype;
Expand Down
1 change: 1 addition & 0 deletions src/compat/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ export { fromPairs } from './object/fromPairs.ts';
export { get } from './object/get.ts';
export { has } from './object/has.ts';
export { invertBy } from './object/invertBy.ts';
export { keys } from './object/keys.ts';
export { keysIn } from './object/keysIn.ts';
export { mapKeys } from './object/mapKeys.ts';
export { mapValues } from './object/mapValues.ts';
Expand Down
179 changes: 179 additions & 0 deletions src/compat/object/keys.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
import { describe, expect, it } from 'vitest';
import { keys } from './keys';
import { args } from '../_internal/args';
import { arrayProto } from '../_internal/arrayProto';
import { numberProto } from '../_internal/numberProto';
import { objectProto } from '../_internal/objectProto';
import { primitives } from '../_internal/primitives';
import { strictArgs } from '../_internal/strictArgs';
import { stringProto } from '../_internal/stringProto';
import { constant } from '../util/constant';
import { stubArray } from '../util/stubArray';

/**
* @see https://github.com/lodash/lodash/blob/afcd5bc1e8801867c31a17566e0e0edebb083d0e/test/keys-methods.spec.js#L1
*/
describe('keys', () => {
it('should return the string keyed property names of `object`', () => {
const actual = keys({ a: 1, b: 1 }).sort();

expect(actual).toEqual(['a', 'b']);
});

it('should not include inherited string keyed properties', () => {
function Foo() {
// @ts-ignore
this.a = 1;
}
Foo.prototype.b = 2;

const expected = ['a'];
// @ts-ignore
const actual = keys(new Foo()).sort();

expect(actual).toEqual(expected);
});

it('should treat sparse arrays as dense', () => {
const array = [1];
array[2] = 3;

const actual = keys(array).sort();

expect(actual).toEqual(['0', '1', '2']);
});

it('should return keys for custom properties on arrays', () => {
const array = [1];
// @ts-ignore
array.a = 1;

const actual = keys(array).sort();

expect(actual).toEqual(['0', 'a']);
});

it('should not include inherited string keyed properties of arrays', () => {
arrayProto.a = 1;

const actual = keys([1]).sort();
const expected = ['0'];
expect(actual).toEqual(expected);

delete arrayProto.a;
});

it('should work with `arguments` objects', () => {
const values = [args, strictArgs];
const expected = values.map(constant(['0', '1', '2']));

const actual = values.map(value => keys(value).sort());

expect(actual).toEqual(expected);
});

it('should return keys for custom properties on `arguments` objects', () => {
const values = [args, strictArgs];
const expected = values.map(constant(['0', '1', '2', 'a']));

const actual = values.map(value => {
// @ts-ignore
value.a = 1;
const result = keys(value).sort();
// @ts-ignore
delete value.a;
return result;
});

expect(actual).toEqual(expected);
});

it(`should not include inherited string keyed properties of \`arguments\` objects`, () => {
const values = [args, strictArgs];
const expected = values.map(constant(['0', '1', '2']));

const actual = values.map(value => {
objectProto.a = 1;
const result = keys(value).sort();
delete objectProto.a;
return result;
});

expect(actual).toEqual(expected);
});

it('should work with string objects', () => {
const actual = keys(Object('abc')).sort();

expect(actual).toEqual(['0', '1', '2']);
});

it('should return keys for custom properties on string objects', () => {
const object = Object('a');
object.a = 1;

const actual = keys(object).sort();

expect(actual).toEqual(['0', 'a']);
});

it(`should not include inherited string keyed properties of string objects`, () => {
stringProto.a = 1;

const expected = ['0'];
const actual = keys(Object('a')).sort();

expect(actual).toEqual(expected);

delete stringProto.a;
});

it('should work with array-like objects', () => {
const object = { 0: 'a', length: 1 };
const actual = keys(object).sort();

expect(actual).toEqual(['0', 'length']);
});

it('should coerce primitives to objects (test in IE 9)', () => {
const expected = primitives.map(value => (typeof value === 'string' ? ['0'] : []));

const actual = primitives.map(keys);
expect(actual).toEqual(expected);

// IE 9 doesn't box numbers in for-in loops.
numberProto.a = 1;
expect(keys(0)).toEqual([]);
delete numberProto.a;
});

it('skips the `constructor` property on prototype objects', () => {
function Foo() {}
Foo.prototype.a = 1;

const expected = ['a'];
expect(keys(Foo.prototype)).toEqual(expected);

Foo.prototype = { constructor: Foo, a: 1 };
expect(keys(Foo.prototype)).toEqual(expected);

const Fake = { prototype: {} };
// @ts-ignore
Fake.prototype.constructor = Fake;
expect(keys(Fake.prototype)).toEqual(['constructor']);
});

it('should return an empty array when `object` is nullish', () => {
const values = [, null, undefined];
const expected = values.map(stubArray);

const actual = values.map((value, index) => {
objectProto.a = 1;
const result = index ? keys(value) : keys();
delete objectProto.a;
return result;
});

expect(actual).toEqual(expected);
});
});
Loading