关于dom:DOM动画效果怎么做

JavaScript 是世界上最风行的脚本语言。 JavaScript 是属于 web 的语言,它实用于 PC、笔记本电脑、平板电脑和移动电话。 JavaScript 被设计为向 HTML 页面减少交互性。 许多 HTML 开发者都不是程序员,然而 JavaScript 却领有非常简单的语法。简直每个人都有能力将小的 JavaScript 片段增加到网页中。上面给大家分享下JavaScript教程全套视频合集:DOM动画成果。 DOM动画成果 让一个元素从左至右进行静止 var box = document.getElementById("box");var t = null;t = setInterval(function(){}) 静止的终止条件 t = setInterval(function(){终止条件}) // 元素的属性值 === 指标点if(dom.attr === target){clearInterval(t);} 静止的三要素 起始点 一个静止的起始点其实就是以后元素的地位,咱们通过API获取以后元素的地位,让这个地位作为静止的起始。 指标速度 DOM动画成果封装 单属性静止框架: function move( ele , attr , target){// 1. 敞开开启定时器;clearInterval( ele.timer );ele.timer = setInterval( function(){// 2. 计算速度;if(attr === "opacity"){var iNow = parseInt(getComputedStyle(ele)[attr] * 100); //0 ~ 100}else{var iNow = parseInt(getComputedStyle(ele)[attr]); //100}var speed = (target - iNow) / 10;// 3. 速度取整;if(speed > 0){speed = Math.ceil(speed);}else{speed = Math.floor(speed);}if(attr === "opacity"){ele.style[attr] = (iNow + speed) / 100 ;}else{ele.style[attr] = iNow + speed + "px";}// 4. 终止条件;if(iNow === target){clearInterval(ele.timer);}} , 50) ...

April 13, 2023 · 1 min · jiezi

关于dom:DOM事件流

文章不易,请关注公众号 毛毛虫的小小蜡笔,多多反对,谢谢。 DOM事件触发程序遵循两个准则:1、先触发捕捉阶段,而后到指标阶段,最初到冒泡阶段2、指标阶段,依照代码程序来触发,不辨别捕捉和冒泡 Demo代码 <head> <meta charset="utf-8"> <title>事件流</title> <style> #a { width: 100px; height: 100px; background: black; margin: auto; } #b { width: 60px; height: 60px; background: red; } #c { width: 30px; height: 30px; background: green; display: inline-block; }</style></head><body> <div id="a"> <div id="b"> <div id="c"></div> </div> </div> <script> var aObj = document.getElementById('a') var bObj = document.getElementById('b') var cObj = document.getElementById('c') aObj.addEventListener('click', function (evt) { console.log('a1') }, true) aObj.addEventListener('click', function (evt) { console.log('a2') }, false) bObj.addEventListener('click', function (evt) { console.log('b1') }, true) bObj.addEventListener('click', function (evt) { console.log('b2') }, false) cObj.addEventListener('click', function (evt) { console.log('c1') }, true) cObj.addEventListener('click', function (evt) { console.log('c2') }, false) cObj.addEventListener('click', function (evt) { console.log('c3') }, true) cObj.addEventListener('click', function (evt) { console.log('c4') }, false)</script></body>成果如下截图所示: ...

April 24, 2022 · 1 min · jiezi

关于dom:DOM操作造成的页面卡顿问题及解决

前言界面上UI的更改都是通过DOM操作实现的,并不是通过传统的刷新页面实现 的。只管DOM提供了丰盛接口供内部调用,但DOM操作的代价很高,页面前端代码的性能瓶颈也大多集中在DOM操作上,所以前端性能优化的一个次要的关注 点就是DOM操作的优化。 DOM操作优化的总准则是尽量减少DOM操作。 先来看看DOM操作为什么会影响性能?在浏览器中,DOM的实现和ECMAScript的实现是拆散的。比方 在IE中,ECMAScrit的实现在jscript.dll中,而DOM的实现在mshtml.dll中;在Chrome中应用WebKit中的 WebCore解决DOM和渲染,但ECMAScript是在V8引擎中实现的,其余浏览器的状况相似。所以通过JavaScript代码调用DOM接 口,相当于两个独立模块的交互。相比拟在同一模块中的调用,这种跨模块的调用其性能损耗是很高的。但DOM操作对性能影响最大其实还是因为它导致了浏览器 的重绘(repaint)和回流(reflow)。 这里咱们先理解下浏览器的渲染原理: 从下载文档到渲染页面的过程中,浏览器会通过解析HTML文档来构建DOM树,解析CSS产生CSS规定树。JavaScript代码在解析过程中, 可能会批改生成的DOM树和CSS规定树(这也是为什么经常把js放在页面底部最初才渲染的起因)。之后依据DOM树和CSS规定树构建渲染树,在这个过程中CSS会依据选择器匹配HTML元素。渲染树包含了每 个元素的大小、边距等款式属性,渲染树中不蕴含暗藏元素及head元素等不可见元素。 最初浏览器依据元素的坐标和大小来计算每个元素的地位,并绘制这些元 素到页面上。重绘指的是页面的某些局部要从新绘制,比方色彩或背景色的批改,元素的地位和尺寸并没用扭转;回流则是元素的地位或尺寸产生了扭转,浏览器需 要从新计算渲染树,导致渲染树的一部分或全副发生变化。渲染树从新建设后,浏览器会从新绘制页面上受影响的元素。回流的代价比重绘的代价高很多,重绘会影 响局部的元素,而回流则有可能影响全副的元素。如下的这些DOM操作会导致重绘或回流: 减少、删除和批改可见DOM元素页面初始化的渲染挪动DOM元素批改CSS款式,扭转DOM元素的尺寸DOM元素内容扭转,使得尺寸被撑大浏览器窗口尺寸扭转浏览器窗口滚动如何防止或者解决DOM操作造成的页面卡顿问题1.合并屡次的DOM操作为单次的DOM操作最常见频繁进行DOM操作的是频繁批改DOM元素的款式,代码相似如下: element.style.borderColor = '#f00';element.style.borderStyle = 'solid';element.style.borderWidth = '1px';复制代码这种编码方式会因为频繁更改DOM元素的款式,触发页面屡次的回流或重绘,下面介绍过,古代浏览器针对这种状况有性能的优化,它会合并DOM操作,但并不是所有的浏览器都存在这样的优化。举荐的形式是把DOM操作尽量合并,如上的代码能够优化为: // 优化计划1element.style.cssText += 'border: 1px solid #f00;';// 优化计划2element.className += 'empty';复制代码示例的代码有两种优化的计划,都做到了把屡次的款式设置合并为一次设置。计划2比计划1略微有一些性能上的损耗,因为它须要查问CSS类。但计划2的维护性最好,这在上一章已经探讨过。很多时候,如果性能问题并不突出,抉择编码方案时须要优先思考的是代码的维护性。 相似的操作还有通过innerHTML接口批改DOM元素的内容。不要间接通过此接口来拼接HTML代码,而是以字符串形式拼接好代码后,一次性赋值给DOM元素的innerHTML接口。 2.把DOM元素离线或暗藏后批改把DOM元素从页面流中脱离或暗藏,这样解决后,只会在DOM元素脱离和增加时,或者是暗藏和显示时才会造成页面的重绘或回流,对脱离了页面布局流的DOM元素操作就不会导致页面的性能问题。这种形式适宜那些须要大批量批改DOM元素的状况。具体的形式次要有三种: (1)应用文档片段文档片段是一个轻量级的document对象,并不会和特定的页面关联。通过在文档片段上进行DOM操作,能够升高DOM操作对页面性能的影响,这 种形式是创立一个文档片段,并在此片段上进行必要的DOM操作,操作实现后将它附加在页面中。对页面性能的影响只存在于最初把文档片段附加到页面的这一步 操作上。代码相似如下: var fragment = document.createDocumentFragment();// 一些基于fragment的大量DOM操作...document.getElementById('myElement').appendChild(fragment);复制代码(2)通过设置DOM元素的display款式为none来暗藏元素这种形式是通过暗藏页面的DOM元素,达到在页面中移除元素的成果,通过大量的DOM操作后复原元素原来的display款式。对于这类会引起页面重绘或回流的操作,就只有暗藏和显示DOM元素这两个步骤了。代码相似如下: var myElement = document.getElementById('myElement');myElement.style.display = 'none';// 一些基于myElement的大量DOM操作...myElement.style.display = 'block';复制代码(3)克隆DOM元素到内存中这种形式是把页面上的DOM元素克隆一份到内存中,而后再在内存中操作克隆的元素,操作实现后应用此克隆元素替换页面中原来的DOM元素。这样一来,影响性能的操作就只是最初替换元素的这一步操作了,在内存中操作克隆元素不会引起页面上的性能损耗。代码相似如下: var old = document.getElementById('myElement');var clone = old.cloneNode(true);// 一些基于clone的大量DOM操作...old.parentNode.replaceChild(clone, old);复制代码在古代的浏览器中,因为有了DOM操作的优化,所以利用如上的形式后可能并不能显著感触到性能的改善。然而在依然占有市场的一些旧浏览器中,利用以上这三种编码方式则能够大幅提高页面渲染性能。 设置具备动画成果的DOM元素的position属性为fixed或absolute把页面中具备动画成果的元素设置为相对定位,使得元素脱离页面布局流,从而防止了页面频繁的回流,只波及动画元素本身的回流了。这种做法能够进步动 画成果的展现性能。如果把动画元素设置为相对定位并不合乎设计的要求,则能够在动画开始时将其设置为相对定位,等动画完结后复原原始的定位设置。在很多的 网站中,页面的顶部会有大幅的广告展现,个别会动画开展和折叠显示。如果不做性能的优化,这个成果的性能损耗是很显著的。应用这里提到的优化计划,则能够 进步性能。审慎获得DOM元素的布局信息后面探讨过,获取DOM的布局信息会有性能的损耗,所以如果存在反复调用,最佳的做法是尽量把这些值缓存在局部变量中。思考如下的一个示例:for (var i=0; i < len; i++) { ...

November 18, 2021 · 1 min · jiezi

关于dom:鼠标事件中的各种坐标

鼠标事件对象中的各种坐标在鼠标的事件对象中,含有很多可形容坐标的属性,然而它们之间是有区别的。常见的属性有: 1. clientX/Y含意: 示意以后鼠标地位间隔可视区域的间隔,这个可视区域指的是浏览器窗口的大小,或者可了解为整个网页,网页是不会因为滚动条滚动而变动的。所以该值并不会因为呈现滚动条而变动。 兼容性: 各个浏览器都反对。 2. pageX/Y含意: 这个值代表的是以后鼠标地位间隔网页首屏的间隔,因为网页首屏是随着滚动条的滚动而变动, 首屏指的是你看见的第一屏,滚动后就是第二屏,第三屏。所以该值会因滚动而变动。 兼容性:IE8及其以下不反对该属性 火狐没有该属性 兼容解决: pageX = clientX + scrollLeft pageY = clientY + scrollTop3. offsetX/Y含意: 示意以后鼠标地位间隔其所属Dom的元素的程度以及垂直偏移量,然而参考点在不同的浏览器是不雷同的,IE认为参考点是dom元素的padding开始的左上角局部,而chrome认为参考点是dom 以border开始的左上角开始的地位。 兼容性:待补充 4. lauyoutX/Y含意: 与pageX/Y 是一个意思,火狐没有pageX/Y属性从而出的一个替代品 兼容性: 5. x/y补充参考点 相对定位元素绝对于body进行定位,参考点是网页首屏,也就是会随着滚动条滚动而变动。参考点等同于pageX固定定位的参考点是整个文档.不会因滚动而变动。参考点等同于clientX

September 18, 2021 · 1 min · jiezi

关于dom:XML数据如何进行解析呢方式有哪些

问题:XML数据如何进行解析呢,形式有哪些?上回咱们说到 JSON 解析的四种形式,那么这次咱们来看看 XML 的四种解析形式。 解析的四种形式DOM 解析SAX 解析JDOM 解析DOM4J 解析案例实操DOM 解析DOM(Document Object Model, 文档对象模型),在应用程序中,基于 DOM 的 XML 分析器将一个 XML 文档转换成一个对象模型的汇合(通常称为 DOM 树),应用程序正是通过对这个对象模型的操作,来实现对 XML 文档数据的操作。XML 自身是以树状的模式呈现的,所以 DOM 操作的时候,也将按章树的模式进行转换。在整个 DOM 树中,最大的中央指的是 Document,示意一个文档,在这个文档中只存在一个根节点。 留神:在应用 DOM 操作的时候,每一个文字的区域也是一个节点,称为文本节点。 外围操作接口 在 DOM 解析中有以下四个外围的操作接口: Document:此接口代表了整个 XML 文档,示意的是整棵 DOM 树的根,提供了对文档中的数据进行拜访和操作的入口,通过 Document 节点能够拜访 XML 文件中所有的元素内容。 Node:此接口在整个 DOM 树中具备无足轻重的位置,DOM 操作的外围接口中有很大一部分接口是从 Node 接口继承过去的。例如:Document、Element 等接口,在 DOM 树中,每一个 Node 接口代表了 DOM 树中的一个节点。 NodeList:此接口示意的是一个节点的汇合,个别用于示意有程序关系的一组节点,例如: 一个节点的子节点,当文档扭转的时候会间接影响到 NodeList 汇合。 NamedNodeMap:此接口示意的是一组节点和其惟一名字对应的一一对应关系,本接口次要用于属性节点的示意上。 DOM 解析过程 ...

December 8, 2020 · 3 min · jiezi

关于dom:react项目如何操作dom

