JavaScript笔记-浏览器[上]
注意:本篇学习笔记基于原网站: JavaScript教程 - 廖雪峰的官方网站
笔记仅作学习留档使用
本篇目录
浏览器对象
操作DOM
操作表单
操作文件
AJAX
浏览器对象
Windows
作为全局作用域,同时表示浏览器的窗口
// 显示浏览器窗口大小
console.log('window inner size: ' + window.innerWidth + ' x ' + window.innerHeight);
innerWidth、innerHeight:浏览器窗口的内部宽度和高度。(指除去菜单栏、工具栏、边框等占位元素后,网页的净宽高)(IE8及往下等古早版本浏览器没有这属性)outerWidth、outerHeight:浏览器窗口的全宽高
navigator
表示浏览器的信息,常用属性如下:
// 浏览器名称
console.log('appName = ' + navigator.appName);
// 浏览器版本
console.log('appVersion = ' + navigator.appVersion);
// 浏览器设置的语言
console.log('language = ' + navigator.language);
// 操作系统类型
console.log('platform = ' + navigator.platform);
// 浏览器设定的user-Agent字符串
console.log('userAgent = ' + navigator.userAgent);
/* 返回结果
appName = Netscape
appVersion = 5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.0.0 Safari/537.36 Edg/143.0.0.0
language = zh-CN
platform = Win32
userAgent = Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.0.0 Safari/537.36 Edg/143.0.0.0
*/
navigator 的信息可以很容易地被用户修改,所以JavaScript读取的值不一定是正确的,为了针对不同浏览器,利用JavaScript对不存在属性返回 undefined 的特性,可以这么获取:
let width = window.innerWidth || document.body.clientWidth;
- 短路运算符
||:如果第一个操作数为真值,则直接返回这个操作数的值;否则,返回第二个操作数的值
screen
表示屏幕的信息,常用属性如下:
- screen.width:屏幕宽度,以像素为单位;
- screen.height:屏幕高度,以像素为单位;
- screen.colorDepth:返回颜色位数,如8、16、24。
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:8080' 域名 + 端口号(80/443不显示)
location.port; // '8080'
location.pathname; // '/path/index.html' 路径
location.search; // '?a=1&b=2' 查询参数
location.hash; // 'TOP' #符号后面的部分
一些执行指令
// 跳转到新页面
location.href = 'https://new.com'
// 重新加载当前页面
location.reload()
// 强制从服务器刷新(清除缓存)
location.reload(true);
// 加载新页面(跳转到首页 './'->url -导航到url对应新页面)
location.assign('./')
// 替换当前页面('./'->url -替换当前页面为url对应页面)
location.replace('./')
document
表示当前页面,HTML在浏览器中以DOM形式表示为树形结构, document 对象就是整个DOM树的根节点。
其中,title 属性是从HTML文档中的 <title>xxx</title> 读取的,但是可以动态改变,例如执行document.title = '努力学习JavaScript!';后,执行的窗口标题就会变成努力学习JavaScript!
要查找DOM树的某个节点,需要从 document 对象开始查找。最常用的查找是根据ID和Tag Name
<dl id="drink-menu">
<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);
// 输出结果:提供的饮料有:摩卡,酸奶,果汁,
document.getElementById('drink-menu'):获取ID为'drink-menu'的元素document.getElementsByTagName('dt'):获取所有标签
document 对象还有一个 cookie 属性,可以获取当前页面的Cookie。服务器用Cookie来区分到底是哪个用户发过来的请求,当一个用户成功登录后,服务器发送一个Cookie:user=ABC123XYZ(加密的字符串)...给浏览器,此后浏览器访问该网站时,会在请求头附上这个Cookie,服务器据此即可区分出用户。
Cookie还可以存储网站的一些设置,例如页面显示的语言等等。document.cookie 可以读取到当前页面的Cookie,而用户的登录信息通常也存在Cookie中,所以可以直接利用恶意代码获取信息。
为了解决这个问题,服务器在设置Cookie时可以使用httpOnly,设置后Cookie将不能被JavaScript读取。(IE从IE6 SP1开始支持)(不是JavaScript的内容所以这里就不展开了)
history
保存了浏览器的历史记录,可以调用 history 的 back() 或 forward (),相当于用户点击了浏览器的“后退”或“前进”按钮。(现在基本不用,因为这个回退和浏览器自带的前进后退一样,不是很智能)但对于使用AJAX动态加载的页面,如果希望页面更新时同时更新 history 对象,应当使用 history.pushState() 方法:
let state = 'any-data';
let newUrl = '/ajax.html#signin';
history.pushState(state, '', newUrl);
// 语法说明
history.pushState(stateObject, title, url);
这样当用户点击“后退”时,浏览器并不会刷新页面,而是触发 popstate 事件,可由JavaScript捕获并更新相应的部分页面内容。
操作DOM
HTML文档被浏览器解析后就是一棵DOM树,需要通过JavaScript来操作DOM改变HTML的结构,对于一颗树,操作就是增删改查。
注:这里的DOM节点是指Element,而DOM节点实际上指Node
HTML中, Node 包括 Element、 Comment、 CDATA_SECTION 等多种类型,还包括根节点 Document 类型
因为Element-是实际控制页面结构的 Node
根节点 Document 已经自动绑定为全局变量 document
获取DOM节点
方法一:document获取
由于ID在HTML文档中是唯一的,所以 getElementById() 可以直接定位唯一的一个DOM节点。 getElementsByTagName() 和getElementsByClassName() 则可以返回一组DOM节点。
为了精确地选择DOM,可以先定位父节点,再从父节点开始选择,以缩小范围。
// 返回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;
- class:自定义分类标记
- Tag:HTML元素的固有标签类型
方法二:querySelector(需要IE8以上)
使用 querySelector() 和 querySelectorAll()的使用条件来获取节点
// 通过querySelector获取ID为q1的节点:
let q1 = document.querySelector('#q1');
// 通过querySelectorAll获取q1节点内的符合条件的所有节点:
// 选择所有直接父元素是 class 为 highlighted 的 div 的 p 元素
let ps = q1.querySelectorAll('div.highlighted > p');
注意,这里获取的基本都是div元素,想要获取p元素,可以这样:
<!— 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"> ← 这是一个 div 元素
<p>Python</p> ← 这是一个 p 元素
<p>Ruby</p> ← 这也是一个 p 元素
<p>Swift</p> ← 这也是一个 p 元素
</div>
<div class="c-green">
<p>Scheme</p>
<p>Haskell</p>
</div>
</div>
// 获取 <p>JavaScript</p>:通过id直接获取
let javascriptElement = document.getElementById('test-p');
// 获取 <p>Haskell</p>:通过最后一个p元素获取
// 这里的p也可以是p:nth-child(2),这是nth-child选择器
let haskellElement = document.querySelector('#test-div .c-green p:last-child');
// 获取三个p元素(Python, Ruby, Swift)
// 通过同时具有c-red和c-green类的div获取
let threeElements = document.querySelectorAll('#test-div .c-red.c-green p');
// 转换为数组
let arr = Array.from(threeElements);
//使用innerText获取节点文本
console.log(javascriptElement.innerText); // 输出: "JavaScript"
更新DOM节点
对于DOM节点,我们有两种方法可以修改:innerHTML/(innerText 或 textContent)
innerHTML
innerHTML 可以直接通过片段修改DOM节点内部的子树
// 获取<p id="p-id">...</p>
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';
// <p>...</p>的内部结构已修改
- 注意是否需要写入HTML。
innerHTML会解析HTML标签,要注意对字符编码来避免XSS攻击
innerText 或 textContent
// 获取<p id="p-id">...</p>
let p = document.getElementById('p-id');
// 设置文本:
p.innerText = '<script>alert("Hi")</script>';
// HTML被自动编码,无法设置一个<script>节点:
// <p id="p-id"><script>alert("Hi")</script></p>
innerText不返回隐藏元素的文本,而textContent返回所有文本。(另外注意IE<9不支持textContent)
修改CSS
DOM节点的 style 属性对应所有的CSS,可以直接获取或设置
// 获取<p id="p-id">...</p>
let p = document.getElementById('p-id');
// 设置CSS:
p.style.color = '#ff0000';
p.style.fontSize = '20px';
p.style.paddingTop = '2em';
注意JavaScript里有些属性名和CSS里格式不一样,使用时需要编写为对应命名:font-size → fontSize
插入DOM节点
对于空的DOM节点,可以直接使用innerHTML直接修改节点内容,对于其他情况,有两种方法可以插入新节点:appendChild 或insertBefore
appendChild
appendChild 可以将一个子节点添加到一个双亲节点的最后一的子节点
例如:把 <p id="js">JavaScript</p>添加到<div id="list"> 的最后一项,
let
js = document.getElementById('js'),
list = document.getElementById('list');
list.appendChild(js);
因为我们插入的 js 节点已经存在于当前的文档树,因此这个节点首先会从原先的位置删除,再插入到新的位置。
<!-- HTML结构 -->
<p id="js">JavaScript</p>
<div id="list">
<p id="java">Java</p>
<p id="python">Python</p>
<p id="scheme">Scheme</p>
</div>
↓变成
<div id="list">
<p id="java">Java</p>
<p id="python">Python</p>
<p id="scheme">Scheme</p>
<p id="js">JavaScript</p>
</div>
从零创建一个新的节点插入到指定位置这样做:
let
list = document.getElementById('list'),
haskell = document.createElement('p'); //创建
haskell.id = 'haskell';
haskell.innerText = 'Haskell';
list.appendChild(haskell);
<!-- 对应结果 -->
<div id="list">
<p id="java">Java</p>
<p id="python">Python</p>
<p id="scheme">Scheme</p>
<p id="haskell">Haskell</p>
</div>
给文档添加了新的CSS定义可以使用appendChild这么做:
let d = document.createElement('style');
// 设置 style 元素的 type 属性
d.setAttribute('type', 'text/css');
// 设置 style 元素的内容(CSS 规则)
d.innerHTML = 'p { color: red }'; // 文字变为红色
// 将 style 元素添加到 <head> 的末尾
document.getElementsByTagName('head')[0].appendChild(d);
insertBefore
parentElement.insertBefore(newElement, referenceElement);
使用此代码可以将子节点插入到parentElement内的 referenceElement 之前。
以上面最开始的HTML为例,如果我们要新建一个 Haskell 插入到 Python 之前:
let
list = document.getElementById('list'),
ref = document.getElementById('python'),
haskell = document.createElement('p');
haskell.id = 'haskell';
haskell.innerText = 'Haskell';
list.insertBefore(haskell, ref);
<!-- 变为 -->
<div id="list">
<p id="java">Java</p>
<p id="haskell">Haskell</p>
<p id="python">Python</p>
<p id="scheme">Scheme</p>
</div>
删除DOM节点
删除节点需要获得该节点本身以及它的双亲节点,然后调用双亲节点的 removeChild 把删掉该节点,删除后的节点还在内存中,可以随时再次被添加到别的位置
// 待删除节点:
let self = document.getElementById('to-be-removed');
// 双亲节点:
let parent = self.parentElement;
// 删除:
let removed = parent.removeChild(self);
removed === self; // true
children属性在子节点变化时会实时更新,例如对于一个有两个子节点的双亲结点,删除第一个节点后,parent.children的节点数量从2变为了1,原有的索引[1]不存在。
操作表单
表单本身也是DOM树,操作表单和操作DOM数类似。表单的输入框、下拉框等可以接收用户输入,可以获得用户输入的内容,或者对一个输入框设置新的内容。
常用输入控件
| 名称 | 代码 | 用途 |
|---|---|---|
| 文本框 | <input type="text"> |
输入文本 |
| 口令框 | <input type="password"> |
输入密码 |
| 单选框 | <input type="radio"> |
单选 |
| 复选框 | <input type="checkbox"> |
复选 |
| 下拉框 | <select> |
单选 |
| 隐藏文本 | <input type="hidden"> |
用户不可见,提交时发送到服务器 |
获取值
对于一个已知的<input> 节点的引用,就可以直接调用 value 获得对应的text、 password、 hidden 以及 select中用户的输入值;对于单选框和复选框, 我们需要获得选项是否被勾选,使用 checked 判断
<input type="text" id="email">
<label><input type="radio" name="weekday" id="monday" value="1"> Monday</label>
<label><input type="radio" name="weekday" id="tuesday" value="2"> Tuesday</label>
let input = document.getElementById('email');
input.value; // '用户输入的值'
let mon = document.getElementById('monday');
let tue = document.getElementById('tuesday');
// value返回预设的HTML
mon.value; // '1'
tue.value; // '2'
mon.checked; // true或者false
tue.checked; // true或者false
设置值
获取值类似,对于 text、password、 hidden 以及 select,直接设置 value = '设置的内容';对于单选框和复选框,设置 checked 为 true 或 false 即可。
HTML5控件
HTML5新增了大量标准控件,这里举例几个常用的
<!-- 显示为可选的日历控件,格式为yyyy/MM/dd -->
<input type="date" value="2021-12-02">
<!-- 显示为一个可选颜色的纯色小方块 -->
<input type="color" value="#ff0000">
<!-- 这是一个数字输入框,min和max限制范围 -->
<input type="number" min="0" max="100" value="50">
<!-- 这是一个滑块控件,适合不需要精确数值的场景 -->
<input type=”range” value=”50” min=”0” max=”100”>
不支持HTML5的浏览器无法识别新的控件,会把它们当做 type="text" 来显示
提交表单
JavaScript可以以两种方式来处理表单的提交,通过<form>元素的 submit()提交或者响应 <form> 本身的 onsubmit 事件。
submit()
通过 <form> 元素的 submit() 方法提交一个表单,例如,响应一个 <button> 的 click 事件,在JavaScript代码中提交表单,但这样会扰乱浏览器对form的正常提交。浏览器会在默认点击 <button type="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的input...
// 提交form:
form.submit();
}
</script>
onsubmit 事件
在提交form时作修改,如果 return false,浏览器将不会继续提交form,这种情况通常对应用户输入有误,提示用户错误信息后终止提交form。
<!-- HTML -->
<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');
// 可以在此修改form的input...
// 继续下一步:
return true;
}
</script>
在检查和修改 <input> 时,可以充分利用 <input type="hidden"> 来传递数据。例如很多登录表单希望用户输入用户名和口令,出于安全考虑,提交表单时不传输明文口令,而是口令的MD5;用户输入了口令提交时,口令框的显示会突然从几个变成32个 (因为MD5有32个字符)。要想不改变用户的输入,可以利用 <input type="hidden"> 实现
<!-- HTML -->
<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:
md5_pwd.value = toMD5(input_pwd.value);
// 继续下一步:
return true;
}
</script>
操作文件
上传文件的唯一控件: <input type="file">
当一个表单包含<input type="file">时,只有表单的 enctype 为 multipart/form-data,且 method 为 post,浏览器才能正确编码并以 multipart/form-data 格式发送表单的数据。
File API
File API提供了 File 和 FileReader 两个主要对象,可以获得文件信息并读取文件。可以实现读取用户选取的图片文件,并在一个 <div> 中预览图像
let
fileInput = document.getElementById('test-image-file'),
info = document.getElementById('test-file-info'),
preview = document.getElementById('test-image-preview');
// 监听change事件:
fileInput.addEventListener('change', function () {
// 清除背景图片:
preview.style.backgroundImage = '';
// 检查文件是否选择:
if (!fileInput.value) {
info.innerHTML = '没有选择文件';
return;
}
// 获取File引用:
let file = fileInput.files[0];
// 获取File信息:
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,/9j/4AAQSk...(base64编码)...'
preview.style.backgroundImage = 'url(' + data + ')';
};
// 以DataURL的形式读取文件:
reader.readAsDataURL(file);
});
以DataURL的形式读取到的文件是一个字符串,类似于 data:image/jpeg;base64,/9j/4AAQSk...(base64编码)...,常用于设置图像。如果需要服务器端处理,把字符串 base64, 后面的字符发送给服务器并用Base64解码就可以得到原始文件的二进制内容。
AJAX
不是JavaScript的规范,是Asynchronous JavaScript and XML的缩写,用JavaScript执行异步网络请求。AJAX请求是异步执行的,需要通过回调函数获得响应。
现代浏览器上写AJAX主要依靠 XMLHttpRequest 对象,现代浏览器还提供以 Promise 方式原生支持的Fetch API(这个老浏览器不兼容)
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;
});
Fetch API的详细用法参考 MDN文档。
安全限制
JavaScript在发送AJAX请求时,URL的域名必须和当前页面完全一致。完全一致的意思是,域名要相同( www.example.com 和 example.com 不同),协议要相同( http 和 https 不同),端口号要相同(http默认是 :80 端口,它和 :8080 就不同)。
JavaScript无法用直接请求的方法请求外域(就是其他网站)的URL,需要用特殊的几种方法(Flash/同源域名代理服务器/JSONP。还有html5支持的CORS),这里介绍一下JSONP:
JSONP利用 <script> 标签可以跨域的特性,让服务器返回JS代码来“传数据”。
简单来说,就是将传来的数据用页面中先准备好的函数包裹之后,返回给浏览器执行,相当于动态读取外域的JavaScript资源。限制是只能用GET请求
浙公网安备 33010602011771号