NOTE_JS

介绍

尚硅谷最新版JavaScript基础全套教程完整版 140集实战教学 JS从入门到精通

实战

  • 图片切换
  • 全选练习
  • 添加删除记录
  • 协议注册
  • 拖拽
  • 定时图片切换
  • div移动优化
  • 轮播图
  • 二级菜单

整理难免有误,欢迎大家批评指正!


JS简介

1、什么是语言

计算机就是一个由人来控制的机器,人让它干嘛,它就得干嘛。

我们要学习的语言就是人和计算机交流的工具,人类通过语言来控制、操作计算机。

编程语言和我们说的中文、英文本质上没有区别,只是语法比较特殊。

语言的发展:

  • 纸带机:机器语言
  • 汇编语言:符号语言
  • 现代语言:高级语言

2、JS起源

JavaScript诞生于1995年,它的出现主要是用于处理网页中的前端验证。

所谓的前端验证,就是指检查用户输入的内容是否符合一定的规则。

比如:用户名的长度,密码的长度,邮箱的格式等。

1

3、JS简史

  • JavaScript是由网景公司发明,起初命名为LiveScript,后来由于SUN公司的介入更名为了JavaScript
  • 1996年微软公司在其最新的IE3浏览器中引入了自己对JavaScript的实现JScript
  • 于是在市面上存在两个版本的JavaScript,一个网景公司的JavaScript和微软的JScript。
  • 为了确保不同的浏览器上运行的JavaScript标准一致,所以几个公司共同定制了JS的标准名命名为ECMAScript

时间表

年份 事件
1995年 网景公司开发了JavaScript
1996年 微软发布了和JavaScript兼容的JScript
1997年 ECMAScript第1版(ECMA-262)
1998年 ECMAScript第2版
1998年 DOM Level1的制定
1998年 新型语言DHTML登场
1999年 ECMAScript第3版
2000年 DOM Level2的制定
2002年 ISO/IEC16262:2002的确立
2004年 DOM Level3的制定
2005年 新型语言AJAX登场
2009年 ECMAScript第5版
2009年 新型语言HTML5登场

4、实现

ECMAScript是一个标准,而这个标准需要由各个厂商去实现。

不同的浏览器厂商对该标准会有不同的实现。

浏览器 JavaScript实现方式
FireFox SpiderMonkey
Internet Explorer JScript/Chakra
Safari JavaScriptCore
Chrome v8
Carakan Carakan

我们已经知道ECMAScript是JavaScript标准。所以一般情况下,这两个词我们认为是一个意思。

但是实际上JavaScript的含义却要更大一些。

一个完整的JavaScript实现应该由以下三个部分构成:

image-20210707211446577

5、学习内容

我们已经知道了一个完整的JavaScript实现包含了三个部分:ECMAScript、DOM和BOM。

由此我们也知道了我们所要学习的内容就是这三部分。

  • ECMAScript
  • DOM
  • BOM

6、JS的特点

  • 解释型语言
  • 类似于C和Java的语法结构
  • 动态语言
  • 基于原型的面向对象

7、HelloWorld

控制浏览器弹出一个警告框

1
alert("Hello World!");

让计算机在页面中输出一个内容

1
document.write("Hello World!");

向控制台输出一个内容

1
console.log("Hello World!");

JS基础

1、JS编写位置

可以将js代码编写到标签的onclick属性中当我们点击按钮时,js代码才会执行

1
<button onclick="alert(\"Fuck! Do not touch me!\")"></button>

可以将js代码写在超链接的href属性中,这样当点击超链接时,会执行js代码

1
<a href="alert(\"What's up, man?\")">Try to click me</a>

虽然可以写在标签的属性中,但是他们属于结构与行为耦合,不方便维护,不推荐使用

可以将js代码编写到script标签

1
2
3
<script type="text/javascript">
alert("I'm inner script.");
</script>

可以将js代码编写到外部js文件中,然后通过script标签引入

1
<script src="/js/script.js" type="text/javascript"></script>

script标签一旦用于引入外部文件了,就不能在编写代码了,即使编写了浏览器也会忽略

如果需要则可以在创建一个新的script标签用于编写内部代码

2、JS注释

多行注释

多行注释,注释中的内容不会被执行,但是可以在源代码中查看

1
2
3
4
5
/*
多行注释...
多行注释...
多行注释...
*/

单行注释

1
// 单行注释

3、注意点

  1. JS中严格区分大小写

  2. JS中每一条语句以分号;结尾

    如果不写分号,浏览器会自动添加,但是会消耗一些系统资源,而且有些时候,浏览器会加错分号,所以在开发中分号必须写

  3. JS中会忽略多个空格和换行,所以我们可以利用空格和换行对代码进行格式化

4、字面量与变量

字面量

字面量,都是一些不可改变的值

字面量都是可以直接使用,但是我们一般都不会直接使用字面量

变量

变量可以用来保存字面量,而且变量的值是可以任意改变的变量更加方便我们使用

所以在开发中都是通过变量去保存一个字面量,而很少直接使用字面量

可以通过变量对字面量进行描述

1
2
3
4
5
6
7
8
9
10
11
// 声明变量: 在js中使用var关键字来声明一个变量
var a;
// 为变量赋值
a = 123;
a = 456;
a = 123124223423424;
// 声明和赋值同时进行
var b = 789;
var c = 0;
var age = 80;
console.log(age);

5、标识符

在JS中所有的可以由我们自主命名的都可以称为是标识符

例如:变量名、函数名、属性名都属于标识符

命名一个标识符时需要遵守如下的规则:

  1. 标识符中可以含有字母、数字、_、$
  2. 标识符不能以数字开头
  3. 标识符不能是ES中的关键字或保留字
  4. 标识符一般都采用驼峰命名法
    • 首字母小写,每个单词的开头字母大写,其余字母小写

关键字

if else do while for
break continue try catch finally
throw true false function return
switch case null typeof instanceof
new var void in with
default debugger delete this

保留字

class enum extends super const export
import implements let private public yield
interface package protected static

其他不建议使用的标识符

boolean byte short char int long
float double String Boolean Number Object
Date Array Math Error SyntaxError EvalError
TypeError URIError RangeError ReferenceError encodeURI decodeURI
parselnt parseFloat NaN isNaN undefined transient
throws native goto eval JSON Infinity
arguments isFinite volatile abstract RegExp Function
synchronize final encodeURICOmponent decodeURIComponent

JS底层保存标识符时实际上是采用的Unicode编码,所以理论上讲,所有的utf-8中含有的内容都可以作为标识符

6、数据类型

数据类型指的就是字面量的类型,在JS中一共有六种数据类型

基本数据类型 String 字符串
Number 数值
Boolean 布尔值
Null 空值
Undefined 未定义
引用类型 Object 对象

其中StringNumberBooleanNullUndefined属于基本数据类型,而Object属于引用数据类型

String字符串

在JS中,字符串需要使用引号引起来,使用单引号或双引号都可以,但不要混合使用

同一种引号不能嵌套,双引号不能放双引号,单引号不能放单引号

在字符串中我们可以使用\作为转义字符,当表示一些特殊符号时可以使用\进行转义

  • \"表示"
  • \'表示'
  • \n表示换行
  • \t制表符
  • \\表示\

Number数值

在JS中,所有的数值都是Number类型,包括整数和浮点数(小数)

可以使用一个运算符typeof,来检查一个变量的类型。语法:typeof 变量

  • 检查字符串时,会返回string
  • 检查数值时,会返回number

MAX_VALUE

JS中可以表示的数字的最大值 Number.MAX_VALUE=1.7976931348623157e+308

如果使用Number表示的数字超过了最大值,则会返回一个Infinity

1
2
var a = Number.MAX_VALUE * Number.MAX_VALUE;
console.log(a); // Infinity

MIN_VALUE

大于0的最小值 Number.MIN_VALUE=5e-324

1
2
var a = Number.MIN_VALUE * Number.MIN_VALUE;
console.log(a); // 0

Infinity

  • Infinity表示正无穷
  • -Infinity 表示负无穷

使用typeof检查,Infinity会返回Number

1
2
var a = Number.MAX_VALUE * Number.MAX_VALUE;
console.log(typeof a); // number

NaN

NaN是一个特殊的数字,表示Not A Number

1
2
var a = 'abc' * 'def';
console.log(a); // NaN

使用typeof检查一个NaN也会返回number

1
2
var a = 'abc' * 'def';
console.log(typeof a); // number

运算精度

在JS中整数的运算基本可以保证精确

如果使用JS进行浮点运算,可能得到一个不精确的结果

1
2
var a = 0.1 + 0.2;
console.log(a); // 0.30000000000000004

所以千万不要使用JS进行对精确度要求比较高的运算

Boolean布尔值

布尔值只有两个,主要用来做逻辑判断

  • true表示真
  • false表示假

使用typeof检查一个布尔值时,会返回boolean

Null

Null类型的值只有一个,就是null

null这个值专门用来表示一个为空的对象

使用typeof检查一个null值时,会返回object

1
2
3
var a3 = null;
console.log(a3); // null
console.log(typeof a3); // object

Undefined

Undefined(未定义)类型的值只有一个,就是undefind

当声明一个变量,但是并不给变量赋值时,它的值就是undefined

使用typeof检查一个undefined时,也会返回undefined

1
2
3
var a4;
console.log(a4); // undefind
console.log(typeof a4); // undefind

7、强制类型转换

指将一个数据类型强制转换为其他的数据类型

类型转换主要指,将其他的数据类型,转换为StringNumberBoolean

7.1、其他数据类型转换为String

方式一:调用被转换数据类型的toString()方法

该方法不会影响到原变量,它会将转换的结果返回

1
2
3
4
5
6
7
8
9
10
// Number转换为String
var a1 = 123;
var b1 = a1.toString();
console.log(typeof a1); // number
console.log(typeof b1); // string
// Boolean转换为String
var a2 = true;
var b2 = a2.toString();
console.log(typeof a2); // boolean
console.log(typeof b2); // string

但是注意:nullundefined这两个值没有toString(),如果调用他们的方法,会报错

1
2
3
4
5
6
7
8
9
10
// Null转换为String
var a3 = null;
var b3 = a3.toString(); // UncaughtTypeError: Cannot read property 'toString' of null
console.log(typeof a3);
console.log(typeof b3);
// Undefined转换为String
var a4 = undefined;
var b4 = a4.toString(); // UncaughtTypeError: Cannot read property 'toString' of undefined
console.log(typeof a4);
console.log(typeof b4);

方式二:调用String()函数,并将被转换的数据作为参数传递给函数

使用String()函数做强制类型转换时,对于NumberBoolean实际上就是调用的toString()方法

但是对于nullundefined,就不会调用toString()方法,而是将

  • null直接转换为"null"
  • undefined 直接转换为"undefined"
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// Number转换为String
var a1 = 123;
var b1 = String(a1);
console.log(typeof a1); // number
console.log(typeof b1); // string
// Boolean转换为String
var a2 = true;
var b2 = String(a2);
console.log(typeof a2); // boolean
console.log(typeof b2); // string
// Null转换为String
var a3 = null;
var b3 = String(a3);
console.log(typeof a3); // object
console.log(typeof b3); // string
// Undefined转换为String
var a4 = undefined;
var b4 = String(a4);
console.log(typeof a4); // undefined
console.log(typeof b4); // string

7.2、其他数据类型转换为Number

方式一:使用Number()函数

  • 字符串 --> 数字

    • 如果是纯数字的字符串,则直接将其转换为数字
    • 如果字符串中有非数字的内容,则转换为NaN
    • 如果字符串是一个空串或者是一个全是空格的字符串,则转换为0
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    // **转换方式一:使用Number()函数**
    // 纯数字的字符串
    var a1 = '123';
    a1 = Number(a1);
    console.log(typeof a1); // number
    console.log(a1); // 123
    // 非数字的内容
    // var a2 = 'abc';
    var a2 = undefined;
    a2 = Number(a2);
    console.log(typeof a2); // number
    console.log(a2); // NaN
    // 空串
    // var a3 = ' ';
    var a3 = null;
    a3 = Number(a3);
    console.log(typeof a3); // number
    console.log(a3); // 0
  • 布尔 --> 数字

    • true转成1
    • false转成0
    1
    2
    3
    4
    5
    6
    7
    8
    var a4 = true;
    a4 = Number(a4);
    console.log(typeof a4); // number
    console.log(a4); // 1
    var a5 = false;
    a5 = Number(a5);
    console.log(typeof a5); // number
    console.log(a5); // 0

方式二:专门用来对付字符串

  • parseInt()把一个字符串转换为一个整数:可以将一个字符串中的有效整数部分取出来,然后转换为Number
  • parseFloat()把一个字符串转换为一个浮点数:可以将一个字符串中的有效小数部分取出来,然后转换为Number
  • 如果对非String使用parseInt()parseFloat(),它会先将其转换为String,然后再操作
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
var a1 = "123";
a1 = parseInt(a1);
console.log(typeof a1); // number
console.log(a1); // 123
var a2 = "123.456";
a2 = parseInt(a2);
console.log(typeof a2); // number
console.log(a2); // 123
var a3 = "123px";
a3 = parseInt(a3);
console.log(typeof a3); // number
console.log(a3); // 123
// var a4 = null;
// var a4 = undefined;
// var a4 = '';
// var a4 = 'abc';
// var a4 = true;
var a4 = false;
a4 = parseInt(a4);
console.log(typeof a4); // number
console.log(a4); // NaN

7.3、其他数据类型转换为Boolean

方式一:使用Boolean()函数

  • 数字-—->布尔
    • 除了0NaN,其余的都是true
  • 字符串-—->布尔
    • 除了空串,其余的都是true
  • nullundefined都会转换为false
  • 对象也会转换为true
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// - 数字-—->布尔
// - 除了`0`和`NaN`,其余的都是`true`
// var a1 = 0;
var a1 = NaN;
a1 = Boolean(a1);
console.log(a1); // false
var a2 = 123;
a2 = Boolean(a2);
console.log(a2); // true
// - 字符串-—->布尔
// - 除了空串,其余的都是`true`
var a3 = "123";
a3 = Boolean(a3);
console.log(a3); // true
var a4 = " ";
a4 = Boolean(a4);
console.log(a4); // true
var a5 = "";
a5 = Boolean(a5);
console.log(a5); // false
// - `null`和`undefined`都会转换为`false`
// var a6 = null;
var a6 = undefined;
a6 = Boolean(a6);
console.log(a6); // false

方式二:隐式类型转换

为任意的数据类型做两次非运算,即可将其转换为布尔值(下一节会介绍)

1
2
3
var a = "123";
var b = !!a;
console.log("a="+a+",b="+b); // a=true,b=true

8、补充

在js中,如果需要表示16进制的数字,则需要以0x开头

如果需要表示8进制的数字,则需要以0开头

如果需要表示2进制的数字,则需要以0b开头,但是不是所有的浏览器都支持

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 十六进制数字
var a = 0x10;
console.log(a); // 16
a = 0xff;
console.log(a); // 255
a = 0xCafe;
console.log(a); // 51966
a = "0x70";
a = parseInt(a,16);
console.log(a); // 112
// 八进制数字
a = 070;
console.log(a); // 56
a = "070";
a = parseInt(a,8);
console.log(a); // 56
// 二进制数字
a = 0b10;
console.log(a); // 2

JS运算

1、运算符

运算符也叫操作符,通过运算符可以对一个或多个值进行运算,并获取运算结果

比如:typeof就是运算符,可以来获得一个值的类型

它会将该值的类型以字符串的形式返回 number string boolean undefined object

2、算数运算符

  • 当对非Number类型的值进行运算时,会将这些值转换为Number然后再运算
  • 任何值和NaN做运算都得NaN

算数运算符

  • + 可以对两个值进行加法运算,并将结果返回

    • 如果对两个字符串进行加法运算,则会将两个字符串拼接为一个字符串,并返回
    • 任何的值和字符串做加法运算,都会先转换为字符串,然后再和字符串做拼串的操作
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    var a1 = 123 + 456;
    console.log(a1); // 579
    var a2 = "123" + "456";
    console.log(a2); // 123456
    var a3 = "123" + 456;
    console.log(a3); // 123456
    var a4 = 123 + "456";
    console.log(a4); // 123456
    var a5 = 123 + true;
    console.log(a5); // 124
    var a6 = 123 + "";
    console.log(a6); // 123
    var a7 = 123 + null;
    console.log(a7); // 123
    var a8 = 123 + undefined;
    console.log(a8); // NaN
    var a9 = 123 + NaN;
    console.log(a9); // NaN
    var a10 = "123" + NaN;
    console.log(a10); // 123NaN
  • - 可以对两个值进行减法运算,并将结果返回

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    var b1 = 456 - 123;
    console.log(b1); // 333
    var b2 = 456 - "123";
    console.log(b2); // 333
    var b3 = 456 - true;
    console.log(b3); // 455
    var b4 = 456 - "";
    console.log(b4); // 456
    var b5 = 456 - null;
    console.log(b5); // 456
    var b6 = 456 - "abc";
    console.log(b6); // NaN
    var b7 = 456 - NaN;
    console.log(b7); // NaN
    var b8 = 456 - undefined;
    console.log(b8); // NaN
  • * 可以对两个值进行乘法运算,并将结果返回

  • / 可以对两个值进行除法运算,并将结果返回

  • % 可以对两个值进行取模运算,并将结果返回

根据这些特点,我们可以利用+""将Number转为String,利用-0*1/1将String转为Number

3、一元运算符

一元运算符,只需要一个操作数

  • + 正号:正号不会对数字产生任何影响
  • - 负号:负号可以对数字进行负号的取反

对于非Number类型的值,它会将先转换为Number,然后再运算

可以对一个其他的数据类型使用+,来将其转换为Number,它的原理和Number()函数一样

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
var a = "10";
var result = +a;
console.log(typeof result); // number
console.log("result="+result); // result=10
result = -a;
console.log("result="+result); // result=-10
a = true;
result = +a;
console.log("result="+result); // result=1
a = "";
result = +a;
console.log("result="+result); // result=0
a = null;
result = +a;
console.log("result="+result); // result=0
a = undefined;
result = +a;
console.log("result="+result); // result=NaN
a = NaN;
result = +a;
console.log("result="+result); // result=NaN

4、自增和自减

自增++

通过自增可以使变量在自身的基础上增加1

