JavaScript笔记-浏览器[上]

注意:本篇学习笔记基于原网站: JavaScript教程 - 廖雪峰的官方网站
笔记仅作学习留档使用

本篇目录

浏览器对象
操作DOM
操作表单
操作文件
AJAX

浏览器对象

Windows

作为全局作用域,同时表示浏览器的窗口

// 显示浏览器窗口大小
console.log('window inner size: ' + window.innerWidth + ' x ' + window.innerHeight);
  • innerWidthinnerHeight:浏览器窗口的内部宽度和高度。(指除去菜单栏、工具栏、边框等占位元素后,网页的净宽高)(IE8及往下等古早版本浏览器没有这属性)
  • outerWidthouterHeight :浏览器窗口的全宽高

表示浏览器的信息,常用属性如下:

// 浏览器名称
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

保存了浏览器的历史记录,可以调用 historyback()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/(innerTexttextContent
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-sizefontSize

插入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 获得对应的textpasswordhidden 以及 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

设置值

获取值类似,对于 textpasswordhidden 以及 select,直接设置 value = '设置的内容';对于单选框和复选框,设置 checkedtruefalse 即可。

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">时,只有表单的 enctypemultipart/form-data,且 methodpost,浏览器才能正确编码并以 multipart/form-data 格式发送表单的数据。

File API

File API提供了 FileFileReader 两个主要对象,可以获得文件信息并读取文件。可以实现读取用户选取的图片文件,并在一个 <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.comexample.com 不同),协议要相同( httphttps 不同),端口号要相同(http默认是 :80 端口,它和 :8080 就不同)。
JavaScript无法用直接请求的方法请求外域(就是其他网站)的URL,需要用特殊的几种方法(Flash/同源域名代理服务器/JSONP。还有html5支持的CORS),这里介绍一下JSONP:
JSONP利用 <script> 标签可以跨域的特性,让服务器返回JS代码来“传数据”。
简单来说,就是将传来的数据用页面中先准备好的函数包裹之后,返回给浏览器执行,相当于动态读取外域的JavaScript资源。限制是只能用GET请求

posted @ 2026-01-15 14:45  qiqimk  阅读(2)  评论(0)    收藏  举报