教程前期准备
预备知识 | 目标 |
---|---|
计算机基础知识,初步理解 HTML 和 CSS。 | 初步了解 JavaScript,包括一些概念、用途、嵌入网站的方法。 |
什么是javascript?
一种编程语言,它可以在网页上实现复杂的功能,使得网页展现给你的不再是简单的静态信息,而是实时的内容更新、交互式的地图、2D/3D 动画、滚动播放的视频等等。JavaScript 是标准 Web 技术蛋糕的第三层,前两层是HTML 和 CSS,这两个也是学习JavaScript前的预备知识。
它是一种解释型语言,运行在客户端(即运行在浏览器里面,但也有运行在服务端的js,比如nodejs),是一种面向对象语言(注意:JavaScript语言和Java是完全不一样的东西,就像雷锋和雷峰塔的关系)
编辑器选择(vs code、 Chrome/Edge)
Chrome/Edge 中console/控制台演示 (如 0.2+0.3)
- Chrome
在Chrome打开的界面空白处右键单击,选择最下面的“检查”
弹出界面:
在右边的顶部索引栏中选择“Console”
测试:
分别输入1、2、1+1、5/2…
- Edge
Edge中使用方法与Chrome相同,不过在Edge中会翻译成控制器。
三种方式添加JavaScript
内部的JavaScript
<script>
//Your JavaScript
</script>
外部的JavaScript
<script src="script.js"></script>
内联的JavaScript(不推荐)
<button onclick="creatParagraph()">Click me</button>
vs code中学习JavaScript
示例代码:
<!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">
<link rel="stylesheet" href="styles/style.css">
<title>JavaScript Crash Course</title>
</head>
<body>
<div class="header">
<h1>JavaScriptCrash Course</h1>
</div>
<div class="container"></div>
</body>
<script></script>
</html>
其中style.css
中代码:
body {
text-align: center;
font-family: Arial,Helvetica, sans-serif;
font-size: 18px;
}
.header {
background:#f4f4f4;
border-bottom:#ccc 3px solid;
padding: 20px;
}
.container {
width: 700px;
margin: 30px auto;
}
我的文件关系为:
我们用一个简单的函数alert();
来测试是否成功链接
- 内部的JavaScript
<script>
alert("Hello!");
</script>
效果:
- 外部的JavaScript
<script src="note7.js"></script>
note7.js
中的代码:
alert("Hello"+"World");
效果:
- console使用
js代码
console.log("Hello".length);
效果:
成功输出“Hello”的长度5
可以在浏览器的console中直接写"Hello".length
得到结果,也可以在vscode中利用console.log()
进行在编译器中的运行测试。
语法
JavaScript 借鉴了 Java 的大部分语法,但同时也受到 Awk,Perl 和 Python的影响。JavaScript区分大小写,使用Unicode字符集。
在 JavaScript 中,指令被称为语句(Statement),并用分号(;)进行分隔。但如果一条语句独占一行的话,那么分号是可以省略的,因为ECMAScript 规定了在语句的末尾自动插入分号(ASI)(但我们不建议这么做,虽然不是必需的,但是在一条语句的末尾加上分号是一个好习惯。这个习惯可以大大减少代码中产生 bug 的可能性。)。如果一行中有多条语句,那么这些语句必须以分号分开。
Javascript 源码从左往右被扫描并转换成一系列由 token、控制字符、行终止符、注释和空白字符组成的输入元素。空白字符指的是空格、制表符和换行符等。
- JavaScript注释
JavaScript的注释和其他许多语言相似,使用双斜杠//
或者/* */
。注意不能嵌套注释,会出现语法错误。
变量
什么是变量?
变量的名字又叫做标识符,其需要遵守一定的规则。
一个变量,就是一个用于存放数值的容器。这个数值可能是一个用于累加计算的数字,或者是一个句子中的字符串。变量一个特性是它存放的数值可以改变,另一个特性就是它们能够存储任何的东西——不只是字符串和数字。变量可以存储更复杂的数据,甚至是函数。
变量是用来存储数值的,那么有一个重要的概念需要区分。变量不是数值本身,它们仅仅是一个用于存储数值的容器。你可以把变量想象成一个个用来装东西的纸箱子。
合法的标识符示例:Number_hits
,temp99
,$credit
和 _name
。
JavaScript中有三种变量声明方式:
声明方式 | 作用 | 语法结构 | 举例 |
---|---|---|---|
var | 声明一个函数范围或全局范围的变量,可以选择初始化一个值。 | var varname1 [= value1] [, varname2 [= value2] ... [, varnameN [= valueN]]]; 变量的初始化值可以是任何合法的表达式。默认值为 undefined 。 | var x = 42; 、var y; |
let | 声明一个块作用域的局部变量,可以选择初始化一个值。 | let name1 [= value1] [, name2 [= value2]] [, ..., nameN [= valueN]; | let x = 42; 、let y; |
const | 声明一个(全局或本地声明的)块作用域的只读常量。 | const name1 = value1 [, name2 = value2 [, ... [, nameN = valueN]]]; | const MY_FAV = 7; 、const number = 42; |
- 提示
const 声明创建一个值的只读引用。但这并不意味着它所持有的值是不可变的,只是变量标识符不能重新分配。
// 定义常量 MY_FAV 并赋值 7
const MY_FAV = 7;
// 报错 - Uncaught TypeError: Assignment to constant variable.
MY_FAV = 20;
// MY_FAV is 7
console.log('my favorite number is: ' + MY_FAV);
// 尝试重新声明会报错
// Uncaught SyntaxError: Identifier 'MY_FAV' has already been declared
const MY_FAV = 20;
// MY_FAV 保留给上面的常量,这个操作会失败
var MY_FAV = 20;
// 也会报错
let MY_FAV = 20;
- 块作用域
if (MY_FAV === 7) {
// 没问题,并且创建了一个块作用域变量 MY_FAV
let MY_FAV = 20;
// MY_FAV 现在为 20
console.log('my favorite number is ' + MY_FAV);
// 这被提升到全局上下文并引发错误
var MY_FAV = 20;
}
// MY_FAV 依旧为 7
console.log('my favorite number is ' + MY_FAV);
boolean类型(布尔类型)
Boolean
对象是一个布尔值的对象包装器。
如果需要,作为第一个参数传递的值将转换为布尔值。如果省略该参数或参数值为 0
、-0
、null
、false
、NaN
、undefined
,或空字符串(""
),则该对象具有的初始值为 false;所有其他值,包括任何对象
,空数组([])
或字符串 "false"
,都会创建一个初始值为 true 的对象。
对于任何对象,即使是值为 false 的 Boolean 对象,当将其传给 Boolean 函数时,生成的 Boolean 对象的值都是 true。
例如,以下 if 语句中的条件评估为 true:
const x = new Boolean(false);
if (x) {
// 这里的代码会被执行
}
基本类型的布尔值不受此规则影响。例如下面的 if 语句的条件为假:
const x = false;
if (x) {
// 这里的代码不会执行
}
-
注意
不要将基本类型中的布尔值 true 和 false 与值为 true 和 false 的 Boolean 对象弄混了。
其值不是undefined
或null
的任何对象(包括其值为 false 的布尔对象)在传递给条件语句时都将计算为 true。 -
小提示:
不要用创建 Boolean 对象的方式将一个非布尔值转化成布尔值,直接将 Boolean 当做转换函数来使用即可,或者使用双重非(!!)运算符:
const x = Boolean(expression); //yes
const x = !!(expression); //yes
const x = new Boolean(expression); //(don`t)
运算符
JavaScript中的运算符和其他编程语言有很多共同之处。
运算符 | 名称 | 作用 | 示例 |
---|---|---|---|
+ | 加法 | 两个数相加。 | 6 + 9 |
- | 减法 | 从左边减去右边的数。 | 20 - 15 |
* | 乘法 | 两个数相乘。 | 3 * 7 |
/ | 除法 | 用右边的数除左边的数 | 10 / 5 |
% | 求余 (有时候也叫取模) | 在你将左边的数分成同右边数字相同的若干整数部分后,返回剩下的余数 | 8 % 3 (返回 2,8 除以 3 的倍数,余下 2。) |
** | 幂 | 取底数的指数次方,即指数所指定的底数相乘。它在 EcmaScript 2016 中首次引入。 | 5 ** 5 (返回 3125,相当于 5 * 5 * 5 * 5 * 5 。) |
– | – | – | – |
++ 、-- | 自加、自减 | 反复把一个变量加 1 或减 1 | Count++; 、x--; 、++num1 、 --num2 |
– | – | – | – |
= | 赋值 | 只是将右边的值赋给左边的变量 | let x = 3 、x=y |
+= | 加法赋值 | 右边的数值加上左边的变量,然后再返回新的变量。 | x = 3; x += 4; 等价于x = 3; x = x + 4; |
-= | 减法赋值 | 左边的变量减去右边的数值,然后再返回新的变量。 | x = 6; x -= 3; 等价于x = 6; x = x - 3; |
*= | 乘法赋值 | 左边的变量乘以右边的数值,然后再返回新的变量。 | x = 2; x *= 3; 等价于x = 2; x = x * 3; |
/= | 除法赋值 | 左边的变量除以右边的数值,然后再返回新的变量。 | x = 10; x /= 5; 等价于x = 10; x = x / 5; |
– | – | – | – |
=== | 严格等于 | 测试左右值是否相同 | 5 === 2 + 4 |
!== | 严格不等于 | 测试左右值是否不相同 | 5 !== 2 + 3 |
< | 小于 | 测试左值是否小于右值。 | 10 < 6 |
> | 大于 | 测试左值是否大于右值 | 10 > 20 |
<= | 小于或等于 | 测试左值是否小于或等于右值。 | 3 <= 2 |
>= | 大于或等于 | 测试左值是否大于或等于正确值。 | 5 >= 4 |
&& | 和 | / | (x < 10 && y > 1) 为 true |
∣ \mid ∣ ∣ \mid ∣ | 或 | / | (x == 5 ∣ \mid ∣ ∣ \mid ∣ y == 5) 为 false |
! | 非 | / | !(x == y) 为 true |
- 三元运算符
条件(三元)运算符是 JavaScript 唯一使用三个操作数的运算符:一个条件后跟一个问号(?),如果条件为真值,则执行冒号(:)前的表达式;若条件为假值,则执行最后的表达式。该运算符经常当作 if…else 语句的简捷形式来使用。
形式:condition ? exprIfTrue : exprIfFalse
- 简单举例:
const age = 26;
const beverage = age >= 21 ? "Beer" : "Juice";
console.log(beverage); // "Beer"
- 处理null值:
const greeting = (person) => {
const name = person ? person.name : "stranger";
return `Howdy, ${name}`;
}
console.log(greeting({ name: "Alice" })); // "Howdy, Alice"
console.log(greeting(null)); // "Howdy, stranger"
- 条件链
三元运算符是右结合的,这意味着它可以按以下方式“链接”起来,类似于 if … else if … else if … else 链:
function example() {
return condition1 ? value1
: condition2 ? value2
: condition3 ? value3
: value4;
}
//等价于:
function example() {
if (condition1) {
return value1;
} else if (condition2) {
return value2;
} else if (condition3) {
return value3;
} else {
return value4;
}
}
- 自加(
++
)、自减(--
)的位置细节
我们在控制台输入这些:
let num1 = 4;num1++;
num1;
会发现先返回4,再是5:
这是因为浏览器返回当前值,然后增加变量。
==
和===
?!=
和!==
?
==
和!=
也可以判断相等和不相等,它们都是 JavaScript 中的有效运算符,但它们与===
、!==
不同,前者测试值是否相同,但是数据类型可能不同,而后者的严格版本测试值和数据类型是否相同。严格的版本往往导致更少的错误,所以更建议使用严格的版本。
例如我们在console输入以下语句:
1===1
1==="1"
1=="1"
1===true
1==true
结果如图所示:
其中第二条1==="1";
是先将前面的1
转换成"1"
再比较"1"
和"1"
,返回true
。第四条和第五条同理。像这样的强制转换在JavaScript中很常见。
条件语句和循环语句
条件选择语句if、switch语句和循环语句for、while、do…while语句与其他编程语言基本一样,这里举例一个简单的交互:
html中:
<button>Start machine</button>
<p>The machine is stopped.</p>
js中:
const btn = document.querySelector('button');
const txt = document.querySelector('p');
btn.addEventListener('click', updateBtn);
function updateBtn() {
if (btn.textContent === 'Start machine') {
btn.textContent = 'Stop machine';
txt.textContent = 'The machine has started!';
} else {
btn.textContent = 'Start machine';
txt.textContent = 'The machine is stopped.';
}
}
效果:
点击后:
作用域
在 JavaScript 中有两种作用域类型:
- 局部作用域
- 全局作用域
JavaScript 拥有函数作用域:每个函数创建一个新的作用域。
作用域决定了这些变量的可访问性(可见性)。
函数内部定义的变量从函数外部是不可访问的(不可见的)。
实例1:
// 此处的代码不能使用 carName 变量
function myFunction() {
var carName = "porsche";
// 此处的代码能使用 carName 变量
}
实例2:
var carName = " porsche";
// 此处的代码能够使用 carName 变量
function myFunction() {
// 此处的代码也能够使用 carName 变量
}
object (对象)
array (数组)(包括:遍历数组,push,pop,reverse,shift,unshift)
在 JavaScript 中,数组不是基本类型,而是具有以下核心特征的 Array 对象:
- 可调整大小的,并且可以包含不同的数据类型。(当不需要这些特征时,可以使用类型化数组。)
- 不是关联数组,因此,不能使用任意字符串作为索引访问数组元素,但必须使用非负整数(或它们各自的字符串形式)作为索引访问。
- 索引从 0 开始:数组的第一个元素在索引 0 处,第二个在索引 1 处,以此类推,最后一个元素是数组的 length 属性减去 1 的值。
- 复制操作创建浅拷贝。(所有 JavaScript 对象的标准内置复制操作都会创建浅拷贝,而不是深拷贝)
数组下标
JavaScript 语法要求使用方括号表示法而不是点号表示法来访问以数字开头的属性。也可以用引号包裹数组下标(例如,years[‘2’] 而不是 years[2]),尽管通常没有必要。
JavaScript 引擎通过隐式的 toString,将 years[2] 中的 2 强制转换为字符串。因此,‘2’ 和 ‘02’ 将指向 years 对象上的两个不同的槽位,下面的例子可能是 true:
console.log(years['2'] !== years['02']);
只有years['2']
是一个实际的数组索引。years['02']
是一个在数组迭代中不会被访问的任意字符串属性。
数组方法概括
方法名 | 功能 | 原数组是否改变 |
---|---|---|
concat() | 合并数组,并返回合并之后的数据 | n |
join() | 使用分隔符,将数组转为字符串并返回 | n |
pop() | 删除最后一位,并返回删除的数据 | y |
shift() | 删除第一位,并返回删除的数据 | y |
unshift() | 在第一位新增一或多个数据,返回长度 | y |
push() | 在最后一位新增一或多个数据,返回长度 | y |
reverse() | 反转数组,返回结果 | y |
sort() | 排序(字符规则),返回结果 | y |
slice() | 截取指定位置的数组,并返回 | n |
splice() | 删除指定位置,并替换,返回删除的数据 | y |
toString() | 直接转为字符串,并返回 | n |
- concat()
concat(data1,data2,...);
用于连接两个或多个数组,该方法不会改变现有的数组,而仅仅会返回被连接数组的一个副本。
var arr1 = [1,2,3]
console.log(arr1.concat("hello","world")); //[1,2,3,"hello","world"]
console.log(arr1.concat(["a","b"],[[3,4],{"name":"admin"}]));
//[1,2,3,"a","b",[3,4],{"name":"admin"}]
console.log(arr1);
//[1,2,3]---原数组未改变
- join()
join(str);
根据指定分隔符将数组中的所有元素放入一个字符串,并返回这个字符串。
var arr = [1,2,3];
console.log(arr.join());
//1,2,3
console.log(arr.join("-"));
//1-2-3
console.log(arr);
//[1,2,3]---原数组未改变
- pop()
方法用于删除并返回数组的最后一个元素。
var arr = [1,2,3];
console.log(arr.pop());
//3
console.log(arr);
//[1,2]---原数组改变
- shift()
方法用于删除并返回数组的第一个元素。
var arr = [1,2,3]
console.log(arr.shift());
//1
console.log(arr);
//[2,3]---原数组改变
- unshift()
unshift(newData1, newData2, ......)
向数组的开头添加一个或更多元素,并返回新的长度。
var arr = [1,2,3];
console.log(arr.unshift("hello"));
//4
console.log(arr);
//["hello",1,2,3]---原数组改变
console.log(arr.unshift("a","b"));
//6
console.log(arr);
//["a","b","hello",1,2,3]---原数组改变
- push()
push(newData1, newData2, ......)
var arr = [1,2,3];
console.log(arr.push("hello"));
//4
console.log(arr);
//[1,2,3,"hello"]---原数组改变
console.log(arr.push("a","b"));
//6
console.log(arr);
//[1,2,3,"hello","a","b"]---原数组改变
- reverse()
var arr = [1,2,3];
console.log(arr.reverse());
//[3,2,1]
console.log(arr);
//[3,2,1]---原数组改变
- sort()
对数组中的元素进行排序,默认是升序。
var arr = [6,1,5,2,3];
console.log(arr.sort());
//[1, 2, 3, 5, 6]
console.log(arr);
//[1, 2, 3, 5, 6]---原数组改变
- slice()
slice(startIndex, endIndex)
可从已有的数组中返回选定的元素。该方法接收两个参数slice(start,end),strat为必选,表示从第几位开始;end为可选,表示到第几位结束(不包含end位),省略表示到最后一位;start和end都可以为负数,负数时表示从最后一位开始算起,如-1表示最后一位。
var arr = ["Tom","Jack","Lucy","Lily","May"];
console.log(arr.slice(1,3));
//["Jack","Lucy"]
console.log(arr.slice(1));
//["Jack","Lucy","Lily","May"]
console.log(arr.slice(-4,-1));
//["Jack","Lucy","Lily"]
console.log(arr.slice(-2));
//["Lily","May"]
console.log(arr.slice(1,-2));
//["Jack","Lucy"]
console.log(arr);
//["Tom","Jack","Lucy","Lily","May"]---原数组未改变
但是在排序前,会先调用数组的toString方法,将每个元素都转成字符之后,再进行排序,此时会按照字符串的排序,逐位比较,进行排序。
var arr = [6,1024,52,256,369];
console.log(arr.sort());
//[1024, 256, 369, 52, 6]
console.log(arr);
//[1024, 256, 369, 52, 6]---原数组改变
sort(callback)
如果需要按照数值排序,需要传参。sort(callback),callback为回调函数,该函数应该具有两个参数,比较这两个参数,然后返回一个用于说明这两个值的相对顺序的数字(a-b)。其返回值如下:
若 a 小于 b,返回一个小于 0 的值。
若 a 等于 b,则返回 0。
若 a 大于 b,则返回一个大于 0 的值。
var arr = [6,1024,52,256,369];
console.log(arr.sort(fn)); //[6, 52, 256, 369, 1024]
console.log(arr); //[6, 52, 256, 369, 1024]---原数组改变
function fn(a,b){
return a-b;
}
- splice()
splice(start,num,data1,data2,...);
向数组中添加,或从数组删除,或替换数组中的元素,然后返回被删除/替换的元素。
1>不传参时:无操作
var arr = ["Tom","Jack","Lucy","Lily","May"];
console.log(arr.splice());
//[]
console.log(arr);
//["Tom","Jack","Lucy","Lily","May"]---无操作
2>只传入start:表示从索引为start的数据开始删除,直到数组结束
var arr = ["Tom","Jack","Lucy","Lily","May"];
console.log(arr.splice(2));
//["Lucy", "Lily", "May"]
console.log(arr);
//["Tom", "Jack"]---原数组改变
3>传入start和num:表示从索引为start的数据开始删除,删除num个
var arr = ["Tom","Jack","Lucy","Lily","May"];
console.log(arr.splice(2,2));
//["Lucy", "Lily"]
console.log(arr);
//["Tom", "Jack", "May"]---原数组改变
4>传入更多:表示从索引为start的数据开始删除,删除num个,并将第三个参数及后面所有参数,插入到start的位置
var arr = ["Tom","Jack","Lucy","Lily","May"];
console.log(arr.splice(2,2,"a","b"));
//["Lucy", "Lily"]
console.log(arr);
//["Tom", "Jack", "a", "b", "May"]---原数组改变
5>传入更多:表示从索引为start的数据开始删除,删除num个,并将第三个参数及后面所有参数,插入到start的位置
var arr = ["Tom","Jack","Lucy","Lily","May"];
console.log(arr.splice(2,0,"a","b"));
//[]
console.log(arr);
//["Tom", "Jack", "a", "b", "Lucy", "Lily", "May"]---原数组改变
- toString()
转换成字符串,类似于没有参数的join()。该方法会在数据发生隐式类型转换时被自动调用,如果手动调用,就是直接转为字符串。
var arr = [1,2,3];
console.log(arr.toString()); //1,2,3
console.log(arr); //[1,2,3]---原数组未改变
function(函数)
每个 JavaScript 函数实际上都是一个 Function 对象。运行
(function(){}).constructor === Function
// true
便可以得到这个结论。
Function()
创建一个新的 Function 对象。直接调用此构造函数可以动态创建函数,但会遇到和 eval() 类似的的安全问题和(相对较小的)性能问题。然而,与 eval() 不同的是,Function 构造函数创建的函数只能在全局作用域中运行。
实例方法
-
Function.prototype.apply(thisArg [, argsArray])
调用一个函数并将其 this 的值设置为提供的 thisArg。参数可用以通过数组对象传递。 -
Function.prototype.bind(thisArg[, arg1[, arg2[, ...argN]]])
创建一个新的函数,该函数在调用时,会将 this 设置为提供的 thisArg。在调用新绑定的函数时,可选的参数序列([, arg1[, arg2[, …argN]]])会被提前添加到参数序列中(译者注:即调用绑定创建的函数时,传递的参数会接续在可选参数序列后)。 -
Function.prototype.call(thisArg[, arg1, arg2, ...argN])
调用一个函数,并将其 this 值设置为提供的值。也可以选择传输新参数。 -
Function.prototype.toString()
返回表示函数源码的字符串。覆盖了 Object.prototype.toString 方法。
Function 构造函数与函数声明之间的不同
由 Function 构造函数创建的函数不会创建当前环境的闭包,它们总是被创建于全局环境,因此在运行时它们只能访问全局变量和自己的局部变量,不能访问它们被 Function 构造函数创建时所在的作用域的变量。这一点与使用 eval() 执行创建函数的代码不同。
var x = 10;
function createFunction1() {
var x = 20;
return new Function('return x;');
// 这里的 x 指向最上面全局作用域内的 x
}
function createFunction2() {
var x = 20;
function f() {
return x;
// 这里的 x 指向上方本地作用域内的 x
}
return f;
}
var f1 = createFunction1();
console.log(f1());
// 10
var f2 = createFunction2();
console.log(f2());
// 20