自增分成两种:后++(a++)和前++(++a

无论是a++还是++a,都会立即使原变量的值自增1

不同的是a++++a的值不同

  • a++是变量的原值(自增前的值)
  • ++a是变量的新值(自增后的值)
1
2
3
4
5
6
7
var a,b;
a = 1;
b = a++;
console.log("a++ = " + b + ", a = " + a); // a++ = 1, a = 2
a = 1;
b = ++a;
console.log("++a = " + b + ", a = " + a); // ++a = 2, a = 2

自减–

通过自减可以使变量在自身的基础上减少1

自减分成两种:后–(a--)和前–(--a

无论是a--还是--a,都会立即使原变量的值自减1

不同的是a----a的值不同

  • a--是变量的原值(自减前的值)
  • --a是变量的新值(自减后的值)
1
2
3
4
5
6
7
var a,b;
a = 1;
b = a--;
console.log("a-- = " + b + ", a = " + a); // a-- = 1, a = 0
a = 1;
b = --a;
console.log("--a = " + b + ", a = " + a); // --a = 0, a = 0

练习

1
2
3
a = 10;
b = a++ + ++a + a; // 10 + 12 + 12;
console.log('a = ' + a + ', b = ' + b); // a = 12, b = 34

5、逻辑运算符

JS中为我们提供了三种逻辑运算符

  • !
  • &&
  • ||

非运算

!可以用来对一个值进行非运算

所谓非运算就是值对一个布尔值进行取反操作,truefalsefalsetrue

  • 如果对一个值进行两次取反,它不会变化
  • 如果对非布尔值进行运算,则会将其转换为布尔值,然后再取反

所以我们可以利用该特点,来将一个其他的数据类型转换为布尔值

可以为一个任意数据类型取两次反,来将其转换为布尔值,原理和Boolean()函数一样

1
2
3
4
5
6
var a,b;
a = true;
b = !a;
console.log("a="+a+",b="+b); // a=true,b=false
b = !!a;
console.log("a="+a+",b="+b); // a=true,b=true

与运算

&&可以对符号两侧的值进行与运算并返回结果

运算规则

  • 两个值中只要有一个值的false就返回false;只有两个值都为true时,才会返回true
  • JS中的“与”属于短路的与,如果第一个值为false,则不会检查第二个值
1
2
3
4
5
6
7
8
9
var a;
a = true && true; // true
console.log(a);
a = true && false; // false
console.log(a);
a = false && true; // false
console.log(a);
a = false && false; // false
console.log(a);

或运算

||可以对符号两侧的值进行或运算并返回结果

运算规则:

  • 两个值中只要有一个true,就返回true;如果两个值都为false,才返回false
  • JS中的“或”属于短路的或,如果第一个值为true,则不会检查第二个值
1
2
3
4
5
6
7
8
9
var a;
a = true || true; // true
console.log(a);
a = true || false; // true
console.log(a);
a = false || true; // true
console.log(a);
a = false || false; // false
console.log(a);

&&、|| 非布尔值的情况

对于非布尔值进行与或运算时,会先将其转换为布尔值,然后再运算,并且返回原值

与运算

  • 如果第一个值为true,则必然返回第二个值
  • 如果第一个值为false,则直接返回第一个值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
var result;
result = 1 && 2;
console.log(result); // 2
result = 2 && 1;
console.log(result); // 1
result = 1 && 0;
console.log(result); // 0
result = 0 && 1;
console.log(result); // 0
result = "" && 1;
console.log(result); //
result = 1 && "";
console.log(result); //
result = null && 1;
console.log(result); // null
result = 1 && null;
console.log(result); // null
result = undefined && 1;
console.log(result); // undefined
result = 1 && undefined;
console.log(result); // undefined

或运算

  • 如果第一个值为true,则直接返回第一个值
  • 如果第一个值为false,则返回第二个值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
var result;
result = 1 || 2;
console.log(result); // 1
result = 2 || 1;
console.log(result); // 2
result = 1 || 0;
console.log(result); // 1
result = 0 || 1;
console.log(result); // 1
result = "" || 1;
console.log(result); // 1
result = 1 || "";
console.log(result); // 1
result = null || 1;
console.log(result); // 1
result = 1 || null;
console.log(result); // 1
result = undefined || 1;
console.log(result); // 1
result = 1 || undefined;
console.log(result); // 1

6、赋值运算符

= 可以将符号右侧的值赋值给符号左侧的变量

  • += a+=5等价于a=a+5
  • -= a-=5等价于a=a-5
  • *= a*=5等价于a=a*5
  • /= a/=5等价于a=a/5
  • %= a%=5等价于a=a%5

7、关系运算符

通过关系运算符可以比较两个值之间的大小关系,如果关系成立它会返回true,如果关系不成立则返回false

  • >:大于号,判断符号左侧的值是否大于右侧的
  • >=:大于等于号,判断符号左侧的值是否大于等于右侧的
  • <:小于号,判断符号左侧的值是否小于右侧的
  • <=:小于等于号,判断符号左侧的值是否小于等于右侧的

任何值和NaN做任何比较都是false

非数值的情况

对于非数值进行比较时,会将其转换为数字然后再比较

如果符号两侧的值都是字符串时,不会将其转换为数字进行比较,而是分别比较字符串中字符的Unicode编码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
console.log(1>true);      // false
console.log(1>=true); // true
console.log(1>"0"); // true
console.log(10>null); // true
// 任何值和NaN做任何比较都是`false`
console.log(10<="hello"); // false
// 比较两个字符串时,比较的是字符串的字符编码
console.log("a" < "b"); // true
// 比较字符编码时是一位一位进行比较
// 如果两位一样,则比较下一位,所以借用它来对英文进行排序
console.log("bcd" < "b"); // false
console.log("11" < "5"); // true
// 比较中文时没有意义
console.log("我" < "你"); // false
// 如果比较的两个字符串型的数字,可能会得到不可预期的结果
// 注意:在比较两个字符串型的数字时,一定一定一定要转型
console.log("12345675432" < +"5"); // false

8、相等运算符

== 相等

相等运算符用来比较两个值是否相等,如果相等会返回true,否则返回false

使用==来做相等运算:当使用==来比较两个值时,如果值的类型不同,则会自动进行类型转换,将其转换为相同的类型然后在比较

1
2
3
4
5
6
7
// undefined 衍生自null,所以这两个值做相等判断时,会返回true
console.log(null == undefined); // true
// NaN不和任何值相等,包括他本身
console.log(NaN == NaN); // false
// 可以通过isNaN()函数来判断一个值是否是NaN
// 如果该值是NaN则返回true,否则返回false
console.log(isNaN(NaN)); // true

!= 不想等

不相等运算符用来判断两个值是否不相等,如果不相等返回true,否则返回false

使用!=来做不相等运算:不相等也会对变量进行自动的类型转换,如果转换后相等它也会返回false

=== 全等

用来判断两个值是否全等,它和相等类似,不同的是它不会做自动的类型转换,如果两个值的类型不同,直接返回false

!==不全等

用来判断两个值是否不全等,和不等类似,不同的是它不会做自动的类型转换,如果两个值的类型不同,直接返回false

1
2
console.log("123" === 123); // false
console.log("123" !== 123); // true

关于改运算符,可以参考下图

img

9、条件运算符

条件运算符也叫三元运算符 条件表达式 ? 语句1: 语句2;

执行的流程:条件运算符在执行时,首先对条件表达式进行求值

  • 如果该值为true,则执行语句1,并返回执行结果
  • 如果该值为false,则执行语句2,并返回执行结果
1
2
3
4
5
6
7
8
9
var a = 30;
var b = 40;
var c = 90;
// 两数中取MAX
var max = a > b ? a : b;
console.log("max="+max);
// 三数中取MAX
max = a > b ? (a > c ? a : c) : (b > c ? b : c);
console.log("max="+max);

如果条件的表达式的求值结果是一个非布尔值,则会将其转换为布尔值,然后再运算

1
"hello" ? alert("111111") : alert("2222222");

10、运算符优先级

就和数学中一样,在JS中运算符也有优先级,比如:先乘除后加减

在JS中有一个运算符优先级的表,在表中越靠上优先级越高,优先级越高越优先计算,如果优先级一样,则从左往右计算

但是这个表我们并不需要记忆,如果遇到优先级不清楚可以使用()来改变优先级

.[]new
()
++--
!+(单目)、-(单目)、typeofvoiddelete
%*/
+(双目)、-(双目)
<<>>>>>
<<=>>=
==!=====
&
^
|
&&
||
?:
=+=-=*=/=%=<<=>>=>>>=&=^=|=
,

补充:Unicode编码表

Unicode官网:https://home.unicode.org/

在字符串中使用转义字符输入Unicode编码:\u四位编码

在网页中使用Unicode编码:&#编码; 这里的编码需要的是10进制

流程控制

1、流程控制语句

JS中的程序是从上到下一行一行执行的

通过流程控制语句可以控制程序执行流程,使程序可以根据一定的条件来选择执行

语句的分类:

  • 条件判断语句
  • 条件分支语句
  • 循环语句

2、条件判断语句

使用条件判断语句,可以在执行某个语句之前进行判断

如果条件成立才会执行语句,条件不成立则语句不执行。

if 语句

语法一

1
2
3
if(条件表达式) {
语句
}

if语句在执行时,会先对条件表达式进行求值判断

  • 如果条件表达式的值为true,则执行if后的语句
  • 如果条件表达式的值为false,则不执行if后的语句

if语句只能控制紧随其后的那个语句,如果希望if语句可以控制多条语句,可以将这些语句统一放到代码块中

if语句后的代码块不是必须的,但是在开发中尽量写上代码块,即使if后只有一条语句

语法二

1
2
3
4
5
if(条件表达式) {
语句1...
} else {
语句2...
}

if...else...语句执行时,会先对if后的条件表达式进行求值判断

  • 如果该值为true,则执行if后的语句
  • 如果该值为false,则执行else后的语句

语法三

1
2
3
4
5
6
7
8
9
if(条件表达式) {
语句1...
} else if(条件表达式) {
语句2...
} else if(条件表达式) {
语句3...
} else{
语句4...
}

if...else if...else语句执行时,会从上到下依次对条件表达式进行求值判断

  • 如果值为true,则执行当前语句
  • 如果值为false,则继续向下判断
  • 如果所有的条件都不满足,则执行最后一个else后的语句
  • 该语句中,只会有一个代码块被执行,一旦代码块执行了,则直接结束语句

练习

prompt()可以弹出一个提示框,该提示框中会带有一个文本框,用户可以在文本框中输入一段内容

该函数需要一个字符串作为参数,该字符串将会作为提示框的提示文字

用户输入的内容将会作为函数的返回值返回,可以定义一个变量来接收该内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// 练习1
// 从键盘输入小明的期末成绩:
// 当成绩为100时,’奖励一辆BMW’
// 当成绩为[80-99]时,’奖励一台iphone15s'
// 当成绩为[60-80]时,’奖励一本参考书’
// 其他时,什么奖励也没有
var score = prompt("请输入小明的期末成绩:");
if(score == 100){
alert("奖励一辆BMW");
} else if(score >80 && score <= 99){
alert("奖励一台iphone15s");
} else if(score >60 && score <= 80){
alert("奖励一本参考书");
} else{
alert("什么奖励也没有");
}

// 练习2
// 大家都知道,男大当婚,女大当嫁。那么女方家长要嫁女儿,当然要提出一定的条件:
// 高:180cm以上;富:1000万以上;帅:500以上;如果这三个条件同时满足,则:’我一定要嫁给他’
// 如果三个条件有为真的情况,则:’嫁吧,比上不足,比下有余。’
// 如果三个条件都不满足,则:’不嫁!’

// 练习3
// 编写程序,由键盘输入三个整数分别存入变量num1、num2、num3,对他们进行排序,并且从小到大输出。

其他练习,大家可以自己尝试做下。练习2还是很简单的,跟练习1差不多,无非就是多了几次输入。练习3的话,如果你是初学编程的话,可以尝试做一做,不过个人感觉可以在学完for循环之后再做,而且这个应该当做简单的算法题。

switch 语句

1
2
3
4
5
6
7
8
9
10
11
switch(条件表达式) {
case 表达式1:
语句1...;
break;
case 表达式2:
语句2...;
break;
default:
语句...;
break;
}

switch...case..语句

在执行时会依次将case后的表达式的值和switch后的条件表达式的值进行全等比较

  • 如果比较结果为true,则从当前case处开始执行代码。当前case后的所有的代码都会执行,我们可以在case的后边跟着一个break关键字,这样可以确保只会执行当前case后的语句,而不会执行其他的case
  • 如果比较结果为false,则继续向下比较
  • 如果所有的比较结果都为false,则只执行default后的语句

switch语句和if语句的功能实际上有重复的,使用switch可以实现if的功能,同样使用if也可以实现switch的功能,所以我们使用时,可以根据自己的习惯选择

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 对于成绩大于60分的,输出’合格’。低于60分的,输出’不合格’
var score = prompt("请输入成绩:");
var tmp = parseInt(score/10);
switch (tmp){
case 10:
case 9:
case 8:
case 7:
case 6:
alert("合格");
break;
default:
alert("不合格");
break;
}

while 语句

循环语句:通过循环语句可以反复的执行一段代码多次

while循环语法:

1
2
3
while(条件表达式) {
语句...
}

while语句在执行时,先对条件表达式进行求值判断

  • 如果值为true,则执行循环体,循环体执行完毕以后,继续对表达式进行判断
  • 如果为true,则继续执行循环体,以此类推
  • 如果值为false,则终止循环
1
2
3
4
var a = 0;
while(true) {
alert(a++);
}

像这种将条件表达式为true的循环,叫做死循环

该循环不会停止,除非浏览器关闭,死循环在开发中慎用。可以使用break,来终止循环

1
2
3
4
5
6
7
var i = 0;
while(true){
document.write(i++ + "<br/>");
if(i > 10){
break;
}
}

创建一个循环,往往需要三个步骤:

  1. 创初始化一个变量
  2. 在循环中设置一个条件表达式
  3. 定义一个更新表达式,每次更新初始化变量
1
2
3
4
5
6
7
// 1.创初始化一个变量
var i = 0;
// 2.在循环中设置一个条件表达式
while(i < 10){
// 3.定义一个更新表达式,每次更新初始化变量
document.write(i++ + "<br/>");
}

练习

1
2
3
4
5
6
7
8
// 假如投资的年利率为5%,试求从1000块增长到5000块,需要花费多少年
var money = 1000;
var year = 0;
while(money < 5000){
money *= 1 + 0.05;
year++;
}
alert("需要花费" + year + "年");

do-while 语句

do...while循环语法:

1
2
3
do{
语句...
}while(条件表达式)

do...while语句在执行时,会先执行循环体,循环体执行完毕以后,在对while后的条件表达式进行判断

  • 如果结果为true,则继续执行循环体,执行完毕继续判断,以此类推
  • 如果结果为false,则终止循环

实际上这两个语句功能类似,不同的是

  • while是先判断后执行,而do...while会先执行后判断
  • do...while可以保证循环体至少执行一次,而while不能

for 语句

for语句,也是一个循环语句,也称为for循环

for循环中,为我们提供了专门的位置用来放三个表达式:

  • 初始化表达式
  • 条件表达式
  • 更新表达式

for循环的语法:

1
2
3
for(①初始化表达式;②条件表达式;③更新表达式) {
④语句...
}

for循环的执行流程:

  • ①执行初始化表达式,初始化变量(初始化表达式只会执行一次)
  • ②执行条件表达式,判断是否执行循环。
    • 如果为true,则执行④语句
    • 如果为false,则终止循环
  • ③执行更新表达式,更新表达式执行完毕,继续重复②

for循环中的三个部分都可以省略,也可以写在外部

如果在for循环中不写任何的表达式,只写两个;,此时循环是一个死循环会一直执行下去,慎用

1
2
3
for(;;){
alert("hello");
}

练习

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
// 练习1、打印1-100之间所有奇数之和
for(i=1,result=0;i<=100;i++){
if(i%2 == 1){
result += i;
}
}
console.log('result='+result);

// 练习2、打印1-100之间所有7的倍数的个数及总和
for(i=1,result=0,count=0;i<=100;i++){
if(i%7 == 0){
result += i;
count++;
}
}
console.log('个数='+count+',总和='+result);

// 练习3、水仙花数
// 水仙花数是指一个3位数,它的每个位上的数字的3次幂之和等于它本身。
// (例如:1^3+5^3+3^3=153),请打印所有的水仙花数。
var hundreds_place;
var tens_place;
var ones_place;
for(i=100;i<1000;i++){
hundreds_place = parseInt(i/100);
tens_place = parseInt(i/10) - parseInt(hundreds_place*10);
ones_place = i % 10;
if(i == (hundreds_place*hundreds_place*hundreds_place
+ tens_place*tens_place*tens_place
+ ones_place*ones_place*ones_place)){
console.log(i);
}
}

// 练习4、在页面中接收一个用户输入的数字,并判断该数是否是质数。
// 质数:只能被1和它自身整除的数,1不是质数也不是合数,质数必须是大于1的自然数。
var num = prompt("请输入一个数字:");
while(isNaN(num)){
num = prompt("请输入一个数字:");
}
flag = true;
for(j=2;j<num;j++){
if(num%j==0){
flag = false;
break;
}
}
if(flag){
alert(num + "是质数");
} else{
alert(num + "不是质数");
}

// 练习5、通过程序,在页面中输出如下的图形:
//*
//**
//***
//****
//*****
// 通过一个for循环来输出图形
// 这个for循环执行几次,图形的高度就是多少
// 它可以用来控制图形的高度
for(i=0;i<5;i++){
// 在循环的内部再创建一个循环,用来控制图形的宽度
// 目前我们的外部的for循环执行1次,内部的就会执行5次
// 内层循环可以来决定图形的宽度,执行几次图形的宽度就是多少
for(j=0;j<=i;j++){
document.write("*");
}
document.write("<br/>");
}
// *****
// ****
// ***
// **
// *
for(i=0;i<5;i++){
for(j=0;j<5-i;j++){
document.write("*");
}
document.write("<br/>");
}

// 练习6、九九乘法表
for(i=1;i<10;i++){
for(j=1;j<=i;j++){
document.write(j + "×" + i + "=" + i*j + "\t");
}
document.write("<br/>");
document.write("<br/>");
}


// 练习7、打印出1~100之间的所有质数
var flag = true;
for(i=2;i<=100;i++){
flag = true;
for(j=2;j<i;j++){
if(i%j==0){
flag = false;
break;
}
}
if(flag){
console.log(i);
}
}

// 质数性能优化
console.time("test");
var flag;
for(i=2;i<=100000;i++){
flag = true;
for(j=2;j<=i/Math.sqrt(i);j++){
if(i%j==0){
flag = false;
break;
}
}
if(flag){
// console.log(i);
}
}
console.timeEnd("test");

break和continue

不能在if语句中使用breakcontinue

break

  • break关键字可以用来退出switch或循环语句
  • break关键字,会立即终止离他最近的那个循环语句

可以为循环语句创建一个label,来标识当前的循环label

循环语句使用break语句时,可以在break后跟着一个label,这样break将会结束指定的循环,而不是最近的

continue

  • continue关键字可以用来跳过当次循环
  • continue也是默认只会对离他最近的循环循环起作用

对象

JS中数据类型

  • String 字符串
  • Number数值
  • Boolean 布尔值
  • Null空值
  • Undefined 未定义

以上这五种类型属于基本数据类型,以后我们看到的值只要不是上边的5种,全都是对象

1、Object 对象

基本数据类型都是单一的值"hello" 123 true,值和值之间没有任何的联系。

在JS中来表示一个人的信息(name gender age):

1
2
3
var name = "孙悟空";
var gender = "男";
var age = 18;

如果使用基本数据类型的数据,我们所创建的变量都是独立,不能成为一个整体。

对象属于一种复合的数据类型,在对象中可以保存多个不同数据类型的属性。

2、对象的分类

2.1、内建对象

由ES标准中定义的对象,在任何的ES的实现中都可以使用

常见内建对象有以下,都可以直接通过new调用构造函数创建对象实例:

  • Object、Function、Array、String、Number、Boolean、Date、RegExp
  • Error(EvalError、RangeError、ReferenceError、SyntaxError、TypeError、URIError)
1
2
3
4
5
6
// Math
Math.sqrt(2);
// String
String(2);
// Number
Number("2");

2.2、宿主对象

由JS的运行环境提供的对象,目前来讲主要指由浏览器提供的对象

比如 BOM DOM

1
2
3
4
// console
console.log("hello");
// document
document.write("hello");

JavaScript实现包括三部分:

组成 作用 地位 例子
ES(ECMAScript) 描述JS语法和基本对象 核心
DOM(Document Object Model 文档对象模型) HTML和XML的应用程序接口,处理网页内容的方法和接口 W3C标准 document
BOM(Browser Object Model 浏览器对象模型) 描述与浏览器进行交互的方法和接口,处理浏览器窗口和框架 浏览器厂商对DOM的实现 window

DOM

img

BOM

img

DOM 和 BOM 的关系

JavaScript的Dom和Bom

2.3、自定义对象

由开发人员自己创建的对象

使用new关键字调用的函数,是构造函数constructor,构造函数是专门用来创建对象的

函数使用typeof检查一个对象时,会返回object

在对象中保存的值称为属性

  • 添加或修改对象属性的语法:对象.属性名=属性值;
  • 读取对象属性的语法:对象.属性名
  • 删除对象属性的语法:delete 对象.属性名;
1
2
3
4
5
6
7
8
9
10
11
var obj = new Object();
// 向obj中添加一个name属性
obj.name = "孙悟空";
// 向obj中添加一个gender属性
obj.gender = "男";
// 向obj中添加一个age属性
obj.age = "18";
// 打印obj
console.log(typeof obj); // object
console.log(obj); // {"age":"18","gender":"男","name":"孙悟空"}
console.log(obj.name); // 孙悟空

属性名

对象的属性名不强制要求遵守标识符的规范,什么乱七八糟的名字都可以使用,但是我们使用是还是尽量按照标识符的规范去做

如果要使用特殊的属性名,不能采用.的方式来操作,而需要使用另一种语法:对象["属性名"]=属性值,读取时也需要采用这种方式

1
2
obj["name"] = "齐天大圣";
console.log(obj["name"]); // 齐天大圣

使用[]这种形式去操作属性,更加的灵活,在[]中可以直接传递一个变量,这样变量值是哪个就会读取哪个属性

1
2
3
var n = "nihao";
obj[n] = "你好";
console.log(obj[n]); // 你好

回顾.[]new这几个运算符的优先级是最高的

属性值

JS对象的属性值,可以是任意的数据类型,包括对象

1
2
3
4
var obj2 = new Object();
obj2.name = "猪八戒";
obj.bro = obj2;
console.log(obj.bro.name); // 猪八戒

in运算符

通过该运算符可以检查一个对象中是否含有指定的属性

如果有则返回true,没有则返回false

语法:"属性名" in 对象

1
2
console.log("test" in obj); // false
console.log("name" in obj); // true

3、基本数据类型和引用数据类型

基本数据类型 String Number Boolean Null Undefined

引用数据类型 Object

基本数据类型

  • JS中的变量都是保存到栈内存中的,基本数据类型的值直接在栈内存中存储
  • 值与值之间是独立存在,修改一个变量不会影响其他的变量
1
2
3
4
5
var a = 1;
var b = a;
console.log("a=" + a + ", b=" + b); // a=1, b=1
b = 2;
console.log("a=" + a + ", b=" + b); // a=1, b=2

引用数据类型

  • 对象是保存到堆内存中的
  • 每创建一个新的对象,就会在堆内存中开辟出一个新的空间,而变量保存的是对象的内存地址(对象的引用)
  • 如果两个变量保存的是同一个对象引用,当一个通过一个变量修改属性时,另一个也会受到影响
1
2
3
4
var obj3 = obj;
obj3.name = "斗战胜佛";
console.log(obj.name); // 斗战胜佛
console.log(obj3.name); // 斗战胜佛

比较

  • 当比较两个基本数据类型的值时,就是比较值。
  • 而比较两个引用数据类型时,它是比较的对象的内存地址,如果两个对象是一摸一样的,但是地址不同,它也会返回false
1
2
3
4
5
var o1 = new Object();
var o2 = new Object();
o1["name"] = "周瑜";
o2["name"] = "周瑜";
console.log(o1 == o2); // false

4、对象字面量

使用对象字面量,可以在创建对象时,直接指定对象属性的语法:{属性名: 属性值, 属性名: 属性值...}

对象字面量的属性名可以加引号也可以不加(建议不加),如果要使用一些特殊的名字,则必须加引号

属性名和属性值是一组一组的名值对结构,名和值之间使用:连接,多个名值对之间使用,隔开

如果一个属性之后没有其他的属性了,就不要写,

1
2
3
4
5
6
7
8
9
var obj = {
name: "孙悟空",
age: 1000,
gender: "男",
bor:{
name: "猪八戒"
}
}
console.log(obj); // {"age":1000,"bor":{"name":"猪八戒"},"gender":"男","name":"孙悟空"}

5、方法

对象的属性值可以是任何的数据类型,也可以是个函数(下一节知识)

函数也可以称为对象的属性,如果一个函数作为一个对象的属性保存,那么我们称这个函数是这个对象的方法

调用函数就说调用对象的方法,但是它只是名称上的区别没有其他的区别

1
2
3
4
5
6
7
8
var obj2 = {
name: "猪八戒",
age: 18,
sayName: function() {
console.log(obj2.name);
}
};
obj2.sayName(); // 猪八戒

6、枚举对象中的属性

使用for...in语句语法:

1
2
3
for(var 变量 in 对象) {
语句...
}

for...in语句对象中有几个属性,循环体就会执行几次

每次执行时,会将对象中的一个属性的名字赋值给变量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
var obj = {
name: "孙悟空",
age: 1000,
gender: "男",
address: "花果山"
};
for(var key in obj){
console.log(key + "=" + obj.key);
// name=undefined
// age=undefined
// gender=undefined
// address=undefined
console.log(key + "=" + obj[key]);
// name=孙悟空
// age=1000
// gender=男
// address=花果山
}

函数

1、函数的简介

函数也是一个对象,可以封装一些功能(代码),在需要时可以执行这些功能(代码),可以保存一些代码在需要的时候调用

使用typeof检查一个函数对象时,会返回function

1
2
3
4
5
6
7
8
// 创建一个函数对象
// 可以将要封装的代码以字符串的形式传递给构造函数
var fun = new Function("console.log('Hello World.');");
// 封装到函数中的代码不会立即执行
// 函数中的代码会在函数调用的时候执行
// 调用函数语法:函数对象()
// 当调用函数时,函数中封装的代码会按照顺序执行
fun(); // Hello World.

使用函数声明来创建一个函数

1
2
3
4
5
function 函数名([形参1, 形参2...形参N]) {
语句...
}
// 调用函数
函数名();

示例

1
2
3
4
5
6
function fun1(){
console.log("Hello world.");
alert("Hello World!");
document.write("Helloworld");
}
fun1();

使用函数表达式(匿名函数)来创建一个函数

1
2
3
4
5
var 函数名 = function([形参1, 形参2...形参N]) {
语句...
};
// 调用函数
函数名();

示例

1
2
3
4
5
6
var fun1 = function(){
console.log("Hello world.");
alert("Hello World!");
document.write("Helloworld");
};
fun1();

2、函数的参数

定义一个用来求两个数和的函数

可以在函数的()中来指定一个或多个形参(形式参数)多个形参之间使用,隔开,声明形参就相当于在函数内部声明了对应的变量

在调用函数时,可以在()中指定实参(实际参数)

  • 调用函数时解析器不会检查实参的类型。所以要注意,是否有可能会接收到非法的参数,如果有可能则需要对参数进行类型的检查

  • 调用函数时,解析器也不会检查实参的数量,多余实参不会被赋值。如果实参的数量少于形参的数量,则没有对应实参的形参将是undefined

1
2
3
4
5
// 创建一个函数,用来计算三个数的和
function sum(a, b, c) {
alert(a + b + c);
}
sum(1, 2, 3, 4); // 6

3、函数的返回值

可以使用return来设置函数的返回值语法:return 值

return后的值将会作为函数的执行结果返回,可以定义一个变量,来接收该结果

在函数中return后的语句都不会执行

如果return语句后不跟任何值,就相当于返回一个undefined;如果函数中不写return,则也会返回undefined

return后可以跟任意类型的值

1
2
3
4
5
6
7
8
9
10
11
12
// 创建一个函数,用来计算三个数的和
function sum(a, b, c) {
// var result = a + b + c;
// return result;
return a + b + c;
}

// 调用函数
// 变量result的值就是函数的执行结果
// 函数返回什么result的值就是什么
var result = sum(1, 2, 3);
console.log("result = " + result);

练习

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 1、定义一个函数,判断一个数字是否是偶数,如果是返回true,否则返回false
function isEven(num){
// if(num % 2 == 0){
// return true;
// }
// return false;
return num % 2 == 0;
}

var result = isEven(6);
console.log(result); // true
result = isEven(7);
console.log(result); // false

// 2、定义一个函数,可以根据半径计算一个圆的面积,并返回计算结果
function calCirc(radius) {
return 3.14 * Math.square(radius);
}

var result = calCirc(2); //

实参可以是任意的数据类型,也可以是一个对象。当我们的参数过多时,可以将参数封装到一个对象

1
2
3
4
5
6
7
8
9
10
11
12
13
function sayHello(o){
console.log("我是" + o.name
+ ",今年我" + o.age
+ "岁了,我是一个" + o.gender
+ "人,我住在" + o.address);
}
var obj = {
name: "孙悟空",
age: 1000,
gender: "男",
address: "花果山"
};
sayHello(obj); // 我是孙悟空,今年我1000岁了,我是一个男人,我住在花果山

实参可以是一个对象,也可以是一个函数

1
2
3
4
5
6
7
8
9
10
11
function calCirc(radius) {
return Math.PI * Math.pow(radius, 2);
}
function fun(a){
console.log("a = " + a);
}
fun(calCirc);
// a = function calCirc(radius) {
// return Math.PI * Math.pow(radius, 2);
// }
fun(calCirc(10)); // a = 314.1592653589793

calCirc(10)

  • 调用函数
  • 相当于使用的函数的返回值

calCirc

  • 函数对象
  • 相当于直接使用函数对象

函数也是一个对象,特殊在其具有功能

break、continue、return对比

  • break可以退出当前的循环
  • continue用于跳过当次循环
  • return可以结束整个函数

在函数内部再声明一个函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function fun3(){
function fun4(){
console.log("I'm fun4.");
}
fun4();
}
fun3(); // I'm fun4.

function fun5(){
function fun6(){
console.log("I'm fun6.");
}
return fun6;
}
var a = fun5();
a(); // I'm fun6.
fun5()(); // I'm fun6.

4、立即执行函数

函数定义完,立即被调用,这种函数叫做立即执行函数

立即执行函数往往只会执行一次

1
2
3
4
5
6
7
// 函数对象()
(function(){
console.log("I'm anoymous function.");
})(); // I'm anoymous function.
(function(a, b){
console.log(a + b);
})(2,3); // 5

作用域

作用域指一个变量的作用的范围

在JS中一共有两种作用域:

  • 全局作用域
  • 函数作用域

1、全局作用域

直接编写在script标签中的JS代码,都在全局作用域

全局作用域在页面打开时创建,在页面关闭时销毁

在全局作用域中有一个全局对象window,它代表的是一个浏览器的窗口,由浏览器创建,可以直接使用

在全局作用域中:

  • 创建的变量都会作为window对象的属性保存
  • 创建的函数都会作为window对象的方法保存

全局作用域中的变量都是全局变量,在页面的任意的部分都可以访问的到

1
2
3
4
5
6
var a = 3;
console.log(window.a); //3
console.log(a); //3

b = 3;
console.log(b); //3

1.1、变量的声明提前

使用var关键字声明的变量,会在所有的代码执行之前被声明

但是如果声明变量时不适用var关键字,则变量不会被声明提前

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 1、变量的声明提前
console.log("a = " + a); // a = undefined
var a = "abc";
// ======相当于======
var a;
console.log("a = " + a); // a = undefined
a = "abc";

// 2、没有变量的声明提前,报错
console.log("b = " + b); // UncaughtReferenceError: b is not defined
b = "abc";
// ======相当于======
console.log("b = " + b); // UncaughtReferenceError: b is not defined
window.b = "abc";

1.2、函数的声明提前

使用函数声明形式创建的函数function

1
2
3
函数(){
语句...
}

它会在所有的代码执行之前就被创建,所以我们可以在函数声明前来调用函数

1
2
3
4
5
6
7
8
9
10
fun1(); // fun1...
fun2(); // UncaughtTypeError: fun2 is not a function
// 函数声明,会被提前创建
function fun1(){
console.log("fun1...");
}
// 函数表达式,不会被提前创建(变量会被提前声明,但函数不会被提前创建)
var fun2 = function(){
console.log("fun2...");
}

2、函数作用域

调用函数时创建函数作用域,函数执行完毕以后,函数作用域销毁

每调用一次函数就会创建一个新的函数作用域,他们之间是互相独立的

  • 在函数作用域中可以访问到全局作用域的变量
  • 在全局作用域中无法访问到函数作用域的变量

当在函数作用域操作一个变量时,它会先在自身作用域中寻找,

  • 如果有就直接使用
  • 如果没有则向上一级作用域中寻找,直到找到全局作用域
  • 如果全局作用域中依然没有找到,则会报错

在函数中要访问全局变量可以使用window对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var a = 10;
function fun2(){
var a = 20;

function fun3(){
var a = 30;
console.log("fun3 ==> a = " + a); // fun3 ==> a = 30
}

fun3();

console.log("fun2 ==>a = " + a); // fun2 ==>a = 20
console.log("a = " + window.a); // a = 10
}
fun2();
console.log("a = " + a); // a = 10

在函数作用域也有声明提前的特性,使用var关键字声明的变量,会在函数中所有的代码执行之前被声明

函数声明也会在函数中所有的代码执行之前执行

1
2
3
4
5
6
7
8
9
10
11
12
// 在函数作用域也有声明提前的特性,使用`var`关键字声明的变量,会在函数中所有的代码执行之前被声明
function func1(){
console.log(a);
var a = "func1";

// 函数声明也会在函数中所有的代码执行之前执行
func2(); // fun2...
function func2(){
console.log("fun2...");
}
}
func1(); // undefined

在函数中,不适用var声明的变量都会成为全局变量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 函数声明且调用
func3();
function func3() {
a = 4;
}
console.log("a = " + window.a); // a = 4
console.log("a = " + window["a"]); // a = 4
console.log("a = " + a); // a = 4
// 函数声明不调用
function func4() {
b = 4;
}
console.log("b = " + window.b); // b = 4
console.log("b = " + window["b"]); // b = 4
console.log("b = " + b); // UncaughtReferenceError: b is not defined

定义形参就相当于在函数作用域中声明了变量

1
2
3
4
5
6
var e = 10;
function fun5(e){
console.log(e);
}
fun5(); // undefined
fun5(55); // 55

练习

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
// 说出以下代码的执行结果
var a = 123;
function fun(){
console.log(a);
}
fun(); // 123
// =====================
var a = 123;
function fun(){
console.log(a);
var a = 456;
}
fun(); // undefined
console.log(a); // 123
// =====================
var a = 123;
function fun(){
console.log(a);
a = 456;
}
fun(); // 123
console.log(a); // 456
// =====================
var a = 123;
function fun(a){
console.log(a);
a = 456;
}
fun(); // undefined
console.log(a); // 123
// =====================
var a = 123;
function fun(a){
console.log(a);
a = 456;
}
fun(789); // 789
console.log(a); // 123

3、this

解析器在调用函数每次都会向函数内部传递进一个隐含的参数,这个隐含的参数就是this

this指向的是一个对象,这个对象我们称为函数执行的上下文对象

根据函数的调用方式的不同,this会指向不同的对象

  • 以函数的形式调用时,this永远都是window
  • 以方法的形式调用时,this就是调用方法的那个对象
1
2
3
4
5
6
7
8
9
10
11
12
// - 以函数的形式调用时,`this`永远都是`window`
function fun(){
console.log(this.name);
}
var name = "ddd"; // ddd
fun();
// - 以方法的形式调用时,`this`就是调用方法的那个对象
var obj = {
name: "孙悟空",
sayName: fun
}
obj.sayName(); // 孙悟空

构造函数与原型对象

1、使用工厂方法创建对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function createPerson(name, age, gender){
// 创建一个新的对象
var obj=new Object();
//向对象中添加属性
obj.name = name;
obj.age = age;
obj.gender = gender;
obj.sayName = function(){
console.log(this.name);
};
//将新的对象返回
return obj;
}

var obj1 = createPerson("孙悟空", 1000, "男");
var obj2 = createPerson("猪八戒", 3600, "男");
var obj3 = createPerson("沙悟净", 10000, "男");

obj1.sayName(); // 孙悟空
obj2.sayName(); // 猪八戒
obj3.sayName(); // 猪八戒

使用工厂方法创建的对象,使用的构造函数都是Object

所以创建的对象都是Object这个类型,就导致我们无法区分出多种不同类型的对象

2、构造函数

创建一个构造函数,专门用来创建Person对象的构造函数就是一个普通的函数

创建方式和普通函数没有区别,不同的是构造函数习惯上首字母大写构造函数

和普通函数的区别就是调用方式的不同

  • 普通函数是直接调用
  • 构造函数需要使用new关键字来调用
1
2
3
4
5
6
7
8
9
function Person(){
console.log(this); // Person{}
}
// 普通函数
var fun = Person();
console.log(fun); // undefined
// 构造函数
var person = new Person();
console.log(person); // Person{}

构造函数的执行流程

  1. 立刻创建一个新的对象
  2. 将新建的对象设置为函数中this,在构造函数中可以使用this来引用新建的对象
  3. 逐行执行函数中的代码
  4. 将新建的对象作为返回值返回
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
function Dog(){

}

function Person(name, age, gender){
//向对象中添加属性
this.name = name;
this.age = age;
this.gender = gender;
this.sayHello = function(){
console.log("My'name is " + this.name + ", " +
"I'm " + this.age + " years old, " +
"and I'm a " + this.gender + ".");
};
}

var person1 = new Person("孙悟空", 1000, "man");
var person2 = new Person("猪八戒", 3600, "man");
var person3 = new Person("沙悟净", 10000, "man");
var dog = new Dog();
person1.sayHello(); // My'name is 孙悟空, I'm 1000 years old, and I'm a man.
person2.sayHello(); // My'name is 猪八戒, I'm 3600 years old, and I'm a man.
person3.sayHello(); // My'name is 沙悟净, I'm 10000 years old, and I'm a man.
console.log(person1); // Person {name: "孙悟空", age: 1000, gender: "man", sayHello: ƒ}
console.log(person2); // Person {name: "猪八戒", age: 3600, gender: "man", sayHello: ƒ}
console.log(person3); // Person {name: "沙悟净", age: 10000, gender: "man", sayHello: ƒ}
console.log(typeof person1); // object
console.log(typeof person2); // object
console.log(typeof person3); // object

使用同一个构造函数创建的对象,我们称为一类对象,也将一个构造函数称为一个类。

我们将通过一个构造函数创建的对象,称为是该类的实例

使用instanceof可以检查一个对象是否是一个类的实例语法:对象 instanceof 构造函数

如果是则返回true,否则返回false

1
2
3
4
console.log(person1 instanceof Person); //true
console.log(person2 instanceof Person); //true
console.log(person3 instanceof Person); //true
console.log(dog instanceof Person); //false

所有的对象都是Object的后代,所以任何对象和Object进行instanceof检查时都会返回true

1
2
3
4
console.log(person1 instanceof Object); //true
console.log(person2 instanceof Object); //true
console.log(person3 instanceof Object); //true
console.log(dog instanceof Object); //true

this的情况:

  • 当以函数的形式调用时,thiswindow
  • 当以方法的形式调用时,谁调用方法this就是谁
  • 当以构造函数的形式调用时,this就是新创建的那个对象

构造函数修改

创建一个Person构造函数

在Person构造函数中,为每一个对象都添加了一个sayName方法,目前我们的方法是在构造函数内部创建的

也就是构造函数每执行一次就会创建一个新的sayName方法也是所有实例的sayName都是唯一的

1
2
3
4
5
6
7
8
9
10
function Person(name, age, gender){
this.name = name;
this.age = age;
this.gender = gender;
this.sayHello = function(){
console.log("My'name is " + this.name + ", " +
"I'm " + this.age + " years old, " +
"and I'm a " + this.gender + ".");
};
}

这样就导致了构造函数执行一次就会创建一个新的方法,执行10000次就会创建10000个新的方法,而10000个方法都是一模一样的

这是完全没有必要,完全可以使所有的对象共享同一个方法

1
2
3
4
5
6
7
8
9
10
11
12
function Person(name, age, gender){
this.name = name;
this.age = age;
this.gender = gender;
this.sayHello = fun;
}
// 将sayName方法在全局作用域中定义
function fun(){
console.log("My'name is " + this.name + ", " +
"I'm " + this.age + " years old, " +
"and I'm a " + this.gender + ".");
};

将函数定义在全局作用域,虽然节省了空间,但却污染了全局作用域的命名空间

而且定义在全局作用域中也很不安全

3、原型对象

原型prototype

我们所创建的每一个函数(不论是普通函数还是构造函数),解析器都会向函数中添加一个属性prototype

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function Person(){

}

function MyClass(){

}

console.log(Person.prototype);
// {constructor: ƒ}
// constructor: ƒ Person()
// arguments: null
// caller: null
// length: 0
// name: "Person"
// prototype: {constructor: ƒ}
// __proto__: ƒ ()
// [[FunctionLocation]]: 09-原型对象.html:8
// [[Scopes]]: Scopes[1]
// __proto__: Object
console.log(Person.prototype == MyClass.prototype); // false

当函数以普通函数的形式调用prototype时,没有任何作用

当函数以构造函数的形式调用prototype时,它所创建的对象中都会有一个隐含的属性,指向该构造函数的原型对象,我们可以通过__proto__来访问该属性

1
2
3
4
5
6
var mc1 = new MyClass();
var mc2 = new MyClass();
var mc3 = new MyClass();
console.log(mc1.__proto__ == MyClass.prototype); // true
console.log(mc2.__proto__ == MyClass.prototype); // true
console.log(mc3.__proto__ == MyClass.prototype); // true

image-20210727225124094

原型对象就相当于一个公共区域,所有同一个类的实例都可以访问到这个原型对象

我们可以将对象中共有的内容,统一设置到原型对象中

1
2
3
4
5
6
7
8
// 向MyClass中添加属性a
MyClass.prototype.a = "123";
console.log(mc1.a); // 123
// 向MyClass中添加方法sayHello
MyClass.prototype.sayHello = function(){
alert("hello");
}
mc3.sayHello();

当我们访问对象的一个属性或方法时,它会先在对象自身中寻找,如果有则直接使用,如果没有则会去原型对象中寻找,如果找到则直接使用

1
2
mc2.a = "456";
console.log(mc2.a); // 456

以后我们创建构造函数时,可以将这些对象共有的属性和方法,统一添加到构造函数的原型对象中

这样不用分别为每一个对象添加,也不会影响到全局作用域,就可以使每个对象都具有这些属性和方法了

hasOwnProperty

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function MyClass(){

}
MyClass.prototype.name = "I'm prototype's name.";
var mc = new MyClass();
mc.age = 18;
// 使用in检查对象中是否含有某个属性时,如果对象中没有但是原型中有,也会返回true
console.log("name" in mc); // true
console.log("age" in mc); // true
// 可以使用对象的hasOwnProperty()来检查对象自身中是否含有该属性
// 使用该方法只有当对象自身中含有属性时,才会返回true
console.log(mc.hasOwnProperty("name")); // false
console.log(mc.hasOwnProperty("age")); // true
console.log(mc.hasOwnProperty("hasOwnProperty")); // false

那么,hasOwnProperty是原型对象中定义的方法吗?

因为对象中没有定义hasOwnProperty方法,那应该就是在原型对象中定义的了,果真如此吗?

我们用hasOwnProperty方法看下有没有hasOwnProperty它自己

1
console.log(mc.__proto__.hasOwnProperty("hasOwnProperty"));  // false

我们发现,原型对象中也没有hasOwnProperty方法,那hasOwnProperty究竟是哪里来的呢?

原型的原型

原型对象也是对象,所以它也有原型,当我们使用一个对象的属性或方法时

  • 会先在自身中寻找,自身中如果有则直接使用

  • 如果没有则去原型对象中寻找,有则使用

  • 如果没有则去原型的原型中寻找,直到找到Object对象的原型

  • Object对象的原型没有原型,如果在Object中依然没有找到,则返回undefined

    1
    console.log(mc.helloWorld);  // undefined

image-20210727231924585

那么,按照这个原理,我们在原型的原型中使用hasOwnProperty方法看看

1
console.log(mc.__proto__.__proto__.hasOwnProperty("hasOwnProperty"));  // true

那既然原型对象有原型,那原型的原型还有原型吗?

话不多说,直接打印看下

1
console.log(mc.__proto__.__proto__.__proto__);  // null

根据上述原理,mc.__proto__.__proto__就是Object对象了

Object对象虽然没有原型,但也有__proto__,只是为null而已

toString

当我们直接在页面中打印一个对象时,事件上是输出的对象的toString()方法的返回值(这里并非视频中所说的那样,有待确认

如果我们希望在输出对象时不输出[object Object],可以为对象添加一个toString()方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function Person(name, age, gender){
this.name = name;
this.age = age;
this.gender = gender;
}
var per1 = new Person("孙悟空", 1000, "man");
var per2 = new Person("猪八戒", 3600, "man");
// 当我们直接在页面中打印一个对象时,事件上是输出的对象的`toString()`方法的返回值
console.log(per1); // Person {name: "孙悟空", age: 1000, gender: "man"}
console.log(per1.toString()); // [object Object]
// 如果我们希望在输出对象时不输出`[object Object]`,可以为对象添加一个`toString()`方法
per1.toString = function(){
return "Person[name=" + this.name + ", age=" + this.age + ", gender=" + this.gender + "]";
}
console.log(per1); // Person {name: "孙悟空", age: 1000, gender: "man", toString: ƒ}
console.log(per1.toString()); // Person[name=孙悟空, age=1000, gender=man]

上述只是修改per1对象的toString方法,不会对其他对象产生影响

如果想要所有对象都执行该方法,可以修改Person原型的toString

1
2
3
4
5
6
console.log(per2.toString()); // [object Object]
// 修改Person原型的toString
Person.prototype.toString = function(){
return "Person[name=" + this.name + ", age=" + this.age + ", gender=" + this.gender + "]";
}
console.log(per2.toString()); // Person[name=猪八戒, age=3600, gender=man]

4、垃圾回收(GC)

就像人生活的时间长了会产生垃圾一样,程序运行过程中也会产生垃圾这些垃圾积攒过多以后,会导致程序运行的速度过慢

所以我们需要一个垃圾回收的机制,来处理程序运行过程中产生垃圾

image-20210728191835732

当一个对象没有任何的变量或属性对它进行引用,我们将永远无法操作该对象

此时这种对象就是一个垃圾,这种对算过多会占用大量的内存空间,导致程序运行变慢

image-20210728192324257

在JS中拥有自动的垃圾回收机制,会自动将这些垃圾对象从内存中销毁,我们不需要也不能进行垃圾回收的操作

我们需要做的只是要将不再使用的对象设置null即可

1
2
3
var obj = new Object();
// ...
obj = null

数组

1、数组简介

数组也是一个对象

它和我们普通对象功能类似,也是用来存储一些值的

不同的是普通对象是使用字符串作为属性名的,而数组时使用数字来作为索引操作元素

索引:从0开始的整数就是索引

image-20210728193018085

数组的存储性能比普通对象要好,在开发中我们经常使用数组来存储一些数据

1
2
3
4
// 创建数组对象
var arr=new Array();
// 使用typeof检查一个数组时,会返回object
console.log(typeof arr); // object

向数组中添加元素

语法:数组[索引] = 值

1
2
3
arr[0] = 10;
arr[1] = 33;
arr[2] = 22;

读取数组中的元素

语法:数组[索引]

如果读取不存在的索引,不会报错而是返回undefined

1
2
console.log(arr[2]); // 22
console.log(arr[3]); // undefined

获取数组的长度

可以使用length属性来获取数组的长度(元素的个数)语法:数组.length

  • 对于连续的数组,使用length可以获取到数组的长度(元素的个数)
  • 对于非连续的数组,使用length会获取到数组的最大的索引 + 1
1
2
3
4
5
console.log(arr.length); // 3
console.log(arr); // {"0":10,"1":33,"2":22,"length":3}
arr[10] = 33;
console.log(arr.length); // 11
console.log(arr); // {"0":10,"1":33,"10":33,"2":22,"length":11}

尽量不要创建非连续的数组

修改数组的长度

  • 如果修改的length大于原长度,则多出部分会空出来
  • 如果修改的length小于原长度,则多出的元素会被删除
1
2
3
4
5
6
arr.length = 100;
console.log(arr.length); // 100
console.log(arr); // {"0":10,"1":33,"10":33,"2":22,"length":100}
arr.length = 2;
console.log(arr.length); // 2
console.log(arr); // {"0":10,"1":33,"length":2}

向数组最后一位添加元素

语法:数组[数组.length] = 值;

1
2
3
4
5
6
arr[arr.length] = 22;
console.log(arr.length); // 3
console.log(arr); // {"0":10,"1":33,"2":22,"length":3}
arr[arr.length] = 33;
console.log(arr.length); // 4
console.log(arr); // {"0":10,"1":33,"2":22,"3":33,"length":4}

2、创建数组的方式

使用字面量创建数组

语法:[]

1
2
3
4
var arr1 = [];
console.log(arr1); // {"length":0}
console.log(typeof arr1); // object
console.log(arr1.length); // 0

使用字面量创建数组时,可以在创建时就指定数组中的元素

1
2
3
var arr2 = [1,2,3,4,5,10];
console.log(arr2); // {"0":1,"1":2,"2":3,"3":4,"4":5,"5":10,"length":6}
console.log(arr2.length); // 6

使用构造函数创建数组

使用构造函数创建数组时,也可以同时添加元素,将要添加的元素作为构造函数的参数传递

元素之间使用,隔开

1
2
3
var arr3 = new Array(1,2,3,4,5);
console.log(arr3); // {"0":1,"1":2,"2":3,"3":4,"4":5,"length":5}
console.log(arr3.length); // 5

字面量和构造函数只有一个数字时的区别

1
2
3
4
5
6
// 创建一个数组数组中只有一个元素10
var arr4 = [10];
// 创建一个长度为10的数
var arr5 = new Array(10);
console.log(arr4.length); // 1
console.log(arr5.length); // 10

3、数组元素类型

任意的数据类型

数字、字符串、布尔值、nullundefined

1
2
3
4
5
6
7
8
9
var arr6 = [2, "13", true, null, undefined];
console.log(arr6);
// Array(5)
// 0: 2
// 1: "13"
// 2: true
// 3: null
// 4: undefined
// length: 5

对象

1
2
3
4
5
6
7
// **也可以是对象**
var obj = {name:"孙悟空"};
var arr7 = [];
arr7[arr7.length] = obj;
console.log(arr7); // {"0":{"name":"孙悟空"},"length":1}
arr7 = [{name:"孙悟空"}, {name:"沙和尚"}, {name:"猪八戒"}];
console.log(arr7); // {"0":{"name":"孙悟空"},"1":{"name":"沙和尚"},"2":{"name":"猪八戒"},"length":3}

函数

1
2
arr7 = [function(){alert(1)},function(){alert(2)}]; 
console.log(arr7); // {"0":"function (){alert(1)}","1":"function (){alert(2)}","length":2}

数组

数组中也可以放数组,如下这种数组我们称为二维数组

1
2
arr7 = [[1,2,3],[4,5,6],[7,8,9]];
console.log(arr7); // {"0":{"0":1,"1":2,"2":3,"length":3},"1":{"0":4,"1":5,"2":6,"length":3},"2":{"0":7,"1":8,"2":9,"length":3},"length":3}

4、数组的方法

数组的方法有很多,这里兹介绍常用的几个方法

image-20210728203916328

push()

该方法可以向数组的末尾添加一个或多个元素,并返回数组的新的长度

可以将要添加的元素作为方法的参数传递,这样这些元素将会自动添加到数组的末尾

1
2
3
4
5
var result = arr.push("唐三藏");
console.log(arr); // ["孙悟空", "猪八戒", "沙悟净", "唐三藏"]
arr.push("菩提老祖", "地藏菩萨", "弥勒佛");
console.log(arr); // ["孙悟空", "猪八戒", "沙悟净", "唐三藏", "菩提老祖", "地藏菩萨", "弥勒佛"]
console.log("result = " + result); // result = 4

pop()

该方法可以删除数组的最后一个元素,并将被删除的元素作为返回值返回

1
2
3
4
console.log(arr); // ["孙悟空", "猪八戒", "沙悟净", "唐三藏", "菩提老祖", "地藏菩萨", "弥勒佛"]
var result = arr.pop();
console.log(arr); // ["孙悟空", "猪八戒", "沙悟净", "唐三藏", "菩提老祖", "地藏菩萨"]
console.log("result = " + result); // result = 弥勒佛

unshift()

向数组开头添加一个或多个元素,并返回新的数组长度

向前边插入元素以后,其他的元素索引会依次调整

1
2
3
4
console.log(arr); // ["孙悟空", "猪八戒", "沙悟净", "唐三藏", "菩提老祖", "地藏菩萨"]
result = arr.unshift("牛魔王", "二郎神");
console.log(arr); // ["牛魔王", "二郎神", "孙悟空", "猪八戒", "沙悟净", "唐三藏", "菩提老祖", "地藏菩萨"]
console.log("result = " + result); // result = 8

shift()

可以删除数组的第一个元素,并将被删除的元素作为返回值返回

1
2
3
4
console.log(arr); // ["牛魔王", "二郎神", "孙悟空", "猪八戒", "沙悟净", "唐三藏", "菩提老祖", "地藏菩萨"]
result = arr.shift();
console.log(arr); // ["二郎神", "孙悟空", "猪八戒", "沙悟净", "唐三藏", "菩提老祖", "地藏菩萨"]
console.log("result = " + result); // result = 7

小结

操作 添加 删除
末尾操作 push:末尾添加 pop:末尾删除
开头操作 unshift:开头添加 shift:开头删除

slice()

从某个已有的数组返回选定的元素,可以用来从数组提取指定元素

该方法不会改变元素数组,而是将截取到的元素封装到一个新数组中返回参数:

  • 截取开始的位置的索引,包含开始索引
  • 截取结束的位置的索引,不包含结束索引
1
2
result = arr.slice(0,3);
console.log(result); // ["二郎神", "孙悟空", "猪八戒"]

第二个参数可以省略不写,此时会截取从开始索引往后的所有元素

1
2
result = arr.slice(3);
console.log(result); // ["沙悟净", "唐三藏", "菩提老祖", "地藏菩萨"]

索引可以传递一个负值,如果传递一个负值,则从后往前计算

  • -1 倒数第一个
  • -2 倒数第二个
1
2
result = arr.slice(4, -1);
console.log(result); // ["唐三藏", "菩提老祖"]

splice()

删除元素,并向数组添加新元素。可以用于删除数组中的指定元素

使用splice()会影响到原数组,会将指定元素从原数组中删除,并将被删除的元素作为返回值返回

参数:

  • 第一个,表示开始位置的索引

  • 第二个,表示删除的数量

    1
    2
    3
    4
    5
    6
    7
    8
    9
    arr = ["牛魔王", "二郎神", "孙悟空", "猪八戒", "沙悟净", "唐三藏", "菩提老祖", "地藏菩萨"];
    result = arr.splice(0, 2);
    console.log(result); // ["牛魔王", "二郎神"]
    console.log(arr); // ["孙悟空", "猪八戒", "沙悟净", "唐三藏", "菩提老祖", "地藏菩萨"]

    arr = ["牛魔王", "二郎神", "孙悟空", "猪八戒", "沙悟净", "唐三藏", "菩提老祖", "地藏菩萨"];
    result = arr.splice(1, 2);
    console.log(result); // ["二郎神", "孙悟空"]
    console.log(arr); // ["牛魔王", "猪八戒", "沙悟净", "唐三藏", "菩提老祖", "地藏菩萨"]
  • 第三个及以后,可以传递一些新的元素,这些元素将会自动插入到开始位置索引前边

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    // 替换元素
    arr = ["孙悟空", "猪八戒", "沙悟净", "唐三藏"];
    result = arr.splice(0, 1, "牛魔王", "铁扇公主", "红孩儿");
    console.log(result); // ["孙悟空"]
    console.log(arr); // ["牛魔王", "铁扇公主", "红孩儿", "猪八戒", "沙悟净", "唐三藏"]
    // 插入元素
    arr = ["孙悟空", "猪八戒", "沙悟净", "唐三藏"];
    result = arr.splice(0, 0, "牛魔王", "铁扇公主", "红孩儿");
    console.log(result); // []
    console.log(arr); // ["牛魔王", "铁扇公主", "红孩儿", "孙悟空", "猪八戒", "沙悟净", "唐三藏"]

小结

  • slice可以提取数组中指定元素
  • splice可以删除元素、替换元素、插入元素(功能更强大)

concat()

concat()可以连接两个或多个数组,并将新的数组返回

该方法不会对原数组产生影响

1
2
3
4
5
6
7
8
var arr1 = ["孙悟空", "猪八戒", "沙悟净"];
var arr2 = ["青毛狮子怪", "黄牙老象", "大鹏金翅雕"];
var arr3 = ["虎力大仙", "鹿力大仙", "羊力大仙"];
var arr4 = arr1.concat(arr2,arr3,"牛魔王","铁扇公主","红孩儿");
console.log(arr1); // ["孙悟空", "猪八戒", "沙悟净"]
console.log(arr2); // ["青毛狮子怪", "黄牙老象", "大鹏金翅雕"]
console.log(arr3); // ["虎力大仙", "鹿力大仙", "羊力大仙"]
console.log(arr4); // ["孙悟空", "猪八戒", "沙悟净", "青毛狮子怪", "黄牙老象", "大鹏金翅雕", "虎力大仙", "鹿力大仙", "羊力大仙", "牛魔王", "铁扇公主", "红孩儿"]

join()

该方法可以将数组转换为一个字符串

该方法不会对原数组产生影响,而是将转换后的字符串作为结果返回

join()中可以指定一个字符串作为参数,这个字符串将会成为数组中元素的连接符

如果不指定连接符,则默认使用,作为连接符

1
2
3
4
5
6
7
8
9
10
11
var arr = ["孙悟空", "猪八戒", "沙悟净"];
var result = arr.join();
console.log(arr); // ["孙悟空", "猪八戒", "沙悟净"]
console.log(result); // 孙悟空,猪八戒,沙悟净
console.log(typeof result); // string

result = arr.join("");
console.log(result); // 孙悟空猪八戒沙悟净

result = arr.join("@");
console.log(result); // 孙悟空@猪八戒@沙悟净

reverse()

该方法用来反转数组(前边的去后边,后边的去前边)

该方法会直接修改原数组

1
2
3
var arr = ["孙悟空", "猪八戒", "沙悟净"];
arr.reverse();
console.log(arr); // ["沙悟净", "猪八戒", "孙悟空"]

sort()

可以用来对数组中的元素进行排序

也会影响原数组,默认会按照Unicode编码进行排序

1
2
3
var arr = ['f', 'b', 'a', 'h', 'e', 'd'];
arr.sort();
console.log(arr); // ["a", "b", "d", "e", "f", "h"]

即使对于纯数字的数组,使用sort()排序时,也会按照Unicode编码来排序

所以对数字进行排序时,可能会得到错误的结果

1
2
3
arr = ['2', '44', '9', '8', '2', '0'];
arr.sort();
console.log(arr); // ["0", "2", "2", "44", "8", "9"]

我们可以目己采指正排序的现则我们可以在sort()添加一个回调函数,来指定排序规则

回调函数中需要定义两个形参,浏览器将会分别使用数组中的元素作为实参去调用回调函数

使用哪个元素调用不确定,但是肯定的是在数组中a一定在b前边

浏览器会根据回调函数的返回值来决定元素的顺序,

  • 如果返回一个大于0的值,则元素会交换位置
  • 如果返回一个小于等于0的值,则元素位置不变
1
2
3
4
5
6
7
8
9
arr = [2, 44, 9, 8, 2, 0, 6];
arr.sort(function(a,b){
if(a > b){
return 1;
} else {
return -1;
}
});
console.log(arr); // [0, 2, 2, 6, 8, 9, 44]
  • 如果需要升序排列,则返回a - b
  • 如果需要降序排列,则返回b - a
1
2
3
4
5
6
7
8
9
10
arr.sort(function(a,b){
// 升序排列
return a - b;
});
console.log(arr); // [0, 2, 2, 6, 8, 9, 44]
arr.sort(function(a,b){
// 降序排列
return b - a;
});
console.log(arr); // [44, 9, 8, 6, 2, 2, 0]

小结

  • 会对原数组产生影响的方法:pushpopshiftunshiftsplicereversesort
  • 不会对原数组产生影响的方法:sliceconcatjoin
  • 添加元素的方法:pushunshiftsplice
  • 删除元素的方法:popshiftsplice
  • 替换元素的方法:splice
  • 连接元素的方法:concatjoin
  • 排序方法:reversesort

5、数组遍历

普通for循环

所谓的遍历数组,就是将数组中所有的元素都取出来

1
2
3
4
5
var arr = ["孙悟空", "猪八戒", "沙悟净", "白龙马"];
// 所谓的遍历数组,就是将数组中所有的元素都取出来
for(var i=0;i<arr.length;i++){
console.log(arr[i]);
}

练习

1、准备工作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 定义Person构造函数
function Person(name, age){
this.name = name;
this.age = age;
}

// 创建Person对象
var per1 = new Person("孙悟空", 18);
var per2 = new Person("猪八戒", 28);
var per3 = new Person("红孩儿", 8);
var per4 = new Person("蜘蛛精", 16);
var per5 = new Person("二郎神", 38);

// 将这些person对象放入到一个数组中
var perArr = [per1, per2, per3, per4, per5];
console.log(perArr);
// 0: Person {name: "孙悟空", age: 18}
// 1: Person {name: "猪八戒", age: 28}
// 2: Person {name: "红孩儿", age: 8}
// 3: Person {name: "蜘蛛精", age: 16}
// 4: Person {name: "二郎神", age: 38}

2、创建一个函数,可以将perArr中的满18岁的Person提取出来,然后封装到一个新的数组中并返回

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function getAdult(perArr){
// 创建一个新的数组
var resultArr = [];
var person;
// 遍历arr,获取arr中Person对象
for(var i=0;i<perArr.length;i++){
person = perArr[i];
// 判断Person对象的age是否大于等于18
if(person.age >= 18){
// 如果大于等于18,则将这个对象添加到newArr中
resultArr.push(person);
}
}
// 将数组返回
return resultArr;
}
var adult = getAdult(perArr);
console.log(adult);
// 0: Person {name: "孙悟空", age: 18}
// 1: Person {name: "猪八戒", age: 28}
// 2: Person {name: "二郎神", age: 38}

forEach方法

一般我们都是使用for循环去遍历数组,JS中还为我们提供了一个方法,用来遍历数组forEach()

兼容性

这个方法只支持IE8以上的浏览器,IE8及以下的浏览器均不支持该方法

所以如果需要兼容IE8,则不要使用forEach,还是使用for循环来遍历

使用

forEach() 方法需要一个函数作为参数

像这种函数,由我们创建但是不由我们调用的,我们称为回调函数

数组中有几个元素,函数就会执行几次,每次执行时,浏览器会将遍历到的元素

以实参的形式传递进来,我们可以来定义形参,来读取这些内容

参数

浏览器会在回调函数中传递三个参数:

  • 第一个参数,就是当前正在遍历的元素
  • 第二个参数,就是当前正在遍历的元素的索引
  • 第三个参数,就是正在遍历的数组
1
2
3
4
5
arr.forEach(function(value, index, obj){
console.log("value = " + value);
console.log("index = " + index);
console.log("obj = " + obj);
});

练习

数组去重

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 创建一个数组
var arr = [1,2,2,3,2,1,3,4,2,5];
// 去除数组中重复的数字
// 获取数组中的每一个元素
for(var i=0;i<arr.length;i++){
// 获取当前元素后的所有元素
for(var j=i+1;j<arr.length;j++){
// 判断两个元素的值是否相等
if(arr[i] == arr[j]){
// 如果相等则证明出现了重复的元素,则删除j对应的元素
// arr.splice(j, 1);
// 当删除了当前j所在的元素以后,后边的元素会自动补位
// 此时将不会再比较这个元素,需要再比较一次j所在位置的元素
// j--;
arr.splice(j--, 1);
}
}
}
console.log(arr);

call、apply和argument

call()和apply()

这两个方法都是函数对象的方法,需要通过函数对象来调用

当对函数调用 call()apply()都会调用函数执行

1
2
3
4
5
6
7
8
9
10
11
var obj = {
name: "obj"
};
var obj2 = {
name:"obj2"
}
function fun(){
console.log(this.name);
}
fun.call(obj); // obj
fun.call(obj2); // obj2

在调用call()apply()可以将一个对象指定为第一个参数此时这个对象将会成为函数执行时的this

  • call()方法可以将实参在对象之后依次传递
  • apply()方法需要将实参封装到一个数组中统一传递
1
2
3
4
5
function fun(a, b){
console.log("a = " + a + ", b = " + b);
}
fun.call(obj, 2, 3); // a = 2, b = 3
fun.apply(obj, [2, 3]); // a = 2, b = 3

this的情况

  1. 以函数的形式调用时,this永远都是window
  2. 以方法的形式调用时,this是调用方法的对象
  3. 以构造函数的形式调用时,this是新创建的那个对象
  4. 使用callapply调用时,this是指定的那个对象

argument

在调用函数时,浏览器每次都会传递进两个隐含的参数:

  • 函数的上下文对象this
  • 封装实参的对象arguments

arguments是一个类数组对象(并非数组),可以通过索引来操作数据,也可以获取长度

1
2
3
4
5
function fun1(){
console.log(arguments instanceof Array); // false
console.log(Array.isArray(arguments)); // false
}
fun1();

在调用函数时,我们所传递的实参都会在arguments中保存

我们即使不定义形参,也可以通过arguments来使用实参,只不过比较麻烦

  • arguments[0]表示第一个实参
  • arguments[1]表示第二个实参
1
2
3
4
5
function fun2(a,b,c){
console.log("arguments.length = " + arguments.length + ", arguments[0] = " + arguments[0]);
}
fun2("hello"); // arguments.length = 1, arguments[0] = hello
fun2(true, "hello"); // arguments.length = 2, arguments[0] = true

它里边有一个属性叫做callee,这个属性对应一个函数对象,就是当前正在执行的函数对象

1
2
3
4
5
6
7
8
function fun3(){
console.log(arguments.callee);
// function fun3(){
// console.log(arguments.callee);
// }
console.log(arguments.callee == fun3); // true
}
fun3();

Date和Math

1、Date

在JS中使用Date对象来表示一个时间

创建一个时间对象

如果直接使用构造函数创建一个Date对象,则会封装为当前代码执行的时间

1
2
3
4
// 创建一个Date对象
// 如果直接使用构造函数创建一个Date对象,则会封装为当前代码执行的时间
var d = new Date();
console.log(d); // Fri Jul 30 2021 21:51:37 GMT+0800 (中国标准时间)

创建一个指定的时间对象

需要在构造函数中传递一个表示时间的字符串作为参数

日期的格式:月/日/年 时:分:秒

1
2
3
4
d = new Date("08/01/2021 12:34:56");
console.log(d); // Sun Aug 01 2021 12:34:56 GMT+0800 (中国标准时间)
d = new Date("08/01/21 12:34:56"); // 为了避免在不同浏览器中产生歧义,尽量指定完整年份
console.log(d); // IE:Mon Aug 01 1921 12:34:56 GMT+0800 (中国标准时间)

Date方法

image-20210730220407701

getDate()

获取当前日期对象是几日

1
2
var date = d.getDate();
console.log("date = " + date); // date = 30

getDay()

获取当前日期对象时周几,会返回一个0-6的值

  • 0 表示 周日
  • 1 表示 周一
  • 6 表示 周六
1
2
var day = d.getDay();
console.log("day = " + day); // day = 5

getMonth()

获取当前时间对象的月份-会返回一个0-11的值

  • 0 表示 1月
  • 1 表示 2月
  • 11 表示 12月
1
2
3
var month = d.getMonth();
console.log("month = " + month); // month = 6
console.log("month = " + (month + 1)); // month = 7

getFullYear()

获取当前日期对象的年份

1
2
var year = d.getFullYear();
console.log("year = " + year); // year = 2021

getTime()

获取当前日期对象的时间戳

时间戳,指的是从格林威治标准时间的1970年1月1日0时0分0秒到当前日期所花费的毫秒数

计算机底层在保存时间时使用都是时间戳

1
2
3
4
// 示例:表示从1970年1月1日0时0分0秒到2021年22时25分26秒所花费的毫秒数
var time = d.getTime();
console.log(d); // Fri Jul 30 2021 22:25:26 GMT+0800 (中国标准时间)
console.log("time = " + time); // time = 1627655017435

既然时间是从格林威治标准时间开始计算的,是不是就意味着1970年1月1日0时0分0秒的时间戳就是0呢?

1
2
3
var d2 = new Date("01/01/1970 00:00:00");
var time = d2.getTime();
console.log("time = " + time); // time = -28800000

我们发现事实并非如此,为什么呢?

这是因为我们的系统是中文系统,采用的是东八区时间,如何验证呢?

1
console.log("距离格林威治时间还有" + -time/1000/60/60 + "小时"); // 距离格林威治时间还有8小时

获取当前的时间戳

1
2
var currentTime = Date.now();
console.log("currentTime = " + currentTime); // currentTime = 1627655852125

其他

1
2
3
4
5
6
7
8
var hour = d.getHours();
var minute = d.getMinutes();
var second = d.getSeconds();
var mmilliSecond = d.getMilliseconds();
console.log("hour = " + hour); // hour = 22
console.log("minute = " + minute); // minute = 21
console.log("second = " + second); // second = 11
console.log("mmilliSecond = " + mmilliSecond); // mmilliSecond = 149

2、Math

Math和其他的对象不同,不是一个构造函数

属于一个工具类,不用创建对象,里边封装了数学运算相关的属性和方法

Math对象属性

image-20210730224441680

1
2
console.log(Math.E); // 2.718281828459045
console.log(Math.PI); // 3.141592653589793

Math对象方法

image-20210730224743982

Math.abs()

可以用来计算一个数的绝对值

1
console.log(Math.abs(-1)); // 1

Math.ceil()

可以对一个数进行向上取整,小数位只有有值就自动进1

1
2
console.log(Math.ceil(1.001)); // 2
console.log(Math.ceil(1.0)); // 1

Math.floor()

可以对一个数进行向下取整,小数部分会被舍掉

1
console.log(Math.floor(1.999999)); // 1

Math.round()

可以对一个数进行四舍五入取整

1
2
console.log(Math.round(1.4)); // 1
console.log(Math.round(1.5)); // 2

Math.random()

可以用来生成一个0-1之间的随机数(开区间范围:(0, 1)

1
2
console.log(Math.random()); // 0.9192011449766921
console.log(Math.random()); // 0.736135736878959

生成一个0-10之间的随机数:Math.round(Math.random() * 10)

生成一个0-X之间的随机数:Math.round(Math.random() * X)

生成一个1-10之间的随机数:Math.round(Math.random() * 9 + 1)

生成一个X-Y之间的随机数:Math.round(Math.random() * (Y - X) + X)

Math.max()

可以获取多个数中的最大值

1
console.log(Math.max(3,6,7,33)); // 33

Math.min()

可以获取多个数中的最小值

1
console.log(Math.min(3,6,7,33)); // 3

Math.pow()

Math.pow(x, y)返回x的y次幂

1
console.log(Math.pow(2,10)); // 1024

Math.sqrt()

用于对一个数进行开方运算

1
console.log(Math.sqrt(81)); // 9

包装类和字符串

1、三大包装类

基本数据类型:StringNumberBooleanNullUndefined

引用数据类型:Object

在JS中为我们提供了三大包装类,通过这三个包装类可以将基本数据类型的数据转换为对象

  • String()可以将基本数据类型字符串转换为String对象
  • Number()可以将基本数据类型的数字转换为Number对象
  • Boolean()可以将基本数据类型的布尔值转换为Boolean对象
1
2
3
4
5
6
7
8
9
10
11
12
var str1 = "hello";
var str2 = new String();
console.log(typeof str1); // string
console.log(typeof str2); // object
var num1 = 3;
var num2 = new Number(3);
console.log(typeof num1); // string
console.log(typeof num2); // object
var bol1 = true;
var bol2 = new Boolean(true);
console.log(typeof bol1); // string
console.log(typeof bol2); // object

但是注意:我们在实际应用中不会使用基本数据类型的对象,如果使用基本数据类型的对象,在做一些比较时可能会带来一些不可预期的结果

1
2
3
4
5
6
7
var n1 = new Number(1);
var n2 = new Number(1);
console.log(n1 == n2); // false
var b = new Boolean(false);
if(b){
console.log(b); // Boolean {false}
}

方法和属性之能添加给对象,不能添加给基本数据类型(按照视频中的解释,是先将data临时转换为了一个包装类对象,进行了属性赋值操作;打印时又临时转换为了一个新的包装类对象,因为两次不是同一个对象,而且该对象刚刚创建,还没有任何属性和方法,所以是获取不到任何值的)

1
2
3
var data = 4;
data.hello = "hello";
console.log(data.hello); // undefined

当我们对一些基本数据类型的值去调用属性和方法时,浏览器会临时使用包装类将其转换为对象,然后在调用对象的属性和方法时,浏览器会临时使用包装类将其转换为对象,然后在调用对象的属性和方法调用完以后,在将其转换为基本数据类型

1
2
3
4
var s = 123;
s = s.toString();
console.log(s); // 123
console.log(typeof s); // string

2、字符串方法

字符串在底层是以字符数组的形式保存的:["H","e","l","l","o"," ","W","o","r","l","d","."]

1
2
3
4
var str = "Hello World.";
console.log(str[0]); // H
console.log(str[5]); //
console.log(str[12]); // undefined

length属性

可以用来获取字符串的长度

1
console.log(str.length); // 12

charAt()

可以返回字符串中指定位置的字符,不会对原字符串产生影响

1
2
3
var result = str.charAt(0);
console.log(str); // Hello World.
console.log(result); // H

charCodeAt()

获取指定位置字符的字符编码(Unicode编码),不会对原字符串产生影响

1
2
3
4
5
6
result = str.charCodeAt(0);
console.log(str); // Hello World.
console.log(result); // 72
var str2 = "您好,世界。";
result = str2.charCodeAt(0);
console.log(result); // 24744

String.formCharCode()

可以根据字符编码去获取字符

1
2
3
4
5
6
result = String.fromCharCode(72);
console.log(result); // H
result = String.fromCharCode(24744);
console.log(result); // 您
result = String.fromCharCode(0x2682);
console.log(result); // ⚂

concat()

可以用来连接两个或多个字符串,作用和+一样,不会对原字符串产生影响

1
2
3
result = str.concat("您好","世界");
console.log(str); // Hello World.
console.log(result); // Hello World.您好世界

indexof()

该方法可以检索一个字符串中是否含有指定内容,不会对原字符串产生影响

  • 如果字符串中含有该内容,则返回其第一次出现的索引
  • 如果没有找到指定的内容,则返回-1
1
2
3
4
5
result = str.indexOf("o");
console.log(str); // Hello World.
console.log(result); // 4
result = str.indexOf("y");
console.log(result); // -1

可以指定一个第二个参数,指定开始查找的位置

1
2
3
4
result = str.indexOf("l",3);
console.log(result); // 3
result = str.indexOf("l",4);
console.log(result); // 9

lastIndexof()

该方法的用法和indexOf()一样,不同的是indexOf是从前往后找,而lastIndexOf是从后往前找

但返回的索引是按照从前往后计数的

1
2
3
4
result = str.lastIndexOf("o");
console.log(result); // 7
result = str.lastIndexOf("l");
console.log(result); // 9

可以指定一个第二个参数,指定开始查找的位置(不过开始位置也是从后往前数的)

1
2
result = str.lastIndexOf("l", 6);
console.log(result); // 3

slice()

可以从字符串中截取指定的内容,不会影响原字符串

  • 第一个参数,开始位置的索引(包括开始位置)
  • 第二个参数,结束位置的索引(不包括结束位置)
1
2
3
result = str.slice(0,2);
console.log(str); // Hello World.
console.log(result); // He

如果省略第二个参数,则会截取到后边所有的

1
2
result = str.slice(6);
console.log(result); // World.

也可以传递一个负数作为参数,负数的话将会从后边计算

1
2
result = str.slice(6,-1);
console.log(result); // World

substring()

可以用来截取一个字符串,不会影响原字符串,和slice()类似

  • 第一个参数,开始位置的索引(包括开始位置)
  • 第二个参数,结束位置的索引(不包括结束位置)
1
2
3
result = str.substring(0,2);
console.log(str); // Hello World.
console.log(result); // He

不同的是这个方法不能接受负值作为参数,如果传递了一个负值,则默认使用0

而且会自动调整参数的位置,如果第二个参数小于第一个,则自动交换

1
2
3
4
result = str.substring(1,-1); // 自动调整为str.substring(0,1);
console.log(result); // H
result = str.substring(1,0); // 自动调整为str.substring(0,1);
console.log(result); // H

substr()

用来截取字符串,不会影响原字符串,不过不建议使用

image-20210731192139982

  • 第一个参数,截取开始位置的索引
  • 第二个参数,截取的长度
1
2
3
result = str.substr(1,3);
console.log(str); // Hello World.
console.log(result); // ell

split()

可以将一个字符串拆分为一个数组,不会影响原字符串

需要一个字符串作为参数,将会根据该字符串去拆分数组

1
2
3
4
5
6
result = str.split("o");
console.log(str); // Hello World.
console.log(result); // ["Hell", " W", "rld."]
console.log(result[0]); // Hell
console.log(result[1]); // W
console.log(result[2]); // rld.

如果传递一个空串作为参数,则会将每个字符都拆分为数组中的一个元素

1
2
result = str.split("");
console.log(result); // ["H", "e", "l", "l", "o", " ", "W", "o", "r", "l", "d", "."]

toUpperCase()

将一个字符串转换为大写并返回,不会影响原字符串

1
2
3
result = str.toUpperCase();
console.log(str); // Hello World.
console.log(result); // HELLO WORLD.

toLowerCase()

将一个字符串转换为小写并返回,不会影响原字符串

1
2
3
result = str.toLowerCase();
console.log(str); // Hello World.
console.log(result); // hello world.

正则表达式

正则表达式用于定义一些字符串的规则,计算机可以根据正则表达式,来检查一个字符串是否符合规则,获取将字符串中符合规则的内容提取出来

1、正则对象

语法:var 变量 = new RegExp("正则表达式", "匹配模式");

1
2
3
// 这个正则表达式可以来检查一个字符串中是否含有a
var reg = new RegExp("a");
console.log(reg); // /a/

使用typeof检查正则对象,会返回object

1
console.log(typeof reg); // object

2、正则方法

正则表达式的方法:test()

使用这个方法可以用来检查一个字符串是否符合正则表达式的规则,如果符合则返回true,否则返回false

1
2
3
4
5
6
var result = reg.test("abd");
console.log(result); // true
result = reg.test("hgf");
console.log(result); // false
result = reg.test("Abd");
console.log(result); // false

在构造函数中可以传递一个匹配模式作为第二个参数,可以是

  • i ignoreCase,忽略大小写
  • g global,全局匹配模式
1
2
3
reg = new RegExp("a","i");
result = reg.test("Abd");
console.log(result); // true

3、正则语法

使用字面量来创建正则表达式,语法:var 变量 = /正则表达式/匹配模式;

使用字面量的方式创建更加简单;使用构造函数创建更加灵活

1
2
3
reg = /a/i;
result = reg.test("Abd");
console.log(result); // true

使用|表示或者的意思

1
2
3
4
5
6
7
8
9
10
// 创建一个正则表达式,检查一个字符串中是否有a或b或c
reg = /a|b|c/i;
result = reg.test("Abcd");
console.log(result); // true
result = reg.test("bcd");
console.log(result); // true
result = reg.test("cd");
console.log(result); // true
result = reg.test("d");
console.log(result); // false

[]里的内容也是或的关系:[abc] == a|b|c

1
2
3
reg = /[abc]/i;
result = reg.test("bcd");
console.log(result); // true

[a-z]任意小写字母

1
2
3
4
5
reg = /[a-z]/;
result = reg.test("Abc");
console.log(result); // true
result = reg.test("ABC");
console.log(result); // false

[A-Z]任意大写字母

1
2
3
4
5
reg = /[A-Z]/;
result = reg.test("abc");
console.log(result); // false
result = reg.test("ABC");
console.log(result); // true

[A-z]任意字母

1
2
3
4
5
reg = /[A-z]/;
result = reg.test("abc");
console.log(result); // true
result = reg.test("ABC");
console.log(result); // true

[0-9]任意数字

1
2
3
4
5
reg = /[0-9]/;
result = reg.test("123");
console.log(result); // true
result = reg.test("abc");
console.log(result); // false

**练习:**检查一个字符串中是否含有abc或adc或aec

1
2
3
4
5
reg = /a[bde]c/;
result = reg.test("adc");
console.log(result); // true
result = reg.test("addc");
console.log(result); // false

[^ ]除了

1
2
3
4
5
reg = /[^a]/; // 除了a以外的字符
result = reg.test("a");
console.log(result); // false
result = reg.test("b");
console.log(result); // true

[^0-9]除了数字

1
2
3
4
5
reg = /[^0-9]/;
result = reg.test("123");
console.log(result); // false
result = reg.test("abc");
console.log(result); // true

小结

表达式 描述
[abc] 查找方括号之间的任何字符
[^abc] 查找任何不在方括号之间的字符
[0-9] 查找任何从0至9的数字
[a-z] 查找任何从小写a到小写z的字符
[A-Z] 查找任何从大写A到大写Z的字符
[A-z] 查找任何从大写A到小写z的字符
[ojbk] 查找给定集合内的任何字符
[^ojbk] 查找给定集合外的任何字符
(ed|blue|green) 查找任何指定的选项

量词

image-20210801094306628

通过量词可以设置一个内容出现的次数

量词只对它前边的一个内容起作用

  • {n}正好出现n次

    1
    2
    3
    4
    5
    6
    7
    8
    9
    // 创建一个正则表达式检查一个字符串中是否含有aaa
    var reg = /a{3}/;
    console.log(reg.test("aaabc")); // true
    // 创建一个正则表达式检查一个字符串中是否含有ababab
    reg = /ab{3}/;
    console.log(reg.test("ababab")); // false
    console.log(reg.test("aaabbb")); // true
    reg = /(ab){3}/;
    console.log(reg.test("ababab")); // true
  • {m,n}出现m-n次

    1
    2
    3
    4
    5
    reg = /ab{3,4}c/;
    console.log(reg.test("abbc")); // false
    console.log(reg.test("abbbc")); // true
    console.log(reg.test("abbbbc")); // true
    console.log(reg.test("abbbbbc")); // false
  • {m,} 出现m次以上

    1
    2
    3
    reg = /ab{3,}c/;
    console.log(reg.test("abbbc")); // true
    console.log(reg.test("abbbbbc")); // true
  • +至少一个,相当于{1,}

    1
    2
    3
    4
    reg = /ab+c/;
    console.log(reg.test("ac")); // false
    console.log(reg.test("abc")); // true
    console.log(reg.test("abbbc")); // true
  • *0个或多个,相当于{0,}

    1
    2
    3
    4
    reg = /ab*c/;
    console.log(reg.test("ac")); // true
    console.log(reg.test("abbc")); // true
    console.log(reg.test("abbbc")); // true
  • ?0个或1个,相当于{0,1}

    1
    2
    3
    4
    reg = /ab?c/;
    console.log(reg.test("ac")); // true
    console.log(reg.test("abc")); // true
    console.log(reg.test("abbc")); // false
  • ^表示开头

    1
    2
    3
    4
    // 检查一个字符串中是否以a开头
    reg = /^a/;
    console.log(reg.test("ac")); // true
    console.log(reg.test("bac")); // false
  • $表示结尾

    1
    2
    3
    4
    // 检查一个字符串中是否以a结尾
    reg = /a$/;
    console.log(reg.test("abac")); // false
    console.log(reg.test("abaca")); // true
  • 如果在正则表达式中同时使用^$,要求字符串必须完全符合正则表达式

    1
    2
    3
    4
    5
    6
    7
    8
    // 以a开头,并立即以a结尾
    reg = /^a$/;
    console.log(reg.test("aba")); // false
    console.log(reg.test("a")); // true
    // 以a开头,或者以a结尾
    reg = /^a|a$/;
    console.log(reg.test("aba")); // true
    console.log(reg.test("a")); // true

**练习:**创建一个正则表达式,用来检查一个字符串是否是一个合法的手机号

  • 第二位:以1开头
  • 第二位:3-9任意数字
  • 三位以后:任意数字9个
1
2
3
4
5
reg = /^1[3-9][0-9]{9}$/;
console.log(reg.test("a14567876543")); // false
console.log(reg.test(1456787)); // false
console.log(reg.test(12567876543)); // false
console.log(reg.test(13567876543)); // true

小结

量词 描述
n+ 匹配任何包含至少一个n的字符串
n* 匹配任何包含零个或多个n的字符串
n? 匹配任何包含零个或一个n的字符串
n{X} 匹配包含X个n的序列的字符串
n{X,Y} 匹配包含X或Y个n的序列的字符串
n{X,} 匹配包含至少X个n的序列的字符串
n$ 匹配任何结尾为n的字符串
^n 匹配任何开头为n的字符串

元字符

image-20210801104551084

检查一个字符串中是否含有.

1
2
3
var reg = /./;
console.log(reg.test("ab")); // true
console.log(reg.test("a.b")); // true

.表示任意字符

在正则表达式中使用\作为转义字符

  • \.来表示.

    1
    2
    3
    reg = /\./;
    console.log(reg.test("ab")); // false
    console.log(reg.test("a.b")); // true
  • \\表示\

    1
    2
    3
    4
    5
    reg = /\\/;
    console.log(reg.test("ab")); // false
    console.log(reg.test("a\")); // Uncaught SyntaxError: Invalid or unexpected token
    console.log(reg.test("a\b")); // false
    console.log(reg.test("a\\b")); // true

**注意:**使用构造函数时,由于它的参数是一个字符串,而\是字符串中转义字符

1
2
3
4
5
6
reg = new RegExp("\."); // 相当于 reg = /./,即包含任意字符
console.log(reg); // /./
console.log(reg.test("ab")); // true
console.log(reg.test("a\b")); // true
console.log(reg.test("a\\b")); // true
console.log(reg.test("a.b")); // true

如果要使用\,则需要使用\\来代替

1
2
3
4
5
6
reg = new RegExp("\\."); // 相当于 reg = /\./,即包含`.`
console.log(reg); // /\./
console.log(reg.test("ab")); // false
console.log(reg.test("a\b")); // false
console.log(reg.test("a\\b")); // false
console.log(reg.test("a.b")); // true

如果要使用\\,则需要使用\\\\来代替

1
2
3
4
5
6
reg = new RegExp("\\\\."); // 相当于 reg = /\\./,即包含`\任意字符`
console.log(reg); // /\\./
console.log(reg.test("ab")); // false
console.log(reg.test("a\b")); // false
console.log(reg.test("a\\b")); // true
console.log(reg.test("a.b")); // false
  • \w 任意字母、数字、_,相当于[A-z0-9_]

    1
    2
    3
    4
    5
    reg = /\w/;
    console.log(reg.test("abc")); // true
    console.log(reg.test(123)); // true
    console.log(reg.test("_")); // true
    console.log(reg.test("!@#$%^&*()")); // false
  • \W 除了字母、数字、_,相当于[^A-z0-9_]

    1
    2
    3
    4
    5
    reg = /\W/;
    console.log(reg.test("abc")); // false
    console.log(reg.test(123)); // false
    console.log(reg.test("_")); // false
    console.log(reg.test("!@#$%^&*()")); // true
  • \d 任意数字,相当于[0-9]

    1
    2
    3
    4
    5
    reg = /\d/;
    console.log(reg.test("abc")); // false
    console.log(reg.test(123)); // true
    console.log(reg.test("_")); // false
    console.log(reg.test("!@#$%^&*()")); // false
  • \D 除了数字,相当于[^0-9]

    1
    2
    3
    4
    5
    reg = /\D/;
    console.log(reg.test("abc")); // true
    console.log(reg.test(123)); // false
    console.log(reg.test("_")); // true
    console.log(reg.test("!@#$%^&*()")); // true
  • \s 空格

    1
    2
    3
    4
    5
    6
    reg = /\s/;
    console.log(reg.test("abc")); // false
    console.log(reg.test(123)); // false
    console.log(reg.test("_")); // false
    console.log(reg.test("!@#$%^&*()")); // false
    console.log(reg.test("d35@ d")); // true
  • \S 除了空格

    1
    2
    3
    4
    5
    6
    reg = /\S/;
    console.log(reg.test("abc")); // true
    console.log(reg.test(123)); // true
    console.log(reg.test("_")); // true
    console.log(reg.test("!@#$%^&*()")); // true
    console.log(reg.test(" ")); // false
  • \b 单词边界

    1
    2
    3
    4
    5
    6
    7
    reg = /child/;
    console.log(reg.test("child")); // true
    console.log(reg.test("hello children")); // true
    reg = /\bchild\b/;
    console.log(reg.test("child")); // true
    console.log(reg.test("hello children")); // false
    console.log(reg.test("hello child ren")); // true
  • \B 除了单词边界

    1
    2
    3
    4
    5
    reg = /\Bchild\b/;
    console.log(reg.test("child")); // false
    console.log(reg.test("hello children")); // false
    console.log(reg.test("hello child ren")); // false
    console.log(reg.test("hellochild ren")); // true

4、字符串和正则相关的方法

image-20210731202642886

split()

可以将一个字符串拆分为一个数组,不会影响原字符串

方法中可以传递一个正则表达式作为参数,这样方法将会根据正则表达式去拆分字符串

split()方法即使不指定全局匹配,也会全都拆分

1
2
3
4
5
// 根据任意字母来将字符串拆分
var str = "1a2b3c4d5e6f7g8h9i0";
var result = str.split(/[A-z]/);
console.log(str); // 1a2b3c4d5e6A7B8C9D0
console.log(result); // ["1", "2", "3", "4", "5", "6", "7", "8", "9", "0"]

可以搜索字符串中是否含有指定内容,不会影响原字符串

如果搜索到指定内容,则会返回第一次出现的索引,如果没有搜索到返回-1

它可以接受一个正则表达式作为参数,然后会根据正则表达式去检索字符串

search()只会查找第一个,即使设置全局匹配也没用

1
2
3
4
str = "Hello abc Hello afc agc";
result = str.search(/a[A-z]c/);
console.log(str); // 1a2b3c4d5e6A7B8C9D0
console.log(result); // 6

match()

可以根据正则表达式,从一个字符串中将符合条件的内容提取出来,不会影响原字符串

默认情况下我们的match只会找到第一个符合要求的内容,找到以后就停止检索

1
2
3
4
str = "1a2b3c4d5e6A7B8C9D0";
result = str.match(/[a-z]/);
console.log(str); // 1a2b3c4d5e6A7B8C9D0
console.log(result); // ["a", index: 1, input: "1a2b3c4d5e6A7B8C9D0", groups: undefined]

可以设置正则表达式为全局匹配模式,这样就会匹配到所有的内容

1
2
result = str.match(/[a-z]/g);
console.log(result); // ["a", "b", "c", "d", "e"]

可以为一个正则表达式设置多个匹配模式,且顺序无所谓

1
2
result = str.split(/[a-z]/ig);
console.log(result); // ["1", "2", "3", "4", "5", "6", "7", "8", "9", "0"]

match()会将匹配到的内容封装到一个数组中返回,即使只查询到一个结果

1
2
3
console.log(Array.isArray(result)); // true
console.log(result[0]); // a
console.log(result[5]); // A

replace()

可以将字符串中指定内容替换为新的内容,不会影响原字符串

参数:

  1. 被替换的内容,可以接受一个正则表达式作为参数
  2. 新的内容
1
2
3
result = str.replace("a","@_@");
console.log(str); // 1a2b3c4d5e6A7B8C9D0
console.log(result); // 1@_@2b3c4d5e6A7B8C9D0

默认只会替换第一个,可以使用正则表达式的全局匹配模式

1
2
3
4
5
6
7
8
9
10
11
str = "1a2a3a4a5a6A7B8C9D0";
result = str.replace("a","@_@");
console.log(result); // 1@_@2a3a4a5a6A7B8C9D0
result = str.replace(/a/g,"@_@");
console.log(result); // 1@_@2@_@3@_@4@_@5@_@6A7B8C9D0
result = str.replace(/a/gi,"@_@");
console.log(result); // 1@_@2@_@3@_@4@_@5@_@6@_@7B8C9D0
result = str.replace(/[a-z]/gi,"@_@");
console.log(result); // 1@_@2@_@3@_@4@_@5@_@6@_@7@_@8@_@9@_@0
result = str.replace(/[a-z]/gi,"");
console.log(result); // 1234567890

小结

  • split()方法用于拆分,即使不指定全局匹配,也会全都拆分
  • search方法用于搜索,只会查找第一个,即使设置全局匹配也没用
  • match方法用于提取
  • replace方法用于替换

**练习:**去除用户输入中的前后空格

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 接收一个用户的输入
var str = " hello world ";
console.log(str); // hello world
// 去除掉字符串中的空格
// 去除空格就是使用""来替换空格
var result = str.replace(/\s/g,"");
console.log(result); // helloworld
// 去除前面的空格
result = str.replace(/^\s+/,"");
console.log(result); // hello world
// 去除后面的空格
result = str.replace(/\s+$/,"");
console.log(result); // hello world
// 去除前面和后面的空格
result = str.replace(/^\s+|\s$/g,"");
console.log(result); // hello world

5、邮件正则

任意字母数字下划线(.任意字母数字下划线){0个或多个}@任意字母数字.任意字母(2-5位)(.任意字母(2-5位)){0个或多个}

1
2
3
4
5
6
7
var reg = /^\w+(\.\w+)*@[A-z0-9]+(\.[A-z]{2,5})+$/;
str = "Vector123_.xxx.163@outLOOK123.Com.cn";
console.log(reg.test(str)); // true
str = "Vector123_.xxx.163@outLOOK123.com123.cn";
console.log(reg.test(str)); // false
str = "#$%^&*()Vector123_.xxx.163@outLOOK123.com123.cn";
console.log(reg.test(str)); // false

附录:常用正则表达式[^1]

校验数字的表达式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
数字:^[0-9]*$
n位的数字:^\d{n}$
至少n位的数字:^\d{n,}$
m-n位的数字:^\d{m,n}$
零和非零开头的数字:^(0|[1-9][0-9]*)$
非零开头的最多带两位小数的数字:^([1-9][0-9]*)+(.[0-9]{1,2})?$
1-2位小数的正数或负数:^(\-)?\d+(\.\d{1,2})?$
正数、负数、和小数:^(\-|\+)?\d+(\.\d+)?$
有两位小数的正实数:^[0-9]+(.[0-9]{2})?$
1~3位小数的正实数:^[0-9]+(.[0-9]{1,3})?$
非零的正整数:^[1-9]\d*$ 或 ^([1-9][0-9]*){1,3}$ 或 ^\+?[1-9][0-9]*$
非零的负整数:^\-[1-9][]0-9"*$ 或 ^-[1-9]\d*$
非负整数:^\d+$ 或 ^[1-9]\d*|0$
非正整数:^-[1-9]\d*|0$ 或 ^((-\d+)|(0+))$
非负浮点数:^\d+(\.\d+)?$ 或 ^[1-9]\d*\.\d*|0\.\d*[1-9]\d*|0?\.0+|0$
非正浮点数:^((-\d+(\.\d+)?)|(0+(\.0+)?))$ 或 ^(-([1-9]\d*\.\d*|0\.\d*[1-9]\d*))|0?\.0+|0$
正浮点数:^[1-9]\d*\.\d*|0\.\d*[1-9]\d*$ 或 ^(([0-9]+\.[0-9]*[1-9][0-9]*)|([0-9]*[1-9][0-9]*\.[0-9]+)|([0-9]*[1-9][0-9]*))$
负浮点数:^-([1-9]\d*\.\d*|0\.\d*[1-9]\d*)$ 或 ^(-(([0-9]+\.[0-9]*[1-9][0-9]*)|([0-9]*[1-9][0-9]*\.[0-9]+)|([0-9]*[1-9][0-9]*)))$
浮点数:^(-?\d+)(\.\d+)?$ 或 ^-?([1-9]\d*\.\d*|0\.\d*[1-9]\d*|0?\.0+|0)$

校验字符的表达式

1
2
3
4
5
6
7
8
9
10
11
12
汉字:^[\u4e00-\u9fa5]{0,}$
英文和数字:^[A-Za-z0-9]+$ 或 ^[A-Za-z0-9]{4,40}$
长度为3-20的所有字符:^.{3,20}$
26个英文字母组成的字符串:^[A-Za-z]+$
26个大写英文字母组成的字符串:^[A-Z]+$
26个小写英文字母组成的字符串:^[a-z]+$
由数字和26个英文字母组成的字符串:^[A-Za-z0-9]+$
由数字、26个英文字母或者下划线组成的字符串:^\w+$ 或 ^\w{3,20}$
中文、英文、数字包括下划线:^[\u4E00-\u9FA5A-Za-z0-9_]+$
中文、英文、数字但不包括下划线等符号:^[\u4E00-\u9FA5A-Za-z0-9]+$ 或 ^[\u4E00-\u9FA5A-Za-z0-9]{2,20}$
可以输入含有^%&',;=?$\"等字符:[^%&',;=?$\x22]+
禁止输入含有~的字符:[^~\x22]+

特殊需求表达式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
Email地址:^\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$
域名:[a-zA-Z0-9][-a-zA-Z0-9]{0,62}(/.[a-zA-Z0-9][-a-zA-Z0-9]{0,62})+/.?
InternetURL:[a-zA-z]+://[^\s]* 或 ^http://([\w-]+\.)+[\w-]+(/[\w-./?%&=]*)?$
手机号码:^(13[0-9]|14[0-9]|15[0-9]|16[0-9]|17[0-9]|18[0-9]|19[0-9])\d{8}$ (由于工信部放号段不定时,所以建议使用泛解析 ^([1][3,4,5,6,7,8,9])\d{9}$)
电话号码("XXX-XXXXXXX""XXXX-XXXXXXXX""XXX-XXXXXXX""XXX-XXXXXXXX""XXXXXXX""XXXXXXXX):^(\(\d{3,4}-)|\d{3.4}-)?\d{7,8}$
国内电话号码(0511-4405222、021-87888822):\d{3}-\d{8}|\d{4}-\d{7}
18位身份证号码(数字、字母x结尾):^((\d{18})|([0-9x]{18})|([0-9X]{18}))$
帐号是否合法(字母开头,允许5-16字节,允许字母数字下划线):^[a-zA-Z][a-zA-Z0-9_]{4,15}$
密码(以字母开头,长度在6~18之间,只能包含字母、数字和下划线):^[a-zA-Z]\w{5,17}$
强密码(必须包含大小写字母和数字的组合,不能使用特殊字符,长度在8-10之间):^(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8,10}$
日期格式:^\d{4}-\d{1,2}-\d{1,2}
一年的12个月(01~09和1~12):^(0?[1-9]|1[0-2])$
一个月的31天(01~09和1~31):^((0?[1-9])|((1|2)[0-9])|30|31)$
钱的输入格式:
1.有四种钱的表示形式我们可以接受:"10000.00" 和 "10,000.00", 和没有 "" 的 "10000" 和 "10,000":^[1-9][0-9]*$
2.这表示任意一个不以0开头的数字,但是,这也意味着一个字符"0"不通过,所以我们采用下面的形式:^(0|[1-9][0-9]*)$
3.一个0或者一个不以0开头的数字.我们还可以允许开头有一个负号:^(0|-?[1-9][0-9]*)$
4.这表示一个0或者一个可能为负的开头不为0的数字.让用户以0开头好了.把负号的也去掉,因为钱总不能是负的吧.下面我们要加的是说明可能的小数部分:^[0-9]+(.[0-9]+)?$
5.必须说明的是,小数点后面至少应该有1位数,所以"10."是不通过的,但是 "10" 和 "10.2" 是通过的:^[0-9]+(.[0-9]{2})?$
6.这样我们规定小数点后面必须有两位,如果你认为太苛刻了,可以这样:^[0-9]+(.[0-9]{1,2})?$
7.这样就允许用户只写一位小数.下面我们该考虑数字中的逗号了,我们可以这样:^[0-9]{1,3}(,[0-9]{3})*(.[0-9]{1,2})?$
8.1到3个数字,后面跟着任意个 逗号+3个数字,逗号成为可选,而不是必须:^([0-9]+|[0-9]{1,3}(,[0-9]{3})*)(.[0-9]{1,2})?$
备注:这就是最终结果了,别忘了"+"可以用"*"替代如果你觉得空字符串也可以接受的话(奇怪,为什么?)最后,别忘了在用函数时去掉去掉那个反斜杠,一般的错误都在这里
xml文件:^([a-zA-Z]+-?)+[a-zA-Z0-9]+\\.[x|X][m|M][l|L]$
中文字符的正则表达式:[\u4e00-\u9fa5]
双字节字符:[^\x00-\xff] (包括汉字在内,可以用来计算字符串的长度(一个双字节字符长度计2,ASCII字符计1))
空白行的正则表达式:\n\s*\r (可以用来删除空白行)
HTML标记的正则表达式:<(\S*?)[^>]*>.*?</\1>|<.*? /> (网上流传的版本太糟糕,上面这个也仅仅能部分,对于复杂的嵌套标记依旧无能为力)
首尾空白字符的正则表达式:^\s*|\s*$或(^\s*)|(\s*$) (可以用来删除行首行尾的空白字符(包括空格、制表符、换页符等等),非常有用的表达式)
腾讯QQ号:[1-9][0-9]{4,} (腾讯QQ号从10000开始)
中国邮政编码:[1-9]\d{5}(?!\d) (中国邮政编码为6位数字)
IP地址:\d+\.\d+\.\d+\.\d+ (提取IP地址时有用)
IP地址:((?:(?:25[0-5]|2[0-4]\\d|[01]?\\d?\\d)\\.){3}(?:25[0-5]|2[0-4]\\d|[01]?\\d?\\d)) (由@飞龙三少 提供,感谢共享)

作者:zxin

出处:http://zxin.cnblogs.com/

本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。


参考资料

[^1]: 最全的常用正则表达式大全——包括校验数字、字符、一些特殊的需求等等 https://www.cnblogs.com/zxin/archive/2013/01/26/2877765.html

DOM

1、DOM简介

DOM,全称Document Object Model 文档对象模型

JS中通过DOM来对HTML文档进行操作。只要理解了DOM就可以随心所欲的操作WEB页面。

文档

文档表示的就是整个的HTML网页文档

对象

对象表示将网页中的每一个部分都转换为了一个对象

模型

使用模型来表示对象之间的关系,这样方便我们获取对象

image-20210801152925307

DOM树体现了节点与节点之间的关系

image-20210801153018626

2、节点

节点Node,是构成我们网页的最基本的组成部分,网页中的每一个部分都可以称为是一个节点

比如:html标签、属性、文本、注释、整个文档等都是一个节点

虽然都是节点,但是实际上他们的具体类型是不同的。比如:

  • 标签称为元素节点
  • 属性称为属性节点
  • 文本称为文本节点
  • 文档称为文档节点

节点的类型不同,属性和方法也都不尽相同

节点类型

节点:Node——构成HTML文档最基本的单元

常用节点分为四类

  • 文档节点:整个HTML文档
  • 元素节点:HTML文档中的HTL标签
  • 属性节点:元素的属性
  • 文本节点:HTML标签中的文本内容

image-20210801153554420

节点属性

image-20210801153633118

文档节点(Document)

文档节点document,代表的是整个HTML文档,网页中的所有节点都是它的子节点

document对象作为window对象的属性存在的,我们不用获取可以直接使用

通过该对象我们可以在整个文档访问内查找节点对象,并可以通过该对象创建各种节点对象

元素节点(Element)

HTML中的各种标签都是元素节点,这也是我们最常用的一个节点

浏览器会将页面中所有的标签都转换为一个元素节点,我们可以通过document的方法来获取元素节点

比如:document.getElementById() 根据id属性值获取一个元素节点对象。

文本节点(Text)

文本节点表示的是HTML标签以外的文本内容,任意非HTML的文本都是文本节点

它包括可以字面解释的纯文本内容

文本节点一般是作为元素节点的子节点存在的

获取文本节点时,一般先要获取元素节点,再通过元素节点获取文本节点。例如:元素节点.firstChild;

获取元素节点的第一个子节点,一般为文本节点

属性节点(Attr)

属性节点表示的是标签中的一个一个的属性,这里要注意的是属性节点并非是元素节点的子节点,而是元素节点的一部分

可以通过元素节点来获取指定的属性节点。例如:元素节点.getAttributeNode("属性名");

注意:我们一般不使用属性节点

浏览器已经为我们提供文档节点对象,这个对象是window

属性可以在页面中直接使用,文档节点代表的是整个网页

1
2
3
4
5
// 获取button对象
var btn = document.getElementById("btn");
console.log(btn); // <button type="button" id="btn">我是一个按钮</button>
// 修改btn的文本节点内容
btn.innerHTML = "I'm a button.";

3、事件

事件,就是文档或浏览器窗口中发生的一些特定的交互瞬间

JavaScript与HTML之间的交互是通过事件实现的

对于Web应用来说,有下面这些代表性的事件:点击某个元素、将鼠标移动至某个元素上方、按下键盘上某个键,等等

image-20210801155425259

我们可以在事件对应的属性中设置一些js代码,这样当事件被触发时,这些代码将会执行

1
<button type="button" id="btn" onclick="alert('Fuck');">我是一个按钮</button>

这种写法我们称为结构和行为耦合,不方便维护,不推荐使用

可以为按钮的对应事件绑定处理函数的形式来响应事件,这样当事件被触发时,其对应的函数将会被调用

1
2
3
4
// 绑定一个单击事件
btn.onclick = function(){
alert("Don't touch me.");
}

像这种为单击事件绑定的函数,我们称为单击响应函数

4、文档的加载

当我们把script标签放到head中时,会报错UncaughtTypeError: Cannot set property 'innerHTML' of null,这是为什么呢?

浏览器在加载一个页面时,是按照自上向下的顺序加载的,读取到一行就运行一行,如果将script标签写到页面的上边,在代码执行时,页面还没有加载,DOM对象也没有加载,会导致无法获取到DOM对象

image-20210801160850690

如果非要这么干,也不是没有办法

onload事件会在整个页面加载完成之后才触发,可以为window对象绑定一个onload事件

1
2
3
4
5
6
7
8
window.onload = function(){
// 获取button对象
var btn = document.getElementById("btn");
// 绑定一个单击事件
btn.onclick = function(){
alert("Don't touch me.");
}
}

该事件对应的响应函数将会在页面加载完成之后执行,这样可以确保我们的代码执行时所有的DOM对象已经加载完毕了

5、DOM查询

获取元素节点

image-20210803222352914

通过document对象调用

为了方便,定义一个通用的函数,专门用来为指定元素绑定单击响应函数

1
2
3
4
5
6
7
// 参数:
// idstr 要绑定单击响应函数的对象的id属性值
// fun 事件的回调函数,当单击元素时,该函数将会被触发
function myClick(idStr, fun){
var btn = document.getElementById(idStr);
btn.onclick = fun;
}
  • getElementById() 通过id属性获取一个元素节点对象

    1
    2
    3
    4
    myClick("btn01", function () {
    // innerHTML 通过这个属性可以获取到元素内部的html代码
    alert(document.getElementById("bj").innerHTML); // 北京
    });
  • getElementsByTagName() 通过标签名获取一组元素节点对象

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    myClick("btn02", function () {
    // getElementsByTagName()可以根据标签名来获取一组元素节点对象
    // 这个方法会给我们返回一个类数组对象,所有查询到的元素都会封装到对象中
    // 即使查询到的元素只有一个,也会封装到数组中返回
    var li_list = document.getElementsByTagName("li");
    alert(li_list.length); // 14

    var arr = [];
    for(var i=0;i<li_list.length;i++){
    arr.push(li_list[i].innerHTML);
    }
    alert(arr); // 北京,上海,东京,首尔,红警,实况,极品飞车,魔兽,IOS,Android,Windows Phone,IOS,Android,Windows Phone
    });
  • getElementsByName() 通过name属性获取一组元素节点对象

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    myClick("btn03", function () {
    var inputs = document.getElementsByName("gender");
    alert(inputs.length); // 2

    var arr = [];
    for(var i=0;i<inputs.length;i++){
    // innerHTML用于获取元素内战的HTML代码的
    // 如果需要读取元素节点属性,直接使用`元素.属性名`
    // 例子:`元素.id` `元素.name` `元素.value`
    arr.push(inputs[i].value);
    // 注意:class属性不能采用这种方式,读取class属性时需要使用`元素.className`
    arr.push(inputs[i].className);
    }
    alert(arr); // male,hello,female,hello
    });

练习:图片切换

HTML代码

1
2
3
4
5
6
<div class="outer">
<p id="info">共5张图片,当前第1张</p>
<img src="img/1.jpg" alt="冰棍"/>
<button type="button" id="prev">上一张</button>
<button type="button" id="next">下一张</button>
</div>

CSS代码

1
2
3
4
5
6
7
8
9
10
11
12
13
*{
margin:0;
padding:0;
}

.outer{
width: 500px;
margin: 50px auto;
padding: 10px;
background-color: greenyellow;
/* 文本居中:内联样式当成是文本 */
text-align: center;
}

JS代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// 上一张
var prev = document.getElementById("prev");
// 下一张
var next = document.getElementById("next");
// 图片
var img = document.getElementsByTagName("img")[0];
// 信息
var info = document.getElementById("info");
// 图片集合
var imgArr = ["img/1.jpg", "img/2.jpg", "img/3.jpg", "img/4.jpg", "img/5.jpg"];
// 记录第几张
var index = 0;
// 上一张绑定单击相应事件
prev.onclick = function(){
// 循环切换
index = (index < 0) ? imgArr.length - 1 : index;
// 修改img的src属性,以切换图片
img.src = imgArr[index];
// 修改文字提示
info.innerHTML = "共" + imgArr.length + "张图片,当前第" + (index + 1) + "张";
// 切换上一张
index--;
};
// 下一张绑定单击相应事件
next.onclick = function(){
// 循环切换
index = (index > imgArr.length - 1) ? 0 :index;
// 修改img的src属性,以切换图片
img.src = imgArr[index];
// 修改文字提示
info.innerHTML = "共" + imgArr.length + "张图片,当前第" + (index + 1) + "张";
// 切换下一张
index++;
};

效果

图片切换

获取元素节点的子节点

image-20210803222419830

通过具体的元素节点调用

  • getElementsByTagName()方法,返回当前节点的指定标签名后代节点

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    myClick("btn04", function () {
    var city = document.getElementById("city");
    // 获取city下1i节点
    var list = city.getElementsByTagName("li");
    alert(list.length); // 4

    var arr = [];
    for(var i=0;i<list.length;i++){
    arr.push(list[i].innerHTML);
    }
    alert(arr); // 北京,上海,东京,首尔
    });
  • childNodes属性,表示当前节点的所有子节点

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    myClick("btn05", function () {
    var city = document.getElementById("city");
    // childNodes属性会获取包括文本节点在内的所有节点
    // 根据DOM标签标签间空白也会当成文本节点
    // 注意:在IE8及以下的浏览器中,不会将空白文本当成子节点
    // 所以该属性在IE8中会返回4个子元素,而其他浏览器是9个
    var list = city.childNodes;
    alert(list.length); // 9

    var arr = [];
    for(var i=0;i<list.length;i++){
    arr.push(list[i]);
    }
    alert(arr); // [object Text],[object HTMLLIElement],[object Text],[object HTMLLIElement],[object Text],[object HTMLLIElement],[object Text],[object HTMLLIElement],[object Text]
    });
    myClick("btn05", function () {
    var city = document.getElementById("city");
    // children属性可以获取当前元素的所有子元素
    var list = city.children;
    alert(list.length); // 4

    var arr = [];
    for(var i=0;i<list.length;i++){
    arr.push(list[i].innerHTML);
    }
    alert(arr); // 北京,上海,东京,首尔
    });
  • firstChild属性,表示当前节点的第一个子节点

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    myClick("btn06", function () {
    var phone = document.getElementById("phone");
    // firstChild可以获取到当前元素的第一个子节点(包括空白文本节点)
    var firstChild = phone.firstChild;
    alert(firstChild); // [object HTMLLIElement]
    alert(firstChild.innerHTML); // IOS
    });
    myClick("btn06", function () {
    var phone2 = document.getElementById("phone2");
    // firstChild可以获取到当前元素的第一个子节点(包括空白文本节点)
    var firstChild = phone2.firstChild;
    alert(firstChild); // [object Text]
    alert(firstChild.innerHTML); // undefined
    });
    myClick("btn06", function () {
    var phone2 = document.getElementById("phone2");
    // firstElementchild不支持IE8及以下的浏览器,如果需要兼容他们尽量不要使用
    var firstElementChild = phone2.firstElementChild;
    alert(firstElementChild); // [object HTMLLIElement]
    alert(firstElementChild.innerHTML); // IOS
    });
  • lastChild属性,表示当前节点的最后一个子节点

    1
    2
    3
    4
    5
    6
    7
    document.getElementById("btn062").onclick = function () {
    var phone = document.getElementById("phone");
    // children属性可以获取当前元素的所有子元素
    var lastChild = phone.lastChild;
    alert(lastChild); // [object HTMLLIElement]
    alert(lastChild.innerHTML); // Windows Phone
    });

获取父节点和兄弟节点

image-20210803222448362

通过具体的节点调用

  • parentNode属性,表示当前节点的父节点

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    myClick("btn07", function () {
    var bj = document.getElementById("bj");
    var parentNode = bj.parentNode;
    alert(parentNode); // [object HTMLUListElement]
    alert(parentNode.innerHTML);
    // <li id="bj">北京</li>
    // <li>上海</li>
    // <li>东京</li>
    // <li>首尔</li>

    // innerText
    // -该属性可以获取到元素内部的文本内容
    // -它和innerHTML类似,不同的是它会自动将htm1去除
    alert(parentNode.innerText);
    // 北京
    // 上海
    // 东京
    // 首尔
    });
  • previousSibling属性,表示当前节点的前一个兄弟节点

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    myClick("btn08", function () {
    var android = document.getElementById("android");
    // 返回#android的前一个兄弟节点(也可能获取到空白的文本)
    var previousSibling = android.previousSibling;
    alert(previousSibling); // [object HTMLLIElement]
    alert(previousSibling.innerHTML); // IOS
    });
    myClick("btn08", function () {
    var android2 = document.getElementById("android2");
    // 返回#android的前一个兄弟节点(也可能获取到空白的文本)
    var previousSibling = android2.previousSibling;
    alert(previousSibling); // [object Text]
    alert(previousSibling.innerHTML); // undefined
    });
    myClick("btn08", function () {
    var android2 = document.getElementById("android2");
    // previousElementSibling获取前一个兄弟元素,IE8及以下不支持
    var previousElementSibling = android2.previousElementSibling;
    alert(previousElementSibling); // [object HTMLLIElement]
    alert(previousElementSibling.innerHTML); // IOS
    });
  • nextSibling属性,表示当前节点的后一个兄弟节点

    1
    2
    3
    4
    5
    6
    7
    myClick("btn082", function () {
    var android = document.getElementById("android");
    // 返回#android的前一个兄弟节点(也可能获取到空白的文本)
    var nextSibling = android.nextSibling;
    alert(nextSibling); // [object HTMLLIElement]
    alert(nextSibling.innerHTML); // Windows Phone
    });

6、全选练习

HTML代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<form method="post" action="">
你爱好的运动是?<input type="checkbox" id="checkedAllBox" />全选/全不选

<br />
<input type="checkbox" name="items" value="足球" />足球
<input type="checkbox" name="items" value="篮球" />篮球
<input type="checkbox" name="items" value="羽毛球" />羽毛球
<input type="checkbox" name="items" value="乒乓球" />乒乓球
<br />
<input type="button" id="checkedAllBtn" value="全 选" />
<input type="button" id="checkedNoBtn" value="全不选" />
<input type="button" id="checkedRevBtn" value="反 选" />
<input type="button" id="sendBtn" value="提 交" />
</form>

全选

1
2
3
4
5
6
7
8
9
document.getElementById("checkedAllBtn").onclick = function(){
var items = document.getElementsByName("items");
for(var i=0;i<items.length;i++){
// 通过多选框的checked属性可以来获取或设置多选框的选中状态
items[i].checked = true;
}
// 全选按钮也要同步选中
document.getElementById("checkedAllBox").checked = true;
}

全不选

1
2
3
4
5
6
7
8
document.getElementById("checkedNoBtn").onclick = function(){
var items = document.getElementsByName("items");
for(var i=0;i<items.length;i++){
items[i].checked = false;
}
// 全选按钮也要同步不选中
document.getElementById("checkedAllBox").checked = false;
}

反选

1
2
3
4
5
6
7
8
9
10
11
12
document.getElementById("checkedRevBtn").onclick = function(){
var items = document.getElementsByName("items");
var flag = true;
for(var i=0;i<items.length;i++){
items[i].checked = !items[i].checked;
if(!items[i].checked){
flag = false;
}
}
// 全选按钮也要同步选中或不选中
document.getElementById("checkedAllBox").checked = flag;
}

提交

1
2
3
4
5
6
7
8
9
10
document.getElementById("sendBtn").onclick = function(){
var items = document.getElementsByName("items");
var arr = [];
for(var i=0;i<items.length;i++){
if(items[i].checked){
arr.push(items[i].value);
}
}
alert(arr);
}

全选/全不选

1
2
3
4
5
6
7
document.getElementById("checkedAllBox").onclick = function(){
var items = document.getElementsByName("items");
for(var i=0;i<items.length;i++){
// 在事件的响应函数中,响应函数是给谁绑定的this就是谁
items[i].checked = this.checked;
}
}

items

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var flag;
var items = document.getElementsByName("items");
for(var i=0;i<items.length;i++){
items[i].onclick = function(){
flag = true;
for(var j=0;j<items.length;j++){
if(!items[j].checked){
flag = false;
break;
}
}
document.getElementById("checkedAllBox").checked = flag;
}
}

效果

image-20210804215409752

image-20210804215455471

image-20210804215513918

7、DOM查询的剩余方法

document.body

document中有一个属性body,它保存的是body的引用

1
2
3
4
5
6
// 注意:如果script标签是定义在head中的,则这里需要window.onload = function(){}包裹,否则会出现null的情况
var body = document.getElementsByTagName("body");
console.log(body); // HTMLCollection [body]
body = document.body;
console.log(body); // <body></body>
console.log(typeof body); // object

document.documentElement

document.documentElement保存的是html根标签

1
2
var html = document.documentElement;
console.log(html);

document.all

document.all代表页面中所有的元素

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var all = document.all;
console.log(all); // HTMLAllCollection(11) [html, head, meta, title, script, script, script, body, script, script, script]
console.log(all.length); // 11
console.log(typeof all); // undefined
for(var i=0;i<all.length;i++){
console.log(all[i]);
}

var el = document.getElementsByTagName("*");
console.log(el); // HTMLCollection(11) [html, head, meta, title, script, script, script, body, script, script, script]
console.log(all.length); // 11
console.log(typeof all); // undefined
for(var i=0;i<el.length;i++){
console.log(el[i]);
}

document.getElementsByClassName()

根据元素的class属性值查询一组元素节点对象

getElementsByClassName()可以根据class属性值获取一组元素节点对象,但是该方法不支持IE8及以下的浏览器

1
2
3
4
var boxs = document.getElementsByClassName("box");
console.log(boxs); // HTMLCollection(3) [div.box, div.box, div.box]
console.log(boxs.length); // 3
console.log(typeof boxs); // object

document.querySelector()

需要一个选择器的字符串作为参数,可以根据一个CSS选择器来查询一个元素节点对象

虽然IE8中没有getElementsByClassName()但是可以使用querySelector()代替

使用该方法总会返回唯一的一个元素,如果满足条件的元素有多个,那么它只会返回第一个

1
2
3
4
5
6
7
var div = document.querySelector(".box div");
console.log(div.innerHTML); // I'm first div.
boxs = document.querySelector(".box");
console.log(boxs);
// <div class="box">
// <div>I'm first div.</div>
// </div>

document.querySelectorAll()

该方法和querySelector()用法类似,不的是它会将符合条件的元素封装到一个数组中返回

即使符合条件的元素只有一个,它也会返回数组

1
2
3
boxs = document.querySelectorAll(".box");
console.log(boxs); // NodeList(3) [div.box, div.box, div.box]
console.log(boxs.length); //3

8、DOM增删改

image-20210804215312180

document.createElement()

可以用于创建一个元素节点对象,它需要一个标签名作为参数,将会根据该标签名创建元素节点对象,并将创建好的对象作为返回值返回

document.createTextNode()

可以用来创建一个文本节点对象,它需要一个文本内容作为参数,将会根据该内容创建文本节点,并将新的节点返回

appendChild()

向一个父节点中添加一个新的子节点,用法:父节点.appendChild(子节点);

insertBefore()

可以在指定的子节点前插入新的子节点,语法:父节点.insertBefore(新节点, 旧节点);

replaceChild()

可以使用指定的子节点替换已有的子节点,语法:父节点.replaceChild(新节点, 旧节点);

removeChild()

可以删除一个子节点,语法:父节点.removeChild(子节点);子节点.parentNode.removeChild(子节点);

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
// 创建一个"广州"节点,添加到#city下
var city = document.getElementById("city");
myClick("btn01",function(){
// 创建元素节点
var li = document.createElement("li");
// 创建文本节点
var gz = document.createTextNode("广州");
// 将文本节点添加到元素节点中
li.appendChild(gz);
// 将元素节点添加至#city下
city.appendChild(li);
});
// 将"广州"节点插入到#bj前面
var bj = document.getElementById("bj");
myClick("btn02",function(){
var li = document.createElement("li");
var gz = document.createTextNode("广州");
li.appendChild(gz);
// 将元素节点插入到#bj前面
city.insertBefore(li,bj);
});
// 使用"广州"节点替换#bj节点
myClick("btn03",function(){
var li = document.createElement("li");
var gz = document.createTextNode("广州");
li.appendChild(gz);
// 将元素节点替换#bj节点
city.replaceChild(li,bj);
});
// 删除#bj节点
myClick("btn04",function(){
// 将元素节点替换#bj节点
// city.removeChild(bj);
// 更常用,不需要知道父节点是什么
bj.parentNode.removeChild(bj);
});
// 使用innerHTML将"广州"节点添加到#city下
myClick("btn07",function(){
// 使用innerHTML也可以完成DOM的增删改的相关操作
// city.innerHTML += "<li>广州</li>";
// 不过这种方式会先删除再替换,耗费性能,所以一般我们会两种方式结合使用
var li = document.createElement("li");
li.innerHTML = "广州";
city.appendChild(li);
});

9、增删练习

准备

image-20210805221937924

HTML代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
<table id="employeeTable">
<tr>
<th>Name</th>
<th>Email</th>
<th>Salary</th>
<th>&nbsp;</th>
</tr>
<tr>
<td>Tom</td>
<td>tom@tom.com</td>
<td>5000</td>
<td><a href="deleteEmp?id=001">Delete</a></td>
</tr>
<tr>
<td>Jerry</td>
<td>jerry@sohu.com</td>
<td>8000</td>
<td><a href="deleteEmp?id=002">Delete</a></td>
</tr>
<tr>
<td>Bob</td>
<td>bob@tom.com</td>
<td>10000</td>
<td><a href="deleteEmp?id=003">Delete</a></td>
</tr>
</table>

<div id="formDiv">
<h4>添加新员工</h4>
<table>
<tr>
<td class="word">name: </td>
<td class="inp">
<input type="text" name="empName" id="empName" />
</td>
</tr>
<tr>
<td class="word">email: </td>
<td class="inp">
<input type="text" name="email" id="email" />
</td>
</tr>
<tr>
<td class="word">salary: </td>
<td class="inp">
<input type="text" name="salary" id="salary" />
</td>
</tr>
<tr>
<td colspan="2" align="center">
<button id="addEmpButton" value="abc">
Submit
</button>
</td>
</tr>
</table>
</div>

JS代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
// a的单击相应函数
function delRow() {
// 添加提示信息
grandPrentNode = this.parentNode.parentNode;
var name = grandPrentNode.children[0].innerHTML;
if (confirm("确认删除" + name + "吗?")) {
// 删除祖先节点
grandPrentNode.parentNode.removeChild(grandPrentNode);
}
// 点击超链接以后,超链接会跳转页面,这个是超链接的默认行为,
// 但是此时我们不希望出现默认行为,可以通过在响应函数的最后return false来取消默认行为
return false;
}

window.onload = function() {
// 1、删除
// 为delete绑定单击相应函数
var a;
var grandPrentNode;
var aList = document.getElementsByTagName("a");
for (var i = 0; i < aList.length; i++) {
aList[i].onclick = delRow;
}

// 2、添加
document.getElementById("addEmpButton").onclick = function() {
// 获取name/email/salary
var empName = document.getElementById("empName").value;
var email = document.getElementById("email").value;
var salary = document.getElementById("salary").value;

// 校验数据是否为空
if (!empName || !email || !salary) {
alert("有数据为空,无法添加!");
return;
}

// 创建文本节点
var empName_text = document.createTextNode(empName);
var email_text = document.createTextNode(email);
var salary_text = document.createTextNode(salary);
var delete_text = document.createTextNode("Delete");

// 创建元素节点
var tr = document.createElement("tr");
var empName_td = document.createElement("td");
var email_td = document.createElement("td");
var salary_td = document.createElement("td");
var a_td = document.createElement("td");
var a = document.createElement("a");

// 添加内容
a.href = "javascript:;";
a.onclick = delRow;

// 添加子节点
empName_td.appendChild(empName_text);
email_td.appendChild(email_text);
salary_td.appendChild(salary_text);
a.appendChild(delete_text);
a_td.appendChild(a);
tr.appendChild(empName_td);
tr.appendChild(email_td);
tr.appendChild(salary_td);
tr.appendChild(a_td);

// 将tr添加至table中
// document.getElementById("employeeTable").appendChild(tr);
// 注意:浏览器生成的table结构会在内部套一层tbody,为了以防万一,也为了结构一致性和样式一致性,应该将其添加至tbody中
var employeeTable = document.getElementById("employeeTable");
var tbody = employeeTable.getElementsByTagName("tbody")[0];
tbody.appendChild(tr);
}

添加优化

结合createElementinnerHTML,优化修改上述添加代码逻辑

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
document.getElementById("addEmpButton").onclick = function() {
// 获取name/email/salary
var empName = document.getElementById("empName").value;
var email = document.getElementById("email").value;
var salary = document.getElementById("salary").value;

// 校验数据是否为空
if (!empName || !email || !salary) {
alert("有数据为空,无法添加!");
return;
}

// 创建元素节点
var tr = document.createElement("tr");

// 添加子节点
var empNameTd = "<td>" + empName + "</td>";
var emailTd = "<td>" + email + "</td>";
var salaryTd = "<td>" + salary + "</td>";
var aTd = "<td><a href=\"javascript:;\">Delete</a></td>";
tr.innerHTML = empNameTd + emailTd + salaryTd + aTd;

// 为a绑定单击相应函数
tr.getElementsByTagName("a")[0].onclick = delRow;

// 将tr添加至table中
// document.getElementById("employeeTable").appendChild(tr);
// 注意:浏览器生成的table结构会在内部套一层tbody,为了以防万一,也为了结构一致性和样式一致性,应该将其添加至tbody中
var employeeTable = document.getElementById("employeeTable");
var tbody = employeeTable.getElementsByTagName("tbody")[0];
tbody.appendChild(tr);
}

a的索引问题

上述中,我们为每个a都添加了单击响应函数,使用了this获取遍历中的a元素,通过this.parentNode.parentNode获取了 tr 元素,如果这里改成aList[i].parentNode.parentNode,能够拿到 tr 元素吗?

看起来好像毫无悬念,但实际上是拿不到的,这是为什么呢?

我们可以改造下 for 循环中 a 元素的单击相应函数,打印下每次拿到的 i

1
2
3
4
5
6
for (var i = 0; i < aList.length; i++) {
aList[i].onclick = function(){
alert(i);
return false;
};
}

image-20210806192603271

会发现,每次打印的结果都是3,而 aList 的长度为3最大索引是2

原因其实很简单,因为单击相应函数的执行是晚于 for 循环的执行的。也就是说,我们在点击 Delete 前,for 循环就已经执行完毕了。即当 i=2 的循环执行之后,会执行 i++,此时 i=3,这是循环条件判断 i ≮ 2,即不满足循环条件,for 循环退出。所以每次拿到的都是 for 循环执行完毕之后的 i,因此通过 aList[i] 的方式是无法取得对应的 a 元素的

总结: for 循环会在页面加载完成之后立即执行,而响应函数会在超链接被点击时才执行当响应函数执行时,for 循环早已执行完毕

10、操作内联样式

修改元素内联样式

通过JS修改元素的内联样式,语法:元素.style.样式名 = 样式值

1
2
box1.style.height = "200px";
box1.style.width = "200px";

注意:如果CSS的样式名中含有一,这种名称在JS中是不合法的,比如background-color

需要将这种样式名修改为驼峰命名法,去掉-,然后将-后的字母大写

1
2
// box1.style.background-color = "red"; // Uncaught SyntaxError: Invalid left-hand side in assignment
box1.style.backgroundColor = "red";

在 w3school 手册中,可以查看到每个样式所对应的 JS 代码

image-20210806194738937

image-20210806194643089

我们通过 style 属性设置的样式都是内联样式,而内联样式有较高的优先级,所以通过JS修改的样式往往会立即显示

但是如果在样式中写了!important,则此时样式会有最高的优先级,即使通过JS也不能覆盖该样式,此时将会导致JS修改样式失效,所以尽量不要为样式添加!important

我们给 background-color设置!important之后,通过 box1.style.backgroundColor = "red";设置的样式就“废”了

1
background-color: yellow !important;

操作内联样式

读取元素内联样式

通过 JS 读取元素的内联样式,语法:元素.style.样式名

通过style属性设置和读取的都是内联样式,无法读取样式表中的样式

1
2
3
alert(box1.style.height); // 
box1.style.height = "200px";
alert(box1.style.height); // 200px

img

别急,耐心往下看

读取元素样式

获取元素的当前显示的样式,语法:元素.currentStyle.样式名

它可以用来读取当前元素正在显示的样式,如果当前元素没有设置该样式,则获取它的默认值

1
2
3
alert(box1.currentStyle.height); // 100px
box1.style.height = "200px";
alert(box1.currentStyle.height); // 200px

不过currentstyle只有 IE 浏览器支持,其他的浏览器都不支持。我们在 IE 中测试是可行的,在 Chrome 或 Edge 中报错的:UncaughtTypeError: Cannot read property 'height' of undefined

img

不过,在其他浏览器中可以使用getComputedStyle(),这个方法来获取元素当前的样式

这个方法是window的方法,可以直接使用,需要两个参数

  • 第一个:要获取样式的元素
  • 第二个:可以传递一个伪元素,一般都传null

该方法会返回一个对象,对象中封装了当前元素对应的样式

可以通过对象.样式名来读取样式,如果获取的样式没有设置,则会获取到真实的值,而不是默认值

比如:没有设置 width,它不会获取到 auto,而是一个长度

但是该方法不支持IE8及以下的浏览器

1
2
3
4
5
var obj = getComputedStyle(box1, null);
alert(obj); // [object CSSStyleDeclaration]
alert(obj.width); // 200px
alert(obj.height); // 200px
alert(obj.backgroundColor); // rgb(2 55, 0, 0)

那么问题来了,如果想要兼容IE8及以下的浏览器,就会陷入一个两难的境地, 该怎么办呢?

img

通过currentStylegetComputedStyle()读取到的样式都是只读的,不能修改,如果要修改必须通过style属性

那么我就只能自己写个函数,来兼容所有浏览器

1
2
3
4
5
6
7
8
9
10
11
// 自定义兼容所有浏览器获取元素样式的方法
function getStyle(obj, name) {
// 判断是否有getComputedStyle方法
if (getComputedStyle) {
// 正常浏览器的方式
return getComputedStyle(obj, null)[name];
} else {
// IE的方式
return obj.currentStyle[name];
}
}

测试结果

Hbuilder内置浏览器

image-20210806204815473

Chrome

image-20210806204840431

Edge

image-20210806204741700

IE11

image-20210806204937646

IE8

image-20210806204535471

怎么 IE8 还是不行,提示“getComputedStyle”未定义

这是因为执行到 if 语句时,会先在 function 中找,找不到会在全局作用域中找,全局作用域中也找不到getComputedStyle,就会报错了

那么怎么解决这个问题呢?

我们先改造一下 function 代码,将getComputedStyle改成window.getComputedStyle

1
2
3
4
5
6
7
8
9
10
function getStyle(obj, name) {
// 判断是否有getComputedStyle方法
if (window.getComputedStyle) {
// 正常浏览器的方式
return getComputedStyle(obj, null)[name];
} else {
// IE的方式
return obj.currentStyle[name];
}
}

效果

image-20210806205500790

为什么呢?

因为变量找不到会报错,而属性找不到返回的是undefined而不会报错,这样就可以利用undefined != true的特点,执行 else 中的代码

同理,下面代码同样可以判断,只不过,会优先走currentStyle的方式,而我们希望的优先走getComputedStyle方法,所以不建议用

1
2
3
4
5
6
7
8
9
10
function getStyle(obj, name) {
// 判断是否有currentStyle属性
if (obj.currentStyle) {
// IE的方式
return obj.currentStyle[name];
} else {
// 正常浏览器的方式
return getComputedStyle(obj, null)[name];
}
}

那么上述代码有没有优化或者说简化的空间呢?当然,我们可以使用三元运算符对其进行精简

1
2
3
function getStyle(obj, name) {
return window.getComputedStyle ? getComputedStyle(obj, null)[name] : obj.currentStyle[name];
}

三元运算符更加简洁,if-else 的方式更加清晰,建议使用 if-else 的方式,不过本质上是一样的,看个人习惯

11、其他样式相关的属性

image-20210806214629582

clientWidth、clientHeight

这两个属性可以获取元素的可见宽度和高度

这些属性都是不带px的,返回都是一个数字,可以直接进行计算

会获取元素宽度和高度,包括内容区和内边距

这些属性都是只读的,不能修改(改只有一种方式,就是通过元素.style.样式 = 样式值

1
2
3
4
5
6
7
8
9
// #box1 {
// width: 100px;
// height: 100px;
// background-color: red;
// padding: 10px;
// border: 10px solid yellow;
// }
alert(box1.clientHeight); // 120
alert(box1.clientWidth); // 120

offsetWidth、offsetHeight

获取元素的整个的宽度和高度,包括内容区、内边距和边框

1
2
3
4
5
6
7
8
9
// #box1 {
// width: 100px;
// height: 100px;
// background-color: red;
// padding: 10px;
// border: 10px solid yellow;
// }
alert(box1.offsetHeight); // 140
alert(box1.offsetWidth); // 140

offsetParent

可以用来获取当前元素的定位父元素

会获取到离当前元素最近的开启了定位(只要position不是sticky)的祖先元素

如果所有的祖先元素都没有开启定位,则返回body

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// <div id="box1"></div>
alert(box1.offsetParent); // [object HTMLBodyElement]

// <div id="box2">
// <div id="box1"></div>
// </div>
alert(box1.offsetParent); // [object HTMLBodyElement]

//<div id="box3">
// <div id="box2">
// <div id="box1"></div>
// </div>
//</div>
alert(box1.offsetParent); // [object HTMLBodyElement]

//<div id="box3" style="position: relative;">
// <div id="box2" style="position: relative;">
// <div id="box1"></div>
// </div>
//</div>
alert(box1.offsetParent); // [object HTMLDivElement]
alert(box1.offsetParent.id); // box2

//<div id="box3" style="position: relative;">
// <div id="box2">
// <div id="box1"></div>
// </div>
//</div>
alert(box1.offsetParent); // [object HTMLDivElement]
alert(box1.offsetParent.id); // box3

offsetLeft、offsetTop

当前元素相对于其定位父元素的水平或垂直偏移量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//<div id="box3">
// <div id="box2">
// <div id="box1"></div>
// </div>
//</div>
alert(box1.offsetLeft); // 8 浏览器的默认样式
alert(box1.offsetTop); // 54

//<div id="box3">
// <div id="box2" style="position: relative;">
// <div id="box1"></div>
// </div>
//</div>
alert(box1.offsetLeft); // 0
alert(box1.offsetTop); // 0

image-20210806215118948

scrollHeight、scrollWidth

可以获取元素整个滚动区域的宽度和高度

1
2
3
4
5
6
7
8
9
10
11
12
13
// #box4 {
// width: 200px;
// height: 300px;
// background-color: yellow;
// overflow: auto;
// }
// #box5 {
// width: 400px;
// height: 600px;
// background-color: #bfa;
// }
alert(box4.scrollHeight); // 600
alert(box4.scrollWidth); // 400

scrollLeft、scrollTop

可以获取水平或垂直滚动条滚动的距离

1
2
3
4
5
6
7
8
9
10
11
12
13
// #box4 {
// width: 200px;
// height: 300px;
// background-color: yellow;
// overflow: auto;
// }
// #box5 {
// width: 400px;
// height: 600px;
// background-color: #bfa;
// }
alert(box4.scrollLeft); // 0/71.19999694824219/92/... 随着水平滚动条滚动而发生变化
alert(box4.scrollTop); // 0/163.1999969482422/116/... 随着垂直滚动条滚动而发生变化

看这么一个问题,打印如下值,将水平和垂直滚动条滚动到底

1
2
alert(box4.clientHeight + ", " + (box4.scrollHeight - box4.scrollTop)); // 283, 283.20001220703125
alert(box4.clientWidth + ", " + (box4.scrollWidth - box4.scrollLeft)); // 183, 183.1999969482422

PS:我这里打印的结果存在小数点,不知为何

  • 当满足scrollHeight - scrollTop == clientHeight,说明垂直滚动条滚动到底了
  • 当满足scrollWidth - scrollLeft == clientwidth,说明水平滚动条滚动到底

那么这个原理有什么用呢?

爱到底到底,管我什么事 有些网站注册时会有一个 霸王条款 用户协议,要确保用户阅读协议了,才允许注册。那问题来了,怎么确保用户阅读了协议呢?就是利用了上述原理,当滚动条拖至最底部时,就可以注册了。

那么接下来,我们就做一个 霸王条款 用户协议

练习

HTML 代码

1
2
3
4
5
6
7
8
9
10
11
12
13
<div id="outer">
<h3>亲爱的用户,欢迎注册本网站</h3>
<p id="info">
亲爱的用户,请仔细阅读以下协议,如果你不仔细阅读你就别注册
此处省略一万字。。。
</p>
<div id="checkDiv">
<input type="checkbox" name="checkInput" value="1" id="checkInput" disabled="disabled" />我已仔细阅读协议,一定遵守
</div>
<div id="submitDiv">
<input type="submit" id="submitInput" disabled="disabled" value="注册"/>
</div>
</div>

CSS 代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#outer {
width: 500px;
}

#outer,
h3,
#checkDiv,
#submitDiv,
#submitInput {
margin: 10px auto;
}

#checkDiv {
width: 250px;
}

#submitInput {
display: block;
}

#info {
height: 600px;
overflow: auto;
}

JS 代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// 为滚动条绑定事件,就是为有滚动条的元素绑定事件
var info = document.getElementById("info");
var checkInput = document.getElementById("checkInput");
var submitInput = document.getElementById("submitInput");
info.onscroll = function() {
// 当滚动条滚动到底时,启用并自动勾选协议,并启用注册按钮
if (parseInt(info.scrollHeight - info.scrollTop) == parseInt(info.clientHeight)) {
// 自动勾选协议
checkInput.disabled = false;
checkInput.checked = true;
// 启用注册按钮
submitInput.disabled = false;
}
}
// 为checkInput绑定勾选响应事件
checkInput.onclick = function(ret) {
// 如果勾选了协议,则启用注册按钮,否则禁用注册按钮
if (!checkInput.checked) {
submitInput.disabled = true;
}
else{
submitInput.disabled = false;
}
}
// 为submit绑定单击响应函数
submitInput.onclick = function(){
if(confirm("确认注册吗?")){
alert("注册成功");
}
}

效果

协议注册

事件对象

1、事件对象

<前情提要>

事件对象

  • 当事件的响应函数被触发时,浏览器每次都会将一个事件对象作为实参传递进响应函数
  • 在事件对象中封装了当前事件相关的一切信息,比如:鼠标的坐标、键盘哪个按键被按下、鼠标滚轮滚动的方向。。。

事件属性

image-20210807213954253

鼠标/键盘属性

image-20210807214712717

<练习1:当鼠标在areaDiv中移动时,在showMsg中来显示鼠标的坐标>

HTML 代码

1
2
<div id="areaDiv"></div>
<div id="showMsg"></div>

CSS 代码

1
2
3
4
5
6
7
8
9
10
11
12
#areaDiv {
border: 1px solid black;
width: 300px;
height: 50px;
margin-bottom: 10px;
}

#showMsg {
border: 1px solid black;
width: 300px;
height: 20px;
}

