1-5、浏览器相关

原理知识:1-3、Promise知识与使用

浏览器下的 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的值

面试常问

  1. location 的 API
  2. 路由相关:跳转、参数、操作等
  3. 实际场景分析:
    1. 项目路由模式:
      1. history:https://www.example.com/user/profile地址不带#;需要服务器支持(因为/user/profile是直接访问服务器资源的,所以需要服务器处理通配让项目只访问主 html 文件)
      2. hash:https://www.example.com/#/user/profile地址带#;不需要服务器支持(因为#后面的不会发送给服务器,所以可以永远只访问主 html)
  4. url 的处理
    1. 如何将地址参数转为对象?
1
2
3
4
5
6
7
8
9
10
11
12
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
}
  1. 使用正则判断是否为 url?
    1. 核心是要知道location的组成,然后一步步判断即可
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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/$.?#]: 匹配任何非空白字符、"/"、"$"、"."、"?""、"#"
// .*: 匹配零个或多个任意字符
// $: 结束位置

监听地址的变化

1
2
3
4
5
6
7
8
window.addEventListener('hashchange', function(event) {
// 当哈希值变化时执行的代码
var newHash = window.location.hash; // 获取新的哈希值
console.log("Hash changed to: ", newHash);

// 根据新的哈希值进行相应的操作,如更新菜单状态、滚动到特定位置等
// ...
});

history

与浏览器的地址历史相关的,允许你在浏览器的历史记录中向前和向后导航,以及在历史记录中添加新的条目。

基础知识

属性

history.length返回历史记录的数量,number类型
history.state返回当前页面状态,object类型,值为null

方法

history.back()跳转到历史记录上一页,等价于浏览器后退一步的操作
history.forward()跳转到历史记录下一页,等价于浏览器前进一步的操作
history.go(n)跳转到历史记录的第 n 页,n 为正数表示向前,负数表示向后

1
2
history.go(-2); // 后退两页
history.go(3); // 前进三页

history.pushState(state, title, url)跳转到指定 url,并新增历史记录。不会触发页面的重新加载,history 路由模式使用它来进行页面跳转的

1
history.pushState({page: 1}, "Page 1", "/page-1");

history.replaceState(state, title, url)跳转到指定 url,不会新增历史记录,并将之前地址的历史替换为新地址。不会触发页面的重新加载

1
history.history.replaceStat({page: 2}, "Page 2", "/page-1")

监听地址的变化

1
2
3
4
5
6
7
8
9
10
11
12
// pushState/replaceState 不会触发 popstate
window.addEventListener('popstate', function(event) {
// 当浏览器历史记录变化时执行的代码

// 可以获取当前历史状态对象,如果使用了pushState或replaceState方法设置的话
var currentState = history.state;

console.log("Navigated to: ", window.location.href);

// 根据新的URL进行相应的操作,如重新渲染页面内容、更新UI等
// ...
});

包含浏览器信息的对象

基础知识

属性

navigator.userAgent返回浏览器信息,string类型,包含浏览器类型、版本、操作系统信息等

1
2
3
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类型,包含浏览器类型、版本、操作系统信息等,更精确

