五、浏览器
1.“window
对象不但充当全局作用域,而且表示浏览器窗口”
'use strict';
// 获取浏览器窗口的内部宽度和高度(除去菜单栏、工具栏、边框等占位元素的显示网页的净宽高),但IE<=8不支持
console.log('window inner size: ' + window.innerWidth + ' x ' + window.innerHeight);
// 获取浏览器窗口的整个宽高
console.log('window outer size: ' + window.outerWidth + ' x ' + window.outerHeight);
// 结果
window inner size: 1120 x 522
window outer size: 1120 x 565
2.“navigator
对象表示浏览器的信息”,常用属性:
navigator.appName // 浏览器名称
navigator.appVersion // 浏览器版本
navigator.language // 浏览器设置的语言
navigator.platform // 操作系统类型
navigator.userAgent // 浏览器设定的`User-Agent`字符串
注意“navigator
的信息可以很容易地被用户修改,所以JavaScript读取的值不一定是正确的”。
3.“screen
对象表示屏幕的信息”,常用属性:
screen.width // 屏幕宽度,以像素为单位
screen.height // 屏幕高度,以像素为单位
screen.colorDepth // 返回颜色位数,如8、16、24
'use strict';
console.log('Screen size = ' + screen.width + ' x ' + screen.height);
console.log('Screen color depth: ' + screen.colorDepth);
// 结果
Screen size = 1120 x 630
Screen color depth: 24
4.“location
对象表示当前页面的URL信息”,对于一个完整的URL:http://www.example.com:8080/path/index.html?a=1&b=2#TOP
location.href // 'http://www.example.com:8080/path/index.html?a=1&b=2#TOP'
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('新的URL')
;若重新加载当前页面,则调用location.reload()
方法。
5.“document
对象表示当前页面,document
的title
属性是从HTML文档中的<title>xxx</title>
读取的,但也可以动态改变;用document
对象提供的getElementById()
和getElementsByTagName()
可以按ID获得一个DOM节点和按Tag名称获得一组DOM节点;document
对象还有一个cookie
属性,可以获取当前页面的Cookie:document.cookie
”,为了解决引入的第三方Js存在恶意代码而获取当前页面Cookie中的用户登陆信息的问题,“服务器端在设置Cookie时,应该始终坚持使用httpOnly
,设定了httpOnly
的Cookie将不能被JavaScript读取”。
6.“history
对象保存了浏览器的历史记录,JavaScript可以调用history
对象的back()
或forward ()
,相当于用户点击了浏览器的“后退”或“前进”按钮”,但这是一个历史遗留问题,最好不用这个对象。
7.参考“HTML DOM教程”(https://www.runoob.com/htmldom/htmldom-tutorial.html)先学习一下HTML DOM相关内容。文档对象模型(Document Object Model)是HTML和XML文档的编程接口,HTML DOM 定义了访问和操作 HTML 文档的标准方法,DOM 以树结构表达 HTML 文档。根据 W3C 的 HTML DOM 标准,HTML 文档中的所有内容都是节点:
1)整个文档是一个文档节点
2)每个 HTML 元素是元素节点
3)HTML 元素内的文本是文本节点
4)每个 HTML 属性是属性节点
5)注释是注释节点
8.注意DOM树中节点的nodeName
属性规定了节点的名称,该属性是只读的,元素节点的nodeName与标签名相同(且是大写的,如
节点的nodeName是P
),属性节点的nodeName与属性名相同,文本节点的nodeName始终是#text,文档节点的nodeName始终是#document。
9.nodeValue属性规定节点的值,元素节点的nodeValue是undefined或null,属性节点的nodeValue是属性值,文本节点的nodeValue是文本本身。获取元素(节点)内容的最简单方法是使用innerHTML属性。
10.nodeType属性返回节点的类型且该属性是只读的,元素节点的nodeType是1,属性节点的是2,文本节点的是3,注视节点的是8,文档节点的是9。
11.注意查找带有相同类名的所有HTML元素应该用方法document.getElementsByClassName(“intro”);其返回包含 class=“intro” 的所有元素的列表(元素的class可能是"intro inner"等,该元素也符合要求)(该方法在IE5、6、7、8中无效)。
12.回到“廖雪峰的Python教程”,“由于ID在HTML文档中是唯一的,所以document.getElementById()
可以直接定位唯一的一个DOM节点,document.getElementsByTagName()
和document.getElementsByClassName()
总是返回一组DOM节点(列表或数组的形式,如果只有一个符合条件也是列表或数组形式),要精确地选择DOM,可以先定位父节点,再从父节点开始选择,以缩小范围”。
13.练习对于如下Html代码,选择出指定条件的节点:
<!-- 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>
js代码如下,js变量得到的是元素节点<p id="test-p">JavaScript</p>
;arr变量对应的document.getElementsByClassName('c-red c-green')[0]
得到的是元素节点 <div class="c-red c-green">
,有两种方法得到该节点的所有子节点;haskell变量对应的document.getElementsByClassName('c-green')[1]
得到的是元素节点<div class="c-green">
,document.getElementsByClassName('c-green')[0]
对应的应该是元素节点<div class="c-red c-green">
:
'use strict';
// 选择<p>JavaScript</p>:
var js = document.getElementById('test-p');
// 选择<p>Python</p>,<p>Ruby</p>,<p>Swift</p>:
var arr = document.getElementsByClassName('c-red c-green')[0].children;
// var arr = document.getElementsByClassName('c-red c-green')[0].getElementsByTagName('p');
// 选择<p>Haskell</p>:
var haskell = document.getElementsByClassName('c-green')[1].lastElementChild;
// 测试:
if (!js || js.innerText !== 'JavaScript') {
alert('选择JavaScript失败!');
} else if (!arr || arr.length !== 3 || !arr[0] || !arr[1] || !arr[2] || arr[0].innerText !== 'Python' || arr[1].innerText !== 'Ruby' || arr[2].innerText !== 'Swift') {
console.log('选择Python,Ruby,Swift失败!');
} else if (!haskell || haskell.innerText !== 'Haskell') {
console.log('选择Haskell失败!');
} else {
console.log('测试通过!');
}
另外还可以使用querySelector()
和querySelectorAll()
获取节点,前者返回匹配指定CSS选择器元素的第一个子元素(不代表只能获取带有class属性的节点),后者返回所有匹配元素。了解querySelector方法(https://www.runoob.com/jsref/met-element-queryselector.html),了解有关css选择器的内容(https://www.runoob.com/cssref/css-selectors.html)(待学习)(选择器element>element,示例:div>p,表示选择所有(直接)父级是
元素)
14.如果通过修改innerHTML
属性来修改节点的内容(文本或者子节点),需要注意是否需要写入HTML,如果写入的字符串是通过网络拿到的,要注意对字符编码来避免XSS攻击;如果修改innerText
或textContent
属性,就可以自动对字符串进行HTML编码,保证无法设置任何HTML标签,两者的区别在于读取属性时innerText
不返回隐藏元素的文本,而textContent
返回所有文本(注意IE<9不支持textContent
)(以上属性被赋予的值都是字符串)。“DOM节点的style
属性对应所有的CSS,可以直接获取或设置(赋予的都是字符串,如test节点修改颜色:test.style.color='#ff0000'
)。因为CSS允许font-size
这样的名称,但它并非JavaScript有效的属性名,所以需要在JavaScript中改写为驼峰式命名fontSize
”。
15.练习对于如下Html结构:
<!-- HTML结构 -->
<div id="test-div">
<p id="test-js">javascript</p>
<p>Java</p>
</div>
获取指定节点并修改:
'use strict';
// 获取<p>javascript</p>节点:
var js = document.getElementById('test-js');
// 修改文本为JavaScript:
// TODO:
js.innerHTML = 'JavaScript';
//js.innerText = 'JavaScript';
//js.textContent = 'JavaScript';
// 修改CSS为: color: #ff0000, font-weight: bold
// TODO:
js.style.color = '#ff0000';
js.style.fontWeight = 'bold';
// 测试:
if (js && js.parentNode && js.parentNode.id === 'test-div' && js.id === 'test-js') {
if (js.innerText === 'JavaScript') {
if (js.style && js.style.fontWeight === 'bold' && (js.style.color === 'red' || js.style.color === '#ff0000' || js.style.color === '#f00' || js.style.color === 'rgb(255, 0, 0)')) {
console.log('测试通过!');
} else {
console.log('CSS样式测试失败!');
}
} else {
console.log('文本测试失败!');
}
} else {
console.log('节点测试失败!');
}
16.“如果插入的js
节点已经存在于当前的文档树,这个节点首先会从原先的位置删除,再插入到新的位置”,在使用appendChile
方法或insertBefore
方法前,需要获取父节点,再为其插入子节点。“使用insertBefore
重点是要拿到一个‘参考子节点’的引用。很多时候,需要循环一个父节点的所有子节点,可以通过迭代父节点的children
属性实现”:
var
i, c,
list = document.getElementById('list');
for (i = 0; i < list.children.length; i++) {
c = list.children[i]; // 拿到第i个子节点
}
17.练习对于如下html代码:
<!-- 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>
按字符串顺序重新排序DOM节点:
'use strict';
// 方法一,直接对子节点进行(傻瓜式)排序:
var list = document.getElementById('test-list');
for (var i = 0; i < list.children.length; ++i) {
for (var j = i+1; j < list.children.length; ++j) {
if (list.children[i].innerText > list.children[j].innerText) {
list.insertBefore(list.children[j], list.children[i]);
}
}
}
// 方法二,把子节点的文本抠出来作为数组排序,然后对子节点一个一个赋值
var list = document.getElementById('test-list');
var i, arr = [];
for (i = 0; i < list.children.length; ++i) {
arr.push(list.children[i].innerHTML);
}
arr.sort();
for (i = 0; i < list.children.length; ++i) {
list.children[i].innerHTML = arr[i];
}
// 测试:
;(function () {
var
arr, i,
t = document.getElementById('test-list');
if (t && t.children && t.children.length === 5) {
arr = [];
for (i=0; i<t.children.length; i++) {
arr.push(t.children[i].innerText);
}
if (arr.toString() === ['Haskell', 'JavaScript', 'Python', 'Ruby', 'Scheme'].toString()) {
console.log('测试通过!');
}
else {
console.log('测试失败: ' + arr.toString());
}
}
else {
console.log('测试失败!');
}
})();
18.“删除多个节点时,要注意children
属性时刻都在变化”,练习对于如下html代码:
<!-- 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开发技术不相关的节点删掉:
'use strict';
var
i,
needs = ['JavaScript', 'HTML', 'CSS'],
list = document.getElementById('test-list'),
children = list.children;
for (i = 0; i < children.length; ++i) {
if(needs.indexOf(children[i].innerHTML) === -1)
list.removeChild(children[i]);
}
// 测试:
;(function () {
var
arr, i,
t = document.getElementById('test-list');
if (t && t.children && t.children.length === 3) {
arr = [];
for (i = 0; i < t.children.length; i ++) {
arr.push(t.children[i].innerText);
}
if (arr.toString() === ['JavaScript', 'HTML', 'CSS'].toString()) {
console.log('测试通过!');
}
else {
console.log('测试失败: ' + arr.toString());
}
}
else {
console.log('测试失败!');
}
})();
19.如果获得了文本框(<input type="text">
)、口令框(<input type="password">
)、下拉框(<select>
)和隐藏文本(<input type="hidden">
)的元素节点,可以通过节点的属性value
获得他们的值或修改值;如果是单选框(<input type="radio">
)和复选框(<input type="checkbox">
),“value
属性返回的永远是HTML预设的值,而需要获得的实际是用户是否‘勾上了’选项,所以应该用属性checked
判断,也可以设置checked
是true
或false
”。
20.在表单中,没有name
属性的<input>
节点的数据不会被提交。
21.练习利用JavaScript检查用户注册信息是否正确,在以下情况不满足时报错并阻止提交表单:
- 1)用户名必须是3-10位英文字母或数字;
- 2)口令必须是6-20位;
- 3)两次输入口令必须一致。
<!-- HTML结构 -->
<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>
'use strict';
var checkRegisterForm = function () {
var
userRe = /[a-zA-Z0-9]{3,10}/,
passRe = /.{6, 20}/,
form = document.getElementById('test-register'),
username = document.getElementById('username'),
password = document.getElementById('password'),
repassword = document.getElementById('password-2');
if (userRe.test(username.value)) {
if (passRe.test(password.value)) {
if (password.value === repassword.value) {
return true;
}else {
window.alert('两次输入的密码应该一致!');
return false;
}
}else {
window.alert('密码的长度应该大于6小于20!');
return false;
}
}else {
window.alert('用户名必须是3-10位英文字母或数字!');
return false;
}
}
// 测试:
;(function () {
window.testFormHandler = checkRegisterForm;
var form = document.getElementById('test-register');
if (form.dispatchEvent) {
var event = new Event('submit', {
bubbles: true,
cancelable: true
});
form.dispatchEvent(event);
} else {
form.fireEvent('onsubmit');
}
})();
22.java中求string的长度用函数string(),javascript中求string的长度用属性length。
23.“任何时候,JavaScript代码都不可能同时有多于1个线程在执行”,执行多任务一般都是异步操作,而为了确保异步操作可以结束,需要设置在执行异步操作前设置一个回调函数,异步操作执行完成后自动执行回调函数然后获得响应。
24.Ajax意味着用JavaScript执行异步网络请求。“默认情况下,JavaScript在发送AJAX请求时,URL和当前页面必须同域,即协议、域名、端口号完全相同,该情况下url是相对路径”。
25.理解ajax跨域请求中的jsonp,参考链接。
26.“拥有"src"这个属性的标签都拥有跨域的能力,比如<script>
、<img>
、<iframe>
”。
27.“跨源域资源共享( CORS )机制允许 Web 应用服务器进行跨源访问控制,从而使跨源数据传输得以安全进行。”学习链接(不管做前端还是后端这篇文章都需要看看,建议先复习一下计算机网络)
28.“如果在js的线程中出现耗时操作,就容易堵塞后续代码的执行。因此在js中如果碰到一些可能需要耗费一些时间的操作,像setTimeout,ajax的回调函数(称其为异步操作)等,js会将其放入一个代办任务队列taskqueue中,当js按顺序执行完其他同步的,不耗时的操作之后,会去依次执行taskqueue队列中的任务”,参考链接。
29.参考b站视频理解promise对象:promise对象用于表示一个异步操作的最终完成(成功或失败)及所返回的结果值。用promise对象可以降低代码的耦合度,参考链接:15分钟理解js中的promise对象
30.对于代码promise对象.then(参数1).catch(参数2)
,参数1和2可以是函数也可以是promise对象(异步任务),如果是函数的话,参数1是函数resolve
,参数2是函数reject
;
对于代码:
var p = new Promise(function (resolve, reject) {
log('start new Promise...');
resolve(123);
});
函数resolve
在异步操作执行成功时执行,函数reject
在异步操作执行失败时执行。
//IE9 及其更早版本不支持第三个及之后的参数
var alertFunc = function(a,b){console.log(a,b)};
setTimeout(alertFunc, 2000, "Runoob", "Google");
//另外一种写法
setTimeout(function(){ alertFunc("Runoob", "Google"); }, 2000);
第三个及之后的参数是setTimeout()函数的可选参数,作为参数传给 setTimeout() 方法里面的匿名函数或者调用的函数。
32.“除了串行执行若干异步任务外,Promise还可以并行执行异步任务”:
var p1 = new Promise(function (resolve, reject) {
setTimeout(resolve, 500, 'P1'); // 'P1'作为参数传递给函数resolve
});
var p2 = new Promise(function (resolve, reject) {
setTimeout(resolve, 600, 'P2'); // 'P2'作为参数传递给函数resolve
});
// 同时执行p1和p2,并在它们都完成后执行then:
Promise.all([p1, p2]).then(function (results) {
console.log(results); // 获得一个Array: ['P1', 'P2']
});
“有时多个异步任务是为了容错,比如同时向两个URL读取用户的个人信息,只需要获得先返回的结果即可。这种情况下,用Promise.race()
实现”,因为p1等待的时间少于p2等待的时间,所以最后输出的结果是’P1’,但p2
仍在继续执行,只是执行结果将被丢弃:
var p1 = new Promise(function (resolve, reject) {
setTimeout(resolve, 500, 'P1');
});
var p2 = new Promise(function (resolve, reject) {
setTimeout(resolve, 600, 'P2');
});
Promise.race([p1, p2]).then(function (result) {
console.log(result); // 'P1'
});
33.“如果浏览器支持Canvas,它将忽略<canvas>
内部的HTML,如果浏览器不支持Canvas,它将显示<canvas>
内部的HTML;Canvas的坐标以左上角为原点,水平向右为X轴,垂直向下为Y轴,以像素为单位,所以每个点都是非负整数。”
34.练习题画k线图,一开始不明白是啥,百度了解了一下基本原理,阳线一般是红色的,阴线是绿色的(敲代码还能学到股市有关知识,绝了)
绘制k线图的函数接收的参数data是一个数组,其每个元素都是一个对象,查了一下Path2D咋用,参考api链接。答案代码如下:
var
canvas = document.getElementById('stock-canvas'), // 获取画布
width = canvas.width,
height = canvas.height,
ctx = canvas.getContext('2d'), // 一个CanvasRenderingContext2D对象,用于绘图
colWidth = canvas.width / 30 / 1.5, // k线图中矩形的宽(阳线和阴线的宽)
start, // 矩形的中心线的起始位置
spacing, // 矩形的中心线的间隔
max = data.map(x => x.high).sort()[data.length - 1], // 30天中的最高价
min = data.map(x => x.low).sort()[0], // 30天中的最低价
unitLen = canvas.height / (max - min), // 单位价格区间长度对应的长度(y坐标)
bottom = canvas.height; // canvas的底部
// 限制矩形的宽度和高度
if (colWidth > 10) {
colWidth = 10;
}
if (unitLen > 1) {
bottom = 1 / unitLen * bottom; // 使图像整体往上移动
unitLen = 1;
}
start = colWidth / 2;
spacing = colWidth * 1.5;
console.log(JSON.stringify(data[0])); // {"date":"20150602","open":4844.7,"close":4910.53,"high":4911.57,"low":4797.55,"vol":62374809900,"change":1.69}
// 序列化并输出data数组的第一个对象,看一下具体数据组成
// 擦除(0,0)位置大小为weigth x height的矩形,即把该区域变透明
ctx.clearRect(0, 0, width, height);
// ctx.fillText('Test Canvas', 10, 10);
for (let i = 0; i < data.length; i++) {
// 绘制每个矩形的中心线
var path = new Path2D();
let coord = start + spacing * i; // 每个矩形的中心线的x坐标
// 将一个新的子路径的起始点移动到对应坐标
path.moveTo(coord, bottom - (data[i].low - min) * unitLen);
//console.log(bottom - (data[i].low - min) * unitLen);
// 使用直线连接子路径的终点到对应坐标
path.lineTo(coord, bottom - (data[i].high - min) * unitLen);
//console.log(bottom - (data[i].low - min) * unitLen);
ctx.strokeStyle = 'black'; // 线的颜色和样式
ctx.stroke(path); // 绘制矩形的中心线
// 绘制矩形,注意细节,canvas的y轴是向下的
let higher, lower;
if (data[i].open < data[i].close) { // 如果开盘价小于收盘价就是红线(阳线)
ctx.fillStyle = 'red';
higher = data[i].close;
lower = data[i].open;
} else { // 如果开盘价大于收盘价就是绿线(阴线)
ctx.fillStyle = 'green';
higher = data[i].open;
lower = data[i].close;
}
// 绘制一个填充了内容的矩形,这个矩形的开始点(左上点)在(coord - colWidth / 2, bottom - (higher - min) * unitLen) ,它的宽度和高度分别由colWidth和(higher - lower) * unitLen)确定,填充样式由当前的fillStyle决定
ctx.fillRect(coord - colWidth / 2, bottom - (higher - min) * unitLen, colWidth, (higher - lower) * unitLen);
}