JS 代码

1
2
3
4
5
6
7
8
9
10
11
var areaDiv = document.getElementById("areaDiv");
var showMsg = document.getElementById("showMsg");
// 绑定鼠标移动事件
areaDiv.onmousemove = function(event){
console.log(event); // IE8:undefined
// clientX可以获取鼠标指针的水平坐标
// cilentY可以获取鼠标指针的垂直坐标
var x = event.clientX;
var y = event.clientY;
showMsg.innerHTML = "x = " + x + ", y = " + y;
}

效果

内置浏览器

image-20210807215044666

Chrome

image-20210807215134476

Edge

image-20210807215203532

IE11

image-20210807215256351

IE8

image-20210807215328802

在IE8中,响应函数被触发时,浏览器不会传递事件对象

在IE8及以下的浏览器中,是将事件对象作为window对象的属性保存的

那么按照之前学习到的思路,我们可以对其进行兼容性改造

1
2
3
4
5
6
7
8
9
10
11
var x;
var y;
if (event) {
x = event.clientX;
y = event.clientY;
}
else{
x = window.event.clientX;
y = window.event.clientY;
}
showMsg.innerHTML = "x = " + x + ", y = " + y;

IE8测试

image-20210807215801842

感觉上述代码不优雅,对上述代码进行二次改造

