博客列表 >JS五个精选的实战案例

JS五个精选的实战案例

吾逍遥
吾逍遥原创
2020年11月07日 18:17:358666浏览

一、实战前准备知识

1、DOM元素常用操作

https://www.php.cn/blog/detail/24740.html已经介绍了DOM元素的获取和遍历,不清楚可以去熟悉下,这里介绍DOM元素常用操作,包括创建元素、添加元素、插入元素、替换元素、删除元素以及大量添加元素时优化方案 文档片断DocumentFragment 。这里增加了一些拓展测试,值得一看

1、 创建元素createElement 语法:document.createElement(‘tag’), 根是document参数是标签名称,用单引号或双引号包裹 ,创建元素对象并不在页面中 ,而在 内存中 ,没有添加到页面,需要挂载到页面才显示。这里要注意标签名称一般是HTML规范的名称,经测试也可以是自定义的。

  1. // 1.创建元素对象,此时在内存中,需要挂载才可显示
  2. const ul = document.createElement('ul');
  3. ul.id = 'ul1';
  4. // 创建元素时,标签名称也可以是自定义的
  5. const score = document.createElement('score');
  6. score.innerHTML = '大家好';

2、 添加元素appendChild 也称 挂载 ,语法:父元素对象.appendChild(新元素对象), 根是父元素对象 ,添加元素前提要有一个父元素,否则无法定位位置, 参数是元素对象,不要引号 元素对象可以是createElement创建的元素对象,也可以是获取的或遍历的得到的元素对象。在测试时发现了它的一个 有趣现象 ,就是测试同一个父元素反复添加和添加到不同父元素的结果

  • 父元素对象 可以是在内存中的元素对象(createElement或createDocumentFragment),也可以页面中元素对象。常见的页面元素对象有document.head,document.body和document.documetElement(Html对象)
  • 参数中元素对象 同父元素对象,但要注意不能是页面中唯一的对象,如body对象、head对象等。经测试document.appendChild时会报唯一对象冲突错误。
  • 总是在尾部添加 append英文翻译是追加,就是在最后添加元素的意思。
  • 同一个父元素反复添加 先说测试结果,反复添加最终是只算一个 ,估计是元素对象在页面文档流中都有唯一标号,不可以重复出现。
  • 添加到不同父元素 这个更有趣,它会 删除以前所在位置出现在最后添加的位置 。这个结果就非常有用了,经典应用场景 就是用户在备用选项中选择,如选择了某项爱好后,它就在提供的备用选项中移动到选择中。
  1. <style>
  2. ul { width: 10em; height: 5em; }
  3. #ul1 { background-color: aquamarine; }
  4. #ul2 { background-color: seagreen; }
  5. </style>
  6. <script>
  7. const ul = document.createElement('ul');
  8. ul.id = 'ul1';
  9. // 2.页面加载元素,父元素一般是页面中元素对象
  10. document.body.appendChild(ul);
  11. const li = document.createElement('li');
  12. li.innerHTML = '123';
  13. // appendChild父元素可以是内存中元素对象
  14. ul.appendChild(li);
  15. const ul2 = document.createElement('ul');
  16. ul2.id = 'ul2';
  17. document.body.appendChild(ul2);
  18. // 当同一个元素添加到不同父元素时,出现有趣移动效果,这个应用场景就是用户在备用选项中选择
  19. ul.onclick = function (ev) {
  20. ul2.appendChild(li);
  21. };
  22. ul2.onclick = function (ev) {
  23. ul.appendChild(li);
  24. };
  25. </script>

appendChild

3、 插入元素insertBefore 相比于appendChild只能在最后添加元素对象的限制外,insertBefore可以在指定元素对象前插入元素对象。语法:父元素对象.insertBefore(新元素对象,参考元素对象)。

  • 父元素对象和参考元素对象 二者是父子关系 即二者所在空间是一致的,即同为内存中元素对象或页面中元素对象。
  • 新元素对象 同appendChild中一样
  • 不在insertAfter JS默认没提供insertAfter,可以根据insertBefore写一个。
  1. const li2 = document.createElement('li');
  2. li2.innerHTML = 'hello';
  3. li2.style.color = 'red';
  4. ul.insertBefore(li2,ul.firstChild);

insert

