共计 41364 个字符,预计需要花费 104 分钟才能阅读完成。
JavaScript 基础知识
一、Hello World
1.script 标签
JavaScript 程序可以在 <script> 标签帮助下插入到 HTML 文档的任何地方。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<p> script 标签之前</p>
<script>
alert("Hello world!");
</script>
<p>...script 标签之后</p>
</body>
</html>
<script> 标签中包裹了 JavaScript 代码,当浏览器遇到 <script> 标签,代码会自动运行
2.外部脚本
如果有大量的 JavaScript 代码,可以将它放入一个单独的文件,脚本文件可以通过 src 特性添加到 HTML 文件中。
<script src="/path/to/script.js"></script>
这里 /path/to/script.js 是脚本文件从网站根目录开始的绝对路径,也可以提供当前页面的相对路径,
src="script.js" 表示当前文件夹中的 script.js 文件。
也可以提供一个完整的 URL 地址,例如:
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.11/lodash.js"></script>
若要附加多个脚本,则要使用多个标签:
<script src="/js/script1.js"></script>
<script src="/js/script2.js"></script>
...
注意:
- 一般会将复杂的脚本放入单独的文件中,使用独立文件的好处是浏览器会下载它,每次使用都从缓存拿出,而不是重新下载,可节省流量和加快浏览速度。
- 一个单独的
<script>标签不能同时有 src 特性和内部包裹的代码。 <script src=”file.js”>
alert(1); //此内容会被忽视
</script>
二、代码结构
语句
语句是执行行为的语法结构和命令。
我们可以在代码中编写任意数量的语句,语句之间可以使用分好进行分割。
<script>
alert("Hello");
alert("World"); //通常每条语句独占一行
</script>
分号
当存在换行符时,在 大多数情况 下可以省略分号。建议在语句之间加分号以避免不必要的错误。
<script>
alert("Hello")
alert("World") //这也是可运行的
</script>
在这,JavaScript 将换行符理解成隐式分号。这也被称为 自动分号插入。
注释
单行注释以两个正斜杠字符 // 开始。
多行注释以一个正斜杠和星号开始,并以一个星号和一个正斜杠结束 /*注释*/ 。
三、变量
变量
变量 是数据的 “命名储存” 。可以用变量来保存商品、访客等信息。
在 JavaScript 中创建一个变量,需要用到 let 关键字。
let message;
message = "Hello"; //保存字符串
alert(message); //显示变量内容
//可以在一行中声明多个变量
let user = "John", age = 25 , message = 'Hello';
//为了方便阅读,通常一行只声明一个变量
let user = 'John';
let age = 25;
//或者
let user = 'John'
, age = 25;
在较旧的脚本中,可能会出现 var ,这与 let 大体相同,目前只需了解。
变量命名
JavaScript 的变量命名有两个限制:
1、变量名称必须仅包含字母、数字、符号 $ 和 _ 。
2、首字符必须非数字。
如果命名包含多个单词,通常采用驼峰式命名法,即除了第一个单词,其他每个单词都以大写开头:myVeryLongName.
常量
声明一个常数不变的变量,可以使用 const:
const myBirthday = '23.01.2002';
const 不能被修改
大写形式的常数
一个普遍的做法是将常量用作别名,以便记住那些在执行之前就已知的难以记住的词。
例如,以十六进制格式为颜色声明常量:
const COLOR_RED = "#F00";
const COLOR_GREEN = "0F0";
const COLOR_BLUE = "00F";
//选择一个颜色
let color = COLOR_BLUE;
alert(color); //#00F
好处:
COLOR_BLUE比00F好记,也更易输入- 阅读代码时
COLOR_BLUE也更易看懂
一般在大写形式的常数用在执行之前就已知的值。
正确命名变量
一些可以遵循的规则:
- 使用易读的命名,例如
userName或shoppingCart - 离例如 a、b、c 这种缩写远一些
- 术语要和团队保持一致,如果网站的访客为 “用户” ,则采用相关的变量命名,比如
currentUser或者newUser, 而不要使用currentVisitor或newManInTown
四、数据类型
Number 类型
let n = 123;
n = 12.345;
number 类型代表整数和浮点数,除了常规数字,还存在一些 “特殊数值”。
Infinity代表 无穷大 ,可以通过除以 0 来得到它。alert(1 / 0); // Infinity //或者在代码中直接使用它 alert(Infinity);NaN代表一个 计算错误 ,他是一个不正确的或者未定义的数学操作所得到的结果,例:alert(“not a number” / 2); // NaN // NaN 是粘性的,任何对 NaN 的进一步操作都会返回 NaN alert(“not a number” / 2 + 3); // NaN
BigInt 类型
在 JavaScript 中,number 类型无法表示大于(2^53 -1)或小于 -(2^53 -1)的整数,
有时我们需要很大的数字,例如用于加密或微秒精度的时间戳。
BigInt 类型用于表示任意长度的 整数,也可以通过将 n 附加到整数字段的末尾来创建 BigInt 值。
// 尾部的 n 表示这是一个 BigInt 类型
const bigInt = 123456789684683784887357357324654676n;
String 类型
JavaScript 中的字符串必须被括在引号里,有三种包含方式:
1.双引号:“Hello”
2.单引号: ‘Hello’
3.反引号: `Hello`
反引号是 功能扩展 引号,它们允许我们通过将变量和表达式包装在 ${...} 中,来将它们嵌入到字符串中。例:
let name = "John";
//嵌入一个变量
alert( `Hello, &{name}!` ); // Hello, John!
//嵌入一个表达式
alert( `the result is ${1 + 2}` ); // the result is 3
&{...} 内的表达式会被计算,计算结果会成为字符串的一部分,可以在 &{...} 内放置任何东西。
Boolean 类型(逻辑类型)
null 值
undefined 值
undefined 的含义是 未被赋值
object 类型和 symbol 类型
object 类型是一个特殊的类型,其他所有类型都被称为“原始类型”,因为它们的值只包含一个内容,而 object 则用于储存数据集合和更复杂的实体。
symbol 类型用于创建对象的唯一标识符。
typeof 运算符
typeof 运算符返回参数的类型,它支持两种语法形式:
1.作为运算符: typeof x
2.函数形式: typeof(x)
typeof undefined // "undefined"
typeof 0 // "number"
typeof 10n // "bigint"
typeof true // "boolean"
typeof "foo" // "string"
typeof Symbol("id") // "symbol"
typeof Math // "object"
typeof null // "object"
typeof alert // "function"
五、交互:alert、prompt 和 confirm
alert
它会显示一条信息,并等待用户按下 “OK”。例如:
alert("Hello");
弹出的这个带有信息的小窗口被称为 模态窗。“modal” 意味着用户不能与页面的其他部分进行交互,直到他们处理完窗口。
prompt
prompt 函数接收两个函数:
result = prompt(title, [default]); // default 旁的方括号表示该参数是可选的,不是必需的
浏览器会显示一个带有文本消息的模态窗口,还有 input 框和确定 / 取消按钮。
title :显示给用户的文本。
default : 可选的第二个参数,指定 input 框的初始值。
访问者可以在提示输入栏中输入内容,我们在 result 中获取该文本,若用户取消输入,则返回 null。
let age = prompt('How old are you?', 100);
alert('You are &{age} years old!'); //You are 100 years old! (默认值)
confirm
result = confirm(question);
confirm 函数显示一个带有 question 以及确定和取消两个按钮 的模态窗口。
点击确定返回 true ,点击取消返回 false。
let isBoss = confirm('Are you the boss?');
alert( isBoss ); //若按下确定,则显示 true
六、类型转换
字符串转换
let value = true;
alert(typeof value); // boolean
value = String(value); // "true"
alert(typeof value); //string
数字型转换
在算术函数和表达式中,会自动的进行 number 类型转换。
比如:当吧除法 “ / ” 用于非 number 类型:
alert("6" / "2"); // 3, string 类型的值被自动转换成 number 类型后进行计算
我们也可以使用 Number(value) 显式的将这个 value 转换为 number 类型。
let str = "123";
alert(typeof str); //string
let num = Number(str); // 变为 number 类型 123
alert(typeof num); // number
如果 Number(str) 内 str 不是一个有效的数字,则会返回 NaN。
number 类型转换规则:
| 值 | 变成…… |
|---|---|
| undefined | NaN |
| null | 0 |
| true 和 false | 1 and 0 |
| string | 去掉首尾空格后的纯数字字符串中含有的数字。如果剩余字符为空,则转换结果为 0 。否则将会从剩余字符串中 “读取” 数字,当类型转换出现 error 时,返回 NaN |
例:
alert(Number(" 123" )); // 123
alert(Number("123z")); // NaN(从字符串读取数字,读到 "z" 时出现错误)
alert(Number(true)); // 1
alert(Number(false)); // 0
布尔型转换
转换规则如下:
- 直观上为 “空” 的值(如 0 、空字符串、null、undefined 和 NaN)将变成 false
- 其他值变成 ture
比如:
alert(Boolean(1)); // true
alert(Boolean(0)); // false
alert(Boolean("hello")); // true
alert(Boolean("")); //false
alert(Boolean("0")); // true
alert(Boolean(" ")); // true (任何非空字符串都是 true)
七、基础运算符-数学
术语:“一元运算符”, “二元运算符”, “运算元”
- 运算元:例: 5 * 2,左运算元 5,右运算元 2。
- 如果一个运算符对应只有一个运算元,那么它是 一元运算符
let x = 1;
x = -x;
alert(x); // -1,一元运算符 负号 生效
- 如果一个运算符拥有两个运算元,那么它是 二元运算符
let x = 1, y = 3;
alert(y - x); // 2, 二元运算符减号做减运算
数学
- 加法
+ - 减法
- - 乘法
* - 除法
/ - 取余
% - 求幂
**
用二元运算符 + 连接字符串
let s = "my" + "string";
alert(s); // mystring
// 只要一个运算符是字符串,那么另外的运算元也会被转化为字符串
alert( '1' + 2 ); //"12"
//运算符是按顺序工作的
alert(2 + 2 + '1'); // "41" 不是 "221"
二元 + 是唯一支持字符串的运算符,其他算术运算符只对数字起作用。
alert(6 - '2'); // 4
alert('6' / 2); // 3
数字转化,一元运算符 +
若加号应用于单个值,对数字没有任何作用,但 对其他运算元,加号会将其 转化为数字。
//无效
let x = 1;
alert(+x); // 1
//转化非数字
alert(+true); // 1
alert(+""); // 0
//在二元运算符加号起作用之前,所有的值都被转化为了数字
let apples = "2";
let orange = "3";
alert(+apples + +oranges); // 5
赋值运算符
赋值 = 返回一个值
let a = 1;
let b = 2;
let c = 3 - (a = b + 1);
alert(a); // 3
alert(c); // 0
链式赋值
let a, b, c;
a = b = c = 2 + 2;
alert(a); // 4
alert(b); // 4
alert(c); // 4
原地修改
let n = 2;
n += 5; // n = 7
n *= 2; // n = 14
自增 / 自减
let counter = 2;
counter++; // 3
counter--; // 2
位运算符
- 按位与
& - 按位或
| - 按位异或
^ - 按位非
~ - 左移
<< - 右移
>> - 无符号右移
>>>
八、空值合并运算符 ‘??’
a ?? b 的结果是:
- 如果 a 是已定义的, 则结果为 a
- 如果 a 不是已定义的,则结果为 b
即如果第一个参数不是 null / undefined 则 ?? 返回第一个参数,否则返回第二个参数。
// 显示第一个已定义的值
let firstName = null;
let lastName = null;
let nickName = "Supercoder";
alert(firstName ?? lastName ?? nickName ?? "Anonymous"); // Supercoder
与 || 比较
// 显示第一个真值
let firstName = null;
let lastName = null;
let nickName = "Supercoder";
alert(firstName ?? lastName ?? nickName ?? "Anonymous"); // Supercoder
九、箭头函数
let func = (arg1, arg2, ...argN) => expression;
// 等同于
let func = function(arg1, arg2, ...argN){
return expression;
}
- 如果只有一个参数,可以省略掉参数外的圆括号
let double = n => n * 2;
- 如果没有参数,应该保留括号
let sayHi = () => alert("Hello!");
- 可以像函数表达式一样使用
let age = prompt("What is your age?", 18);
let welcome = (age < 18) ?
() => alert('Hello'): // 若大于等于18
() => alert('Greetings!'); // 若小于18
welcome();
代码质量
JavaScript 代码规范及调试:
使用 Mocha 进行自动化测试
Mocha 中文网:
Mocha 实例教程:
https://www.ruanyifeng.com/blog/2015/12/a-mocha-tutorial-of-examples.html
开发 “pow” : 规范
在创建函数 pow 的代码之前,我们应该想象函数应该做什么并且描述出来。
这样的描述被称作 规范,包含用例的描述以及针对他们的测试。
describe("pow", function() {
it("raises to n-th power", function() {
assert.equal(pow(2, 3), 8);
});
});
一个规范包含三个主要的模块:
describe("title", function(){ ... })
表示我们正在描述的功能是是什么。
it("use case description", funtion() { ... })
it 里面的描述部分,以一种 易于理解 的方式描述特定的用例,第二个参数是用于对其进行测试的函数。
assert.equal(value1, value2)
it 块中的代码,如果实现是正确的,它应该在执行的时候不产生任何错误。
assert.* 函数用于检查函数(用例中为 pow)是否按照预期工作。在用例中我们使用了其一的
assert.equal 它会对参数进行比较,如果他们不相等则会抛出一个错误,这里他检查了 pow(2, 3) 的值是否等于 8
开发流程
1.编写初始规范,测试最基本的功能。
2.创建一个最初始的实现。
3.检查它是否工作,运行测试框架 Mocha 来运行测试,当功能未完成时,将显示错误,我们将持续修正直到一切都能工作。
4.现在我们拥有一个带有测试的能工作的初步实现。
5.增加更多的用例到规范中。
6.回到第 3 步,更新程序直到测试不会抛出错误。
7.重复 3 – 6 步,直到功能完善。
行为规范
- Mocha —— 核心框架,提供了包括通用型测试函数
describe和it,以及用于测试的主函数。 - Chai —— 提供很多断言支持的库
- Sinon —— 用于监视函数、模拟内置函数和其他函数的库
<!DOCTYPE html>
<html>
<head>
<!-- add mocha css, to show results -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/mocha/3.2.0/mocha.css">
<!-- add mocha framework code -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/mocha/3.2.0/mocha.js"></script>
<script>
mocha.setup('bdd'); // minimal setup
</script>
<!-- add chai -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/chai/3.5.0/chai.js"></script>
<script>
// chai has a lot of stuff, let's make assert global
let assert = chai.assert;
</script>
</head>
<body>
<script>
function pow(x, n) {
/* function code is to be written, empty now */
}
</script>
<!-- the script with tests (describe, it...) -->
<script src="test.js"></script>
<!-- the element with id="mocha" will contain test results -->
<div id="mocha"></div>
<!-- run tests! -->
<script>
mocha.run();
</script>
</body>
</html>
该页面分为五个部分:
1.<head> —— 添加用于测试的第三方库和样式文件。
2.<script> 包含测试函数,在我们的例子中为和 pow 相关的代码。
3.测试代码 —— 在案例中是名为 test.js 的脚本,它包含 describe("pow", ...) 。
4.HTML 元素 <div id = "mocha"> 将被 Mocha 用来输出结果。
5.可以使用 mocha.run() 命令来开始测试。
改进规范
有时会发现测试通过了,但函数是错误的,这表明我们的规范不完善,需要添加更多的测试用例。
1.第一种 —— 在同一个 it 中再添加一个 assert :
describe("pow", function() {
it("raises to n-th power", function() {
assert.equal(pow(2, 3), 8);
assert.equal(pow(3, 4), 81);
});
});
2.第二种 —— 写两个测试:(推荐)
describe("pow", function() {
it("2 raised to power 3 is 8", function() {
assert.equal(pow(2, 3), 8);
});
it("3 raised to power 4 is 81", function() {
assert.equal(pow(3, 4), 81);
});
});
改进实现
function pow(x, n) {
let result = 1;
for (let i = 0; i < n; i++) {
result *= x;
}
return result;
}
为了确保函数可以很好的工作,除了手动的编写 it 代码块,我们可以用 for 循环生成他们:
describe("pow", function() {
function makeTest(x) {
let expected = x * x * x;
it(`${x} in the power 3 is ${expected}`, function() {
assert.equal(pow(x, 3), expected);
});
}
for (let x = 1; x <= 5; x++) {
makeTest(x);
}
});
嵌套描述
辅助函数 makeTest 应该和 for 组合在一起,只有在 for 中才需要 mackTest 函数。
使用嵌套的 describe 来进行分组:
describe("pow", function() {
describe("raises x to power 3", function() {
function makeTest(x) {
let expected = x * x * x;
it(`${x} in the power 3 is ${expected}`, function() {
assert.equal(pow(x, 3), expected);
});
}
for (let x = 1; x <= 5; x++) {
makeTest(x);
}
});
// ……可以在这里写更多的测试代码,describe 和 it 都可以添加在这。
});
Object(对象):基础知识
一、对象
我们可以使用带有可选 属性列表 的花括号 {...} 来创建对象。一个属性就是一个键值对(key : value)。
我们可以用下面两种语法来创建一个空的对象:
let user = new Object(); //构造函数的语法
ler user = {}; //字面量的语法
文本和属性
我们可以在创建对象的时候,将一些属性以键值对的形式放到 {…} 中。
let user = { //一个对象
name: "John", //键 "name", 值 "Johhn"
age: 30 //键 "age", 值 "30"
};
我们可以随时添加、删除和读取文件,可以使用点符号访问属性值:
//读取文件的属性
alert( user.name ); //John
alert( user.age ); //30
//添加布尔类型属性
user.isAdmin = ture;
//用 delete 操作符移除属性
delete user.age;
//可以用多字词语作为属性名,但必须加上引号
let user = {
"likes birds": true
};
注意:使用 const 声明的对象是可以被修改的
const user = {
name: "John"
};
user.name = "Pete";
alert(user.name); // Pete
方括号
对于多词属性,点操作就不能用了,此时有另一种方法,就是使用方括号,可以用于任何字符串。
let user = {};
user["likes birds"] = true;
alert(user["likes birds"]);
delete user["likes birds"];
方括号同时提供了一种通过任意表达式来获取属性名的方法
let key = "likes birds";
user[key] = true;
//变量 key 也可以是用户输入得到的
let user = {
name: "John",
age: 30
};
let key = prompt("What do you want to know about the user?", "name");
//访问变量
alert( user[key] ); //John (如果输入 "name")
计算属性
当创建一个对象时,我们可以在对象字面量中使用方括号,这叫做 计算属性。
let fruit = prompt("Which fruit to buy?", "apple");
let bag = {
[fruit]: 5,
};
alert( bag.apple ); //5, (如果fruit="apple")
我们可以在方括号中使用更复杂的表达式
let fruit = "apple";
let bag = {
[fruit + "Computers"]: 5 //bag.appleComputers = 5
};
属性值简写
在实际开发中,通常用已存在的变量当做属性名。
function makeUser(name, age) {
return {
name: name,
age: age,
//...其他属性
};
}
let user = makeUser("John", 30);
alert(user.name); //John
有一种更特殊的 属性值缩写 方法,使属性名变得更短。
function makeUser(name, age) {
return {
name, //与 name: name 相同
age,
//...
}
}
属性名称限制
对象的属性名不受保留字的限制 (除了_proto_)
let obj = {
for: 1,
let: 2,
return: 3
};
alert( obj.for + obj.let + obj.return ); // 6
属性名可以是任何字符串或者 symbol,其他类型会自动转换为字符串。
let obj = {
0: "test" // 等同于 "0": "test"
};
alert( obj["0"] ); // test
alert( obj[0] ); // test
属性存在性测试,“in” 操作符
JavaScript 的对象能够被访问任何属性,属性不存在只会得到 undefined。
这里我们可以用 == 来判断,也可以用操作符 “in”
"key" in object
// 例:
let user = {
name: "John",
age: 30
};
alert( "age" in user ); // true
alert( "blabla" in user ); // false
注意: in 的左边必须是属性名,如果省略引号,意味着左边是一个变量
let user = {
age: 30
};
let key = "age";
alert( key in user ); // true, 属性 "age" 存在
“for…in” 循环
for (key in object) {
// 对此对象属性中的每个键执行的代码
}
// 例如,列出 user 所有的属性
let user = {
name: "John",
age: 30,
isAdmin: true
};
for (let key in user) {
// keys
alert( key ); // name, age, isAdmin
// 属性键的值
alert( user[key] ); // John, 30, true
}
像对象一样排序
对象有特别的顺序:整数属性会被进行排序,其他属性则按照创建的顺序显示。
let codes = {
"49": "Germany",
"41": "Switzerland",
"44": "Great Britain",
//...
"1": "USA"
};
for(let code in codes) {
alert(code); //1, 41, 44, 49
}
所以为了解决某些问题,我们可以使用非整数属性来欺骗程序。
let codes = {
"+49": "Germany",
"+41": "Switzerland",
"+44": "Great Britain",
//...
"+1": "USA"
};
for (let code in codes) {
alert( +code ); // 49, 41, 44, 1
}
二、对象引用和复制
与原始类型相比,对象的根本区别之一就是对象是 通过引用 被储存和复制的,而原始类型始终是以 ”整体值“ 的形式被复制的。
let message = "Hello";
let phrase = massage;
// 这样就有了两个独立的变量,都储存着字符串 "Hello"
赋值了对象的变量存储的不是对象本身,而是该对象在 “内存中的地址”
当一个对象变量被复制,引用则被复制,而该对象并没有被复制
let user = { name: "John"};
let admin = user; // 复制引用
这里仍然只有一个对象,但有两个引用它的变量。
我们可以通过其中任意一个变量来访问该对象并修改它的内容。
let user = { name: 'John'};
let admin = user;
admin.name = 'Pete'; //通过 "admin" 引用来修改
alert( user.name ); // "Pete", 修改能通过 "user" 引用看到
克隆与合并, Object.assign
如果我们想要复制一个对象,则需要创建一个新对象,并通过遍历现有属性的结构,在原始类型的层面,将其复制到新对象,以此复制已有对象的结构。
let user = {
name: "John",
age: 30
};
let clone = {}; // 新的空对象
// 将 user 中所有的属性拷贝到其中
for (let key in user) {
clone[key] = user[key];
}
// 现在 clone 是带有相同内容的完全独立的对象
clone.name = "Pete";
alert( user.name ); // user 中的 name 属性仍然是 John
我们也可以使用 Object.assign 方法来达成同样的效果
Object.assign(dest, [src1, src2, src3...])
- 第一个参数 dest 是指目标对象
- 更后面的参数 src1, …, srcN 是源对象
- 该方法将所有源对象的属性拷贝到目标对象 dest 中,
- 调用结果返回 dest
例如,用它来合并多个对象
let user = { name: "John" };
let permission1 = { canView: true };
let permission2 = { canEdit: true };
//将 permission1 和 permission2 中的所有属性都拷贝到 user 中
Object.assign(user, permission1, permission2);
//现在 user = { name: "John", canView: true, canEdit: true };
如果被拷贝的属性名已经存在,那么它会被覆盖。
我们也可以用 Object.assign 代替 for...in 循环来进行简单克隆:
let user = {
name: "John",
age: 30
};
//将 user 中的所有属性拷贝到一个空对象中,并返回这个新的对象
let clone = Objectl.assign({}, user);
深层克隆
属性可以是对其他对象的引用,这样应该怎样处理他们呢。
let user = {
naem: "John",
sizes: {
height: 182,
width: 50
}
};
alert( user.sizes.height ); //182
let clone = Object.assign({}, user);
alert( user.sizes === clone.sizes ); //true, 同一个对象
// user 和 clone 分享同一个 sizes
user.sizes.width++ // 通过其中一个改变属性值
alert( cloen.sizes.width ); // 51, 能从另外一个看到变更的结果
为了解决此问题,我们应该使用会检查每个 user[key] 的值的克隆循环,如果值是一个对象,那么也要复制他的结构,这就是深拷贝。
我们可以用递归实现,或者使用 JavaScript 库的 lodash 的 _.cloneDeep(obj)。
JavaScript 的拷贝(克隆)问题
三、垃圾回收
可达性
JavaScript 中主要的内存管理概念是 可达性。
1.这里列出固有的可达值的基本集合,这些值 不能被释放
- 当前函数的局部变量和参数
- 嵌套调用时,当前调用链上所有函数的变量与参数
- 全局变量
这些值被称作 根
2.如果一个值可以通过引用或引用链从根访问任何其他值,则认为该值是可达的。JavaScript 引擎中有个称作 垃圾回收器 的东西在后台执行,它监控所有对象的状态,并删除掉那些已经不可达的。
简单的例子
//user 具有对这个对象的引用
let user = {
name: "John"
};
// 重写 user
user = null;
// 现在 John 变成不可达的了,因为没有引用,垃圾回收器会认为它是垃圾数据并进行回收,然后释放内存
两个引用
// user 具有对这个对象的引用
let user = {
name: "John"
};
// 把 user 的引用扶着给 admin
let admin = user;
// 重写 user
user = null;
/* 对象仍然可以通过 admin 这个全局变量访问到,所以对象还在内存中,但如果我们又重写了 admin ,对象就会被删除 */
四、对象方法 “this”
方法示例
作为对象属性的函数被称为 方法。
也可以使用预先声明的函数作为方法:
let user = {
//...
};
// 声明函数
function sayHi() {
alert("Hello!";)
};
// 然后将其作为一个方法添加
user.sayHi = sayHi;
user.sayHi(); // Hello!
方法简写
user = {
sayHi: function() {
alert("Hello");
}
};
let user = {
sayHi() { // 与 "sayHi: function()" 一样
alert("Hello");
}
};
方法中的 “this”
通常,对象需要访问对象中存储的信息才能完成其工作。
例如 user.sayHi() 中的代码可能需要用到 user 的 name 属性。
为了访问该对象,方法中可以使用 this 关键字。
let user = {
name: "John",
age: 30,
sayHi() {
// this 指的是当前的对象
alert(this.name);
}
};
user.sayHi(); // John
“this” 不受限制
在 JavaScript 中,this 关键字可以用于任何函数,即使它不是对象的方法。
function sayHi() {
alert( this.name );
}
this 的值是在代码运行时计算出来的,它取决于代码上下文。
let user = { name: "John" };
let admin = { name: "Admin" };
function sayHi() {
alert( this.name );
}
// 在两个对象中使用相同的函数
user.f = sayHi;
admin.f = sayHi;
// 这两个调用有不同的 this 值
// 函数内部的 "this" 是 “点符号” 前面的那个对象
user.f(); // John ( this == user )
admin.f(); // Admin ( this == admin)
箭头函数没有自己的 “this”
箭头函数有些特别:他们没有自己的 this 。如果在这函数中引用 this ,this 值取自外部 ”正常的函数“。
例:这里的 arrow() 使用的 this 来自外部的 user.sayHi() 方法
let user = {
firstName: "Ilya",
sayHi() {
let arrow = () => alert( this.firstName );
arrow();
}
};
user.sayHi(); // Ilya
若我们并不想要一个独立的 this ,想从 外部上下文 中获取时,可以使用箭头函数。
五、构造器和操作符 “new”
常规的 {...} 语法允许创建一个对象,但是我经常需要创建许多类似的对象,例如多个用户或菜单项等。
这里可以使用构造函数和 new 操作符来实现。
构造函数
构造函数在技术上是常规函数,但有两个规定:
1.他们的命名以 大写字母开头。
2.他们 只能由 new 操作符 来执行。
function User(name) {
this.name = name;
this.isAdmin = false;
}
let user = new User("Jack");
alert(user.name); // Jack
alert(user.isAdmin); // false
当一个函数被使用 new 操作符执行时,它按照以下步骤:
1.一个新的空对象别创建并分配给 this。
2.函数体执行,通常会修改 this ,为其添加新的属性。
3.返回 this 的值。
构造器的 return
- 如果 return 返回的是一个对象,则返回这个对象,而不是 this
- 如果 return 返回的是一个原始类型,则忽略
换句话说,带有对象的 return 返回该对象,在所有其他情况下返回 this.
例:这里 return 通过返回一个对象覆盖 this
function BigUser() {
this.name = "John";
return { name: "Godzilla" }; // 返回这个对象
}
alert( new BigUser().name ); // Godzilla ,得到了那个对象
// return 为空的例子
function SmallUser() {
this.name = "John";
return; // 返回 this
}
alert( new SmallUser().name ); // John
构造器中的方法
使用构造器函数来创建对象会带来很大的灵活性。
例:
function User(name) {
this.name = name;
this.sayHi = function() {
alert( "My name is: " + this.name );
};
}
let john = new User("John");
john.sayHi(); // My name is: John
六、可选链 “?.”
“不存在的属性” 的问题
假设我们有很多个 user 对象,其中存储了我们的用户数据。
大多数用户的地址都存储在 user.address 中,街道地址存储在 user.address.street 中,但有些用户没有提供这些信息,在这种情况下,当我们尝试获取 user.address.street, 而该用户恰好没有提供信息,我们则会收到一个错误:
let user = {}; // 一个没有 "address" 属性的 user 对象
alert( user.address.street ); // Error
我们希望避免出现这种错误,而是接收 null 作为结果。
最先想到的是使用 if 或 ? 对该值进行检查:
let user = {};
alert( user.address ? user.address.street : undefined);
这虽然可以避免错误,但是对于多重嵌套的属性就会出现更多重复的代码,于是可以使用 && 运算符:
let user = {};
alert( user.address && user.address.stree && user.street.name ); // undefined (不报错)
依次对整条路径属性使用与运算进行了判断,确保所有节点是存在的,但仍不够优雅,于是 可选链 ?. 被加入到了 JavaScript中。
可选链
如果可选链 ?. 前面的部分是 undefined 或者 null ,它会停止运算并返回该部分。
例如 value?.prop:
- 如果 value 存在,则结果与 value.prop 相同
- 否则(当 value 为 undefined / null 时)则返回 undefined
下面是一种使用 ?. 安全的访问 user.address.street 的方式:
let user = {}; // user 没有 address 属性
alert( user?.address?.street ); // undefined (不报错)
注意:?. 语法使其前面的值成为可选值,但不会对其后面起作用。
例如:在 user?.address.street.name 中,?. 允许 user 为 null / undefined,但仅此而已。
注意:
- 不要过度使用可选链应该只讲 ?. 使用在一些 可以不存在 的地方。
- ?. 前的变量必须已声明如果未声明变量 user ,那么 user.anything 会触发一个错误
ReferenceError: user is not defined。
短路效应
如果 ?. 左边部分不存在,就会立即停止运算(短路效应)
let user = null;
let x = 0;
user?.sayHi(x++); // 没有 "sayHi" ,因此代码没有执行 x++
alert(x); // 0
其他变体: ?.(), ?.[]
可选链是一个特殊的语法结构,还可以与函数和方括号一起使用
let userAdmin = {
admin() {
alert("I am admin");
}
};
let userGuest = {};
userAdmin.admin?.(); // I am admin
userGuest.admin?.(); // 什么都没有
此外,我们还可以将 ?. 跟 delete 一起使用
delete user?.name; // 如果 user 存在,则删除 user.name
七、Symbol 类型
Symbol
“Symbol” 值表示唯一的标识符
可以使用 Symbol() 来创建这种类型的值,创建时,可以给 Symbol 一个描述:
// id 是描述为 "id" 的Symbol, id 是 Symbol 的一个实例化对象
let id = Symbol("id");
// Symbol 保证是唯一的
let id1 = Symbol("id");
let id2 = Symbol("id");
alert( id1 == id2 ) // false
隐藏属性
Symbol 允许我们创建对象的 “隐藏”属性,代码的任何其他部分不能意外访问或重写这些属性。
let user = { // 属于另一个代码
name: "John";
};
let id = Symbol("id");
user[id] = 1;
alert( user[id] ); // 使用 Symbol 作为键来访问
对象字面量中的 Symbol
如果我们要在对象字面量 {...} 中使用 Symbol,则需要用方括号括起来。
let id = Symbol("id");
let user = {
name: "John",
[id]: 123 // 不是 "id": 123
}; // 这是因为我们需要变量 id 作为键而不是字符串
Symbol 在 for…in 中会被跳过
let id = Symbol("id");
let user = {
name: "John",
age: 30,
[id]: 123
};
for (let key in user) alert(key); // name, age (no symbols)
// 使用 Symbol 任务直接访问
alert( "Direct: " + user[id] );
全局 symbol
通常所有的 Symbol 都是不同的,但有时我们想要名字相同的 Symbol 具有相同的实体。
为了实现这一点,有一个 全局 Symbol 注册表。我们可以在其中创建 Symbol 并在稍后访问他们。
从注册表中读取(不存在则创建)Symbol,Symbol.for(key)。
// 从全局注册表中读取
let id = Symbol.for("id"); // 如果该 Symbol 不存在,则创建它
// 再次读取(可能是在代码中的另一个位置)
let idAgain = Symbol.for("id");
// 相同的 Symbol
alert( id === idAgain ); // true
Document
一、浏览器环境,规格
文档对象模型(DOM)
文档对象模型,将所有页面内容表示为可以修改的对象。
document 对象是页面的主要 “入口点” ,我们可以使用它来更改或创建页面上的任何内容。
// 将背景颜色修改为红色
document.body.style.background = "red";
// 在一秒后将其修改回来
setTimeout(() => document.body.style.background = "", 1000);
浏览器对象模型(BOM)
浏览器对象模型,表示由浏览器(主机环境)提供的用于处理文档(document)之外的所有内容的其他对象。
例:
- navigator 对象提供了有关浏览器和操作系统的背景信息,例如
navigator.platform— 关于平台(帮助区分 Windows / Linuw / Mac 等),navigator.userAgent— 关于当前浏览器。 - location 对象允许我们读取当前 URL,并且可以将浏览器重定向到新的 URL。
// 使用 location 对象的方法
alert(location.href); // 显示当前 URL
if (confirm("Go to Wikipedia?")) {
location.href = "https://wikipedia.org"; // 将浏览器重定向到另一个 URL
}
函数 alert / confirm / prompt 也是 BOM 的一部分。
二、DOM 树
HTML 文档的主干是标签(tag)。
根据文档对象模型(DOM),每个 HTML 标签都是一个对象,嵌套的标签是闭合标签的 ”子标签“,标签内的文本也是一个对象。
例如:document.body 是表示 <body> 标签的对象
document.body.style.background = "red"; // 将背景设置为红色
setTimeout(() => document.body.style.background = '', 3000); // 恢复
除了用 style.background 来修改 document.body 的背景颜色,还有其他属性,如:
innerHTML— 节点的 HTML 内容offsetWidth— 节点的宽度(以像素度量)- …
DOM 的例子
<!DOCTYPE HTML>
<html>
<head>
<title>About ekl</title>
</head>
<body>
The truth about elk.
</body>
</html>
DOM 将 HTML 表示为标签的树形结构:
- HTML
- HEAD
- #text ↵␣␣␣␣
- TITLE
- #text About elk
- #text ↵␣␣
- #text ↵␣␣
- BODY
- #text The truth about elk.
- HEAD
标签被称为 元素节点 ,形成了树状结构:<html> 在根节点,<head> 和 <body> 是其子项。
元素内的文本形成 文本节点, 被标记为 #text 。一个文本节点只包含一个字符串,他没有子项,且总是树的叶子。
注意文本节点中的特殊字符:
- 换行符:↵(在JavaScrip 中为 \n )
- 空格:␣
空格和换行符都是完全有效的字符,他们形成文本节点并称为 DOM 的一部分。
有两个顶级排除项:
1.<head> 之前的空格和换行符均被忽略。
2.付过在 </body> 后放置一些东西,那么他会被自动移动到 body 内,并处于最下方,因为 HTML 规范要求所有内容必须位于 <body> 内。
自动修正
如果浏览器遇到格式不正确的 HTML ,它会在形成 DOM 时自动更正它。
<p>Hello
<li>Mom
<li>and
<li>Dad
一个没有关闭标签的文档将成为一个正常的 DOM, 因为浏览器在读取标签时会填补缺失的部分:
- HTML
- HEAD
- BODY
- p
- #text Hello
- li
- #text Mom
- li
- #text and
- li
- #text Dad
- p
其他节点类型
除了元素和文本节点,还有其他的节点类型,例如注释:
<!DOCTYPE HTML>
<html>
<body>
The truth about elk.
<ol>
<li>An elk is a smart</li>
<!-- comment -->
<li>...and cunning animal!</li>
</ol>
</body>
</html>
- HTML
- HEAD
- BODY
- #text The truth about elk.
- ol
- #text ↵␣␣␣␣␣␣
- li
- #text An elk is a smart
- #text ↵␣␣␣␣␣␣
- #comment comment
- #text ↵␣␣␣␣␣␣
- li
- #text …and cunning animal!
- #text ↵␣␣␣␣
- #text ↵␣␣↵
在这可以看到一个新的树节点类型 — comment node, 被标记为 comment。
HTML中的所有内容,甚至注释,都会称为 DOM 的一部分
一共有 12种节点类型 ,通常用到其中 4 种:
document— DOM 的 “入口点”- 元素节点 — HTML 标签,树构建块
- 文本节点 — 包含文本
- 注释 — 他不会显示,但 JS 可以从 DOM 中读取它
三、搜索:getElement, querySelector
document.getElementById 或者只使用 id
如果一个元素有 id 特性, 我们就可以使用 document.getElementById 方法获取该元素。
<div id="elem">
<div id="elem=content">Element</div>
</div>
<script>
// 获取该元素
let elem = document.getElementById("elem");
// 将该元素背景改为红色
elem.style.background = "red";
</script>
document.getElementsByTagName
使用 document.getElementsByTagName 方法可以返回带有指定标签名的 对象的集合
注意:
- 得到的是一个对象的集合(伪数组),所以需要遍历去访问
- 得到元素对象是动态的
- 还可以获取某个元素内部所有指定标签名的子元素
querySelectorAll
它返回 elem 中与给定 CSS 选择器匹配的 所有元素。
例:查找所有为最后一个子元素的 <li> 元素;
<ul>
<li>The</li>
<li>test</li>
</ul>
<ul>
<li>has</li>
<li>passed</li>
</ul>
<script>
let elements = document.querySelectorAll('ul > li:last-child');
for(let elem of elements) {
alert( elem.innerHTML ); // "test", "passed"
}
</script>
- 也可以使用伪类例如:
document.querySelectorAll(':hover')会返回鼠标指针现在已经结束的元素的集合(按嵌套顺序:从最外层<html>到嵌套最多的元素
querySelector
elem.querySelector(css) 调用会返回给定 CSS 选择器的第一个元素
注意需要加标点 .box #nav
matches
elem.matches(css) 不会查找任何内容,只会检查 elem 是否与给定的 CSS 选择器匹配。它返回 true 或者 false.
当我们遍历元素并想过滤我们不感兴趣的元素时,可以使用这个方法。
<a href="http://example.com/file.zip">...</a>
<a href="http://ya.ru">...</a>
<script>
for (let elem of document.body.children) {
if (elem.matches('a[href$="zip"]')) {
alert("The archive reference: " + elem.href );
}
}
</script>
closest
elem.closest(css) 方法会查找与 CSS 选择器匹配的最近的祖先。elem 自己也会被搜索。
<h1>Contents</h1>
<div class="contents">
<ul class="book">
<li class="chapter">Chapter 1</li>
<li class="chapter">Chapter 1</li>
</ul>
</div>
<script>
let chapter = document.querySelector('.chapter'); // li
alert(chapter.closest('.book')); // ul
alert(chapter.closest('.contents')); //div
alert(chapter.closest('h1')); // null ,h1 不是祖先
</script>
getElementsBy*
elem.getElementsByTagName(tag)查找具有给定标签的元素,并返回他们的集合。tag 参数也可以是对于任何标签的星号 “ * ”。elem.getElenmentsByClassName(className)返回具有给定 CSS 类的元素document.getElementsByName(name)返回在文档范围内具有给定 name 特性的元素
// 获取文档中所有 div
let divs = document.getElementsByTagName('div');
实时的集合
所有的 getElementsBy* 方法都会返回一个 实时的集合 。这集合会在文档发生更改时 自动更新。
<div>First div</div>
<script>
let divs = document.getElementsByTagName('div');
alert(divs.length); // 1
</script>
<div>Second div</div>
<script>
alert(divs.length); // 2
</script>
相反 querySelectorAll 返回的是一个 静态的集合。
<div>First div</div>
<script>
let divs = document.querySelectorAll('div');
alert(divs.length); // 1
</script>
<div>Second div</div>
<script>
alert(divs.length); // 1
</script>
四、节点属性: type, tag 和 content
DOM 节点类
不同的 DOM 节点可能有不同的属性。例如,标签 <a> 相对应的元素节点具有链接相关的(link-related)属性,标签 <input> 相对应的元素节点具有与输入相关的属性,等。这些标签对应的 DOM 节点之间也存在共有的属性和方法,所有类型的 DOM 节点都形成了一个单一层次结构。
每个DOM节点都术语相应的内建类。
层次结构的根节点是 EventTarget,Node 继承自它,其他 DOM 节点继承自 Node。
类如下所示:
- EventTarget — 是根的“抽象(abstract)”类。该类的对象从未被创建。它作为一个基础,以便让所有 DOM 节点都支持所谓的“事件(event)”,我们会在之后学习它。
- Node — 也是一个“抽象”类,充当 DOM 节点的基础。它提供了树的核心功能:
parentNode,nextSibling,childNodes等(它们都是 getter)。Node类的对象从未被创建。但是有一些继承自它的具体的节点类,例如:文本节点的Text,元素节点的Element,以及更多异域(exotic)类,例如注释节点的Comment。 - Element — 是 DOM 元素的基本类。它提供了元素级的导航(navigation),例如
nextElementSibling,children,以及像getElementsByTagName和querySelector这样的搜索方法。浏览器中不仅有 HTML,还会有 XML 和 SVG。Element类充当更多特定类的基本类:SVGElement,XMLElement和HTMLElement。 - HTMLElement — 最终是所有 HTML 元素的基本类。各种 HTML 元素均继承自它:
- HTMLInputElement —
<input>元素的类, - HTMLBodyElement —
<body>元素的类, - HTMLAnchorElement —
<a>元素的类, - ……等,每个标签都有自己的类,这些类可以提供特定的属性和方法。
- HTMLInputElement —
我们可以通过回调来查看 DOM 节点类名,因为对象通常都具有 constructor 属性。它引用类的 constructor,constructor.name 就是它的名称:
alert( document.body.constructor.name ); // HTMLBodyElement
……或者我们可以对其使用 toString 方法:
alert( document.body ); // [object HTMLBodyElement]
或使用 instanceof 来检查继承:
alert( document.body instanceof HTMLBodyElement ); // true
alert( document.body instanceof HTMLElement ); // true
alert( document.body instanceof Element ); // true
alert( document.body instanceof Node ); // true
alert( document.body instanceof EventTarget ); // true
“nodeType” 属性
nodeType 属性提供了另一种用来获取 DOM 节点类型的方法。
- 对于元素节点
elem.nodeType == 1 - 对于文本节点
elem.nodeType == 3 - 对于 document 对象
elem.nodeType == 9
标签:nodeName 和 tagName
给定一个 DOM 节点,我们可以从 nodeName 或者 tagName 属性中读取它的标签名:
alert( document.body.nodeName ); // BODY
alert( document.body.tagName ); // BODY
tagName 仅受元素节点支持(因为它起源于 Element 类),而 nodeName 则可以说明其他节点类型。
<body><!-- comment -->
<script>
// for comment
alert( document.body.firstChild.tagName ); // undefined(不是一个元素)
alert( document.body.firstChild.nodeName ); // #comment
// for document
alert( document.tagName ); // undefined(不是一个元素)
alert( document.nodeName ); // #document
</script>
</body>
innerHTML: 内容
innerHTML 属性允许将元素中的 HTML 获取为字符串形式。
<body>
<p>A paragraph</p>
<div>A div</div>
<script>
alert( document.body.innerHTML ); // 读取当前内容
document.body.innerHTML = 'The new BODY!'; // 替换它
</script>
</body>
注意:“innerHTML +=” 会进行完全重写
outerHTML: 元素的完整 HTML
outerHTML 属性包含了元素的完整 HTML。
<div id="elem">Hello <b>World</b></div>
<script>
alert(elem.outerHTML); // <div id="elem">Hello <b>World</b></div>
</script>
注意:与 innerHTML 不同,写入 outerHTML 不会改变元素。而是在 DOM 中替换他。
<div>Hello, world!</div>
<script>
let div = document.querySelector('div');
// 使用 <p>...</p> 替换 div.outerHTML
div.outerHTML = '<p>A new element</p>'; // (*)
// 蛤!'div' 还是原来那样!
alert(div.outerHTML); // <div>Hello, world!</div> (**)
</script>
outerHTML 赋值不会修改 DOM 元素,而是将其从 DOM 中删除并在其位置插入新的 HTML。
在 div.outerHTML=... 中发生的事为:
div被从文档(document)中移除。- 另一个 HTML 片段
<p>A new element</p>被插入到其位置上。 div仍拥有其旧的值。新的 HTML 没有被赋值给任何变量。
nodeValue / data: 文本节点内容
文本节点具有他们的对应项:nodeValue 和 data 属性。
<body>
Hello
<!-- Comment -->
<script>
let text = document.body.firstChild;
alert(text.data); // Hello
let comment = text.nextSibling;
alert(comment.data); // Comment
</script>
</body>
textContent: 纯文本
textContent 提供了对元素内的 文本 的访问权限:仅文本,去掉所有 <tags>。
<div id="news">
<h1>Headline!</h1>
<p>Martians attack people!</p>
</div>
<script>
// Headline! Martians attack people!
alert(news.textContent);
</script>
textContent ,允许以 “安全方式” 写入文本
假设有一个用户输入任意字符串,我们希望将其显示出来
- 使用
innerHTML,我们将其 “作为 HTML” 插入,带有所有 HTML 标签。 - 使用
textContent,我们将其 “作为文本” 插入,所有符号均按字面意义处理。
<div id="elem1"></div>
<div id="elem2"></div>
<script>
let name = prompt("What's your name?", "<b>Winnie-the-Pooh!</b>");
elem1.innerHTML = name;
elem2.textContent = name;
</script>
1.第一个 div 获取 name “作为 HTML” :所有标签都变为标签,我们可以看到粗体的 name。
2.第二个 div 获取 name “作为文本” :我们可以从字面上看到 <b>Winner-the-Pooh!</b>
“hidden” 属性
我们可以通过 hidden 指定元素是否可见。
<div>Both divs below are hidden</div>
<div hidden>With the attribute "hidden"</div><!--不可见-->
<div id="elem">JavaScript assigned the property "hidden"</div>
<!--不可见-->
<script>
elem.hidden = true;
</script>
或者用于作为 blinking 元素:
<div id="elem">A blinking element</div>
<script>
setInterval(() => elem.hidden = !elem.hidden, 1000);
</script>
<!--A blinking element 这一文本每1秒出现一次,即闪烁效果-->
五、特性和属性
DOM 属性
DOM 节点是常规的 JavaScript 对象。我们可以 alert 它们。
document.body.myData = {
name: 'Caesar',
title: 'Imperator'
};
alert(document.body.myData.title); // Imperator
我们还可以修改内建属性的原型,例如修改 Element.prototype 为所有元素添加一个新方法:
Element.prototype.sayHi = function() {
alert(`Hello, I'm ${this.tagName}`);
};
document.documentElement.sayHi(); // Hello, I'm HTML
document.body.sayHi(); // Hello, I'm BODY
DOM 属性和方法的行为就像常规的 Javascript 对象一样:
- 它们可以有很多值。
- 它们是大小写敏感的(要写成
elem.nodeType,而不是elem.NoDeTyPe)。
HTML 特性
在 HTML 中,标签可能拥有特性(attributes)。当浏览器解析 HTML 文本,并根据标签创建 DOM 对象时,浏览器会辨别 标准的 特性并以此创建 DOM 属性。
所以,当一个元素有 id 或其他 标准的 特性,那么就会生成对应的 DOM 属性。但是非 标准的 特性则不会。
<body id="test" something="non-standard">
<script>
alert(document.body.id); // test
// 非标准的特性没有获得对应的属性
alert(document.body.something); // undefined
</script>
</body>
如果一个特性不是标准的,那么就没有相对应的 DOM 属性。
所有特性都可以通过使用以下方法进行访问:
elem.hasAttribute(name)— 检查特性是否存在。elem.getAttribute(name)— 获取这个特性值。elem.setAttribute(name, value)— 设置这个特性值。elem.removeAttribute(name)— 移除这个特性。
示例:
<body>
<div id="elem" about="Elephant"></div>
<script>
alert( elem.getAttribute('About') ); // (1) 'Elephant',读取
elem.setAttribute('Test', 123); // (2) 写入
alert( elem.outerHTML ); // (3) 查看特性是否在 HTML 中(在)
for (let attr of elem.attributes) { // (4) 列出所有
alert( `${attr.name} = ${attr.value}` );
}
/*
id = elem
about = Elephant
test = 123
*/
</script>
</body>
非标准的特性,dataset
有时,非标准的特性常常用于将自定义的数据从 HTML 传递到 JavaScript,或者用于为 JavaScript “标记” HTML 元素。
<!-- 标记这个 div 以在这显示 "name" -->
<div show-info="name"></div>
<!-- 标记这个 div 以在这显示 "age" -->
<div show-info="age"></div>
<script>
// 这段代码找到带有标记的元素,并显示需要的内容
let user = {
name: "Pete",
age: 25
};
for(let div of document.querySelectorAll('[show-info]')) {
// 在字段中插入相应的信息
let field = div.getAttribute('show-info');
div.innerHTML = user[field]; // 首先 "name" 变为 Pete,然后 "age" 变为 25
}
</script>
它们还可以用来设置元素的样式。
例如,这里使用 order-state 特性来设置订单状态:
<style>
/* 样式依赖于自定义特性 "order-state" */
.order[order-state="new"] {
color: green;
}
.order[order-state="pending"] {
color: blue;
}
.order[order-state="canceled"] {
color: red;
}
</style>
<div class="order" order-state="new">
A new order.
</div>
<div class="order" order-state="pending">
A pending order.
</div>
<div class="order" order-state="canceled">
A canceled order.
</div>
为什么使用特性比使用 .order-state-new,.order-state-pending,.order-state-canceled 这些样式类要好?
因为特性值更容易管理。我们可以轻松地更改状态:
// 比删除旧的或者添加一个新的类要简单一些
div.setAttribute('order-state', 'canceled');
但自定义的特性也存在问题,例如自定义的非标准的特性被引入到了标准中并有了自己的用途。
为了避免冲突,存在 data-* 特性。
所有以 data- 开头的特性均被保留供程序员使用。他们可在 dataset 属性中使用
例如,如果一个 elem 有一个名为 "data-about" 的特性,那么可以通过 elem.dataset.about 取到它。
<body data-about="Elephants">
<script>
alert(document.body.dataset.about); // Elephants
</script>
六、修改文档
DOM 修改是创建 实时页面 的关键
例子:展示一条消息
我们将在页面上添加一条比 alert 更好看的消息。外观如下:
<style>
.alert {
padding: 15px;
border: 1px solid #d6e9c6;
border-radius: 4px;
color: #3c763d;
background-color: #dff0d8; /* 一个绿色的盒子 */
}
</style>
<div class="alert">
<strong>Hi there!</strong> You've read an important message.
</div>
这是一个 HTML 示例。现在,让我们使用 JavaScript 创建一个相同的 div(假设样式已经在 HTML/CSS 文件中)。
创建一个元素
要创建 DOM 节点,这里有两种方法:
document.createElement(tag)
用给定的标签创建一个新 元素节点(element node):
let div = document.createElement('div');
document.createTextNode(text)
用给定的文本创建一个 文本节点:
let textNode = document.createTextNode('Here I am');
大多数情况下,我们需要为此消息创建像 div 这样的元素节点。
创建一条消息
// 1. 创建 <div> 元素
let div = document.createElement('div');
// 2. 将元素的类设置为 "alert"
div.className = "alert";
// 3. 填充消息内容
div.innerHTML = "<strong>Hi there!</strong> You've read an important message.";
它到目前为止只是一个变量,尚未在页面中,我们无法在页面上看到它。
插入方法
为了让 div 显示出来,我们需要将其插入到 document 的某处。
对此有一个特殊的方法 document.body.append(div)。
<style>
.alert {
padding: 15px;
border: 1px solid #d6e9c6;
border-radius: 4px;
color: #3c763d;
background-color: #dff0d8;
}
</style>
<script>
let div = document.createElement('div');
div.className = "alert";
div.innerHTML = "<strong>Hi there!</strong> You've read an important message.";
document.body.append(div); //-------------
</script>
node.append(...nodes or strings)—— 在node末尾 插入节点或字符串,node.prepend(...nodes or strings)—— 在node开头 插入节点或字符串,node.before(...nodes or strings)—— 在node前面 插入节点或字符串,node.after(...nodes or strings)—— 在node后面 插入节点或字符串,node.replaceWith(...nodes or strings)—— 将node替换为给定的节点或字符串。
下面是使用这些方法将列表项添加到列表中,以及将文本添加到列表前面和后面的示例:
<ol id="ol">
<li>0</li>
<li>1</li>
<li>2</li>
</ol>
<script>
ol.before('before'); // 将字符串 "before" 插入到 <ol> 前面
ol.after('after'); // 将字符串 "after" 插入到 <ol> 后面
let liFirst = document.createElement('li');
liFirst.innerHTML = 'prepend';
ol.prepend(liFirst); // 将 liFirst 插入到 <ol> 的最开始
let liLast = document.createElement('li');
liLast.innerHTML = 'append';
ol.append(liLast); // 将 liLast 插入到 <ol> 的最末尾
</script>
<!--
before
1.prepend
2.0
3.1
4.2
5.append
after
-->
节点移除
想要移除一个节点,可以使用 node.remove()。
例:让消息在一秒后消失:
<style>
.alert {
padding: 15px;
border: 1px solid #d6e9c6;
border-radius: 4px;
color: #3c763d;
background-color: #dff0d8;
}
</style>
<script>
let div = document.createElement('div');
div.className = "alert";
div.innerHTML = "<strong>Hi there!</strong> You've read an important message.";
document.body.append(div);
setTimeout(() => div.remove(), 1000);//-----------------------
</script>
注意: 如果我们要将一个元素 移动 到另一个地方,则无需将其从原来的位置中删除。
所有插入方法都会自动从旧位置删除该节点。
克隆节点
调用 elem.cloneNode(true) 来创建元素的一个“深”克隆 — 具有所有特性(attribute)和子元素。如果我们调用 elem.cloneNode(false),那克隆就不包括子元素。
<style>
.alert {
padding: 15px;
border: 1px solid #d6e9c6;
border-radius: 4px;
color: #3c763d;
background-color: #dff0d8;
}
</style>
<div class="alert" id="div">
<strong>Hi there!</strong> You've read an important message.
</div>
<script>
let div2 = div.cloneNode(true); // 克隆消息
div2.querySelector('strong').innerHTML = 'Bye there!'; // 修改克隆
div.after(div2); // 在已有的 div 后显示克隆
</script>
<!--
Hi there! You've read an important message.
Bye there! You've read an important message.
-->
七、节点操作
1.父节点
node.parentNode
- parentNode 属性可返回某节点的父节点,(最近的一个父节点)
- 如果指定节点没有父节点则返回 null
2.子节点
parentNode.childNodes //(标准)
parentNOde.children //(非标准)
注意:parentNode.childNodes 返回值包含所有子节点包括元素节点、文本节点等。
若只想要获得里面的元素节点,则需要专门处理:
var ul = document.querySelector("ul");
for (var i = 0; i < ul.childNodes.length; i++) {
if (ul.childNodes[i].nodeType == 1) {
// ul.childNodes[i] 是元素节点
console.log(ul.childNodes[i])
}
}
// 包含全部子节点 包括文本
parentNode.firstChild // 返回第一个子节点
parentNode.lastChild // 返回最后一个子节点
// 只支持 IE9+
parentNode.firstElementChild // 返回第一个子元素节点
parentNode.lastElementChild // 返回最后一个子元素节点
3.兄弟节点
// 包含所有节点包括文本节点
node.nextSibling // 返回当前元素的下一个兄弟节点
node.previousSibling // 返回当前元素上一个兄弟节点
// IE9+
node.nextElementSibling // 返回当前元素下一个兄弟元素节点
node.previousElementSibling // 返回当前元素上一个兄弟节点
4.创建节点
// 创建
document.creatElement("tagName");
// 添加
node.appendChild(child) // 将一个节点添加到指定父节点的子节点列表末尾
node.insertBefore(child,指定元素) // 将一个节点添加到父节点的指定子节点前面
5.删除节点
node.removeChild(child)
6.复制节点(克隆节点)
node.cloneNode()
注意:
1.如果括号参数为 空或者 false,则是浅拷贝,只复制节点本身,不克隆里面的子节点。
2.如果括号参数为 true,则是深拷贝,会复制节点本身和里面的子节点
7.三种动态创建元素的区别
- document.write()
- element.innerHTML
- document.createElement()
区别
1.doucment.write() 是直接将内容写入页面的内容流,当文档流执行完毕,再执行会导致页面重绘。
2.innerHTML 是将内容写入某个 DOM 节点,不会导致页面全部重绘。
3.innerHTML 创建多个元素效率更高(不拼接字符,采取数组形式拼接),结构略复杂。
4.createElement() 创建多个元素效率稍微较低,但结构跟清晰。
八、DOM 总结
1.创建
- document.write
- innerHTML
- createElement
2.增
- appendChild
- insertBefore
3.删
- removeChild
4.改
- 修改元素属性: src、href、title 等
- 修改普通元素内容: innerHTML、innerText
- 修改表单元素: value、type、disabled 等
- 修改元素样式: style、className
5.查
- DOM 提供的 API 方法: getElementById、getElementByTagName
- H5 提供的方法: querySelector、querySelectorAll
- 利用节点操作获取元素: parentNode、children、previousElementSibling、nextElementSibling
6.属性操作
- setAttribute: 设置 dom 的属性值
- getAttribute: 获取 dom 的属性值
- removeAttribute: 移除属性
7.事件操作
| 鼠标事件 | 触发条件 |
|---|---|
| onclick | 鼠标点击触发 |
| onmouseover | 鼠标经过触发 |
| onmouseout | 鼠标离开触发 |
| onfocus | 获得鼠标焦点触发 |
| onblur | 失去鼠标焦点触发 |
| onmousemove | 鼠标移动触发 |
| onmouseup | 鼠标弹起触发 |
| onmousedown | 鼠标按下触发 |
浏览器事件
一、事件简介
事件 是某事发生的信号。所有的 DOM 节点都生成这样的信号(但事件不仅限于 DOM)。
这是最有用的 DOM 事件的列表:
鼠标事件:
click—— 当鼠标点击一个元素时(触摸屏设备会在点击时生成)。contextmenu—— 当鼠标右键点击一个元素时。mouseover/mouseout—— 当鼠标指针移入/离开一个元素时。mousedown/mouseup—— 当在元素上按下/释放鼠标按钮时。mousemove—— 当鼠标移动时。
键盘事件:
keydown和keyup—— 当按下和松开一个按键时。
表单(form)元素事件:
submit—— 当访问者提交了一个<form>时。focus—— 当访问者聚焦于一个元素时,例如聚焦于一个<input>。
Document 事件:
DOMContentLoaded—— 当 HTML 的加载和处理均完成,DOM 被完全构建完成时。
CSS 事件:
transitionend—— 当一个 CSS 动画完成时。
事件处理程序
为了对事件做出相应,可以分配一个 处理程序 — 一个在事件发生时运行的函数。
HTML 特性
处理程序可以设置在 HTML 中名为 on<event> 的特性中。
<input value="Click me" onclick="alert('Click!')" type="button">
在鼠标点击时,onclick 中的代码就会运行。
我们最好在 HTML中调用 JavaScript 函数:
<script>
function countRabbits() {
for(let i=1; i<=3; i++) {
alert("Rabbit number " + i);
}
}
</script>
<input type="button" onclick="countRabbits()" value="Count rabbits!">
DOM 属性
我们可以使用 DOM 属性来分配处理程序。
<input id="elem" type="button" value="Click me">
<script>
elem.onclick = function() {
alert('Thank you');
};
</script>
因为这里只有一个 onclick 属性,所以我们无法分配更多事件处理程序。
<input type="button" id="elem" onclick="alert('Before')" value="Click me">
<script>
elem.onclick = function() { // 覆盖了现有的处理程序
alert('After'); // 只会显示此内容
};
</script>
访问元素:this
处理程序中的 this 的值是对应的元素。就是处理程序所在的那个元素。
下面这行代码中的 button 使用 this.innerHTML 来显示它的内容:
<button onclick="alert(this.innerHTML)">Click me</button>
this 指向的是事件函数的 调用者
可能出现的错误
我们可以将一个现存的函数用作处理程序:
function sayThanks() {
alert('Thanks!');
}
elem.onclick = sayThanks;
注意: 函数应该是以 sayThanks 的形式进行赋值,而不是 sayThanks()。
如果我们添加了括号,那么 sayThanks() 就变成了一个函数调用。所以,最后一行代码实际上获得的是函数执行的 结果。
addEventListener
我们有一种特殊方法 addEventListener 和 removeEnentListenter 来管理处理程序的替代方法。
添加处理程序的语法:
element.addEventListener(event, handler[, options]);
event
事件名,例如:"click"。
handler
处理程序。
options
具有以下属性的附加可选对象:
once:如果为true,那么会在被触发后自动删除监听器。capture:事件处理的阶段。由于历史原因,options也可以是false/true,它与{capture: false/true}相同。passive:如果为true,那么处理程序将不会调用preventDefault()。
移除处理程序:
element.removeEventListener(event, handler[, options]);
多次调用 addEventListener 允许添加多个处理程序:
<input id="elem" type="button" value="Click me"/>
<script>
function handler1() {
alert('Thanks!');
};
function handler2() {
alert('Thanks again!');
}
elem.onclick = () => alert("Hello");
elem.addEventListener("click", handler1); // Thanks!
elem.addEventListener("click", handler2); // Thanks again!
</script>
对于某些事件,只能通过 addEventListener 设置处理程序
例如,DOMContentLoaded 事件,该事件在文档加载完成并且 DOM 构建完成时触发。
// 永远不会运行
document.onDOMContentLoaded = function() {
alert("DOM built");
};
// 这种方式可以运行
document.addEventListener("DOMContentLoaded", function() {
alert("DOM built");
});
事件对象
当事件发生时,浏览器会创建一个 event 对象,将详细信息放入其中,并将其作为参数传递给处理程序。
下面是一个从 event 对象获取鼠标指针的坐标的示例:
<input type="button" value="Click me" id="elem">
<script>
elem.onclick = function(event) {
// 显示事件类型、元素和点击的坐标
alert(event.type + " at " + event.currentTarget);
alert("Coordinates: " + event.clientX + ":" + event.clientY);
};
</script>
event 对象的一些属性:
event.type
事件类型。
event.currentTarget
处理事件的元素,与 this 相同。
event.clientX / event.clientY
指针事件的指针的窗口相对坐标。
对象处理事件:handleEvent
我们可以使用 addEventListener 将一个对象分配为事件处理程序,当事件发生时,就会调用该对象的 handleEvent 方法。
<button id="elem">Click me</button>
<script>
let obj = {
handleEvent(event) {
alert(event.type + " at " + event.currentTarget);
}
};
elem.addEventListener('click', obj);
</script>
我们也可以对此使用一个类:
<button id="elem">Click me</button>
<script>
class Menu {
handleEvent(event) {
switch(event.type) {
case 'mousedown':
elem.innerHTML = "Mouse button pressed";
break;
case 'mouseup':
elem.innerHTML += "...and released.";
break;
}
}
}
let menu = new Menu();
elem.addEventListener('mousedown', menu);
elem.addEventListener('mouseup', menu);
</script>
handleEvent 方法不必通过自身完成所有的工作。它可以调用其他特定于事件的方法,例如:
<button id="elem">Click me</button>
<script>
class Menu {
handleEvent(event) {
// mousedown -> onMousedown
let method = 'on' + event.type[0].toUpperCase() + event.type.slice(1);
this[method](event);
}
onMousedown() {
elem.innerHTML = "Mouse button pressed";
}
onMouseup() {
elem.innerHTML += "...and released.";
}
}
let menu = new Menu();
elem.addEventListener('mousedown', menu);
elem.addEventListener('mouseup', menu);
</script>
在这里,事件处理程序已经分离了出来,更容易进行代码编写和后续维护。
二、操作元素
1.改变元素内容
// 从起始位置到终止位置的内容,但会去除 HTML 标签(不识别标签),同时空格和换行也会去掉
element.innerText
// 起始到终止位置全部内容,包括 HTML 标签,保留空格和换行
element.innerHTML
2.修改元素属性
常用元素的属性操作
1.innerText、innerHTML 改变元素内容
2.src、href
3.id、alt、title
3.修改表单属性
利用 DOM 可以操作如下表单元素的属性:
type、value、checked、selected、disabled
4.修改样式属性
1.element.style 行内样式操作
2.element.className 类名样式操作
- 若样式修改较多,可以采取操作类名方式更改元素样式
- className 会直接更改元素类名,会覆盖原先的类名
class.className = ‘first second’;这样会显示 second 的样式,且继承了 first 的样式。
显示隐藏文本框内容
<script>
// 获取元素
var text = document.querySelector('input');
// 注册事件,获得焦点事件 onfocus
text.onfucus = function() {
if (this.value === '手机') {
this.value = '';
}
// 把文本框例的文字颜色变黑
this.style.color = "#333";
}
// 注册事件 失去焦点事件 onblur
text.onblur = function() {
if(this.value === '') {
this.value = '手机';
}
//失去焦点需要把文本框里面的文字颜色变浅色
this.style.color = '#999';
}
</script>
5.排他思想(算法)
如果有一组元素,若想要某一个元素实现某种样式,则需要用到循环的排他思想算法:
1.清除所有元素样式
2.给当前元素设置样式
<body>
<button>按钮1</button>
<button>按钮2</button>
<button>按钮3</button>
<script>
// 获取所有按钮
var btns = document.getElementsByTagName('button');
for (var i = 0; i < btns.length; i++) {
btns[i].onclick = function() {
// 清除样式
for (var i = 0; i < btns.length; i++) {
btns[i].style.backgroundColor = '';
}
// 设置样式
this.style.backgroundColor = 'pink';
}
</script>
</body>
6.自定义属性
自定义属性目的:为了保存数据到页面中而不用保存到数据库中。
有些自定义属性很容易引起歧义,不易判断是否为自定义。
1.获取属性值
- element.属性 // 获取内置属性值(元素本身自带的属性)
element.getAttribute(‘属性’);// 获得自定义的属性H5 新增:element.dataset.index或者element.dataset['index']
2.设置属性值
element.属性 = ‘值’// 设置内置属性值element.setAttribute(‘属性’, ‘值’);// 针对自定义属性
3.移除属性
element.removeAttribute('属性');
4.设置H5 自定义属性
H5规定自定义属性要 data- 开头作为属性名并且赋值。
例:<div data-index="1">l</div>
或使用 JS 设置 element.setAttribute("data-index", 2);
三、事件高级
1.注册事件
传统注册方式
- 利用 on 开头的事件 onclick
<button onclick = "alert("hi")"></button>btn.onclick = function() {}- 特点: 注册事件的 唯一性
- 同一元素同一事件只能设置一个处理函数,最后注册的处理函数会覆盖前面注册的处理函数
方法监听注册方式
- addEventListener() 它是一个方法
- IE9 之前不支持,可用 attachEvent() 代替
- 特点:同一元素同一事件可用注册多个监听器
- 按注册顺序依次执行
addEventListener () 事件监听方式
eventTarget.addEventListener(type, listener[, useCapture])
btn.addEventListener("click", function() {
alert("hi");
})
eventTarget.addEventListener() 方法将指定的监听器注册到 eventTarget(目标对象)上,当该对象触发指定事件时,会执行事件处理函数。
- type: 事件类型字符串,比如: click、mouseover 注意不要带 on 且要加引号
- listener: 事件处理函数,事件发生时,会调用该监听函数
- useCapture: 可选参数,是一个布尔值,默认是 false
cookie 存储
一、cookie 简介
cookie 是当用户浏览网站时,网站存储在用户计算机上的一个文本文件,记录了用户的 ID、密码、浏览过的网页、停留的事件等信息,网站可以通过读取 cookit 得知用户信息,避免每次访问都登录,或者做出相应的动作。
二、cookie 分类
cookie 分为 临时 cookie (会话 cookie) 和 持久 cookie。
- 会话 cookie 指不设定它的生命周期,浏览器的开启到关闭就是一次会话,当关闭浏览器时,会话 cookie 就会销毁。
- 持久 cookie 则设定了它的生命周期,关闭浏览器后不会销毁,直到设定的过期时间到期,浏览器会把 cookie 保存在硬盘上,可以在同一个浏览器中传递数据,使用户保持登录状态。
三、cookie 的创建、查找、修改和删除
1.创建 cookie
<script>
document.cookie="username = name";
</script>
在页面中保存变量 name 的值到 cookie 中。
2.查找 cookie
<script>
document.cookie="username = jack"
var x = document.cookie;
document.write(x);
</script>
先创建了一个 cookie ,赋值给变量 x ,然后打印到页面中。
3.修改 cookie
<script>
document.cookie="username=jack";
document.cookie="username=mark"; // 新建的 cookie
var x = document.cookie;
document.write(x);
</script>
cookie 的修改类似于创建,旧的 cookie 将被覆盖。
4.删除 cookie
<script>
document.cookie="username=jack";
document.cookie="username=mark;expires=Thu, 01 Jan 1970 00:00:00 GMT";
var x = document.cookie;
document.write(x);
</script>
删除 cookie 只需要将有效期设置为以前的时间即可。
5. cookie 属性
<script>
document.cookie="username=name;path=/";
</script>
| 属性名 | 含义 | 例子 |
|---|---|---|
| path | 读取 cookie 的路径 | path=/; |
| expires | cookie 的过期时间 | expires=Thu 01 Jan 1970 00:00:00 GMT; |
| domain | 读取 cookie 的域名 | domain=taobao.com; |
| secure | 传输加密 | Secure; |
1.path
默认情况下,与创建 cookie 的网页在同一目录或子目录下的所有网页都可以读取 cookie。
path 中区分大小写,若 path=/D:/one 写成 path=/d:/one ,那么就会形成一个新的 cookie。
<script>
// 设置多个值
document.cookie="name=jack,sex=men,age=18";
</script>
2.expires
expires 用来控制 cookie 的生命周期,时间格式必须是标准的 GMT 格式,通常 cookie 时间都采用在当前时间的基础上增加指定时间的方式:
<script>
var expire = new Date(new Date().getTime() + 1*600000); //相对于当前,设置了10分钟的 cookie 有效期
</script>
3.domain
一般网站除了主域名以外,还有很多二级域名,通常处于安全考虑,只允许每个域名访问自己所创建的 cookie ,如果想要多个域共享 cookie 信息,就需要设置 cookie 的 domain 属性。
<script>
document.cookie = "name=jack;domain=baidu.com" // cookie 被百度所有二级域中的页面共享
</script>
4.secure
secure 属性是用来保证 cookie 安全的,表示创建的 cookie 只能在 HTTPS 链接中被浏览器传递到服务器端进行会话验证,如果是 HTTP 连接则不会传递该信息。
<script>
document.cookie="name=jack,sex=men,age=18;domain=baidu.com;secure"
</script>
5.cookie 案例
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>cookie</title>
</head>
<head>
<script>
// 设置 cookie 值的函数,创建一个函数用于存储访问者的名字
function setCookie(username,name) {
var d = new Date();
d.setTime(d.getTime() + 60000); // 设置 cookie 有效时间, cookie 一分钟后到期
// 根据格林威治时间(GMT) 把 Date 对象转换为字符串并返回结果
var expires = "expires=" + d.toGMTString();
document.cookie=username + "=" + name +o ";" + expires;
}
// 获取 cookie 值的函数
function getCookie(username) {
var value=username + "="; // 创建一个用于检索指定 cookie:username+"=" 的变量 value
var arr = document.cookie.split(';');
for(var i=0; i<arr.length; i++) {
var a = arr[i];
if (a.indexOf(value) == 0) { // 找到 cookie 值
return a.substr(value.length); // 返回 cookie 值
}
}
return ""; // 若没有找到 cookie,返回 ""
}
// 检查是否有 cookie 值的函数
function checkCookie() {
var user = getCookie("users"); // 调用 getCookie 函数,传入参数,获取 cookie 值 user
// 如果 user 不为空
if(user != ""){
alert(user + "你好欢迎再次访问");
}
else {
uesr = prompt("请输入你的姓名:"); // 弹窗
if (user != "" && user != null){
setCookie("users",user); // 调用函数,传参,创建 cookie
}
}
}
</script>
</head>
<body onload="checkCookie()"></body>
</html>