1
2
3
4
5
6
//if (!event) {
// event = window.event;
//}
event = event || window.event;
var x = event.clientX;
var y = event.clientY;

<练习2:div跟随鼠标移动>

1
2
3
4
5
6
7
// 兼容性写法
event = event || window.event;
var left = event.clientX;
var top = event.clientY;
// div随鼠标移动,注意style属性是有单位的
box1.style.left = (left - box1.clientWidth / 2) + "px";
box1.style.top = (top - box1.clientHeight / 2) + "px";

div随鼠标移动

但是,当我们给body设置一个较大height属性值时,会发现一个问题,就是鼠标指针与 div 之间存在一定距离

div随鼠标移动2

这是为什么呢?

  • clientXclientY用于获取鼠标在当前的可见窗口的坐标 div 的偏移量,是相对于整个页面的
  • pageXpageY可以获取鼠标相对于当前页面的坐标,但是这个两个属性在IE8中不支持,所以如果需要兼容IE8,则不要使用
1
2
var left = event.pageX;
var top = event.pageY;

再试下效果

div随鼠标移动3

貌似好了哈,那直接测试下 IE8?

image-20210807233339306

这要怎么办?

我们现在给 body 设置了一个height,红色框表示可见区域大小,蓝色框表示 body 的实际区域大小

