很实用的树形数据操作

532 阅读2分钟

在实际工作中,树形数据很常见,例如vue中的路由数据,系统菜单也是树形数据,组织管理也是树状的,DOM解构也是如此。例如有如下数据结构:

let tree = [
    {
        id:0,
        name:'江西',
        children:[
            { id:'01',name:'南昌',children:[]},
            { id:'02',name:'九江',children:[]}
        ]
    },{
        id:1,
        name:'湖南',
        children:[
            { id:'11',name:'长沙',children:[]},
            { id:'12',name:'衡阳',children:[]},
            { id:'13',name:'常德',children:[]},
        ]
    }
]

在理论上讲这种结构可以无限延伸。每个节点下可能有children,children下可能还有children. 下面列举几种对上述结构操作的常用方法仅供参考:
(以下代码都是假如属性都是通过children字段进行拓展的,每个对象唯一标识为id字段。)

平展树

function flatTree(data) {
    let result = []   
    for(let i = 0; i<data.length;i++){
      let obj = {
        id:data[i].id,
        name:data[i].name
      }
      result.push(obj)
      if (data[i]['children'] && data[i]['children'].length) {
        result = result.concat(flatTree(data[i]['children']))
      } 
    }
    return result
}
// [{id:0,name:'江西'},{id:'01',name:'南昌'}...]

查找某个节点


function findNode(data,id) {
    let result = []   
    for(let i = 0; i<data.length;i++){
      if (data[i].id === id) {
        result.push(data[i])
      }
      if (data[i]['children'] && data[i]['children'].length) {
        result = result.concat(findNode(data[i]['children'],id))
      } 
    }
    return result
}

通过查找某个id将返回一个数组结构,如果有id重复的,会返回所有重复的id对象数组

删除节点

// 删除树操作
function deleteNode(data,id) {
    for (let i =0;i<data.length;i++) {
        if(data[i].id === id) {
            data.splice(i,1)
            i=i-1 // 防止有多个id相同情况下删除不干净
        } else {
            if (data[i]['children'] && data[i]['children'].length) {
                deleteNode(data[i]['children'],id)
            }else{
                continue
            } 
        }
        
    }
    return 
}

修改树

// 修改树
function updateNode(data,id,attr,callback) {
    for(let i =0;i<data.length;i++) {
        if(data[i].id === id) {
            if (typeof callback == 'function') {
                data[i][attr] = callback(data[i])
            } else {
                data[i][attr] = callback
            }
            continue
        }
        if (data[i]['children'] && data[i]['children'].length) {
            updateNode(data[i]['children'],id,attr,callback)
        }

    }
}

例如我们需要往id=0节点增加一个属性enName:'江西',只需要:updateNode(tree,0,'enName','jiangxi') 假如我们需要判断id=0节点下是否有children,如果有,我们增加一个hasChild:true,所以callback参数也可以传入一个回调函数,第一个参数为当前节点

updateNode(tree,'12',0,(node)=>{
    return node.children && node.children.length ? true:false
})

生成path

function formPath(data,parentId=[]) {
    for (let i = 0;i<data.length;i++) {
        data[i].path = parentId.concat(data[i].id)
        if(data[i].children && data[i].children.length){
            formPath(data[i].children,data[i].path)
        }
    }
}
formPath(tree)

同级节点上下移动一位

function nodeMoveUpOrDown(data,id,flag=1) {
    for(let i = 0;i<data.length;i++) {
        if(data[i].id === id && flag === 1 && i!== data.length-1) {
            const temp = data[i+1]
            data[i+1] = data[i]
            data[i] = temp
        }
        if(data[i].id === id && flag === 0 && i!== 0) {
            const temp = data[i-1]
            data[i-1] = data[i]
            data[i] = temp
        }
        if(data[i].children && data[i].children.length) {
            nodeMoveUpOrDown(data[i].children,id,flag)
        }
    }
}

向上移动flag=0 向下移动flag=1

同级节点下两个节点替换

function nodeMove(data,current,target) {
    let temp=[]
    for(let i=0;i<data.length;i++){
        if(data[i].id === current || data[i].id === target){
            temp.push(i)
        }
        if(data[i].children && data[i].children.length){
            nodeMove(data[i].children,current,target)
        }

    }
    if(temp.length === 2){
        let before = data[temp[0]]
        data[temp[0]] = data[temp[1]]
        data[temp[1]] = before
    }
}