资讯专栏INFORMATION COLUMN

localStorage实现本地储存树形菜单

William_Sang / 2597人阅读

摘要:因为任务需要添加到树的结构上,所以要记录任务是添加到哪个结点上的,需要为每个树结点添加一个作为标识以便于在结点上添加任务,树状结构中每个结点的按照树的先序遍历将结点的依次储存于数组中。

localStorage实现本地储存树形菜单

最近在写一个Todo-list的页面,页面布局和操作都写完后,想要用localStorage实现本地储存。然而对储存数据的方法一无所知,就先去了解了web的数据储存。

数据储存

常用的web的数据储存有cookie和Web Storage储存机制。

cookie

cookie是“小型文本文件”,主要用途是辨别用户身份、保存用户登录信息。cooki是由服务器端生成、储存在客户端上的数据(通常经过加密)。

Cookie会被附加在每个HTTP请求中,所以无形中增加了流量。

HTTP请求中的Cookie是明文传递的,安全性成问题。(除非用HTTPS)

Cookie的大小限制在4KB左右。于复杂的存储需求来说是不够用的。

Web Storage

Web Storage定义了两种储存数据的对象,localStorage和sessionStorage,它们储存大小一般为5MB,机制如下:

sessionStorage由浏览器存储某个会话(browsing session)的数据,数据在当前会话下有效,刷新页面数据依旧存在,但在关闭页面或浏览器后被清除。

localStorage由浏览器存储数据,数据没有过期时间(expiration time),也就是浏览器关闭再重新打开后数据仍然存在。

localStorage对象的操作

在存储中设置值:Storage.setItem()
从存储中获取值:Storage.getItem()
响应存储的变化:window.addEventListener("storage", function(e){});
删除数据记录:Storage.removeItem() 接受一个参数——你想要移除的数据项的键,然后会将对应的数据项从域名对应的存储对象中移除。Storage.clear() 不接受参数,只是简单地清空域名对应的整个存储对象。

树形菜单的数据储存

在我的todo-list页面中,用户对树的操作有:

在树形菜单上添加、删除任务分类

在任务分类中添加、删除该分类中的任务。
也是是说用户的每次操作后都要更新存储的数据,以便在刷新页面后显示出用户操作后更改过的页面。

由于本地储存只能存字符串数据,所以存储属性菜单可以用到JSON。用JSON.parse将一个JavaScript对象字符串化为JSON,然后整个存入localStorage来实现保存。用JSON.stringify将JSON字符串解析成为一个JavaScript对象来从存储中获取值。
那么怎么用对象存储树形菜单呢,我想到了两个方式。

方式一

一种是用一个对象treeframe存储树的结构(所有的任务分类,不包括分类中的任务)以及树结点中的任务,存储形式如下。一旦对树形菜单执行任何一项操作,就更新treeframe对象。

var treeframe = {
    "任务列表":{
        "使用说明":[
            {
                "title":"普通任务列表",
                "content":"可以设定任务的名称、任务描述、任务截止时间",
                "endTime":"2016-05-20",
                "type":"normal",
            },
            {
                "title":"添加新分类",
                "content":"通过左侧自定义分类的文件夹图标添加新分类",
                "endTime":"2017-07-20",
                "type":"normal",
            }
        ],
        "今日任务":{},
        "短期任务":{
            "7月":{},
            "8月":{},
            "9月":{}
        },
        "长期任务":{}
     }
}
方式二

另一种方式是用一个对象treeframe存储树的结构,还有一个对象allTasks存储所有结点中的任务。这种方式只需要在添加、删除任务分类时更新树的结构treeframe;添加、删除任务时更改allTasks。因为任务需要添加到树的结构上,所以要记录任务是添加到哪个结点上的,需要为每个树结点添加一个id作为标识以便于在结点上添加任务,树状结构中每个结点的id按照树的先序遍历将结点的id依次储存于数组treeClassify.list中。为了每一个新增的结点的id都不与已存在的id的结点相同,用一个计数器fatherIdjsq来记录可添加的新id。
存储形式如下:

var treeframe = {
    "任务列表":{
        "使用说明":{},
        "今日任务":{},
        "短期任务":{
            "7月":{},
            "8月":{},
            "9月":{}
        },
        "长期任务":{}
     }
}

var allTasks = {
    "list" : [    {
                "fatherId":"fatherId1"
                "title":"普通任务列表",
                "cnode":{"value":"普通任务列表"},
                "pnode":{"value":"使用说明"},
                "content":"可以设定任务的名称、任务描述、任务截止时间",
                "endTime":"2016-05-20",
                "type":"normal",
            },
            {
                "fatherId":"fatherId5"
                "title":"添加新分类",
                "cnode":{"value":"添加新分类"},
                "pnode":{"value":"使用说明"},
                "content":"通过左侧自定义分类的文件夹图标添加新分类",
                "endTime":"2017-07-20",
                "type":"fast",
            }]
};
var treeClassfiy = {"list":["fatherId0","fatherId1","fatherId2","fatherId3","fatherId4","fatherId5","fatherId6","fatherId8","fatherId7"]}
var fatherIdjsq = 9

我觉得如果最开始写整个todo-list时对整个页面的功能和操作以及逻辑都理的特别清楚的话,第一种存储方法一定用起来更加方便。但是我选了第二种,因为这样便于我分别对树的结构和任务列表进行操作,和我之前写好的代码比较容易融合在一起。
所以最终要存储的数据如下:

- 树状结构的存储:对象字面量形式与json的相互转化
- 树状结构中每个结点的id:按照树的先序遍历将结点的id依次储存于数组中
- 所有任务:每一个任务存储于一个对象中,所有任务的对象组成一个数组
- 可以添加的新id
树形菜单的操作与储存 1. 删除任务分类

更改树状结构,在界面上删除这个分类的后,将这个分类的id从存储id的数组中删去。

var jsq = 0;                                                      //计数器jsq用来记录按先序遍历树,并将结点存于数组时,该结点在数组中的位置
preOrder(treedata);                                               //先序遍历treeframe(treeframe初始储存于treedata)
function preOrder(treeframe) {
    for(var key in treeframe) {
        if(jsq===fatherIdArr.indexOf(choseDiv.id)) {              //查找到当前要删除的结点(通过查看当前要删除结点的id在储存结点id的数组(treeClassfiy.list)中的位置)
            delete treeframe[key];                                   //通过detele删除对象的属性来删除结点
            var pos = treeClassifyIdArr.list.indexOf(choseDiv.id);//查找当前要删除的结点id在储存结点id的数组中(treeClassfiy.list)的位置
            treeClassifyIdArr.list.splice(pos,1);                  //把id从数组中删除
            fatherIdArr.splice(jsq, 1);
            break;
        }
        jsq++;
        if(typeof treeframe[key] === "object") {
            preOrder(treeframe[key]);
        }
    }
}
localStorage.setItem("treeframe", JSON.stringify(treedata));      //将更新的treedata存入设置的值treeframe中
2. 添加任务分类

每次手动添加任务分类时,树状结构会改变。也就是说树状结构的改变,意味着树的结点增加/删除了,存储结点id的数组也要改变。
所以要更改树状结构,为新添的分类设置一个新的id,在界面上创建这个分类的后,将这个分类的id插入到存储id的数组中。

