《JavaScript全栈教程》09:浏览器与DOM操作——从window对象到Canvas绘图全解
本系列前几期我们深入了JavaScript语言核心,本期将带您进入浏览器环境,掌握如何用JS操作浏览器窗口、DOM树、表单、文件上传,并理解AJAX、Promise、async异步编程,最后还会解锁Canvas绘图。学完本篇,您将能独立编写交互丰富、数据驱动的现代Web页面。
本篇核心收获
- 掌握浏览器核心对象:window、navigator、screen、location、document、history的属性和方法
- 精通DOM节点的增删改查,用原生JS实现动态页面更新
- 学会获取和设置表单控件的值,实现表单验证与安全提交
- 理解文件上传与File API,实现图片本地预览
- 掌握AJAX同源策略与CORS跨域原理,使用Fetch发送请求
- 深入理解Promise与async/await,写出优雅的异步代码
- 入门Canvas 2D绘图,完成天气预报图表绘制
一、浏览器环境与全局对象
JavaScript诞生之初就是为了在浏览器中运行,因此浏览器是JS开发者必须关注的平台。目前主流浏览器包括:
- IE 6~11:国内使用广泛,对W3C标准支持较差。IE10开始支持ES6。
- Chrome:Google出品,基于WebKit内核,内置强大的V8引擎。自动升级,最新版已支持ES6。
- Safari:Apple Mac系统自带,基于WebKit。OS X 10.7 Lion起支持ES6,现版本9.x早已支持。
- Firefox:Mozilla自研Gecko内核和OdinMonkey引擎。现已采用自动升级,保持最新。
- 移动设备:iOS和Android分别使用Safari和Chrome,均为WebKit核心,对HTML5和ES6支持良好。
其他浏览器如Opera市场份额太小可忽略。国产浏览器(如某某安全浏览器)大多只是壳,内核实际调用IE或双核(IE+Webkit)。
不同浏览器对JS支持的差异主要体现在API接口(如AJAX、File接口)以及对ES6特性的支持度。编写JS时必须考虑这些差异,尽量让同一份代码运行在不同浏览器中。
1.1 window对象
window 对象既充当全局作用域,又表示浏览器窗口。它提供以下常用属性:
innerWidth/innerHeight:获取浏览器窗口内部的净宽高(除去菜单栏、工具栏、边框等占位元素后用于显示网页的区域)。outerWidth/outerHeight:获取浏览器窗口的整个宽高(包括菜单栏、边框等)。
// 可以调整浏览器窗口大小试试:
console.log('window inner size: ' + window.innerWidth + ' x ' + window.innerHeight);兼容性:IE <= 8 不支持
innerWidth/innerHeight。
1.2 navigator对象
navigator 对象表示浏览器的信息,常用属性:
| 属性 | 说明 |
|---|---|
navigator.appName | 浏览器名称 |
navigator.appVersion | 浏览器版本 |
navigator.language | 浏览器设置的语言 |
navigator.platform | 操作系统类型 |
navigator.userAgent | 浏览器设定的User-Agent字符串 |
console.log('appName = ' + navigator.appName);
console.log('appVersion = ' + navigator.appVersion);
console.log('language = ' + navigator.language);
console.log('platform = ' + navigator.platform);
console.log('userAgent = ' + navigator.userAgent);⚠️ 避坑指南:
navigator的信息可以被用户轻易修改,因此读取的值不一定准确。切勿用if判断浏览器版本来编写差异化代码。正确方法是利用JavaScript对不存在属性返回undefined的特性,用短路运算符||:let width = window.innerWidth || document.body.clientWidth;
1.3 screen对象
screen 对象表示屏幕的信息,常用属性:
screen.width:屏幕宽度(像素)screen.height:屏幕高度(像素)screen.colorDepth:颜色位数(如8、16、24)
console.log('Screen size = ' + screen.width + ' x ' + screen.height);1.4 location对象
location 对象表示当前页面的URL信息。例如一个完整的URL:http://www.example.com:8080/path/index.html?a=1&b=2#TOP
可以通过 location.href 获取完整URL,也可分别获取各部分:
location.protocol; // 'http:'
location.host; // 'www.example.com'
location.port; // '8080'
location.pathname; // '/path/index.html'
location.search; // '?a=1&b=2'
location.hash; // '#TOP'要加载一个新页面,调用 location.assign();重新加载当前页面,调用 location.reload():
if (confirm('重新加载当前页' + location.href + '?')) {
location.reload();
} else {
location.assign('/'); // 设置一个新的URL地址
}1.5 document对象
document 对象表示当前页面,是整个DOM树的根节点。常用操作:
- 修改页面标题:
document.title = '努力学习JavaScript!'; - 查找DOM节点:
getElementById()、getElementsByTagName()、getElementsByClassName() - 读取Cookie:
document.cookie
示例:查找DOM节点
<dl id="drink-menu" style="border:solid 1px #ccc;padding:6px;">
<dt>摩卡</dt>
<dd>热摩卡咖啡</dd>
<dt>酸奶</dt>
<dd>北京老酸奶</dd>
<dt>果汁</dt>
<dd>鲜榨苹果汁</dd>
</dl>let menu = document.getElementById('drink-menu');
let drinks = document.getElementsByTagName('dt');
let s = '提供的饮料有:';
for (let i = 0; i < drinks.length; i++) {
s = s + drinks[i].innerHTML + ',';
}
console.log(s);Cookie与安全
Cookie是服务器发送的key-value标识符,用于区分用户。JavaScript可通过 document.cookie 读取当前页面的Cookie:
document.cookie; // 'v=123; remember=true; prefer=zh'⚠️ 安全隐患:页面中引入第三方JS(如
http://www.foo.com/jquery.js)时,恶意代码可读取本页Cookie,窃取用户登录信息。解决方案:服务器设置Cookie时使用
httpOnly选项,带此标记的Cookie将不能被JavaScript读取(主流浏览器均支持,IE6 SP1开始支持)。服务器端应始终坚持使用httpOnly。
1.6 history对象
history 对象保存浏览器的历史记录,提供 back() 和 forward() 方法,相当于点击浏览器的后退/前进按钮。
⚠️ 避坑指南:新手常在登录成功后调用
history.back()试图回到登录前页面,这是一种错误做法。对于现代AJAX页面,应使用history.pushState()方法:
// AJAX完成后
let state = 'any-data';
let url = '/ajax.html#signin';
history.pushState(state, '', url);当用户点击“后退”时,浏览器不会刷新页面,而是触发 popstate 事件,可由JavaScript捕获并更新页面部分内容。
参考文档:MDN上的Window、Navigator、Screen、Location、Document、History对象。
二、操作DOM
DOM(文档对象模型)是一棵树形结构。操作DOM本质上就是四种操作:更新、遍历、添加、删除。
2.1 获取DOM节点
最常用的方法:
| 方法 | 说明 | 返回值 |
|---|---|---|
document.getElementById(id) | 根据ID获取 | 单个节点 |
document.getElementsByTagName(tag) | 根据标签名获取 | 节点集合 |
document.getElementsByClassName(class) | 根据类名获取 | 节点集合 |
document.querySelector(selector) | 根据CSS选择器获取第一个 | 单个节点 |
document.querySelectorAll(selector) | 根据CSS选择器获取所有 | 节点集合 |
示例:
// 返回ID为'test'的节点:
let test = document.getElementById('test');
// 先定位ID为'test-table'的节点,再返回其内部所有tr节点:
let trs = document.getElementById('test-table').getElementsByTagName('tr');
// 先定位ID为'test-div'的节点,再返回其内部所有class包含red的节点:
let reds = document.getElementById('test-div').getElementsByClassName('red');
// 获取节点test下的所有直属子节点:
let cs = test.children;
// 获取节点test下第一个、最后一个子节点:
let first = test.firstElementChild;
let last = test.lastElementChild;
// 通过querySelector获取ID为q1的节点:
let q1 = document.querySelector('#q1');
// 通过querySelectorAll获取q1节点内的符合条件的所有节点:
let ps = q1.querySelectorAll('div.highlighted > p');兼容性:低版本IE<8不支持
querySelector和querySelectorAll;IE8仅有限支持。
注意:DOM节点严格来说是指Element,但实际还有Comment、CDATA等类型,我们最关心的是Element。根节点Document已自动绑定为全局变量
document。
2.2 练习:选择指定条件的节点
给定HTML结构:
<div id="test-div">
<div class="c-red">
<p id="test-p">JavaScript</p>
<p>Java</p>
</div>
<div class="c-red c-green">
<p>Python</p>
<p>Ruby</p>
<p>Swift</p>
</div>
<div class="c-green">
<p>Scheme</p>
<p> Haskell</p>
</div>
</div>请选择出指定条件的节点:
// 选择<p>JavaScript</p>:
let js = document.getElementById('test-p');
// 选择<p>Python</p>,<p>Ruby</p>,<p>Swift</p>:
let arr = document.querySelectorAll('.c-red.c-green p');
// 选择<p>Haskell</p>:
let haskell = document.querySelector('.c-green p:last-of-type');
// 测试(略)2.3 更新DOM
方法一:修改innerHTML
不仅可以修改文本内容,还可以直接插入HTML片段,重构子树。
let p = document.getElementById('p-id');
// 设置文本为abc:
p.innerHTML = 'ABC'; // <p id="p-id">ABC</p>
// 设置HTML:
p.innerHTML = 'ABC <span style="color:red">RED</span> XYZ';⚠️ 安全提醒:如果写入的字符串来自网络,要注意对字符编码,避免XSS攻击。
方法二:修改 innerText 或textContent
自动对字符串进行HTML编码,无法设置任何HTML标签。
let p = document.getElementById('p-id');
p.innerText = "<script>alert(\"Hi\")</script>";
// 实际效果:<p id="p-id"><script>alert("Hi")</script></p>
innerText不返回隐藏元素的文本,而textContent返回所有文本。IE<9不支持textContent。
修改CSS:通过节点的 style 属性,使用驼峰命名法。
let p = document.getElementById('p-id');
p.style.color = "#ff0000";
p.style.fontSize = '20px';
p.style.paddingTop = '2em';2.4 练习:更新DOM节点
给定HTML结构:
<div id="test-div">
<p id="test-js">javascript</p>
<p>Java</p>
</div>获取节点并修改:
let js = document.getElementById('test-js');
js.innerText = 'JavaScript';
js.style.color = '#ff0000';
js.style.fontWeight = 'bold';2.5 插入DOM
情况一:目标节点为空
直接使用 innerHTML = '<span>child</span>'。
情况二:目标节点非空
appendChild:将子节点添加到父节点的最后一个子节点之后。insertBefore:将子节点插入到指定子节点之前。
示例:appendChild
<!-- 初始结构 -->
<p id="js">JavaScript</p>
<div id="list">
<p id="java">Java</p>
<p id="python">Python</p>
<p id="scheme">Scheme</p>
</div>let js = document.getElementById('js'),
list = document.getElementById('list');
list.appendChild(js);注意:如果插入的节点已存在于文档树,会先从原位置删除,再插入到新位置。
动态创建新节点:
let list = document.getElementById('list'),
haskell = document.createElement('p');
haskell.id = 'haskell';
haskell.innerHTML = 'Haskell';
list.appendChild(haskell);实用场景:动态添加CSS样式
let d = document.createElement('style');
d.setAttribute('type', 'text/css');
d.innerHTML = 'p { color: red }';
document.getElementsByTagName['head'](0).appendChild(d);示例:insertBefore
<div id="list">
<p id="java">Java</p>
<p id="python">Python</p>
<p id="scheme">Scheme</p>
</div>把Haskell插入到Python之前:
let list = document.getElementById('list'),
ref = document.getElementById('python'),
haskell = document.createElement('p');
haskell.id = 'haskell';
haskell.innerHTML = 'Haskell';
list.insertBefore(haskell, ref);遍历子节点:通过 children 属性迭代。
let i, c, list = document.getElementById('list');
for (i = 0; i < list.children.length; i++) {
c = list.children[i];
}2.6 练习:按字符串顺序排序DOM节点
给定HTML结构:
<ol id="test-list">
<li class="lang">Scheme</li>
<li class="lang">JavaScript</li>
<li class="lang">Python</li>
<li class="lang">Ruby</li>
<li class="lang">Haskell</li>
</ol>按字符串顺序重新排序:
let ol = document.getElementById('test-list');
let items = Array.from(ol.children);
items.sort((a, b) => a.innerText.localeCompare(b.innerText));
items.forEach(item => ol.appendChild(item));2.7 删除DOM
步骤:1)获得待删除节点;2)获得其父节点;3)调用父节点的 removeChild。
let self = document.getElementById('to-be-removed');
let parent = self.parentElement;
let removed = parent.removeChild(self);
removed === self; // true删除后的节点虽不在文档树中,但仍存在于内存中,可随时重新添加。
⚠️ 易错点:children 属性是只读且实时更新的。遍历并删除多个节点时,注意索引会动态变化。
<div id="parent">
<p>First</p>
<p>Second</p>
</div>错误示例:
let parent = document.getElementById('parent');
parent.removeChild(parent.children[0]);
parent.removeChild(parent.children[1]); // 报错!因为删除第一个后children长度变为1正确做法:先收集要删除的节点,再统一删除。
2.8 练习:删除非Web技术节点
给定HTML结构:
<ul id="test-list">
<li>JavaScript</li>
<li>Swift</li>
<li>HTML</li>
<li>ANSI C</li>
<li>CSS</li>
<li>DirectX</li>
</ul>删除与Web开发无关的节点(只保留JavaScript、HTML、CSS):
let list = document.getElementById('test-list');
let itemsToRemove = [];
for (let i = 0; i < list.children.length; i++) {
let item = list.children[i];
if (item.innerText !== 'JavaScript' && item.innerText !== 'HTML' && item.innerText !== 'CSS') {
itemsToRemove.push(item);
}
}
itemsToRemove.forEach(item => list.removeChild(item));三、操作表单
表单本身也是DOM树,但表单控件可接收用户输入。常用表单控件:
| 控件类型 | HTML标签 | 说明 |
|---|---|---|
| 文本框 | <input type="text"> | 输入文本 |
| 口令框 | <input type="password"> | 输入密码 |
| 单选框 | <input type="radio"> | 选择一项 |
| 复选框 | <input type="checkbox"> | 选择多项 |
| 下拉框 | <select> | 选择一项 |
| 隐藏文本 | <input type="hidden"> | 用户不可见,随表单提交 |
3.1 获取值
对于 text、password、hidden、select,使用 value 属性:
let input = document.getElementById('email');
input.value; // '用户输入的值'对于单选框和复选框,使用 checked 属性判断是否勾选:
let mon = document.getElementById('monday');
let tue = document.getElementById('tuesday');
mon.value; // '1'
tue.value; // '2'
mon.checked; // true或false
tue.checked; // true或false3.2 设置值
同样,对于文本框等直接设置 value:
let input = document.getElementById('email');
input.value = 'test@example.com';对于单选框/复选框,设置 checked = true/false。
3.3 HTML5控件
HTML5新增的常用控件:date、datetime-local、color 等。
<input type="date" value="2021-12-02">
<input type="datetime-local" value="2021-12-02T20:21:12">
<input type="color" value="#ff0000">不支持HTML5的浏览器会将这些控件当作
type="text"显示。支持HTML5的浏览器会保证value为有效格式(如YYYY-MM-DD)。
3.4 提交表单
方式一:调用 submit() 方法
<form id="test-form">
<input type="text" name="test">
<button type="button" onclick="doSubmitForm()">Submit</button>
</form>
<script>
function doSubmitForm() {
let form = document.getElementById('test-form');
// 可在此修改表单输入...
form.submit();
}
</script>这种方式扰乱了浏览器对form的正常提交(如回车键提交)。
方式二:响应 onsubmit 事件(推荐)
<form id="test-form" onsubmit="return checkForm()">
<input type="text" name="test">
<button type="submit">Submit</button>
</form>
<script>
function checkForm() {
let form = document.getElementById('test-form');
// 可在此修改表单输入...
return true; // 返回false则阻止提交
}
</script>安全传输示例(MD5加密口令):
为避免明文传输口令,使用隐藏字段。
<form id="login-form" method="post" onsubmit="return checkForm()">
<input type="text" id="username" name="username">
<input type="password" id="input-password">
<input type="hidden" id="md5-password" name="password">
<button type="submit">Submit</button>
</form>
<script>
function checkForm() {
let input_pwd = document.getElementById('input-password');
let md5_pwd = document.getElementById('md5-password');
md5_pwd.value = toMD5(input_pwd.value); // 假设toMD5已实现
return true;
}
</script>关键:只有
name属性的输入框数据才会被提交。用户输入的口令框没有name属性,因此不会发送明文。
3.5 练习:注册表单验证
实现以下验证规则:
- 用户名必须是3-10位英文字母或数字
- 口令必须是6-20位
- 两次输入口令必须一致
<form id="test-register" action="#" target="_blank" onsubmit="return checkRegisterForm()">
<p id="test-error" style="color:red"></p>
<p>用户名: <input type="text" id="username" name="username"></p>
<p>口令: <input type="password" id="password" name="password"></p>
<p>重复口令: <input type="password" id="password-2"></p>
<p><button type="submit">提交</button> <button type="reset">重置</button></p>
</form>
<script>
window.checkRegisterForm = function () {
let username = document.getElementById('username').value;
let password = document.getElementById('password').value;
let password2 = document.getElementById('password-2').value;
let error = document.getElementById('test-error');
error.innerHTML = '';
if (!/^[a-zA-Z0-9]{3,10}$/.test(username)) {
error.innerHTML = '用户名必须是3-10位英文字母或数字';
return false;
}
if (password.length < 6 || password.length > 20) {
error.innerHTML = '口令必须是6-20位';
return false;
}
if (password !== password2) {
error.innerHTML = '两次输入口令必须一致';
return false;
}
return true;
};
</script>四、操作文件
文件上传的唯一控件是 <input type="file">。
注意:当表单包含文件上传时,必须指定 enctype="multipart/form-data" 和 method="post"。
出于安全,JavaScript无法对 value 赋值,也无法获取真实路径(显示为 C:\fakepath\...)。可以在提交前检查文件扩展名:
let f = document.getElementById('test-file-upload');
let filename = f.value;
if (!filename || !(filename.endsWith('.jpg') || filename.endsWith('.png') || filename.endsWith('.gif'))) {
alert('只能上传图片文件');
return false;
}4.1 File API 与图片预览
HTML5提供了 File 和 FileReader 对象,可读取文件内容。
let fileInput = document.getElementById('test-image-file'),
info = document.getElementById('test-file-info'),
preview = document.getElementById('test-image-preview');
fileInput.addEventListener('change', function () {
preview.style.backgroundImage = '';
if (!fileInput.value) {
info.innerHTML = '没有选择文件';
return;
}
let file = fileInput.files[0];
info.innerHTML = '文件: ' + file.name + '<br>' +
'大小: ' + file.size + '<br>' +
'修改: ' + file.lastModified;
if (file.type !== 'image/jpeg' && file.type !== 'image/png' && file.type !== 'image/gif') {
alert('不是有效的图片文件!');
return;
}
let reader = new FileReader();
reader.onload = function (e) {
let data = e.target.result; // data:image/jpeg;base64,...
preview.style.backgroundImage = 'url(' + data + ')';
};
reader.readAsDataURL(file);
});以DataURL形式读取后,字符串形如
data:image/jpeg;base64,/9j/4AAQSk...,可用于设置图像。如需服务器处理,可将base64,之后的字符发送给服务器解码。
4.2 回调与异步
JavaScript是单线程执行模式,多任务通过异步回调实现。例如 reader.readAsDataURL(file) 发起异步读取,必须预先设置 onload 回调函数,文件读取完成后自动调用。
五、AJAX
AJAX(Asynchronous JavaScript and XML)即用JavaScript执行异步网络请求,让用户停留在当前页面同时刷新数据。最早大规模使用是Gmail。
5.1 原生 Fetch API(现代推荐)
现代浏览器支持Fetch API,以Promise方式提供:
async function get(url) {
let resp = await fetch(url);
let result = await resp.text();
return result;
}
// 发送异步请求:
get('/content.html').then(data => {
let textarea = document.getElementById('fetch-response-text');
textarea.value = data;
});更详细用法参考MDN文档。
5.2 同源策略与安全限制
同源策略:AJAX请求的URL必须与当前页面协议、域名、端口号完全一致,否则请求被浏览器阻止。
跨域解决方案
- Flash插件:麻烦且逐渐淘汰。
- 代理服务器:在同源域名下架设代理,转发请求到外域。
- JSONP:只支持GET请求,利用
<script>可跨域引用JS的特性,返回函数调用如foo('data')。 - CORS(推荐):HTML5规范,通过响应头
Access-Control-Allow-Origin授权跨域。
5.3 CORS详解
CORS全称Cross-Origin Resource Sharing。当浏览器向外域发起请求时,检查响应头中的 Access-Control-Allow-Origin 是否包含本域(或为 *),若包含则请求成功,否则失败。
简单请求:满足以下条件:
- 方法为GET、HEAD、POST
- POST的Content-Type为
application/x-www-form-urlencoded、multipart/form-data或text/plain - 无自定义头

