企业🤖AI智能体构建引擎,智能编排和调试,一键部署,支持私有化部署方案 广告
事件机制、跨域,存储相关等 ### 事件机制 **事件触发过程是怎么样的?知道什么是事件代理吗?** #### 事件触发三个阶段 事件触发有三个阶段: 1. window往事件触发处传播,遇到注册的**捕获事件会触发**(事件捕获阶段) 2. 传播到事件触发处时**触发注册的事件**(事件目标处理函数) 3. 从事件触发处往window传播,遇到注册的**冒泡事件**会触发(事件冒泡) 事件触发一般来说会按照上面的顺序进行,特例:如果给一个body中的子节点同时注册冒泡事件和捕获事件,**事件触发会按照注册的顺序执行**。 ~~~ // 以下会先打印冒泡然后是捕获 node.addEventListener( 'click', event => { console.log('冒泡') }, false ) node.addEventListener( 'click', event => { console.log('捕获 ') }, true ) ~~~ #### 注册事件 使用addEventListener注册事件,第三个参数可以是布尔值,也可以是对象。布尔值useCapture 默认false,决定了注册的事件是捕获事件还是冒泡事件。 如果是对象: * capture:和useCapture作用一样 * once:true表示该回调函数只会调用一次,调用后移除监听 * passive:布尔值,表示永远不会调用preventDefault 一般我们只希望触发在目标上,可以使用**stopPropagation**来阻止事件的传播。(可以阻止事件冒泡,也可以阻止事件捕获)`stopImmediatePropagation`同样也能实现阻止事件,但是还能阻止**该事件目标执行别的注册事件**。 ~~~ node.addEventListener( 'click', event => { event.stopImmediatePropagation() console.log('冒泡') }, false ) // 点击 node 只会执行上面的函数,该函数不会执行 node.addEventListener( 'click', event => { console.log('捕获 ') }, true ) ~~~ 详细: * **事件的捕获阶段**:当某个元素触发某个事件(click),顶级对象document发出一个事件流,顺着dom的树节点向触发它的目标节点流去,知道达到目标元素。 触发事件一般会按照上面的顺序进行,也有特例,如果给一个body中的子节点同时注册冒泡和捕获事件,事件触发会按照注册的顺序执行。(层层递进,向下找目标,此过程与事件响应的函数是不会触发的) * **事件目标处理函数阶段**:到达目标函数,便会执行绑定在此元素上的与事件相应的函数 * **事件冒泡**:从目标元素起,再依次网顶层元素对象传递,途中如果节点绑定了**同名事件**,则执行相应的事件 * 通常事件相应的函数事件在冒泡节点执行**,addEventListener的第三个参数默认为false,表示冒泡节点执行**(true表示捕获阶段执行) *` e.stopPropgation()或e.cancelBubble = true(IE)`可以阻断事件向当前元素的父元素冒泡。 * 同个元素可以绑定多个事件 #### 事件代理 **如果一个节点张的子节点是动态生成的,那么子节点需要注册事件的话应该注册在父节点上**。 ~~~ <ul id="ul"> <li>1</li> <li>2</li> <li>3</li> <li>4</li> <li>5</li> </ul> <script> let ul = document.querySelector('#ul') ul.addEventListener('click', (event) => { console.log(event.target); }) </script> ~~~ 事件代理vs直接给目标注册事件 1. 节省内存 2. 不需要给子节点注销事件 ### 跨域 **什么是跨域?为什么浏览器要使用同源策略?你有几种方式可以解决跨域问题?了解预检请求嘛?** 因为浏览器出于**安全考虑**,有同源策略。 跨域:如果协议、域名或者端口有一个不同,Ajax请求就会失败 什么安全考虑引用这种机制? 主要是用来**防止CSRF攻击的**。**CSRF攻击是利用用户的登录状态发起恶意请求**。 如果没有同源策略:**A网站可以被任意其他来源的Ajax访问到内容,如果你当前A网站还涔在登录态,那么对方就可以通过Ajax获取你的任何信息**,跨域并不能完全阻止CSRF。 跨域:请求发出去了,但浏览器拦截了响应。(**为什么表单的方式可以发起跨域请求**) 归根结底:跨域是为了阻止用户读取到另一个域名下的内容,Ajax可以获取响应,浏览器任务这不安全,所以拦截了响应,**但是表单并不会获取新内容,所以可以发起跨域请求**。同时说明了跨域并不能完全阻止CSRF,因为请求毕竟发出去了。 解决跨域的问题 1. JSONP 2. CORS 3. document.domain 4. postMessage #### JSONP 原理:利用script标签没有跨域限制的漏洞,通过script标签执行一个需要访问的地址并提供一个回调函数来接收数据当需要通讯时。 ~~~ <script src="http://domain/api?param1=a&param2=b&callback=jsonp"></script> <script> function jsonp(data) { console.log(data) } </script> ~~~ **JSONP使用简单且兼容性不错,但是只限于get请求**。 开发中可能会遇到过个JSONP请求的回调函数名是相同的,这时需要自己封装一个JSONP ~~~ function jsonp(url, jsonpCallback, success){ let script = document.createElement('script'); script.src = url; script.async = true; script.type = "text/javascript"; window[jsonpCallback] = function(data){ success && success(data) } document.body.appendChild(script) } jsonp('http://xxx', 'callback', function(value) { console.log(value) }) ~~~ #### CORS CORS需要浏览器和后端同时支持,IE 8 和 9 需要通过`XDomainRequest`来实现。 浏览器会自动进行CORS通讯,**实现CORS通信的关键是后端**,只要后端实现了CORS,就实现了跨域。 服务端设置`Access-Control-Allow-Origin`就可以开启 CORS。 虽然CORS和前端没什么关系,但是通过这种方式解决跨域问题的话,会在发送请求是出现两种情况,分别是**简单请求和复杂请求**。 ##### 简单请求 以Ajax为例,当满足以下条件时,触发简单请求 1. 使用下列方法之一(get、head、post) 2. Content-type(text/plain,multipart/form-data,application/x-www-form-urlencoded) 请求中的任意`XMLHttpRequestUpload`对象均没有注册任何事件监听器;`XMLHttpRequestUpload`对象可以使用`XMLHttpRequest.upload`属性访问。 补充: 新版本的XMLHttpRequest对象,传送数据的时候,有一个progress事件,用来返回进度信息。分为上传和下载两种情况 * 下载的progress事件属于XMLHttpRequest对象 * 上传的progress事件属于XMLHttpRequest.uplaod对象。 先定义progress事件的回调函数 ~~~ xhr.onprogress = updateProgress; xhr.upload.onprogress = updateProgress; ~~~ 回调函数中,使用这个事件的一个属性 ~~~ function updateProgress(event) { if(event.lengthComputable) { var percentComplete = event.loaded / event.total; } ~~~ event.total是需要传输的总字节,event.loaded是已经传输的字节。如果event.lengthComputable不为真,则event.total等于0。 ##### 复杂请求 不符合以上条件 复杂请求:首先会发起一个**预检请求**,**该请求是option方法的**。通过该请求来指导服务端是否允许跨域请求。 #### document.domain 该方式只能用于二级域名相同的情况下,比如:`a.test.com`和`b.test.com` 只需要给页面添加**document.domain = "test.com"**表示二级域名都统统可以实现跨域 #### postMessage 这种方式通常用于获取**嵌入页面中的第三方页面数据**。一个页面发送消息,另一个页面判断来源并接收消息。 ~~~ // 发送消息端 window.parent.postMessage('message', 'http://test.com') // 接收消息端 var mc = new MessageChannel() mc.addEventListener('message', event => { var origin = event.origin || event.originalEvent.origin if (origin === 'http://test.com') { console.log('验证通过') } }) ~~~ ### 存储 **有几种方式可以实现存储功能,分别有什么优缺点?什么是 Service Worker?** cookie,localStorage,sessionStorage,indexDB ![](https://box.kancloud.cn/2d36c165bcdaf8ba9d77975a5cc933f4_695x275.png) 可见,**cookie已经不建议用于存储**。 如果**没有大量数据存储需求**的话,可以使用**localStorage和sessionStorage**。 对于**不怎么改变的数据**,尽量使用localStorage存储,否则可以用sessionStorage存储 ![](https://box.kancloud.cn/eb996b151fbcca8dcdfd32490c735673_539x296.png) #### Service Worker Service Worker是运行在浏览器背后的独立线程,一般可以用来实现缓存功能。**传输协议必须为HTTPS**。(主要因为Service Worker中涉及到请求拦截,用https保障安全) Service Worker实现缓存一般三个步骤 1. 先注册Service Worker 2. 监听到install事件以后就可以缓存需要的文件 3. 下次用户访问的时候就可以通过拦截请求的方式查询是否存在缓存,存在缓存的话就可以直接读取缓存文件,否则就去请求数据。 ~~~ // index.js if (navigator.serviceWorker) { navigator.serviceWorker .register('sw.js') .then(function(registration) { console.log('service worker 注册成功') }) .catch(function(err) { console.log('servcie worker 注册失败') }) } // sw.js // 监听 `install` 事件,回调中缓存所需文件 self.addEventListener('install', e => { e.waitUntil( caches.open('my-cache').then(function(cache) { return cache.addAll(['./index.html', './index.js']) }) ) }) // 拦截所有请求事件 // 如果缓存中已经有请求的数据就直接用缓存,否则去请求数据 self.addEventListener('fetch', e => { e.respondWith( caches.match(e.request).then(function(response) { if (response) { return response } console.log('fetch source') }) ) }) ~~~ 4.