image-20210807233458455

既然我们没办法使用pageXpageY兼容IE8,那暂时只能使用clientXclientY了,而clientXclientY是按照可见区域大小计算的,那让 div 的水平和垂直偏移量也按照可见区域大小计算不就行了吗?但是我们又暂时没办法让 div 总是参考可见区域大小的原点作为定位的原点,难道就没有办法了吗?

我们之前学习过,scrollTop表示滚动条的垂直滚动距离,而div位置原点 与鼠标指针原点的差距应该刚好是滚动条垂直滚动的距离,那么是不是可以利用这两个属性来“弥补” 这两者之间的距离差呢?

1
box1.style.top = (document.body.scrollTop + top - box1.clientHeight / 2) + "px";

div随鼠标移动4

发现还是不行,要知道我们是给 body 设置的height属性,之所以出现滚动条是因为 body 的父元素容不下 body 了,所以应该获取谁的scrollTop属性?body 的父元素,即 html

1
box1.style.top = (document.documentElement.scrollTop + top - box1.clientHeight / 2) + "px";

Chrome

div随鼠标移动6

IE8

div随鼠标移动5


在视频中,测试的结果是Chrome和火狐等浏览器获取scrollTop的对象不一致,需要做兼容

chrome认为浏览器的滚动条是body的,可以通过body.scrollTop来获取火狐等浏览器认为浏览器的滚动条是html的,

