基础介绍
Object 类
在 JavaScript 中,Object 是一个内置对象, 用于表示和操作复杂的数据结构m, 几乎所有类型的值都可以被视为对象, 包括数组、函数和日期等, 甚至 数值, 字符串, 和布尔值都有对应的包装类, Object 对象提供了一系列方法和属性, 用于创建、操作和检查对象, 所以在 JS 中 万物皆对象
更多的内容可以查看MDN文档
创建对象
// 字面量创建
const user = {
id: 1,
name: 'tom'
}
// new 关键字创建
const obj = new Object();
const date = new Date();
const reg = new RegExp("\d");
遍历对象
const user = {
id: 1,
name: 'tom'
}
for (let key in user) {
if(user.hasOwnProperty(key)) {
console.log(key, user[key]);
}
}
获取属性与遍历对象
对象属性的几种类型
- 自身的普通属性
- Symbol 作为 key 的属性
- 通过 Object.defineProperty 及 修饰符对象 定义的属性(主要不可枚举的, 因为对于读取来说可枚举的属性和普通的属性没区别)
- 对象的原型对象的属性
const obj = {
id: 1,
name: 'tom',
email: 'tom@qq.com',
[Symbol('private_property')]: 'xxx',
};
Object.setPrototypeOf(obj, {
proto_property: 'prototype property value',
});
Object.defineProperty(obj, 'cannotIteratable', {
value: 'cannotIteratable',
enumerable: false, // 是否可以枚举(迭代)
readable: true, // 是否可读
writable: true, // 是否可写
});
获取对象属性的方式
// 1. .操作符
console.log(obj.name);
// 2. []动态获取属性
const k = 'name';
console.log(obj[k]);
// 3. Reflect.get
console.log(Reflect.get(obj, 'name'));
// 4. in: 能检测对象是否有某个属性
console.log('name' in obj);
遍历对象的几种方式及原理
for...in
function forIn(obj, handler, onlyOwnProperty = true) {
if (obj === null || typeof obj !== 'object') {
throw new TypeError('obj is not an object');
}
if (typeof handler !== 'function') {
throw new TypeError('handler must be a function');
}
for (const key in obj) {
const value = obj[key];
if (onlyOwnProperty) {
obj.hasOwnProperty(key) && handler(key, value, obj);
} else {
handler(key, value, obj);
}
}
}
const output = (key, val) => console.log({ key, val });
// for in 无法获取出 Symbol 属性 和不可迭代的属性
// { key: 'id': val: 1 }
// { key: 'name', val: 'tom' }
// { key: 'email', val: 'tom@qq.com' }
forIn(obj, output);
// 但是可以获取原型对象上的属性(当然一般是不需要这样操作的)
// { key: 'id': val: 1 }
// { key: 'name', val: 'tom' }
// { key: 'email', val: 'tom@qq.com' }
// { key: 'proto_property', val: 'prototype property value' }
forIn(obj, output, false);
keys/values/entries
- Object.keys: 获取对象自身的可枚举的字符串键属性名组成的数组
- Object.values: 获取对象自身的可枚举的字符串键属性值组成的数组
- Object.entries: 获取对象自有的可枚举字符串键属性的键值对
const keys = Object.keys(obj);
console.log('keys:', keys);
// ['id', 'name', 'email']
const values = Object.values(obj);
console.log('values:', values);
// [1, 'tom', 'tom@qq.com']
const entries = Object.entries(obj);
console.log('entries:', entries);
// [['id', 1], ['name', 'tom'], ['email', 'tom@qq.com']]
// 迭代器方法 无法获取出 Symbol 属性 和不可迭代的属性也无法获取原型对象上的属性
Object.prototype.getOwnPropertyNames
获取对象中所有自有属性(包括不可枚举属性, 但不包括使用 symbol 值作为名称的属性)组成的数组
const propNames = Object.getOwnPropertyNames(obj);
console.log(propNames); // ['id', 'name', 'email', 'cannotIteratable']
// 可以获取 自身的属性和不可迭代的属性, 但是无法获取 Symbol 属性和原型对象属性
Reflect.ownKeys
获取对象自身的属性键组成的数组(有点像 Object.entries)
const ownKeys = Reflect.ownKeys(obj);
console.log(ownKeys);
// ['id', 'name', 'email', 'cannotIteratable', Symbol(private_property)]
// 可以获取: 自身属性, 不可迭代属性, Symbol 属性, 但是无法获取原型对象的属性
几种遍历对象的区别
- for..in: 可以获取自身属性和原型对象的属性(1, 4), 不可获取其他属性
- keys/values/eniters: 可以获取自身的普通属性(1), 不可获取其他属性
- getOwnPropertyNames: 可以获取自身属性,不可枚举属性(1,3), 不可获取其他属性
- Reflect.ownKeys: 可以获取自身属性,不可枚举属性,Symbol 作为 key 的属性(1,3,4) 但是不能获取原型对象的属性
静态方法学习
文档
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object
特殊功能类
Object.defineProperty
Object.defineProperty: 主要用于给对象定义属性, 并且定义描述, 还可以代理 get/set, vue2.x的双向绑定的原理就是基于这种方式
注意, 这个方法是 Object 类上的静态方法, 不要通过实例来调用
var obj = {
id: 101
};
var objId = obj['id'];
var descriptor = {
configurable: true,
// configurable: 是否可以修改描述符, 默认true
// 如果false: 不能删除属性
// 而且 writable 自动为 false, 但是可以强行覆盖(writable: true)
// 而且不能再用 Object.defineProperty 去重新定义
enumerable: true, // 是否可以枚举(可以遍历获取每一项), 如果false就无法用 for in 遍历获取
// value: 'test', // 属性的值, 与 getter/setter 互斥
// writable: true, // 当前的属性是否可以被修改 obj.id = 102, 如果false就无法修改, 与 getter/setter 互斥
get: function() { // getter: 当属性的值被获取时调用
console.log("啊,我的值被获取了");
return objId;
},
set: function(newValue) { // setter: 当属性的值被修改的时候调用
console.log("啊, 我的值被设置成了" + newValue);
objId = newValue;
return true;
}
};
Object.defineProperty(obj, 'id', descriptor);
var objId = obj.id; // 啊,我的值被获取了
obj.id = 1101; // 啊, 我的值被设置成了1101
// 当一个对象设置了 Object.defineProperty
Object.getOwnPropertyDescriptor
// 获取属性的 描述符(descriptor)
var obj = {
id: 101,
};
Object.definePropertyDescriptor(obj, 'name', {
configurable: false,
enumerable: false,
value: 'obj-name-value'
});
var idDescriptor = Object.getOwnPropertyDescriptor(obj, id);
console.log(idDescriptor);
// { configurable: true, enumerable: true, value: 101, writable: true }
var nameDescriptor = Object.getOwnPropertyDescriptor(obj, name);
console.log(nameDescriptor);
// { configurable: false, enumerable: false, value: 'obj-name-value', writable: false }
Object.freeze
冻结对象: 不能增加/修改/删除属性
var obj = {
id: 101,
email: "admin@qq.com"
};
Object.freeze(obj);
delete obj.id; // false
obj.email = 'xxx@qq.com'; // 修改不成功
obj.password = '123456'; // 添加不成功
// 这种方式只会冻结第一层, 如果是对象嵌套, 就无法冻结了
// 如果需要深度冻结,可以使用这种方式
Object.$deepFreeze = function (obj) {
var o = Object.freeze(Object(obj));
var val;
for (var key in o) {
if (Object.hasOwnProperty.call(o, key)) {
val = o[key];
if (val && typeof val === "object") {
return Object.$deepFreeze(val);
}
}
}
return o;
};
var userInfos = {
id: 1001,
infos: {
name: 'tom',
avatar: {
url: 'https://xxx.com/1.jpg'
}
}
};
Object.$deepFreeze(userInfos);
// 修改失败: 抛出异常
userInfos.infos.name = 'alex';
userInfos.infos.avatar.url = 'https://xxx.com/2.jpg';
Object.seal
密封对象: 不能增加/删除修改, 但是可以修改属性的值
var obj = {
id: 101,
email: "admin@qq.com"
};
Object.seal(obj);
delete obj.id; // false
obj.email = 'xxx@qq.com'; // 修改成功
obj.password = '123456'; // 添加不成功
Object.preventExtension
禁止扩展对象
var obj = {
id: 1001,
};
Object.preventExtensions(obj);
// 添加失败(静默失败)
obj.name = 'tom';
// 添加失败(抛出异常)
Object.defineProperty(obj, 'sex', {value: 'boy'});
console.log(obj); // {id: 1001}
赋值/取值类
Object.assign
合并两个对象的属性, 如果有重复的key, 后面的覆盖前面的
然后返回一个新的对象
var o1 = {id: 1001};
var o2 = {id: 101, email: 'admin@qq.com'};
var o3 = Object.assign(o1, o2);
console.log(o3);// { id: 101, email: 'admin@qq.com' }
Object.entries
var obj = { id: 101, email: 'admin@qq.com' };
var entries = Object.entries(obj); // 返回一个迭代器对象
for(item of entries) {
console.log(item);
// 第一次输出: ['id', 101]
// 第二次输出: ['email', 'admin@qq.com']
}
Object.create
创建对象, 并且指定这个对象的原型对象
var plainObj = Object.create(null); // 没有 prototype 的对象
var proto = {
show() {
console.log('function show was executed');
}
};
var obj = Object.create(proto);
obj.show(); // function show was executed
Object.getPrototypeOf
获取对象的原型对象
var d = new Date();
var dateProto = Object.getPrototypeOf(d);
console.log(d.__proto__ === dateProto); // true
console.log(Date.prototype === dateProto); // true
Object.setPrototypeOf
设置对象(实例)的原型对象, 与Object.create方法功能类似, 但是这是设置已经存在的对象,而不是创建
var d = new Date();
var proto = {
show() {
console.log('function show was executed');
}
};
Object.setPrototypeOf(d, proto);
d.show(); // function show was executed
Object.keys/Object.values
- keys: 获取所有的key并且返回一个数组
- values: 获取所有的值并且返回一个数组
var obj = {
id: 1001,
email: "admin@qq.com",
};
var keys = Object.keys(obj);
console.log(keys); // ['id', 'email']
var values = Object.values(obj);
console.log(values); // [1001, 'admin@qq.com']
Object.getOwnPropertySymbols
获取对象上的所有 Symbol 类型的属性, 普通方法是无法获取 Symbol 类型属性的
var obj = {
[Symbol('uid')]: 'uuid',
[Symbol('privateKey')]: 'privateValue'
};
var symbolProps = Object.getOwnPropertySymbols(obj);
console.log(SymbolProps);
// 输出: [ Symbol('uid'), Symbol('privateKey') ]
判断类
Object.is
判断两个值是否是同一个值
console.log(Object.is(true, true)); // true
var o1 = {};
var o2 = o1;
console.log(Object.is(o1, o2)); // true
console.log(Object.is(undefined, undefined)); // true
console.log(Object.is(null, null)); // true
console.log(Object.is(undefined, null)); // false
console.log(Object.is(NaN, NaN)); // true, 可以替代 Number.isNaN 来判断NaN
var fn1 = function () {};
var fn2 = fn1;
console.log(Object.is(fn1, fn2)); // true
Object.isFreezen
判断对象是否是冰冻状态
var obj = {id: 1};
console.log(Object.isFreezen()); // false
Object.freeze(obj);
console.log(Object.isFreezen()); // true
Object.isSealed
判断对象是否是封闭状态
var obj = {id: 1};
console.log(Object.isSealed()); // false
Object.seal(obj);
console.log(Object.isSealed()); // true
Object.isExtensiable
判断对象是否可扩展(非冰冻 && 非封闭)的状态
var obj1 = {id: 1};
console.log(Object.isExtensiable()); // true
Object.seal(obj1);
console.log(Object.isExtensiable()); // false
var obj2 = {id: 2};
console.log(Object.isExtensiable()); // true
Object.freeze(obj2);
console.log(Object.isExtensiable()); // false
关于操作原型对象的建议
为什么用这些方法而不是直接获取/或者赋值?
比如: setPrototypeOf, 好像完全没有必要, 我直接赋值不行吗??
其实在文档中说的非常清楚了, 所以建议用这些内置的方法, 而不是直接操作
实例方法学习
文档
Object.prototype.hasOwnProperty
判断一个属性是否是自有的属性, 而不是继承而来的属性
class Car {
constructor(color) {
this.color = color;
}
}
const car = new Car("black");
console.log(car.hasOwnProperty("toString")); // false
console.log(car.hasOwnProperty("color")); // true
Object.prototype.isPrototypeOf
方法用于检查一个对象是否存在于另一个对象的原型链中
class Animal { }
class Cat extends Animal { }
const a = new Animal();
const c = new Cat();
console.log(Animal.isPrototypeOf(c)); // false
console.log(Cat.isPrototypeOf(c)); // false
function Foo() {}
function Bar() {}
Bar.prototype = Object.create(Foo.prototype);
const bar = new Bar();
console.log(Foo.prototype.isPrototypeOf(bar)); // true
console.log(Bar.prototype.isPrototypeOf(bar)); // true
Object.prototype.toString
返回一个表示该对象的字符串, 或者说: 在对象被转为字符串时(当作字符串使用就会自动转换)自动调用这个方法
class Cat {
constructor(name) {
this.name = name;
}
// override Object.prototype.toString
toString() {
return `${this.name}`;
}
}
const c = new Cat("tom");
console.log(c.toString());
console.log(c + "-some-string");
// es5 like this:
function Dog(name) {
this.name = name;
}
Dog.prototype.toString = function dogToString() {
return `${this.name}`;
};
const d = new Dog('Gabby');
console.log(d.toString());
对象克隆
引用值和原始值
原始值 Primitive values
数据存在栈区, 赋值就直接传值 Number/Boolean/String/null/undefined/Symbol/BigInt
let a = 10;
let b = a;
b += 10;
console.log(a, b); // 10, 20
引用值 Reference Values
数据存在堆中, 变量只是一个指针, 因此赋值是指针传递, 如 Object/Array/Function/Date..
等
const tom = {
name: "tom",
}
// 由于是 "指针" 所以是直接传递内存地址,
// 所以修改的是同一个堆内存空间的数据
const jerry = tom;
jerry.name = "jerry";
console.log(tom.name, jerry.name); // tom, tom
什么叫克隆
由于引用数据类型的数据, 是存在堆区的, 变量名存储的是堆区的内存地址, 所以直接重新创建一个变量然后再赋值是没有用的, 指向的还是同一个堆, 如果想要复制一份一模一样的, 就要遍历数据去克隆
const num1 = 10;
const num2 = num1; // 基本类型可以这样赋值
const obj = { id: 1001, name: "Jerry" };
const obj2 = obj; // 这样赋值是没有作用的, obj2 如果修改了, obj 的值也会被修改
obj2.name = "Tom";
console.log(obj.name === obj2.name); // true, 证明修改的是同一个堆内存中的数据
使用内置API, 转换类型
使用 JSON.stringify 和 JSON.parse
- 先将引用类型转换为基本类型, 然后在赋值
- 缺点:
JSON.stringify
会忽略Function
类型的数据, 如果数据较大, 会比较消耗内存 - 缺点: 无法处理循环引用的对象
const obj1 = { id: 1001, name: "Jerry" };
const obj2 = JSON.parse(JSON.stringify(obj1));
// JSON.stringify 将对象转字符串, 字符串是基本数据类型, 所以是直接复值, 而不是一个堆区的地址
// JSON.parse 将字符串转对象
obj2.name = "Tom";
console.log(obj1.name === obj2.name); // false, 证明修改的不是同一个堆内存中的数据
使用 structuredClone
function deepClone(origin) {
return structuredClone(origin);
}
const obj1 = { id: 1001, name: "Jerry" };
const obj2 = structuredClone(obj1);
console.log(obj1 === obj2, obj1, obj2); // false
使用 MessageChannel
function deepClone(origin) {
return new Promise((resolve) => {
const { port1, port2 } = new MessageChannel();
port1.postMessage(origin);
port2.onmessage = (target) =>resolve(target);
});
}
手动实现
浅拷贝
只会处理第一层, 不会深度的去处理一个对象中所有的引用值
function shallowClone(obj, target) {
target = target || {};
for (var key in obj) {
if (obj.hasOwnProperty(key)) {
target[key] = obj[key];
}
}
return target;
}
var p1 = {
name: "张三",
age: 10,
isBoy: true,
children: {
first: "张小三",
second: "张小五",
},
};
var p2 = shallowClone(p1);
p2.name = "李四";
console.log(p1.name === p2.name); // false
p2.children.first = "李大锤";
console.log(p1.children.first === p2.children.first); // true, 证明此时引用的是同一个堆内存
深拷贝
深度处理一个对象中的所有, 完全复制一个新的
function deepClone(obj, target) {
target = target || {};
var toString = Object.prototype.toString;
var val;
for (var key in obj) {
val = obj[key];
if (obj.hasOwnProperty(key)) {
if (val && typeof val === "object") {
target[key] = toString.call(val) === "[object Array]" ? [] : {};
deepClone(val, target[key]);
} else {
target[key] = val; // 非引用值, 直接赋值
}
}
}
return target;
}
var p1 = {
name: "张三",
age: 10,
isBoy: true,
children: {
first: "张小三",
second: "张小五",
},
};
var p2 = deepClone(p1);
p2.name = "李四";
console.log(p1.name === p2.name); // false
p2.children.first = "李大锤";
console.log(p1.children.first === p2.children.first); // false, 不是同一个堆内存
// 上面写的 deepClone 其实还不够完善, 如果是非 Object 或者 Array
// 上面写的那个 deepClone 就无法克隆了
function deepClone(origin, hashMap = new WeakMap()) {
if (!origin || typeof origin !== "object") {
return origin;
}
if (origin instanceof Date) {
return new Date(origin);
}
if (origin instanceof RegExp) {
return new RegExp(origin);
}
const hashKey = hashMap.get(origin);
if (hashKey) {
return hashKey;
}
const target = new origin.constructor();
hashMap.set(origin, target);
for (const key in origin) {
if (Object.hasOwnProperty.call(origin, key)) {
target[key] = deepClone(origin[key], hashMap);
}
}
return target;
}
树形数据处理
什么叫树型数据?
这其实是数据结构与算法中的一个概念
树形结构的数据
const data = [
{
label: "肉类",
children: [
{
label: "猪肉",
children: [{ label: "五花肉" }, { label: "里脊肉" }],
},
{
label: "鸡肉",
children: [{ label: "鸡腿" }, { label: "鸡翅" }],
},
],
},
{
label: "蔬菜",
children: [
{
label: "叶菜类",
children: [{ label: "大白菜" }, { label: "小白菜" }],
},
{
label: "根茎类",
children: [{ label: "萝卜" }, { label: "土豆" }],
},
],
},
];
遍历树形结构数据
纵向遍历
深度优先, 所以也叫深度遍历
/**
* 深度优先遍历(递归)
* @param {Array} data 需要遍历的树形结构数
* @param {String} children 子类的key
* @param {Function} handler 遍历每一项的callback
* @param {Object} thisArg
* @returns {Array}
*/
function eachTreeDfs(data, children, handler, thisArg = null) {
if (!Array.isArray(data)) {
throw new TypeError("data must be instance of Array");
}
if (typeof handler !== "function") {
throw new TypeError("handler is not a function");
}
if (typeof children !== "string") {
throw new TypeError("the children mest be a string");
}
// 遍历函数
function each(data, handler) {
for (let i = 0; i < data.length; i++) {
const node = data[i];
handler(node);
const subNodes = node[children];
if (subNodes && Array.isArray(subNodes)) {
each(subNodes, handler);
}
}
}
each(data, handler.bind(thisArg));
}
// 深度优先遍历树形结构数据并转成数组
function tree2list(data, children) {
const items = [];
eachTreeDfs(data, children, items.push, items);
return items;
}
console.log(tree2list(data, "children"));
// [
// { label: '肉类', children: [...] },
// { label: '猪肉', children: [...] },
// { label: '五花肉' },
// { label: '里脊肉' },
// { label: '鸡肉', children: [...] },
// { label: '鸡腿' },
// { label: '鸡翅' },
// { label: '蔬菜', children: [...] },
// { label: '叶菜类', children: [...] },
// { label: '大白菜' },
// { label: '小白菜' },
// { label: '根茎类', children: [...] },
// { label: '萝卜' },
// { label: '土豆' },
// ];
横向遍历
广度优先, 所以也叫广度遍历, 层序遍历
/**
* 利用栈的结构,横向遍历树形结构数据生成平铺数据
* @param {Array} data 需要遍历的树形结构数
* @param {String} children 子类的key
* @param {Function} handler 遍历每一项的处理函数
* @param {Object} thisArg
* @returns
*/
function eachTreeBfs(data, children, func, thisArg = null) {
if (!Array.isArray(data)) {
throw new TypeError("data must be instance of Array");
}
if (typeof children !== "string") {
throw new TypeError("childrenKey must be a string");
}
if (typeof func !== "function") {
throw new TypeError("callback is not a function");
}
const handler = func.bind(thisArg);
let node, subNodes;
let stack = [].concat(data); // 不改变原数据
while (stack.length) {
node = stack.shift();
handler(node);
subNodes = node[children];
if (subNodes && Array.isArray(subNodes)) {
stack = stack.concat(subNodes);
}
}
}
function tree2list(data, children) {
const items = [];
eachTreeBfs(data, children, items.push, items);
return items;
}
// [
// { label: "肉类", children: [..] },
// { label: "蔬菜", children: [..] },
// { label: "猪肉", children: [..] },
// { label: "鸡肉", children: [..] },
// { label: "叶菜类", children: [..] },
// { label: "根茎类", children: [..] },
// { label: "五花肉" },
// { label: "里脊肉" },
// { label: "鸡腿" },
// { label: "鸡翅" },
// { label: "大白菜" },
// { label: "小白菜" },
// { label: "萝卜" },
// { label: "土豆" },
// ];
将平铺数据转树型结构数据
所谓平铺的数据, 就可以简单理解为数组(线性数据结构)
平铺数据(有关系的)
const menus = [
{
id: 1,
desc: "用户管理",
path: "",
level: 0,
pid: 0,
},
{
id: 2,
desc: "用户列表",
path: "/users",
level: 1,
pid: 1,
},
{
id: 3,
desc: "权限管理",
path: null,
level: 0,
pid: 0,
},
{
id: 4,
desc: "角色管理",
path: "/roles",
level: 1,
pid: 3,
},
{
id: 5,
desc: "权限管理",
path: "/permissions",
level: 1,
pid: 3,
},
{
id: 6,
desc: "测试管理",
path: "/test",
level: 2,
pid: 5,
},
];
递归(filter)
/**
* 克隆递归将线性数据变成树形结构的数据
* @param {Array} data 待遍历的数据
* @param {Object} options 生成树形数据的参数选项
* @param {String|Number} options.rootId 顶层id的值
* @param {String} options.idKey 当前数据的 id
* @param {String} options.pidKey 当前数据的父级 id
* @param {String} options.children 当前数据的子级 id
* @param {Boolean} options.cloneSource 是否需要克隆原数据
* @returns
*/
function list2tree(data, options = {}) {
if (!Array.isArray(data)) {
throw new TypeError("data is not an array");
}
const config = Object.assign(
{
root: 0,
idKey: "id",
pidKey: "pid",
children: "children",
cloneSource: true,
},
options
);
// 克隆一份不会改变原数据
const { root: rootId, idKey, pidKey, children, cloneSource } = config;
const target = cloneSource ? JSON.parse(JSON.stringify(data)) : data;
return target.filter((root) => {
const children = target.filter((child) => root[idKey] === child[pidKey]);
if (children.length) {
root[children] = children;
}
return root.pid === rootId;
});
}
// [
// {
// id: 1,
// desc: "用户管理",
// path: "",
// pid: 0,
// subNodes: [{ id: 2, desc: "用户列表", path: "/users", pid: 1 }],
// },
// {
// id: 3,
// desc: "权限管理",
// path: null,
// pid: 0,
// subNodes: [
// { id: 4, desc: "角色管理", path: "/roles", pid: 3 },
// {
// id: 5,
// desc: "权限管理",
// path: "/permissions",
// pid: 3,
// subNodes: [{ id: 6, desc: "测试管理", path: "/test", pid: 5 }],
// },
// ],
// },
// ];
映射(手动 for)
/**
* 使用对象映射key+修改对象引用来生成树形数据
* @param {Array} data
* @param {Object} options
* @param {Number|String} option.root
* @param {String} option.id
* @param {String} option.pid
* @param {String} option.children
* @returns {Array}
*/
function list2tree(data, options = {}) {
if (!Array.isArray(data)) {
throw new TypeError("data is not an array");
}
const config = Object.assign(
{
root: 0,
idKey: "id",
pidKey: "pid",
children: "children",
cloneSource: true,
},
options
);
const { root, idKey, pidKey, children, cloneSource } = config;
const target = cloneSource ? JSON.parse(JSON.stringify(data)) : data;
const dataMap = {};
const result = [];
for (let i = 0; i < target.length; i++) {
const item = target[i];
const id = item[idKey];
const pid = item[pidKey];
if (!dataMap[id]) {
// { [id]: item }
dataMap[id] = item;
}
// 顶级类
if (pid === root) {
result.push(item);
continue;
}
// 非顶级类
if (!dataMap[pid]) {
// 没有顶级类的二级类(一般是脏数据)
dataMap[pid] = {};
}
if (!Array.isArray(dataMap[pid][children])) {
dataMap[pid][children] = [];
}
dataMap[pid][children].push(item);
}
return result;
}
console.log(list2tree(menus, 0, "id", "pid", "subNodes"));
// [
// {
// id: 1,
// desc: "用户管理",
// path: "",
// pid: 0,
// subNodes: [{ id: 2, desc: "用户列表", path: "/users", pid: 1 }],
// },
// {
// id: 3,
// desc: "权限管理",
// path: null,
// pid: 0,
// subNodes: [
// { id: 4, desc: "角色管理", path: "/roles", pid: 3 },
// {
// id: 5,
// desc: "权限管理",
// path: "/permissions",
// pid: 3,
// subNodes: [{ id: 6, desc: "测试管理", path: "/test", pid: 5 }],
// },
// ],
// },
// ];