页面生命周期
- window.onload
- window.onunload
不要使用 unload 事件,而是使用 pagehide 事件。所有触发 unload 事件的情况都会触发pagehide事件,更重要的是,当页面被放入 bfcache 时也会触发。
window.onbeforeunload
document.addEventListener("DOMContentLoaded", fn); DOMContentLoaded 只能用addEventListener监听
document.addEventListener('readystatechange', fn);
- 对应 document.readyState 属性,取值:loading | interactive | complete
- Loading: html 加载中
- interactive: html加载完成, dom 解析完成,可以操作dom,但是js、图像等资源还在加载,触发 DOMContentLoaded 事件
- complete: 资源完全加载,触发 window.onload
viewport | 视口
缩放改变的是 window.devicePixelRatio
布局视口:渲染后页面的实际大小,包含滚动情况下溢出的区域,页面缩放时(调整缩放级别)大小不变。
可见视口:页面可见的部分,缩放时尺寸不变的部分(地址栏、书签栏)都不算可见视口的区域。
- 可见视口大小与窗口大小和缩放级别有关
- 如何读写?
let pageWidth = window.innerWidth;
let pageHeight = window.innerHeight;
if (typeof pageWidth !== 'number') {
if (document.compatMode === 'CSS1Compat') {
pageWidth = document.documentElement.clientWidth;
pageHeight = document.documentElement.clientHeight;
} else {
// 混杂模式
pageWidth = document.body.clientWidth;
pageHeight = document.body.clientHeight;
}
}
window.screen.availHeight\Width\Left\Top
- 网页缩放时布局视口、可见视口的变化?
- https://zh.javascript.info/popup-windows#yi-dong-he-tiao-zheng-da-xiao
旧文档
布局视窗可以通过
<meta>
标签设置 viewport 来修改。每个浏览器默认都会有一个设置,例如iOS,Android这些机型设置布局视窗宽度为980px,所以PC上的网页基本能在手机上呈现,只不过元素看上去很小,一般可以通过手指动双击缩放网页。控制放大和缩小的就是视窗Viewport
window.screen.width:获取屏幕的宽度,等于系统设置里面的屏幕分辨率,和页面没有关系(包括resize 、缩放)
什么是 viewport 视口
- 可视区域,不包含滚动之外的内容
- 视口 === 布局视口 === document.documentElement.clientWidth
- layout viewport | visual viewport (布局视口)
- https://developer.mozilla.org/en-US/docs/Glossary/Viewport
window.innerWidth
: visual viewport 的宽度。页面缩放会影响该值,放大时值变小。window.outerWidth
: 整个浏览器窗口的尺寸,包含从标签页顶部的尺寸获取文档尺寸
const scrollHeight = Math.max(
document.body.scrollHeight, document.documentElement.scrollHeight,
document.body.offsetHeight, document.documentElement.offsetHeight,
document.body.clientHeight, document.documentElement.clientHeight
);
- 获取浏览器视口大小
function getViewportSize() {
// 混杂模式
if (document.compatMode === 'BackCompat') {
return {
width: document.body.clientWidth,
height: document.body.clientHeight,
};
}
return {
width: document.documentElement.clientWidth,
height: document.documentElement.clientHeight,
}
}
DOM操作
document.documentElement === document.querySelector('html')
// truedocument.documentElement === document.firstElementChild
// truedocument === ele.ownerDocument
contentDocument
:返回父级的 documentDOM元素的属性名不区分大小写
HTML 转义
const p = document.createElement('p');
p.textContent = '<b id="abc">xxxxx</b>';
console.debug(p.innerHTML); // 转义后的字符串
存储HTML元素 =>
DocumentFragment
批量将元素添加到 document 中,避免多次渲染document
所属窗口或 iframe:document.defaultView || document.parentWindow(ie)
,document.defaultView === window
读写元素的属性,遍历元素属性:
element.attributes[xx].nodeValue
attributes 会包含 HTML 中指定的 style 属性
for (let attr of $0.attributes) {
// attr.specified 判断是否是代码指定的(不是浏览器默认值)
console.debug(attr.nodeName, attr.nodeValue, attr.specified);
}
创建 HTML 文档
// 创建完整的 HTML 文档
const doc = document.implementation.createHTMLDocument('标题');
// 创建其他类型(XML、XHTML)的文档
const doctype = document.implementation.createDocumentType('html', '-//W3C//DTD XHTML 1.0 Strict//EN',
'http:、、www.w3.org/TR/xhtml1/DTD/xhtml-strict.dtd')
const doc = document.implementation.createDocument('http://w3.org/1999/xhtml', 'html', doctype);
文档模式
IE8 引入一个新的概念【文档模式】。页面的文档模式决定了可以使用哪个级别的 CSS、哪些 JS API以及如何对待文档类型。 要强制浏览器以某种【文档模式】渲染页面,可以使用 HTTP 头部信息【X-UA-Compatible】或通过等价的 <meta>
标签来设置: <meta http-equiv="X-UA-Compatible" content="IE=edge">
IE=edge
表示始终已最新的文档模式渲染页面。
NodeList & HTMLCollection
动态脚本
const js = document.createElement('script');
js.type = 'text/javascript';
// ie
js.text = `console.debug('执行动态脚本')`;
// 或者 js.appendChild(document.createTextNode(`console.debug('执行动态脚本')`));
document.body.appendChild(js)
插入HTML
如果某个 dom 元素有一个事件处理程序或引用了一个 JS 对象作为属性(dom.xx = obj),当使用 innerHTML
、outerHTML
、insertAdjacentHTML
将该元素从 dom 树中输出后,元素与事件处理程序(或 JS 对象)之间的绑定关系在内存中并没有删除,可能会出现浏览器内存占用问题。
因此,最好在删除元素前手动清除元素上的监听器、对象属性。
innerText
、outerText
、textContent
innerText
和outText
在读取文本时效果一样,设置元素的文本时outerText
会替换元素本身。innerText
和textContet
的区别:innerText
会忽略行内的<style>
和<script>
,而textContent
会返回标签中的内容。
- 自动转义 HTML 字符,避免 XSS:
dom.textContent = dom.innerHTML
注意
- outerHTML 和 innerHTML 在不同浏览器中返回的字符串可能不同;
- outerHTML 和 innerHTML 插入
<script>
标签不会执行其中的脚步;
Node
- 判断节点之间的关系
Node.contains()
Node.compareDocumentPosition(node2)
:兼容性比Node.contains
更好,判断是否是包含关系用:!!(node1.compareDocumentPosition(node2) & 16)
遍历元素
描述:遍历DOM 接口:NodeIterator、TreeWalker,对给定的起点对DOM执行深度优先遍历。
querySelecto:深度优先 ele.closest(): 查找祖先元素
范围
描述:通过【范围】可以选择文档中的一个区域,而不必考虑节点的界限。IE 以专有方式实现了自己的范围特性。 接口:document.createRange <Range>类型
命名空间
只有一种语言编写的 XML 文档情况下,命名空间没什么用,但是在一个文档中使用多种语言的情况下命名空间可以解决命名冲突(标签、属性名冲突)的问题。 比如:
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>标题</title>
</head>
<body>
<svg xmlns="http://www.w3.org/2000/svg" version=1.1>
<rect x="60" y="10" rx="10" ry="10" width="30" height="30" stroke="black" fill="transparent" stroke-width="5"/>
</svg>
</body>
</html>
<!--
// 还可以为命名空间定义一个前缀,比如定义前缀 xhtml
<xhtml:html xmlns:xhtml="http://www.w3.org/1999/xhtml">
<xhtml:head></xhtml:head>
使用命名空间指定属性?
<xhtml:body xhtml:class="xxx"></xhtml:body>
</xhtml:html>
-->
- 将一个文档中的某个接口导入到另一个文档中
document.importNode()
与cloneNode()
类似
元素尺寸
clientHeight offsetHeight scrollHeight
clientWidth、offsetWidth、scrollWidth 都是只读属性,而且每次访问这些属性时都会重新计算。
clientHeight
内容区域加内边距,不包含滚动条和 border,可用于内容的文档的可见部分的 width/heigh,inline
元素获取的 clientWidth 和 clientHeight 为 0 ;offsetWidth
包含边框和滚动条的宽度,不包含伪元素 (::before、::after)scrollHeight
包含滚动隐藏尺寸,包含伪元素,如果元素没有溢出,该值等于 clientHeight
getBoundingClientRect
计算元素相对于视口的位置
- viewport 内(以可视区域的左上角为坐标原点),盒模型左上角和右小脚的坐标(x, y)
- ie 不支持 element.getBoundingClientRect().x
const rect = ele.getBoundingClientRect();
rect.right - rect.left === rect.width === ele.offsetWidth; // 三者相等
https://developer.mozilla.org/en-US/docs/Web/API/Element/getBoundingClientRect确定元素的尺寸-MDN
- getBoundingClientRect 无法使用时计算
element.left
和element.top
// 相对于 viewport 的偏移
function getElementLeft(ele, left = 0, top = 0) {
if (!ele) return { left, top };
const { offsetLeft, offsetTop } = ele;
return getElementLeft(ele.offsetParent, left + offsetLeft, top + offsetTop)
}
元素位置
- Element.clientTop/Element.clientLeft: 左/上边框的宽度,包含滚动条宽度,不含 margin、padding。
- Element.offsetTop: 元素的边框相对于最近的定位祖先元素的边框的距离
- Element.scrollTop: 元素内容区域滚动的距离
监听元素变化
监听DOM元素变化: MutationObserver https://zh.javascript.info/mutation-observer
监听DOM元素尺寸变化: ResizeObserver 、Polyfill iframe+onresize
监听DOM元素是否可见: IntersectionObserver
DOMSubtreeModified DOMNodeInserted DOMNodeRemoved DOMNodeInsertedIntoDocument DOMNodeRemovedFromDocument DOMAttrModified DOMCharacterDataModified
滚动
window.pageYOffset
以左上角为坐标原点,页面水平、垂直滚动的距离,只读window.scrollBy()
和window.scrollTo()
window.scrollBy(x, y)
相对于当前位置滚动,window.scrollTo(x, y)
相对于文档左上角滚动document.documentElement.scrollTop
和document.body.scrollTop/Left
(safari)、ele.scrollTop
垂直滚动距离,可读可写ele.scrollIntoView
element.focus()
也会导致页面滚动并显示获得焦点的元素判断是否溢出
ele.clientWidth < ele.scrollWidth || ele.clientHeight < ele.scrollHeight
Web Worker
浏览器中 JavaScript 引擎是单线程执行的。也就是在同一时间内只能有一段代码被 JavaScript 引擎执行。如果同一时间还有其它代码需要执行的话,则这些代码需要等待 JavaScript 引擎执行完当前的代码之后才有可能获得被执行的机会。
正常情况下,JavaScript 引擎会顺序执行页面上的所有 JavaScript 代码。当页面加载完成之后,JavaScript 引擎会进入空闲状态。用户在页面上的操作会触发一些事件,这些事件的处理方法会交给 JavaScript 引擎来执行。由于 JavaScript 引擎的单线程特性,一般会在内部维护一个待处理的事件队列。每次从事件队列中选出一个事件处理方法来执行。如果在执行过程中,有新的事件发生,则新事件的处理方法只会被加入到队列中等待执行。如果当前正在执行的事件处理方法非常耗时,则队列中的其它事件处理方法可能长时间无法得到执行,造成用户界面失去响应,严重影响用户的使用体验。
主线程异步创建 web worker,主线程代码不会阻塞在这里等待 worker 线程去加载、执行指定的脚本文件,而是会立即向下继续执行后面代码。
Web Worker 自身是由 webkit(浏览器内核) 多线程实现,但它并没有为 JavaScript 语言带来多线程编程特性,我们现在仍然不能在 JavaScript 代码中创建并管理一个线程,或者主动控制线程间的同步与锁等特性。
在我看来,Web Worker 是 worker 编程模型在浏览器端 JavaScript 语言中的应用。浏览器的运行时, 同其他 GUI 程序类似,核心逻辑像是下面这个无限循环:
while(true){
// 1 更新数据和对象状态
// 2 渲染可视化UI
}
在 Web Worker 之前,JavaScript 执行引擎只能在一个单线程环境中完成这两项任务。而在其他典型 GUI 框架,如前文 Swing 库中,早已引入了 Swing Worker 来解决大量计算对 UI 渲染的阻塞问题。Web Worker 的引入,是借鉴了 worker 编程模型,给单线程的 JavaScript 带来了后台计算的能力。
通信
主线程和 web work 线程通过 postMessage、onmessage 传递数据,数据是以【拷贝】 的方式进行值传递。
- 这种方式传递数据时可以在发送方调用 postMessage 后断开其他变量对该数据源的引用,利用垃圾回收机制释放内存。
- 浏览器内部现将数据序列化成字符串,再将字符串发送给对方,对方接收后再反序列化。
当传递的数据较大时有较大的性能问题,因此 JS 允许主线程直接将数据交给 web work 而不需要拷贝。但转交后主线程就不能继续使用这些数据。该方法称为 transferable object (可转让对象)。
- 可转让对象的使用场景:传递的数据量大并且数据集中于少数变量
- ES2017 引入
SharedArrayBuffer
,允许 Worker 线程与主线程共享同一块内存。SharedArrayBuffer
的 API 与ArrayBuffer
一模一样,唯一的区别是后者无法共享数据。 - Atomics
参考
- 腾讯全端 AlloyTeam-Web Worker
- JS 工作线程实现方式-setTimeout & web worker
- setTimeout & setInterval 执行过程;
实现动画的方法
- css: transition animation
- js: setTimeout、setInterval、requestAnimationFrame
- html5: canvas ?
参考
- pixi.js-2D渲染
- 三维动画库
- 带圆角的三角形-动画
- Animation
- Web Animations
- lottie-
- Lottie 是 Airbnb 开源的一个支持 Web、Android、iOS 以及 ReactNative等平台的动画库,它可以结合 AE 和 Bodymovin 来快速实现跨平台动画
- Lottie for React
- lottie 在线预览
特殊运算符
void 0 & undefined
undefined 不是 JS 中的保留字,而是全局对象的一个属性,在 ES5 之前是可修改的(undefined = 1
),ES5 之后修改为只读属性,但是在局部作用域内依然可以覆盖 undefined 的值,如:const undefined = 1;
。
new this
使用 new
实例化对象时构造函数内部执行步骤
若执行 new Foo()
,过程如下:
- 创建新对象 o;
- 给新对象的内部属性赋值,关键是给
[[Prototype]]
属性赋值,构造原型链(如果构造函数的原型是 Object 类型,则指向构造函数的原型;不然指向 Object 对象的原型); - 执行函数 Foo,执行过程中内部 this 指向新创建的对象 o;
- 如果 Foo 内部显式返回对象类型数据,则返回该数据;否则返回新创建的对象 o。
在 Person 函数体中判断函数是否已 new
调用
this instanceof Person
- ES6 环境下:
new.target === Person
String
概念
16位 Unicode 字符 | 双字节字符
UTF-16 最多能表示 2^16=65536 个字符, 这 65536 个字符称为 basic multilingual plane (BMP) 字符集,可以用 \uxxxx
表示。
Unicode code points range from 0 to 1114111 (0x10FFFF). The first 128 Unicode code points are a direct match of the ASCII character encoding.
Unicode 字符集远大于 65536,超过的字符就用两个 UTF-16 编码单元(4字节)表示,这两个编码单元每个的取值在 0xD800 - 0xDFFF 范围。
每个 Unicode 字符由一个或两个 UTF-16 编码单元表示,又称 Unicode code point,用 \u{xxxx}
1到6位16进制数表示。
"unicode": "2764 FE0F", "icon": "❤️"
"unicode": "1F525", "icon": "🔥"
😀
- length: 2
- uncode:
[\uD83D, \uDE00]
=> [55357, 56832] - 转义表示:
\u{1f600}
a
- ASCII: 97 0x60
- 转义表示:
\u0061
或\u{61}
方法
- String.prototype.charCodeAt String.fromCharCode ==> UTF-16 code unit
- String.prototype.codePointAt String.fromCodePoint ==> Unicode code point
- btoa atob https://developer.mozilla.org/en-US/docs/Web/API/btoa
参考
Number
在内部,数字是以 64 位格式 IEEE-754 表示的,所以正好有 64 位可以存储一个数字:1个符号位、11个指数位(exponent)、52个小数位(mantissa)。
e
等于 11 位指数位表示的数,最大为 2^11-1-1=1023,最小为1
11 位指数有两个特殊情况 1)全为0:当52位小数全为0时表示正负0,否则表示非规范化浮点数 2)全为1:当52位小数全为0时表示正负无穷大,否则表示 NaN
Integers can only be represented without loss of precision in the range -2^53 + 1 to 2^53 - 1
。实际测试为 -2^53 到 2^53
最大安全整数:Number.MAX_SAFE_INTEGER = 2^53
最大整数 精度丢失 isNaN isFinite
解决精度问题
eval
参数:预期是字符串 返回值:若参数是字符串,返回值就是字符串执行的结果,否则返回值是输入参数本身
两种调用方式: 直接调用:eval()
间接调用:eval?.()
、别名、对象属性、表达式
直接调用、间接调用的区别:
是否继承上级作用域的 strict 模式
- 间接调用不继承上级作用域的 'use strict' 模式,比如在一个声明了 'use strict' 的函数中以间接的方式调用 eval,eval 中代码是在非严格模式下执行
- 直接调用会继承 strict 模式
执行环境(作用域)
- 间接调用的执行作用域为全局作用域,eval 中的 script 不能访问局部作用域中的变量
- 直接调用的作用域为?
- 直接调用的 eval 可以访问访问其他上下文中的变量【危险,可能修改局部变量】
变量声明和函数声明
- 在非 strict 模式下,对于直接调用形式,var声明的变量和函数属于上级作用域(surrounding scope),eval 中的代码可能修改外部的变量值;对于间接调用,var声明的变量和函数属于全局作用域
- 在 strict 模式下,var声明的变量和函数属于局部作用域,和调用方式无关
- let 和 const 声明的变量始终属于局部作用域
- 直接 eval 会读取和修改周围作用域中的绑定,这可能导致外部输入破坏本地数据。
样式
JS 访问 DOM 元素样式的方法有:
- JS 通过
ele.style
(CSSStyleDeclaration) 读写元素的 style 属性 window.getComputedStyle(ele)
返回当前元素的所有计算样式(CSSStyleDeclaration),IE 不支持该方法,但可以使用ele.currentStyle
达到相同的效果。
getComputedStyle(ele); // 只读
getComputedStyle(ele).getPropertyValue('font-size'); // 使用全称查询
getComputedStyle($0)['font-size'];
document.styleSheets
应用于文档的所有样式表(CSSStyleSheet),包括<style>
元素和 rel 属性为 stylesheet 的<link>
元素。
- 根据
<style>
或<link>
元素获取样式表对象(CSSStyleSheet):
element.sheet || element.styleSheet; // 兼容IE
- 读写样式表中的css属性
const rules = CSSStyleSheet.cssRules || CSSStyleSheet.rules;
rules[0].selectorText;
rules[0].cssText;
rules[0].style.width; // 与读写dom元素的 style 属性一样
动态样式
function loadStyleStr(cssStr) {
const style = document.createElement('style');
style.type = 'text/css';
try {
style.appendChild(document.createTextNode(cssStr));
} catch {
// ie
style.styleSheet.cssText = cssStr;
}
const head = document.getElementsByTagName('head')[0];
head.appendChild(style);
}
function loadStyle(url) {
const link = document.createElement('link');
link.type = 'text/css';
link.rel = 'stylesheet';
link.href = url;
const head = document.getElementsByTagName('head')[0];
head.appendChild(link);
}
window.opener
- 在页面 A 中使用
window.open(B)
B 页面的window.opener
指向 A - 当标签页 A 打开标签页 B ,并且 A、B两个 window 对象需要通信时,那么新标签页不能运行在独立的进程中。如果将
opener
设置为null
表示在单独的进程中运行新的标签页。
const bWin = window.open('B-url');
bWin.opener = null;
- 检测弹窗的窗口或页面是否被浏览器或扩展程序屏蔽
let blocked = false;
try {
if (null === window.open('xxx')) {
blocked = true;
}
} catch(err) {
}
if (blocked) {
alert('窗口被屏蔽');
}
定时任务
setTimeout
和setInterval
经过指定的时间将指定的回调添加到任务队列中,所以在回调执行的时间与设置的时间不一致。这个时间值代表了消息被实际加入到队列的最小延迟时间- 使用
setTimeout
模拟setInterval
是一种最佳模式,因为setInterval
可能会在上一个回调执行完成前启动下一次回调。
window.location
属性
- window.location.assign
- window.location.replace
- window.location.reload