4、 替换元素replaceChild 语法:父元素对象.replaceChild(新元素对象,参考元素对象)。比较简单,参考插入,不再演示
5、 删除元素removeChild 语法: 父元素对象.removeChild(存在元素对象),其实删除并不是真正的删除,它在内存中仍然存在,可再次挂载。

  1. // 删除并不是真正的删除,它在内存中仍然存在,可再次挂载
  2. score.onclick = function (ev) {
  3. document.body.removeChild(score);
  4. document.body.appendChild(score);
  5. };

2、大量dom元素操作时优化方案

在说优化方案之前,我简单说下网页的二个重要部分:DOM树和内存,前者就是页面中已经存在的元素,后者应该是页面缓存(我也不清楚对不对,有的文档称为内存)。DOM树是已经渲染完成后的结果,每一次更新操作都会导致页面再渲染,所以大量dom操作则会导致页面一直忙于渲染,这种 “页面回流” 用户体验就非常不好。目前老师解决方案是文档片断,来优化或提升dom操作的效率。不过在前面我在偶然发现在内存中也可以组装,于是就有了两种优化方案。比较如下:

  1. // 大量元素时优化方案
  2. ul = document.createElement('ul');
  3. // 第一种优化方案:内存组装,一次加载
  4. for (let i = 0; i < 1000; i++) {
  5. let li = document.createElement('li');
  6. li.innerHTML = 'item' + i;
  7. ul.appendChild(li);
  8. }
  9. // 第二种优化方案:文档片断组装,一次加载
  10. // const frag = document.createDocumentFragment();
  11. // for (let i = 0; i < 1000; i++) {
  12. // let li = document.createElement('li');
  13. // li.innerHTML = 'item' + i;
  14. // frag.appendChild(li);
  15. // }
  16. // ul.appendChild(frag);
  17. document.body.appendChild(ul);

good1
good2

测试结果和结论: 二者加载1000个列表元素 时间相近 ,实质都是在 内存中组装 。关于二者区别,后来咨询了老师,老师说文档片断是通用容器,可临时存储任何类型对象,不过前者在内存中好像也是可以存储任何类型,目前我没发现什么区别。

3、事件对象

事件是js操作中经常要打交道的,我们重点关注和用户交互的事件,就是鼠标事件和键盘事件。不过之前还是要看下事件常用的两个属性target和currentTarget。

事件的两个重要属性: 触发者target和绑定者currentTarget 以前错误想法是target是当前元素,而currentTarget是上级对象,经测试才明白它们的区别

  • currentTarget 绑定者就是 绑定这个事件的元素对象 ,即是在JS中定义事件时的对象。
  • target 触发者是 触发事件行为的元素对象 ,估计这个不好理解。那就实际测试理解,测试结果是 触发者一定是绑定者或绑定者的子孙元素 。如只定义了元素的事件,那么该元素的子孙元素若没有定义事件时,则自动继承该事件。

上面对target的测试的结论,是 事件委托代理的工作原理 ,就是 事件也有继承性 。本文中实战案例大量应用事件委托代理,其实在日常JS编程中事件委托代理是经常使用的技巧,可简化逻辑和代码。

  1. <style>
  2. .parent {
  3. width: 20em;
  4. height: 20em;
  5. background-color: red;
  6. }
  7. .self {
  8. width: 15em;
  9. height: 15em;
  10. background-color: green;
  11. }
  12. .child {
  13. width: 10em;
  14. height: 10em;
  15. color: white;
  16. background-color: blue;
  17. }
  18. </style>
  19. <div class="parent">
  20. <div class="self">
  21. <div class="child">大家好,学习事件</div>
  22. </div>
  23. </div>
  24. <script>
  25. const parent1 = document.querySelector('.parent');
  26. const self1 = document.querySelector('.self');
  27. const child = document.querySelector('.child');
  28. // 1.target和currentTarget
  29. parent1.onclick = function (ev) {
  30. console.log('类名:%s => 触发者:%s , 绑定者:%s', this.className, ev.target, ev.currentTarget);
  31. };
  32. </script>

event-target

鼠标事件MouseEvent: 感觉常用的就是点击类型type、各种位置坐标x与y,具体可以console.dir打印

键盘事件KeyboardEvent 感觉常用的就是键盘事件类型type、key和keyCode等,一般键盘事件添加到window或input。

  1. window.onkeyup=function(ev){
  2. console.log(ev.key);
  3. console.dir(ev);
  4. }

