除ES基础之外,Web前端经常会⽤到⼀些跟浏览器相关的API
知识点梳理
- BOM操作
- DOM操作
- 事件绑定
- Ajax
- 存储
BOM
BOM(浏览器对象模型)是浏览器本身的⼀些信息的设置和获取,例如获取浏览器的宽度、高度,设置让浏览器跳转到哪个地址。 + navigator + screen + location + history
这些对象就是⼀堆⾮常简单粗暴的API,去MDN或者w3school这种⽹站⼀查就都明⽩了。下⾯列举⼀下常⽤功能的代码示例获取浏览器特性(即俗称的UA
)然后识别客户端,例如判断是不是Chrome浏览器。
获取浏览器特性(即俗称的UA)然后识别客户端,例如判断是不是Chrome浏览器。 js var ua = navigator.userAgent var isChrome = ua.indexOf('Chrome') console.log(isChrome)
获取屏幕的宽度和高度: js console.log(screen.width) console.log(screen.height)
获取⽹址、协议、path、参数、hash等:
js //例如当前⽹址是 https://juejin.im/timeline/frontend?a=10&b=10#some console.log(location.href) // https://juejin.im/timeline/frontend?a=10&b=10#some console.log(location.protocol) // https: console.log(location.search) // ?a=10&b=10 console.log(location.hash) // #some
另外,还有浏览器的前进、后退功能等: js history.back() history.forward()
DOM
题⽬:DOM和HTML区别和联系
什么是DOM
讲DOM先从HTML讲起,讲HTML
先从XML
讲起。XML是⼀种可扩展的标记语⾔,所谓可扩展就是它可以描述任何结构化的数据,它是⼀棵树!
xml <?xml version="1.0" encoding="UTF-8" ?> <note> <to>Tove</to> <from>Jani</from> <heading>Reminder</heading> <body>Don't forget me this weekend!</body> <other> <a></a> <b></b> </other> </note>
HTML是⼀个有既定标签标准的XML格式,标签的名字、层级关系和属性,都被标准化(否则浏览器⽆法解析)。同样,它也是⼀棵树。 html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <div> <p>this is p</p> </div> </body> </html>
开发完的HTML代码会保存到⼀个⽂档中(⼀般以.html
或者.htm
结尾),⽂档放在服务器上,浏览器请求服务器,这个⽂档被返回。因此,最终浏览器拿到的是⼀个⽂档⽽已,⽂档的内容就是HTML格式的代码。
但是浏览器要把这个⽂档中的HTML按照标准渲染成⼀个⻚⾯,此时浏览器就需要将这堆代码处理成⾃⼰能理解的东⻄,也得处理成JS能理解的东⻄,因为还得允许JS修改⻚⾯内容呢。
基于以上需求,浏览器就需要把HTML
转变成DOM
,HTML是⼀棵树,DOM也是⼀棵树。对DOM的理解,可以暂时先抛开浏览器的内部因素,先从JS着⼿,即可以认为DOM就是JS能识别的HTML结构,⼀个普通的JS对象或者数组。
获取DMO节点
最常⽤的DOM API就是获取节点,其中常⽤的获取⽅法如下⾯代码示例: ```js // 通过 id 获取 var div1 = document.getElementsById('div1') // 元素
// 通过 tagname 获取 var divList = document.getElementsByClassName('div') // 集合 console.log(divList.length) console.log(divList[0])
// 获取 class 获取 var containerList = document.getElementsByClassName('containerList') // 集合
// 通过 CSS 选择期获取 var pList = document.querySelectorAll('p') // 集合 ```
题目:property和attribute的区别是什么?
property
DOM节点就是⼀个JS对象,它符合之前讲述的对象的特征——可扩展属性,因为DOM节点本质上也是⼀个JS对象。因此,如下代码所示,p
可以有style
属性,有classNamenodeNamenodeType
属性。注意,这些都是JS范畴的属性,符合JS语法标准的。
```js var pList = document.querySelectorAll('p') var p = pList[0] console.log(p.style.width) // 修改样式 console.log(p.className); // 获取 class p.className = 'p1' // 修改 class
// 获取 nodeName 和 nodeType console.log(p.nodeName) console.log(p.nodeType) ```
attribute
property
的获取和修改,是直接改变JS对象,而attribute
是直接改变HTML的属性,两种有很大的区别。attribute就是对HTML属性的get和set,和DOM节点的JS范畴的property没有关系。 js var pList = document.querySelectorAll('p') var p = pList[0] p.getAttribute('data-name') p.setAttribute('data-name', 'juejin') p.getAttribute('style') p.setAttribute('style', 'font-size:30px')
而且,get和set attribute时,还会触发DOM的查询或者重绘、重排,频繁操作会影响⻚⾯性能。
题目:DOM操作的基本API有哪些?
DOM树操作
新增节点 ```js var div1 = document.getElementById('div1')
// 新增新节点 var p1 = document.createElement('p') p1.innerHTML = 'this is p1' div1.appendChild(p1) //添加新创建的元素
// 移动已有的父节点。注意,这里的"移动",并不是拷贝 var p2 = document.getElementById('p2') div1.appendChild(p2) ```
获取父节点 js var div1 = document.getElementById('div1') var parent = document.parentElement
获取子节点 js var div1 = document.getElementById('div1') var child = div1.childNodes
删除节点 js var div1 = document.getElementById('div1') var child = div1.childNodes div1.removeChild(child[0])
还有其他操作的API,例如获取前⼀个节点、获取后一个节点等.
事件
事件绑定
普通的事件绑定写法如下: js var btn = document.getElementById('btn') btn.addEventListener('click', function (event) { // event.preventDefault() // 阻止默认行为 // event.stopPropagation() // 阻止冒泡 console.log('clicked'); })
为了编写简单的事件绑定,可以编写通⽤的事件绑定函数。这里虽然比较简单,后面再完善。 ```js // 通用的事件绑定函数 function bindEvent(el, type, fn) { el.addEventLister(type, fn) }
var a = document.getElementById('link') // 写起来更加简单了 bindEvent(a, 'click', function(e) { e.preventDefault(); alter('clicked') }) ```
题目:什么是事件冒泡? ```html
激活
取消
取消
取消
``` 对于以上HTML代码结构,要求点击`p1`时候进激活状态,点击其他任何`取消
取消
`都取消激活状态,如何实现?代码如下,注意看注释: ```js var body = document.body bindEvent(body, 'click', function (e) { // 所有p的点击都会冒泡到body上, 因为DOM结构中body是p的上级节点 // 事件会沿着DOM树向上冒泡 alert('取消') }) var p1 = document.getElementById('p1') bindEvent(body, 'click', function (e) { e.stopPropagation(); // 阻止冒泡 alert('激活') }) ``` 如果我们在`p1` `div1` `body`中都绑定了事件,它是会根据DOM的结构来冒泡,从下到上挨个执行`e.stopPropagation()`就可以阻止冒泡。 > 题目:如何使⽤事件代理?有何好处? ### 事件代理 设定一种场景,如下代码,一个`
`中包含了若干个` `,⽽且还能继续增加。那如何快捷⽅便地为所有` `绑定事件呢? ```html ``` 这里就会用到事件代理。我们要监听` `的事件,但要把具体的事件绑定到``上,然后看事件的触发点是不是` `。 ```js var div1 = document.getElementById('div1') div1.addEventListener('click', function (e) { // e.target 可以监听到出发点击事件的元素是哪一个 var target = e.target if (e.nodeName === 'A') { // 点击的是 元素 alert(target.innerHTML) } }) ``` 现在完善一下之前写的通⽤事件绑定函数,加上事件代理。 ```js function bindEvent(elem, type, selector, fn) { if (fn == null) { fn = selector selector = null } // 绑定事件 elem.addEventListener(type, function(e) { var target if (selector) { // 有 slector 说明需要做的事件代理 // 获取触发时间的元素,即 e.target target = e.target // 看是否符合 selector 这个条件 if (target.matches(selector)) { fn.call(target, e) } } else { // 无 selector,说明不需要事件代理 fn(e) } }) } ``` 然后这样使用,简单很多。 ```js // 使用代理,bindEvent 多一个 'a'参数 var div1 = document.getElementById('div1') bindEvent(div1, 'click', 'a', function(e) { console.log(this.innerHTML); }) //不使用代理 var a = document.getElementById('a') bindEvent(div1, 'click', function(e) { console.log(this.innerHTML); }) ``` 最后,使用代理的优点如下: + 使代码简洁 + 减少浏览器的内存占用 ### Ajax > 题目:手写XMLHttpRequest不借助任何库 ```js var xhr = new XMLHttpRequest() xhr.onreadystatechange = function() { // 这里的函数异步执行 if (xhr.readyState == 4) { if (xhr.status == 200) { alert(xhr.responseText) } } } xhr.open('GET', '/api', false) xhr.send(null) ``` 当然,使用`jQuery`、`Zepto`或`Fetch`等库来写就更加简单了。 ### 状态码说明 上述代码中,有两处状态码需要说明。`xhr.readyState`是浏览器判断请求过程中各个阶段的,`xhr.status`是HTTP协议中规定的不同结果的返回状态说明。 `xhr.readyState`的状态码说明: + 0-代理被创建,但尚未调用open()方法。 + 1 - `open()`方法已经被调用。 + 2 - `send()`方法已经被调用,并且头部和状态已经可获得。 + 3 - 下载中,`responseText`属性已经包含部分数据。 + 4 - 下载操作已完成 > 题目:HTTP协议中,response的状态码,常见的有哪些? `xhr.status`即HTTP状态码,有`2xx` `3xx` `4xx` `5xx` 这几种,比较常见的有以下几种: + `200` 正常 + `3xx` + `301` 永久重定向。如`http://xxx.com`这个GET请求(最后没有`/`),就会被301到h`ttp://xxx.com/`(最后是`/`) + `302` 临时重定向。临时的,不是永久的 + `304` 资源找到但是不符合请求条件,不会返回任何主体。如发送GET请求时,head中有If-Modified-Since: xxx(要求返回更新时间是xxx时间之后的资源),如果此时服务器端资源未更新,则会返回304,即不符合要求 + `404` 找不到资源 + `5xx` 服务器端出错了 理解为何上述代码中要同时满足`xhr.readyState == 4`和`xhr.status == 200`。 ### Fetch API ⽬前已经有个获取HTTP请求更加⽅便的API:`Fetch`,通过Fetch提供的`fetch()`这个全局函数⽅法可以很简单地发起异步请求,并且支持`Promise`的回调。但是Fetch API是比较新的API,具体使⽤的时候还需要查查`caniuse`,看下其浏览器兼容情况。 ```js fetch('some/api/data.json', { method: 'POST', headers: {}, body: {}, mode: '', credentials:'', cache: '', }).then(res => {}) ``` `Fetch`支持`headers`定义,通过headers⾃定义可以⽅便地实现多种请求⽅法(PUT、GET、POST等)、请求头(包括跨域)和`cache`策略等;除此之外还⽀持response(返回数据)多种类型,比如支持二进制文件、字符串和`formData`等。 ### 跨域 > 题目:如何实现跨域? 浏览器中有**同源策略**,即一个域下的页面中,无法通过Ajax获取到其他域的接口。例如有一个接口`http://m.juejin.com/course/ajaxcourserecom?cid=459`,你自己的个页面`http://www.yourname.com/page1.html`中的Ajax无法获取这个接口。这正是命中了“同源策略”。如果浏览器哪些地方忽略了同源策略,那就是浏览器的安全漏洞,需要紧急修复。 url哪些地⽅不同算作跨域? + 协议 + 域名 + 端口 但是`HTML`中几个标签能逃避过同源策略——`解决跨域 - 服务器端设置 http header
这是需要在服务器端设置的,⽽且,现在推崇的跨域解决⽅案是这⼀种, ⽐JSONP简单许多。 ```js response.setHeader("Access-Control-Allow-Origin", "http://m.juejin.com/"); // 第⼆个参数填写允许跨域的域名称, 不建议直接写 "*" response.setHeader("Access-Control-Allow-Headers", "X-Requested-With"); response.setHeader("Access-Control-Allow-Methods", " PUT,POST,GET,DELETE,OPTIONS"); // 接收跨域的 cookie response.setHeader("Access-Control-Allow-Credentials", "true"); ```存储
题⽬: cookie 和 localStorage 有何区别?
cookie 本身不是⽤来做服务器端存储的(计算机领域有很多这种 “ 狗拿耗⼦ ” 的例⼦, 例如 CSS 中的 float ), 它是设计⽤来在服务器和客户端进⾏信息传递的, 因此我们的每个 HTTP 请求都带着 cookie 。 但是 cookie 也具备浏览器端存储的能⼒ (例如记住⽤户名和密码),因此就被开发者⽤上了。
使⽤起来也⾮常简单,
document.cookie = . . . .
即可。但是 cookie 有它致命的缺点: + 存储量太⼩,只有 4KB + 所有HTTP请求都带着,会影响获取资源的效率 + API简单,需要封装才能⽤
localStroage 和 sessionStorage
后来,HTML5 标准就带来了
sessionStorage
和localStorage
, 先拿 localStorage 来说, 它 是专⻔为了浏览器端缓存⽽设计的。其优点有: + 存储量增⼤到5MB + 不会带到 HTTP 请求中 + API 适⽤于数据存储localStorage.setItem(key, value)
localStorage.getItem(key)
sessionStorage
的区别就在于它是根据session
过去时间⽽实现, ⽽ localStorage 会永久有 效, 应⽤场景不同。例如, ⼀些需要及时失效的重要信息放在 sessionStorage 中, ⼀些不重要但 是不经常设置的信息, 放在 localStorage 中。另外有个⼩技巧, 针对
localStorage.setItem
, 使⽤时尽量加⼊到try-catch
中,某些浏览器是禁⽤这个API的, 要注意。小结
⽇常开发中最常⽤的 API 和知识。