原文链接(格式好):《1-5 浏览器相关》
原理知识:《1-3 课程里面的浏览器原理》
浏览器下的 JS
包含:
- ECMAScript:基础逻辑与数据处理
const a = [1,2,3].map(i => i*2) - BOM:对浏览器功能的处理
location.href = 'www.a.com' - DOM:HTML文本的操作
document.title = 'hello'
JS 与 ES 的区别:ES 是普通话,JS 是方言;
官方话术:ECMAScript 是一种语言标准,而 JavaScript 是对这个标准的一种具体实现(额外扩展了一些浏览器交互操作)。
BOM
location
与浏览器的地址相关的
基础知识
举例:
location.href = https://www.yuque.com/u53094/ux9b3x/frct1y9u1fppzpqf?q=1#aXLrY
location 组成
- 协议 protocol => location.protocol,
https: - 域名 host => location.host/location.hostname,
www.yuque.com - 端口 port => location.port,
'' - 路径 path => location.pathname,
/u53094/ux9b3x/frct1y9u1fppzpqf - 参数 query => location.search,
[?,#)之间的,?q=1 - 锚点 anchor => location.hash,
[#之后的,#aXLrY
进阶知识
location.assgin(url)
跳转到指定页面,会创建新的浏览器历史,跟location.href效果一样
location.replace(url)
跳转到指定页面,不会创建新的浏览器历史,并将之前地址的历史替换为新地址
location.reload()
刷新当前页面
location.toString()
获取当前地址,等价于location.href的值
面试常问
- location 的 API
- 路由相关:跳转、参数、操作等
- 实际场景分析:
-
- 项目路由模式:
-
-
- history:
https://www.example.com/user/profile地址不带#;需要服务器支持(因为/user/profile是直接访问服务器资源的,所以需要服务器处理通配让项目只访问主 html 文件) - hash:
https://www.example.com/#/user/profile地址带#;不需要服务器支持(因为#后面的不会发送给服务器,所以可以永远只访问主 html)
- history:
-
- url 的处理
-
- 如何将地址参数转为对象?
function getQueryObject(search = location.search){
const searchObj = {}
if(!search) return searchObj
const searchSplitArray = search.slice(1).split('&')
searchSplitArray.map(item => {
const [key, value] = item.split('=')
searchObj[key] = value
})
return searchObj
}
-
- 使用正则判断是否为 url?
-
-
- 核心是要知道
location的组成,然后一步步判断即可
- 核心是要知道
-
function isURL(str) {
// 使用正则表达式进行 URL 验证
// 这个正则表达式并不涵盖所有可能的情况,但对于一般的 URL 验证已经足够
const urlRegex = /^(https?|ftp)://[^\s/$.?#].[^\s]*$/;
return urlRegex.test(str);
}
// 示例用法
console.log(isURL("https://www.example.com")); // true
console.log(isURL("ftp://file.example.com")); // true
console.log(isURL("invalid-url")); // false
// ^: 开始位置
// (https?|ftp): 匹配 "http", "https", 或 "ftp"
// ://: 匹配 "://"
// [^\s/$.?#]: 匹配任何非空白字符、"/"、"$"、"."、"?""、"#"
// .*: 匹配零个或多个任意字符
// $: 结束位置
history
与浏览器的地址历史相关的,允许你在浏览器的历史记录中向前和向后导航,以及在历史记录中添加新的条目。
基础知识
属性
history.length返回历史记录的数量,number类型
history.state返回当前页面状态,object类型,值为null或
方法
history.back()跳转到历史记录上一页,等价于浏览器后退一步的操作
history.forward()跳转到历史记录下一页,等价于浏览器前进一步的操作
history.go(n)跳转到历史记录的第 n 页,n 为正数表示向前,负数表示向后
history.go(-2); // 后退两页
history.go(3); // 前进三页
history.pushState(state, title, url)跳转到指定 url,并新增历史记录。不会触发页面的重新加载,history 路由模式使用它来进行页面跳转的
history.pushState({page: 1}, "Page 1", "/page-1");
history.replaceState(state, title, url)跳转到指定 url,不会新增历史记录,并将之前地址的历史替换为新地址。不会触发页面的重新加载
history.history.replaceStat({page: 2}, "Page 2", "/page-1")
navigator
包含浏览器信息的对象
基础知识
属性
navigator.userAgent返回浏览器信息,string类型,包含浏览器类型、版本、操作系统信息等
Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)
AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36
Edg/119.0.0.0
navigator.userAgentData返回浏览器信息,object类型,包含浏览器类型、版本、操作系统信息等,更精确
{
"brands": [{
"brand": "Microsoft Edge", // 浏览器品牌
"version": "119" // 浏览器版本
}, {
"brand": "Chromium",
"version": "119"
}, {
"brand": "Not?A_Brand",
"version": "24"
}],
"mobile": false,
"platform": "macOS"
}
navigator.language返回用户首选语言,string类型,我的值为zh-CN
navigator.platform返回浏览器运行的平台,string类型,我的值为MacIntel
navigator.cookieEnable返回浏览器是否启用了 cookie,boolean类型
面试常问
- UA 的获取与解读
-
- 获取:
navigator.userAgent或navigator.userAgentData - 解读:按需解读即可,也可以借鉴 platform.js 库
- 获取:
- 如何获取剪切板的内容?
-
- 借用
navigator.clipbord实现读取剪切板:readText()
- 借用
补充
navigation通常出现在 Web APIs 中,用于提供有关浏览器导航的信息。一个常见的使用场景是在 Service Worker 中使用
screen(有可视化经历必问)
浏览器设备的屏幕相关的
基础知识
属性
screen.height/screen.width:电脑屏幕的宽高,随浏览器设备的分辨率变化
screen.availHeight/screen.availWidth:电脑屏幕的可用宽高(减去了mac/window系统的上下任务栏的高度),随浏览器设备的分辨率变化
screen.orientation:屏幕方向对象,如screen.orientation.type横向(landscape)或纵向(portrait)
参考:如何在JavaScript中获取屏幕,窗口和网页大小_js 如何计算screenx
进阶知识
a. 获取屏幕的大小有哪些方式?
- 使用
screen.width/screen.height获取电脑屏幕的宽高,随浏览器设备的分辨率变化 - 使用
window.innerWidth/window.innerHeight获取浏览器窗口的宽高(包括滚动条),随浏览器窗口大小而变化 - 使用
document.documentElement.clientWidth/document.documentElement.clientHeight获取 html 元素的宽高(不包括滚动条) - 使用
document.body.clientWidth/document.body.clientHeight获取 body 元素的宽高(不包括滚动条)
b. 这几个值之间的关系?
1>=2==3==4
c. 这几个值分别受什么影响?
1受设备分辨率影响
2受浏览器窗口大小影响
3受给 html 元素设置的 CSS 宽高影响
4受给 body 元素设置的 CSS 宽高影响
d. 如何监听浏览器的大小变化?
监听 resize 事件
window.addEventListener('resize', () => {
// 获取窗口当前的大小
const currentWidth = window.innerWidth;
const currentHeight = window.innerHeight;
// 其他逻辑判...
}
e. offsetWidth/offsetHeight与clientWidth/clientHeight的关系
clientWidth/clientHeight=元素可视区域的宽/高 + padding(若有)-滚动条(若有)
offsetWidth/offsetHieght=clientWidth/clientHeight+边框(若有)+滚动条(若有)
f. 滚动条宽高计算方式有哪些?
ⅰ. 全局入手
window.innerWidth(浏览器宽度+滚动条(若有)) - document.documentElement.clientWidth(浏览器宽度)
ⅱ. 元素入手-1(自己想的)
元素(无边框+无padding+可滚动时).offsetWidth - 元素(无边框+无padding+可滚动时).clientWidth
ⅲ. 元素入手-2(iView 的做法)
元素(可滚动时).offsetWidth - 元素(未滚动前).offsetWidth
g. 常见的scroll*属性
ⅰ. scrollWidth/scrollHeight
元素的实际宽高,非可视区域的
若无滚动条,则等于clientWidth/clientHeight
若有滚动条,则等于实际内容宽高(一般跟子元素宽高有关)+padding(若有)
ⅱ. scrollLeft/scrollTop
元素最左端/最上端与窗口最左端/最上端的距离,即滚动条左/上滚的距离
ⅲ. scrollX/scrollY===pageXOffset/pageYOffset
返回元素滚动了的距离
举例:window.scrollX===pageXOffset===document.documentElement.scrollLeft
h. offsetLeft/offsetTop
返回元素的左/上端与最近定位元素(默认为body)的左/上边的距离
i. 如何元素监听滚动事件?
一般滚动的 HTML 结构为:父元素高度小于子元素高度并且父元素允许滚动,这样才能滚动
所以监听的滚动事件是设置在父元素上面的
父元素.addEventListener('scroll', function(){
const { scrollTop } = this
})
j. 如何判断元素是否在可视区域内?
ⅰ. 计算法-父元素为任意元素
对比父元素.scrollTop与子元素.offsetTop+子元素.offsetHeight(若需要)的差值,大于 0 表明还在,小于 0 表明不在
ⅱ. 计算法(getBoundingClientRect)-父元素仅为窗口
元素.getBoundingClientRect()获取元素与可视窗口(body 0,0)之间的关系,返回值为object类型
- width:元素宽度
- height:元素高度
- left:元素左边与可视窗口的左边距离
- right:元素右边与可视窗口的左边距离 = left + width
- top:元素上边与可视窗口的上边距离
- botton:元素下边与可视窗口的上边距离 = top + height
- x:元素鼠标的 X 坐标,等价于 left
- y:元素鼠标的 Y 坐标,等价于 top
- 兼容性问题:IE 会多出 2px(边框计算有差异)
当元素同时满足以下4个条件时,则表明在可视窗口内:
元素.left>= 0元素.top>= 0元素.right<=窗口宽度(document.body.clientWidth)元素.bottom<=窗口高度(document.body.clientHeight)
ⅲ. API 法-IntersectionObserver(性能最好)
采用IntersectionObserver来观察两个元素是否重叠
基本语法:new IntersectionObserver(callback, options),其中的 callback 将会在满足和不满足重叠时分别调用一次
官网文档:
Intersection Observer API:Intersection Observer API - Web API 接口参考 | MDN
IntersectionObserver:IntersectionObserver - Web API 接口参考 | MDN
entry 对象:IntersectionObserverEntry - Web API 接口参考 | MDN
const Observer = new IntersectionObserver((entries, oberver) =>{
// entries:所有子元素的数组,下面 observe() 每次调用时,该数组就会新增长度
// oberver:完整的 options 对象
entries.forEach(entry => {
const {
isIntersecting // 是否满足设置的 threshold 重叠,返回 boolean 值
} = entry
if(isIntersecting) {
// do something....
} else {
// do otherthing....
}
})
},
{
root: null // 监听的父元素,默认为视口,null||ElementDom
threshold: 1 // 表示两个元素的重叠比例,number||number[]
})
Observer.observe(childrenEl) // 调用 observe 触发观察,参数为 ElementDom 类型
面试常问
a. 如何 Tab 实现吸顶效果
思路:监听父元素的滚动事件,实时判断父元素的 scrollTop 是否大于 Tab 的 offsetTop 高度,若大于则说明该 Tab 将滚出可视区域了,所以马上设置固定定位,定位的 Top/Left 需要根据父元素的 offsetLeft/offsetTop
parentEl.addEventListener("scroll", function () {
const { scrollTop } = this;
const { offsetTop } = tabEl;
if (scrollTop >= offsetTop) {
tabEl.style.position = "fixed";
tabEl.style.top = element.offsetTop + "px";
tabEl.style.left = element.offsetLeft + "px";
} else {
tabEl.style.position = "relative";
tabEl.style.top = 0;
tabEl.style.left = 0;
}
});
事件模型
常说的事件队列、EventLoop 是 JS 语言的机制
普通浏览器
概念
而这里的浏览器事件模型讲的是:浏览器处理事件传播的方式,分为三个阶段
- 捕获:先从文档根节点向下传播到目标元素
- 目标:到达目标元素,触发其绑定的事件(若有)
- 冒泡:最后再从目标元素向上传播到文档根节点
事件注册
使用 DOM Level 0 事件处理程序:
element.onclick = (event) => { // event 事件对象
// 这里的 this 指向全局对象(window)
// event 参数是通过函数参数传递的事件对象(包含事件类型、触发元素、按键等)
console.log(this); // 输出全局对象(window)
console.log(event); // 输出事件对象
// do something.....
}
// 优点:
// 1、简单粗暴的赋值
// 缺点:
// 1、this 指向 window,因为是作为普通函数在全局声明的,this 就被指向 window,无法通过 this
// 直接获取到触发的元素,需要加形参表示事件对象
// 2、只支持冒泡
使用 DOM Level 2 事件处理程序:
element.addEventListener('click', (event) => {
// 这里的 this 指向触发的元素(dom)
// event 参数是通过函数参数传递的事件对象(包含事件类型、触发元素、按键等)
console.log(this); // 输出触发的元素(dom)
console.log(event); // 输出事件对象
console.log(event.target === this); // 输出 true
// do something.....
}, false) // true:捕获时触发,false:冒泡时触发(不传默认)
// 优点:
// 1、同一个元素可以绑定多个相同事件
// 2、第三个参数可以控制事件的触发阶段:冒泡或捕获
事件解绑
使用 DOM Level 0 事件处理程序:
element.onclick = null
使用 DOM Level 2 事件处理程序:
element.removeEventListener('click', callback)
// callback 必须与 addEventListener 的一致,否则将无法解绑
// 为了避免这种请情况,一般将 callback 单独抽出来写
阻止事件传播
event.stopPropagation()
阻止当前元素上的所有本事件的传播,不在触发捕获与冒泡,但已注册未执行的事件都会按序触发
白话:我自己执行完后,只是断掉传播,别人的还是能执行
假设:一个元素上绑定了 3 个 click 事件,若在之一写了event.stopPropagation(),其他两个的 click 事件会正常触发,但都没法传播了,但其他事件(scroll)不受影响
event.stopImmediatePropagation()
阻止当前元素上的所有本事件的传播,不在触发捕获与冒泡,并且已注册未执行的事件不会再触发
白话:我自己执行完后,不仅要断掉传播,并且让别人也没法执行,做事更绝。
假设:一个元素上绑定了 3 个 click 事件,若在之一写了event.stopImmediatePropagation(),其他未执行的 click 事件不会正常触发,并且从我开始阻断传播,但其他事件(scroll)不受影响
IE 浏览器(了解即可)
概念
- IE 8 及更早版本只支持事件冒泡,不支持事件捕获。
- IE 9 及之后的版本开始支持事件冒泡和捕获
事件注册
element.attachEvent('onclick', callback),必须带上on
attachEvent 是后绑定先执行
element.attachEvent('onclick', () => {
const event = window.event; // 这样访问事件对象
}) // 默认为冒泡
事件解绑
element.detachEvent('onclick', callback),必须带上on,并且 callback 必须与 attachEvent 的一致,否则将无法解绑
阻止事件传播
event.cancelBubble = true
使用场景
- 事件代理:
-
- 基于冒泡,可将列表 item 的点击事件绑定到父级上,在父级上通过
event.target获取到触发事件的元素,然后进行对应操作。节省子元素的 item 的获取与绑定效率
- 基于冒泡,可将列表 item 的点击事件绑定到父级上,在父级上通过
面试常问
1. 区分不同的阻止
event.stopPropagation()/event.stopImmediatePropagation()/event.preventDefault()
2. 手写多浏览器兼容的事件注册、解绑、阻止等
const EventModal = {
// 事件注册
addEventListener(element, eventType, callback, useCaption) => {
if(element.addEventListener) {
// 普通浏览器
element.addEventListener(eventType, callback, useCaption)
} else if(element.attachEvent) {
// IE7/8
element.attachEvent('on' + eventType, callback)
} else {
element['on' + eventType] = callback
}
},
// 事件解绑
removeEventListener(element, eventType, callback) => {
if(element.removeEventListener) {
// 普通浏览器
element.removeEventListener(eventType, callback, useCaption)
} else if(element.detachEvent) {
// IE7/8
element.detachEvent('on' + eventType, callback)
} else {
element['on' + eventType] = null
}
},
// 事件传播阻止
stopPropagation(event) => {
if(event.stopPropagation) {
// 普通浏览器
event.stopPropagation()
} else {
// IE7/8
event.cancalBubble = true
}
},
// 默认事件阻止
preventDefault(event) => {
if(event.preventDefault) {
// 普通浏览器
event.preventDefault()
} else {
// IE7/8
event.returnValue = false
}
}
}
网络
ajax - XMLHttpRequest 对象
Async JavaScript adn XML:通过异步方式与服务器进行数据交互
基本步骤
// 1、创建对象
const xhr = new XMLHttpRequest()
// 2、创建链接(不会发送):指定类型、请求地址、是否异步(默认异步)
xhr.open('POST', 'https://www.xxx.com/api/user1')
// 3、指定回调
xhr.onreadystatechange = () => {
if(xhr.readyState == 4 && xhr.status == 200) {
// 请求成功回调
const data = JSON.parse(xhr.responseText)
}
}
// 4、发送请求
xhr.send()
进阶设置
// 超时时间
xhr.timeout = 2000 // 单位毫秒
// 超时触发事件
xhr. ontimeout = () => {
}
详细解释
xhr.readyState的状态有:
- 0:未初始化,尚未调用
open方法 - 1:启动,
open方法已调用 - 2:发送,
send方法已调用 - 3:接收,正在接收服务器数据
- 4:完成,数据接收完毕,可以在浏览器用了
xhr.statusHTTP 的状态码,服务器返回的
- 1xx:提示信息
- 2xx:成功,200(请求成功)
- 3xx:重定向,301(永久重定向),302(临时重定向)
- 4xx:客户端错误,400(请求语法有错),401(请求未授权),403(服务器拒绝服务),404(请求资源不存在)
- 5xx:服务器错误,500(服务器错误),503(服务器已不能处理请求)
自行封装
// 使用方式:
ajax({
url: 'http:www.xxx.com/api/xx',
method: 'get',
data: {
userId: 1
},
timeout: 3000,
}).then(res => {
console.log('成功:', res)
},err => {
console.log('失败:', err)
})
// 补充完下面的函数
const ajax = (options) => {
return new Promise((resolve, reject) => {
const { url, method, data, timeout } = options
const _method = method.toUpperCase()
let _data = null
let _url = url
if(_method === 'GET') {
// 参数字符串处理
const queryString = Object.keys(data||{}).map(key => {
return encodeURLComponent(key) + '=' + encodeURLComponent(data[key])
}).join('&')
_url += '?' + queryString
} else {
_data = data || null
if(_data) {
_data = JSON.stringify(_data)
xhr.setRequestHeader('Content-type', 'application/json;charset=UTF-8')
}
}
// 1、创建对象
const xhr = new XMLHttpRequest()
// 2、创建连接
xhr.open(_method, _url)
// 3、成功触发事件
xhr.onreadystatechange = () => {
if(xhr.readyState === 4) {
if(xhr.status === 200) {
const data = JSON.parse(xhr.responseText)
resolve && resolve(data)
} else {
reject && reject()
}
}
}
// 4、发送请求
xhr.send(_data)
// 5、超时时间
xhr.timeout = timeout
// 6、超时触发事件
xhr.ontimeout = () => reject && reject('超时')
// 7、失败触发事件
xhr.onerror = (err) => reject && reject(err)
})
}
浏览器缓存
访问资源时,不必重新请求。
强缓存
明确告知浏览器一定时间内使用本地缓存
Cach-Control
- max-age: 定义资源被认为是新鲜的最长时间(秒)。
- s-maxage: 覆盖 max-age,但仅适用于共享缓存(比如代理服务器)。
- public: 允许所有内容缓存,包括代理服务器。
- private: 仅允许终端用户缓存。
Expries资源过期时间,服务器返回的 HTTP 头
协商缓存
浏览器向服务器发请求验证缓存是否有效
- Last-Modified / If-Modified-Since:
-
- 服务器通过 Last-Modified 头告诉浏览器资源的最后修改时间。
- 浏览器通过 If-Modified-Since 头将最后的修改时间发送给服务器,如果时间仍然一致,服务器返回 304,表示资源未被修改,可以使用本地缓存。
- ETag / If-None-Match:
-
- 服务器通过 ETag 头生成一个唯一的标识符(通常是文件内容的哈希值)。
- 浏览器通过 If-None-Match 头将该标识符发送给服务器,如果标识符仍然一致,服务器返回 304。
面试常问
1. RESTFUL 请求的区别 - GET、POST、DELETE、PUT、OPTION
| GET | POST | DELETE | PUT | OPTION |
|---|---|---|---|---|
| 获取数据 | 提交数据 | 删除数据 | 更新数据 | 预检,询问服务器支持哪些请求类型,通常在 CORS 请求中进行预检 |
| 参数在 url 上,有长度限制 | 参数在请求体上,浏览器/服务器有大小限制 | 参数在 url 上,有长度限制 | 参数在请求体上,浏览器/服务器有大小限制 | 无参数 |
2. 跨域问题
同源策略:协议、域名、端口都一致
跨域的原因是:浏览器的同源策略导致的,非同源的不允许访问
解决办法:
- 本地环境:可配置代理
- 生产环境:服务端设置响应头,允许跨域(CORS-Cross Origin Resource Share)
补充知识
window.requestIdleCallback(callback,options)
允许在浏览器空闲时执行一些任务,以避免阻塞用户界面的流畅性。通常用于拆分大型任务(任务分片),确保不会在主线程执行太久而导致影响用户体验
用之前检查兼容性
window.requestIdleCallback(callback, options);
// callback 是一个在浏览器空闲时执行的函数。
// options 是一个配置对象,可选,包含以下属性:
// timeout:一个时间,指定在执行回调前等待的最长时间。
// 如果空闲时间小于 timeout,则会立即执行回调。
function myIdleCallback(deadline) {
// deadline 是一个对象,包含有关浏览器何时认为它将再次空闲的信息
console.log(`剩余空闲时间:${deadline.timeRemaining()}ms`);
// 执行一些任务
for (let i = 0; i < 1000; i++) {
// 一些计算密集的操作
}
// 如果任务未完成,可以请求下一次空闲回调
if (deadline.timeRemaining() > 0) {
window.requestIdleCallback(myIdleCallback);
}
}
// 启动第一次空闲回调
window.requestIdleCallback(myIdleCallback);
实际场景举例:
- 性能监测和分析:空闲时可以做
- 自动保存和同步:空闲时可以做
- 前端性能优化:空闲时预加载资源、图片等
window.requestAnimationFrame(callback)
可在浏览器下一次重绘之前执行指定的回调函数,以确保动画的平滑运行
function animate(timestamp) {
console.log(timestamp); // timestamp 回调被触发的时间戳(动画开始时的时间)
// 在这里执行动画相关的操作
// do something.......
// 继续下一帧动画
window.requestAnimationFrame(animate);
}
// 启动动画
window.requestAnimationFrame(animate);
阻止默认事件
event.preventDefault()阻止浏览器的默认行为,比如:a 标签点击后会默认跳转
envnt.returnValue = falseIE 阻止浏览器的默认行为