对于PUT、DELETE以及 application/json 的POST请求,浏览器会先发送一个 OPTIONS预检请求(preflighted),询问服务器是否允许。服务器必须正确响应 Access-Control-Allow-Methods 等头信息。
无论是否使用CORS,都要理解其原理。引用第三方CDN字体时,若对方未设置正确的CORS头,浏览器也无法加载字体。
六、Promise
JavaScript是单线程,所有网络操作、浏览器事件都必须异步执行。传统回调写法容易形成“回调地狱”。Promise对象将“承诺将来会执行”的动作优雅地封装起来。
6.1 Promise基础
最简单的Promise例子:生成0-2随机数,小于1则成功,否则失败。
function test(resolve, reject) {
let timeOut = Math.random() * 2;
console.log('set timeout to: ' + timeOut + ' seconds');
setTimeout(function () {
if (timeOut < 1) {
console.log('call resolve()...');
resolve('200 OK');
} else {
console.log('call reject()...');
reject('timeout in ' + timeOut + ' seconds');
}
}, timeOut * 1000);
}
let p1 = new Promise(test);
p1.then(function (result) {
console.log('成功:' + result);
}).catch(function (reason) {
console.log('失败:' + reason);
});链式简化:
new Promise(test)
.then(function (result) {
console.log('成功:' + result);
})
.catch(function (reason) {
console.log('失败:' + reason);
});执行效果:

