ResizeObserver
介绍
ResizeObserver 是一个 Web API,用于监听元素的大小变化。它可以观察一个或多个元素的尺寸改变,并在尺寸变化时触发回调函数。使用 ResizeObserver,您可以监测元素的宽度、高度或者它们的任意变化,而不需要轮询或使用其他复杂的解决方案。
案例
比如在开发h5的时候需要对屏幕是横屏还是竖屏进行监听
const target = document.querySelector('body');
const observer = new ResizeObserver(entries => {
for (let entry of entries) {
const { width, height } = entry.contentRect;
if (width < height) {
// 屏幕从横屏变为竖屏
// 执行逻辑处理
} else {
// 屏幕从竖屏变为横屏
// 执行逻辑处理
}
}
});
// 启动观察
observer.observe(target);
// 停止观察
observer.unobserve(target);
getBoundingClientRect
介绍
getBoundingClientRect 是一个 DOM API,用于获取元素的大小和位置信息。它返回一个 DOMRect 对象,包含了元素的位置、宽度、高度和其他相关属性。
示例
比如图片懒加载的原理
window.addEventListener('scroll', () => {
const lazyImages = document.querySelectorAll('.lazy-image');
for (let i = 0; i < lazyImages.length; i++) {
const image = lazyImages[i];
if (isInViewport(image)) {
image.src = image.dataset.src;
image.classList.remove('lazy-image');
}
}
});
function isInViewport(element) {
const rect = element.getBoundingClientRect();
return (
rect.top < (window.innerHeight || documentElement.clientHeight)
);
}
IntersectionObserver
介绍
IntersectionObserver是一个JavaScript API,用于异步检测目标元素与其祖先或视窗之间的交叉状态。它提供了一种有效的方法来监听元素是否进入或离开视窗,或者与其祖先元素相交的程度。这对于实现懒加载、无限滚动、元素可见性等功能非常有用。
示例
实现一个简单的传送门的效果,类似于vue3的Teleport,在我们看b站视频的时候,下拉看评论的时候,会看到右下角会出现一个小的弹窗同步我们看视频的效果。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Video Portal</title>
<style>
#video {
width: 250px;
height: auto;
}
#portal {
display: none;
position: fixed;
bottom: 20px;
right: 20px;
width: 250px;
height: auto;
background-color: rgba(0, 0, 0, 0.7);
color: #fff;
padding: 20px;
text-align: center;
cursor: pointer;
}
</style>
</head>
<body>
<h1>Video Portal</h1>
<div id="v-box">
<video id="video" src="https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.webm" controls></video>
</div>
<div style="height: 1000px;width: 100%;"></div>
<div id="portal"></div>
<script>
// 创建 IntersectionObserver 实例
const observer = new IntersectionObserver((entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
hidePortal();
} else {
showPortal();
}
});
});
// 获取视频元素和传送门元素
const video = document.getElementById('video');
const portal = document.getElementById('portal');
const vBox = document.getElementById('v-box');
document.addEventListener('scroll', function () {
// 观察视频元素
observer.observe(vBox);
})
// 停止观察
// observer.unobserve(vBox)
// 显示传送门
function showPortal() {
portal.appendChild(video)
portal.style.display = 'block'
}
// 隐藏传送门
function hidePortal() {
vBox.appendChild(video)
portal.style.display = 'none'
}
</script>
</body>
</html>
requestAnimationFrame
介绍
requestAnimationFrame是一个用于优化动画和渲染性能的JavaScript方法。它是浏览器提供的一个API,用于在下一次浏览器重绘之前调用指定的回调函数。
使用requestAnimationFrame来执行动画和其他需要频繁更新的操作比使用传统的setTimeout或setInterval方法更有效,原因如下:
requestAnimationFrame会在浏览器准备好进行重绘时触发回调函数,通常是每秒60次,以匹配大多数显示器的刷新频率。这样可以避免在不必要的时候进行重绘,提高性能和电池寿命。- 当页面处于非激活状态时,
requestAnimationFrame会暂停,避免不必要的计算和功耗。
示例
实现一个可以暂停和继续加载的进度条动画,通过切换浏览器的窗口可以发现进度条会暂停,切换回去之后又重新加载,非常的智能。
<!DOCTYPE html>
<html>
<head>
<title>Loading Progress Bar Example</title>
<style>
.progress-bar-container {
width: 100%;
height: 20px;
background-color: #ddd;
}
.progress-bar {
height: 100%;
background-color: #007bff;
width: 0;
transition: width 0.5s;
}
#start-button {
margin-top: 10px;
}
</style>
</head>
<body>
<div class="progress-bar-container">
<div class="progress-bar"></div>
</div>
当前进度:<span id="number"></span>
<button id="start-button">开始加载</button>
<button id="stop-button">暂停</button>
<script>
const startButton = document.getElementById('start-button');
const progressBar = document.querySelector('.progress-bar');
const progressNum = document.getElementById('number');
const stopButton = document.getElementById('stop-button');
let tag = true
let width = 0;
startButton.addEventListener('click', () => {
function animate() {
if ( tag && width < 100) {
width++;
progressBar.style.width = width + '%';
progressNum.innerHTML = width + '%';
requestAnimationFrame(animate);
}
}
animate();
});
stopButton.addEventListener('click', () => {
if(tag){
tag = false
stopButton.innerHTML = '暂停'
} else {
tag = true
stopButton.innerHTML = '继续';
startButton.click();
}
})
</script>
</body>
</html>
Reflect & Object
1. 方法的区别:
Object对象中提供了一些方法,例如Object.defineProperty、Object.getOwnPropertyDescriptor、Object.setPrototypeOf等。这些方法用于操作对象的属性和原型链。Reflect对象提供了与Object对象中相同的方法,例如Reflect.defineProperty、Reflect.getOwnPropertyDescriptor、Reflect.setPrototypeOf等。与Object对象不同的是,Reflect的方法具有一致的函数签名。这使得在使用Reflect方法时更加统一和方便,便于函数式编程。
2. 性能的区别:
- 从性能的角度来看,
Reflect的方法通常比Object的相应方法更快。这是因为Reflect的方法不会进行类型转换,而且它们是针对内部方法的直接映射,因此可以更高效地执行操作。 - 另外,使用
Reflect方法还可以避免因为对象原型链上的方法重写而导致的性能问题。因为Reflect方法总是会调用目标对象的原型链上的对应方法,而不会像Object方法一样受到重写方法的影响。
引入 Reflect 的原因:
Reflect对象的引入是为了提供一种更统一、更直观的方式来操作对象。它使得对象操作的语义更加一致,避免了Object对象中的一些不一致性,例如Object.defineProperty的返回值和异常处理等。- 另外,
Reflect方法的引入也是为了实现元编程(metaprogramming),即在运行时操作和修改代码结构的能力。通过使用Reflect,可以更容易地实现对象的动态代理和元编程的功能。
总结起来,Reflect 提供了一组更统一、更直观的方法来操作对象,并且在性能上通常比 Object 对象中的相应方法更好。它的设计目的是为了提供一种一致的、更强大的对象操作能力,并支持元编程的需求。
对比两者用法
属性获取
const obj = { name: 'Alice' };
const value = Object.getOwnPropertyDescriptor(obj, 'name').value;
console.log(value); // 输出:Alice
const obj = { name: 'Alice' };
const value = Reflect.get(obj, 'name');
console.log(value); // 输出:Alice
属性设置
const obj = { name: 'Alice' };
Object.defineProperty(obj, 'name', { value: 'Bob' });
console.log(obj.name); // 输出:Bob
const obj = { name: 'Alice' };
Reflect.set(obj, 'name', 'Bob');
console.log(obj.name); // 输出:Bob
函数调用
function greet(name) {
console.log(`Hello, ${name}!`);
}
Reflect.apply(greet, null, ['Alice']); // 输出:Hello, Alice!
greet('Jack')
创建对象实例
class Person {
constructor(name) {
this.name = name;
}
}
const obj = new Person('Alice');
console.log(obj instanceof Person); // 输出:true
class Person {
constructor(name) {
this.name = name;
}
}
const obj = Reflect.construct(Person, ['Alice']);
console.log(obj instanceof Person); // 输出:true
检查对象是否存在
const obj = { name: 'Alice' };
const hasProperty = obj.hasOwnProperty('name');
console.log(hasProperty); // 输出:true
const obj = { name: 'Alice' };
const hasProperty = Reflect.has(obj, 'name');
console.log(hasProperty); // 输出:true
删除属性
const obj = { name: 'Alice' };
delete obj.name;
console.log(obj.name); // 输出:undefined
const obj = { name: 'Alice' };
const bool = Reflect.deleteProperty(obj, 'name');
console.log(obj.name, bool); // 输出:undefined true
defineProperty & Proxy
Object.defineProperty 和 Proxy 是 JavaScript 中用于劫持对象的两种不同机制。它们的使用方式和性能特点有一些区别。
Object.defineProperty 是一个对象方法,用于在一个对象上定义一个新的属性或修改现有属性的特性。它允许你精确地控制属性的各个方面,如可写性、可枚举性、可配置性等。
- @descriptor
- configurable 为 true 时,属性才能重新被定义,默认为 false。
- enumerable 为 true 时,该属性才能够出现在对象的枚举属性中,此时才能通过for in遍历属性。默认为 false。
- writable 为 true 时,value属性值才能被修改。默认为 false,此时value属性值为只读状态。
- value 该属性对应的初始值。可以是任何有效的 JavaScript 值(数值,对象,函数等)。默认为 undefined。
- get 一个给属性提供 getter 的方法,如果没有 getter 则为 undefined。当访问该属性时,该方法会被执行。默认为 undefined。
- set 一个给属性提供 setter 的方法,如果没有 setter 则为 undefined。当属性值修改时,触发执行该方法。该方法将接受唯一参数,即该属性新的参数值。默认为 undefined。
const obj = {};
Object.defineProperty(obj, 'name', {
get() {
console.log('Getting property: name');
return this._name;
},
set(value) {
console.log('Setting property: name =', value);
this._name = value;
}
});
console.log(obj.name); // 输出:Getting property: name,然后输出:undefined
obj.name = 'Alice'; // 输出:Setting property: name = Alice
console.log(obj.name); // 输出:Getting property: name,然后输出:Alice
Proxy 是 ES6 引入的一个特性,它允许你创建一个代理对象,可以拦截、修改和扩展目标对象的行为。通过使用 Proxy,你可以劫持对象的各种操作,如属性读取、属性设置、函数调用等。无论是只能删改查,都可以使用这个方法来对对象进行劫持然后做相应的处理
const obj = {};
const proxy = new Proxy(obj, {
get(target, property) {
console.log(`Getting property: ${property}`);
return target[property];
},
set(target, property, value) {
console.log(`Setting property: ${property} = ${value}`);
target[property] = value;
}
});
proxy.name = 'Alice'; // 输出:Setting property: name = Alice
proxy.age = 18 // Setting property: add = add
delete proxy.age // true
console.log(proxy.name); // 输出:Getting property: name,然后输出:Alice
##示例 在html中用Object.defineProperty实现一个简单的响应式劫持
总结
上面这些方法我只是浅尝辄止的说明了下,但是无论是日常开发还是性能优化都十分有用,不仅仅是了解,我们应该更多的实践让我们的开发方式更加灵活,做到有的放矢,优雅的开发起来。