keyevent

4、 视口高度clientHeight、滚动高度scrollTop和元素偏移高度offsetTop

  • 视口高度clientHeight 通俗地讲就是 可视区域高度 ,它总是小于设备屏幕尺寸。通过 document.documentElement.clientHeight 获取,要注意不是viewHeight,我案例中开始以为它,它是代表看过的高度。
  • 滚动高度scrollTop 就是滚动条上边距可视区域顶部的高度。它加上视口高度所包括的内容就是用户可以浏览的内容。通过 document.documentElement.scrollTop 获取。
  • 元素偏移高度offsetTop 元素在文档流中,到文档顶部的高度。通过 元素的offsetTop 属性获取。

三者关系见下图:

clientheight

5、其它的

  • this 事件函数中this表示触发者,若是事函数使用箭头函数时,此时this不是事件触发者。
  • innerHTML和innerText 前者功能比后者强大,可以解析html标签元素。
  • dataset自定义数据属性 在元素中,用户可以通过data-为前缀添加自定义的数据属性,尤其是多个元素中同步切换子元素时非常有用,如tab选项卡中tab和选项区两个同步。

二、实战案例1:留言本

实现功能:

  1. 用户输入留言后,若不是空格或空,回车后则添加留言区
  2. 最新的留言总是在最上面
  3. 留言条可以删除
  1. <div class="container">
  2. <label for="content">输入留言:</label>
  3. <input type="text" id="content" name="content" value="" placeholder="输入留言后回车确认" />
  4. <ul id="lists"></ul>
  5. </div>
  6. <script>
  7. const content = document.querySelector('#content');
  8. const lists = document.querySelector('#lists');
  9. content.onkeyup = function (ev) {
  10. // console.log(ev.key);
  11. // 判断回车时,添加内容到列表中
  12. if (ev.key == 'Enter') {
  13. // trim除去空格,空内容不添加
  14. if (content.value.trim().length > 0) {
  15. let li = document.createElement('li');
  16. li.innerHTML = content.value + "<button onclick='del(this)'>删除</button>";
  17. // 若列表有内容则添加到最前,没有则追加
  18. lists.childElementCount == 0 ? lists.appendChild(li) : lists.insertBefore(li, lists.firstElementChild);
  19. // 添加内容后,清空输入框
  20. content.value = null;
  21. } else {
  22. // 无效添加后,输入框获取焦点
  23. content.focus();
  24. }
  25. }
  26. };
  27. function del(el) {
  28. // confirm确认返回true,取消返回false
  29. if (confirm('确认删除')) lists.removeChild(el.parentElement);
  30. }
  31. </script>

todolist

Codepen演示 https://codepen.io/woxiaoyao81/pen/ExyeEad

三、实战案例2:tab选项卡

tab选项卡功能就不说了,这里我是通过dataset自定义属性同步tab和内容区,另一个我是通过切换order来实现内容区切换的

  1. <script>
  2. const divs = document.querySelectorAll('.container div:nth-child(n)');
  3. const ul = document.querySelector('ul');
  4. // console.log(divs);
  5. ul.addEventListener('click', tab, false);
  6. function tab(ev) {
  7. // tab菜单切换:先清除所有,再设置当前选择的
  8. for (let el of ul.children) {
  9. el.classList.remove('active');
  10. }
  11. ev.target.classList.toggle('active');
  12. // tab区切换:先清除激活样式和order,然后设置和tab相同数据属性的区
  13. for (let el of divs) {
  14. if (el.dataset.index == ev.target.dataset.index) {
  15. el.classList.add('active');
  16. el.style.order = 0;
  17. } else {
  18. el.classList.remove('active');
  19. el.style.order = 1;
  20. }
  21. }
  22. }
  23. </script>

tab

四、实战案例3:页面换背景

太简单了,就是设置body的background-image

  1. <div class="container">
  2. <img src="static/images/1.jpg" alt="" />
  3. <img src="static/images/2.jpg" alt="" />
  4. <img src="static/images/3.jpg" alt="" />
  5. </div>
  6. <script>
  7. const container = document.querySelector('.container');
  8. container.onclick = ev => (document.body.style.backgroundImage = 'url(' + ev.target.src + ')');
  9. </script>

background

五、实战案例4:图片懒加载

