lodash.get
一般实现
js
function customGet(obj, path, defaultValue) {
if (!obj || typeof obj !== "object") return defaultValue;
const pathList = Array.isArray(path) ? path : path.split(".");
try {
for (let i = 0; i < pathList.length; i++) {
const key = pathList[i];
if (/\[\d+\]/.test(key)) {
// 处理数组索引
const index = parseInt(key.match(/\d+/)[0]);
obj = obj[index];
} else {
// 处理属性为 null 的情况
if (obj[key] === null || obj[key] === undefined) {
obj = defaultValue;
break;
}
obj = obj[key];
}
}
return obj !== undefined ? obj : defaultValue;
} catch (err) {
// 处理异常,例如属性路径中间的属性为 undefined 或者访问数组越界等情况
return defaultValue;
}
}
const examObj = {
a: {
b: {
c: [1, 2, 3]
}
}
}
const result = customGet(examObj, 'a.b.c[1]'. 'Not Found')
console.log(result) // output: 2
const notFoundResult = customGet(examObj, 'x.y.z', 'Not Found')
console.log(notFoundResult) // output: Not Found
lodash 实现
缓存 memorize
js
/**
* Creates a function that memoizes the result of `func`. If `resolver` is
* provided, it determines the cache key for storing the result based on the
* arguments provided to the memoized function. By default, the first argument
* provided to the memoized function is used as the map cache key. The `func`
* is invoked with the `this` binding of the memoized function.
*
* **Note:** The cache is exposed as the `cache` property on the memoized
* function. Its creation may be customized by replacing the `memoize.Cache`
* constructor with one whose instances implement the
* [`Map`](http://ecma-international.org/ecma-262/7.0/#sec-properties-of-the-map-prototype-object)
* method interface of `clear`, `delete`, `get`, `has`, and `set`.
*
* @since 0.1.0
* @category Function
* @param {Function} func The function to have its output memoized.
* @param {Function} [resolver] The function to resolve the cache key.
* @returns {Function} Returns the new memoized function.
* @example
*
* const object = { 'a': 1, 'b': 2 }
* const other = { 'c': 3, 'd': 4 }
*
* const values = memoize(values)
* values(object)
* // => [1, 2]
*
* values(other)
* // => [3, 4]
*
* object.a = 2
* values(object)
* // => [1, 2]
*
* // Modify the result cache.
* values.cache.set(object, ['a', 'b'])
* values(object)
* // => ['a', 'b']
*
* // Replace `memoize.Cache`.
* memoize.Cache = WeakMap
*/
function memoize(func, resolver) {
if (
typeof func !== "function" ||
(resolver != null && typeof resolver !== "function")
) {
throw new TypeError("Expected a function");
}
const memoized = function (...args) {
const key = resolver ? resolver.apply(this, args) : args[0];
const cache = memoized.cache;
if (cache.has(key)) {
return cache.get(key);
}
const result = func.apply(this, args);
memoized.cache = cache.set(key, result) || cache;
return result;
};
memoized.cache = new (memoize.Cache || Map)();
return memoized;
}
memoize.Cache = Map;
export default memoize;
toKey
js
const toString = Object.prototype.toString;
/**
* Gets the `toStringTag` of `value`.
*
* @private
* @param {*} value The value to query.
* @returns {string} Returns the `toStringTag`.
*/
function getTag(value) {
if (value == null) {
return value === undefined ? "[object Undefined]" : "[object Null]";
}
return toString.call(value);
}
/**
* Checks if `value` is classified as a `Symbol` primitive or object.
*
* @since 4.0.0
* @category Lang
* @param {*} value The value to check.
* @returns {boolean} Returns `true` if `value` is a symbol, else `false`.
* @example
*
* isSymbol(Symbol.iterator)
* // => true
*
* isSymbol('abc')
* // => false
*/
function isSymbol(value) {
const type = typeof value;
return (
type === "symbol" ||
(type === "object" && value != null && getTag(value) === "[object Symbol]")
);
}
/** Used as references for various `Number` constants. */
const INFINITY = 1 / 0;
/**
* Converts `value` to a string key if it's not a string or symbol.
*
* @private
* @param {*} value The value to inspect.
* @returns {string|symbol} Returns the key.
*/
function toKey(value) {
if (typeof value === "string" || isSymbol(value)) {
return value;
}
const result = `${value}`;
return result === "0" && 1 / value === -INFINITY ? "-0" : result;
}
export default toKey;
castPath
js
// castPath
import isKey from "./isKey.js";
import stringToPath from "./stringToPath.js";
/**
* Casts `value` to a path array if it's not one.
*
* @private
* @param {*} value The value to inspect.
* @param {Object} [object] The object to query keys on.
* @returns {Array} Returns the cast property path array.
*/
function castPath(value, object) {
if (Array.isArray(value)) {
return value;
}
return isKey(value, object) ? [value] : stringToPath(value);
}
export default castPath;
stringToPath
js
import memoize from "../memoize.js";
/** Used as the maximum memoize cache size. */
const MAX_MEMOIZE_SIZE = 500;
/**
* A specialized version of `memoize` which clears the memoized function's
* cache when it exceeds `MAX_MEMOIZE_SIZE`.
*
* @private
* @param {Function} func The function to have its output memoized.
* @returns {Function} Returns the new memoized function.
*/
function memoizeCapped(func) {
const result = memoize(func, (key) => {
const { cache } = result;
if (cache.size === MAX_MEMOIZE_SIZE) {
cache.clear();
}
return key;
});
return result;
}
const charCodeOfDot = ".".charCodeAt(0);
const reEscapeChar = /\\(\\)?/g;
const rePropName = RegExp(
// Match anything that isn't a dot or bracket.
"[^.[\\]]+" +
"|" +
// Or match property names within brackets.
"\\[(?:" +
// Match a non-string expression.
"([^\"'][^[]*)" +
"|" +
// Or match strings (supports escaping characters).
"([\"'])((?:(?!\\2)[^\\\\]|\\\\.)*?)\\2" +
")\\]" +
"|" +
// Or match "" as the space between consecutive dots or empty brackets.
"(?=(?:\\.|\\[\\])(?:\\.|\\[\\]|$))",
"g"
);
/**
* Converts `string` to a property path array.
*
* @private
* @param {string} string The string to convert.
* @returns {Array} Returns the property path array.
*/
const stringToPath = memoizeCapped((string) => {
const result = [];
if (string.charCodeAt(0) === charCodeOfDot) {
result.push("");
}
string.replace(rePropName, (match, expression, quote, subString) => {
let key = match;
if (quote) {
key = subString.replace(reEscapeChar, "$1");
} else if (expression) {
key = expression.trim();
}
result.push(key);
});
return result;
});
export default stringToPath;
baseGet
js
import castPath from "./castPath.js";
import toKey from "./toKey.js";
/**
* The base implementation of `get` without support for default values.
*
* @private
* @param {Object} object The object to query.
* @param {Array|string} path The path of the property to get.
* @returns {*} Returns the resolved value.
*/
function baseGet(object, path) {
path = castPath(path, object);
let index = 0;
const length = path.length;
while (object != null && index < length) {
object = object[toKey(path[index++])];
}
return index && index === length ? object : undefined;
}
/**
* Gets the value at `path` of `object`. If the resolved value is
* `undefined`, the `defaultValue` is returned in its place.
*
* @since 3.7.0
* @category Object
* @param {Object} object The object to query.
* @param {Array|string} path The path of the property to get.
* @param {*} [defaultValue] The value returned for `undefined` resolved values.
* @returns {*} Returns the resolved value.
* @see has, hasIn, set, unset
* @example
*
* const object = { 'a': [{ 'b': { 'c': 3 } }] }
*
* get(object, 'a[0].b.c')
* // => 3
*
* get(object, ['a', '0', 'b', 'c'])
* // => 3
*
* get(object, 'a.b.c', 'default')
* // => 'default'
*/
function get(object, path, defaultValue) {
const result = object == null ? undefined : baseGet(object, path);
return result === undefined ? defaultValue : result;
}
export default get;