Promise最大的好处:把执行代码和处理结果的代码清晰分离。
6.2 串行执行多个异步任务
job1.then(job2).then(job3).catch(handleError);模拟串行计算:
// 0.5秒后返回 input*input
function multiply(input) {
return new Promise(function (resolve, reject) {
setTimeout(resolve, 500, input * input);
});
}
// 0.5秒后返回 input+input
function add(input) {
return new Promise(function (resolve, reject) {
setTimeout(resolve, 500, input + input);
});
}
let p = new Promise(function (resolve, reject) {
resolve(123);
});
p.then(multiply)
.then(add)
.then(multiply)
.then(add)
.then(function (result) {
console.log('Got value: ' + result);
});6.3 并行执行:Promise.all / Promise.race
Promise.all:等待所有任务完成,返回结果数组。
let p1 = new Promise(resolve => setTimeout(resolve, 500, 'P1'));
let p2 = new Promise(resolve => setTimeout(resolve, 600, 'P2'));
Promise.all([p1, p2]).then(results => console.log(results)); // ['P1', 'P2']Promise.race:返回最先完成的任务结果,其余丢弃。
let p1 = new Promise(resolve => setTimeout(resolve, 500, 'P1'));
let p2 = new Promise(resolve => setTimeout(resolve, 600, 'P2'));
Promise.race([p1, p2]).then(result => console.log(result)); // 'P1'七、async函数
async / await 让异步代码写起来像同步代码,可读性大大提高。
7.1 基本用法
async function get(url) {
let resp = await fetch(url);
let result = await resp.json();
return result;
}async function定义一个异步函数,等价于返回Promise。await只能在async function内部使用,它自动实现异步调用并等待结果。
错误处理:用传统的 try...catch。
async function get(url) {
try {
let resp = await fetch(url);
let result = await resp.json();
return result;
} catch (e) {
// 处理错误
}
}7.2 在普通函数中调用async函数
普通函数不能直接使用 await,但可以获取返回的Promise对象,再调用 .then()。
async function get(url) {
let resp = await fetch(url);
return resp.text();
}
function doGet() {
let promise = get('/content.html');
promise.then(data => {
document.getElementById('test-response-text').value = data;
});
}
doGet();结论:定义异步任务用
async function更简单;调用异步任务用await更简单;捕获错误用try...catch。只要浏览器支持,完全可以用async简洁实现异步操作。
八、Canvas绘图
Canvas是HTML5新增组件,可用JS绘制图形、动画等,替代Flash。
8.1 创建与检测
<canvas id="test-canvas" width="300" height="200">
<p>你的浏览器不支持Canvas</p>
</canvas>let canvas = document.getElementById('test-canvas');
if (canvas.getContext) {
console.log('你的浏览器支持Canvas!');
let ctx = canvas.getContext('2d'); // 获取2D上下文
// 如需3D: canvas.getContext("webgl");
} else {
console.log('你的浏览器不支持Canvas!');
}8.2 坐标系统
Canvas坐标以左上角为原点,水平向右为X轴正方向,垂直向下为Y轴正方向,单位是像素。

