!浏览器—— BOM(history vs hash、js宽高属性)、DOM(事件|监听|委托...)、 ES
浏览器运行状态下的JS
包含BOM、DOM、ECMAScript
ECMAScript - 基础逻辑、数据处理
DOM - 浏览器视窗内HTML文本
的相关操作(文档对象模型)
BOM - 对浏览器本身功能区域
的处理(浏览器对象模型)
bom
提供与浏览器窗口、历史记录和导航等浏览器功能交互的方法。
dom
代表网页的结构和内容,允许操作和修改网页元素。
日常面试:
提问两个页面之间进行交互怎么处理 ?==> 利用浏览器本身功能处理
BOM
location
location对象是JavaScript 中 window 对象的一个属性,它提供了对当前文档(网页)URL的访问和操作。
实践window.location 和 location的都能访问到
常见的属性:
location.href => “https://www.zhaowa.com/search?class=browser#comments” | “localhost:8080”
协议://主机名:端口/路径?查询参数#锚
href
: 当前window对应的超链接URL, 完整的URL
.origin
: ‘https:www.zhaowa.com’ ===> 源
.protocol
: ‘https:’ ===> 当前的协议
.host
: ‘www.zhaowa.com’ or ‘localhost:8080’ ===> 当前页面域名 /主机名(地址) (有端口带端口)
.hostname
: ‘www.zhaowa.com’ or ‘localhost’ ===>当前页面域名 /主机地址(不带端口)
.port
: ‘’ ===> 端口 一般是8080
.pathname
: ‘/search/index/…’ ===> 路径
.search
: ‘?class=browser’ ===> 查询字符串
history模式中赋值会替换掉?
后的字符,而hash模式直接插入到#字符前面
.hash
: ‘#comments’ ===> 哈希值 URL标识中的#
和后面 URL片段标识符(通常在hash路由模式中使用到,而history模式则无用)
.username:URL中的username(很多浏览器已经禁用)
.password:URL中的password(很多浏览器已经禁用)
方法:
`刷新`当前页面,就像刷新按钮一样。
location.reload()
(重新加载当前文档。如果带有参数 forceReload 为 true,则会强制从服务器重新加载文档,而不使用缓存。)
window.location.reload(true))。
刷新页面,新URL`替换`当前URL。
location.replace(url);
用新的文档替换当前文档,`不会在历史记录中`生成新的条目。
当前页面不会保存到会话历史中,新的URL保存到会话历史中。这样,用户点击回退按钮时,将不会再跳转到该页面。
window.location.replace('https://www.baidu.com')
`加载新文档`;替换pathname路径,再定向到指定路径。
location.assign(url);
加载显示指定的 URL 的内容(加载新的文档并替换当前文档,类似于用户点击链接跳转页面)旧URL可回退。
window.location.assign('https://www.baidu.com')
面试:解析路径类 | 路由管理类 | 过滤
history
history 是 HTML5 提供的新特性,允许开发者直接更改前端路由,也就是更改 url 地址而无需向后端发送 http 请求。
history 是 window.history 的简写模式,是 History 构造函数的实例化对象。
History 接口允许操作浏览器的曾经在标签页或者框架里访问的会话历史记录。
也就是说 History 里面保存着当前标签页的所有浏览页面的访问记录。
history.state
=> 存储当前页面的状态(只读)
history.pushState(state, title, url)
=> 更新到指定状态上
history.replaceState(state, title, url)
=> 替换当前的状态
state:一个对象,popState 事件触发时,state 对象会传入回调函数。如无需传参,则设置为 null 。
title:新页面的标题,但是所有浏览器目前都忽略这个值,因此可以设置为空字符串 “” 或者 null 。
url:新的网址地址,必须与当前页面处于同一个域下,浏览器的地址栏将显示这个网址。
history.pushState({page: 1}, 'title 1', '?page=1')
// URL 显示为 http://example.com/example.html?page=1
history.pushState({page: 2}, 'title 2', '?page=2');
// URL 显示为 http://example.com/example.html?page=2
history.replaceState({page: 3}, 'title 3', '?page=3');
// URL 显示为 http://example.com/example.html?page=3
history.back()
// URL 显示为 http://example.com/example.html?page=1
history.back()
// URL 显示为 http://example.com/example.html
history.go(2)
// URL 显示为 http://example.com/example.html?page=3
面试: – 路由方向的history和hash模式的对比
hash模式:
hash 模式是一种把前端路由的路径用 # 拼接在真实 url 后面的模式。
在hash模式下,本质上是修改location.href
实现的。
前端路由的改变依托于#
锚点,而锚点后边的值
我们可以通过修改location.hash
的值来修改/获取,
每一次hash值的变化都会导致触发hashchange这个事件,hash模式就是通过 hashchange 事件来监听
hash 值的改变从而渲染页面对应的组件。
在hash模式下修改页面不会刷新
,因此hash模式不会向后端发送http请求,不会导致浏览器向后端发送请求。
window.addEventListener('hashchange', function() {
console.log('hash值被修改了');
console.log('hashchage 事件被触发了');
}, false);
// hash值的改变也会触发 window.onpopstate事件,onpopstate事件在 history模式中再做介绍
window.addEventListener("popstate", () => {
console.log("popstate 事件被触发了");
})
直接修改浏览器url地址,添加 hash 值 #/about。可以看出,修改 hash 值会优先触发 popstate 事件,然后再触发 hashchange 事件。
window.onpopstate 事件
window.onpopstate 事件是用来监听浏览历史记录变化的。
调用 history.pushState() 或者 history.replaceState() 不会触发 popstate 事件。popstate 事件只会在浏览器某些行为下触发,比如点击前进、后退按钮(或者在 JavaScript 中调用 history.back()、history.forward()、 history.go() 方法)。即,在同一文档的两个历史记录条目之间导航会触发该事件。
区别:
www.a.com/a/a.html
single page application ---- SPA 单页面应用
利用以上----都可以实现前端页面不刷新----hash和history区别?
-
外观上(表面)
hash:带有#号,不美观。
history:没有#号,更美观一些。 -
实现原理(本质)
hash:通过动态锚点技术加window对象的onhashchange方法实现。
history:通过history对象的pushState实现。- 使用 JS来对 loaction.hash 进行赋值,改变 URL 的 hash值;可以使用 hashchange 事件来监听 hash 值的变化;
- HTML5 提供了 History API 来实现 URL 的变化。其中最主要的 API有以下两个:history.pushState()和 history.repalceState()。这两个API可以在不进行刷新的情况下,操作浏览器的历史纪录。唯一不同的是,前者是新增一个历史记录,后者是直接替换当前的历史记录。
-
应用场景
hash:hash模式的项目通过第三方app分享,其地址可能会被标记为不合法地址。
history:history模式的项目部署到服务器后直接刷新可能会报404,这需要后端配置 通过重定向来解决此类问题。
history使用时,需要后台指向一个标准的文件 不存在的路径时默认指向一个默认页面文件。 -
兼容性
hash 兼容IE8以上。
history 兼容 IE10 以上,HTML5 新推出的 API。
解决history模式下页面刷新404问题
在项目开发阶段,很多人使用脚手架开发,会用到history模式。在页面刷新之后也没有报404,这看似与上面说的前后矛盾。但这是因为vue脚手架内部解决了history的这一问题,如果把项目部署到服务器上,这时候脱离了脚手架环境,直接刷新就会出现404问题。
在 history 下,你可以自由的修改 path,但刷新页面时,如果服务器中没有相应的响应或者资源,则会出现404页面,因为刷新页面会发送 http 请求。也就是说,使用 history 路由模式,需要通过服务端来允许地址可访问,后端也必须配置了当前资源路径地址才行。
如果后台部署使用了 nginx(高性能的HTTP和反向代理web服务器),可以对 nginx 进行如下配置来解决页面刷新问题(摘录):
server {
listen 8080;
server_name localhost;
location / {
alias /Data/nginx/portal/;
index /index.html;
try_files $uri $uri/ /index.html;
}
}
(上原文链接:https://blog.youkuaiyun.com/datacreating/article/details/136868449)
navigator 对象 --导航信息
- 浏览器系统导航信息的集合站
navigator.userAgent => 获取用户身份属性的集合
navigator 对象包含了浏览器相关的信息,并提供了一些与设备交互的方法。
定义函数来识别不同设备:
面试:1.UA => 浏览器兼容性 | 系统行为 | 用户身份收集 2. 剪切板 | 键盘输入
screen 对象 --设备屏幕
screen 对象是浏览器中的一个内建对象,用于获取用户设备屏幕
的信息。它提供了关于屏幕尺寸、可用空间、颜色深度等的属性,常用于处理与设备显示
相关的任务,如调整布局、定位窗口、处理全屏模式等。
以下属性值不会
随着屏幕缩放改变
:
screen.width
: 返回屏幕可视宽度,以像素为单位。screen.height
: 返回屏幕可视高度,以像素为单位。screen.availWidth
: 返回屏幕可用宽度,不包括任务栏或浏览器边框等,以像素为单位。(屏幕可视宽度 减 任务栏)screen.availHeight
: 返回屏幕可用高度,同样排除任务栏或浏览器边框等,以像素为单位。(屏幕可视高度 减 任务栏)screen.colorDepth
: 返回屏幕的颜色深度,即每个像素点可以显示的颜色数量,通常为24(真彩色)。screen.pixelDepth
: 返回屏幕的实际颜色分辨率,通常与colorDepth相同,但在某些情况下可能不同。
注意事项:
- 响应式设计:利用screen对象的属性可以帮助你实现更精细的响应式布局,比如根据屏幕尺寸动态调整CSS样式。
- 兼容性和准确性:尽管大多数现代浏览器对screen对象的支持一致,但在某些老旧或特定设备上,某些属性可能返回不准确或不可预知的结果。在关键逻辑中使用时应进行适当的兼容性测试。
- 隐私考虑:获取用户的屏幕信息虽不直接涉及个人身份信息,但仍应遵守隐私保护原则,确保这些信息仅用于改善用户体验而非其他目的。
- DPI和缩放:screen.width和screen.height给出的是物理像素尺寸,而在高DPI(每英寸点数)屏幕上,逻辑像素(CSS像素)与物理像素的比例可能不同。这意味着直接使用这些属性进行布局计算时,可能需要考虑设备的像素比。
- 功能限制:与history和location对象相比,screen对象的功能较为简单且专一,主要用于获取硬件相关的屏幕信息,不能直接用于页面导航或其他高级功能。
补充——js各种宽高属性——以下几点来自转载
https://blog.youkuaiyun.com/hqianyi/article/details/144595402
浏览器视口的可视宽/高
window.innerWidth
— 浏览器可视宽
window.innerHeight
— 浏览器可视高
属性值会
随着屏幕缩放改变
浏览器外框宽高(属性值不会
随着屏幕缩放改变)
window.outerWidth
— 浏览器外框宽 = screen.availWidth
window.outerHeight
— 浏览器外框高 = screen.availHeight
el.offset [width/height]
属性值会
随着屏幕缩放改变
offsetWdith
— box的实际占位宽,border+padding+width
offsetHeight
— box的实际占位高,border+padding+height
offsetLeft
— box的外border与父级的内border的左距离
offsetTop
— box的外border与父级的内border的上距离
el.client [width/height]
属性值会
随着屏幕缩放改变
clientWdith
— box的视口宽,padding + width - 滚动条
clientHeight
— box的视口高,padding+height - 滚动条
clientTop
— box的上边框
clientLeft
— box的左边框
el.scroll [width/height]
注意!左溢出和上溢出的内容宽高不能算上(定位属性)
el.scrollWidth
— padding + width + 右侧溢出宽度
el.scrollHeight
— padding + height + 下侧溢出高度
el.scrollLeft
– 水平滚动的距离
el.scrollTop
– 垂直滚动的距离
面试——判断元素位置 & 区域大小
视窗判断:
元素尺寸:client 、offset 、边框、内容、滚动条
clientHeight / clientWidth = 元素的height / width + padding (不包含滚动条、边框、外边距)
offsetHeight / offsetWidth / offsetTop / offsetLeft => 元素相较于parent的边界框距离
- 追问:offsetLeft | left 区别 ?
- fixed => null | 具体数字
- offset 内边距距离相对 | 外边距
- offset是多级定位 left 祖先+body
相对定位:
offsetTop | offsetLeft - 相对左上边距离
scrollTop | scrollLeft - 相对左上边滚动距离
- 追问: clientHeight 和 offsetHeight ?
- offsetHeight | offsetWidth = clientHeight | clientWidth + 垂直 | 水平滚动条 + 边框 + 外边距
集合体:
el.getBoundingClientRect()
.top
.left
.bottom
.right
sticky插件 监听窗口滚动事件,当滚动距离超过元素上边距时,添加.am-sticky类,将元素的position设置为fixed,同时设置一个top值(默认为 0)。
DOM——事件模型
获取元素:dom.getElementByXxx / querySelector() / querySelectorAll()
(来源:https://blog.youkuaiyun.com/qq_44885775/article/details/124420956)
1 document.getElementById("");
2 document.getElementsByClassName();
3 document.getElementsByTagName();
var doc=document;
var box=doc.getElementById("box");
var surfaces=box.getElementsByClassName("surfaces");
var li=box.getElementsByTagName("li");
querySelector() 方法仅仅返回匹配指定选择器的第一个元素。
var node = document.querySelector("#lover"); // 获取文档中的第一个id=lover的元素
var node = document.querySelector(".lover"); // 获取文档中的第一个class="lover"的元素
var node = document.querySelector("p.lover"); // 获取class=“lover” 的第一个p元素
var node = document.querySelector("a[target]");// 获取第一个带target属性的a元素
var element = document.querySelector('.foo,.bar');//返回带有foo或者bar样式类的首个元素
document.querySelector(\"body\").style=""; // 移除style属性
document.querySelector("h2,h3").style.backgroundColor = "blue";//为文档的第一个h2元素添加背景颜色,但是,如果文档中<h3>元素位于<h2>元素之前,<h3>元素将会被设置指定的背景颜色,总结,多元素选择时,哪个先匹配就是谁咯,只有一个被选中。
querySelector()可以直接加点击事件,而querySelectorAll()不能直接加点击事件 .
var elementList = document.querySelectorAll(selectors);
// let li = document.querySelectorAll('li');
elementList 是一个静态的 NodeList 类型的对象————伪数组。
selectors 是一个由逗号连接的包含一个或多个 CSS 选择器的字符串。
document.querySelector.bind和document.querySelectorAll.bind
document.querySelectorAll遍历&函数借用:
正确的使用方式
借助Array的forEach方法进行遍历,对每个Element进行事件绑定
Array方法的使用:
这里以forEach函数为例
querySelectorAll的forEach遍历:
[].forEach.call(color_btn,function(item,index,input){
item.addEventListener('click',()=>(alert("text")));
});
Array.prototype.forEach.call(color_btn,function(item,index,input){
item.addEventListener('click',function(){
alert("text");
});
});
给元素加事件监听:addEventListener()
addEventListener()
- 还没点击,就打印出999,点击后打印出666,说明 addEventListener()函数并
不阻断代码执行
。 - 可以
绑定多个handler
,且不影响行内绑定的
var box = document.querySelector('.box');
//给元素添加一个事件监听器 (绑定事件处理程序)
box.addEventListener('click',function(){
console.log(666);
});
console.log(999);
box.addEventListener('click',function(){
console.log(6662);
});
行内解绑:
<div class="box" onclick="null">hello</div>
解绑: 不让你保存函数,不就是解绑了么
box.onclick = null; //false也可以
解绑应用:抢红包
抢红包时,一旦点击了抢红包按钮,函数正常运行,就会连接后台,会很卡,因为突然很多人在抢(点的那一刻,大家都在同一台服务器发送网络请求),此时就点不动(如果还能点动,函数又运行,更卡,所以不合理),抢红包的按钮就只按一下,运行了就不能点了。所以怎么实现函数运行(一点击),就不能再点击(运行了)?解绑就可以了。
<body>
<div class="box">抢红包</div>
<script>
var box = document.querySelector(".box");
//绑定点击事件 属性要保存函数
box.onclick = function () {
box.onclick = null;
console.log('红包已经点击,再次点击无效');
};
</script>
</body>
事件流分为三个阶段
1.捕获阶段
2.目标阶段
3.冒泡阶段。
事件的冒泡和捕获:
如果一个元素的事件被触发,那么他的所有父级元素的同名事件也会被依次触发
元素->父元素->body->html->document->window
事件冒泡一直存在,只不过以前我们没有给父级元素加同名事件
从最顶级的父元素一级一级往下找子元素触发同名事件,直到触发事件的元素为止
事件捕获,只能通过addEventListener
并且参数写true
才是事件捕获 其他都是冒泡(不是通过addEventListener添加、addEventListener
参数为false
)
使用element.addEventListener(event, function, useCapture)来绑定捕获和冒泡事件
el.addEventListener(event,function, useCapture) //默认- false
event
表示要绑定的事件类型
,function
表示事件触发时要执行
的函数,useCapture
是一个可选的参数
,用于指定事件是使用捕获还是冒泡阶段进行处理。
当
useCapture
为false
或未提供
时,事件将在冒泡阶段
进行处理;
当useCapture
为true
时,事件将在捕获阶段
进行处理,此时事件不再冒泡,只捕获不冒泡。
<div id="blueBox">
<div id="yellowBox">
<div id="greenBox"></div>
</div>
</div>
<script>
let blueBox = document.getElementById('blueBox');
let yellowBox = document.getElementById('yellowBox');
let greenBox = document.getElementById('greenBox');
blueBox.addEventListener('click', () => {
console.log('blueBox')
})
yellowBox.addEventListener('click', () => {
console.log('yellowBox')
})
greenBox.addEventListener('click', () => {
console.log('greenBox');
// event.stopPropagation()
//调用event.stopPropagation()会阻止事件继续传播到外层元素,
//所以只会输出"greenBox",而不会输出”yellowBox”和”blueBox”。
})
</script>
false 冒泡阶段处理—— 当点击绿色方块
时,输出greenBox、yellowBox、blueBox,因为绿色包含在黄色里,黄色和绿色被包含蓝色中。addEventListener不写第三个参数时,事件将在冒泡阶段进行处理,从目标元素开始,逐级向外层
元素传播,直到达到最外层的元素,也就是绿色、黄色、蓝色
。
同理,点击黄色时,输出yellowBox、blueBox。点击蓝色时,输出blueBox。
true 捕获阶段处理—— 点击绿色
(greenBox),将输出blueBox、yellowBox、greenBox,因为事件将在捕获阶段进行处理,事件捕获的顺序是从最外层
的元素开始
,逐级向内部
元素传播,直到达到目标元素,也就是蓝色、黄色、绿色
。
事件委托 (利用冒泡)
事件委托也称为事件代理(Event Delegation),事件委托是一种将事件处理程序绑定到
一个父元素
上,而不是
将事件处理程序绑定到每个子元素上
的技术。
事件委托正是利用事件流的冒泡特性
,将本来要绑定到多个元素的事件函数,委托
到了其(父)祖先元素
上。
通过事件委托,可以减少事件处理程序的数量,提高性能
和代码的可维护性
。
X示范:
监听器耗费性能----处理:代理
利用冒泡机制
—> 一个监听器
ul父元素监听
list.addEventListener('click', onclick)
//事件代理 节约内存 提升性能(不需要注销子节点)
let ul = document.getElementById("ul");
ul.addEventListener("click", (event) => {
console.log(event.target.innerHTML); // 通过 event 对象拿到必要的信息
console.log(event.type); // 获取事件类型(click)
console.log(event.target); // 获取目标元素(myButton)
})
阻止
1.如何阻止事件传播
调用该方法会阻止事件继续传播,但不会阻止其他事件处理程序被触发。也就是说,如果一个元素上绑定了多个事件处理程序,调用该方法只会阻止事件传播到更高层级的元素,而不会阻止同一元素上的其他事件处理程序被触发。
event.stopPropagation()
2.如何阻止默认事件
event.preventDefault()
// 使用:
const myList = document.getElementById('myList');
myList.addEventListener('click', (event) => {
event.preventDefault();
// 取消事件委托,不会触发链接的默认跳转行为
});
当列表项(li)内部有链接时,event.preventDefault() 方法将阻止链接的默认跳转行为
,即点击链接不会跳转到新页面。
请注意,阻止事件冒泡或取消事件委托可能会影响到事件传播的正常流程。确保在适当的时候使用这些方法,避免过度使用导致不可预料的问题。
3.相同节点上绑定了同类事件
- 不仅会阻止事件的进一步传播,还会阻止当前元素上注册的其他同类型事件处理函数的执行。
event.stopImmediatePropagation()
事件对象—event
在上面的代码中,当按钮被单击时,浏览器将自动创建一个事件对象event,并将其传递给事件监听器函数的第二个参数。我们可以通过访问event对象的属性来获取有关事件的信息。
当
事件被触发
时(如onclick
事件),浏览器会自动创建一个名为event
的JavaScript对象,这个对象存在于window对象中。window.event
(window可以省略)表示当前正在处理的事件的详细信息(如事件类型(click、mousemove等)、目标元素、鼠标位置、按键状态等)。
- event.type - 获取事件类型(例如,“click”、"mousedown"等)。
- event.target -获取目标元素(即发出事件的元素)。
- event.preventDefault() - 取消事件的默认行为。
- event.stopPropagation() - 停止事件在DOM树中的传播。
- event.clientX和event.clientY -获取鼠标光标的坐标。
- event.keyCode - 获取按下的键的键码。
面试核心
兼容相关 – 事件绑定的浏览器兼容
性能相关多子节点的事件绑定优化
JavaScrip脚本可以处理的事件
常见的事件类型
以下是一些常见的可以监听的事件类型:
- 鼠标事件
click:用户点击元素时触发。
dblclick:用户双击元素时触发。
mousedown:鼠标按钮按下时触发。
mouseup:鼠标按钮释放时触发。
mousemove:鼠标在元素上移动时触发。
mouseover:鼠标移到元素上时触发。
mouseout:鼠标移出元素时触发。 - 键盘事件
keydown:用户按下键盘上的某个键时触发。
keyup:用户释放键盘上的某个键时触发。
keypress:用户按下键盘上的字符键时触发(已废弃,推荐使用 keydown 或 input)。 - 表单事件
submit:用户提交表单时触发。
focus:元素获得焦点时触发。
blur:元素失去焦点时触发。
change:元素的值发生变化时触发(如输入框内容改变后失去焦点)。
input:元素的值发生变化时实时触发(如输入框内容改变时)。 - 触摸事件(用于移动设备)
touchstart:用户触摸屏幕时触发。
touchmove:用户在屏幕上滑动时触发。
touchend:用户结束触摸时触发。
页面加载事件
load:页面或图像加载完成时触发。
unload:页面卸载时触发(如关闭页面或跳转到其他页面)。
DOMContentLoaded:HTML 文档加载并解析完成时触发(不包括样式表、图像等)。 - 其他事件
resize:窗口大小改变时触发。
scroll:用户滚动页面时触发。
error:加载资源(如图像、脚本)失败时触发。
animationend 和 transitionend:CSS 动画或过渡结束时触发。
使用 addEventListener 监听事件
以下是一个使用 addEventListener 监听事件的示例:
- 调整浏览器窗口大小时执行
1| <body onresize="myFunction()">
- 使用 addEventListener() 方法在 window 对象上附加 “resize” 事件:
2| window.addEventListener("resize", myFunction);
在 Vue.js 中,如果你需要在组件中添加全局的事件监听器(例如
window.addEventListener),通常会在组件的生命周期钩子中进行操作。最常见的是在 mounted 钩子中添加监听器,并在
beforeDestroy 或 unmounted 钩子中移除监听器removeEventListener
,以避免内存泄漏和潜在的错误。
<template>
<div>
<!-- 你的模板内容 -->
</div>
</template>
<script>
import { onMounted, onUnmounted } from 'vue';
export default {
name: 'MyComponent',
setup() {
const handleResize = () => {
// 处理窗口大小变化的逻辑
console.log('Window resized!');
};
onMounted(() => {
// 在组件挂载后添加事件监听器
window.addEventListener('resize', handleResize);
});
onUnmounted(() => {
// 在组件销毁前移除事件监听器
window.removeEventListener('resize', handleResize);
});
return {
// 返回需要在模板中使用的响应式数据或方法
};
},
};
</script>
<style scoped>
/* 你的组件样式 */
</style>
网络 —— 协议
XMLHttpRequest
const xhr = new XMLHttpRequest()
xhr.open()xhr.send()xhr.onreadystatechange=()=>{}
xhr.status
xhr.timeout
xhr.ontimeout=()=>{}
面试:
- RESTFUL - GET POST PUT DELETE OPTION
- 跨域
- 状态码 304 => 浏览器缓存 => 强缓存 vs 协商缓存