Appearance
BOM与定时器
BOM(Browser Object Model,浏览器对象模型)提供了与浏览器交互的对象和方法。本章将介绍 window 对象、location、history、navigator、screen 等核心对象,以及定时器的使用。
window 对象
全局作用域
javascript
// window 是全局对象,所有全局变量和函数都是它的属性
var globalVar = '全局变量';
function globalFunc() {
console.log('全局函数');
}
console.log(window.globalVar); // 全局变量
window.globalFunc(); // 全局函数
// let 和 const 声明的变量不会成为 window 的属性
let letVar = 'let 变量';
console.log(window.letVar); // undefined
// 内置对象也是 window 的属性
console.log(window.document);
console.log(window.console);
console.log(window.Math);
console.log(window.Date);窗口尺寸
javascript
// 获取窗口尺寸
console.log(window.innerWidth); // 视口宽度(包含滚动条)
console.log(window.innerHeight); // 视口高度
// 获取文档尺寸
console.log(document.documentElement.clientWidth); // 视口宽度(不含滚动条)
console.log(document.documentElement.clientHeight); // 视口高度
// 获取整个窗口尺寸(包含工具栏等)
console.log(window.outerWidth);
console.log(window.outerHeight);
// 滚动位置
console.log(window.scrollX); // 水平滚动距离
console.log(window.scrollY); // 垂直滚动距离
// 或
console.log(window.pageXOffset);
console.log(window.pageYOffset);
// 滚动方法
window.scrollTo(0, 0); // 滚动到指定位置
window.scrollTo({
top: 0,
left: 0,
behavior: 'smooth' // 平滑滚动
});
window.scrollBy(0, 100); // 相对滚动
window.scrollBy({
top: 100,
behavior: 'smooth'
});窗口操作
javascript
// 打开新窗口
let newWindow = window.open('https://example.com', '_blank', 'width=800,height=600');
// 参数说明:
// 第一个参数:URL
// 第二个参数:目标(_blank, _self, _parent, _top 或窗口名称)
// 第三个参数:窗口特性字符串
// 窗口特性
window.open('https://example.com', '_blank',
'width=800,height=600,left=100,top=100,menubar=no,toolbar=no');
// 关闭窗口
// newWindow.close();
// 检查窗口是否已关闭
console.log(newWindow.closed); // false
// 移动窗口(需要用户允许)
// window.moveTo(100, 100);
// window.moveBy(50, 50);
// 调整窗口大小(需要用户允许)
// window.resizeTo(800, 600);
// window.resizeBy(100, 100);
// 打印
window.print();
// 弹窗方法
alert('警告框');
let confirmed = confirm('确认框');
let input = prompt('输入框', '默认值');location 对象
URL 信息
javascript
// location 对象包含当前 URL 的信息
console.log(location.href); // 完整 URL
console.log(location.protocol); // 协议(http: 或 https:)
console.log(location.host); // 主机名和端口
console.log(location.hostname); // 主机名
console.log(location.port); // 端口
console.log(location.pathname); // 路径
console.log(location.search); // 查询字符串(?后面的部分)
console.log(location.hash); // 片段标识符(#后面的部分)
console.log(location.origin); // 源(协议+主机+端口)
// 示例
// URL: https://example.com:8080/path/page.html?id=123#section
// href: https://example.com:8080/path/page.html?id=123#section
// protocol: https:
// host: example.com:8080
// hostname: example.com
// port: 8080
// pathname: /path/page.html
// search: ?id=123
// hash: #section
// origin: https://example.com:8080页面跳转
javascript
// 跳转到新页面(会记录历史)
location.href = 'https://example.com';
// 或
location.assign('https://example.com');
// 替换当前页面(不记录历史,无法后退)
location.replace('https://example.com');
// 重新加载页面
location.reload(); // 从缓存重新加载
location.reload(true); // 从服务器重新加载
// 修改 URL 的各个部分
location.hash = '#new-section'; // 修改片段
location.search = '?page=2'; // 修改查询字符串
location.pathname = '/new-path'; // 修改路径解析查询字符串
javascript
// 获取查询参数
let searchParams = new URLSearchParams(location.search);
// 获取单个参数
let id = searchParams.get('id');
let name = searchParams.get('name');
// 检查参数是否存在
console.log(searchParams.has('id'));
// 获取所有同名参数
let tags = searchParams.getAll('tag');
// 遍历所有参数
for (let [key, value] of searchParams) {
console.log(key, value);
}
// 添加/修改参数
searchParams.set('page', '2');
searchParams.append('tag', 'new');
// 删除参数
searchParams.delete('id');
// 转换为字符串
let newSearch = searchParams.toString();
// 解析完整 URL
let url = new URL('https://example.com/path?name=test&id=123#section');
console.log(url.hostname); // example.com
console.log(url.pathname); // /path
console.log(url.searchParams.get('name')); // testhistory 对象
历史记录导航
javascript
// history 对象管理浏览历史
// 历史记录数量
console.log(history.length);
// 前进/后退
history.back(); // 后退一步
history.forward(); // 前进一步
history.go(-1); // 后退一步
history.go(1); // 前进一步
history.go(0); // 刷新当前页面
history.go(-2); // 后退两步History API
javascript
// pushState:添加历史记录
history.pushState({ page: 1 }, '标题', '/page1');
// 参数:
// state: 状态对象
// title: 标题(目前大多数浏览器忽略)
// url: 新的 URL
// replaceState:替换当前历史记录
history.replaceState({ page: 2 }, '', '/page2');
// 监听历史记录变化
window.addEventListener('popstate', function(e) {
console.log('历史记录改变');
console.log('状态:', e.state);
});
// 示例:单页应用路由
function navigateTo(path) {
history.pushState({ path }, '', path);
renderPage(path);
}
window.addEventListener('popstate', function(e) {
if (e.state) {
renderPage(e.state.path);
}
});
function renderPage(path) {
console.log('渲染页面:', path);
}navigator 对象
浏览器信息
javascript
// navigator 对象包含浏览器信息
// 用户代理字符串
console.log(navigator.userAgent);
// 浏览器名称和版本(已废弃,不推荐使用)
console.log(navigator.appName);
console.log(navigator.appVersion);
// 平台
console.log(navigator.platform);
// 语言
console.log(navigator.language);
console.log(navigator.languages);
// 是否在线
console.log(navigator.onLine);
// Cookie 是否启用
console.log(navigator.cookieEnabled);
// 产品名称
console.log(navigator.product);
// 检测浏览器类型
function getBrowser() {
let ua = navigator.userAgent;
if (ua.includes('Chrome')) return 'Chrome';
if (ua.includes('Firefox')) return 'Firefox';
if (ua.includes('Safari')) return 'Safari';
if (ua.includes('Edge')) return 'Edge';
if (ua.includes('IE')) return 'IE';
return 'Unknown';
}
// 检测移动设备
function isMobile() {
return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
}地理位置
javascript
// 获取地理位置
navigator.geolocation.getCurrentPosition(
// 成功回调
function(position) {
console.log('纬度:', position.coords.latitude);
console.log('经度:', position.coords.longitude);
console.log('精度:', position.coords.accuracy);
console.log('海拔:', position.coords.altitude);
console.log('速度:', position.coords.speed);
},
// 错误回调
function(error) {
console.log('错误:', error.message);
},
// 选项
{
enableHighAccuracy: true, // 高精度
timeout: 5000, // 超时时间
maximumAge: 0 // 缓存时间
}
);
// 持续监听位置变化
let watchId = navigator.geolocation.watchPosition(
function(position) {
console.log('位置更新:', position.coords);
}
);
// 停止监听
navigator.geolocation.clearWatch(watchId);剪贴板 API
javascript
// 写入剪贴板
navigator.clipboard.writeText('要复制的文本')
.then(() => console.log('复制成功'))
.catch(err => console.log('复制失败:', err));
// 读取剪贴板
navigator.clipboard.readText()
.then(text => console.log('剪贴板内容:', text))
.catch(err => console.log('读取失败:', err));
// 复制按钮示例
function copyToClipboard(text) {
navigator.clipboard.writeText(text)
.then(() => alert('复制成功'))
.catch(() => {
// 降级方案
let textarea = document.createElement('textarea');
textarea.value = text;
document.body.appendChild(textarea);
textarea.select();
document.execCommand('copy');
document.body.removeChild(textarea);
alert('复制成功');
});
}其他 API
javascript
// 电池状态 API
navigator.getBattery().then(function(battery) {
console.log('电量:', battery.level * 100 + '%');
console.log('是否充电:', battery.charging);
battery.addEventListener('levelchange', function() {
console.log('电量变化:', battery.level);
});
});
// 网络连接 API
console.log(navigator.connection.effectiveType); // 4g, 3g, 2g
console.log(navigator.connection.downlink); // 下行速度
navigator.connection.addEventListener('change', function() {
console.log('网络状态变化');
});
// 分享 API
if (navigator.share) {
navigator.share({
title: '分享标题',
text: '分享内容',
url: 'https://example.com'
})
.then(() => console.log('分享成功'))
.catch(err => console.log('分享失败:', err));
}
// 在线/离线事件
window.addEventListener('online', function() {
console.log('网络已连接');
});
window.addEventListener('offline', function() {
console.log('网络已断开');
});screen 对象
javascript
// screen 对象包含屏幕信息
// 屏幕尺寸
console.log(screen.width); // 屏幕宽度
console.log(screen.height); // 屏幕高度
// 可用屏幕尺寸(排除任务栏等)
console.log(screen.availWidth);
console.log(screen.availHeight);
// 颜色深度
console.log(screen.colorDepth); // 颜色深度(位)
console.log(screen.pixelDepth); // 像素深度
// 设备像素比(高清屏)
console.log(window.devicePixelRatio); // 1 = 普通屏, 2 = Retina 屏
// 检测屏幕方向
console.log(screen.orientation.type); // portrait-primary, landscape-primary
screen.orientation.addEventListener('change', function() {
console.log('屏幕方向改变:', screen.orientation.type);
});定时器
setTimeout
javascript
// setTimeout:延迟执行一次
let timerId = setTimeout(function() {
console.log('1秒后执行');
}, 1000);
// 传递参数
setTimeout(function(name, age) {
console.log(name, age);
}, 1000, '张三', 25);
// 使用箭头函数
setTimeout(() => {
console.log('箭头函数');
}, 1000);
// 清除定时器
clearTimeout(timerId);
// 立即执行(延迟 0 毫秒)
setTimeout(() => {
console.log('下一个事件循环执行');
}, 0);
console.log('同步代码');
// 输出顺序:同步代码 -> 下一个事件循环执行setInterval
javascript
// setInterval:间隔重复执行
let count = 0;
let intervalId = setInterval(function() {
count++;
console.log('执行次数:', count);
if (count >= 5) {
clearInterval(intervalId); // 停止
}
}, 1000);
// 清除定时器
clearInterval(intervalId);
// 注意:setInterval 可能会累积
// 如果处理函数执行时间超过间隔时间,会导致多个处理函数排队
// 更安全的间隔执行方式
function safeInterval(callback, delay) {
let running = false;
setInterval(() => {
if (running) return;
running = true;
callback();
running = false;
}, delay);
}递归 setTimeout
javascript
// 使用递归 setTimeout 替代 setInterval
// 可以确保上一次执行完成后再开始下一次
function repeatWithDelay() {
console.log('执行');
setTimeout(repeatWithDelay, 1000);
}
repeatWithDelay();
// 带停止条件的递归
function repeatWithCondition(max, current = 0) {
console.log('执行:', current);
if (current < max) {
setTimeout(() => {
repeatWithCondition(max, current + 1);
}, 1000);
}
}
repeatWithCondition(5);
// 可取消的递归定时器
class RepeatingTimer {
constructor(callback, delay) {
this.callback = callback;
this.delay = delay;
this.timerId = null;
this.running = false;
}
start() {
this.running = true;
this.tick();
}
tick() {
if (!this.running) return;
this.timerId = setTimeout(() => {
this.callback();
this.tick();
}, this.delay);
}
stop() {
this.running = false;
if (this.timerId) {
clearTimeout(this.timerId);
}
}
}
let timer = new RepeatingTimer(() => console.log('tick'), 1000);
timer.start();
// timer.stop();requestAnimationFrame
javascript
// requestAnimationFrame:用于动画,与屏幕刷新同步
// 通常每秒 60 次(约 16.67ms)
function animate() {
// 更新动画
console.log('动画帧');
requestAnimationFrame(animate);
}
requestAnimationFrame(animate);
// 停止动画
let animationId;
function startAnimation() {
function frame() {
// 更新动画
animationId = requestAnimationFrame(frame);
}
animationId = requestAnimationFrame(frame);
}
function stopAnimation() {
cancelAnimationFrame(animationId);
}
// 动画示例:移动元素
let element = document.getElementById('box');
let position = 0;
function moveRight() {
position += 2;
element.style.transform = `translateX(${position}px)`;
if (position < 300) {
requestAnimationFrame(moveRight);
}
}
requestAnimationFrame(moveRight);
// 计算帧间隔时间
let lastTime = 0;
function animateWithDelta(timestamp) {
let deltaTime = timestamp - lastTime;
lastTime = timestamp;
console.log('帧间隔:', deltaTime, 'ms');
requestAnimationFrame(animateWithDelta);
}
requestAnimationFrame(animateWithDelta);定时器管理
javascript
// 封装定时器管理
class TimerManager {
constructor() {
this.timers = new Map();
this.intervals = new Map();
this.animations = new Map();
}
// 添加延迟定时器
setTimeout(name, callback, delay) {
this.clearTimeout(name);
const id = setTimeout(callback, delay);
this.timers.set(name, id);
return id;
}
// 清除延迟定时器
clearTimeout(name) {
if (this.timers.has(name)) {
clearTimeout(this.timers.get(name));
this.timers.delete(name);
}
}
// 添加间隔定时器
setInterval(name, callback, delay) {
this.clearInterval(name);
const id = setInterval(callback, delay);
this.intervals.set(name, id);
return id;
}
// 清除间隔定时器
clearInterval(name) {
if (this.intervals.has(name)) {
clearInterval(this.intervals.get(name));
this.intervals.delete(name);
}
}
// 添加动画帧
requestAnimationFrame(name, callback) {
this.cancelAnimationFrame(name);
const animate = () => {
callback();
const id = requestAnimationFrame(animate);
this.animations.set(name, id);
};
const id = requestAnimationFrame(animate);
this.animations.set(name, id);
return id;
}
// 取消动画帧
cancelAnimationFrame(name) {
if (this.animations.has(name)) {
cancelAnimationFrame(this.animations.get(name));
this.animations.delete(name);
}
}
// 清除所有定时器
clearAll() {
this.timers.forEach(id => clearTimeout(id));
this.intervals.forEach(id => clearInterval(id));
this.animations.forEach(id => cancelAnimationFrame(id));
this.timers.clear();
this.intervals.clear();
this.animations.clear();
}
}
// 使用
const timerManager = new TimerManager();
timerManager.setInterval('tick', () => {
console.log('tick');
}, 1000);
timerManager.setTimeout('delay', () => {
console.log('延迟执行');
}, 3000);
// 清除特定定时器
timerManager.clearInterval('tick');
// 清除所有
timerManager.clearAll();实战案例
倒计时
javascript
function countdown(seconds, callback) {
let remaining = seconds;
let timer = setInterval(() => {
callback(remaining);
if (remaining <= 0) {
clearInterval(timer);
return;
}
remaining--;
}, 1000);
return timer;
}
// 使用
countdown(10, (remaining) => {
if (remaining > 0) {
console.log(`剩余 ${remaining} 秒`);
} else {
console.log('倒计时结束!');
}
});防抖函数
javascript
function debounce(func, delay) {
let timer = null;
return function(...args) {
clearTimeout(timer);
timer = setTimeout(() => {
func.apply(this, args);
}, delay);
};
}
// 使用
let handleResize = debounce(() => {
console.log('窗口大小改变');
}, 300);
window.addEventListener('resize', handleResize);节流函数
javascript
function throttle(func, delay) {
let lastTime = 0;
return function(...args) {
let now = Date.now();
if (now - lastTime >= delay) {
lastTime = now;
func.apply(this, args);
}
};
}
// 使用
let handleScroll = throttle(() => {
console.log('滚动位置:', window.scrollY);
}, 200);
window.addEventListener('scroll', handleScroll);小结
本章学习了 BOM 和定时器的核心知识:
- window 对象:全局作用域、窗口尺寸、窗口操作
- location 对象:URL 信息、页面跳转、查询字符串
- history 对象:历史记录导航、History API
- navigator 对象:浏览器信息、地理位置、剪贴板
- screen 对象:屏幕信息
- 定时器:setTimeout、setInterval、requestAnimationFrame
下一章我们将学习 ES6+新特性,了解现代 JavaScript 的新功能。