这里关键是:一个是图片占位的概念,另一个就是通过真正的图片路径已经包括在元素data-src自定义属性中。当元素的偏移高度小于视口高度和滚动高度之和就加载图片。

  1. <script>
  2. const imgs = document.querySelectorAll('.container img');
  3. // 视口高度,即可视区域高度
  4. const clientHeight = document.documentElement.clientHeight;
  5. window.addEventListener('scroll', lazy, false);
  6. window.addEventListener('load', lazy, false);
  7. function lazy(ev) {
  8. // 滚动高度,可视区域滚动过的距离
  9. let scrollTop = document.documentElement.scrollTop;
  10. for (let img of imgs) {
  11. // 元素在文档中偏移高度,也可称为真实高度
  12. let offsetTop = img.offsetTop;
  13. // 当元素偏移高度小于(视口高度+滚动高度)时,元素就出现在可视区域了
  14. if (offsetTop <= scrollTop + clientHeight) {
  15. setTimeout(() => (img.src = img.dataset.src), 500);
  16. }
  17. }
  18. }
  19. </script>

lazy

六、实战案例5:用户选择爱好

功能描述: 从提供的爱好中选择自己的爱好,此时备选区的爱好就移动到自己爱好区。
关键技术: 还记得本文前面介绍appendChild有趣的现象了吗?不知道可以向上看看。

select

  1. <div class="container">
  2. <div class="box">
  3. <h2>你的爱好:</h2>
  4. <ul id="selected" class="item"></ul>
  5. </div>
  6. <div class="box">
  7. <h2>从下面选择爱好:</h2>
  8. <ul id="unselected" class="item">
  9. <li>摄影</li>
  10. <li>编程</li>
  11. <li>游戏</li>
  12. <li>旅游</li>
  13. <li>驾驶</li>
  14. <li>文学</li>
  15. </ul>
  16. </div>
  17. </div>
  18. <script>
  19. // 移动关键是利用appendchild一个特性:不同父元素添加同一个元素时,以前的位置会删除,最终出现在最后的位置
  20. const ulSelect = document.querySelector('#selected');
  21. const ul = document.querySelector('#unselected');
  22. ul.addEventListener('click', ulAdd, false);
  23. function ulAdd(ev) {
  24. // 判断是否有子元素,防止一次多选择
  25. if (ev.target.childElementCount == 0) ulSelect.appendChild(ev.target);
  26. }
  27. ulSelect.addEventListener('click', ulDel, false);
  28. function ulDel(ev) {
  29. if (ev.target.childElementCount == 0) ul.appendChild(ev.target);
  30. }
  31. </script>

Codepen演示代码 https://codepen.io/woxiaoyao81/pen/mdEGxyJ

七、学习后的总结

今天是原生JS实战课,非常感谢朱老师这段时间的耐心的讲解,同时也很庆幸自己每节课都认真梳理、测试和总结,在实战环节没什么压力,视频看了一遍就自己完成代码,只参考了老师核心的思想,具体实现有自己的改进。上面只贴了关键代码,源文件欢迎访问我的GitHubhttps://github.com/woxiaoyao81/phpcn13或Giteehttps://gitee.com/freegroup81/phpcn13

  • JS基本知识的核心要理解透,如条件、循环控制、函数和dom操作
  • 熟悉json、ajax的原理和流程,最好能动手写出代码。
  • 熟悉事件的添加、传递和委托代理,尤其是本文中测试的target和currentTaget的总结。

到这里原生JS已经学完,学习关键是多写代码,多思为什么,对自己疑问要测试,不要轻易相信网上搜的文章,要经过验证才能变成自己的。

声明:本文内容转载自脚本之家,由网友自发贡献,版权归原作者所有,如您发现涉嫌抄袭侵权,请联系admin@php.cn 核实处理。
全部评论
文明上网理性发言,请遵守新闻评论服务协议
吾逍遥2020-11-09 07:28:391楼
通过你的举例子我明白了两者区别了,就比如ul...li结构,一般都是页面已经存在ul元素了,li是动态添加,此时文档片断就虚拟ul角色,在内存中组装,然后一次性加载。而内存直接组装则因为没有父元素无法进行。所以在内存组装阶段,文档片断是虚拟了页面中真实存在的元素,挂载时将子元素真实添加到页面中的元素下