1
2
3
4
5
6
7
8
9
10
11
12
13
14
{
"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类型

面试常问

  1. UA 的获取与解读
    1. 获取:navigator.userAgentnavigator.userAgentData
    2. 解读:按需解读即可,也可以借鉴 platform.js
  2. 如何获取剪切板的内容?
    1. 借用 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 中获取屏幕,窗口和网页大小 - 掘金

进阶知识

获取屏幕的大小有哪些方式?

  • 使用screen.width/screen.height获取电脑屏幕的宽高,随浏览器设备的分辨率变化
  • 使用window.innerWidth/window.innerHeight获取浏览器窗口的宽高(包括滚动条),随浏览器窗口大小而变化
  • 使用document.documentElement.clientWidth/document.documentElement.clientHeight获取 html元素的宽高(不包括滚动条)
  • 使用document.body.clientWidth/document.body.clientHeight获取 body 元素的宽高(不包括滚动条)

这几个值之间的关系?

1>=2==3==4

这几个值分别受什么影响?

1受设备分辨率影响
2受浏览器窗口大小影响
3受给 html元素设置的 CSS 宽高影响
4受给 body元素设置的 CSS 宽高影响

如何监听浏览器的大小变化?

监听 resize 事件

1
2
3
4
5
6
window.addEventListener('resize', () => {
// 获取窗口当前的大小
const currentWidth = window.innerWidth;
const currentHeight = window.innerHeight;
// 其他逻辑判...
}

offsetWidth/offsetHeightclientWidth/clientHeight的关系

clientWidth/clientHeight=元素可视区域的宽/高 + padding(若有)-滚动条(若有)

offsetWidth/offsetHieght=clientWidth/clientHeight+边框(若有)+滚动条(若有)

滚动条宽高计算方式有哪些?

全局入手

window.innerWidth(浏览器宽度+滚动条(若有)) - document.documentElement.clientWidth(浏览器宽度)

元素入手-1(自己想的)

元素(无边框+无 padding+可滚动时).offsetWidth - 元素(无边框+无 padding+可滚动时).clientWidth

元素入手-2(iView 的做法)

元素(可滚动时).offsetWidth - 元素(未滚动前).offsetWidth

常见的scroll*属性

scrollWidth/scrollHeight

元素的实际宽高,非可视区域的
若无滚动条,则等于clientWidth/clientHeight
若有滚动条,则等于实际内容宽高(一般跟子元素宽高有关)+padding(若有)

scrollLeft/scrollTop

元素最左端/最上端与窗口最左端/最上端的距离,即滚动条左/上滚的距离

scrollX/scrollY===pageXOffset/pageYOffset

返回元素滚动了的距离
举例:window.scrollX===pageXOffset===document.documentElement.scrollLeft

offsetLeft/offsetTop

返回元素的左/上端与最近定位元素(默认为 body)的左/上边的距离

如何元素监听滚动事件?

一般滚动的 HTML 结构为:父元素高度小于子元素高度并且父元素允许滚动,这样才能滚动
所以监听的滚动事件是设置在父元素上面的

1
2
3
父元素.addEventListener('scroll', function(){
const { scrollTop } = this
})

如何判断元素是否在可视区域内?

计算法-父元素为任意元素

对比父元素.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 个条件时,则表明在可视窗口内:

  1. 元素.left >= 0
  2. 元素.top >= 0
  3. 元素.right <=窗口宽度(document.body.clientWidth)
  4. 元素.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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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 类型

面试常问

如何 Tab 实现吸顶效果

思路:监听父元素的滚动事件,实时判断父元素的 scrollTop 是否大于 Tab 的 offsetTop 高度,若大于则说明该 Tab 将滚出可视区域了,所以马上设置固定定位,定位的 Top/Left 需要根据父元素的 offsetLeft/offsetTop

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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 事件处理程序:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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 事件处理程序:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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 事件处理程序:

1
element.onclick = null

使用 DOM Level 2 事件处理程序:

1
2
3
4
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 是后绑定先执行

1
2
3
element.attachEvent('onclick', () => {
const event = window.event; // 这样访问事件对象
}) // 默认为冒泡

事件解绑

element.detachEvent('onclick', callback),必须带上on,并且 callback 必须与 attachEvent 的一致,否则将无法解绑

阻止事件传播

event.cancelBubble = true

使用场景

  1. 事件代理:
    1. 基于冒泡,可将列表 item 的点击事件绑定到父级上,在父级上通过event.target获取到触发事件的元素,然后进行对应操作。节省子元素的 item 的获取与绑定效率

面试常问

区分不同的阻止

event.stopPropagation()/event.stopImmediatePropagation()/event.preventDefault()

手写多浏览器兼容的事件注册、解绑、阻止等

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 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()

进阶设置

1
2
3
4
5
6
// 超时时间
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(服务器已不能处理请求)

自行封装

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
// 使用方式:
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。

面试常问

RESTFUL 请求的区别 - GET、POST、DELETE、PUT、OPTION

GET POST DELETE PUT OPTION
获取数据 提交数据 删除数据 更新数据 预检,询问服务器支持哪些请求类型,通常在 CORS 请求中进行预检
参数在 url 上,有长度限制 参数在请求体上,浏览器/服务器有大小限制 参数在 url 上,有长度限制 参数在请求体上,浏览器/服务器有大小限制 无参数

跨域问题

同源策略:协议、域名、端口都一致
跨域的原因是:浏览器的同源策略导致的,非同源的不允许访问
解决办法:

  1. 本地环境:可配置代理
  2. 生产环境:服务端设置响应头,允许跨域(CORS-Cross Origin Resource Share)

补充知识

window.requestIdleCallback(callback,options)

允许在浏览器空闲时执行一些任务,以避免阻塞用户界面的流畅性。通常用于拆分大型任务(任务分片),确保不会在主线程执行太久而导致影响用户体验
用之前检查兼容性

1
2
3
4
5
window.requestIdleCallback(callback, options);
// callback 是一个在浏览器空闲时执行的函数。
// options 是一个配置对象,可选,包含以下属性:
// timeout:一个时间,指定在执行回调前等待的最长时间。
// 如果空闲时间小于 timeout,则会立即执行回调。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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)

可在浏览器下一次重绘之前执行指定的回调函数,以确保动画的平滑运行

1
2
3
4
5
6
7
8
9
10
11
12
function animate(timestamp) {
console.log(timestamp); // timestamp 回调被触发的时间戳(动画开始时的时间)

// 在这里执行动画相关的操作
// do something.......

// 继续下一帧动画
window.requestAnimationFrame(animate);
}

// 启动动画
window.requestAnimationFrame(animate);

阻止默认事件

event.preventDefault()阻止浏览器的默认行为,比如:a 标签点击后会默认跳转
envnt.returnValue = falseIE 阻止浏览器的默认行为


1-5、浏览器相关
https://mrhzq.github.io/职业上一二事/前端面试/前端八股文/1-5、浏览器相关/
作者
黄智强
发布于
2024年1月13日
许可协议