1
var st = document.body.scrollTop || document.documentElement.scrollTop;

但是不知道什么原因(浏览器对scrollTopscrollLeft都统一兼容了?毕竟视频是几年前的了),我这里并没有这个问题,所以上述问题存疑,待考究,后面以我实际代码测试结果为准


同理,当水平方向有滚动条时,也要消除水平方向上的距离差,所以综合代码如下

1
2
box1.style.left = (document.documentElement.scrollLeft + left - box1.clientWidth / 2) + "px";
box1.style.top = (document.documentElement.scrollTop + top - box1.clientHeight / 2) + "px";

我这里通过documentElement获取的scrollLeftscrollTop在 Chrome、Edge、IE11、IE8 中均正常

2、事件的冒泡(Bubble)

HTML 代码

1
2
3
4
5
6
<div id="box1">
我是div
<span id="s1">
我是span
</span>
</div>

CSS 代码

1
2
3
4
5
6
7
8
#box1{
width:200px;
height: 200px;
background-color: #99FF99;
}
#s1{
background-color: yellowgreen;
}

JS 代码

1
2
3
4
5
6
7
8
9
10
11
12
document.getElementById("s1").onclick = function(){
alert("我是span"); // 我是span 我是div 我是body 我是HTML
};
document.getElementById("box1").onclick = function(){
alert("我是div"); // 我是div 我是body 我是HTML
};
document.body.onclick = function(){
alert("我是body"); // 我是body 我是HTML
};
document.documentElement.onclick = function(){
alert("我是HTML"); // 我是HTML
};