var jsq = 0;
preOrder(treedata);
function preOrder(treeframe) {
    for(var key in treeframe) {
        if(jsq===fatherIdArr.indexOf(choseDiv.id)) {             //找到被选中的结点
            fatherIdArr.push("fatherId"+fatherIdjsq);            //先在数组中添加新的id,使新的结点渲染到页面上
            treeframe[key][value] = {};                          //更新树状结构,添加结点(为记录树的结构的对象添加属性)
            preOrder2(treeframe[key]);
            function preOrder2(treeframe) {                      //jsq记录被选中结点的的最后一个子节点
                for(var key in treeframe) {
                    jsq++;
                    if(typeof treeframe[key] === "object") {
                        preOrder2(treeframe[key]);
                    }
                }
            }
            break;
        }
        jsq++;
        if(typeof treeframe[key] === "object") {
            preOrder(treeframe[key]);
        }
    }
}
localStorage.setItem("treeframe", JSON.stringify(treedata));     //将更新的treedata存入设置的值treeframe中
/*此处渲染页面*/ 
fatherIdArr.pop();                                               //新的结点渲染到页面上后删除该结点的id
fatherIdArr.splice(jsq, 0, "fatherId"+fatherIdjsq++);            //更新该结点id在id数组中的位置
treeClassifyIdArr.list = [];                                     //将id数组中的元素添加到对象中
for(var j=0;j
3. 删除任务
tasklistArr.splice(i,1);
allTasks.list = [];
for(var j=0;j
4. 添加任务
allTasks.list = [];
for(var i=0;i
5. 测试本地存储是否已被填充
if(!localStorage.getItem("treeClassify")) {
//本地没有存储,加载初始设置的页面
} else {
//本地有存储,不加载初始设置的页面,而是根据储存来渲染页面
}
所有存储操作的封装
var Storage = (function(mod) {
    // 删除、添加、编辑快速任务和普通任务的存储
    mod.TaskChange = function() {
        allTasks.list = [];
        for(var i=0;i
需要注意的地方

加载初始设置的页面的过程中,不需要存储任何数据,也就是不需要操作localStorage对象。这样的话如果用户不对页面进行操作,就没有任何数据需要存储。只有在用户对页面进行操作后,才会储存并再页面再次刷新时按照储存的内容进行页面的渲染。

在线查看demo

在线demo可供参考,欢迎review代码,求批评、建议和交流:p
localStorage实现本地储存树形菜单_demo

参考资料

详说 Cookie, LocalStorage 与 SessionStorage
JSON.stringify
链使用 Web Storage API

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/86455.html

相关文章

  • 前端面试之htm5新特性

    摘要:今天来谈谈前端面试中基本上每次一面的时候都会被问到的一个问题,那就是的新特性。新表单元素元素,表示电话号码。和通过设置和特性,可以将输入框的数值输入范围限定在最低值和最高值之间。一旦为某输入型控件设置了特性,那么此项必填,否则无法提交表单。 今天来谈谈前端面试中基本上每次一面的时候都会被问到的一个问题,那就是html5的新特性。这个是学习前端必须掌握的基础知识。 新增的元素 html5...

    teren 评论0 收藏0
  • htm5新特性(转)

    摘要:转自今天来谈谈前端面试中基本上每次一面都会被问到的一个问题,那就是的新特性了。元素,表示生成密匙。和通过设置和特性,可以将输入框的数值输入范围限定在最低值和最高值之间。一旦为某输入型控件设置了特性,那么此项必填,否则无法提交表单。 转自:http://hyuhan.com/2017/07/06/... 今天来谈谈前端面试中基本上每次一面都会被问到的一个问题,那就是html5的新特性了。...

    focusj 评论0 收藏0
  • Chrome扩展程序开发

    摘要:这一步可以参考应用商店上传扩展程序一文最后终于搞定,线上可见学习资源建立扩展程序插件开发攻略如何成为一名应用开发者扩展的开发下一步插件功能丰富化插件可在网页上高亮展示标记的文本用重构需要使用框架吗注本文源码位于仓库,线上产品见和 十一在家无聊时开发了这个项目。其出发点是想通过chrome插件,来保存网页上选中的文本。后来就顺手把前后端都做了(Koa2 + React): chrome...

    dinfer 评论0 收藏0
  • 利用localStorage本地储存js文件

    摘要:通过采用同步的形式获取内容,取得内容后,执行文件的内容,设置保存到中,再删除中上个版本的文件。同步获取文件内容。 利用localStorage储存js文件,只有在第一次访问该页面的时候加载js文件,以后在访问的时候加载本地localStorage执行 封装lsFile类 有url、filename(key前缀)、lname(key)、filetext(值)属性 var lstora...

    haitiancoder 评论0 收藏0

发表评论

0条评论

William_Sang

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<