8.3 绘制形状
let canvas = document.getElementById('test-shape-canvas'),
ctx = canvas.getContext('2d');
ctx.clearRect(0, 0, 200, 200); // 擦除矩形区域(变透明)
ctx.fillStyle = '#dddddd';
ctx.fillRect(10, 10, 130, 130); // 填充矩形
// 使用Path绘制复杂路径(笑脸)
let path = new Path2D();
path.arc(75, 75, 50, 0, Math.PI * 2, true);
path.moveTo(110, 75);
path.arc(75, 75, 35, 0, Math.PI, false);
path.moveTo(65, 65);
path.arc(60, 65, 5, 0, Math.PI * 2, true);
path.moveTo(95, 65);
path.arc(90, 65, 5, 0, Math.PI * 2, true);
ctx.strokeStyle = '#0000ff';
ctx.stroke(path);8.4 绘制文本
let canvas = document.getElementById('test-text-canvas'),
ctx = canvas.getContext('2d');
ctx.fillStyle = '#fff';
ctx.fillRect(0, 0, 300, 100);
ctx.shadowOffsetX = 2;
ctx.shadowOffsetY = 2;
ctx.shadowBlur = 2;
ctx.shadowColor = '#ccc';
ctx.font = '28px Arial';
ctx.fillStyle = '#999';
ctx.fillText('带阴影的文字', 20, 40);8.5 性能优化建议
- 通过不可见的Canvas绘图,再将结果复制到可见Canvas
- 尽量使用整数坐标
- 创建多个重叠Canvas绘制不同层
- 不变背景图片直接用
<img>标签放在最底层
8.6 练习:绘制天气预报图表
给定天气数据数组,在Canvas上绘制未来天气预报(柱状图等)。
let data = [
{ high: 35, low: 22 },
{ high: 37, low: 24 },
{ high: 37, low: 25 },
{ high: 34, low: 24 },
{ high: 33, low: 23 }
];
let canvas = document.getElementById('weather-canvas');
// 在此实现绘图逻辑(400x200)
// 下载图片
let download = document.getElementById('weather-download');
download.href = canvas.toDataURL();读者可自行实现柱状图绘制,展示每日最高温和最低温。
本篇核心知识点速记
- 浏览器对象:
window(窗口尺寸)、navigator(浏览器信息,不可靠)、screen(屏幕信息)、location(URL操作)、document(DOM根节点、Cookie)、history(历史记录,配合pushState使用) - DOM操作:获取(
getElementById/querySelector)、更新(innerHTML/innerText/style)、插入(appendChild/insertBefore)、删除(removeChild),注意children实时更新 - 表单操作:用
value获取文本框/下拉框值,用checked获取单选/复选框;提交表单用onsubmit事件并return true/false;安全传输用hidden字段 - 文件上传:
<input type="file">+FormData;File API可读取本地文件实现图片预览;异步回调处理 - AJAX与跨域:同源策略限制;CORS通过响应头授权跨域;简单请求与预检请求;Fetch API推荐使用
- Promise:解决回调地狱,支持链式调用(
then/catch),提供all/race并行控制 - async/await:异步函数同步写,错误用
try...catch,必须在async函数内使用await - Canvas:2D绘图上下文,坐标左上角原点,可绘制形状、文本,支持性能优化
