DOM
DOM是哪种数据结构
树(DOM树),基于树状结构才有了父节点、子节点。
DOM操作的常用API
- DOM节点操作:
- 获取1个DOM元素:getElementByld()/querySelector()
- 获取多个DOM元素(nodeList集合):getElementsByTagName()/getElementsByClassName()/querySelectorAll()
- DOM结构操作(具体可参考“JS中修改/创建/移动/删除 HTML DOM元素
“):- document.createElement()创建 元素节点
- document.createTextNode() 创建 文本节点
- Node.appendChild() 添加/移动 子节点
- Node.removeChild() 删除 子节点
- attribute操作:
- getAttribute()获取attribute
- setAttribute()修改attribute
- removeAttribute()删除attribute
- createAttribute()仅建立一个attribute
- property的操作:
- 取值:用“.”就可以,
var id = div1.id; var className = div1.className;
- 赋值:和基本的js对象属性赋值一样,用“.”即可,
div1.className = 'a'; div1.align = 'center'; div1.AAAAA = true; div1.BBBBB = [1, 2, 3];
- 对属性Property可以赋任何类型的值,而对特性Attribute只能赋值字符串!
- 取值:用“.”就可以,
- 使用getAttribute()获取 和 直接获取property值 的区别:直接获取property的方式只能获取节点本来具有的属性,而getAttribute()可以获取自定义的属性,例如
data-xxx
;- 补充:html5允许自定义属性,可以通过
data-xxx
来设置我们需要的自定义属性,进行数据存放。 - jquery中获取自定义属性的方法: 1是
.attr('data-xxx')
,2是.data('xxx')
- js中获取自定义属性的方法:
- 通过attribute:
getAttribute('data-xxx')
- 通过dataset(html5属性):以下例子假设自定义属性
data-xxx
- 取值:
ele.dataset.xxx
- 赋值:
ele.dataset.xxx = "3m666";
- 新增data-age属性:
ele.dataset.age = "100";
- 删除:设置成null,或者delete
ele.dataset.xxx = null; delete ele.dataset.age;
- 取值:
- 通过attribute:
- 补充:html5允许自定义属性,可以通过
attribute和property的区别
- attribute:修改html属性,会改变html结构
- attribute 是“死”的,是 DOM元素 在文档中作为 html 标签拥有的固有属性
- property:修改JS对象属性,不会体现到html结构中
- property 是“活”的,是经过浏览器解析后 DOM元素 在 js 中作为对象拥有的属性
- “class”在Property中叫做“className”,因为“class”是ECMA的关键字。所以通过
.
获取样式名时要通过ele.className
获取!
- attributes是属于property的一个子集
- 有些固有属性通过
.
和getAttribute()
获取到的是不同的,比如:获取onclick属性时,通过getAttribute()
获取到的是函数执行结果,而通过.
获取到的是函数体(例子)。所以一般只有在取自定义值的时候才用getAttribute()
- 两者都有可能引起DOM重新渲染,但建议尽量使用property(但是使用property无法获取自定义属性)
HTML attribute(特性) | DOM property(属性) |
---|---|
值永远是字符串或 null | 值可以是任意合法 js 类型 |
大小写不敏感 | 大小写敏感 |
不存在时返回 null | 不存在时返回 undefined |
对于 href, 返回 html 设置的值 | 对于 href 返回解析后的完整 url |
更新 input的value, 属性property也更新 | 更新 input的value, 特性attribute不更新(例子) |
(可参考这篇博客)
一次性插入多个DOM节点,考虑性能
将频繁操作改为一次性操作:
- 通过
createDocumentFragment()
先创建一个文档片段,这个文档片段是保存在JS中的(不是在DOM树中) - 将循环10次产生的子节点都添加到文档片段中
- 将 文档片段 添加到DOM树上:
BOM
如何识别浏览器的类型
通过navigator.userAgent
识别浏览器的类型。
分析拆解url各个部分
- location.href:整个网站地址
- location.protocol:采用的协议(比如
https:
/http:
) - location.host:域名
- location.search:路由参数,即
?
后的内容 - location.hash:网址的哈希,即
#
后的内容 - location.pathname:路径
事件
编写一个通用的事件监听函数(事件代理)
- 【简单版】可监听 普通绑定 的通用事件监听函数:
- 【正式版】可以同时监听 普通绑定 和 代理绑定 的通用事件监听函数:普通绑定时this就指向触发事件的DOM元素;事件代理时就需要
事件对象.target
来获取触发事件的DOM元素- 注意:事件代理的监听就是我们希望绑定在div上的事件可以且仅在监听到a标签点击事件时触发
Element.matches()
- 如果 元素 匹配指定的 选择器 字符串,则Element.matches()返回true,否则返回false。
- 语法:
let result = element.matches(selectorString);
- result 的值为 true 或 false.
- selectorString 是个css选择器字符串.
- MDN文档
event事件对象
- 可参考“DOM基础”
- DOM事件的回调函数中的第一个参数event指的是事件对象
- 可以使用**
event.preventDefault()
取消事件的默认动作**,比如:如果 type 属性是 “submit”,在事件传播的任意阶段可以调用任意的事件句柄,通过调用该方法,可以阻止提交表单。注意,如果 Event 对象的 cancelable 属性是 fasle,那么就没有默认动作,或者不能阻止默认动作。无论哪种情况,调用该方法都没有作用。 事件对象.target
就是事件绑定函数对应的事件触发的DOM元素
target 事件属性
事件对象.target
事件属性可返回事件的目标节点(DOM元素)(触发该事件的节点),如生成事件的DOM元素、文档或窗口。- 具体可参考菜鸟教程
描述事件冒泡的流程
- 基于DOM树形结构,事件流图示
- DOM结构是一个树型结构,当一个DOM元素触发一个事件时,该事件会在元素结点与根结点之间的路径传播,路径所经过的结点都会收到该事件,这个传播过程可称为 DOM 事件流(DOM event flow )。
- 冒泡:事件会顺着触发元素往根节点冒泡传播
- 应用场景:事件代理(简单例子)
无限下拉的图片列表,如何监听每个图片的点击?
- 事件代理
- 用e.target获取触发元素(参考在上面)
- 用**Element.matches()**来判断是否是触发元素(参考在上面)
AJAX
手写简易ajax
模拟404情况,换成不存在的地址:
浏览器的同源策略
- 可参考JS Web API AJAX
- 同源策略:ajax请求时,浏览器要求当前网页和server必须同源(为了安全)
- 注意:同源是浏览器规定的,在服务端是可以发起跨域攻击的,比如爬虫,因为server端并无同源策略
- 同源:协议、域名、端口,三者必须一致
- 比如:前端
http://a.com:8080/
;server:https://b.com/api/xxx
(默认443端口)那么这前后端是三者都不一致
- 比如:前端
- 同源策略限制了跨域时的以下行为:
- Cookie、LocalStorage 和 IndexDB 跨域无法读取,只有同源才能获取他们
- sessionstorage同源也不一定共享,毕竟只在同一个浏览器窗口才共享(参考本文最下方)
- DOM 和 JS 对象跨域无法获取
- Ajax请求跨域时无效,可发送,但浏览器会拒绝接受响应
- Cookie、LocalStorage 和 IndexDB 跨域无法读取,只有同源才能获取他们
跨域的使用场景
- 跨域问题 是浏览器对于ajax请求的一种安全限制:一个页面发起的ajax请求,只能是于当前页同源的路径,这能有效的阻止跨站攻击。
- 前提:
- 所有的跨域,都必须经过server端允许和配合(包括前端jsonp)
- 未经server端允许就实现跨域,说明浏览器有漏洞,危险信号
- 具体配合可参考[《JS Web API AJAX》]
- 加载img CSS js可无视同源策略:
- 开发环境中,前后端联调时 使用nginx反向代理 或 proxy反向代理 根据url派发不同的端口,把跨域变为不跨域,使Ajax请求变成同源的从而传递数据
- 例子1:假设前端页面是3000端口,后端页面是8000端口。那么用户打开页面时打开的就是3000端口的页面,前端页面中使用ajax请求获取数据时就会去获取3000端口下的对应路径的数据。而后端页面是8000端口,所以数据其实保存在8000端口下的对应路径中。这就无法获取数据。此时使用nginx进行反向代理,遇到/api/开头的url时就分配到8000端口,遇到/开头的url时就分配到3000端口,这样就没问题了。
- 例子2:
- 开发环境中,假设前端服务是
http://localhost:8089/
,后端接口是存在http://test01.7debao.com/
服务上的 - 那么前端打开页面时打开的就是
http://localhost:8089/
,前端页面中使用ajax请求获取数据时就会去获取http://localhost:8089/
下的对应路径的数据 - 而后端接口是存在
http://test01.7debao.com/
服务上的,所以数据其实保存在http://test01.7debao.com/
下的对应路径中,这就导致无法获取数据 - 此时使用proxy进行反向代理,结合axios配置给接口加d baseUrl
/api
,遇到以/api
开头的URL请求会被代理分配到后端服务,其他以/
开头的URL请求会被前端服务处理显示页面或返回404错误 - 注意: 生产和测试环境下,我开发的项目前后端代码应该是部署在同一域名下,接口请求不存在跨域问题,所以不需要设置反向代理,具体是运维在云服务上配的,我只能看到没有处理接口,连axios的baseUrl都没设置,看起来是同源直接请求
- 开发环境中,假设前端服务是
跨域的实现方式
- 注意:如果是协议和端口造成的跨域问题,“前端”无法解决。
- 前提:所有的跨域,都必须经过server端允许和配合(包括前端jsonp)
- 可通过jsonp、nginx反向代理、CORS实现跨域
- jsonp原理:可参考博客JS Web API AJAX
- nginx反向代理:可参考博客项目登录(前端联调)和nginx反向代理
- [proxy反向代理] : 可参考博客axios使用和简单封装
- CORS(纯服务端):可参考博客JS Web API AJAX
实际项目中 ajax 的常用插件
- 参考《JS Web API AJAX》
- jquery
- fetch
- axios
存储
描述cookie localStorage sessionStorage区别
- 共同点:都保存在浏览器端。
- 区别:
- 生命周期:
- Cookie:设置的cookie过期时间之前一直有效,即使窗口或浏览器关闭。未设置则默认为关闭浏览器后失效。(不是浏览器标签页,而是整个浏览器)
- Localstorage:除非被手动清除,否则永久保存,窗口或浏览器关闭也一直保存,因此用作持久数据。
- Sessionstorage:仅在当前网页会话下有效,关闭页面或浏览器后就会被清除(刷新页面不会被清除),自然也就不可能持久保持。
- 存放数据:
- Cookie:4k左右(cookie数据还有路径(path)的概念,可以限制cookie只属于某个路径下)
- Localstorage和sessionstorage:可以保存5M的信息
- 是否跟随http请求发送出去:
- Cookie:每次都会携带在http头中,如果使用cookie保存过多数据会带来性能问题
- 其他两个:不会自动把数据发给服务器,仅在客户端即浏览器中保存,不参与和服务器的通信
- API易用性:
- Cookie:需要程序员自己封装,原生的cookie接口不友好(document.cookie增删查改)
- 其他两个:即可采用原生接口(getItem、setItem、removeItem、clear),亦可再次封装
- 应用场景:
- 从安全性来说,因为每次http请求都回携带cookie信息,这样子浪费了带宽,所以cookie应该尽可能的少用,此外cookie还需要指定作用域,不可以跨域调用,限制很多,但是对于用户识别用户登陆来说,cookie还是比storage好用
- 其他情况下可以用storage,localstorage可以用来在页面传递参数
- sessionstorage可以用来保存一些临时的数据,防止用户刷新页面后丢失了一些参数。
- 作用域: