参考文档: jsdom操作MDN文档
目的: 为了查找方便而编辑整理的API文档,可以直接复制代码使用.
测试: 编辑文档时,做了基本的测试,结果符合文档描述.但是没有做复杂情况下的测试.测试浏览器是谷歌 79.0.3945.88 版本以上.
包装: js的api名字很多比较长,写起来比较啰嗦.用比较短的函数名包装一遍,可以明显减少打字量.也可以减少源码长度.
生成一个DOM元素,参数是html元素的名字.下面生成一个DIV.
let divdom = document.createElement('div');
代码有些长,可以封装成一个方法如下
function newDom(name) { return document.createElement(name); }
方法将一个节点添加到指定父节点的子节点列表末尾.
var child = node.appendChild(child);
appendChild 方法会把要插入的这个节点引用作为返回值返回.
如果被插入的节点已经存在于当前文档的文档树中,则那个节点会首先从原先的位置移除,然后再插入到新的位置
如果你需要保留这个子节点在原先位置的显示,则你需要先用Node.cloneNode方法复制出一个节点的副本,然后在插入到新位置
这个方法只能将某个子节点插入到同一个文档的其他位置,如果你想跨文档插入,你需要先调用document.importNode方法
void ParentNode.append((Node or DOMString)... nodes);
一组要插入的 Node 或 DOMString 对象
与 Node.appendChild() 的差异:
删除元素.父元素删除子元素.例如一个div元素,要删除自己的子元素p,那么如下
div.removeChild(p)
如果有p对象,没有父级div对象,要删除p,可以如下
p.parentNode.removeChild(p);
插入元素.例如一个p元素,要将自己插入到另一个div元素的"前"~"后"~"内前"~"内后",那么如下
div.insertAdjacentElement('beforebegin',p); // p在div的前面
div.insertAdjacentElement('afterbegin',p); // p在div内部的第一个子节点之前
div.insertAdjacentElement('beforeend',p); // p在div内部的最后一个子节点之后
div.insertAdjacentElement('afterend',p); // p在div的后面
注意: 当节点处于DOM树中而且有一个父元素的时候 beforebegin 和 afterend操作才能起作用
这句话的意思是,p如果要插入到div的前面或者后面,前提是div必须要有一个父元素.
如果是新生成的div,没有父级,但是要用beforebegin 和 afterend操作怎么办?可以建立文档片段DocumentFragment,将div放入其中就可以用了.
返回一个布尔值,指示该元素是否包含有指定的属性
let result = element.hasAttribute(attName);
result 为返回的布尔值true 或 false
attName 是一个字符串,表示属性的名称
返回元素上一个指定的属性值.如果指定的属性不存在,则返回null或"".
返回值attribute是一个包含attributeName 属性值的字符串.
let attribute = element.getAttribute(attributeName);
例如取val的值 <div val="time"></div>
let timeval=div.getAttribute['val']; // time
只读属性,返回一个元素的类属性的实时 DOMTokenList 集合
const elementClasses = elementNodeReference.classList;
如果类属性未设置或为空,那么 elementClasses.length 返回 0
添加指定的样式类名.如果这些类已经存在于元素的属性中,那么它们将被忽略
参数是不定个数的,用逗号隔开
删除指定的类值.注意:即使删除不存在的类值也不会导致抛出异常
当只有一个参数时: 切换类值;也就是说,即如果类值存在,则删除它并返回 false,如果不存在,则添加它并返回 true
当存在第二个参数时: 若第二个参数的执行结果为 true,则添加指定的类值,若执行结果为 false,则删除它
检查元素的类 class 属性中是否存在指定的类值.返回值为 true | false
用一个新类值替换已有的类值.测试时发现,成功替换返回true,失败返回false
element.setAttribute(name, value);
设置指定元素上的某个属性值.如果属性已经存在,则更新该值;否则,使用指定的名称和值添加一个新的属性
div.setAttribute('val', 100); // div上设置val,值100
element.removeAttribute(attrName);
元素方法 removeAttribute() 从指定的元素中删除一个属性.如果属性不存在,不会报出异常.
若要彻底移除一个属性的效果,应当使用 removeAttribute(),而不是使用 setAttribute() 将属性值设置为 null.对于许多属性,如果仅将其值设为 null,这不会造达成和预期一样的效果
const fragment = document.createDocumentFragment();
生成一个dom片段,表示一个没有父级文件的最小文档对象.
DocumentFragment 不是真实 DOM 树的一部分,它的变化不会触发 DOM 树的重新渲染,且不会导致性能等问题
这个方法非常有用,当需要一个html模板时(例如做js组件),可以用js生成html,存放在DocumentFragment中.
对DocumentFragment中的模板进行dom操作,不会影响到当前页面,需要将模板显示在当前页面中时,只需要appendChild(DocumentFragment).
片段被添加(append)或被插入(inserted)的是片段的所有子节点, 而非片段本身.
var closestElement = targetElement.closest(selectors);
匹配特定选择器且离当前元素最近的祖先元素(也可以是当前元素本身).如果匹配不到,则返回 null
这个方法从自己开始,到父元素到祖先元素,如果匹配selector,就返回这一个元素.参数selector是一个选择器,是必传的.
参考文档 https://developer.mozilla.org/zh-CN/docs/Web/API/Element/closest
let range = document.createRange();
let fragment = range.createContextualFragment(html);
createContextualFragment(htmlString)方法可以解析html字符串,返回一个DocumentFragment对象.
如果htmlstring含有script脚本,那么将解析后的文档片段添加到文档时,是可以执行的.
// 含有script的html字符串 let html ='<div>let range = document.createRange();</div><script src="abc.js"><\/script><script>console.log("hello world")<\/script>' let range = document.createRange(); let fragment = range.createContextualFragment(html); // 添加到文档 document.body.appendChild(fragment); // 控制台打印出 hello world
上面的html字符串含有2个script标记,一个是内联,一个是外联.添加到文档后,脚本执行了.
需要注意的是,脚本没有按script标记书写的顺序执行.这个例子先执行的内联js,后执行的abc.js.
参考文档 https://developer.mozilla.org/zh-CN/docs/Web/API/Document/createDocumentFragment
element = document.querySelector(selectors);
element = baseElement.querySelector(selectors);
element = documentfragment.querySelector(selectors);
返回一个 Element 表示与指定的选择器组匹配的第一个元素.如果没有匹配,返回null
selectors是一个选择器,例如 "#name"(id选择器) ".name"(类选择器) "[prop=val]属性选择器"
selectors参数必须是浏览器支持的选择器,否则会异常.
不仅在document对象上可以使用,也可以在Element对象上使用,还可以在DocumentFragment对象上使用
误区: 可能会认为在document上调用,是从文档跟元素开始查找.而在baseElement上调用,是从baseElement开始查找.其实不是这样的.
选择器首先会应用到整个文档,而不是baseElement,来创建一个可能有匹配元素的初始列表.然后从结果元素中检查它们是否是baseElement的后代元素.如果是,那么第一个匹配的元素将会被querySelector()方法返回
做一个测试,以id="box"的div为基础元素,匹配第二个a元素
<div id="box"> <a>1</a> <div><a>第二个a元素</a></div> </div> // 测试代码和结果 var baseElement = document.querySelector("#box"); let a2 = baseElement.querySelector("div div a"); // <a>第二个a元素</a> let a1 = baseElement.querySelector("div a"); // <a>1</a>
elementList = document.querySelectorAll(selectors);
elementList = parentNode.querySelectorAll(selectors);
elementList = documentfragment.querySelectorAll(selectors);
返回一个 NodeList 表示元素的列表,把当前的元素作为根与指定的选择器组相匹配.
做一个测试,以id="box"的div为当前元素,匹配所有的div元素.结果找到一个div
这说明查找是以当前元素(调用对象)为根(匹配起点)的,并且不包含当前元素.
<div id="box"> <a>1</a> <div><a>第二个a元素</a></div> </div> // 测试代码和结果 var container = document.querySelector("#box"); let matches = container.querySelector("div"); // NodeList[div] <div><a>第二个a元素</a></div>
注意:不要在选择器上使用伪类.否则结果总是空的NodeList[].
document.querySelector("div::first"); // 结果为空NodeList[].
匹配有menuitem样式类的span元素
div.querySelectorAll("span.menuitem");
匹配有menuitem和active样式类的span元素
div.querySelectorAll("span.menuitem.active");
匹配有checked属性(选中的)的input元素.
div.querySelectorAll("input[checked]");
匹配val属性值为30的span元素.注意,'30'加了引号,否则报异常:选择器不合法.
div.querySelectorAll("span[val='30']");
匹配val属性值为30,和有menuitem样式类的span元素.
div.querySelectorAll("span[val='30'],span.menuitem");
var insertedNode = parentNode.insertBefore(newNode, referenceNode);
如果 referenceNode 为 null 则 newNode 将被插入到子节点的末尾.referenceNode 引用节点不是可选参数——你必须显式传入一个 Node 或者 null
函数返回被插入过的子节点;当 newNode 是 DocumentFragment 时,返回空 DocumentFragment
例: 把span插入到div的第一个子元素前面.返回值result就是span元素
let result = div.insertBefore(span, div.firstChild);
fetch(string: url,requestInit: [{method:'post',body:formdata}])
参数1是url,第二个参数可以配置请求方式和数据,还有跨域之类的配置.
fetch()执行后返回一个Promise对象,里面有response信息.如请求结果,成功失败标志等.
get一个数据
fetch('/user/info') .then(response => response.json()) .then(json=>console.log(json))
不需要写第二个参数,默认method是GET方式.
fetch()返回的是Promise对象,这个对象有个then()方法,所以可以链式写法.then(callback(response))方法需要一个函数callback做参数,
这个函数可以使用一个参数就是response,包含请求结果,成功失败标志等信息.then()方法调用完成后,还是返回Promise对象,所以可以继续调用then().
fetch()返回第一个Promise(1)对象,第一次调用then()之后,又返回Promise(2)对象.显然两个Promise对象是不会一样的,而then()的函数参数
的作用就是影响要返回的Promise对象.也就是说,Promise对象是一个包装,里面包有then(callback)的函数参数callback()返回的结果.就这样,
通过Promise对象包装,通过then()方法,将前一个then()的结果,传给后一个then(),一直then()就会一直传下去...
明白了这个逻辑,对于ajax请求数据,就需要写两个then().第一个then()的函数参数callback返回一个json对象,具体是response.json(),
这个json()方法是response对象的方法,(response对象的详细内容可以打印到控制台查看).第一个then()返回的json对象,会包装到Promise对象,以供
第二个then()使用,使用第二个then()里,就可以写拿到json数据后的业务逻辑了.
除了res.json()解析返回类型为json的数据,还有res.text()方法,接受返回类型为文本的数据.如果,不是json类型返回值,用了res.json()会爆解析异常.
Promise对象的回调函数写在then()方法里,比起以往的callback()里的callback()要好一些,至少层次清晰,是同步代码的习惯.
业务中经常遇到的场景,要通过多此ajax请求才能得到最终数据,于是不得不在回调函数中再次ajax,再次回调函数...嵌套很多次,代码不好维护
这个是回调地狱问题,如果采用Promise对象一层层传递结果的方法,则可以减轻这种痛苦.
例如一个2次回调拿到数据的场景
fetch('/api/json1') // 第一次then(),返回api/json1接口的数据,json1 .then(res1=>res1.json()) // 第二次then(),在方法中继续发出api/json2接口的请求,拿到的数据加上json1,一起返回 // 当然了,如果接口2需要接口1的结果做请求参数,也是在这个then()里写 .then(json1=> fetch('/api/json2') .then(res2=>res2.json()) .then(json2=>{ json2.data1=json1; return json2; }) ) // 这里,拿到了两此请求的所有数据 .then(data=>console.log(data))
then(callback(res))的callback()方法的返回值可以是json对象,string对象,或者其它对象,自定义对象,它会被包装成Promise对象返回,
下一个then()的函数参数callback(res)的参数res就是上一个then()方法返回的对象.
.catch((err)=>{console.log(err)}) .then()之后的.catch()方法,在出现错误时会调用.ajax出错的常见情况有:网路不通,
ajax方法本身有异常,接口地址错了,服务器异常了.这时都会到.catch()方法里来,前提是要写上.
fetch(url) .then(res=>res.json()) .catch(err=>{console.log(err)})
还有一种错误是由于没有弄清返回值类型而报出异常.比如一个接口返回值不是json类型,而是文本类型,此时如果在.then(res=>res.json())
就会异常,因为返回值不是json类型,所以res.json()的解析就会出错.所以要事先知道返回值类型.根据类型选择res.json()或res.text()
// 一种错误处理办法 fetch(url) .then(res=>{ // 正常情况返回json对象给后续处理 if(res.ok) return res.json(); // 异常情况时,丢出异常.让后面的catch()执行 throw new Error('出错了') }) .catch(err=>{ // 异常信息 console.log(err.message); })
详细讲解fetch() https://www.cnblogs.com/libin-1/p/6853677.html
promise对象参考文档 https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Guide/Using_promises
nextNode = node.nextSibling
Node.nextSibling 是一个只读属性,返回其父节点的 childNodes 列表中紧跟在其后面的节点,如果指定的节点为最后一个节点,则返回 null
其它测试说明
Gecko内核的浏览器会在源代码中标签内部有空白符的地方插入一个文本结点到文档中.因此,
使用诸如 Node.firstChild 和 Node.previousSibling 之类的方法可能会引用到一个空白符文本节点, 而不是使用者所预期得到的节点.
var elem = document.getElementById('#id'); // elem到组后一个时,值为null while (elem) { elem = elem.nextSibling; }
参考文档 https://developer.mozilla.org/zh-CN/docs/Web/API/Node/nextSibling
topPos = element.offsetTop;
HTMLElement.offsetTop 为只读属性,它返回当前元素相对于其 offsetParent 元素的顶部内边距的距离
参考文档 https://developer.mozilla.org/zh-CN/docs/Web/API/HTMLElement/offsetTop
left = element.offsetLeft;
left 是一个整数,表示向左偏移的像素值
HTMLElement.offsetLeft 是一个只读属性,返回当前元素左上角相对于 HTMLElement.offsetParent 节点的左边界偏移的像素值
对块级元素来说,offsetTop~offsetLeft~offsetWidth 及 offsetHeight 描述了元素相对于 offsetParent 的边界框
参考文档 https://developer.mozilla.org/zh-CN/docs/Web/API/HTMLElement/offsetLeft
var offsetWidth =element.offsetWidth;
HTMLElement.offsetWidth 是一个只读属性,返回一个元素的布局宽度
这个属性将会 round(四舍五入)为一个整数.如果你想要一个fractional(小数)值,请使用element.getBoundingClientRect()
注: 各浏览器的offsetWidth可能有所不同)offsetWidth是测量包含元素的边框(border)~水平线上的内边距(padding)~
竖直方向滚动条(scrollbar)(如果存在的话)~以及CSS设置的宽度(width)的值
参考文档 https://developer.mozilla.org/zh-CN/docs/Web/API/HTMLElement/offsetWidth
var offsetHeight = element.offsetHeight;
HTMLElement.offsetHeight 是一个只读属性,它返回该元素的像素高度,高度包含该元素的垂直内边距和边框,且是一个整数
这个属性值会被四舍五入为整数值,如果你需要一个浮点数值,请用 element.getBoundingClientRect()
参考文档 https://developer.mozilla.org/zh-CN/docs/Web/API/HTMLElement/offsetHeight