Blogger Information
Blog 54
fans 6
comment 31
visits 107494
Popular Tutorials
More>
Latest Downloads
More>
Web Effects
Website Source Code
Website Materials
Front End Template
JS五个精选的实战案例
吾逍遥
Original
8697 people have browsed it

一、实战前准备知识

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已经学完,学习关键是多写代码,多思为什么,对自己疑问要测试,不要轻易相信网上搜的文章,要经过验证才能变成自己的。

Correcting teacher:天蓬老师天蓬老师

Correction status:qualified

Teacher's comments:1. 文档片断不会被添加到页面,只有它的引用才会被添加 2. 具体到本例,如果<ul>是页面中已经存在的元素,需要给它创建1000个子元素,你觉得应该怎么做呢?是否是创建一个添加一个呢? 咱们课堂上的案例是一个特例,没有体现出文档片断的优势
Statement of this Website
The copyright of this blog article belongs to the blogger. Please specify the address when reprinting! If there is any infringement or violation of the law, please contact admin@php.cn Report processing!
All comments Speak rationally on civilized internet, please comply with News Comment Service Agreement
1 comments
吾逍遥 2020-11-09 07:28:39
通过你的举例子我明白了两者区别了,就比如ul...li结构,一般都是页面已经存在ul元素了,li是动态添加,此时文档片断就虚拟ul角色,在内存中组装,然后一次性加载。而内存直接组装则因为没有父元素无法进行。所以在内存组装阶段,文档片断是虚拟了页面中真实存在的元素,挂载时将子元素真实添加到页面中的元素下
1 floor
Author's latest blog post