JavaScript,是一门前端编程语言。它是一个网页的大脑,控制着网页的行为。废话不多说,下文我们就来学习 JavaScript~
目录
1. JavaScript 简介
1.1 JS 简史
最初的前端,就是简简单单的 HTML + CSS,没有过多的功能,没有绚丽的动画。但是我们需要这些功能,怎么办呢?1995 年,当时如日中天的 Netscape Navigator(网景领航员,现在已经消失,继任者是开放的 Firefox)浏览器提出要创建一门新的脚本语言,来控制网页的行为。于是,网景的天才程序员 Brendan Eich 花了 10 天,写出了 JavaScript(说来很多人 10 天连 JavaScript 都只能学一个皮毛,天才还是天才),简称 JS。由于 JavaScript 在早期没有标准,程序员开发非常困难,1997 年,ECMA 协会正式推出了 JavaScript 的第一个标准:ECMAScript 1。随后 JavaScript 就一发不可收拾,成为了前端开发必备的语言,前端三剑客之一,并且一路发展到了 ECMAScript 13~
(还有哈,Java 和 JavaScript 没有一点关系,他们只是恰巧撞名了~)
1.2 JS 版本选择
目前 JavaScript 使用最广泛的版本是 2015 年推出的 ECMAScript 6(简称 ES6),我们今后的学习将会以 ES6 作为开发使用的版本。
或许有同学会问了:我的浏览器都支持到 ES10 了,为什么还要使用 ES6 啊?还有 JavaScript 目前最新的版本不是 ES13 吗?学这个老东西干嘛?这里就要好好说一下 ES6 了。ES6,是一个划时代的 JavaScript 版本,加入了众多新特性,比如面向对象,模块化编程等,让前端的开发得到了极大的便利。ES6 以后的版本都没有什么重大的更新。还有,一些大型企业或是服务器,他们可能不能接受那么快的更新浏览器,浏览器可能不支持 ES7 及更新版本的特性,但是 ES6 已经受到了大面积普及,所以我们使用 ES6,是最稳妥的选择。
还有一些同学在很多地方听过 ES6 还没普及,我们尽量使用 ES5 来开发 JavaScript,或是使用 webpack 或者 babel 把 ES6 转译成 ES5。我只想说,放心大胆地去用 ES6。目前浏览器圈里除了 IE,其他全部支持 ES6。然鹅 IE 已经停更了 8 年,并且微软官方都放弃它了。如果你正在用 IE,请换成 Edge 或其他浏览器,因为 ES5 已经 11 年了,已经快成活化石了。
1.3 JS 运行环境
前面聊了那么多 JS,那 JS 怎么使用呢?JS 代码保存在 .js 文件中,并在 HTML 文档中 head 标签中添加下面代码以引入 JS 代码:
<script src="JS文件路径"></script>
当然我们为了方便学习,这里还有一个快速运行 JS 代码的地方:浏览器控制台。学过 Python 的同学应该都知道大名鼎鼎的 Python Shell。浏览器控制台提供了和 Python Shell 差不多的功能。我们随便打开一个网站,比如 Greasy Fork,然后右边三个点——更多功能——开发人员工具或按下 Ctrl + Shift + I(Mac 是 Option + Control + I),打开开发人员工具;
然后把右边的开发人员工具拉到最大,我们不需要管左边的网页;
强迫症的同学还可以给左边的网页的 body 标签加上 display: none 样式(没学过 CSS 的看这里),选中 body 标签,然后在右边的 element.style 那里加。这样子我们就彻底看不到网页了(JavaScript 前期不需要涉及到网页)
最后点击控制台,你就打开了浏览器控制台。里面可以写 JS 代码,当然和 Python Shell 一样,得一行一行写。
2. JavaScript 语法
JavaScript 是一门编程语言,学习编程语言肯定要先学习它的语法。JS 的语法比 HTML CSS 难多了,但是对有学过一些后端语言比如 C++ Python Java 的同学来说,毫无压力。如果你恰好会 C#,那么恭喜你,JavaScript 的语法你已经掌握了 80%,因为 JS 与 C# 的语法极为相似。如果你没有一门后端编程语言的基础,那我建议你先去学习一门编程语言,我推荐 Python,它能帮助你很快地上手编程。学习之后你再来学 JavaScript 会轻松很多。
2.1 Hello World
学习编程语言约定俗成的第一行代码是输出 Hello World。在 JavaScript 里输出 Hello World 非常简单,几乎和 Python 一样简单。在控制台输入下面的语句:
console.log("Hello World!");
console.log 是操作控制台的一个函数,它的作用是把内容输出在控制台上。后面的一对括号是函数后必要的,括号里面的内容是函数的参数,参数是我们调用函数是需要传递进去的内容,函数里面需要执行的程序将会使用这个内容。用双引号括起来的 Hello World! 是 JS 里的字符串,它代表一串文字。句尾的分号表示这是一行 JS 代码,每行 JS 代码的末尾都需要有这个分号。
回车,你会发现控制台上打印出了 Hello World!,后面的 undefined 是函数的返回值。
2.2 严格检查模式
我们知道,JavaScript 是用 10 天写出来的,正是用这么短的时间里写出来,让 JavaScript 的语法非常不严谨,你写的代码出现了很离谱的语法错误 JS 都不会报错继续执行,比如函数里面参数重名了。所以后人们给 JS 添加了严格检查模式。在这个模式下有很多非常离谱的语法错误得以被纠正,对小白学习非常有帮助。即使你以后熟练了,也不能把它关掉,万一你写错了代码它也会帮你纠错。输入下面的代码以启用严格检查模式,以后在 .js 文件里写 JS 代码第一行一定也要是下面这行代码:
"use strict";
2.3 注释
注释的内容不会被执行,是给程序员看的。JS 的注释非常简单,具体看代码。
// 这是单行注释,双斜杠后面的文字将会被视为注释
/*
这是多行注释
/* 和 */ 之间的所有的内容将会被视为注释
*/
2.4 变量
JS 里只有一个变量类型:var。不管你的变量是字符串是数字还是布尔值(true 和 false),他们统统是 var。变量的名称可以随便取,只是不能取 JS 的关键字,比如 var。
// 字符串变量
var str = "string";
// 数字变量
var num = 123;
var floatNum = 1.23;
// 布尔值变量
var trueValue = true;
var falseValue = false;
使用 var 定义变量是老版本 JavaScript 里的用法,因为 var 出现了很多问题,比如作用域问题(后面会讲作用域)。我们实际开发中使用 ES6 里定义变量的关键字 let。
// ES6 变量 let
let newVar = "New Var";
如果你想定义一个变量,不想让人修改比如圆周率,就可以使用关键字 const。const 定义的是常量,常量定义了就不能更改。
const PI = 3.14;
变量可以当作普通的数值使用,比如你想打印出一个数的立方可以使用变量:
let num = 3;
console.log(num * num * num);
其中 console.log 里的 num 就代表 3。注意哈,* 号代表乘号。同样的 / 号代表除号。下面列举出 JavaScript 里常用的运算符:
/*
+ 加号
- 减号
* 乘号
/ 除号
% 取余,比如 16%3 代表取 16 除以 3 的余数
++ 自增,相当于 +1,比如 i++
-- 自减,相当于 -1,比如 i--
*/
2.5 条件语句
条件语句非常简单,就是 if……else if……else,下方上代码,注意看代码里的注释帮助理解:
let time = 16;
if (time < 9){ // 这是 if 语句,括号里的是一个条件语句,判断 time < 9 是否为真。
console.log("早上好!"); // 如果 time < 9 为真那么执行这行代码,在 if 后面花括号里的代码都会被执行
} else if (time < 15){ // 这是 else if 语句,当前面的 if 条件为假时再判断下一个条件:time < 15
// else if 语句在条件语句中必须要跟在 if 的后面,不能独自出现,且不一定需要用
console.log("中午好!"); // 当前面 time < 9 的条件不满足又满足 time < 15 的条件时执行这行代码
} else if (time < 20){ // else if 语句在条件语句中不止可以出现一次
console.log("下午好!"); // 当前面的条件都不满足又满足 time < 20 的条件时执行这一行
} else { // 当前面的条件都不满足就执行下面的代码,else 在条件语句中不一定需要用,且必须跟在 if 或者 else if 语句后面
console.log("晚上好!");
}
这是你在 JavaScript 里实现的第一个小程序,会根据时间(time)来相应的输出问候语。可以试着把它写进 .js 文件里,注释不一定要写,然后在 HTML 文档里引入,并查看控制台打印了什么信息。
if 语句是可以嵌套的,没有禁止套娃,你套个一百层都没事。
if 语句中的比较操作符这里浅说一下:有大于,小于,等于,和与或
/*
> 大于号
< 小于号
== 相等号,请注意一个等于号是赋值语句中的,要判断两个值是否相等要用两个等于号
=== 严格相等号,判断条件更严格,区别见:https://m.php.cn/article/475590.html
&& 和,这个符号前面的条件判断语句和后面的条件判断语句都要为真才能为真
比如 5 == 5 && 3 > 4 这个语句为假
|| 或,这个符号前面的条件判断语句和后面的条件判断语句其中一个为真就为真
比如 5 == 5 || 3 > 4 这个语句为真
*/
除了 if 语句,JS 里还有一种更便捷的条件选择方式:switch……case 语句。简单来说就是在 switch 里放入一个值,然后在 case 里一个一个比较,如果一样就执行 case 下的代码。废话不多说上代码:
let age = 80;
switch (age){ // 这是 switch 语句,把 age 放进 switch 里
case 20: // 如果 age 和 case 后面的 20 一样,执行冒号之后的代码
console.log("弱冠");
break; // 每一个 case 代码最后一定要加一个 break; 跳出 switch 语句
// 要不然它会执行下一个 case 的语句,这个特性称作贯穿
case 30: // case 不止有一个,可以有很多个
console.log("而立");
break;
case 40:
console.log("不惑");
break;
case 50:
console.log("知天命");
break;
case 60:
console.log("花甲");
break;
case 70:
console.log("古稀");
break;
case 80:
case 90: // 由于八九十岁都叫耄耋(读音帽叠),我们这里有意利用贯穿的特性让 80 和 90 岁的人都打印耄耋
console.log("耄耋");
break;
case 100:
console.log("期颐");
break;
default: // 如果上面所有 case 都不满足那么执行 default 中的语句,default 非必需
console.log("对不起,您的年龄没有没有对应的中式称呼");
// default 由于是最后一个,所以不需要 break;
}
上面这个示例打印了特定年龄对应的中式称呼。我们可以把这个示例放进 .js 里试一试。我们很明显的发现,使用 switch case 避免了很多不必要的比较,让整个程序变得更加简洁。
2.6 作用域
刚刚我们学习条件判断语句的时候是不是出现了花括号?一个花括号就代表 JS 里的一个代码段。如果你在花括号外定义变量,花括号内可以使用;但是如果你在花括号内定义变量,花括号外不能使用。
我们刚刚学习的 if 语句 switch 语句,还有等一会的 for 循环,函数,类,所用到的花括号里面的代码都属于代码段,有作用域。不过,你也可以手动创建代码段,直接使用花括号 {} 括起来即可,这个花括号里的代码也符合作用域的规则。
let something = 3;
console.log("括号外 something 是"+something); // + 号连接字符串
// 如果一边是字符串一边不是那 JS 会自动把不是字符串的一边转换为字符串然后连接
{ // 花括号定义作用域, if switch 里的花括号也属于作用域
console.log("括号内 something 是"+something); // 括号内 something 也能识别到
let anything = 4; // 在作用域内定义了 anything
console.log("括号内 anything 是"+anything);
}
console.log("括号外 anything 是"+anything); // 报错
// 因为 anything 是在作用域内定义的,到了作用域外他就失效了
可以在控制台上试一下。最后一行控制台报了这个错,显示 anything 未定义。这表明了作用域的作用。
Uncaught ReferenceError: anything is not defined
at <anonymous>:1:13
有兴趣的同学们可以把 let 换成 var 试一试,如果定义的是 var 变量,那么它不会报错,还是会打印 4,这违背了编程语言封装的理念。这说明了为什么 ES6 要弃用 var 改用 let 了。
(这里科普一下封装。封装,你可以把一个作用域理解为一个收音机,收音机外面有一个盒子把里面的线路封装起来。我们只需要知道收音机它会播放声音可以调台就行了,而不需要知道收音机内部的线路是怎么实现播放声音怎么实现调台的。如果你像 var 一样,在作用域的外部也可以打印出来,就像收音机没有外壳一样,会让人感到困惑。作用域遵循封装的理念,这个概念在后面函数的时候会理解地更清晰。)
2.7 循环语句
最简单的循环语句是 for 循环:
for (let i = 0; i < 3; i++){
// 上面的语句是一个 for 循环,循环中间有三个初始化语句,用分号隔开
// 第一个 let i = 0; 定义了 for 循环需要初始化的变量,在循环作用域内部可以使用
// 第二个 i < 3 是条件判断,当 for 循环里的数据满足不了这个条件时 for 循环结束
// 第三个 i++,在每次循环结束时都会执行一次
// 这三个式子如果有哪些不需要就放空,比如 for (; i < 3;) 也是符合标准的
console.log("Hello World, i = "+i);
// 然后接着循环,i++,判断 i 是否小于 3
}
执行结果显而易见:
Hello World, i = 0
Hello World, i = 1
Hello World, i = 2
还有一种循环语句,名叫 while,更简单:
let i = 0;
while (i < 3){
// 上面是一个 while 循环,只要 i < 3 循环就会继续下去
console.log("Hello World, i = "+i);
i++;
}
运行结果显而易见,和上面 for 循环的一模一样。while 循环有一种变体,叫做 do……while。这个循环和上面 while 不同的是,do……while 不管条件成不成立,总会把代码先执行一遍再判断。一般来说我们很少用 do……while,但是代码还是要上一下:
let i = 3;
do {
console.log("Hello World, i = "+i);
i++;
} while (i < 3);
// 运行结果 Hello World, i = 3
如果我们想在某一个循环中跳过这个循环后面的语句,直接进入下一个循环那怎么办呢?我们可以用 continue 语句,上代码:
for (let i = 0; i < 3; i++){
if (i == 2){
continue; // 在 i 等于 2 的时候跳过循环后面的部分
}
console.log("Hello World, i = "+i);
}
运行结果:
Hello World, i = 0
Hello World, i = 1
如果我们想立即终止循环,跳出循环语句,可以使用 break。之前跳出 switch 语句用的也是 break:
for (let i = 0; i < 3; i++){
if (i == 2){
// 在 i == 2 的时候打印语句然后跳出循环
console.log("i == "+i+", 跳出循环!");
break;
}
console.log("Hello World, i = "+i);
}
结果:
Hello World, i = 0
Hello World, i = 1
i = 2,跳出循环!
2.8 函数
函数,其实就是一串代码,它根据不同的参数执行不同的代码从而产生不同的结果。我们可以把函数理解成一个收音机,我们调的频道就是函数的参数,收音机播放出声音就是产生的结果。函数遵循封装的思想。由于我学过 Java,所以有时候会把函数称为方法,但它们的意思是一样的。话不多说上代码:
function myFunc(str){
console.log(str);
}
我们定义了一个函数名称叫 myFunc,后面的括号里面是函数的参数。函数可以没有参数但是括号是必须的。函数如果需要有多个那么参数名之间用 , 隔开。后面跟着的是一个代码块,代码快里面是函数的代码,代码里可以调用我们传递进来的参数。
那怎么调用函数呢?最简单的方法:
// 函数名(函数参数1,函数参数2……);
// 如果没有参数括号也要加上,要不然它表示的是这个函数本身
myFunc("hello");
控制台里试一下,看一下函数是否打印 hello。函数的参数一定要不多也不少,要不然报错了我不管~
如果函数要返回一个值,我们可以使用 return 语句。有返回值的函数调用可以直接当作值来使用:
function add(num1,num2){
return num1 + num2;
}
console.log(add(1,2));
把它在控制台里输入,应该会打印出 3~
如果你想中途退出函数,也可以用 return,使用了 return 会直接退出函数。带不带返回值都可以,不带返回值直接写成 return; 的形式~
函数其实就是一个变量,如果你想把这个函数赋值给其他变量可以直接使用函数名,不需要带括号。这在 Python 里已经算是非常不可思议的事情,而我作为一个准 Java 程序员在学到这个的时候更是直接惊呆了~
function add(num1,num2){
return num1 + num2;
}
let plus = add;
console.log(plus(1,2));
2.9 数组
数组大家都学过,python 的同学就记住数组相当于列表就可以了。话不多说上代码;
// 数组语法:[值1,值2,值3……]
let myArray = [1,2,3,"a","b","c"];
(Java 和 C++ 的同学请注意,由于 JS 是弱类型脚本语言,所以数组里的值类型不一定要相同)
我们可以通过下标来取值,即数组名[下标], 比如你想取数组当中第三个值,就把 3-1=2 得到他的下标,然后用数组名[2]就可以得到它的值~
下面列举一下数组的常用方法:
// 下标取值
console.log(myArray[0]);
// 使用下标更改数组某一项的值
myArray[2] = 5;
// sort 方法重新升序排列数组
myArray.sort()
// push 方法把参数添加到数组的末尾,可接收任意数量参数,返回新数组长度
myArray.push("D","E);
// length 属性代表数组长度
console.log(myArray.length);
// pop 方法移除数组末尾项,返回该项
myArray.pop();
// 反转数组值
myArray.reverse();
// concat 方法把新数组增添至数组末尾,接收参数为一个数组
myArray.concat(["X","Y","Z"]);
这里再讲一个特殊的循环语句:foreach,它是 for 循环的衍生,它的作用是遍历数组中的每一个项。下面上代码:
// 使用 foreach 遍历数组
for (index in myArray){ // 语法:for (下标 in 需要遍历的数组名){}
console.log(myArray[index]); // 每一次循环中的 index 下标值都不一样,第一次循环 index 是 myArray 的第一项,第二次就变成了第二项,以此类推
// 请注意 index 指的是下标!不是指数组中的项!
}
执行结果:
0
1
2
a
b
c
2.10 字典
很多书把 JS 中的字典称作对象,JS 自己也是这么说的。但是我觉得作为一个前 Python 叫做字典会更贴切(没学过 Python 的同学就记得字典就类似于 Java 和 C++ 中的枚举,但是语法更简单)。而且更黎谱的是 JS 里有 OOP(面向对象)机制……
废话不多说,直接上代码:
let student = { // 括号里的是字典,一对一对冒号是属性
age: 3, // 属性之间用逗号隔开
name: "JavaScript",
score: 100,
}; // 别忘了分号
(函数也可以当作对象的属性,刚刚才学的函数名可以赋值呢)
当我们需要知道一个字典的值或是想要更改字典的值时,我们可以使用 字典名.属性名。上代码:
console.log(student.age);
student.age = 13;
console.log(student.age);
结果是 3 和 13。下面就要开始货真价实的对象了,请做好准备~
2.11 对象
前面那位是挂羊头卖狗肉,这一节要学的 class 才是货真价实的对象。如果你之前没有学过另外一门后端编程语言学这个会很难受~
// 这段代码的注释一定要看
class student{
// 这是一个类
// 类属性,不需要加 let
name = 0;
age = 0;
// 类方法,不需要加 function,爽不爽
printInfo(){
console.log("Student " + this.name + ": age " + this.age);
}
constructor(name,age){ // 这是类的构造函数,构造函数的函数名必须为 constructor
// 构造函数也不需要加 function~
this.name = name; // 在类里面 this 指代对象本身,this.属性指代对象的属性
// 科普以下类和对象的区别:类是收音机的构造蓝图,对象是生产出来的收音机
this.age = age;
}
}
如果我们想创建一个对象,就使用 new 关键字(这估计是 JavaScript 和 Java 唯一有关系的地方了把……)。类后面传递的参数是构造函数的。构造函数在创建对象时总是会执行~
let student1 = new student("小明",3);
let student2 = new student("小红",2);
// 一个类可以创建无数个对象
对象可以访问属性,也可以执行对象的方法~
console.log(student1.age);
student1.printInfo();
下面简单叙述类的两个特性:静态属性/方法和继承~
静态属性/方法,可以直接使用类名.静态属性/方法名来调用,对象调用不了静态属性/方法。静态方法内不能调用非静态属性或方法,因为非静态属性/方法是对象专有的;
class student{
// 静态属性/函数前面要加 static
static help = "Student is a class.";
static printHelp(){
console.log(this.help);
}
}
student.printHelp(); // 调用静态函数直接使用类名.静态函数名
继承,简单来说就是爸爸和儿子的关系。继承当中的儿子(称作子类,爸爸称作父类)可以拥有父类的所有属性和方法,还可以根据自己的需要自己定义新方法~ 继承使用 extends 关键字(又发现了一个 JavaScript 和 Java 相似的例子~),下面简单举一个例子:
class father{
hello = 3;
printHello(){
console.log("Hello = "+this.hello);
}
// 类可以没有构造函数,默认为空
}
class son extends father{
}
let son1 = new son();
son1.printHello();
可以看到它打印出了 Hello = 3~
当然上面这些只是面向对象的冰山一角,还有很多功能我们没有学,但是一般用不到~
2.12 接口与 import
有的时候,我们需要把一些功能性的属性,函数或类写在一个 JS 文件里,比如二分算法排序数组,需要的时候我们导入这些东西,不是非常方便吗?这时候我们就需要接口与 import 了。接口是一个字典,里面可以放一些功能性的属性,函数和类。而 import 的作用,就是引入 JS 文件里的接口。
打开代码编辑器,先新建一个 JS 文件 module.js(名字在实际开发中可以随便取),这是我们建立的模块文件。
'use strict';
// 我们一般使用默认接口,即 export default,在导入 JS 文件时默认导入默认接口
// 此外还有一种命名式接口,我感觉语法太复杂也没啥用,所以这里不介绍~
// 默认接口里是一个字典
export default{
// 可以写普通的字典属性
value: 3, // 逗号千万不能忘
// 也可以写函数,语法如下
// 函数名: function(参数){}
printHello: function(参数){
console.log("Hello!");
}, // 请注意逗号
// 还可以写类
// 类名: class{}
student: class{
static help = "student";
static printHelp(){
console.log(this.help);
}
} // 最后一个值就不需要逗号了
}
// 悄悄告诉你,普通的字典里也可以写函数和类哦~
// 如果你需要多个接口,大可不必使用命名式接口,因为字典里可以嵌套字典~
(一定要记得加 use strict,你不会都忘了吧)
然后在同一个文件夹里再新建一个 main.js,并把其引入进任意一个 HTML 文档,引入时使用下面语句(实际开发中一定要加 type="module",要不然浏览器会报错);
<script src="main.js" type="module"></script>
import 导入很简单,就是 import 模块名 from "./js 文件路径";,请注意一定要加 ./。接口的使用也很简单,直接接口名.属性名即可。
'use strict';
// 导入接口,hello 是我们给这个接口取的名字,可以随便取
// 简不简单
import hello from "./module.js";
// 接口的使用
// 接口名.属性名,很简单
// 接口名就是上面定义的 hello
// 调用接口值
console.log(hello.value);
// 调用接口函数
hello.printHello();
// 调用接口类
hello.student.printHelp();
保存。运行刚刚的那个 index.html。打开控制台,如果有看到这个报错,说明是跨域策略问题,解决方法也很简单~
打开你的前端编辑器 VS Code(其他的不知道行不行,话说用 VS Code 写前端真的很舒适),安装扩展 Live Server;
然后右键你的 index.html 文件,选择 Open with Live Server;
Live Server 会把你的文件打开至浏览器,再次打开控制台,你会发现你的代码已经运行成功了~(Live reload enabled. 那行是 Live Server 打印的)
(养成一个好习惯,打开 HTML 用 Live Server)
到这里我们 JavaScript 语法部分的学习就完成了。接下来我们要做的是怎么将 JavaScript 和 HTML 网页结合在一起~
3. JavaScript DOM
DOM(Document Object Model,文档对象模型),你们可以把它理解为 HTML 中的元素。其实说白了,JS 存在的意义就是为了操作 DOM~ DOM 以树形的方式存在,下图可以清晰的帮助你了解 DOM 树~
图源:Runoob
3.1 获取 DOM
JavaScript 有一个内置变量 document,它和 console 差不多都是内置在 JS 里的。document 代表当前网页的 DOM 树,就是上图的 Document 元素~
我们想要操作 DOM,就必须获取 DOM 元素。获取 DOM 元素就可以使用上面的 document 了~
下面列举了几种常用的获取 DOM 的方法。除了 ID 获取法其他方法获取的全部都是一个数组,需要通过下标取值来获取真正的 DOM 元素~
// ID 获取法,获取指定 id 的 DOM 元素
document.getElementById("id名");
// Class 获取法,获取所有指定 class 的 DOM 元素
document.getElementsByClassName("class 名");
// 标签名获取法,获取所有指定标签的 DOM 元素,如获取所有 p 标签
document.getElementsByTagName("标签名");
// 其实细心的同学从函数名里的 Element 的单复数形式里就可以看出端倪
还有人说这种方法太麻烦,想用 jQuery。jQuery 是一个 JavaScript 插件,它提供了很多方便的功能。jQuery 直接使用 CSS 的选择器来获取 DOM 元素虽然很方便,但是有两个原因让我不想用 jQuery:第一个,我们需要在 HTML 文档里另外引入 jQuery 的 CDN,文件非常大,很多功能我们都用不到。第二个,jQuery 有一个缺点就是 DOM 操作太频繁,并且正在过时的路上渐行渐远~
3.2 操作 DOM
我们获取到了 DOM,那只单单获取了也没啥用。我们需要对他进行操作。下面举个例子。我们先打开 Greasy Fork,呼出控制台(由于我们要和网页交互,调成如下所示的样子最好,还有别隐藏网页了);
如果我们需要获取网页上所有的图片,我们就可以用 JavaScript 实现。首先先获取网页中所有图片的 DOM,将它保存在变量 imgs 里~
let imgs = document.getElementsByTagName("img");
然后由于获取到的是一个数组,不能直接使用,所以我们需要使用下标取值。获取到真实的 DOM 后,我们可以直接把 imgs 的第一项 DOM 打印出来看看它到底是什么,相信我,你会一秒钟理解 DOM~
我们连 HTML 标签都获取到了,获取它的属性值还不简单?比如我们想获取这些图片的原图片路径(src 属性),就可以用 DOM.属性值获取~
console.log(imgs[0].src);
打印出来这个链接就是网站上第一个图片的链接。可以尝试一下~
是不是又 get 到了一个获取网图的小知识~ 悄悄告诉你们,这个方法还适用于获取网页中 audio video 等标签的 src 属性值,你品,你细品~
不仅如此我们还可以改变 DOM 中的属性值。如果你仔细观察你会发现上面那个 img[0] 是 Greasy Fork 的 Logo。我们就奇思妙想一下,把它换成百度的 Logo~
首先我们先获取到百度 Logo 的原图片链接~ 打开百度,经过一番查找我们发现百度的 Logo 是在网页中图片的第 12 个~
把链接复制下来,然后打开 Greasy Fork 的网页,控制台输入下列代码~
let imgs = document.getElementsByTagName("img");
// 更改 DOM 属性方法:DOM.属性名 = 属性值
imgs[0].src = "你刚刚获取到的百度 logo 链接";
// 目前是 https://www.baidu.com/img/PCtm_d9c8750bed0b3c7d089fa7d55720d6cf.png
有没有瞬间感觉 Greasy Fork 变得度里度气了呢~
如果我们想修改的值不是属性,而是 HTML 标签里的内容呢?DOM 对象里有一个隐藏属性 innerHTML,它指代的是 HTML 标签里的内容,比如 <a href="link.html">CONTENT</a> 这个 a 标签里的 CONTENT。我们可以通过修改 innerHTML 属性,从而达到修改 HTML 内容的效果。
// 语法 dom.innerHTML = xxx;
document.getElementsByTagName("a")[0].innerHTML = "被更改后的值";
// 上面这行代码简单解析一下
// document.getElementsByTagName("a") 他在 JS 里指代这个函数的返回值,可以直接使用下标取值
// 下标取值后的 DOM 对象也可以直接更改其属性
3.3 与 DOM 交互
在学这个之前,我们先学习一个标签 button。这个标签非常简单,是一个按钮~ 我们手敲一段代码(下面的),然后保存为 test.html。
<!DOCTYPE html>
<!-- test.html -->
<html>
<head>
<title>Button Test</title>
</head>
<body>
<button>Hello!</button>
</body>
</html>
使用 Live Server 在浏览器中实验一下(忘了?右键文件 Open with Live Server!),嗯,没错,是一个按钮~
如果我们只学了 HTML 和 CSS 这个按钮是什么用处也没有的。但是现在时代变了,JavaScript 有了,我们可以给这个按钮绑定 JavaScript 函数~
写一段 JS 代码保存为 bind.js,然后把它引入 HTML 文档里。这段 JS 代码非常简单,就是写了一个函数实现在网页的上方弹出窗口~
function whenClick(){
// alert 函数是 JS 里内置的,和 console.log 差不多,只不过 alert 是在网页上方弹出窗口~
alert("button 也是生命,请你不要点人家~");
}
然后把那个 button 设置一个属性 onclick,onclick 参数里可以写 JS 代码,但最简便的方式是调用一个 JS 函数~ 保存,然后再次用 Live Server 打开这个 HTML~
<button onclick="whenClick();">Hello!</button>
哈哈,绑定成功了~
如果这个标签不是 button 同样也可以绑定 JS,比如当你点击一个标题的时候触发一个 JS 函数,这些都是可以实现的,只需要加 onclick 就可以~
此外 JS 事件的绑定不止 onclick 点击一种,下面列举了几种常用的~
// onclick 属性:当用户点击标签时触发
// onload 属性:当用户打开或者回到网页时触发,通常是 body 标签的属性
// onunload 属性:当用户离开网页时(不是关闭网页)触发,通常是 body 标签的属性
// 有些网页在你离开他的网页去访问其他网页时,标题改成桥豆麻袋就是用的这两个属性
// onchange 属性:当用户在文本框里输入的内容改变时触发,通常是 input 输入标签的属性
// onmouseover 属性:当用户的鼠标在标签上浮动时触发
// onmouseout 属性:当用户的鼠标移开标签时触发
// onmousedown 属性:当用户在标签上按下鼠标时触发
// onmouseup 属性:当用户在标签上按下鼠标后释放时触发
// 上面两个属性触发完之后会触发 onclick 属性,这三个属性构成了点击标签的全过程
3.4 添加或删除 DOM
既然我们可以更改 DOM 中的属性,那为什么就不能直接新增或者删除一个 DOM 呢?嗯,你说的没错,JS 确实可以实现。下面列举出 3 个添加删除 DOM 的 JS 函数~ 其中 parent 指代父元素~
parent.appendChild(newChild):这个函数的作用是将一个新的 DOM 元素添加到父元素的尾部。DOM 可以使用 document.createElement("标签名"); 和 document.createTextNode("文本"); 两个方法创建~ 前一个方法创建的是一个标签,后一个方法创建的是一串文本~ 这两个函数返回的是货真价实的 DOM,可以进行各种操作~
parent.removeChild(child):这个函数的作用就是删除 DOM 元素。DOM 元素需要先获取才能删除;
parent.replaceChild(newChild,oldChild):这个函数的作用是把 oldChild 这个 DOM 元素替换成 newChild~
大家有没有发现上面三种方法都需要一个父元素。就拿移除元素来说吧,因为 DOM 它不能自沙,需要请它的爸爸沙了它~(这规矩也够离谱的)那我们怎么获取到他的爸爸呢?使用 parentElement 属性。我们举个例子,我们先打开刚刚那个 test.html,然后控制台输入以下语句:
let child = document.getElementsByTagName("button");
child.parentElement.removeChild(child);
可以看到按钮被删除了。我就不理解,想删一个东西必须要找到它的爸爸然后让它爸亲手删了它,真的是离离原上谱~
还有在实际开发中可能需要在网页的根部直接新建一个 DOM,不需要建一个 div,因为有原生的:body 元素~ 我们可以调用 body 元素的 appendChild 的方法~
3.5 快速更改 DOM 的 CSS
粽锁粥汁我们可以通过更改 DOM 的 style 属性来更改其 CSS 样式,但是有没有更快捷的方式?有!我们可以通过 style.样式 = "值"的语法更改 DOM 的样式~
// 语法 dom.style.样式名 = "值"
// 在 Greasy Fork 下运行此代码
document.getElementById("main-header").style.color = "red";
4. JavaScript BOM
到这里我们 JavaScript 的核心也就讲完了。但还有一个芝士点是不可或缺的,那就是 BOM~
BOM,即浏览器对象模型(Browser Object Model,BOM),它存在的作用就是让 JS 能够和浏览器“对话”。BOM 非常简单,我保证人人都能学会~
4.1 浏览器窗口 window
window,它指代浏览器的窗口。它是一个全局变量,而且 JavaScript 里所有的全局变量都是它的属性,比如 console 也可以写成 window.console~
window.console.log("Hello World!");
window 它既然指代窗口,那就一定可以获得窗口的高度与宽度。我们使用 innerHeight 和 innerWidth 属性获取高度和宽度~
// innerWidth 属性代表宽度,innerHeight 属性代表高度
console.log("Width: "+window.innerWidth+" Height: "+window.innerHeight);
执行结果因浏览器而异~
不仅如此我们还可以通过 window 来打开和关闭窗口~
// 打开链接,在新窗口打开
window.open("链接");
// 关闭当前窗口
window.close();
4.2 页面地址 location
location 指代页面地址,它是一个全局变量。它可以获得当前页面的地址 (URL),并把浏览器重定向到新的页面。
使用 href 属性获取当前页面的地址:
console.log(location.href);
使用 assign 方法打开一个新页面,并且在当前页打开;
location.assign("https://greasyfork.org/zh-CN");
4.3 窗口历史记录 history
history 是一个全局变量,通过它可以实现前一个网页/后一个网页的功能,相当于浏览器上的前进后退键~
// 后退
history.back();
// 前进
history.forward();
5. 用户脚本
恭喜你!你已经把前端三件套全部学会了,可以去开发一些简单的网页了。但不知道大家有没有一个想法:可不可以在不是你自己的 HTML 网页中,比如百度中,一打开网页就触发一段 JS 代码,并且和网页相绑定?用户脚本(Userscripts)就是上述中的 JS 代码。用户脚本除了一些特殊的头语法外其他语法全部和 JS 一样。它简单来说就是小型的浏览器扩展。用户脚本可以实现很多功能,比如下载网页中所有的图片、音频、视频,或是更改网页的样式,消除网页的限制等等。除此之外它还有一个耳熟能详的名字:油猴脚本~
如果对开发用户脚本该兴趣的同学可以接着往下走。反正刚刚已经把 JS 都学完了,还怕这个那么简单的用户脚本吗~
5.1 安装脚本管理器
使用用户脚本的前提是安装脚本管理器。有了脚本管理器你才能让你写的 JS 作用在网页上。目前最流行的脚本管理器是 TamperMonkey,支持 Edge Chrome Firefox Safari Opera,就没有一个浏览器你不支持。其他还有 Violentmonkey,Greasemonkey 等,但都不如 TamperMonkey 来的支持广泛。
打开 Greasy Fork,翻到上图位置,选择你的浏览器后面的 TamperMonkey 开始下载。Chrome 如果安不了的可以按照这个教程来,里面的插件 ID 填 dhdgffkkebhmkfjojejmpbldmpobfkfo。
安装完成点右上角那只黑色猴子(什么?那是猴子?),然后点添加新脚本;
里面就可以写 JS 代码了,下面开始正式学习用户脚本的语法~
5.2 用户脚本的语法
可以看到默认它给了你这些代码:
// ==UserScript==
// @name New Userscript
// @namespace http://tampermonkey.net/
// @version 0.1
// @description try to take over the world!
// @author You
// @match https://crxdl.com/topic/0008-pc-chrome-crx.html
// @icon https://www.google.com/s2/favicons?sz=64&domain=crxdl.com
// @grant none
// ==/UserScript==
(function() {
'use strict';
// Your code here...
})();
前面那一大串注释是用户脚本的头部信息。下面开始拆解:
// ==UserScript== 和 // ==/UserScript==:这两个是用户脚本头部信息的开始语和结束语。头部信息全部写在这两行中间;
// @name New Userscript:@name 属性是这个脚本的名称,New Userscript 是默认名称,你可以更改;
// @namespace http://tampermonkey.net/:@namespace 是这个脚本的命名空间。由于很多脚本都重名,我们需要通过这个命名空间来确保脚本的唯一性。这个的值一般写的是你脚本的官网,如果没有官网的写脚本 GitHub 仓库的链接,如果实在没有就随便写吧……
// @version 0.1:@version 是这个脚本的版本,更新脚本时需要用到,大家应该都很熟了~
// @description try to take over the world!:@description 是这个脚本功能的简单描述;
// @author You:脚本作者的名称,别愣着,填你名字呀~
// @match https://crxdl.com/topic/0008-pc-chrome-crx.html:@match 指定了这个脚本会在打开什么链接时被激活。一个脚本可以有多个 @match。@match 后面的链接可以有通配符 *,比如你想让它在以 https://www.baidu.com/ 为开头的所有链接时激活,就可以使用 https://www.baidu.com/*。这样子你在打开 https://www.baidu.com/hello.html 和 https://www.baidu.com/world.html 这样的链接都会激活脚本~
如果你想让脚本在所有网页都激活,那就填 // @match *,但我个人不建议这样做,因为这样会拖慢网页的打开速度。
// @icon https://www.google.com/s2/favicons?sz=64&domain=crxdl.com:@icon 指定了脚本的图标;
// @grant none:@grant 指定了脚本需要用到的权限,比如剪贴板权限。一般我们只需要剪贴板权限,其他权限用不上。当我们需要时,把这一行更改成这样:
// @grant GM_setClipBoard
然后当我们需要把文字复制到剪贴板时用 GM_setClipBoard 函数:
GM_setClipBoard("文字");
此外还有一些默认没有加上的,我们这里也介绍一下:
// @license:这个属性指明了脚本所使用的协议,一般我们使用 GNU GPLv3 开源软件协议。如果你的脚本要发布出去必须要加上 @license;
// @license GNU GPLv3
// @require:这个属性指明了脚本需要用到的模块,比如 jQuery。我们使用时就直接复制所需模块的 CDN 链接,然后复制到 // @require 后面。这个属性可以出现多次。
如果你想了解更详细的,可以看用户脚本的官方文档:链接。网站为全英文,所以慎入~
那关于后面的这些代码:
(function() {
'use strict';
// Your code here...
})();
这个函数里的内容是脚本的主体部分,它已经帮你加好了 use strict。函数里面就可以写你的脚本代码啦~
5.3 查找和发布脚本
最后一个部分很简单,其实就是 Greasy Fork 的使用。Greasy Fork 是一个脚本社区,里面有很多别人写的脚本可供安装和使用。我们也可以自己写脚本然后上传上去,但是要先登录。我们可以在里面查找你需要的脚本。比如你需要一个抓取 QQ 音乐播放器音频的脚本,就可以在里面搜索 QQ 音乐抓取;
找到你想安装的脚本,比如图中的 StuckGraber 音乐下载器(悄悄告诉你这个脚本是我写的),然后点击;
点安装,我这里由于已经装过了所以只能重新安装。下面是这个脚本的说明和使用方法~
然后 TamperMonkey 就可以自动识别到想要安装的脚本。下面可以看代码。我的实现其实就是把网页中所有 audio 标签的第一个找出来,然后把标签的 src 属性复制到剪贴板。至于你看到的那么多代码有绝大多数都是绑定快捷键的。下面给出一个特别方便的绑定快捷键的代码,需要自取~
window.onload = function() {
HotKeyHandler.Init();
}
let HotKeyHandler = {
currentMainKey: null,
currentValueKey: null,
Init: function() {
HotKeyHandler.Register(0, "D", // 这里 0 代表 Ctrl, 1 代表 shift, 2 代表 alt
// 后面的字母代表快捷键的字母,比如 0,"D" 是指 Ctrl+D
function() {
// 这个里面写自己的代码,当按下快捷键时会触发
});
},
Register: function(tag, value, func) {
let MainKey = "";
switch (tag) {
case 0:
MainKey = 17; // Ctrl
break;
case 1:
MainKey = 16; // Shift
break;
case 2:
MainKey = "18"; // Alt
break;
}
document.onkeyup = function(e) {
HotKeyHandler.currentMainKey = null;
}
document.onkeydown = function(event) {
// 获取键值
let keyCode = event.keyCode;
let keyValue = String.fromCharCode(event.keyCode);
event.preventDefault();
if (HotKeyHandler.currentMainKey != null) {
if (keyValue == value) {
HotKeyHandler.currentMainKey = null;
if (func != null) func();
}
}
if (keyCode == MainKey) HotKeyHandler.currentMainKey = keyCode;
}
}
}
点击安装就可以了~
当你打开脚本后面规定 @match 的那些网页,你如果看到右边 TamperMonkey 处多了一个红点,这说明脚本已经启用。可以按下 Ctrl + D 测试效果~
那如果你想在 Greasy Fork 上发布你的脚本呢?首先你确保你要登录,然后点击你的用户名;
点击”发布你编写的脚本";
把你的代码复制进去,然后再按照规则填写即可~
你的脚本就发布成功了。恭喜你!JavaScript 完结撒花!
6. 小结
Congratulations!你已经把前端三剑客全部学完了~ 下一步就是学习一个前端开发框架,他能帮你快速开发出一个大型网站。目前比较流行的框架有 3 个:Angular.js,React.js 和 Vue.js。之前我浅学了一下 Vue.js,但是悲催的是听不懂(T_T)所以我只好转向 React.js。学完之后我才发现:此生不悔入 React~
然后就是 UI 设计框架。它的作用是美化网页,弥补原生样式带来的缺憾,而且比 CSS 简单很多。目前比较流行的有饿了么旗下的 Element UI,使用 Vue.js;阿里旗下的 Ant Design,使用 React.js;还有一个是 Bootstrap,但是它的 UI 设计有点过时。我后面会出 React.js + Ant Design 的开发教程,敬请期待~ 当然在学这些之前 Node.js 是必须的,我也会出教程~
虽然本文我爆肝 2w 字写了 JS,但是 JavaScript 的使用远不止这些。所以平时查资料是必须的~ 大家 Node.js 见~
附:2022.9.28 更新,由于本文的语法介绍后来检查还有很多疏漏,所以特此补了一篇:链接,文章不长,但是里面的语法都很重要~