平时在做react我的项目过程中难免会遇到一些非凡场景须要去操作DOM实现,比方锚点,这篇文章会从两个角度(类组件、函数组件)去总结一些常见操作DOM的形式办法。 1、父组件操作子组件DOM——React.forwardRef在16.3版本之前,没有方法将ref传递到一个函数组件之中,然而只有名字不雷同是能够通过props传递到子组件。 平凡的fb在react 16.3中新加了一个React.forwardRef函数,使之子组件能够间接接管ref function forwardRef<T, P = {}>(render: ForwardRefRenderFunction<T, P>): ForwardRefExoticComponent<PropsWithoutRef<P> & RefAttributes<T>>;interface ForwardRefRenderFunction<T, P = {}> { (props: PropsWithChildren<P>, ref: ForwardedRef<T>): ReactElement | null; displayName?: string; // explicit rejected with `never` required due to // https://github.com/microsoft/TypeScript/issues/36826 /** * defaultProps are not supported on render functions */ defaultProps?: never; /** * propTypes are not supported on render functions */ propTypes?: never;}其实,React.forwardRef就是一个HOC,接管一个函数组件,这个函数组件能够接管ref参数,说白了,React.forwardRef的作用就是能够使被包裹组件接管ref参数。 这里须要留神,泛型类型T指ref的类型,P指props类型。 上面举个例子阐明下用法: // 父组件const Father = () => { const ref = React.createRef<HTMLDivElement>(); useEffect(() => { if (ref.current) { ref.current.style.fontSize = '16px'; } }) return <Child ref={ref}></Child>}// 子组件const Child = React.forwardRef<HTMLDivElement, any>((props, ref) => { return <div ref={ref}>child</div>})在页面初始渲染完后会批改Child组件中div文本字体大小。 ...

December 8, 2020 · 2 min · jiezi

关于dom:前端面试每日-31-第597天

明天的知识点 (2020.12.03) —— 第597天 (我也要出题)[html] 你有应用过kbd标签吗?说说它的用处[css] css变量受哪些影响?[js] 写一个办法将虚构Dom转化为实在DOM[软技能] 你有本人或者为公司写过专利吗?《论语》,曾子曰:“吾日三省吾身”(我每天屡次检查本人)。前端面试每日3+1题,以面试题来驱动学习,每天提高一点!让致力成为一种习惯,让奋斗成为一种享受!置信 保持 的力量!!!欢送在 Issues 和敌人们一起探讨学习! 我的项目地址:前端面试每日3+1【举荐】欢送跟 jsliang 一起折腾前端,零碎整顿前端常识,目前正在折腾 LeetCode,打算买通算法与数据结构的任督二脉。GitHub 地址 微信公众号欢送大家前来探讨,如果感觉对你的学习有肯定的帮忙,欢送点个Star, 同时欢送微信扫码关注 前端剑解 公众号,并退出 “前端学习每日3+1” 微信群互相交换(点击公众号的菜单:交换)。 学习不打烊,充电加油只为遇到更好的本人,365天无节假日,每天早上5点纯手工公布面试题(死磕本人,愉悦大家)。心愿大家在这虚夸的前端圈里,放弃沉着,保持每天花20分钟来学习与思考。在这变幻无穷,类库层出不穷的前端,倡议大家不要等到找工作时,才狂刷题,提倡每日学习!(不忘初心,html、css、javascript才是基石!)欢送大家到Issues交换,激励PR,感激Star,大家有啥好的倡议能够加我微信一起交换探讨!心愿大家每日去学习与思考,这才达到来这里的目标!!!(不要为了谁而来,要为本人而来!)交换探讨欢送大家前来探讨,如果感觉对你的学习有肯定的帮忙,欢送点个[Star]

December 3, 2020 · 1 min · jiezi

关于dom:前端面试每日-31-第589天

明天的知识点 (2020.11.25) —— 第589天 (我也要出题)[html] 要缩小DOM的数量有什么方法吗?[css] pc端和挪动端应用两套布局和应用一套自适应布局别离有哪些优缺点?[js] 请解释下算法有什么特色(基本要素)?[软技能] 说说你是如何对做技术评审的,都要评审哪些方面呢?为什么?《论语》,曾子曰:“吾日三省吾身”(我每天屡次检查本人)。前端面试每日3+1题,以面试题来驱动学习,每天提高一点!让致力成为一种习惯,让奋斗成为一种享受!置信 保持 的力量!!!欢送在 Issues 和敌人们一起探讨学习! 我的项目地址:前端面试每日3+1【举荐】欢送跟 jsliang 一起折腾前端,零碎整顿前端常识,目前正在折腾 LeetCode,打算买通算法与数据结构的任督二脉。GitHub 地址 微信公众号欢送大家前来探讨,如果感觉对你的学习有肯定的帮忙,欢送点个Star, 同时欢送微信扫码关注 前端剑解 公众号,并退出 “前端学习每日3+1” 微信群互相交换(点击公众号的菜单:交换)。 学习不打烊,充电加油只为遇到更好的本人,365天无节假日,每天早上5点纯手工公布面试题(死磕本人,愉悦大家)。心愿大家在这虚夸的前端圈里,放弃沉着,保持每天花20分钟来学习与思考。在这变幻无穷,类库层出不穷的前端,倡议大家不要等到找工作时,才狂刷题,提倡每日学习!(不忘初心,html、css、javascript才是基石!)欢送大家到Issues交换,激励PR,感激Star,大家有啥好的倡议能够加我微信一起交换探讨!心愿大家每日去学习与思考,这才达到来这里的目标!!!(不要为了谁而来,要为本人而来!)交换探讨欢送大家前来探讨,如果感觉对你的学习有肯定的帮忙,欢送点个[Star]

November 25, 2020 · 1 min · jiezi

关于dom:DOM事件

Event.preventDefault办法会阻止以后事件的解决行为: 案例一: 点击链接阻止默认的跳转事件<a href="//w3cschool.cc/" id='a'>Go to W3Cschool.cc</a><script>document.getElementById('a').onclick = function(event){ event.preventDefault();}</script>HTTP头user agent:User Agent中文名为用户代理,简称 UA,它是一个非凡字符串头,使得服务器可能辨认客户应用的操作系统及版本、CPU 类型、浏览器及版本、浏览器渲染引擎、浏览器语言、浏览器插件等。一些网站经常通过判断 UA 来给不同的操作系统、不同的浏览器发送不同的页面,因而可能造成某些页面无奈在某个浏览器中失常显示,但通过假装 UA 能够绕过检测。 浏览器的UA字串规范格局为: 浏览器标识 (操作系统标识; 加密等级标识; 浏览器语言) 渲染引擎标识 版本信息(然而不同的浏览器的格局是不同的,大体都包含这些内容)。

November 23, 2020 · 1 min · jiezi

关于dom:DOM性能

1、dom查问做缓存2、将频繁操作改为一次操作 //频繁操作const list = document.getElementById("list")for(let i = 0 ; i<10;i++){ const li = document.createElement("li") li.innerHTML = `List Item ${i}` list.appendChild(li)}//一次操作const list = document.getElementById("list")//创立一个文档片段,此时还没有插入到dom构造中const frag = document.createDocumentFragment()for(let i =0;i<10;i++){ const li = document.createElement("li") li.innerHTML = `List Item ${i}` frag.appendChild(li)}list.appendChild(frag)

September 11, 2020 · 1 min · jiezi

关于dom:DOM操作

JS基础知识,规定语法(ECMA 262 规范)JS Web API,网页操作的API(W3C规范)JS Web API:DOMBOM事件绑定ajax存储DOM(document object model)的实质获取DOM节点 //html和css//css.container{ border:1px solid #ccc; height:50px;}.red{ color:red;}//html<div id="div1"> <p>这是一段文字1</p> <p>这是一段文字2</p> <p>这是一段文字3</p><div><div class="container"> 内容</div>const div1 = document.getElementById('div1');//元素const divList = doucument.getElementsByTagName('div')//汇合console.log(divList.length,divList[0])const containerList = doucument.getElelmentsByClassName("container") //汇合const pList = document.querySelectorAll("p") //汇合property和attributeproperty:批改对象属性,不会体现在html构造中。attribute:批改html属性,会扭转html构造。两者都可能引起DOM从新渲染。(尽量用property)js const pList = doucument.querySelectorAll('p')const p1 = pList[0]property模式(对dom元素js变量进行批改) p1.style.width="100px"console.log(p1.style.width)p1.calssName = "red"console.log(p1.calssName)console.log(p1.nodeName)//打印的是节点名称pconsole.log(p1.nodeType)//打印节点类型1attribute模式(设置的是节点属性) p1.setAttribute("class","container")console.log(p1.getAttribute("class"))DOM构造操作新增/插入节点获取子元素列表,获取父元素删除字节点const div1 = document.getElementById("div1")//增加新节点const newP = document.createElement('p')newP.innerHTML = "this is p1"div1.appendChild(newP)//增加新创建的元素//挪动已有节点,留神是挪动const p2 = document.getElementById('p2')div1.appendChild(p2)//获取子元素列表const div1 = doucument.getElementById("div1")const div1ChildNodes = div1.childNodesconsole.log(div1.childNodes)//打印出两种标签,一种是p标签,一种是text标签,因为p标签外面含有文本,text的nodeType为3,p的nodeType为1,所以通过转化为数组过滤。const div1ChildNodesP = Array.prototype.slice.call(div1.childNodes).filter(child=>{ if(child.nodeType == 1){ return true } return false})//删除子节点div1.removeChild(div1ChildNodesP[0])//获取父元素const div1 = document.getElementById("div1")const parent = div1.parentNode

September 11, 2020 · 1 min · jiezi

关于dom:前端面试每日-31-第514天

明天的知识点 (2020.09.11) —— 第514天 (我也要出题)[html] H5中video的事件的触发程序是怎么的?[css] 如何创立stacking context?[js] 请说说DOM节点的操作如何优化?[软技能] 当业务与技术抵触时你作为管理者你该如何决择?《论语》,曾子曰:“吾日三省吾身”(我每天屡次检查本人)。前端面试每日3+1题,以面试题来驱动学习,每天提高一点!让致力成为一种习惯,让奋斗成为一种享受!置信 保持 的力量!!!欢送在 Issues 和敌人们一起探讨学习! 我的项目地址:前端面试每日3+1【举荐】欢送跟 jsliang 一起折腾前端,零碎整顿前端常识,目前正在折腾 LeetCode,打算买通算法与数据结构的任督二脉。GitHub 地址 微信公众号欢送大家前来探讨,如果感觉对你的学习有肯定的帮忙,欢送点个Star, 同时欢送微信扫码关注 前端剑解 公众号,并退出 “前端学习每日3+1” 微信群互相交换(点击公众号的菜单:交换)。 学习不打烊,充电加油只为遇到更好的本人,365天无节假日,每天早上5点纯手工公布面试题(死磕本人,愉悦大家)。心愿大家在这虚夸的前端圈里,放弃沉着,保持每天花20分钟来学习与思考。在这变幻无穷,类库层出不穷的前端,倡议大家不要等到找工作时,才狂刷题,提倡每日学习!(不忘初心,html、css、javascript才是基石!)欢送大家到Issues交换,激励PR,感激Star,大家有啥好的倡议能够加我微信一起交换探讨!心愿大家每日去学习与思考,这才达到来这里的目标!!!(不要为了谁而来,要为本人而来!)交换探讨欢送大家前来探讨,如果感觉对你的学习有肯定的帮忙,欢送点个[Star]

September 11, 2020 · 1 min · jiezi

前端培训中级阶段6-jQuery元素节点操作20190718期

前端最基础的就是 HTML+CSS+Javascript。掌握了这三门技术就算入门,但也仅仅是入门,现在前端开发的定义已经远远不止这些。前端小课堂(HTML/CSS/JS),本着提升技术水平,打牢基础知识的中心思想,我们开课啦(每周四)。 上一节我们讲了 jQuery 的基本使用。这节我们讲元素节点相关的内容。 内容目录选择器文档处理筛选选择器选择器在初级的时候就讲过了。上节也简单的列了一下。这节就找一点骚东西说一说吧。 Sizzle 选择器引擎先问问DOM的API查找性能,有不知道的吗?由快到慢 ID > Class > Name > Tag。CSS 选择器匹配的顺序,有不知道的吗?从左往右jQuery的选择器匹配的顺序,有没有不知道的?Sizzle 选择器引擎从左往右,当然,既然人家优秀肯定有道理。如果支持更快querySelector会选择更快的方法。如果有id选择器,他会先进行id选择,缩小范围。感兴趣可以去看看Sizzle选择器引擎介绍,有能力读源码当然更好了。 举个栗子 查询语句:$('div span')代码结构: div div div span div span先查div的话,我们需要遍历多少次?两次可以吗?但是如果先查span,那我们parent去找直到找到头就完事了。再说另一个,子节点只有一个父节点。但是父节点会有很多子节点。文档处理append、appendTo 和 prepend、prependTo操作的是父子节点,效果也等同于原生的api。已存在节点是移动,新节点是新增。 parent.append(child) 是将child添加到parent的最后面。链式操作对象为parent。对应原生appendChildchild.appendTo(parent) 是将child添加到parent的最后面。链式操作对象为child。parent.prepend(child) 是将child添加到parent的最前面。链式操作对象为parent。child.prependTo(parent) 是将child添加到parent的最前面。链式操作对象为child。insertAfter、after 和 insertBefore、before操作的是兄弟节点,效果同上。 A.after(B) 是在A后面增加B。链式操作对象为AB.insertAfter(A) 是把B增加到A后面。链式操作对象为BA.before(B) 是在A前面增加B。链式操作对象为AB.insertBefore(A) 是把B增加到A前面。链式操作对象为Bwrap、unwrap、wrapall、wrapInner、replaceWith、replaceAll说实话这几个我用都没用过。 $("a").wrap("<div class='wrap'></div>") 是指,将所有a标签,用后面的节点包起来。$('li').unwrap()将他们的父节点移除。也就是说所有子节点占据了原来父级的位置。$("a").wrapAll("<div class='wrap'></div>") 是指,将所有到标签都合并到第一个位置,并且包裹起来。$("a").wrapInner("<b></b>")是指,讲a标签的内容,用b标签包裹起来。$("a").replaceWith('<a href="//www.lilnong.top">lilnong.top</a>') 将所有的a标签,用新标签替换。那么链式操作对象是谁?$('<a href="//www.lilnong.top">lilnong.top</a>').replaceAll("a") 用新标签替换,把所有的a标签替换。那么链式操作对象是谁?empty、remove、detach$("a").empty() 删除匹配的元素集合中所有的子节点。$("a").remove() 从DOM中删除所有匹配的元素。这个方法不会把匹配的元素从jQuery对象中删除,因而可以在将来再使用这些匹配的元素。但除了这个元素本身得以保留之外,其他的比如绑定的事件,附加的数据等都会被移除。$("a").detach() 从DOM中删除所有匹配的元素。这个方法不会把匹配的元素从jQuery对象中删除,因而可以在将来再使用这些匹配的元素。与remove()不同的是,所有绑定的事件、附加的数据等都会保留下来。clone([Event[,deepEven]])克隆一个副本出来。我们知道,如果这个元素是文档内的,那么上面的方法会变成移动。当我们不想移动的时候,就需要clone。 event: 一个布尔值(true 或者 false)指示事件处理函数是否会被复制。V1.5以上版本默认值是:falsedeepEven: 一个布尔值,指示是否对事件处理程序和克隆的元素的所有子元素的数据应该被复制。筛选hasClass(class) 判断当前元素有没有对应class。比如我们在模拟复选框,我们需要判断当前的状态。$(this).hasClass('checked')filter(class) 比如我们现有一个所有复选框的合集,我们要过滤出所有选中状态的。next() 获取下一个元素nextAll() 获取后面所有元素nextUntil() 获取后面所有元素,可以设置终止条件。parent()、prev() 基本有next()相识的方法siblings() 获取所有兄弟元素end() 我觉得这个方法就很厉害,把当前的链式操作对象移交给上次。$("p").find("span").end()目前操作对象是$("p")等等…… 我就列举这些常用的吧。微信公众号:前端linong 初级阶段文章目录前端培训-初级阶段(17) - 数据存储(cookie、session、stroage)前端培训-初级阶段(13) - 正则表达式前端培训-初级阶段(13) - 类、模块、继承前端培训-初级阶段(13) - ECMAScript (内置对象、函数)前端培训-初级阶段(13) - ECMAScript (语法、变量、值、类型、运算符、语句)前端培训-初级阶段(13、18)前端培训-初级阶段(9 -12)前端培训-初级阶段(5 - 8)前端培训-初级阶段(1 - 4)中级阶段文章目录前端培训-中级阶段(2) - 事件(event) 事件冒泡、捕获 - (2019-06-20期)前端培训-中级阶段(3) - DOM 文档对象模型(2019-06-27期)前端培训-中级阶段(4)- BOM 浏览器对象模型(2019-07-04期)前端培训-中级阶段(5)- jQuery的概念与基本使用(2019-07-11期)资料前端培训目录、前端培训规划、前端培训计划jQuery 速查地址

June 28, 2019 · 1 min · jiezi

前端培训中级阶段3-DOM-文档对象模型20190627期

前端最基础的就是 HTML+CSS+Javascript。掌握了这三门技术就算入门,但也仅仅是入门,现在前端开发的定义已经远远不止这些。前端小课堂(HTML/CSS/JS),本着提升技术水平,打牢基础知识的中心思想,我们开课啦(每周四)。 前面我们已经基本掌握常规的语法语义,以及基本的使用方法。接下来我们讲深入进去了解其中内在的原理。 今天讲什么?什么是 DOM ?DOM 文档对象模型HTML 元素接口DOM 元素继承什么是 DOM ?DOM 通常上来讲,我们可以理解为用 JS 操作 HTML 的 API或者说 JS 和 HTML 中间的处理器适配器。 文档对象模型 (DOM) 是 HTML 和 XML 文档的编程接口文档对象模型 (DOM) 将 web 页面与到脚本或编程语言连接起来。通常是指 JavaScript,但将 HTML、SVG 或 XML 文档建模为对象并不是 JavaScript 语言的一部分。DOM模型用一个逻辑树来表示一个文档,树的每个分支的终点都是一个节点(node),每个节点都包含着对象(objects)。DOM的方法(methods)让你可以用特定方式操作这个树,用这些方法你可以改变文档的结构、样式或者内容。节点可以关联上事件处理器,一旦某一事件被触发了,那些事件处理器就会被执行。文档对象模型 (DOM) - mdnDOM 文档对象模型圈起来的是比较常用的接口。 DOM 接口测试地址 DOM 接口 Attr用来表示一个 DOM元素的属性。大多数场景你可能会直接通过字符串的方式获取属性值(Element.getAttribute('name'))。其实还有(Element.getAttributeNode())返回Attr类型。目前 Attr接口 继承于 Node接口。DOM4 级别上会移出,所以尽量不要使用 Node接口上的属性 DOM 接口 Element非常通用的基类,所有 Document对象下的对象都继承它。Element接口继承 Node接口 DOM 接口 CommentComment 接口代表标签(markup)之间的文本符号(textual notations)。尽管它通常不会显示出来,但是在查看源码时可以看到它们。在 HTML 和 XML 里,注释(Comments)为 '<!--' 和 '-->' 之间的内容。在 XML 里,注释中不能出现字符序列 '--'。 ...

June 20, 2019 · 1 min · jiezi

8-个你不知道的-DOM-功能

翻译:疯狂的技术宅原文:https://blog.logrocket.com/8-... 未经许可严禁转载! 最近关注了太多的工具,现在最好从所有 React 和 npm-install-everything 的文章中休息一下,来看看一些纯粹的 DOM 和 Web API 功能,它们可以在不依赖任何第三方库的前提下在现代浏览器中运行。 这篇文章将讲解八个鲜为人知的 DOM 功能,这些功能具有强大的浏览器支持。为了帮助你理解每个功能的工作原理,我将通过大量的测试代码为你自己提供演示,这些代码都放在了CodePen上。 学习这些方法和属性没有陡峭的学习曲线,并且可以与项目中所使用的任何工具集在一起使用。 addEventListener() 的新参数 options你肯定用 addEventListener() 处理过将事件附加到 Web 文档中的元素。通常 addEventListener() 调用看起来像这样: element.addEventListener('click', doSomething, false);第一个参数是正在监听的事件。第二个参数是一个回调函数,它将在事件发生时执行。第三个参数是一个名为 useCapture 的布尔值,用于指示是否要使用事件冒泡或捕获。 这些大家都知道(特别是前两个)。但也许你不知道 addEventListener() 也接受一个替换最终布尔值的参数。这个新参数是一个 options 对象,如下所示: element.addEventListener('click', doSomething, { capture: false, once: true, passive: false});请注意,该语法允许定义三个不同的属性。以下是每个含义的快速概述: capture — 与之前提到的 useCapture 参数相同的布尔值once — 布尔值,如果设置为 true,则表示该事件应仅在目标元素上运行一次,然后被删除passive — 一个最终的布尔值,如果设置为 true,表示该函数永远不会调用 preventDefault(),即使它被包含在函数体中其中最有趣的是 once 选项。这肯定会在很多情况下派上用场,并且无需用 removeEventListener() 或使用其他一些复杂的技术来强制单个事件触发器。如果你用过 jQuery,可能熟悉该库中的类似功能:.one() 方法。 你可以试着运行以下 CodePen 项目中关于 options 对象的一些代码: ...

June 3, 2019 · 3 min · jiezi

前端培训初级阶段场景实战20190523移动端适配bug

前端最基础的就是 HTML+CSS+Javascript。掌握了这三门技术就算入门,但也仅仅是入门,现在前端开发的定义已经远远不止这些。前端小课堂(HTML/CSS/JS),本着提升技术水平,打牢基础知识的中心思想,我们开课啦(每周四)。 场景实战这块内容每个人的内容都不一样。所以最近的更新基本都是我遇到并解决掉的问题。后期会把他们的内容贴地址。 今天我们要讲什么?flexible 适配方案flexible 适配方案(放大ios中的vConsole)flexible 适配方案遭遇放大手机字体大小主流移动端适配方案()flexible 适配方案flexible 是什么amfe/lib-flexible 是手淘的可伸缩布局方案,学习的话可以点进去,都是中文的,我就不用复制了吧。 flexible 原理它是把屏幕分成了10份,1份==1rem。如750/10=75。之后在 <html> 标签上增加 data-dpr属性和 font-size样式。然后我们就可以快乐的使用rem来基于根节点设置了。 flexible 注意事项如果页面有 viewprot 他会使用页面旧有的。flexible 设置了一个最大值(75),这样出来在页面中看到的效果就是居左750设计稿的样式。基于第二点,在部分曲面屏手机上或者大屏幕手机(三星 note8)上,会出现右边出现大片空白。解决方案如下 单位改成vw。(其实就是换方案了,flexible官方也建议换了)设置父级,然后居中。这样就两边空白一样了。基本可以接受。flexible 适配方案(放大ios中的vConsole)因为默认 flexible 是根据系统缩放的。我们为了测试方便,我们可以人为设置一下<meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no,viewport-fit=cover">。注意以上方案上线时记得去掉。否则部分小页面会出问题。 flexible 适配方案遭遇放大手机字体大小因为我司历史产品原因,有部分老年用户(不说老年了,我爸也是调)。因为手机字体太小,调节了手机的字体大小和字体缩放大小。这就导致基础值被异常的放大了,页面显示乱了。 修改系统字体大小的解决方案客户端设置(网上查的,因为客户端大哥不给改,且需要发版。所以我没试)vw 方案,我不用字体大小还不行吗?嗯,这个方案的确可以。既然你放大了,那我给你缩小不就好了。 获取所有标签,然后给 font-size 缩小。你别说,这个方案还真行。但是这个方法太恐怖了。而且后续节点不可控修改 flexible 增加 zoom 的控制。嗯,完美解决。 ;(function(win, lib) { // 默认1:1 var zoom = 1; try{ // 构建一个真实的DOM var dom = document.createElement('vv-ln-test-fontsize'); // 设置为一个理想值 dom.style.fontSize = '16px' // 追加到DOM树中 document.head.appendChild(dom) // 获取理想值和实际值的比例 zoom = 16/parseFloat(window.getComputedStyle(dom).fontSize); console.log(zoom, document.documentElement.style.fontSize) }catch(e){ console.log(e) } var vv_fontSizeZoom = lib.vv_fontSizeZoom || (lib.vv_fontSizeZoom = zoom);})(window, window['lib'] || (window['lib'] = {}));function refreshRem(){ var width = docEl.getBoundingClientRect().width; if (width / dpr > 750) { width = 750 * dpr; } var rem = width / 10 * lib.vv_fontSizeZoom;//计算值进行比例换算 docEl.style.fontSize = rem + 'px'; flexible.rem = win.rem = rem;}移动端适配方案前端培训-初级阶段(9 -12) 之 移动端适配原理 rem(px、em、rem、%、vm) ...

May 15, 2019 · 1 min · jiezi

浏览器中的JavaScript文档对象模型与-DOM-操作

翻译:疯狂的技术宅原文:https://www.valentinog.com/bl... JavaScript 并没有那么糟糕。作为运行在浏览器中的脚本语言,它对于网页操作非常有用。在本文中,我们将看到可以用哪些手段来修改 HTML 文档和交互。 什么是文档对象模型?文档对象模型是在浏览器中一切的基础。但它究竟是什么呢? 当我们访问网页时,浏览器会计算出如何解释每个 HTML 元素。这样它就可以创建 HTML 文档的虚拟表示,并保存在内存中。 HTML 页面被转换为树状结构并且每个 HTML 元素都变成一个叶子结点,连接到父分支。看一下这个简单的 HTML 页面: <!DOCTYPE html><html lang="en"> <head> <title>A super simple title!</title> </head> <body> <h1>A super simple web page!</h1> </body></html>在这个结构的顶部有一个文档,也称为根元素,它包含另一个元素:html。 html 元素包含一个 head ,而 head 内又有一个 title。然后 body 中包含一个 h1。每个 HTML 元素都由特定类型(也称为接口)表示,并且可以包含文本或其他嵌套元素: document (HTMLDocument) | | --> html (HTMLHtmlElement) | | --> head (HtmlHeadElement) | | | | --> title (HtmlTitleElement) | | --> text: "A super simple title!" | | --> body (HtmlBodyElement) | | | | --> h1 (HTMLHeadingElement) | | --> text: "A super simple web page!"每个HTML元素都来自 Element,但其中很大一部分都是专用的。你可以通过检查原型以查找元素所属的“种类”。例如,h1元素是 HTMLHeadingElement: ...

April 30, 2019 · 2 min · jiezi

【11】winter重学前端 笔记 - 浏览器:一个浏览器是如何工作的?(阶段二)DOM树构建

请支持正版https://time.geekbang.org/col…整体流程字符流 -> 状态机 -> 词token -> 栈 -> dom构建 DOM 的过程是:从父到子,从先到后,一个一个节点构造,并且挂载到DOM树上如何解析请求回来的 HTML 代码把respone拿到的字符流通过状态机解析成一个个的词词(token)是如何被拆分的拆分:最小有意义单元 - token(词)词的种类大约只有标签开始、属性、标签结束、注释、CDATA 节点…eg:<p class=“a”>text text text</p><p“标签开始”的开始;class=“a” 属性“标签开始”的结束;text text text 文本;/p>标签结束状态机为什么使用:我们每读入一个字符,其实都要做一次决策,而且这些决定是跟“当前状态”有关的定义:把每个词的“特征字符”逐个拆开成独立状态,然后再把所有词的特征字符链合并起来,形成一个联通图结构。绝大多数语言的词法部分都是用状态机实现的,HTML 官方文档规定了 80 个状态eg: 词法部分DOM 树又是如何构建的栈来实现,当接收完所有输入,栈顶就是最后的根节点,我们 DOM 树的产出,就是这个 stack 的第一项Node 类,所有的节点都会是这个 Node 类的实例。不一样的 HTML 节点对应了不同的 Node 的子类,此处的实现,我们进行简化,只把 Node 分为 Element 和 Textfunction Element(){ this.childNodes = [];}function Text(value){ this.value = value || “”;}规则:token中tag start和tag end需要成对实现,使用的栈正是用于匹配开始和结束标签的方案(编译原理技巧)Text 节点:把相邻的 Text 节点合并起来,当词(token)入栈时,检查栈顶是否是 Text 节点,果是的话就合并 Text 节点构建过程:(默认:源代码完全遵循 xhtml,HTML 具有很强的容错能力,奥妙在于当 tag end 跟栈顶的 start tag 不匹配的时候如何处理,暂时不考虑)栈顶元素就是当前节点;遇到属性,就添加到当前节点;遇到文本节点,如果当前节点是文本节点,则跟文本节点合并,否则入栈成为当前节点的子节点;遇到注释节点,作为当前节点的子节点;遇到 tag start 就入栈一个节点,当前节点就是这个节点的父节点遇到 tag end 就出栈一个节点(还可以检查是否匹配)完整的语法和词法分析代码词法分析每一个状态是一个函数,通过“if else”来区分下一个字符做状态迁移。这里所谓的状态迁移,就是当前状态函数返回下一个状态函数。const EOF = void 0// 词法分析器接受字符的function HTMLLexicalParser(syntaxer) { let state = data let token = null let attribute = null let characterReference = ’’ this.receiveInput = function (char) { if (state == null) { throw new Error(’there is an error’) } else { // 通过 state 来处理输入的字符流 state = state(char) } } this.reset = function () { state = data } // 状态机 c:每一个字符 function data(c) { switch (c) { case ‘&’: return characterReferenceInData // tagOpenState 是接受了一个“ < ” 字符,来判断标签类型的状态 case ‘<’: return tagOpen // perhaps will not encounter in javascript? // case ‘\0’: // error() // emitToken(c) // return data // can be handle by default case // case EOF: // emitToken(EOF) // return data default: emitToken(c) return data } } // only handle right character reference function characterReferenceInData(c) { if (c === ‘;’) { characterReference += c emitToken(characterReference) characterReference = ’’ return data } else { characterReference += c return characterReferenceInData } } function tagOpen(c) { if (c === ‘/’) { return endTagOpen } if (/[a-zA-Z]/.test(c)) { token = new StartTagToken() token.name = c.toLowerCase() return tagName } // no need to handle this // if (c === ‘?’) { // return bogusComment // } return error(c) } function tagName(c) { if (c === ‘/’) { return selfClosingTag } if (/[\t \f\n]/.test(c)) { return beforeAttributeName } if (c === ‘>’) { emitToken(token) return data } if (/[a-zA-Z]/.test(c)) { token.name += c.toLowerCase() return tagName } } function beforeAttributeName(c) { if (/[\t \f\n]/.test(c)) { return beforeAttributeName } if (c === ‘/’) { return selfClosingTag } if (c === ‘>’) { emitToken(token) return data } if (/["’<]/.test(c)) { return error(c) } attribute = new Attribute() attribute.name = c.toLowerCase() attribute.value = ’’ return attributeName } function attributeName(c) { if (c === ‘/’) { token[attribute.name] = attribute.value return selfClosingTag } if (c === ‘=’) { return beforeAttributeValue } if (/[\t \f\n]/.test(c)) { return beforeAttributeName } attribute.name += c.toLowerCase() return attributeName } function beforeAttributeValue(c) { if (c === ‘"’) { return attributeValueDoubleQuoted } if (c === “’”) { return attributeValueSingleQuoted } if (/\t \f\n/.test(c)) { return beforeAttributeValue } attribute.value += c return attributeValueUnquoted } function attributeValueDoubleQuoted(c) { if (c === ‘"’) { token[attribute.name] = attribute.value return beforeAttributeName } attribute.value += c return attributeValueDoubleQuoted } function attributeValueSingleQuoted(c) { if (c === “’”) { token[attribute.name] = attribute.value return beforeAttributeName } attribute.value += c return attributeValueSingleQuoted } function attributeValueUnquoted(c) { if (/[\t \f\n]/.test(c)) { token[attribute.name] = attribute.value return beforeAttributeName } attribute.value += c return attributeValueUnquoted } function selfClosingTag(c) { if (c === ‘>’) { emitToken(token) endToken = new EndTagToken() endToken.name = token.name emitToken(endToken) return data } } function endTagOpen(c) { if (/[a-zA-Z]/.test(c)) { token = new EndTagToken() token.name = c.toLowerCase() return tagName } if (c === ‘>’) { return error(c) } } // 输出解析好的 token(词) function emitToken(token) { syntaxer.receiveInput(token) } function error(c) { console.log(warn: unexpected char '${c}') }}class StartTagToken {}class EndTagToken {}class Attribute {}module.exports = { HTMLLexicalParser, StartTagToken, EndTagToken}// 使用const { HTMLLexicalParser } = require(’./lexer’)const testHTML = &lt;html maaa=a &gt; &lt;head&gt; &lt;title&gt;cool&lt;/title&gt; &lt;/head&gt; &lt;body&gt; &lt;img src="a" /&gt; &lt;/body&gt;&lt;/html&gt;const dummySyntaxer = { receiveInput: (token) => { if (typeof token === ‘string’) { console.log(String(${token.replace(/\n/, '\\n').replace(/ /, '&lt;whitespace&gt;')})) } else { console.log(token) } }}const lexer = new HTMLLexicalParser(dummySyntaxer)for (let c of testHTML) { lexer.receiveInput(c)}//便于理解:状态迁移代码var state = data;var charwhile(char = getInput()) state = state(char);语法分析//简单实现:伪代码function HTMLSyntaticalParser(){ var stack = [new HTMLDocument]; this.receiveInput = function(token) { //…… } this.getOutput = function(){ return stack[0]; }}const { StartTagToken, EndTagToken } = require(’./lexer’)class HTMLDocument { constructor () { this.isDocument = true this.childNodes = [] }}// 仅仅把 Node 分为 Element 和 Textclass Node {}class Element extends Node { constructor (token) { super(token) for (const key in token) { this[key] = token[key] } this.childNodes = [] } [Symbol.toStringTag] () { return Element&lt;${this.name}&gt; }}class Text extends Node { constructor (value) { super(value) this.value = value || ’’ }}function HTMLSyntaticalParser () { const stack = [new HTMLDocument] // receiveInput 负责接收词法部分产生的词(token),构建dom树的算法 this.receiveInput = function (token) { // 检查栈顶是否是 Text 节点,如果是的话就合并 Text节点 if (typeof token === ‘string’) { if (getTop(stack) instanceof Text) { getTop(stack).value += token } else { let t = new Text(token) getTop(stack).childNodes.push(t) stack.push(t) } } else if (getTop(stack) instanceof Text) { stack.pop() } // 匹配开始和结束标签 if (token instanceof StartTagToken) { let e = new Element(token) getTop(stack).childNodes.push(e) return stack.push(e) } if (token instanceof EndTagToken) { return stack.pop() } } this.getOutput = () => stack[0]}function getTop (stack) { return stack[stack.length - 1]}module.exports = { HTMLSyntaticalParser}// 使用const { HTMLSyntaticalParser } = require(’./syntaxer’)const { HTMLLexicalParser } = require(’./lexer’)const syntaxer = new HTMLSyntaticalParser()const lexer = new HTMLLexicalParser(syntaxer)const testHTML = &lt;html maaa=a &gt; &lt;head&gt; &lt;title&gt;cool&lt;/title&gt; &lt;/head&gt; &lt;body&gt; &lt;img src="a" /&gt; &lt;/body&gt;&lt;/html&gt;for (let c of testHTML) { lexer.receiveInput(c)}console.log(JSON.stringify(syntaxer.getOutput(), null, 2))扩展阅读:从Chrome源码看浏览器如何构建DOM树https://zhuanlan.zhihu.com/p/… ...

April 19, 2019 · 4 min · jiezi

简述dom diff原理

前言:关于react的虚拟dom以及每次渲染更新的dom diff,网上文章很多。但是我一直信奉一个原则,即:但凡复杂的知识,理解之后都只需要记忆简单的东西,而想简单、精确描述一个复杂知识,是极困难的事。正文dom diff是什么?1.从根节点开始遍历所有节点;2.对于不同类型的标签,删除原标签,新建标签;3.对于类型相同、属性不同的标签,只修改属性;4.对于同一个父节点下的复数同类型标签(即列表类型),基于key对比、修改。解析关于1:-遍历用的是前序遍历(先序遍历)关于2:-不同类型的标签是指:比如div和span就是不同类型的标签-如果同一个位置的标签类型改变(依然以div和span为例),那么直接删除div标签,新建一个span标签,重新渲染。原本的div标签里的一切都跟新的span标签没有关系-对于自定义的组件比如<Header />、<TodoList />之类的也适用-标签位置只相对于父节点有意义。假设原本A节点的父节点是B,更新后A节点的父节点是C,那么对于dom diff来说,原本的A节点会被销毁,在C节点下的A节点是一个新的节点,跟原本的A节点没有关系关于3:-这一个比较好理解,对于仅仅属性不同的标签,修改属性即可关于4-假设一个div下有五个span节点,此时我们要插入一个节点虚拟dom并不知道插入后是ABFCDE,而会认为除了AB以外的节点都改变了所以对于虚拟dom来说此时是ABGHIJ,付出了额外的消耗。于是react引入了key的概念。两个key相同的节点,虚拟dom会认为是同一个节点,从而对其进行比较。引入了key之后,react就知道节点是ABFCDE了。

April 18, 2019 · 1 min · jiezi

《JavaScript DOM编程艺术(第2版)》笔记

《JavaScript DOM 编程艺术(第2版)》笔记第1章:JavaScript 简史JavaScript 的起源JavaScript 是 Netscape 公司和 Sun 公司合作开发的。DOMDOM 是一套对文档的内容进行抽象和概念化的方法。浏览器战争今天,几乎所有的浏览器都内置了对 DOM 的支持,只要遵循 DOM 标准,就可以放心大胆的去做。第2章:JavaScript 语法语句建议在每条语句末尾都加上分号。注释// 单行注释/* 多行注释 多行注释*/变量var mood = “happy”;var age = 33;数据类型字符串数值布尔值数组数组关联数组对象操作符算术操作符+ ,-,*,/,++,–,+=比较操作符>,=,<,>=,<=,==,!=,===…….==并不表示严格相等,认为 false 与 "" 表示的含义相同。false == ‘’;// true=== 进行严格比较,不仅比较值,而且比较变量的类型。false === ‘’;// false逻辑操作符条件语句和循环语句if (condions) { statemen}while循环while (conditions) { statements;}for 循环函数与对象函数:function name(arguments) { statements;}对象(object):对象是一种非常重要的数据类型。对象的两种访问形式:属性 Object.property方法 Object.method()宿主对象:在Web应用中就是由浏览器提供的预定义对象。第3章:DOMDOM:文档:D (document)对象:O(object)用户定义对象内建对象:如 Array,Math,Data等。宿主对象:由浏览器提供的对象。模型: M(model)节点(node):元素节点文本节点属性节点获取元素:getElementByIdgetElementsByTagNamegetElementsByClassName获取属性:getAttribute设置属性:setAttribute第4章:案例研究:JavaScript图片库介绍了 DOM 提供的几个新属性:childNodesnodeTypenodeValuefirstChildlastChild第5章:最佳实战平稳退化平稳退化(graceful degradation):正确使用 JavaScript 脚本,可以让访问者在他们的浏览器不支持 JavaScript 的情况下仍然能顺利地浏览你的网站。不能平稳退化会影响你的网页在搜索引擎上的排名。“javascript:” 伪协议:这种做法非常不好真协议:用来在因特网上的计算机之间传输数据包,如HTTP协议(http://)、FTP协议(ftp://)等。伪协议:非标准化的协议。// 用"javascript:" 伪协议调用 popUp()函数:<a href=“javascript:popUp(‘http://www.example.com/');">Example</a>内嵌的事件处理函数<a href=”#" onclick=“popUp(‘http://www.example.com/'); return false;">Example</a>非常不好,因为#只是创建了一个空链接。平稳退化办法:将 href 属性设置为真实存在的 URL 地址,让它成为一个有效的链接。<a href=“http://www.example.com/" onclick=“popUp(this.href); return false;">Example</a>这样,即使 javascript 被禁止,这个链接也是可用的。渐进增强渐进增强就是用一些额外的信息层去包裹原始数据。按照“渐进增强”原则创建出来的网页几乎都符合“平稳退化”原则。结构、样式、行为要分离。向后兼容对象检测// 例如if (!document.getElementById) { return false;}浏览器嗅探技术通过提取浏览器供应商提供的信息来解决向后兼容问题。性能考虑尽量少访问DOM和尽量减少标记合并和放置脚本推荐把 functionA.js,functionB.js,functionC.js合并到一个脚本文件中,这样可以减少加载页面时发送的请求数量。把 <script>标签都放到文档的末尾</body>标记之前。因为位于<head>中的脚本会导致浏览器无法并行加载其他文件。压缩脚本JSMin(http://javascript.crockford.c…)YUI Compressor第6章:案例研究:图片库改进版共享onload事件:addLoadEvent()函数需要多个函数都在页面加载时执行。addLoadEvent只有一个参数:打算在页面加载完毕时执行的函数的名字。把现有的 window.onload 事件处理函数的值存入变量 oldonload;如果这个处理函数上还没有绑定任何函数,就像平时那样把新函数添加给它;如果这个处理函数上已经绑定了一些函数,就把新函数追加到现有指令的末尾。function addLoadEvent(func) { var oldonload = window.onload; if (typeof window.onload != “function”) { window.onload = func; } else { window.onload = function (){ oldonload(); func(); } }}第7章 动态创建标记动态添加注意:用document.createElement创建一个空白的p元素,想在p元素内部添加内容,实际上内容也是一个文本节点,所以应该document.createTextNode创建一个文本节点,再用.appendChild添加到p节点中。window.onload = function () { var testdiv = document.getElementById(“testdiv”); var para = document.createElement(“p”); // 创建 p 元素节点 var txt1 = document.createTextNode(“This is”); // 创建 文本节点 var em = document.createElement(“em”); // 创建 em 元素节点 var txt2 = document.createTextNode(“content.”); // 创建 文本节点 var txt3 = document.createTextNode(“my “); // 创建 文本节点 testdiv.appendChild(para); // 将 p 元素节点添加到 div 中 para.appendChild(txt1); // 将文本节点添加到 p 元素中 para.appendChild(em); para.appendChild(txt2); em.appendChild(txt3); // 将文本节点添加到 em 元素中}在现有元素后插入一个元素:insertAfter()函数function insertAfter(newElement, targetElement) { var parent = targetElement.parentNode; if (parent.lastChild == targetElement) { parent.appendChild(newElement); } else { parent.insertBefore(newElement, targetElement.nextSibling); }}第8章:充实文档的内容把文档里的缩略语显示为一个“缩略语列表”为文档里引用的每段文献节选生成一个“文献来源链接”把文档所支持的快捷键线是位于分“快捷键清单”第9章:CSS-DOM给一个元素追加新的 class:function addClass(element,value) { if (!element.className) { element.className = value; } else { newClassName = element.className; newClassName+= " “; newClassName+= value; element.className = newClassName; }}第10章 :用JavaScript实现动画效果位置positionstaticfixedrelativeabsolute时间var variable = setTimeout(“function”,interval)clearTimeout(variable)parseInt把字符串里的数值信息提取出来第11章:HTML5canvas音频和视频表单第12章:综合示例 ...

April 4, 2019 · 2 min · jiezi

回到基础:理解 JavaScript DOM

翻译:疯狂恶的技术宅https://medium.freecodecamp.o…本文首发微信公众号:jingchengyideng欢迎关注,每天都给你推送新鲜的前端技术文章Javascript DOM(文档对象模型)是一个允许开发人员操纵页面内容、结构和风格的接口。在本文中,我们将理解什么是 DOM 以及如何用 Javascript 去操作它。本文还可以作为基本 DOM 操作的参考。什么是 DOM?基本上网页由 HTML 和 CSS 文档组成。浏览器用于创建文档的描述被称为文档对象模型(DOM)。它使 Javascript 能够访问和操作页面的元素和样式。该模型构建在基于对象的树结构中,并定义:HTML 元素作为对象HTML 元素的属性和事件访问HTML元素的方法HTML DOM模型元素的位置称为节点。不仅元素获得节点,而且元素和文本的属性也有属于它们自己的节点(属性节点和文本节点)。DOM 文档DOM 文档是网页中所有其他对象的所有者。这意味着如果你想访问网页上的任何对象,必须从这里开始。它还包含许多重要的属性和方法,使我们能够访问和修改自己的页面。查找 HTML 元素现在我们了解了 DOM 文档是什么,接下来就可以开始获取我们的第一个 HTML 元素了。 Javascript DOM 有许多不同的方法可以用,不过这些最常见:按 ID 获取元素getElementById() 方法用于通过其 id 获取单个元素。我们来看一个例子:var title = document.getElementById(‘header-title’);我们得到 id 为 header-title 的元素,并将其保存到变量中。按类名获取元素还可以用 getElementsByClassName() 方法获取多个对象,该方法返回一个元素数组。var items = document.getElementsByClassName(‘list-items’);这里我们得到类 list-items 的所有项目,并将它们保存到变量中。按标签名称获取元素还可以用 getElementsByTagName() 方法按标记名称获取元素。var listItems = document.getElementsByTagName(‘li’);这里我们获取 HTML 文档中所有得 li 元素并将它们保存到变量中。QueryselectorquerySelector() 方法返回与指定的 CSS选择器匹配的第一个元素。这意味着你可以通过id、class、tag和所有其他有效的 CSS 选择器获取元素。在这里我列出了一些最常用的选项。按 id 获取:var header = document.querySelector(‘#header’)按 class 获取:var items = document.querySelector(‘.list-items’)按标签获取:var headings = document.querySelector(‘h1’);获取更具体的元素:我们还可以使用 CSS Selectors 获得更多的特定元素。document.querySelector(“h1.heading”);在这个例子中,我们同时搜索标记和类,并返回传递给 CSS Selector 的第一个元素。QueryselectorallquerySelectorAll() 方法与 querySelector() 完全相同,只是它返回符合 CSS Selector 的所有元素。var heading = document.querySelectorAll(‘h1.heading’);在这个例子中,我们得到所有属于 heading 类的 h1 标签,并将它们存储在一个数组中。本文首发微信公众号:jingchengyideng欢迎关注,每天都给你推送新鲜的前端技术文章更改 HTML 元素HTML DOM 允许我们通过更改其属性来对 HTML 元素的内容和样式进行修改。更改HTMLinnerHTML 属性可用于修改 HTML 元素的内容。document.getElementById(“#header”).innerHTML = “Hello World!”;在这个例子中,我们得到 id 为 header 的元素,并把其内容设置为“Hello World!”。InnerHTML 还可以把标签放入另一个标签中。document.getElementsByTagName(“div”).innerHTML = “<h1>Hello World!</h1>“在这里将 h1 标记放入所有已存在的 div 中。更改属性的值还可以用 DOM 更改属性的值。document.getElementsByTag(“img”).src = “test.jpg”;在这个例子中,我们把所有 <img/> 标签的 src 改为 test.jpg。改变样式要更改 HTML 元素的样式,需要更改元素的样式属性。以下是更改样式的示例语法:document.getElementById(id).style.property = new style接下来看一个例子,我们获取一个元素并将底部边框改为纯黑线:document.getElementsByTag(“h1”).style.borderBottom = “solid 3px #000”;CSS 属性需要用 camelcase 而不是普通的 css 属性名来编写。在这个例子中,我们用 borderBottom 而不是 border-bottom。添加和删除元素现在我们来看看如何添加新元素和删除现有元素。添加元素var div = document.createElement(‘div’);在这里我们用了 createElement() 方法创建一个 div 元素,该方法将标记名作为参数并将其保存到变量中。之后只需要给它一些内容,然后将其插入到 DOM 文档中。var content = document.createTextNode(“Hello World!”); div.appendChild(newContent);document.body.insertBefore(div, currentDiv);这里用了 createTextNode() 方法创建内容,该方法用字符串作参数,然后在文档中已经存在的 div 之前插入新的 div 元素。删除元素var elem = document.querySelector(’#header’);elem.parentNode.removeChild(elem);本例中我们得到一个元素并使用 removeChild() 方法将其删除。替换元素现在让我们来看看怎样替换一个项目。var div = document.querySelector(’#div’);var newDiv = document.createElement(‘div’);newDiv.innerHTML = “Hello World2"div.parentNode.replaceChild(newDiv, div);这里我们使用 replaceChild() 方法替换元素。第一个参数是新元素,第二个参数是要替换的元素。直接写入HTML输出流还可以使用 write() 方法将 HTML 表达式和 JavaScript 直接写入 HTML 输出流。document.write(“<h1>Hello World!</h1><p>This is a paragraph!</p>”);我们也可以把像日期对象这样的参数传给 JavaScript 表达式。document.write(Date());write() 方法还可以使用多个参数,这些参数会按其出现的顺序附加到文档中。事件处理HTML DOM 允许 Javascript 对 HTML 事件做出反应。下面列出了一些比较重要的事件:鼠标点击页面加载鼠标移动输入字段更改分配事件可以用标记上的属性直接在 HTML 代码中定义事件。以下是 onclick 事件的例子:<h1 onclick=”this.innerHTML = ‘Hello!’”>Click me!</h1>在此例中,单击按钮时,<h1/> 的文本将被改为 “Hello!”。还可以在触发事件时调用函数,如下一个例子所示。<h1 onclick=”changeText(this)”>Click me!</h1>这里我们在单击按钮时调用 changeText() 方法,并将该元素作为属性传递。还可以用 Javascript 代码为多个元素分配相同的事件。document.getElementById(“btn”).onclick = changeText();指定事件监听器接下来看看怎样为 HTML 元素分配事件监听器。document.getElementById(“btn”)addEventListener(‘click’, runEvent);这里我们刚刚指定了一个 click 事件,在单击 btn 元素时调用 runEvent 方法。当然还可以把多个事件指定给单个元素:document.getElementById(“btn”)addEventListener(‘mouseover’, runEvent);节点关系DOM Document 中的节点之间具有层次关系。这意味着节点的结构类似于树。我们用 parent,sibling 和 child 等术语来描述节点之间的关系。顶级节点称为根节点,是唯一一个没有父节点的节点。普通 HTML 文档中的根是 <html/> 标记,因为它没有父标记,并且是文档的顶部标记。在节点之间导航可以用以下属性在节点之间导航:parentNodechildNodesfirstChildlastChildnextSibling下面是得到 h1 的父元素的例子。var parent = document.getElementById(“heading”).parentNode总结望本文能帮助你理解 Javascript DOM 以及如何用它来操作页面上的元素。如果你觉得本文有用,请推荐给你的朋友和他们分享。如果你有什么问题或反馈,请在下面的评论中告诉我。本文首发微信公众号:jingchengyideng欢迎扫描二维码关注公众号,每天都给你推送新鲜的前端技术文章欢迎继续阅读本专栏其它高赞文章:12个令人惊叹的CSS实验项目世界顶级公司的前端面试都问些什么CSS Flexbox 可视化手册过节很无聊?还是用 JavaScript 写一个脑力小游戏吧!从设计者的角度看 ReactCSS粘性定位是怎样工作的一步步教你用HTML5 SVG实现动画效果程序员30岁前月薪达不到30K,该何去何从7个开放式的前端面试题React 教程:快速上手指南 ...

March 28, 2019 · 2 min · jiezi

[译] 究竟什么是DOM?!

原文自工程师Kara Luton博客,传送门DOM,当我第一次在训练营学习编码时,就一直听到这个词,但是我从来不知道它到底是什么意思。是我写的HTML吗?还是我偶尔打开控制台检查元素的时候点击的元素?说实话,我花了好长时间才弄明白DOM究竟是什么。根据W3C,文档对象模型(DOM)是“有效HTML和格式良好的XML文档的应用程序编程接口。它定义了文档的逻辑结构以及文档的访问和操作方式”。什么?简单来说,DOM是HTML在浏览器中的表示形式,允许您操纵页面。那么为什么它经常被称为树呢?这是因为DOM从一个父项开始,该父项扩展为子项。这些子项的项也可以延伸到他们自己的小树中,就像你在上图中看到的那样。我在一些网站上读到,你在DevTools中看到的就是DOM的可视化表示,虽然它非常接近,但并不完全正确。DevTools将包含一些额外的信息(比如伪类),这些信息在技术上不是DOM的一部分。如果你是像我这样的可视化人员,这是我们在浏览器中所能看到的最接近DOM的图像。但等一下,这难道不意味着DOM和我们正在编写的HTML是一样的吗?不。您是否曾经不小心遗漏了一个必需的元素,并让浏览器为您修复它?您将在DOM中看到这个元素,尽管您已经将它从HTML中删除了。如果您通过JavaScript操作DOM,那么DOM也将不同于HTML。使用JavaScript可以编辑页面的CSS、添加事件监听器、更改节点属性等等。这些都在改变DOM原来在HTML中编写的内容。总结一下,虽然DOM听起来非常吓人,但实际上是它决定着在浏览器页面上呈现什么,以及JavaScript可以通过DOM来操纵呈现的元素。非常感谢你阅读我关于DOM的文章!一定要在Twitter上关注我关于技术的大量推文,说实话,我也发了很多关于狗的推特。如果您有任何问题,请随时在下面发表评论或发推文给我。

March 19, 2019 · 1 min · jiezi

原生 js 获取 dom 元素 querySelector() 替代 getElementById()

原生 js 获取 dom 元素 querySelector() 替代 getElementById()替代 getElementById()很长一段时间以来,除了 jQuery 的选择器之外,我一直在用下面这几个方法获取 dom 元素document.getElementById()document.getElementsByClassName()document.getElementsByTagName()document.getElementsByName()后来才发现 querySelector() 这个方法,这个方法跟 jquery 的获取元素方法是一样的。里面填写的是 css 选择器。比如,下面这几个获取的元素是一样的:// getElementById() 方式document.getElementById(‘username’);// querySelector() 方式document.querySelector(’#username’);// jquery 方式$(’#username’)[0] // 不理解这个可以百度 jquery 与 dom 相互转换querySelector() 有两种方式querySelector( css选择器字符串 ) // 获取第一个匹配元素querySelectorAll( css选择器字符串 ) // 获取所有匹配元素效果如图:其获取元素的方式跟 jquery 很像,但取到的元素并不一样,jquery 取得的是 jquery 元素,而 querySelector() 获取的是 dom 对象。例子关于选择器,参阅: http://www.w3school.com.cn/cs…比如,现在需要获取 所有 class 以 text- 开头的元素,也就是说包含 text-success,text-danger,text-warning 等元素,就这样写:document.querySelectorAll("[class^=‘text-’]")

March 11, 2019 · 1 min · jiezi

回到基础:如何用原生 DOM API 生成表格

翻译:疯狂的技术宅原文:https://www.valentinog.com/bl…怎样用原生 JavaScript 生成表格需?本文告诉你答案!这是一个刷 JavaScript 经验值的好机会:在技术面试中出现的最多的一个问题就是怎样用原生 API 操作 DOM 。在下面的教程中,我们将了解如何使用 JavaScript 生成表格,而无需依赖任何库或框架。你将学到些什么在本教程中,你将学习如何:用 JavaScript 生成一个表格用本机 DOM API 来操作表要求要学习本教程,你应该对 HTML 和 JavaScript 有基本的了解。需求Eloquent JavaScript 是一本非常好的 JavaScript 书籍。这本书很简洁,也不乏味,同时有大量的练习。以下练习改编自第 14 章,它被称为“构建表格”。题目要求你用 JavaScript 构建一个 HTML 表。你的任务是依据 “mountains” 数组中的数据生成表格,将对象中的key对应到列并且每行一个对象。每个对象都是如下形式:{ name: “Monte Falco”, height: 1658, place: “Parco Foreste Casentinesi” }我们有一个名称,一个高度和一个山峰所在的位置。但 HTML 表格是什么? HTML 表格是包含表格数据的元素,以行和列的形式显示。这意味着给出以下数组:let mountains = [ { name: “Monte Falco”, height: 1658, place: “Parco Foreste Casentinesi” }, { name: “Monte Falterona”, height: 1654, place: “Parco Foreste Casentinesi” }];我们打算生成下表:<table> <thead> <tr> <th>name</th> <th>height</th> <th>place</th> </tr> </thead> <tbody> <tr> <td>Monte Falco</td> <td>1658</td> <td>Parco Foreste Casentinesi</td> </tr> <tr> <td>Monte Falterona</td> <td>1654</td> <td>Parco Foreste Casentinesi</td> </tr> </tbody></table>如你所见,该表有一个 thead(表头),其中包含一个具有三个th(表格标题)的 tr(表格行) 。然后是tbody(表体) 中包含一堆 tr(表格行)。每个表格行包含一定数量的 td元素(表格单元格)。有了这些要求,就可以开始编写 JavaScript 文件了。我们的起点可以是以下 HTML:<!DOCTYPE html><html lang=“en”><head> <meta charset=“UTF-8”> <title>Build a table</title></head><body><table><!– here goes our data! –></table></body><script src=“build-table.js”></script></html>将文件另存为 build-table.html 并继续下一部分。生成表头在与 build-table.html 相同的文件夹中创建一个名为 build-table.js 的新文件,并在文件定义数组:let mountains = [ { name: “Monte Falco”, height: 1658, place: “Parco Foreste Casentinesi” }, { name: “Monte Falterona”, height: 1654, place: “Parco Foreste Casentinesi” }, { name: “Poggio Scali”, height: 1520, place: “Parco Foreste Casentinesi” }, { name: “Pratomagno”, height: 1592, place: “Parco Foreste Casentinesi” }, { name: “Monte Amiata”, height: 1738, place: “Siena” }];第一个目标是生成表头。我们知道本机方法 createElement() 会创建传递给它的任何元素。假设我们要创建一个表头,可以用 document.createElement(‘thead’)。不过还有更好的办法吗?让我们先到 MDN 上查阅一下 element table reference 。你可以看到表格的DOM接口是 HTMLTableElement。HTMLTableElement 的有趣之处在于它公开的方法中有 createTHead()。没错!createTHead 返回与给定表关联的表头元素,更 6 的是,如果表中不存在头的话,createTHead 会帮我们创建一个。有了这些知识,接下来在我们的文件中创建一个函数,将 table 作为参数。鉴于我们的需求,可以在其中创建一个新的 thead:function generateTableHead(table) { let thead = table.createTHead();}现在找到我们的表格(记住在 build-table.html 中有一个)并将其传给我们的函数:function generateTableHead(table) { let thead = table.createTHead();}let table = document.querySelector(“table”);generateTableHead(table);如果你在浏览器中打开 build-table.html,在屏幕上还看不到任何内容,不过可以在开发者工具中看到处理的结果。填充表头的工作只做了一半,可以看到表头中填充了一堆 th。每个表头必须映射到对象描述数据组成的 key 上。信息已经存在于数组 mountains 中的第一个对象内部。可以迭代第一个对象的 key:let mountains = [ { name: “Monte Falco”, height: 1658, place: “Parco Foreste Casentinesi” }, //];然后用得到的 key 生成三个表头。但是要先在 thead 中添加一行!这时候该用 document.createElement(“TR”) 了吧?不不不。HTMLTableRowElement 提供了一个 insertRow() 方法,可以在表头上调用。让我们重构一下 generateTableHead 函数:function generateTableHead(table) { let thead = table.createTHead(); let row = thead.insertRow();}新行应包含三个 th(表头),需要手动创建,对于每个 th 元素,我们将附加一个文本节点。这个函数可以使用另一个参数来进行迭代:function generateTableHead(table, data) { let thead = table.createTHead(); let row = thead.insertRow(); for (let key of data) { let th = document.createElement(“th”); let text = document.createTextNode(key); th.appendChild(text); row.appendChild(th); }}let table = document.querySelector(“table”);let data = Object.keys(mountains[0]);generateTableHead(table, data);保存文件并刷新 build-table.html:你应该看到你的表头中被填充了 name、height 和 place。 有时用 React 和 Vue 偷懒的感觉真好,直接操作 DOM 是多么艰难和繁琐。不过我们的工作还没有完成。接下来该填表了……生成行和单元格为了填充表格可以遵循同样的方法,但这次我们需要迭代 mountains 数组中的每个对象。当进入 for…of 循环时,将为每个项目创建一个新行。要创建行,你将用到 insertRow()。但我们不能止步于此。在主循环内部,需要一个内循环,这次要用到 for… in 。内部循环迭代当前对象的每个 key,同时它:创建一个新单元格创建一个新的文本节点将文本节点附加到单元格使用 HTMLTableRowElement 的另一个方法 insertCell() 创建单元格。也就是说通过以上逻辑可以填充我们的表。打开 build-table.js 并创建一个名为 generateTable 的新函数。命名可以与我们现有的函数相同:function generateTable(table, data) { for (let element of data) { let row = table.insertRow(); for (key in element) { let cell = row.insertCell(); let text = document.createTextNode(element[key]); cell.appendChild(text); } }}可以这样调用它:generateTable(table, mountains);最后来看看完整的代码:let mountains = [ { name: “Monte Falco”, height: 1658, place: “Parco Foreste Casentinesi” }, { name: “Monte Falterona”, height: 1654, place: “Parco Foreste Casentinesi” }, { name: “Poggio Scali”, height: 1520, place: “Parco Foreste Casentinesi” }, { name: “Pratomagno”, height: 1592, place: “Parco Foreste Casentinesi” }, { name: “Monte Amiata”, height: 1738, place: “Siena” }];function generateTableHead(table, data) { let thead = table.createTHead(); let row = thead.insertRow(); for (let key of data) { let th = document.createElement(“th”); let text = document.createTextNode(key); th.appendChild(text); row.appendChild(th); }}function generateTable(table, data) { for (let element of data) { let row = table.insertRow(); for (key in element) { let cell = row.insertCell(); let text = document.createTextNode(element[key]); cell.appendChild(text); } }}let table = document.querySelector(“table”);let data = Object.keys(mountains[0]);generateTableHead(table, data);generateTable(table, mountains);你觉得它能工作吗?让我们来试试看:呃……看起来行被附加到了表头而不是表体。另外没有table body!但是如果切换函数调用顺序会怎么样呢?试试吧:// omitted for brevitylet table = document.querySelector(“table”);let data = Object.keys(mountains[0]);generateTable(table, mountains); // generate the table firstgenerateTableHead(table, data); // then the head再次刷新浏览器:好使!另外还得到了一个 tbody。为什么会这样?当你在空表上调用 insertRow() 时,这些方法会为自动你创建一个tbody(如果没有的话)。做得好!不过我们的代码可能没进行很好的组织(有太多的全局绑定),这些将会在下一篇文章中提到。到此为止,你应该能够在不依赖任何外部库的情况下操作HTML表了。恭喜!总结在本教程中,我们学到了如何用原生 JavaScript 生成表格。 HTML 表格在DOM中由 HTMLTableElement 体现。这个接口公开了很多有用的方法,其中 createTHead 用于操作表头,insertRow 用来插入行。另外 HTML 表格的行继承自 HTMLTableRowElement。这个接口有两种方法,其中最重要的是 insertCell。给定一个对象数组,可以使用 for…of 循环来迭代生成行。对于每个对象,我们可以使用 for … in 生成单元格。我们有一些带有全局绑定的代码(请参阅执行上下文和调用堆栈以获取更多信息)。在下一篇文章中,我们将看到怎样重构这些代码。jQuery正逐渐消失。 Bootstrap将在版本5中删除它。 原生DOM API 越来越好了,替换以前用 jQuery 做的事情是可行的,没有(几乎)任何额外的依赖。但即使没有 jQuery 也很容易掉进坑里。有人说你不应该在没有 $shinyNewLibrary 的情况下去操纵 DOM。实际上每个认真的 JavaScript 开发人员都应该知道原生 DOM API,以及如何使用 JavaScript 操作 DOM 。这些问题在技术面试中很容易被问到,你不想因此被拒绝吧?本教程的代码可在 Github 下载(https://github.com/valentinog…)。感谢阅读并敬请期待后续!欢迎继续阅读本专栏其它高赞文章:12个令人惊叹的CSS实验项目世界顶级公司的前端面试都问些什么CSS Flexbox 可视化手册过节很无聊?还是用 JavaScript 写一个脑力小游戏吧!从设计者的角度看 ReactCSS粘性定位是怎样工作的一步步教你用HTML5 SVG实现动画效果程序员30岁前月薪达不到30K,该何去何从7个开放式的前端面试题React 教程:快速上手指南本文首发微信公众号:jingchengyideng欢迎扫描二维码关注公众号,每天都给你推送新鲜的前端技术文章 ...

March 10, 2019 · 3 min · jiezi

关于DOM操作是异步的还是同步的相关理解

作者:心叶时间:2019-03-08 09:45我的理解先列出我的理解,然后再从具体的例子中说明:DOM操作本身应该是同步的(当然,我说的是单纯的DOM操作,不考虑ajax请求后渲染等)DOM操作之后导致的渲染等是异步的(在DOM操作简单的情况下,是难以察觉的)证明存在异步DOM从操作到渲染结束,我想先用一个具体的例子来说明。例子说明:把img标签先追加到页面,然后把img里面的内容绘制到canvas上,代码如下:<body> <div id=‘target’></div> <canvas id=‘canvas’></canvas> <script> var template = ‘<img ’ + ‘id=“img” ’ + ‘width=“300” ’ + ‘height=“150” ’ + ‘src=“data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg'><foreignObject ’ + ‘width='120' ’ + ‘height='50' ’ + ‘><body xmlns='http://www.w3.org/1999/xhtml'>’ + ‘<p>这是一个例子</p>’ + ‘</body></foreignObject></svg>” />’; // 第一步,添加到页面 document.getElementById(’target’).innerHTML = template; // 第二步:绘制到canvas上 document.getElementById(‘canvas’) .getContext(‘2d’) .drawImage(document.getElementById(‘img’), 0, 0); </script></body>看看运行效果:canvas上什么也没有绘制出来,而img上面是有内容的(也就是「这是一个例子」这段文字)。接着,在img添加到页面后,绘制canvas前添加一个延迟,我们修改一下第二步地方的代码如下: window.setTimeout(function () { document.getElementById(‘canvas’) .getContext(‘2d’) .drawImage(document.getElementById(‘img’), 0, 0); }, 100);再次运行,查看效果:内容出来了。因此,异步是存在的,只不过是在DOM操作还是渲染上就不清楚了。证明DOM操作是同步的接着上面的例子,想证明DOM操作是同步的很简单,依旧修改第二步的代码如下: window.setTimeout(function () { document.getElementById(‘canvas’) .getContext(‘2d’) .drawImage(document.getElementById(‘img22’), 0, 0); }, 100);我们修改drawImage方法查找结点的id为一个错误的’img22’,显然查找不到,运行结果如下:我们看见浏览器报错了,因此,如果DOM操作是异步的,在没有添加延迟的时候不应该是什么都没有绘制出来,而是应该报错,因此DOM是同步的,那么渲染就是异步的。例子结束,代码地址:https://github.com/yelloxing/…总结DOM操作只是结点操作,而页面最终的效果还会有render渲染树等参与,因此,虽然DOM操作是同步的,而你期望的「DOM操作」却不一定是同步的,包括调用外设(外设要看具体设备,有的设备会阻塞浏览器执行,什么意思,就是浏览器的异步操作也会停止,结合这里的异步操作的理解,就可以解释一些奇怪现象了)等,需要在日常开发的时候注意。 ...

March 8, 2019 · 1 min · jiezi

JavaScript 是如何工作: Shadow DOM 的内部结构+如何编写独立的组件!

这是专门探索 JavaScript 及其所构建的组件的系列文章的第 17 篇。如果你错过了前面的章节,可以在这里找到它们:JavaScript 是如何工作的:引擎,运行时和调用堆栈的概述!JavaScript 是如何工作的:深入V8引擎&编写优化代码的5个技巧!JavaScript 是如何工作的:内存管理+如何处理4个常见的内存泄漏!JavaScript 是如何工作的:事件循环和异步编程的崛起+ 5种使用 async/await 更好地编码方式!JavaScript 是如何工作的:深入探索 websocket 和HTTP/2与SSE +如何选择正确的路径!JavaScript 是如何工作的:与 WebAssembly比较 及其使用场景!JavaScript 是如何工作的:Web Workers的构建块+ 5个使用他们的场景!JavaScript 是如何工作的:Service Worker 的生命周期及使用场景!JavaScript 是如何工作的:Web 推送通知的机制!JavaScript是如何工作的:使用 MutationObserver 跟踪 DOM 的变化!JavaScript是如何工作的:渲染引擎和优化其性能的技巧!JavaScript是如何工作的:深入网络层 + 如何优化性能和安全!JavaScript是如何工作的:CSS 和 JS 动画底层原理及如何优化它们的性能!JavaScript的如何工作的:解析、抽象语法树(AST)+ 提升编译速度5个技巧!JavaScript是如何工作的:深入类和继承内部原理+Babel和 TypeScript 之间转换!JavaScript是如何工作的:存储引擎+如何选择合适的存储API!概述Web Components 是一套不同的技术,允许你创建可重用的定制元素,它们的功能封装在你的代码之外,你可以在 Web 应用中使用它们。Web组件由四部分组成:Shadow DOM(影子DOM)HTML templates(HTML模板)Custom elements(自定义元素)HTML Imports(HTML导入)在本文中主要讲解 Shadow DOM(影子DOM)Shadow DOM 这款工具旨在构建基于组件的应用。因此,可为网络开发中的常见问题提供解决方案:隔离 DOM:组件的 DOM 是独立的(例如,document.querySelector() 不会返回组件 shadow DOM 中的节点)。作用域 CSS:shadow DOM 内部定义的 CSS 在其作用域内。样式规则不会泄漏,页面样式也不会渗入。组合:为组件设计一个声明性、基于标记的 API。简化 CSS - 作用域 DOM 意味着您可以使用简单的 CSS 选择器,更通用的 id/类名称,而无需担心命名冲突。Shadow DOM本文假设你已经熟悉 DOM 及其它的 Api 的概念。如果不熟悉,可以在这里阅读关于它的详细文章—— https://developer.mozilla.org…。阴影 DOM 只是一个普通的 DOM,除了两个区别:创建/使用的方式与页面其他部分有关的行为方式通常,你创建 DOM 节点并将其附加至其他元素作为子项。 借助于 shadow DOM,您可以创建作用域 DOM 树,该 DOM 树附加至该元素上,但与其自身真正的子项分离开来。这一作用域子树称为影子树。被附着的元素称为影子宿主。 您在影子中添加的任何项均将成为宿主元素的本地项,包括 <style>。 这就是 shadow DOM 实现 CSS 样式作用域的方式通常,创建 DOM 节点并将它们作为子元素追加到另一个元素中。借助于 shadow DOM,创建一个作用域 DOM 树,附该 DOM 树附加到元素上,但它与实际的子元素是分离的。这个作用域的子树称为 影子树,被附着的元素称为影子宿主。向影子树添加的任何内容都将成为宿主元素的本地元素,包括 <style>,这就是 影子DOM 实现 CSS 样式作用域的方式。创建 shadow DOM影子根是附加到“宿主”元素的文档片段,元素通过附加影子根来获取其 shadow DOM。要为元素创建阴影 DOM,调用 element.attachShadow() :var header = document.createElement(‘header’);var shadowRoot = header.attachShadow({mode: ‘open’});var paragraphElement = document.createElement(‘p’);paragraphElement.innerText = ‘Shadow DOM’;shadowRoot.appendChild(paragraphElement);规范定义了元素列表,这些元素无法托管影子树,元素之所以在所选之列,其原因如下:浏览器已为该元素托管其自身的内部 shadow DOM(<textarea>、<input>)。让元素托管 shadow DOM 毫无意义 (<img>)。例如,以下方法行不通:document.createElement(‘input’).attachShadow({mode: ‘open’});// Error. &lt;input&gt; cannot host shadow dom.Light DOM这是组件用户写入的标记。该 DOM 不在组件 shadow DOM 之内,它是元素的实际孩子。假设已经创建了一个名为<extended-button> 的定制组件,它扩展了原生 HTML 按钮组件,此时希望在其中添加图像和一些文本。代码如下:<extended-button> <!– the image and span are extended-button’s light DOM –> <img src=“boot.png” slot=“image”> <span>Launch</span></extended-button>“extension -button” 是定义的定制组件,其中的 HTML 称为 Light DOM,该组件由用户自己添加。这里的 Shadow DOM 是你创建的组件 extension-button。Shadow DOM是 组件的本地组件,它定义了组件的内部结构、作用域 CSS 和 封装实现细节。扁平 DOM 树浏览器将用户创建的 Light DOM 分发到 Shadow DOM,并对最终产品进行渲染。扁平树是最终在 DevTools 中看到的以及页面上呈渲染的对象。<extended-button> #shadow-root <style>…</style> <slot name=“image”> <img src=“boot.png” slot=“image”> </slot> <span id=“container”> <slot> <span>Launch</span> </slot> </span></extended-button>模板 (Templates)如果需要 Web 页面上重复使用相同的标签结构时,最好使用某种类型的模板,而不是一遍又一遍地重复相同的结构。这在以前也是可以实现,但是 HTML <template> 元素(在现代浏览器中得到了很好的支持)使它变得容易得多。此元素及其内容不在 DOM 中渲染,但可以使用 JavaScript 引用它。一个简单的例子:<template id=“my-paragraph”> <p> Paragraph content. </p></template>这不会出现在页面中,直到使用 JavaScrip t引用它,然后使用如下方式将其追加到 DOM 中:var template = document.getElementById(‘my-paragraph’);var templateContent = template.content;document.body.appendChild(templateContent);到目前为止,已经有其他技术可以实现类似的行为,但是,正如前面提到的,将其原生封装起来是非常好的,Templates 也有相当不错的浏览器支持:模板本身是有用的,但它们与自定义元素配合会更好。 可以 customElement Api 能定义一个自定义元素,并且告知 HTML 解析器如何正确地构造一个元素,以及在该元素的属性变化时执行相应的处理。让我们定义一个 Web 组件名为 <my-paragraph>,该组件使用之前模板作为它的 Shadow DOM 的内容:customElements.define(‘my-paragraph’, class extends HTMLElement { constructor() { super(); let template = document.getElementById(‘my-paragraph’); let templateContent = template.content; const shadowRoot = this.attachShadow({mode: ‘open’}).appendChild(templateContent.cloneNode(true)); }});这里需要注意的关键点是,我们向影子根添加了模板内容的克隆,影子根是使用 Node.cloneNode() 方法创建的。因为将其内容追加到一个 Shadow DOM 中,所以可以在模板中使用 <style> 元素的形式包含一些样式信息,然后将其封装在自定义元素中。如果只是将其追加到标准 DOM 中,它是无法工作。例如,可以将模板更改为:<template id=“my-paragraph”> <style> p { color: white; background-color: #666; padding: 5px; } </style> <p>Paragraph content. </p></template>现在自定义组件可以这样使用:<my-paragraph></my-paragraph><slot> 元素模板有一些缺点,主要是静态内容,它不允许我们渲染变量/数据,好可以让我们按照一般使用的标准 HTML 模板的习惯来编写代码。Slot 是组件内部的占位符,用户可以使用自己的标记来填充。让我们看看上面的模板怎么使用 slot :<template id=“my-paragraph”> <p> <slot name=“my-text”>Default text</slot> </p></template>如果在标记中包含元素时没有定义插槽的内容,或者浏览器不支持插槽,<my-paragraph> 就只展示文本 “Default text”。为了定义插槽的内容,应该在 <my-paragraph> 元素中包含一个 HTML 结构,其中的 slot 属性的值为我们定义插槽的名称:<my-paragraph> <span slot=“my-text”>Let’s have some different text!</span></my-paragraph>可以插入插槽的元素称为 Slotable; 当一个元素插入一个插槽时,它被称为开槽 (slotted)。注意,在上面的例子中,插入了一个 <span> 元素,它是一个开槽元素,它有一个属性 slot,它等于 my-text,与模板中的 slot 定义中的 name 属性的值相同。在浏览器中渲染后,上面的代码将构建以下扁平 DOM 树:<my-paragraph> #shadow-root <p> <slot name=“my-text”> <span slot=“my-text”>Let’s have some different text!</span> </slot> </p></my-paragraph>设定样式使用 shadow DOM 的组件可通过主页来设定样式,定义其自己的样式或提供钩子(以 CSS 自定义属性的形式)让用户替换默认值。组件定义的样式作用域 CSS 是 Shadow DOM 最大的特性之一:外部页面的 CSS 选择器不应用于组件内部组件内定义的样式不会影响页面的其他元素,它们的作用域是宿主元素shadow DOM 内部使用的 CSS 选择器在本地应用于组件实际上,这意味着我们可以再次使用公共vid/类名,而不用担心页面上其他地方的冲突,最佳做法是在 Shadow DOM 内使用更简单的 CSS 选择器,它们在性能上也不错。看看在 #shadow-root 定义了一些样式的:#shadow-root<style> #container { background: white; } #container-items { display: inline-flex; }</style><div id=“container”></div><div id=“container-items”></div>上面例子中的所有样式都是#shadow-root的本地样式。使用<link>元素在#shadow-root中引入样式表,这些样式表也都属于本地的。:host 伪类选择器使用 :host 伪类选择器,用来选择组件宿主元素中的元素 (相对于组件模板内部的元素)。<style> :host { display: block; /* by default, custom elements are display: inline / }</style>当涉及到 :host 选择器时,应该小心一件事:父页面中的规则具有比元素中定义的 :host 规则具有更高的优先级,这允许用户从外部覆盖顶级样式。而且 :host 只在影子根目录下工作,所以你不能在Shadow DOM 之外使用它。如果 :host(<selector>) 的函数形式与 <selector> 匹配,你可以指定宿主,对于你的组件而言,这是一个很好的方法,它可让你基于宿主将对用户互动或状态的反应行为进行封装,或对内部节点进行样式设定:<style> :host { opacity: 0.4; } :host(:hover) { opacity: 1; } :host([disabled]) { / style when host has disabled attribute. / background: grey; pointer-events: none; opacity: 0.4; } :host(.pink) > #tabs { color: pink; / color internal #tabs node when host has class=“pink”. */ }</style>:host-context(<selector>):host-context(<selector>) 或其任意父级与 <selector> 匹配,它将与组件匹配。 例如,在文档的元素上可能有一个用于表示样式主题 (theme) 的 CSS 类,而我们应当基于它来决定组件的样式。比如,很多人都通过将类应用到 <html> 或 <body> 进行主题化:<body class=“lightheme”> <custom-container> … </custom-container></body>在下面的例子中,只有当某个祖先元素有 CSS 类theme-light时,我们才会把background-color样式应用到组件内部的所有元素中::host-context(.theme-light) h2 { background-color: #eef;}/deep/组件样式通常只会作用于组件自身的 HTML 上,我们可以使用 /deep/ 选择器,来强制一个样式对各级子组件的视图也生效,它不但作用于组件的子视图,也会作用于组件的内容。在下面例子中,我们以所有的元素为目标,从宿主元素到当前元素再到 DOM 中的所有子元素::host /deep/ h3 { font-style: italic;}/deep/ 选择器还有一个别名 >>>,可以任意交替使用它们。/deep/ 和 >>> 选择器只能被用在仿真 (emulated)模式下。 这种方式是默认值,也是用得最多的方式。从外部为组件设定样式有几种方法可从外部为组件设定样式:最简单的方法是使用标记名称作为选择器,如下custom-container { color: red;}外部样式比在 Shadow DOM 中定义的样式具有更高的优先级。例如,如果用户编写选择器:custom-container { width: 500px;}它将覆盖组件的样式::host { width: 300px;}对组件本身进行样式化只能到此为止。但是如果人想要对组件的内部进行样式化,会发生什么情况呢?为此,我们需要 CSS 自定义属性。使用 CSS 自定义属性创建样式钩子如果组件的开发者通过 CSS 自定义属性提供样式钩子,则用户可调整内部样式。其思想类似于<slot>,但适用于样式。看看下面的例子:<!– main page –><style> custom-container { margin-bottom: 60px; - custom-container-bg: black; }</style><custom-container background>…</custom-container>在其 shadow DOM 内部::host([background]) { background: var( - custom-container-bg, #CECECE); border-radius: 10px; padding: 10px;}在本例中,该组件将使用 black 作为背景值,因为用户指定了该值,否则,背景颜色将采用默认值 #CECECE。作为组件的作者,是有责任让开发人员了解他们可以使用的 CSS 定制属性,将其视为组件的公共接口的一部分。在 JS 中使用 slotShadow DOM API 提供了使用 slot 和分布式节点的实用程序,这些实用程序在编写自定义元素时迟早派得上用场。slotchange 事件当 slot 的分布式节点发生变化时,slotchange 事件将触发。例如,如果用户从 light DOM 中添加/删除子元素。var slot = this.shadowRoot.querySelector(’#some_slot’);slot.addEventListener(‘slotchange’, function(e) { console.log(‘Light DOM change’);});要监视对 light DOM 的其他类型的更改,可以在元素的构造函数中使用 MutationObserver。以前讨论过 MutationObserver 的内部结构以及如何使用它。assignedNodes() 方法有时候,了解哪些元素与 slot 相关联非常有用。调用 slot.assignedNodes() 可查看 slot 正在渲染哪些元素。 {flatten: true} 选项将返回 slot 的备用内容(前提是没有分布任何节点)。让我们看看下面的例子:<slot name=’slot1’><p>Default content</p></slot>假设这是在一个名为 <my-container> 的组件中。看看这个组件的不同用法,以及调用 assignedNodes() 的结果是什么:在第一种情况下,我们将向 slot 中添加我们自己的内容:<my-container> <span slot=“slot1”> container text </span></my-container>调用 assignedNodes() 会得到 [<span slot= " slot1 " > container text </span>],注意,结果是一个节点数组。在第二种情况下,将内容置空:<my-container> </my-container>调用 assignedNodes() 的结果将返回一个空数组 []。在第三种情况下,调用 slot.assignedNodes({flatten: true}),得到结果是: [<p>默认内容</p>]。此外,要访问 slot 中的元素,可以调用 assignedNodes() 来查看元素分配给哪个组件 slot。事件模型值得注意的是,当发生在 Shadow DOM 中的事件冒泡时,会发生什么。当事件从 Shadow DOM 中触发时,其目标将会调整为维持 Shadow DOM 提供的封装。也就是说,事件的目标重新进行了设定,因此这些事件看起来像是来自组件,而不是来自 Shadow DOM 中的内部元素。下面是从 Shadow DOM 传播出去的事件列表(有些没有):聚焦事件:blur、focus、focusin、focusout鼠标事件:click、dblclick、mousedown、mouseenter、mousemove,等等滚轮事件:wheel输入事件:beforeinput、input键盘事件:keydown、keyup组合事件:compositionstart、compositionupdate、compositionend拖放事件:dragstart、drag、dragend、drop,等等自定义事件默认情况下,自定义事件不会传播到 Shadow DOM 之外。如果希望分派自定义事件并使其传播,则需要添加 bubbles: true 和 composed: true 选项。让我们看看派发这样的事件是什么样的:var container = this.shadowRoot.querySelector(’#container’);container.dispatchEvent(new Event(‘containerchanged’, {bubbles: true, composed: true}));浏览器支持如希望获得 shadow DOM 检测功能,请查看是否存在 attachShadow:const supportsShadowDOMV1 = !!HTMLElement.prototype.attachShadow;有史以来第一次,我们拥有了实施适当 CSS 作用域、DOM 作用域的 API 原语,并且有真正意义上的组合。 与自定义元素等其他网络组件 API 组合后,shadow DOM 提供了一种编写真正封装组件的方法,无需花多大的功夫或使用如 <iframe> 等陈旧的东西。你的点赞是我持续分享好东西的动力,欢迎点赞!一个笨笨的码农,我的世界只能终身学习!更多内容请关注公众号《大迁世界》! ...

January 27, 2019 · 4 min · jiezi

React组件卸载、路由跳转、页面关闭(刷新)之前进行提示

React组件卸载生命周期、路由跳转和页面关闭三者看起来有些类似的地方,比如都是当前组件即将从视口消失,但实际上所触发的事件均不相同。以一个实际案例出发:某单页应用的文章编辑页用户正在编辑文章,此时尚未保存。当用户不小心要跳转到另外一个路由时需要提醒用户是否继续跳转,这个过程需要触发路由跳转以及组件卸载;而用户不小心点了关闭标签页按钮,或刷新了页面。这个过程触发了页面卸载事件;在这个案例中我们需要实现:1. 用户跳转页面时弹出提示框(路由采用histroy模式)2. 用户关闭页面时弹出提示框componentWillUnmount首先这个钩子函数是在组件卸载前调用的一个函数,它并不能阻止当前组件的卸载。所以不要想方设法在这里做提示,因为即便提示了,组件还是会卸载,文章还是会消失。路由守卫-<Prompt/>为了实现第一个功能,需要一个跳转路由之前进行的判断。在react-router-dom 4.0 之后取消了先前的路由守卫(其实我没研究过之前版本的,这个描述摘自网络)。在react-router-dom 4.0之后,实现这个功能可以依靠<Prompt/>组件。文档链接↗把这个组件添加到你的文章编辑页组件的任意部分import {Prompt} from ‘react-router-dom’;const Editor=()=>{ return ( <div> <Prompt when={true} message={location => ‘文章要保存吼,确定离开吗?’} /> </div> )}这里有一点需要注意,使用<Prompt/>时,你的路由跳转必须通过<Link/>实现,而不能依靠<a/>原生标签。点击取消时就会留在当前页面。至此已经实现了路由跳转时提醒用户进行保存的功能。窗口关闭事件-beforeunload实现第二个功能需要依靠对窗口的监听。React应用中对于窗口事件的应用远没有DOM事件频繁,所以好久没碰到还是有点手生的。最关键的就是,应该在何时进行监听?应该在组件挂载时监听事件,组件卸载时移除事件监听。因为我已经开始全面采用hooks新特性了,所以这里使用到useEffect。import React,{useEffect} from ‘react’;const Editor=()=>{ //监听窗口事件 useEffect(() => { const listener = ev => { ev.preventDefault(); ev.returnValue=‘文章要保存吼,确定离开吗?’; }; window.addEventListener(‘beforeunload’, listener); return () => { window.removeEventListener(‘beforeunload’, listener) } }, []); //return …}这里有几个需要注意的地方:useEffect第二个参数为空数组,表示只调用了componentDidMount和componentWillUnmount两个钩子事件监听和移除的第二个参数为同一个事件处理函数在beforeunload事件中的confirm,prompt,alert会被忽略。取而代之的是一个浏览器内置的对话框。(参考:MDN|beforeunload)必须要有returnValue且为非空字符串,但是在某些浏览器中这个值并不会作为弹窗信息

January 16, 2019 · 1 min · jiezi

从一次重写原生方法遇到的坑,总结一下Web中的事件系统

写在前面前段时间,我写过一篇文章前端开发中的Error以及异常捕获。 在文章中,我提到了这个问题:经过不断探索(不想再喷自己了),我找到了原因。下面一一道来。本文主要讲解自己找问题原因的思路,如果想看结论和总结,请直接跳到文末。问题复现我是在自己以前的项目中测试addEventListener的重写的。这里直接上精简后的问题代码:import React from ‘react’;import ReactDOM from ‘react-dom’;const nativeAddEventListener = EventTarget.prototype.addEventListener;EventTarget.prototype.addEventListener = function (type, func, options) { const wrappedFunc = function (…args) { try { return func.apply(this, args); } catch (e) { const errorObj = { error_msg: e.message || ‘’, error_stack: e.stack || (e.error && e.error. error_native: e }; } } return self.nativeAddEventListener.call(this, type, wrappedFunc, options);};const App = function() { return <div>11111</div>};ReactDOM.render(<App/>, document.body);运行这段代码,浏览器上一片空白,但是却没有任何报错。我一脸懵逼。问题初探索删掉那一点重写addEventListener的代码后,表现符合预期了。应该是重写那儿的问题。但是仔细看了过后,那段代码并没有什么问题。并且这段代码我在其他地方也试过,表现一直是正常的。是不是和React哪里冲突了?我使用的React版本是我搜索了react-dom源码中的addEventListener关键字,总共出现了四次。初步看了一下,并没有什么问题,只是注册了一些事件而已。没有具体分析这些代码的含义,我选择了先更换React的版本试一试,于是,我换成了15.6.2的版本。令人吃惊的是,表现符合预期了。难道真的和React的版本有关系? 在我的认知中,两个版本中最大的不同就是:React v16采用了全新的Fiber架构,而我对Fiber的理解大概就是:重新设计了react node的数据结构,模拟实现了自己的任务堆栈,结合时间分片来进行任务的调度,从而更新整个系统。另外,React有自己的一套任务系统,addEventListener和任务也是紧密相关的,难道影响到了这个?继续探索我决定从ReactDOM.render()这个方法入手,调试一下ReactDOM的源代码。之前并没有研究过React的源码,压力有点大。调试了一翻之后,我并没有发现什么问题,并且已经有点懵逼了。我准备同时调试react v15和react v16的代码,看看有什么不同。为了方便,我将问题代码全部抽了出来,全部写到了一个html文件中,并且直接引用React的cdn地址。这个时候,我发现了一个神奇的问题:直接引用cdn地址后,不管React是什么版本,就算是v16版本,也不会出现之前问题,表现都是符合预期的。我更加懵逼了。发现问题静下心来仔细观察后,我发现了,我cdn引用的都是react的production版本,而我在项目中使用的react代码,却是development版本的,难道是development和production的diff代码,导致了上面的问题。于是我重新仔细看了一下v16的development的代码,找到了代码中一段长长的注释:大意就是:在开发版本中,react不会采用try{}catch(){}的方式来捕获错误,而是会把所有开发者定义的callback用一个叫做invokeGuardedCallback的函数包裹起来,然后使用一个假的dom,监听、触发自定义事件来执行invokeGuardedCallback,并且通过一个全局的错误捕捉函数来捕获错误。在这段注释的下面,就是注释中提到的invokeGuardedCallback的代码。我仔细研究了这个invokeGuardedCallback的代码,其核心就是:function invokeGuardedCallback(name, func, context, a, b, c, d, e, f){ … var fakeNode = document.createElement(‘react’); var evt = document.createEvent(‘Event’); var evtType = ‘react-’ + (name ? name : ‘invokeguardedcallback’); var callCallback = function(){ … fakeNode.removeEventListener(evtType, callCallback, false); // 这里很重要!!! … func.apply(context, funcArgs); // 这里是真正执行react中的逻辑代码 } fakeNode.addEventListener(evtType, callCallback, false); evt.initEvent(evtType, false, false); fakeNode.dispatchEvent(evt); …} react将所有容易出错的函数,都用这个invokeGuardedCallback包了起来。每一次都重新造一个虚拟的element,然后监听其自定义事件,并且立即触发这个自定义事件。调试了这个invokeGuardedCallback后,我发现在react v16中,发现很多函数被多次执行。为什么会多次执行呢? 终于,我找到了问题的原因:我重写了addEventListener, 在函数外包了一层try{}catch(){},返回的是一个新的函数,所以,最终注册在事件监听器上的,并不是我传入的那个函数。这个时候,调用removeEventListener时,无法移除我传入addEventListener的函数。在invokeGuardedCallback中,removeEventListener的逻辑相当于并没有生效。于是,在Fiber的调度中,某个函数被多次重复执行了,而被重复执行的函数并不是幂等的,问题便产生了。问题的总结与思考问题终于定位了,一句总结,就是:重写了addEventListener,却并没有考虑到与之对应的removeEventListener,导致removeEventListener无法正常工作。下面是一些思考:一开始,如果我仔细看一下react源码中addEventListener周围的代码,或许能更早发现这个问题,就不用绕这么大一个圈了。自己对于第三方库的development版本和production版本,并没有一个很强烈的认知、意识,以前上线的不少项目,线上竟然还是用的第三方库的development版本,这个毛病,一定得改掉。分析问题的能力还很欠缺,不够敏感。考虑问题的全面性需要提高。真的不要随便重写原生方法。。。写在后面在探索这个问题的过程中,我看到了react巧妙应用自定义事件来捕获错误。于是,我全面总结一下了Web中的事件系统,也算是对基础的巩固。由于篇幅已经不够了,这里就直接放文章链接吧:谈一谈web中的事件谈一谈web中的事件欢迎关注我的公众号: 符合预期的CoyPan,这里只有干货,符合你的预期。 ...

January 14, 2019 · 1 min · jiezi

小心,querySelector前方10米有坑

在写一个小组建的时候用到了document.querySelector,被小伙伴提醒说这个可能有坑,是啥呢?先来一篇MDN的文档解解馋:戳我戳我戳我>>>>>>>NodeList翻译一下主要部分:对于现代浏览器来说,虽然NodeList不是Array,但是它是可枚举的,因而它可以直接使用forEach等方法;对于一些老版本的浏览器,可以使用Array.from或者Array.prototype.forEach来转换NodeList为数组,继而使用forEach等方法;在有些情况下,NodeList是live(实时变化的),但有些时候不是。比如,使用document.getElementById,获取该元素的childNodes,那么这个集合是live的。但是,用document.querySelectorAll()获取到的集合却不是live的。这个live具体指什么呢?意思是,如果你事先获取到一个集合,保存在一个变量A上。在对这个集合进行增删改等操作,如果变量A能够实时反应你的增删改操作,说明集合是live的,反之则不是。这个「坑」就在于,文档也没有能够说明清楚,在用querySelector后,具体什么时候NodeList是live,什么时候NodeList不是live。于是小伙们做起了实验,大致发现,如果对节点进行删除,那么是live;如果新增节点则不是live。朋友们也可以自己做做实验。如果有新发现欢迎评论留言~另一个值得注意的是:关于HTMLCollection和NodeList。从MDN的文档上笼统来说,所有集合都可以叫做NodeList,不过需要注意如下:

January 7, 2019 · 1 min · jiezi

Js中DOM事件绑定分析

在这篇文章中小编给大家整理了关于JS中DOM事件绑定的相关知识点,写的十分的全面细致,具有一定的参考价值,对此有需要的朋友可以参考学习下。如有不足之处,欢迎批评指正。Js事件绑定JavaScript 有三种事件模型:内联模型脚本模型DOM2 模型1、内联模型//基本废除不用<input type=“button” value=“按钮” onclick=“alert(‘Lee’);” /><input type=“button” value=“按钮” onclick=“box();” />2、脚本模型//基本不用var input = document.getElementsByTagName(‘input’)[0]; //得到 input 对象 input.onclick = function () { //匿名函数执行 alert(‘Lee’);};事件处理函数 影响的元素 何时发生onabort 图像 当图像加载被中断时onblur 窗口、框架、所有表单对象 当焦点从对象上移开时onchange 输入框,选择框和文本区域 当改变一个元素的值且失去焦点时onclick 链接、按钮、表单对象、图像映射区域 当用户单击对象时ondblclick 链接、按钮、表单对象 当用户双击对象时ondragdrop 窗口 当用户将一个对象拖放到浏览器窗口时onError 脚本 当脚本中发生语法错误时onfocus 窗口、框架、所有表单对象 当单击鼠标或者将鼠标移动聚焦到窗口或框架时onkeydown 文档、图像、链接、表单 当按键被按下时onkeypress 文档、图像、链接、表单 当按键被按下然后松开时onkeyup 文档、图像、链接、表单 当按键被松开时onload 主题、框架集、图像 文档或图像加载后onunload 主体、框架集 文档或框架集卸载后onmouseout 链接 当图标移除链接时onmouseover 链接 当鼠标移到链接时onmove 窗口 当浏览器窗口移动时onreset 表单复位按钮 单击表单的 reset 按钮onresize 窗口 当选择一个表单对象时onselect 表单元素 当选择一个表单对象时onsubmit 表单 当发送表格到服务器时欢迎加入前端全栈开发交流圈一起学习交流:8643058603、内联模型“DOM2 级事件”定义了两个方法,用于添加事件和删除事件处理程序的操作:addEventListener()和 removeEventListener()。所有 DOM 节点中都包含这两个方法,并且它们都接受 3 个参数;事件名、函数、冒泡或捕获的布尔值(true 表示捕获,false 表示冒泡)window.addEventListener(’load’, function () { alert(‘Lee’);}, false);window.removeEventListener(’load’, function () { alert(‘Mr.Lee’);}, false)//欢迎加入前端全栈开发交流圈一起学习交流:864305860PS: IE 实现了与 DOM 中类似的两个方法:attachEvent()和 detachEvent()。这两个方法接受相同的参数:事件名称和函数。在使用这两组函数的时候,先把区别说一下:IE 不支持捕获,只支持冒泡;IE 添加事件不能屏蔽重复的函数;IE 中的 this 指向的是 window 而不是 DOM 对象。在传统事件上,IE 是无法接受到 event 对象的,但使用了 attchEvent()却可以,但有些区别。Javascriptwindow.attachEvent(’load’, function () { alert(‘Lee’);}, false);window.detachEvent(’load’, function () { alert(‘Mr.Lee’);}, false)//欢迎加入前端全栈开发交流圈一起学习交流:864305860PS:IE 中的事件绑定函数 attachEvent()和 detachEvent()可能在实践中不去使用,有几个原因:1.IE9 就将全面支持 W3C 中的事件绑定函数;2.IE 的事件绑定函数无法传递 this;3.IE的事件绑定函数不支持捕获;4.同一个函数注册绑定后,没有屏蔽掉;5.有内存泄漏的问题结语感谢您的观看,如有不足之处,欢迎批评指正。 ...

December 19, 2018 · 1 min · jiezi

DOM事件机制

前言本文主要介绍DOM事件级别、DOM事件模型、事件流、事件代理和Event对象常见的应用,希望对你们有些帮助和启发!本文首发地址为GitHub博客,写文章不易,请多多支持与关注!一、DOM事件级别DOM级别一共可以分为四个级别:DOM0级、DOM1级、DOM2级和DOM3级。而DOM事件分为3个级别:DOM 0级事件处理,DOM 2级事件处理和DOM 3级事件处理。由于DOM 1级中没有事件的相关内容,所以没有DOM 1级事件。1.DOM 0级事件el.onclick=function(){}// 例1var btn = document.getElementById(‘btn’); btn.onclick = function(){ alert(this.innerHTML); }当希望为同一个元素/标签绑定多个同类型事件的时候(如给上面的这个btn元素绑定3个点击事件),是不被允许的。DOM0事件绑定,给元素的事件行为绑定方法,这些方法都是在当前元素事件行为的冒泡阶段(或者目标阶段)执行的。2.DOM 2级事件el.addEventListener(event-name, callback, useCapture)event-name: 事件名称,可以是标准的DOM事件callback: 回调函数,当事件触发时,函数会被注入一个参数为当前的事件对象 eventuseCapture: 默认是false,代表事件句柄在冒泡阶段执行// 例2var btn = document.getElementById(‘btn’);btn.addEventListener(“click”, test, false);function test(e){ e = e || window.event; alert((e.target || e.srcElement).innerHTML); btn.removeEventListener(“click”, test)}//IE9-:attachEvent()与detachEvent()。//IE9+/chrom/FF:addEventListener()和removeEventListener()IE9以下的IE浏览器不支持 addEventListener()和removeEventListener(),使用 attachEvent()与detachEvent() 代替,因为IE9以下是不支持事件捕获的,所以也没有第三个参数,第一个事件名称前要加on。3.DOM 3级事件在DOM 2级事件的基础上添加了更多的事件类型。UI事件,当用户与页面上的元素交互时触发,如:load、scroll焦点事件,当元素获得或失去焦点时触发,如:blur、focus鼠标事件,当用户通过鼠标在页面执行操作时触发如:dblclick、mouseup滚轮事件,当使用鼠标滚轮或类似设备时触发,如:mousewheel文本事件,当在文档中输入文本时触发,如:textInput键盘事件,当用户通过键盘在页面上执行操作时触发,如:keydown、keypress合成事件,当为IME(输入法编辑器)输入字符时触发,如:compositionstart变动事件,当底层DOM结构发生变化时触发,如:DOMsubtreeModified同时DOM3级事件也允许使用者自定义一些事件。二、DOM事件模型和事件流DOM事件模型分为捕获和冒泡。一个事件发生后,会在子元素和父元素之间传播(propagation)。这种传播分成三个阶段。(1)捕获阶段:事件从window对象自上而下向目标节点传播的阶段;(2)目标阶段:真正的目标节点正在处理事件的阶段;(3)冒泡阶段:事件从目标节点自下而上向window对象传播的阶段。DOM事件捕获的具体流程捕获是从上到下,事件先从window对象,然后再到document(对象),然后是html标签(通过document.documentElement获取html标签),然后是body标签(通过document.body获取body标签),然后按照普通的html结构一层一层往下传,最后到达目标元素。而事件冒泡的流程刚好是事件捕获的逆过程。接下来我们看个事件冒泡的例子:// 例3<div id=“outer”> <div id=“inner”></div></div>……window.onclick = function() { console.log(‘window’);};document.onclick = function() { console.log(‘document’);};document.documentElement.onclick = function() { console.log(‘html’);};document.body.onclick = function() { console.log(‘body’);}outer.onclick = function(ev) { console.log(‘outer’);};inner.onclick = function(ev) { console.log(‘inner’);};正如我们上面提到的onclick给元素的事件行为绑定方法都是在当前元素事件行为的冒泡阶段(或者目标阶段)执行的。三、事件代理(事件委托)由于事件会在冒泡阶段向上传播到父节点,因此可以把子节点的监听函数定义在父节点上,由父节点的监听函数统一处理多个子元素的事件。这种方法叫做事件的代理(delegation)。1.优点减少内存消耗,提高性能假设有一个列表,列表之中有大量的列表项,我们需要在点击每个列表项的时候响应一个事件// 例4<ul id=“list”> <li>item 1</li> <li>item 2</li> <li>item 3</li> …… <li>item n</li></ul>如果给每个列表项一一都绑定一个函数,那对于内存消耗是非常大的,效率上需要消耗很多性能。借助事件代理,我们只需要给父容器ul绑定方法即可,这样不管点击的是哪一个后代元素,都会根据冒泡传播的传递机制,把容器的click行为触发,然后把对应的方法执行,根据事件源,我们可以知道点击的是谁,从而完成不同的事。动态绑定事件在很多时候,我们需要通过用户操作动态的增删列表项元素,如果一开始给每个子元素绑定事件,那么在列表发生变化时,就需要重新给新增的元素绑定事件,给即将删去的元素解绑事件,如果用事件代理就会省去很多这样麻烦。2.如何实现接下来我们来实现上例中父层元素 #list 下的 li 元素的事件委托到它的父层元素上:// 给父层元素绑定事件document.getElementById(’list’).addEventListener(‘click’, function (e) { // 兼容性处理 var event = e || window.event; var target = event.target || event.srcElement; // 判断是否匹配目标元素 if (target.nodeName.toLocaleLowerCase === ’li’) { console.log(’the content is: ‘, target.innerHTML); }});四、Event对象常见的应用event. preventDefault()如果调用这个方法,默认事件行为将不再触发。什么是默认事件呢?例如表单一点击提交按钮(submit)跳转页面、a标签默认页面跳转或是瞄点定位等。很多时候我们使用a标签仅仅是想当做一个普通的按钮,点击实现一个功能,不想页面跳转,也不想瞄点定位。//方法一:<a href=“javascript:;">链接</a>也可以通过JS方法来阻止,给其click事件绑定方法,当我们点击A标签的时候,先触发click事件,其次才会执行自己的默认行为//方法二:<a id=“test” href=“http://www.cnblogs.com”>链接</a><script>test.onclick = function(e){ e = e || window.event; return false;}</script>//方法三:<a id=“test” href=“http://www.cnblogs.com”>链接</a><script>test.onclick = function(e){ e = e || window.event; e.preventDefault();}</script>接下来我们看个例子:输入框最多只能输入六个字符,如何实现?// 例5 <input type=“text” id=‘tempInp’> <script> tempInp.onkeydown = function(ev) { ev = ev || window.event; let val = this.value.trim() //trim去除字符串首位空格(不兼容) // this.value=this.value.replace(/^ +| +$/g,’’) 兼容写法 let len = val.length if (len >= 6) { this.value = val.substr(0, 6); //阻止默认行为去除特殊按键(DELETE\BACK-SPACE\方向键…) let code = ev.which || ev.keyCode; if (!/^(46|8|37|38|39|40)$/.test(code)) { ev.preventDefault() } } } </script>event.stopPropagation() & event.stopImmediatePropagation()event.stopPropagation() 方法阻止事件冒泡到父元素,阻止任何父事件处理程序被执行。上面提到事件冒泡阶段是指事件从目标节点自下而上向window对象传播的阶段。我们在例4的inner元素click事件上,添加event.stopPropagation()这句话后,就阻止了父事件的执行,最后只打印了’inner’。 inner.onclick = function(ev) { console.log(‘inner’); ev.stopPropagation();};stopImmediatePropagation 既能阻止事件向父元素冒泡,也能阻止元素同事件类型的其它监听器被触发。而 stopPropagation 只能实现前者的效果。我们来看个例子:<body> <button id=“btn”>click me to stop propagation</button></body>……const btn = document.querySelector(’#btn’);btn.addEventListener(‘click’, event => { console.log(‘btn click 1’); event.stopImmediatePropagation();});btn.addEventListener(‘click’, event => { console.log(‘btn click 2’);});document.body.addEventListener(‘click’, () => { console.log(‘body click’);});// btn click 1如上所示,使用 stopImmediatePropagation后,点击按钮时,不仅body绑定事件不会触发,与此同时按钮的另一个点击事件也不触发。event.target & event.currentTarget老实说这两者的区别,并不好用文字描述,我们先来看个例子: <div id=“a”> <div id=“b”> <div id=“c”> <div id=“d”></div> </div> </div> </div> <script> document.getElementById(‘a’).addEventListener(‘click’, function(e) { console.log(’target:’ + e.target.id + ‘&currentTarget:’ + e.currentTarget.id); }); document.getElementById(‘b’).addEventListener(‘click’, function(e) { console.log(’target:’ + e.target.id + ‘&currentTarget:’ + e.currentTarget.id); }); document.getElementById(‘c’).addEventListener(‘click’, function(e) { console.log(’target:’ + e.target.id + ‘&currentTarget:’ + e.currentTarget.id); }); document.getElementById(’d’).addEventListener(‘click’, function(e) { console.log(’target:’ + e.target.id + ‘&currentTarget:’ + e.currentTarget.id); }); </script>当我们点击最里层的元素d的时候,会依次输出:target:d&currentTarget:dtarget:d&currentTarget:ctarget:d&currentTarget:btarget:d&currentTarget:a从输出中我们可以看到,event.target指向引起触发事件的元素,而event.currentTarget则是事件绑定的元素,只有被点击的那个目标元素的event.target才会等于event.currentTarget。也就是说,event.currentTarget始终是监听事件者,而event.target是事件的真正发出者。参考文章DOM级别与DOM事件DOM事件机制解惑事件模型JavaScript 事件委托详解JavaScript 事件的学与记:stopPropagation 和 stopImmediatePropagationevent.target和event.currentTarget的区别 ...

December 5, 2018 · 2 min · jiezi

究竟什么是DOM?

文档对象模型或“DOM”是网页的接口。 它本质上是页面的API,允许程序读取和操作页面的内容,结构和样式。网页是如何构建的?浏览器如何从源HTML文档转到在视口中显示样式化和交互式页面称为“关键渲染路径”。 虽然这个过程可以分解为几个步骤,正如我在“理解关键渲染路径”一文中所述,这些步骤大致可分为两个阶段。 第一阶段涉及浏览器解析文档以确定最终将在页面上呈现的内容,第二阶段涉及浏览器执行呈现。第一阶段的结果是所谓的“渲染树”。 渲染树是将在页面上呈现的HTML元素及其相关样式的表示。 为了构建这个树,浏览器需要两件事:CSSOM,与元素相关的样式的表示DOM,元素的表示如何创建DOM(以及它看起来像什么)?DOM是源HTML文档的基于对象的表示。 它有一些差异,我们将在下面看到,但它本质上是一种尝试将HTML文档的结构和内容转换为可供各种程序使用的对象模型。DOM的对象结构由所谓的“节点树”表示。 它之所以被称为是因为它可以被认为是具有单个父茎的树,其分枝成几个子枝,每个子枝可以具有叶子。 在这种情况下,父“stem”是根<html>元素,子“branches”是嵌套元素,“leaves”是元素中的内容。我们以此HTML文档为例:<!doctype html><html lang=“en”> <head> <title>My first web page</title> </head> <body> <h1>Hello, world!</h1> <p>How are you?</p> </body></html>此文档可以表示为以下节点树:DOM不是什么?在上面给出的示例中,看起来DOM是源HTML文档的一对一映射或您看到的DevTools的映射。 但是,正如我所提到的,存在差异。 为了完全理解DOM是什么,我们需要看看它不是什么。DOM不是您的源HTML尽管DOM是从源HTML文档创建的,但它并不总是完全相同。 有两个实例,DOM可以与源HTML不同。当HTML无效时DOM是有效HTML文档的接口。 在创建DOM的过程中,浏览器可以纠正HTML代码中的一些无效。我们以此HTML文档为例:<!doctype html><html>Hello, world!</html>该文档缺少<head>和<body>元素,这是有效HTML的要求。 如果我们查看生成的DOM树,我们将看到这已得到纠正:当Javascript修改DOM时除了作为查看HTML文档内容的界面之外,还可以修改DOM,使其成为活动的资源。例如,我们可以使用Javascript为DOM创建其他节点。var newParagraph = document.createElement(“p”);var paragraphContent = document.createTextNode(“I’m new!”);newParagraph.appendChild(paragraphContent);document.body.appendChild(newParagraph);这将更新DOM,但当然不是我们的HTML文档。DOM不是您在浏览器中看到的(即渲染树)您在浏览器视口中看到的是渲染树,正如我所提到的,它是DOM和CSSOM的组合。 真正将DOM与渲染树分开的是,后者只包含最终将在屏幕上绘制的内容。因为渲染树仅关注渲染的内容,所以它会排除视觉上隐藏的元素。 例如,具有display:none的样式。<!doctype html><html lang=“en”> <head></head> <body> <h1>Hello, world!</h1> <p style=“display: none;">How are you?</p> </body></html>DOM将包含<p>元素:但是,渲染树以及因此在视口中看到的内容将不包含该元素。DOM不是DevTools中的东西这种差异有点小,因为DevTools元素检查器提供了我们在浏览器中最接近的DOM。 但是,DevTools检查器包含不在DOM中的其他信息。最好的例子是CSS伪元素。 使用::before和::after选择器创建的伪元素构成CSSOM和渲染树的一部分,但在技术上不是DOM的一部分。 这是因为DOM仅由源HTML文档构建,不包括应用于元素的样式。尽管伪元素不是DOM的一部分,但它们仍在我们的devtools元素检查器中。这就是为什么伪元素不能被Javascript作为目标的原因,因为它们不是DOM的一部分。概括DOM是HTML文档的接口。 它被浏览器用作确定在视口中呈现内容的第一步,并通过Javascript程序来修改页面的内容,结构或样式。虽然与其他形式的源HTML文档类似,但DOM在许多方面有所不同:它总是有效的HTML它是一个可以通过Javascript修改的活模型它不包含伪元素(例如::after)它确实包含隐藏元素(例如display: none)

December 4, 2018 · 1 min · jiezi

DOM知识整理

由于工作中一直在用框架来解决问题,在平时对dom的关注也比较少(特别像angular这种自己封装了一层视图层的框架,并不建议直接操作DOM),所以dom相关的知识也忘的差不多了,这次做公司产品的官网,没有太多的交互和功能,直接用了原生js,正好借此整理一下遗忘的dom的知识 DOM中定义的一些常用接口:EventTarget事件接口,规定了事件的属性和方法,大多数接口都继承于此接口。Window继承接口:EventTarget/AbstractViewwindow对象实现了这个接口,表示一个包含DOM文档的窗口。Node继承接口:EventTarget节点接口,规定了节点的属性和方法。Document继承接口:Nodedocument对象不仅实现了Document接口,也实现了HTMLElement接口,用来标识当前窗口内的文档节点。Element继承接口:Node描述了所有相同种类的元素所普遍具有的方法和属性。HTMLElement继承接口:Element/GlobalEventHandlers所有html元素都直接或间接实现了HTMLElement接口。由于要实现一些滚动偏移相关的功能,所以整理了一些相关的属性:Window接口相关属性screenX/screenY 浏览器窗口距屏幕左侧/顶部的距离outerHeight/outerWidth 浏览器窗口的高/宽innerHeight/innerWidth 页面内容区域的高/宽scrollX/scrollY 文档已水平/垂直滚动的像素数pageXOffset/pageYOffset scrollX/scrollY的别称(浏览器兼容性更好些)Window接口相关方法Window.scroll(X,Y) 滚动窗口到文档中的指定位置(指定一个绝对位置)Window.scrollTo() 同scroll(),参数可以是横纵坐标,也可以是一个对象{top: y-coord,left: x-coord,behavior:‘auto’}//smooth(平滑滚动),instant(瞬间滚动)默认aotu=instantwindow.scrollBy(X,Y) 滚动指定的距离(相对位置)ps:window.scrollBy(0, window.innerHeight)// 滚动一页Window.scrollByLines() 按给定的行数滚动文档window.scrollByPages() 在当前文档页面按照指定的页数翻页HTMLElement接口相关属性offsetHeight 元素自身可视高度加上上下border的宽度offsetWidth 元素自身可视宽度加上左右border的宽度offsetParent 元素的父元素,如果没有就是body元素offsetTop 元素自己border顶部距离父元素顶部或者body元素border顶部的距离offsetLeft 元素自己border左边距离父元素border左边或者body元素border左边的距离

December 4, 2018 · 1 min · jiezi

一小波DOM骚操作:querySelectorAll和classList

虽然现在MVVM框架带来了诸多便利,但你真的就不再需要操作DOM了吗?本文通过几个小例子来介绍一些DOM操作的小技巧场景一:querySelectorAll陆小鸡最近遇到了这样一个问题,他引用了一个第三方的表格组件,他引入组件的代码如下:<jj-table id=“mytable”></jj-table>组件渲染后的结构大致如下: <div class=“table-wrapper” id=“mytable”> <div class=“table-header”> <table></table> </div> <div class=“table-body”> <table> <tbody class=“table-tbody”></tbody> </table> </div> </div>为了获取tbody这个dom节点,他写下了如下代码:var el = document.getElementsByClassName(’table-tbody’)[0]console.log(el)控制台打印一看,发现有点不对啊,原来这个页面中还引入了一个表格,这种方式得到的是页面中的第一个表格,并不是这个表格。所以,他改进了下代码:var mytable = document.getElementById(‘mytable’)var el = mytable.getElementsByClassName(’table-tbody’)[0]这下终于正确了。但是,善于思考的小鸡同学又想了想,如果层级更复杂,那写起来不是很麻烦吗?能不能像css选择器一样选择DOM节点呢?最终,他写下了如下代码:var el = document.querySelectorAll(’#mytable tbody’)[0]有人可能会说,jQuery不就是通过CSS选择符查询DOM文档取得元素的引用吗?没错!但通过querySelectorAll方法,原生也可以做到。注意:还有一个类似的方法querySelector(),其接收的参数与 querySelectorAll()方法一样,都是一个 CSS 选择符,但返回的是一个元素而不是所有匹配的元素(一个 NodeList 的实例)。场景二:classList张大鹏最近遇到了这样一个问题,需要找到表格中各行的序列号,将其存入ids数组中,其中的序列号已经写入到每行的class类名中,如下: <table> <tbody class=“table-tbody”> <tr class=“table-row 1”> <td>td1</td> </tr> <tr class=“table-row 4”> <td>td4</td> </tr> <tr class=“table-row 2”> <td>td2</td> </tr> <tr class=“table-row 3”> <td>td3</td> </tr> </tbody> </table>他略加思索,写出了如下代码:var el = document.querySelectorAll(’.table-tbody’)[0]var rows = el.rowsvar ids = []for (var i = 0; i < rows.length; i++) { let classNames = rows[i].className ids.push(classNames.split(’ ‘)[1])}console.log(ids)看上去是不错啊,但是感觉获取类名有点麻烦,并且还得进行一次类型转换才能取到序列号,能不能一步到位呢?通过查阅,他写出了如下代码:var el = document.querySelectorAll(’.table-tbody’)[0]var rows = el.rowsvar ids = []for (var i = 0; i < rows.length; i++) { ids.push(rows[i].classList[1])}console.log(ids)HTML5 新增了一种操作类名的方式,可以让操作更简单也更安全,那就是为所有元素添加classList 属性。这个新类型还定义如下方法:add(value):将给定的字符串值添加到列表中。如果值已经存在,就不添加了。contains(value):表示列表中是否存在给定的值,如果存在则返回 true,否则返回 false。remove(value):从列表中删除给定的字符串。toggle(value):如果列表中已经存在给定的值,删除它;如果列表中没有给定的值,添加它。 ...

December 1, 2018 · 1 min · jiezi

虚拟DOM

虚拟DOM可以看看这个文章如何理解虚拟DOM? - 戴嘉华的回答 - 知乎https://www.zhihu.com/questio…深度剖析:如何实现一个 Virtual DOM 算法 #13是什么什么是DOM?DOM 是 JavaScript 操作网页的接口,全称为“文档对象模型”(Document Object Model)。它的作用是将网页转为一个 JavaScript 对象,从而可以用脚本进行各种操作(比如增删内容)。DOM就是将网页转化为一个对象并提供操作这个对象接口(即操作这个对象的方法),所以可以通过DOM对网页中的元素进行操作。如对某个节点增加属性,增加孩子,删除等。DOM就是网页里你看得见的对应的某个元素。什么是虚拟DOM?举例说明:如果网页中有一个表格,表头是姓名,年级,分数。如果我希望点击姓名表格就按照字典序排序,点击年级,按照年级从大到小排序等等操作,那么如果直接去操作DOM的话就很难实现。例如,我们删除了一个DOM结点,或者新增了一条数据,那么重新进行排序,就会删除所有DOM然后重新渲染一遍DOM。如果数据很多的话,就会很浪费资源,影响网页的性能,可能会卡顿。为什么会卡顿呢?是因为一个节点元素实际上包含很多属性方法,创建一个DOM就包含上百条数据,加载上绑定的事件等。性能开销很大。我可以根据DOM结构,然后自己创建一个数据结构,自己创建的这个DOM和真实的DOM 是一一映射的。然后我们操作的时候就操作自己的数据结构,数据量很小,不管进行排序或其他处理都会很迅速。处理好之后,再根据这个数据结构把它变为真实的DOM。即我们用虚拟的DOM结构替换需要处理的DOM结构,对虚拟的DOM 进行操作之后再进行渲染,就成为了真实的数据。有什么用这样的好处是如果我们需要对DOM结点进行改变,那么我们只需要查看我们自己创建的虚拟DOM,看看其中哪条数据发生了改变,然后修改虚拟DOM,并把它渲染成真实的数据即可。例如我们本来就有500条数据,然后需要添加10条,那么我们只添加10条新的虚拟DOM,然后再把这10条虚拟DOM转化为真实的DOM即可,不需要从新吧510跳全部重新渲染一遍。这样性能会提升。所谓的虚拟DOM实际上就是我们根据真实的DOM结构,创建一个和真实DOM映射的一个数据结构,然后对数据结构进行操作,最后把这个数据结构反映到真实的DOM中。我们可以在逻辑上把这个数据结构渲染成真实的DOM,他在数据结构上和真实DOM是差不多的举个例子:我们可以使用一个数据结构来映射DOM(用JS对象模拟DOM树):我们将节点用一个对象来表示,tag属性表示他的种类,children属性表示他拥有的儿子数组。那么:这就是虚拟的DOM,体积很轻量,没有所有的属性和接口!用来操作的时候不需要耗费很高的性能。代码如下:<!DOCTYPE html><html><head> <meta charset=“utf-8”> <title>JS Bin</title></head><body><!– <div> <p><span>xiedaimala.com</span></p> <span>jirengu.coms</span> </div> –></body></html>let nodesData = { tag: ‘div’, children: [ { tag: ‘p’, children: [ { tag: ‘span’, children: [ { tag: ‘#text’, text: ‘xiedaimala.com’ } ] } ] }, { tag: ‘span’, children: [ { tag: ‘#text’, text: ‘jirengu.com’ } ] } ]}接下来我们只需要将这个虚拟的DOM渲染成真实的DOM就可以了,例如写一个函数来渲染DOM。function createElement (data){ }举例说明虚拟DOM的作用:这时我们修改了DOM,例如我们将div中的p标签中span标签的内容由xiedaimala.com修改为baidu.com,那么我们只需要修改我们创建的数据结构中的span标签text那个属性,然后将原来内存中的nodesData与修改后的nodesData2进行比较。例如:let nodesData2 = { tag: ‘div’, children: [ { tag: ‘p’, children: [ { tag: ‘span’, children: [ { tag: ‘#text’, text: ‘baidu.com’//这里变了 } ] } ] }, { tag: ‘span’, children: [ { tag: ‘#text’, text: ‘jirengu.com’ } ] } ]}发现span标签的text内容改变了,那么我们在修改真实DOM的时候不需要把所有的真实DOM的很多属性和方法都检索一遍,然后重新渲染一遍,而只需要重新渲染在虚拟DOM中比较出来的修改的部分,即只需要重新渲染text部分就可以了。以下为深度剖析:如何实现一个 Virtual DOM 算法 #13文章中的一段解释既然原来 DOM 树的信息都可以用 JavaScript 对象来表示,反过来,你就可以根据这个用 JavaScript 对象表示的树结构来构建一棵真正的DOM树。之前的章节所说的,状态变更->重新渲染整个视图的方式可以稍微修改一下:用 JavaScript 对象表示 DOM 信息和结构,当状态变更的时候,重新渲染这个 JavaScript 的对象结构。当然这样做其实没什么卵用,因为真正的页面其实没有改变。但是可以用新渲染的对象树去和旧的树进行对比,记录这两棵树差异。记录下来的不同就是我们需要对页面真正的 DOM 操作,然后把它们应用在真正的 DOM 树上,页面就变更了。这样就可以做到:视图的结构确实是整个全新渲染了,但是最后操作DOM的时候确实只变更有不同的地方。如何实现简单实现:虚拟DOM渲染为真实DOM/** * @author ruoyu * @description 虚拟 DOM Demo * @todo 暂时不考虑复杂情况 */class VNode { constructor(tag, children, text) { this.tag = tag this.text = text this.children = children } render() { if(this.tag === ‘#text’) { return document.createTextNode(this.text) } let el = document.createElement(this.tag) this.children.forEach(vChild => { el.appendChild(vChild.render()) }) return el }}/以上为ES6写法,改为ES5写法为://*******function VNode() { this.tag = tag this.text = text this.children = children}VNode.prototype.render = function() { if(this.tag === ‘#text’) { return document.createTextNode(this.text) } let el = document.createElement(this.tag) this.children.forEach(vChild => { el.appendChild(vChild.render())//递归生成子节点 }) return el} ****这几句代码的作用是将js对象表示的虚拟DOM渲染为真实的DOM//这个函数的作用是传入几个参数,然后返回对象/function v(tag, children, text) { if(typeof children === ‘string’) { text = children children = [] } return new VNode(tag, children, text)}/ 这里是js对象虚拟dom的数据结构let nodesData = { tag: ‘div’, children: [ { tag: ‘p’, children: [ { tag: ‘span’, children: [ { tag: ‘#text’, text: ‘xiedaimala.com’ } ] } ] }, { tag: ‘span’, children: [ { tag: ‘#text’, text: ‘jirengu.com’ } ] } ]} *//使用v函数将几个参数转化为对象并返回/let vNodes = v(‘div’, [ v(‘p’, [ v(‘span’, [ v(’#text’, ‘xiedaimala.com’) ] ) ] ), v(‘span’, [ v(’#text’, ‘jirengu.com’) ]) ] )/渲染为真实的DOM/console.log(vNodes) /下方有打印的结果/console.log(vNodes.render())我们看一下打印的结果DOM数据更新以下仅为简单实现,是为了理解原理,实际上要想做到很完美的虚拟DOM,需要考虑很多function patchElement(parent, newVNode, oldVNode, index = 0) { if(!oldVNode) {//如果没有,直接创建新的DOM,例如patchElement(root, vNodes1) parent.appendChild(newVNode.render()) } else if(!newVNode) {//删除DOM的操作,例如patchElement(root) parent.removeChild(parent.childNodes[index]) } else if(newVNode.tag !== oldVNode.tag || newVNode.text !== oldVNode.text)//替换(修改)DOM操作,例如两个VNode比较简单,然后互相比较 { parent.replaceChild(newVNode.render(), parent.childNodes[index]) } else {//递归替换孩子DOM,递归比较 for(let i = 0; i < newVNode.children.length || i < oldVNode.children.length; i++) { patchElement(parent.childNodes[index], newVNode.children[i], oldVNode.children[i], i) } }}let vNodes1 = v(‘div’, [ v(‘p’, [ v(‘span’, [ v(’#text’, ‘xiedaimala.com’) ] ) ] ), v(‘span’, [ v(’#text’, ‘jirengu.com’) ]) ] )let vNodes2 = v(‘div’, [ v(‘p’, [ v(‘span’, [ v(’#text’, ‘xiedaimala.com’) ] ) ] ), v(‘span’, [ v(’#text’, ‘jirengu.coms’), v(’#text’, ‘ruoyu’) ]) ] )const root = document.querySelector(’#root’)patchElement(root, vNodes1)//创建新的DOM,patchElement(root)//删除DOM的操作patchElement(root, vNodes2,vNodes1)//替换(修改)DOM操作以上只是简单实现!有很多bug总结问:说说虚拟DOM:当我们修改真正的DOM树的时候,因为DOM中元素节点有许多的属性和方法,当DOM中节点过多时往往需要消耗很大的性能。解决方法是:使用js对象来表示DOM树的信息和结构,这个js对象可以构建一个真正的DOM树。当状态变更的时候用修改后的新渲染的的js对象和旧的虚拟DOM js对象作对比,记录着两棵树的差异。把差别反映到真实的DOM 结构上最后操作真正的DOM的时候只操作有差异的部分就可以了 ...

September 29, 2018 · 2 min · jiezi