浏览器

78 阅读5分钟

事件机制

事件触发有三个阶段
    window往事件触发处传播(事件捕获)
    传播到事件触发处时触发注册事件
    从事件触发处往window传播,遇到注册得冒泡事件触发(事件冒泡)

addEventListener参数,第一个是事件类型,第二个是回调函数,第三个是:
    可以为布尔true捕获,fasle冒泡
    也可以为对象
        { 
            capture,    // 布尔,true捕获,false冒泡
            once,       // 布尔,true时该回调只会调用一次,调用后移除
            passive     // 布尔,表示永远不会调用preventDefault 
        }

事件只触发在目标上,使用 stopPropagation 阻止事件得进一步传播,捕获和冒泡都可以阻止,stopImmediatePropagation 也可以阻止事件,还可以阻止目标执行别的注册事件。

事件代理,通过给父节点注册事件,优点:
    节省内存
    不用给子节点注销事件

跨域

同源策略:协议、域名、端口

JSONP
利用 script 标签没有跨域限制得漏洞,通过script标签指向需要访问的地址并提供一个回调函数来做接收数据。

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需要浏览器和后端同时支持,IE89需要通过XDomainRequist来实现。
浏览器会自动进行CORS通信,服务端设置 ACccess-Control-Allow-Origin 就开启CORS了,该属性表示哪些域名可以访问资源,通配符就是所有域名都可以访问到资源。


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('验证通过')
  }
})

Event Loop

JS是单线程,和浏览器交互如果是多线程会混乱(删节点,加节点)
JS在执行时会产生执行环境,并顺序加入到执行栈中,异步的代码会被挂起并加入到Task(有多种Task)队列,一旦执行栈执行完为空,EventLoop就会从Task队列中拿出需要执行的异步代码放入执行栈中执行,本质上JS的异步还是同步。
不同的任务源会分配不同的Task队列中,任务源分为:
    微任务 microtask || jobs
    宏任务 macrotask || task

微任务包括 process.nextTick ,promise ,Object.observeMutationObserver
宏任务包括 script , setTimeoutsetInterval ,setImmediate ,I/O ,UI rendering

所以正确的一次 Event loop 顺序:
    执行同步代码,这属于宏任务
    执行栈为空,查询是否有微任务需要执行
    执行所有微任务
    必要的话渲染 UI
    然后开始下一轮 Event loop,执行宏任务中的异步代码

console.log('script start')
setTimeout(function() {
  console.log('setTimeout')
}, 0)
new Promise(resolve => {
  console.log('Promise')
  resolve()
})
  .then(function() {
    console.log('promise1')
  })
  .then(function() {
    console.log('promise2')
  })
console.log('script end')

// new Promise里面的函数体是同步的,.then后才是异步微任务
// script start => Promise => script end => promise1 => promise2 => setTimeout




Node中的 EventLoop
分为六个阶段,按照顺序反复运行:
    timers
        执行setTimeoutsetInterval,时间范围[1, 2147483647],不在范围将被设置为1
    I/O
        执行除了close事件、定时器、setImmediate的回调
    idle, prepare
        内部实现
    poll
        执行到点的定时器
        执行poll队列中的事件
            如果poll中没有定时器
                如果有setImmediate需要执行就进入check阶段
                如果没有等待回调被加入到队列并立即执行回调
            如果有别的定时器需要被执行,会回到timer阶段执行回调
    check
        执行setImmediate
    close callbacks
        执行close事件

setTimeout(() => {
    console.log('setTimeout')
}, 0)
setImmediate(() => {
    console.log('setImmediate')
})
// 这里可能会输出 setTimeout,setImmediate
// 可能也会相反的输出,这取决于性能
// 因为可能进入 event loop 用了不到 1 毫秒,这时候会执行 setImmediate
// 否则会执行 setTimeout

var fs = require('fs')
fs.readFile(__filename, () => {
    setTimeout(() => {
        console.log('timeout')
    }, 0)
    setImmediate(() => {
        console.log('immediate')
    })
})
// 因为 readFile 的回调在 poll 中执行
// 发现有 setImmediate ,所以会立即跳到 check 阶段执行回调
// 再去 timer 阶段执行 setTimeout
// 所以以上输出一定是 setImmediate,setTimeout

Node 中的 process.nextTick 会先于其他 microtask 执行。

存储

cookie
    服务器生成,可以设置过期时间,4K,与服务端通信每次携带header中
localStorage
    除非被清理不然一直存在,5M
sessionStorage
    页面关闭就清理,5M
indexDB
    除非被清理不然一直存在,无限

cookie属性
    value       如果用于保存用户登陆状态,应该加密
    http-only   不能通过JS访问Cookie,减少XSS攻击
    secure      只能在HTTPS协议的请求中携带
    same-site   规定浏览器不能在跨域请求中携带Cookie,减少CSRF攻击


Service Worker
本质上充当 Web 应用程序与浏览器之间的代理服务器,也可以在网络可用时作为浏览器和网络间的代理,创建有效的离线体验,拦截网络请求并基于网络是否可用以及更新的资源是否驻留在服务器上来采取适当的动作,允许访问推送通知和后台同步 API,通常用来做缓存文件提高首屏速度。

// 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')
        })
    )
})