所谓的冒泡指的就是事件的向上传导,当后代元素上的事件被触发时,其祖先元素的相同事件也会被触发

在开发中大部分情况冒泡都是有用的,如果不希望发生事件冒泡可以通过事件对象来取消冒泡

可以将事件对象的cancelBubble设置为true,即可取消冒泡

1
2
3
4
5
6
document.getElementById("s1").onclick = function(event){
// 兼容event
event = event || window.event;
alert("我是span"); // 我是span
event.cancelBubble = true;
};

3、事件的委派(Delegate)

HTML 代码

1
2
3
4
5
6
<button type="button" id="btn">Add</button>
<ul id="ulDiv">
<li><a href="javascript:;">超链接1</a></li>
<li><a href="javascript:;">超链接2</a></li>
<li><a href="javascript:;">超链接3</a></li>
</ul>

JS 代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function clickFun(){
alert("超链接");
}

window.onload = function(){
// 为每一个超链接都绑定一个单击响应函数
var aList = document.getElementsByTagName("a");
for(var i=0;i<aList.length;i++){
aList[i].onclick = clickFun;
}
var btn = document.getElementById("btn");
var ulDiv = document.getElementById("ulDiv");
btn.onclick = function(){
var li = document.createElement("li");
li.innerHTML = "<a href=\"javascript:;\">add超链接</a>";
ulDiv.appendChild(li);
};
};

这里我们为每一个超链接都绑定了一个单击响应函数,这种操作比较麻烦

而且这些操作只能为已有的超链接设置事件,而新添加的超链接必须重新绑定

我们希望,只绑定一次事件,即可应用到多个的元素上,即使元素是后添加的

我们可以尝试将其绑定给元素的共同的祖先元素

1
2
3
ulDiv.onclick = function(){
alert("事件委派超链接");
};

事件委派是指将事件统一绑定给元素的共同的祖先元素

这样当后代元素上的事件触发时,会一直冒泡到祖先元素,从而通过祖先元素的响应函数来处理事件

事件委派是利用了冒泡,通过委派可以减少事件绑定的次数,提高程序的性能

但是也有个问题,我们是给整个 ul 绑定的单击响应事件,ul 是块元素,在超链接所在行点击任何位置都会触发事件

事件委派

那怎么办呢?我们就需要再加一层判断: 如果触发事件的对象是我们期望的元素,则执行否则不执行

那怎么知道触发事件的对象是什么呢?

image-20210808143058357

1
2
3
4
5
6
7
8
ulDiv.onclick = function(event){
event = event || window.event;
// 如果触发事件的对象是我们期望的元素,则执行否则不执行
// alert(event.target); // 点击超链接外:[object HTMLLIElement]; 点击超链接:javascript:;
if(event.target.className == "link"){
alert("事件委派超链接");
}
};

但是这种写法有点问题,当其class属性有多个时,就不对了

1
2
3
<li><a href="javascript:;" class="link hello">超链接1</a></li> <!-- 失效 -->
<li><a href="javascript:;" class="link">超链接2</a></li>
<li><a href="javascript:;" class="link">超链接3</a></li>

我这里将tagName代替className作为判断条件进行判断

1
2
3
4
5
6
ulDiv.onclick = function(event){
event = event || window.event;
if(event.target.tagName == "A" || event.target.tagName == "a"){
alert("事件委派超链接");
}
};

4、事件的绑定(Bind)

on事件名

使用对象.事件 = 函数的形式绑定响应函数,它只能同时为一个元素的一个事件绑定一个响应函数

不能绑定多个,如果绑定了多个,则后边会覆盖掉前边的

1
2
3
4
5
6
7
8
9
var btn = document.getElementById("btn");
// 为btn绑定一个单击响应函数
btn.onclick = function() {
alert(1);
};
// 为btn绑定第二个响应函数
btn.onclick = function() {
alert(2); // 2
};

addEventListener()

addEventListener()通过这个方法也可以为元素绑定响应函数,参数:

  • 事件的字符串,不要on
  • 回调函数,当事件触发时该函数会被调用
  • 是否在捕获阶段触发事件,需要一个布尔值,一般都传false

使用addEventListener()可以同时为一个元素的相同事件同时绑定多个响应函数

这样当事件被触发时,响应函数将会按昭函数的绑定顺序执行

1
2
3
4
5
6
7
8
9
btn.addEventListener("click", function(){
alert(1); // 1
}, false);
btn.addEventListener("click", function(){
alert(2); // 2
}, false);
btn.addEventListener("click", function(){
alert(3); // 3
}, false);

我们直接在 IE8 中进行测试,这个方法不支持IE8及以下的浏览器

image-20210808145926568

那说了半天,IE8 需要用什么方法替代呢?

attachEvent()

attachEvent()在 IE8 中可以用来绑定事件,参数:

  • 事件的字符串,要on
  • 回调函数
1
2
3
4
5
6
7
8
9
btn.attachEvent("onclick", function(){
alert(1); // 1
});
btn.attachEvent("onclick", function(){
alert(2); // 2
});
btn.attachEvent("onclick", function(){
alert(3); // 3
});

继续测试,在 IE8 中没有报错,但是执行顺序却是相反的,而且其他浏览器中直接就不行了

image-20210808152803255

总结: 这个方法也可以同时为一个事件绑定多个处理函数,不同的是它是后绑定先执行,执行顺序和addEventListener()相反

看起来,我们还是要自己封装一个方法来兼容不同的浏览器

1
2
3
4
5
6
7
8
9
10
11
12
// 定义一个函数,用来为指定元素绑定响应函数
// 参数:
// - obj 要绑定事件的对象
// - eventStr 事件的字符串
// - callback 回调函数
function bind(obj, eventStr, callback) {
if (obj.addEventListener) {
obj.addEventListener(eventStr, callback, false);
} else {
obj.attachEvent("on" + eventStr, callback);
}
}

我们调用下只能自定义的bind函数

1
2
3
bind(btn, "click", function() {
alert(1);
});

测试下效果,发现在 IE8 和其他浏览器中均支持

image-20210808152623132

好,我们接着再看个问题

1
2
3
bind(btn, "click", function() {
alert(this); // IE8: [object window];非IE8:[object HTMLButtonElement]
});

测试发现,在 Chrome 中打印的是[object HTMLButtonElement]

image-20210808152944028

而在 IE8 中打印的却是[object window]

image-20210808153020678

addEventListener()中的this是绑定事件的对象,attachEvent()中的thiswindow,需要统一两个方法this

我们之前讲过callapply方法,this是指定的那个对象,是不是可以利用call或者apply方法对bind函数进行优化呢?

1
2
3
4
5
6
7
8
9
10
11
12
function bind(obj, eventStr, callback) {
if (obj.addEventListener) {
obj.addEventListener(eventStr, callback, false);
} else {
// this是谁由调用方式决定
// callback.call(obj)
obj.attachEvent("on" + eventStr, function(){
// 在匿名函数中调用回调函数
callback.call(obj);
});
}
}

5、事件的传播

关于事件的传播网景公司和微软公司有不同的理解

  • 微软公司认为事件应该是由内向外传播,也就是当事件触发时,应该先触发当前元素上的事件,然后再向当前元素的祖先元素上传播,也就说件应该在 冒泡阶段 执行
  • 网景公司认为事件应该是由外向内传播的,也就是当前事件触发时,应该先触发当前元素的最外层的祖先元素的事件,然后在向内传播给后代元素
  • W3C综合了两个公司的方案,将事件传播分成了三个阶段
    1. 捕获阶段:在捕获阶段时从最外层的祖先元素,向目标元素进行事件的捕获,但是默认此时不会触发事件
    2. 目标阶段:事件捕获到目标元素,捕获结束开始在目标元素上触发事件
    3. 冒泡阶段:事件从目标元素向他的祖先元素传递,依次触发祖先元素上的事件

image-20210808155725476

如果希望在捕获阶段就触发事件,可以将addEventListener()的第三个参数设置为true

一般情况下我们不会希望在捕获阶段触发事件,所以这个参数一般都是false

IE8 及以下的浏览器中没有捕获阶段

6、拖拽

拖拽的流程

  1. 当鼠标在被拖拽元素上按下时,开始拖拽 onmousedown
  2. 当鼠标移动时被拖拽元素跟随鼠标移动 onmousemove
  3. 当鼠标松开时,被拖拽元素固定在当前位置 onmouseup

image-20210808160841285

HTML 代码

1
2
<div id="box1"></div>
<div id="box2"></div>

CSS 代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#box1 {
width: 100px;
height: 100px;
background-color: red;
position: absolute;
}
#box2 {
width: 100px;
height: 100px;
background-color: yellow;
position: absolute;
left: 300px;
top: 300px;
}

JS 代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
var box1 = document.getElementById("box1");
// 1. 当鼠标在被拖拽元素上按下时,开始拖拽 `onmousedown`
box1.onmousedown = function(event) {
event = event || window.event;
var boxLeft = event.clientX - box1.offsetLeft; // 鼠标水平坐标 - 元素水平偏移量 = 鼠标原点和元素原点水平距离
var boxTop = event.clientY - box1.offsetTop; // 鼠标垂直坐标 - 元素垂直偏移量 = 鼠标原点和元素原点垂直距离
// 2. 当鼠标移动时被拖拽元素跟随鼠标移动 `onmousemove`
document.onmousemove = function(event) {
event = event || window.event;
box1.style.left = event.clientX - boxLeft + "px";
box1.style.top = event.clientY - boxTop + "px";
};
// 3. 当鼠标松开时,被拖拽元素固定在当前位置 `onmouseup`
document.onmouseup = function(event) {
// 取消document的onmousemove事件
document.onmousemove = null;
// 取消document的onmouseup事件
document.onmouseup = null;
};
};

效果

拖拽

当我们拖拽一个网页中的内容时,浏览器会默认去搜索引擎中搜索内容,此时会导致拖拽功能的异常,这个是浏览器提供的默认行为

拖拽1

如果不希望发生这个行为,则可以通过return false来取消默认行为

拖拽2

但是这招对 IE8 不起作用

拖拽3

那有什么方法可以兼容 IE8 呢?我们先接着往下看

setCapture()

1
2
3
4
5
6
7
8
9
10
11
12
var btn1 = document.getElementById("btn1");
var btn2 = document.getElementById("btn2");

btn1.onclick = function() {
alert(1);
}
btn2.onclick = function() {
alert(2);
}
// 设置btn1对鼠标按下相关的事件进行捕获
// 当调用一个元素的setCapture()方法以后,这个元素将会把下一次所有的鼠标按下相关的事件捕获到自身上
btn1.setCapture();

我们点击 btn2 按钮,发现只有刷新后的第一次点击的提示为1,再次点击就变成了2

image-20210808182709374

我们可以利用setCapture()方法对 IE8 浏览器的默认行为进行限制吗?当拖拽元素时捕获事件,取消拖拽时释放对事件的捕获

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
var box1 = document.getElementById("box1");
// 1. 当鼠标在被拖拽元素上按下时,开始拖拽 `onmousedown`
box1.onmousedown = function(event) {
// 设置box1捕获所有鼠标按下的事件
// 只有IE支持,但是在火狐中调用时不会报错,而如果使用Chrome调用,会报错
box1.setCapture && box1.setCapture();

event = event || window.event;
var boxLeft = event.clientX - box1.offsetLeft; // 鼠标水平坐标 - 元素水平偏移量 = 鼠标原点和元素原点水平距离
var boxTop = event.clientY - box1.offsetTop; // 鼠标垂直坐标 - 元素垂直偏移量 = 鼠标原点和元素原点垂直距离
// 2. 当鼠标移动时被拖拽元素跟随鼠标移动 `onmousemove`
document.onmousemove = function(event) {
event = event || window.event;
box1.style.left = event.clientX - boxLeft + "px";
box1.style.top = event.clientY - boxTop + "px";
};
// 3. 当鼠标松开时,被拖拽元素固定在当前位置 `onmouseup`
document.onmouseup = function(event) {
// 取消document的onmousemove事件
document.onmousemove = null;
// 取消document的onmouseup事件
document.onmouseup = null;
// 当鼠标松开时,取消对事件的捕获
box1.releaseCapture && box1.releaseCapture();
};
// 取消默认行为
return false;
};

测试在 IE8 中的效果

拖拽4

如果我想拖动 div2呢?这个时候我们需要封装一个函数,方便我们直接传参调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 拖拽方法封装成一个函数
function draw(obj){
obj.onmousedown = function(event) {
obj.setCapture && obj.setCapture();
event = event || window.event;
var boxLeft = event.clientX - obj.offsetLeft;
var boxTop = event.clientY - obj.offsetTop;
document.onmousemove = function(event) {
event = event || window.event;
obj.style.left = event.clientX - boxLeft + "px";
obj.style.top = event.clientY - boxTop + "px";
};
document.onmouseup = function(event) {
document.onmousemove = null;
document.onmouseup = null;
obj.releaseCapture && obj.releaseCapture();
};
return false;
};
}

HTML 代码

1
2
3
<div id="box1"></div>
<div id="box2"></div>
<img src="img/an.jpg" id="img" style="width: 320px;height: 320px;position: absolute;left:200px;top:400px;"/>

JS 代码调用函数

1
2
3
4
5
6
var box1 = document.getElementById("box1");
var box2 = document.getElementById("box2");
var img = document.getElementById("img");
draw(box1);
draw(box2);
draw(img);

效果

Chrome

拖拽5

IE8

拖拽6

滚轮事件与键盘事件

1、滚轮事件

onmousewheel、DOMMouseScroll

onmousewheel:鼠标滚轮滚动的事件,会在滚轮滚动时触发,但是火狐不支持该属性

DOMMouseScroll:在火狐中使用DOMMouseScroll来绑定滚动事件,注意该事件需要通过addEventListener()函数来绑定

event.wheelDelta、event.detail

event.wheelDelta:可以获取鼠标滚轮滚动的方向:向上滚(120),向下滚(-120),这个值我们不看大小,只看正负

event.detailwheelDelta这个属性火狐中不支持,在火狐中使用event.detail来获取滚动的方向:向上滚(-3),向下滚(3)

return false、event.preventDefault()

当滚轮滚动时,如果浏览器有滚动条,滚动条会随之滚动,这是浏览器的默认行为

如果不希望发生,则可以使用return false来取消默认行为

使用addEventListener()方法绑定响应函数,取消默认行为时不能使用return false,需要使用event来取消默认行为

但是 IE8 不支持event.preventDefault()这个玩意,如果直接调用会报错

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
window.onload = function() {
var box1 = document.getElementById("box1");
box1.onmousewheel = function(event) {
event = event || window.event;
// alert(event.wheelDelta); // IE/内置:120/-120;Chrome/Edge:150/-150;Firefox:undefined/undefined
// alert(event.detail); // IE/内置/Chrome/Edge:0/0;Firefox:-3/3;

// 当鼠标滚轮向下滚动时,box1变长
// 当鼠标滚轮向上滚动时,box1变短
if (event.wheelDelta > 0 || event.detail < 0) {
box1.style.height = box1.clientHeight - 10 + "px";
} else {
if (box1.clientHeight - 10 > 0) {
box1.style.height = box1.clientHeight + 10 + "px";
}
}

// 使用addEventListener()方法绑定响应函数,取消默认行为时不能使用return false,需要使用event来取消默认行为
// 但是IE8不支持event.preventDefault()这个玩意,如果直接调用会报错
event.preventDefault && event.preventDefault();

// 当滚轮滚动时,如果浏览器有滚动条,滚动条会随之滚动
// 这是浏览器的默认行为,如果不希望发生,则可以取消默认行为
return false;
};
// 兼容addEventListener
bind(box1, "DOMMouseScroll", box1.onmousewheel);
}

function bind(obj, eventStr, callback) {
if (obj.addEventListener) {
obj.addEventListener(eventStr, callback, false);
} else {
// this是谁由调用方式决定
// callback.call(obj)
obj.attachEvent("on" + eventStr, function(){
// 在匿名函数中调用回调函数
callback.call(obj);
});
}
}

效果

滚动事件

2、键盘事件

onkeydown、onkeyup

image-20210809225130743

onkeydown按键被按下

  • 如果一直按着某个按键不松手,则事件会一直触发
  • 连续触发时,第一次和第二次之间会间隔稍微长一点,其他的会非常的快,这种设计是为了防止误操作的发生

onkeyup按键被松开

键盘事件一般都会绑定给一些可以获取到焦点的对象或者是document

键盘事件属性

image-20210809225738645

image-20210809225838699

可以通过keyCode来获取按键的编码,通过它可以判断哪个按键被按下

除了keyCode,事件对象中还提供了几个属性altKeyctrlKeyshiftKey

这个三个用来判断altctrlshift是否被按下,如果按下则返回true,否则返回false

<练习:键盘移动div>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// 定义速度
var speed = 10;
var box1 = document.getElementById("box1");
// 绑定键盘响应事件
document.onkeydown = function(event) {
event = event || window.event;
// 按ctrl加速
speed = event.ctrlKey ? 30 : 10;
// console.log(event.keyCode); // 左:37;上:38;右:39;下:40
switch (event.keyCode) {
// 左移
case 37:
box1.style.left = box1.offsetLeft - speed + "px";
break;
// 上移
case 38:
box1.style.top = box1.offsetTop - speed + "px";
break;
// 右移
case 39:
box1.style.left = box1.offsetLeft + speed + "px";
break;
// 下移
case 40:
box1.style.top = box1.offsetTop + speed + "px";
break;
default:
break;
}
return false;
}

效果

键盘移动div

BOM

1、BOM

BOM:浏览器对象模型

BOM 可以使我们通过 JS 来操作浏览器

在 BOM 中为我们提供了一组对象,用来完成对浏览器的操作 BOM 对象

Window

代表的是整个 浏览器的窗口,同时 window 也是网页中的全局对象

代表的当前 浏览器的信息,通过该对象可以来识别不同的浏览器

Location

代表当前 浏览器的地址栏信息,通过 Location 可以获取地址栏信息,或者操作浏览器跳转页面

History

代表 浏览器的历史记录,可以通过该对象来操作浏览器的历史记录由于隐私原因

该对象不能获取到具体的历史记录,只能操作 浏览器向前或向后翻页,而且该操作只在当次访问时有效

Screen

代表用户的 屏幕的信息,通过该对象可以获取到用户的显示器的相关的信息

image-20210811200758569

这些 BOM 对象在浏览器中都是作为 window 对象的属性保存的,可以通过 window 对象来使用,也可以直接使用

image-20210811201317053

1
2
3
4
5
console.log(window); // [object Window]
console.log(navigator); // [object Navigator]
console.log(location); // [object Object]
console.log(history); // [object History]
console.log(screen); // [object Screen]

2、Navigator

image-20210811213218347

由于历史原因,Navigator对象中的大部分属性都已经不能帮助我们识别浏览器了

1
console.log(navigator.appName); //Chrome/Firefox/Edge/IE11:Netscape;IE10及以下:Microsoft Internet Explorer

那既然如此,我们要怎么判断不同的浏览器呢?

一般我们只会使用userAgent来判断浏览器的信息,userAgent是一个字符串

这个字符串中包含有用来描述浏览器信息的内容,不同的浏览器会有不同的userAgent

1
2
3
4
5
6
7
8
9
console.log(navigator.userAgent);
// Chrome: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.85 Safari/537.36
// Firefox:Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:90.0) Gecko/20100101 Firefox/90.0
// Edge: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.131 Safari/537.36 Edg/92.0.902.67
// IE11: Mozilla/5.0 (Windows NT 10.0; WOW64; Trident/7.0; .NET4.0C; .NET4.0E; .NET CLR 2.0.50727; .NET CLR 3.0.30729; .NET CLR 3.5.30729; rv:11.0) like Gecko
// IE10: Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 10.0; WOW64; Trident/7.0; .NET4.0C; .NET4.0E; .NET CLR 2.0.50727; .NET CLR 3.0.30729; .NET CLR 3.5.30729)
// IE9: Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 10.0; WOW64; Trident/7.0; .NET4.0C; .NET4.0E; .NET CLR 2.0.50727; .NET CLR 3.0.30729; .NET CLR 3.5.30729)
// IE8: Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 10.0; WOW64; Trident/7.0; .NET4.0C; .NET4.0E; .NET CLR 2.0.50727; .NET CLR 3.0.30729; .NET CLR 3.5.30729)
// IE7/IE5:Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 10.0; WOW64; Trident/7.0; .NET4.0C; .NET4.0E; .NET CLR 2.0.50727; .NET CLR 3.0.30729; .NET CLR 3.5.30729)

我们可以根据userAgent中特有的标识符来判断是哪个浏览器

1
2
3
4
5
6
7
8
9
10
var ua = navigator.userAgent;
if (/edg/i.test(ua)) {
alert("Edge浏览器");
} else if (/firefox/i.test(ua)) {
alert("火狐浏览器");
} else if (/chrome/i.test(ua)) {
alert("谷歌浏览器");
} else if (/msie/i.test(ua)) {
alert("IE浏览器");
}

在 IE11 中已经将微软和 IE 相关的标识都已经去除了,所以我们基本已经不能通过userAgent来识别一个浏览器是否是 IE 了

那么,我们要怎么判断统一是否是 IE 呢?

还是要 找特殊 ,我们根据之前知识,知道currentStyleattchEvent是 IE 所特有的

除此之外,还有个ActiveXObject也是 IE 中所特有的,我们可以根据这个来做判断

1
2
3
4
5
6
// 利用`ActiveXObject`是 IE 中特有的属性,以及通过`window.属性 == undefined`特点来判断是否是 IE
if(window.ActiveXObject){
alert("IE浏览器");
} else{
alert("非IE浏览器");
}

我们直接在 IE 中进行测试

image-20210811204751369

不是说ActiveXObject也是 IE 中所特有的吗?怎么不行呢?

我们在 IE11 中打印下window.ActiveXObject是否等于true

1
2
// 利用两次使用`!!`将任意值转换成bool值
console.log(!!window.ActiveXObject); // false

What? 这?

别急,我们换种方式,利用in来判断 window 中是否包含某个属性

1
console.log("ActiveXObject" in window); // true

我们来完善下对 IE11 的判断逻辑

1
2
3
4
5
6
7
8
9
10
11
12
var ua = navigator.userAgent;
if (/edg/i.test(ua)) {
alert("Edge浏览器");
} else if (/firefox/i.test(ua)) {
alert("火狐浏览器");
} else if (/chrome/i.test(ua)) {
alert("谷歌浏览器");
} else if (/msie/i.test(ua)) {
alert("IE浏览器");
} else if ("ActiveXObject" in window) {
alert("IE11浏览器");
}

3、Location

image-20210811205755795

length

length属性,可以获取到当次访问的链接数量

1
2
3
4
alert(history.length);
// 访问History页面:1
// 访问Test02页面并跳转至History页面:2
// 访问Test01页面并跳转至History页面:3

back()

可以用来回退到上一个页面,作用和浏览器的回退按钮一样

1
history.back();

history-back

forward()

可以跳转下一个页面,作用和浏览器的前进按钮一样

1
history.forward();

history-forwar

go()

可以用来跳转到指定的页面,它需要一个整数作为参数

  • 1:表示向前跳转一个页面,相当于forward()
  • 2:表示向前跳转两个页面
  • -1:表示向后跳转一个页面,相当于back()
  • -2:表示向后跳转两个页面
1
history.go(2);

history-go

1
history.go(-2);

history-go

4、Location

如果直接打印location,则可以获取到地址栏的信息(当前页面的完整路径)

1
alert(location); // http://127.0.0.1:8848/Demo/17-04-Location.html

如果直接将location属性修改为一个完整的路径,或相对路径则我们页面会自动跳转到该路径,并且会生成相应的历史记录

1
location = "http://www.baidu.com";

location

1
location = "17-03-History.html";

location2

其他属性方法

image-20210811213202215

assign()

用来跳转到其他的页面,作用和直接修改location一样

会生成历史记录, 能使用回退按钮回退

1
location.assign("http://www.baidu.com");

location-assign

replace()

可以使用一个新的页面替换当前页面,调用完毕也会跳转页面

不会生成历史记录,不能使用回退按钮回退

1
location.replace("17-03-History.html");

location-replace

reload()

用于重新加载当前页面,作用和刷新按钮(F5)一样

1
location.reload();

location-reload

如果在方法中传递一个true,作为参数,则会强制清空缓存刷新页面(Ctrl + F5)

1
location.reload(true);

location-reload(true)

定时调用与延时调用

1、定时调用

JS 的程序的执行速度是非常非常快的如果希望一段程序,可以每间隔一段时间执行一次,可以使用定时调用

setInterval()

定时调用,可以将一个函数,每隔一段时间执行一次

参数:

  1. 回调函数,该函数会每隔一段时间被调用一次
  2. 每次调用间隔的时间,单位是毫秒

返回值:返回一个Number类型的数据,这个数字用来作为定时器的唯一标识

1
2
3
4
5
var num = 1;
info = document.getElementById("info");
setInterval(function(){
info.innerHTML = num++;
}, 1000);

setInterval

1
2
3
setInterval(function(){
info.innerHTML = num++;
}, 100);

setInterval-2

clearInterval()

可以用来关闭一个定时器,方法中需要一个定时器的标识作为参数,这样将关闭标识对应的定时器

1
2
3
4
5
6
var timer = setInterval(function(){
info.innerHTML = num++;
if(num > 100){
clearInterval(timer);
}
}, 10);

clearInterval

<练习1:定时图片切换>

HTML 代码

1
2
3
<img src="img/1.jpg" id="img" /><br>
<button type="button" id="btnStart">开始</button>
<button type="button" id="btnEnd">结束</button>

JS 代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
var btnStart = document.getElementById("btnStart");
var btnEnd = document.getElementById("btnEnd");
var img = document.getElementById("img");
// 设置轮播图片数组
var imgArr = ["img/1.jpg", "img/2.jpg", "img/3.jpg", "img/4.jpg", "img/5.jpg"];
// 设置轮播图片索引
var index = 0;
// 为开始按钮绑定单击响应函数
var timer;
btnStart.onclick = function() {
// 清除上一个定时器
clearInterval(timer);
// 设置定时器
timer = setInterval(function() {
// 切换图片
img.src = imgArr[index++];
// 判断索引是否超过最大索引
index %= imgArr.length;
}, 500);
};
// 为结束按钮绑定单击响应函数
btnEnd.onclick = function() {
clearInterval(timer);
};

效果

定时切换图片

注意点一:循环切换图片

当索引超过最大索引时,需要将索引重置,以达到轮播图片之目的

1
2
3
4
// if(index >= imgArr.length){
// index = 0;
// }
index %= imgArr.length;

注意点二:不点击开始,而直接点击结束

clearInterval()可以接收任意参数

  • 如果参数是一个有效的定时器的标识,则停止对应的定时器
  • 如果参数不是一个有效的标识,则什么也不做

即使没有点开始,timer 为 undefined 也不会报错,可以放心大胆的去使用

注意点三:多次点击开始按钮导致切换速度过快问题

目前,我们每点击一次按钮,就会开启一个定时器,点击多次就会开启多个定时器

这就导致图片的切换速度过快,并且我们只能关闭最后一次开启的定时器

在开启定时器之前,需要将当前元素上的其他定时器关闭

<练习2:div移动优化>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
// 定义速度
var speed = 20;
// 定义方向
var direct = 0;
// 定时器只控制方向
setInterval(function(){
switch (direct) {
case 37:
box1.style.left = box1.offsetLeft - speed + "px";
break;
case 38:
box1.style.top = box1.offsetTop - speed + "px";
break;
case 39:
box1.style.left = box1.offsetLeft + speed + "px";
break;
case 40:
box1.style.top = box1.offsetTop + speed + "px";
break;
default:
break;
}
}, 50);
// 键盘按下控制速度
var box1 = document.getElementById("box1");
document.onkeydown = function(event) {
event = event || window.event;
// 修改速度
speed = event.ctrlKey ? 50 : 20;
// 捕获方向
direct = event.keyCode;
return false;
};
// 键盘松开清空速度和方向
document.onkeyup = function(event){
direct = 0;
}

效果

div移动优化

优化思路

  • 定时器控制方向,键盘按下控制速度和捕获方向,键盘松开清空速度和方向

这就好比一辆汽车,速度就像汽车的油门,定时器就像汽车的方向盘,而键盘就像汽车的离合和档位

油门一直在踩着,发动机就一直匀速运转,就能保证速度一直存在,启动或转向就不会出现卡顿的现象

当键盘按下时,就是松离合换挡位;当键盘松开时,就是踩离合

不过,跟现实世界不同的是,JS 的世界没有惯性,所以只要松离合,div 就不会再移动了

2、延时调用

setTimeout()、clearTimeout()

延时调用,延时调用一个函数不马上执行,而是隔一段时间以后在执行,而且只会执行一次

延时调用和定时调用的区别:定时调用会执行多次,而延时调用只会执行一次

延时调用和定时调用实际上是可以互相代替的,在开发中可以根据自己需要去选择

1
2
3
4
5
6
7
8
var num = 1;
var timer = setInterval(function(){
console.log(num++); // 1 2 3 4 5 ...
}, 1000);
var timer = setTimeout(function(){
console.log(num++); // 1
}, 1000);
clearTimeout(timer);

3、定时器的应用(一)

<练习:点击按钮div移动>

HTML 代码

1
2
3
4
5
<button type="button" id="btn1">点击按钮box1向右移动</button>
<button type="button" id="btn2">点击按钮box1向左移动</button>
<br><br>
<div id="box1"></div>
<div id="line"></div>

CSS 代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
* {
margin: 0;
padding: 0;
}

#box1 {
width: 100px;
height: 100px;
background-color: red;
/* 开启定位 */
position: absolute;
left: 0;
top: 0;
}

#line {
width: 0;
height: 1000px;
border: 1px solid black;
position: absolute;
top: 0;
left: 800px;
left: 0;
top: 200px;
}

JS 代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
// 自定义兼容所有浏览器获取元素样式的方法
function getStyle(obj, name) {
return window.getComputedStyle ? getComputedStyle(obj, null)[name] : obj.currentStyle[name];
}
window.onload = function() {
var timer;
var speed = 19;
var btn1 = document.getElementById("btn1");
var btn2 = document.getElementById("btn2");
var box1 = document.getElementById("box1");
btn1.onclick = function() {
// 清空上一个定时器
clearInterval(timer);
// 设置定时器
timer = setInterval(function() {
// 获取旧值
var oldValue = parseInt(getStyle(box1, "left"));
// 获取新值
var newValue = oldValue + speed;
// 当达到一定值时停下来
newValue = newValue > 800 ? 800 : newValue;
// 赋新值
box1.style.left = newValue + "px";
// 当值不再变化时,清空定时器
if(newValue == 800){
clearInterval(timer);
}
}, 50);
};
btn2.onclick = function() {
// 清空上一个定时器
clearInterval(timer);
// 设置定时器
timer = setInterval(function() {
// 获取旧值
var oldValue = parseInt(getStyle(box1, "left"));
// 获取新值
var newValue = oldValue - speed;
// 当达到一定值时停下来
newValue = newValue < 0 ? 0 : newValue;
// 赋新值
box1.style.left = newValue + "px";
// 当值不再变化时,清空定时器
if(newValue == 0){
clearInterval(timer);
}
}, 50);
};
}

点击按钮div移动

优化1:封装移动方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 封装移动方法
// obj:要执行动画的对象
// target:执行动画的目标位置
// speed:移动的速度(正数向右移动,负数向左移动)
var timer;
function move(obj, target, speed) {
clearInterval(timer);
timer = setInterval(function() {
var oldValue = parseInt(getStyle(obj, "left"));
var newValue = oldValue + speed;
newValue = speed > 0 ? (newValue > target ? target : newValue) : (newValue < target ? target : newValue);
obj.style.left = newValue + "px";
if (newValue == target) {
clearInterval(timer);
}
}, 50);
}

优化2:智能判断方向

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function move(obj, target, speed) {
clearInterval(timer);
var current = parseInt(getStyle(obj, "left"));
// 智能判断方向
speed = target < current ? -speed : speed;
timer = setInterval(function() {
var oldValue = parseInt(getStyle(obj, "left"));
var newValue = oldValue + speed;
newValue = speed > 0 ? (newValue > target ? target : newValue) : (newValue < target ? target : newValue);
obj.style.left = newValue + "px";
if (newValue == target) {
clearInterval(timer);
}
}, 50);
}

优化3:消除多个div影响

点击按钮div移动2

目前我们的定时器的标识由全局变量 timer 保存,所有的执行正在执行的定时器都在这个变量中保存

那么我们就不能定义全局的了,而是需要向执行动画的对象中添加一个 timer 属性,用来保存它自己的定时器的标识

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function move(obj, target, speed) {
clearInterval(obj.timer);
var current = parseInt(getStyle(obj, "left"));
// 智能判断速度的方向
speed = target < current ? -speed : speed;
obj.timer = setInterval(function() {
var oldValue = parseInt(getStyle(obj, "left"));
var newValue = oldValue + speed;
newValue = speed > 0 ? (newValue > target ? target : newValue) : (newValue < target ? target :
newValue);
obj.style.left = newValue + "px";
if (newValue == target) {
clearInterval(obj.timer);
}
}, 50);
}

点击按钮div移动3

这样,执行动画的对象之间就不会再互相产生影响了

优化4:支持多属性

只需要将left相关的属性改为变量传入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// obj:要执行动画的对象
// attr:要执行动画的样式
// target:执行动画的目标位置
// speed:移动的速度(正数向右移动,负数向左移动)
function move(obj, attr, target, speed) {
clearInterval(obj.timer);
var current = parseInt(getStyle(obj, attr));
// 智能判断速度的方向
speed = target < current ? -speed : speed;
obj.timer = setInterval(function() {
var oldValue = parseInt(getStyle(obj, attr));
var newValue = oldValue + speed;
newValue = speed > 0 ? (newValue > target ? target : newValue) : (newValue < target ? target :
newValue);
obj.style[attr] = newValue + "px";
if (newValue == target) {
clearInterval(obj.timer);
}
}, 50);
}

调用修改后的函数

1
2
3
4
5
6
7
8
9
10
11
12
btn1.onclick = function() {
move(box1, "left", 800, speed);
};
btn2.onclick = function() {
move(box1, "left", 0, speed);
};
btn3.onclick = function() {
move(box2, "top", 500, speed);
};
btn4.onclick = function() {
move(box3, "height", 500, speed);
};

点击按钮div移动4

优化5:添加回调函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// obj:要执行动画的对象
// attr:要执行动画的样式
// target:执行动画的目标位置
// speed:移动的速度(正数向右移动,负数向左移动)
// callback:回调函数
function move(obj, attr, target, speed, callback) {
clearInterval(obj.timer);
var current = parseInt(getStyle(obj, attr));
// 智能判断速度的方向
speed = target < current ? -speed : speed;
obj.timer = setInterval(function() {
var oldValue = parseInt(getStyle(obj, attr));
var newValue = oldValue + speed;
newValue = speed > 0 ? (newValue > target ? target : newValue) : (newValue < target ? target :
newValue);
obj.style[attr] = newValue + "px";
if (newValue == target) {
clearInterval(obj.timer);
callback && callback(); // 即使不传回调函数也不会报错
}
}, 50);
}

调用回调函数

1
2
3
4
5
6
7
8
9
10
11
btn4.onclick = function() {
move(box3, "height", 500, speed, function(){
move(box3, "width", 500, speed, function(){
move(box3, "height", 100, speed, function(){
move(box3, "width", 100, speed, function(){

});
});
});
});
};

点击按钮div移动5

优化6:封装JS文件

新建 js 文件夹,新建 tools.js 文件,复制 move 相关方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// 自定义兼容所有浏览器获取元素样式的方法
function getStyle(obj, name) {
return window.getComputedStyle ? getComputedStyle(obj, null)[name] : obj.currentStyle[name];
}
// 封装移动方法
// obj:要执行动画的对象
// attr:要执行动画的样式
// target:执行动画的目标位置
// speed:移动的速度
// callback:回调函数
function move(obj, attr, target, speed, callback) {
clearInterval(obj.timer);
var current = parseInt(getStyle(obj, attr));
speed = target < current ? -speed : speed;
obj.timer = setInterval(function() {
var oldValue = parseInt(getStyle(obj, attr));
var newValue = oldValue + speed;
newValue = speed > 0 ? (newValue > target ? target : newValue) : (newValue < target ? target :
newValue);
obj.style[attr] = newValue + "px";
if (newValue == target) {
clearInterval(obj.timer);
callback && callback();
}
}, 50);
}

最后再引入 js 文件,大功告成

1
<script src="js/tools.js" type="text/javascript" charset="utf-8"></script>

4、定时器应用(二)

<练习:轮播图>

HTML 代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<div id="outer">
<ul id="pic-list">
<li><img src="img/1.jpg" /></li>
<li><img src="img/2.jpg" /></li>
<li><img src="img/3.jpg" /></li>
<li><img src="img/4.jpg" /></li>
<li><img src="img/5.jpg" /></li>
<li><img src="img/1.jpg" /></li>
</ul>
<ul id="nav-list">
<li><a href="javascript:;"></a></li>
<li><a href="javascript:;"></a></li>
<li><a href="javascript:;"></a></li>
<li><a href="javascript:;"></a></li>
<li><a href="javascript:;"></a></li>
</ul>
</div>

CSS 代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
/* 去除浏览器默认样式 */
* {
margin: 0;
padding: 0;
}

ul {
list-style: none;
}

a{
text-decoration: none;
}

/* 总体布局 */
#outer {
width: 500px;
height: 332px;
margin: 100px auto;
box-shadow: 10px 10px 5px rgba(0, 0, 0, .2);
position: relative;
overflow: hidden;
}

/* 轮播图片 */
#pic-list {
width: 2550px;
position: absolute;
left: 0;
top: 0;
/* 添加过渡效果 */
/* transition: left 0.3s; */
}

#pic-list li {
float: left;
margin-right: 10px;
}

/* 轮播按钮 */
#nav-list {
position: absolute;
left: 187.5px;
bottom: 10px;
}

#nav-list li{
float: left;
width: 15px;
height: 15px;
background-color: red;
opacity: 0.5;
margin: 0 5px;
}

#nav-list a:hover{
background-color: black;
}

#nav-list li a{
display: block;
height: 100%;
line-height: 100%;
}

JS 代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
window.onload = function() {
var outer = document.getElementById("outer");
var picList = document.getElementById("pic-list");
var imgArr = document.getElementsByTagName("img");
var navList = document.getElementById("nav-list");
var aArr = document.getElementsByTagName("a");
var index = 0;

// 动态调整picList的宽度,以自适应图片的数量变化
picList.style.width = (outer.clientWidth + 10) * imgArr.length + "px";
// 动态调整navList的水平偏移量
navList.style.left = (outer.clientWidth - 25 * aArr.length) / 2 + "px";
// 设置第一个轮播按钮悬浮颜色
aArr[index].style.backgroundColor = "black";

// 点击按钮切换图片
for (var i = 0; i < aArr.length; i++) {
aArr[i].index = i;
aArr[i].onclick = function() {
// 设置index
index = this.index;
// 清空定时器
clearInterval(timer);
move(picList, "left", -(outer.clientWidth + 10) * index, 100, function() {
// 开启定时器
autoSwitch();
});
setColor();
};
}

autoSwitch();

// 自动切换图片
var timer;
function autoSwitch() {
timer = setInterval(function() {
index++;
index %= imgArr.length;
move(picList, "left", -(outer.clientWidth + 10) * index, 100, function() {
if (index >= imgArr.length - 1) {
picList.style.left = "0px";
}
setColor();
});
}, 3000);
}

// 设置轮播按钮悬浮颜色
function setColor() {
// 重置所有轮播按钮颜色:由于修改的是内联样式,优先级较高,会把css样式覆盖,导致悬浮效果失效
// 那么这里不使用内联样式,将其置为空,这样就会找css样式
for (var i = 0; i < aArr.length; i++) {
aArr[i].style.backgroundColor = "";
}
index %= aArr.length;
aArr[index].style.backgroundColor = "black";
}
};

轮播图

5、类的操作

修改class属性

HTML 代码

1
2
3
<button type="button" id="btn1">点击按钮修改box1样式</button>
<br><br>
<div id="box1" class="b1"></div>

CSS 代码

1
2
3
4
5
.b1{
width: 100px;
height: 100px;
background-color: red;
}

JS 代码

1
2
3
box1.style.width = "200px";
box1.style.height = "200px";
box1.style.backgroundColor = "yellow";

通过style属性来修改元素的样式,每修改一个样式,浏览器就需要重新渲染一次页面

这样执行的性能是比较差的,而且这种形式当我们要修改多个样式时,也不太方便

那怎么办呢?

我们可以先事先定义好一个 class 属性,里面写好我们需要变化的样式

1
2
3
4
5
.b2{
width: 200px;
height: 200px;
background-color: yellow;
}

然后在 JS 中修改className属性即可

1
box1.className = "b2";

效果是一样的

修改class属性

我们可以通过修改元素的class属性来间接的修改样式

这样一来,我们只需要修改一次,即可同时修改多个样式

浏览器只需要重新渲染页面一次,性能比较好,并且这种方式,可以使表现和行为进一步的分离

添加class属性

我们可以在此样式基础之上,定义一个函数,用来向一个元素中添加指定的 class 属性值

1
2
3
4
5
6
// 参数:
// obj 要添加class属性的元素
// cn 要添加的class值
function addClass(obj, cn){
obj.className += " " + cn;
}
1
2
3
4
5
//.b3{
// position: absolute;
// left: 100px;
//}
addClass(box1, "b3");

添加class属性

但是也存在一个问题,虽然从效果上来看没有什么不同,但多次点击后会重复添加相同的 class 属性,而这个操作是多余的

类的操作3

我们就需要在写一个函数来判断是否已经存在 class 属性

1
2
3
4
5
6
7
8
9
10
function hasClass(obj, cn) {
// return obj.className.indexOf(cn) != -1;
var reg = new RegExp("\\b"+cn+"\\b");
return reg.test(obj.className);
}
function addClass(obj, cn) {
if (!hasClass(obj, cn)) {
obj.className += " " + cn;
}
}

类的操作4

删除class属性

删除一个元素中的指定的 class 属性

1
2
3
4
function removeClass(obj, cn) {
var reg = new RegExp("\\b" + cn + "\\b");
obj.className = obj.className.replace(reg, "");
}

删除class属性

切换class属性

1
2
3
4
5
6
7
8
9
10
// toggleClass可以用来切换一个类
// 如果元素中具有该类,则删除
// 如果元素中没有该类,则添加
function toggleClass(obj, cn) {
if (hasClass(obj, cn)) {
removeClass(obj, cn);
} else {
addClass(obj, cn);
}
}

 切换class属性

<练习:二级菜单>

HTML 代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<div id="my_menu" class="sdmenu">
<div>
<span class="menuSpan">在线工具</span>
<a href="#">图像优化</a>
<a href="#">收藏夹图标生成器</a>
<a href="#">邮件</a>
<a href="#">htaccess密码</a>
<a href="#">梯度图像</a>
<a href="#">按钮生成器</a>
</div>
<div class="collapsed">
<span class="menuSpan">支持我们</span>
<a href="#">推荐我们</a>
<a href="#">链接我们</a>
<a href="#">网络资源</a>
</div>
<div class="collapsed">
<span class="menuSpan">合作伙伴</span>
<a href="#">JavaScript工具包</a>
<a href="#">CSS驱动</a>
<a href="#">CodingForums</a>
<a href="#">CSS例子</a>
</div>
<div class="collapsed">
<span class="menuSpan">测试电流</span>
<a href="#">Current or not</a>
<a href="#">Current or not</a>
<a href="#">Current or not</a>
<a href="#">Current or not</a>
</div>
</div>

CSS 代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
@charset "utf-8";

/* sdmenu */

div.sdmenu {
width: 150px;
margin: 0 auto;
font-family: Arial, sans-serif;
font-size: 12px;
padding-bottom: 10px;
background: url(bottom.gif) no-repeat right bottom;
color: #fff;
}

div.sdmenu div {
background: url(title.gif) repeat-x;
overflow: hidden;
}

div.sdmenu div:first-child {
background: url(toptitle.gif) no-repeat;
}

div.sdmenu div.collapsed {
height: 25px;
}

div.sdmenu div span {
display: block;
height: 15px;
line-height: 15px;
overflow: hidden;
padding: 5px 25px;
font-weight: bold;
color: white;
background: url(expanded.gif) no-repeat 10px center;
cursor: pointer;
border-bottom: 1px solid #ddd;
}

div.sdmenu div.collapsed span {
background-image: url(collapsed.gif);
}

div.sdmenu div a {
padding: 5px 10px;
background: #eee;
display: block;
border-bottom: 1px solid #ddd;
color: #066;
}

div.sdmenu div a.current {
background: #ccc;
}

div.sdmenu div a:hover {
background: #066 url(linkarrow.gif) no-repeat right center;
color: #fff;
text-decoration: none;
}

JS 代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 为兼容IE8,用querySelectorAll
var menuSpan = document.querySelectorAll(".menuSpan");
var myMenu = document.getElementById("my_menu");
var thisNode;
// 定义一个变量,来保存当前打开的菜单
var openNode = menuSpan[0].parentNode;
for (var i = 0; i < menuSpan.length; i++) {
// 一级菜单绑定单击响应函数
menuSpan[i].onclick = function() {
thisNode = this.parentNode;
// 切换collapsed的class属性
toggleClass(thisNode, "collapsed");
// 打开菜单以后,应该关闭之前打开的菜单
if (openNode != thisNode && !hasClass(openNode, "collapsed")) {
// 为了可以统一处理动画过渡效果,我们希望在这将addClass改为toggleClass
// addClass(openNode, "collapsed");
// 此处toggleClass()不需要有移除的功能
toggleClass(openNode, "collapsed");
}
openNode = thisNode;
};
}

二级菜单1

添加动画的过渡效果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
var beginHeight;
var endHeight;
for (var i = 0; i < menuSpan.length; i++) {
menuSpan[i].onclick = function() {
thisNode = this.parentNode;
// 切换前高度
beginHeight = thisNode.offsetHeight;
// 切换
toggleClass(thisNode, "collapsed");
// 切换后高度
endHeight = thisNode.offsetHeight;
// 动画执行前内联高度
thisNode.style.height = beginHeight + "px";
// 设置动画效果
move(thisNode, "height", endHeight, 30, function(){

});
if (openNode != thisNode && !hasClass(openNode, "collapsed")) {
toggleClass(openNode, "collapsed");
}
openNode = thisNode;
};
}

二级菜单2

因为我们执行动画前添加了一个内联高度,而内联属性的优先级是最高的

当添加collapsed的 class 属性后不会起作用,因此同时需要在动画执行完毕后去除内联样式

1
2
3
4
move(thisNode, "height", endHeight, 30, function(){
// 动画执行后内联高度
thisNode.style.height = "";
});

二级菜单3

我们只对展开添加了动画效果,折叠时并没有添加动画

因为添加动画的逻辑是一致的,所以这里我们可以封装一个函数,用来执行带有动画效果的折叠和展开动作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 带有动画效果的折叠和展开动作
function toggleMenu(obj) {
// 切换前高度
beginHeight = obj.offsetHeight;
// 切换
toggleClass(obj, "collapsed");
// 切换后高度
endHeight = obj.offsetHeight;
// 动画执行前内联高度
obj.style.height = beginHeight + "px";
// 设置动画效果
move(obj, "height", endHeight, 30, function() {
// 动画执行后内联高度
obj.style.height = "";
});
}

调用 toggleMenu 函数

1
2
3
4
5
6
7
8
9
10
11
12
for (var i = 0; i < menuSpan.length; i++) {
menuSpan[i].onclick = function() {
thisNode = this.parentNode;
// 切换
toggleMenu(thisNode);
// 关闭其他div
if (openNode != thisNode && !hasClass(openNode, "collapsed")) {
toggleMenu(openNode);
}
openNode = thisNode;
};
}

二级菜单4

JSON

JS 中的对象只有 JS 自己认识,其他的语言都不认识

JSON就是一个特殊格式的字符串,这个字符串可以被任意的语言所识别,并且可以转换为任意语言中的对象,JSON在开发中主要用来数据的交互

JSON简介

image-20210815161757491

JavaScript Object Notation,JS 对象表示法

JSON 和 JS 对象的格式一样,只不过 JSON 字符串中的属性名必须加双引号,其他的和JS语法一致

JSON分类

  • 对象{}
  • 数组[]
1
2
3
4
5
6
7
8
9
10
var obj = {
"name": "孙悟空",
"age": 1000,
"gender": "男"
};
console.log(typeof obj); // object
var jsonObjStr = '{"name": "孙悟空","age": 1000,"gender": "男"}';
console.log(typeof jsonObjStr); // string
var jsonArrStr = '[1,2,3,"hello", true]';
console.log(typeof jsonArrStr); // string

JSON中允许的值

  • 字符串
  • 数值
  • 布尔值
  • null
  • 对象
  • 数组
1
2
3
4
// json对象可以包含json数组
var obj1 = '{"arr":[1,2,3]}';
// json数组可以包含json对象
var obj2 = '[{"name": "孙悟空","age": 1000,"gender": "男"},{"name": "孙悟空","age": 1000,"gender": "男"}]';

JSON和JS间转换

在 JS 中,为我们提供了一个工具类,就叫JSON

这个对象可以帮助我们将一个JSON转换为 JS 对象,也可以将一个 JS 对象转换JSON

JSON.parse()

可以将JSON字符串转换为 JS 中的对象

需要一个JSON字符串作为参数,会将该字符串转换为 JS 对象并返回

1
2
3
4
5
6
7
8
9
10
11
12
13
var jsonObj = JSON.parse(jsonObjStr);
console.log(typeof jsonObj); // object
console.log(jsonObj); // { name: "孙悟空", age: 1000, gender: "男" }
console.log(jsonObj.name); // 孙悟空
console.log(jsonObj.age); // 1000
console.log(jsonObj.gender); // 男

var jsonArr = JSON.parse(jsonArrStr);
console.log(typeof jsonArr); // object
console.log(jsonArr); // (5) [ 1, 2, 3, "hello", true ]
console.log(jsonArr[0]); // 1
console.log(jsonArr[3]); // hello
console.log(jsonArr[4]); // true

JSON.stringify()

可以将一个 JS 对象转换为JSON字符串

需要一个 JS 对象作为参数,会返回一个JSON字符串

1
2
3
4
5
6
7
8
var obj2 = {
name: "猪八戒",
age: 2000,
gender: "男"
};
var obj2JSONStr = JSON.stringify(obj2);
console.log(typeof obj2JSONStr); // string
console.log(obj2JSONStr); // {"name":"猪八戒","age":2000,"gender":"男"}

JSON对象在 IE7 及以下的浏览器中不支持,所以在这些浏览器中调用时会报错

image-20210815155616771

eval()

这个函数可以用来执行一段字符串形式的 JS 代码,并将执行结果返回

1
2
var str = 'alert("hello")';
eval(str);

image-20210815160227547

如果使用eval()执行的字符串中含有{},它会将{}当成是代码块

1
2
var jsonObjStr = '{"name": "孙悟空","age": 1000,"gender": "男"}';
eval(jsonObjStr);

image-20210815160250908

如果不希望将其当成代码块解析,则需要在字符串前后各加一个()

1
2
3
var jsonObjStr = '{"name": "孙悟空","age": 1000,"gender": "男"}';
var result = eval("(" + jsonObjStr + ")");
console.log(result); // {age: 1000, gender: "男", name: "孙悟空"}

eval()这个函数的功能很强大,可以直接执行一个字符串中的 JS 代码

但是在开发中尽量不要使用,首先它的执行性能比较差,然后它还具有安全隐患

img

兼容IE7

如果需要兼容 IE7 及以下的JSON操作,则可以通过引入一个外部的 JS 文件来处理

1
<script src="js/json2.js" type="text/javascript" charset="utf-8"></script>

然后在 IE7 浏览器中调用JSON相关方法就不会报错了

1
console.log(JSON.parse(jsonObjStr)); // {age: 1000, gender: "男", name: "孙悟空"}

一开始介绍JSON时,说JSON 字符串中的属性名必须加双引号

如果我就是不加呢?

1
2
var jsonObjStr = '{name: "孙悟空","age": 1000,"gender": "男"}';
console.log(JSON.parse(jsonObjStr));

那就是一堆错误等着你了

image-20210815161357814

image-20210815161624439

image-20210815161719543