JS基础

JS基础

JS基本语法,数据类型和类型转换

基本语法

JS编写位置

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

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

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

1
<a href="javascript: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文件中</strong>,然后通过script`标签引入

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

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

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

JS注释

多行注释:

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

单行注释:

1
// 单行注释

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

注意点

  1. JS中严格区分大小写

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

    如果不写分号,浏览器会自动添加,但是会消耗一些系统资源

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

字面量与变量

#字面量

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

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

#变量

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

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

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

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);

标识符

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

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

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

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

关键字

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中含有的内容都可以作为标识符

数据类型和类型转换

数据类型

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

基本数据类型 String 字符串
Number 数值
Boolean 布尔值
Null 空值
Undefined 未定义
引用类型 Object 对象:任意的对象
Array 数组:一种特殊的对象(可以执行)
Function 函数:一种特殊的对象(数值下标,内部数据是有序的)

数据类型的判断方法:

  • typeof(返回数据类型的字符串):

    • 可以判断: undefined/数字/字符串/布尔值/function
    • 不能判断: null和object ,array不能相互判断,返回的都是object
    • 使用typeof(null)返回'object'的原因:csdn链接
  • instanceof:

    • 如果B函数的显式原型对象在A对象的原型链上, 返回true, 否则返回false,详见[js函数高级]

    • 判断三个对象的是否对应具体类型

      1
      2
      3
      funcArg instanceof Function //true
      objArg instanceof Object //true
      arrArg instanceof Array //true
  • ===(数值和类型都相等,undefined和null只有一个值):

    • 可以判断:undefined,null

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

1
2
3
NaN是一个特殊的数字,表示'Not A Number'
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

初始值赋值为null,表明赋值对象为对象;回收对象时候将对象赋值为null.

Undefined

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

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

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

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

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

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

类型转换

其他数据类型转换为String

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

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

1
2
3
4
5
// Number转换为String
var a1 = 123;
var b1 = a1.toString();
console.log(typeof a1); // number
console.log(typeof b1); // string

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

1
2
3
4
5
6
7
// Null转换为String
var a3 = null;
var b3 = a3.toString(); // UncaughtTypeError: Cannot read property 'toString' of null

// Undefined转换为String
var a4 = undefined;
var b4 = a4.toString(); // UncaughtTypeError: Cannot read property 'toString' of undefined

方式二:调用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

其他数据类型转换为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

其他数据类型转换为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

运算符

概念

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

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

算数运算符

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

算数运算符

  • + 可以对两个值进行加法运算,并将结果返回
    • 如果对两个字符串进行加法运算,则会将两个字符串拼接为一个字符串,并返回
    • 任何的值和字符串做加法运算,都会先转换为字符串,然后再和字符串做拼串的操作
1
2
3
4
5
6
7
8
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 a8 = 123 + undefined;
console.log(a8); // NaN
  • -可以对两个值进行减法运算(效果同上)
  • * 可以对两个值进行乘法运算
  • / 可以对两个值进行除法运算
  • % 可以对两个值进行取模运算

可以利用+""将Number转为String,利用-0*1/1将String转为Number

1
2
3
4
5
6
var b= 33;
var x = b+'';
document.write(typeof(x))//string
var w = '123';
var k = w-0;
document.write(typeof(k))//number

一元运算符

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

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

对于非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

自增和自减

自增++

通过自增可以使变量在自身的基础上增加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

逻辑运算符

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

赋值运算符

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

  • += 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

关系运算符

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

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

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

非数值的情况

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

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

相等运算符

== 相等

相等运算符用来比较两个值是否相等,如果相等会返回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

条件运算符

条件运算符也叫三元运算符 条件表达式 ? 语句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");

运算符优先级

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

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

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

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

Unicode编码表

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

流程控制

流程控制

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

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

语句的分类:

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

条件判断语句

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

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

if 语句

语法一

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

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

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

语法二

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

语法三

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

switch 语句

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

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

while 语句

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

while循环语法:

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

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

  • 如果值为true,则执行循环体,循环体执行完毕以后,继续对表达式进行判断
  • 如果为true,则继续执行循环体,以此类推
  • 如果值为false,则终止循环

do-while 语句

do...while循环语法:

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

do...while语句在执行时,会先执行循环体,循环体执行完毕以后,在对

for 语句

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

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

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

for循环的语法:

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

for循环的执行流程:

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

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

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

break和continue

不能在if语句中使用breakcontinue

break

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

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

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

continue

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

对象和函数

Object 对象

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

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

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

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

对象的分类

内建对象

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

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

  • Object、Function、Array、String、Number、Boolean、Date、RegExp
  • Error(EvalError、RangeError、ReferenceError、SyntaxError、TypeError、URIError)

宿主对象

由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

DOM

BOM

BOM

DOM 和 BOM 的关系

JavaScript的Dom和Bom

自定义对象

使用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]); // 你好

属性值

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

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

基本数据类型 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 obj2 = obj;
obj.name = 1;
console.log(obj.name); // 1
console.log(obj2.name); // 1

内存中的存储

比较

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

img

对象字面量

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

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

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

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

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":"孙悟空"}

方法

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

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

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

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

枚举对象中的属性

使用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:'liu',
age: 25,
gender: 'male',
ambition: 'leader',
bro: {
take: 3
}

}

for (var key in obj){
document.write(key + '=' + obj[key])
document.write(' ')

}
// name=liu age=25 gender=male ambition=leader bro=[object Object]

函数

函数的简介

使用函数声明来创建一个函数,函数也是一个对象

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

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

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

函数的参数

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

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

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

  • 调用函数时解析器不会检查实参的类型。所以要注意,是否有可能会接收到非法的参数,如果有可能则需要对参数进行类型的检查
  • 调用函数时,解析器也不会检查实参的数量,多余实参不会被赋值。如果实参的数量少于形参的数量,则没有对应实参的形参将是undefined
1
2
3
4
5
6
7
function sum(a,b,c){
document.write(a+b+c)
}

sum(1,2,3);//6

sum(3,4);//Nan

函数的返回值

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

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

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

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

return后可以跟任意类型的值

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

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

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

break、continue、return对比

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

立即执行函数

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

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

1
2
3
(function(){
document.write('立即执行函数')
})();

作用域

全局作用域

直接编写在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

变量的声明提前

使用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";

函数的声明提前

使用函数声明形式创建的函数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...");
}

函数作用域

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

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

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

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

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

在函数中要访问全局变量可以使用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

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
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这个类型,就导致我们无法区分出多种不同类型的对象

构造函数

创建一个构造函数,专门用来创建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个方法都是一模一样的

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

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

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

原型对象

__proto__ 是对象自身的属性,表示该对象实际继承自哪一个原型对象。
prototype 是构造函数的属性,决定了从该构造函数创建的所有实例对象将共享哪些方法和属性

原型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

同类的公共区域

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

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

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
3
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

原型对象

那么,按照这个原理,我们在原型的原型中使用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

如果我们希望在输出对象时不输出[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]

数组

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

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}

创建数组的方式

使用字面量创建数组

语法:[]

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

数组元素类型

数组中存储的可以是数字,字符串,布尔值,null,undefined,对象,数组

还可以是函数

1
2
3
4
5
6
7
8
9
10
fun = function(){
return('函数返回值')
}

arr = [function(){document.write("hello world")},function(){alert('this is truth')},fun()]

message = arr[2]

document.write(message) //函数返回值

数组的方法

方法 描述
concat() 连接两个或更多的数组,并返回结果。
join() 把数组的所有元素放入一个字符串。元素通过指定的分隔符进行分隔。
pop() 删除并返回数组的最后一个元素
push() 向数组的末尾添加一个或更多元素,并返回新的长度。
reverse() 颠倒数组中元素的顺序。
shift() 删除并返回数组的第一个元素
slice() 从某个已有的数组返回选定的元素
sort() 对数组的元素进行排序
splice() 删除元素,并向数组添加新元素。
toSource() 返回该对象的源代码。
toString() 把数组转换为字符串,并返回结果。
toLocaleString() 把数组转换为本地数组,并返回结果。
unshift() 向数组的开头添加一个或更多元素,并返回新的长度。
valueOf() 返回数对象的对象的原始值

添加和删除

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
orArr = [1,2,3,4];
// push()该方法可以向数组的末尾添加一个或多个元素,并返回数组的新的长度

message = orArr.push(5);
document.write(message);//5
document.write(orArr);//1,2,3,4,5
document.write('----');

// pop()方法可以删除数组的最后一个元素,并将被删除的元素作为返回值返回
message2 = orArr.pop();
document.write(message2);//5(被删除的元素为5)
document.write(orArr);//1,2,3,4
document.write('----');

// unshift()向数组开头添加一个或多个元素,并返回新的数组长度向前边插入元素以后,其他的元素索引会依次调整
message3 = orArr.unshift(0);
document.write(message3);//5(添加元素后的数组长度为5)
document.write(orArr);//0,1,2,3,4
document.write('----');

// shift()可以删除数组的第一个元素,并将被删除的元素作为返回值返回
message4 = orArr.shift();
document.write(message4);//0(被删除的元素为0)
document.write(orArr);//1,2,3,4
document.write('----');
操作 添加 删除
末尾操作 push:末尾添加 pop:末尾删除
开头操作 unshift:开头添加 shift:开头删除

切片操作

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
// slice()从某个已有的数组返回选定的元素,可以用来从数组提取指定元素

// 该方法不会改变元素数组,而是将截取到的元素封装到一个新数组中返回参数:
// 截取开始的位置的索引,包含开始索引
// 截取结束的位置的索引,不包含结束索引

message5 = orArr.slice(0,2);
document.write(message5);//1,2
document.write(orArr);//1,2,3,4
document.write('----');

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

// 使用splice()会影响到原数组,会将指定元素从原数组中删除,并将被删除的元素作为返回值返回
// 参数:
// 第一个,表示开始位置的索引
// 第二个,表示删除的数量

message6 = orArr.splice(0,2);
document.write(message6);//1,2
document.write(orArr);//3,4

// 第三个及以后,可以传递一些新的元素,这些元素将会自动插入到开始位置索引前边
orArr.splice(0,0,1,2);
// document.write(orArr);//1,2,3,4

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

合并,转为string,排序

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
// concat()可以连接两个或多个数组,并将新的数组返回
// 该方法不会对原数组产生影响
message7 = orArr.concat(0,2);
document.write(message7);//1,2,3,4,0,2
document.write(orArr);//1,2,3,4
document.write('----');

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

// 该方法不会对原数组产生影响,而是将转换后的字符串作为结果返回
// 在join()中可以指定一个字符串作为参数,这个字符串将会成为数组中元素的连接符
// 如果不指定连接符,则默认使用,作为连接符
var message8 = orArr.join('!');
document.write(message8);//1!2!3!4
document.write(typeof(message8));//string
document.write(typeof(orArr));//object
document.write('----');


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

// 该方法会直接修改原数组



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

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

即使对于纯数字的数组,使用sort()排序时,也会按照Unicode编码来排序,所以对数字进行排序时,可能会得到错误的结果

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

sort()

  • 如果需要升序排列,则返回a - b
  • 如果需要降序排列,则返回b - a
1
2
3
4
5
6
7
8
9
10
11
arr = ['2', '44', '9', '8', '2', '0'];
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

数组遍历

普通for循环

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

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

forEach方法

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

兼容性

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

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

使用

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

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

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

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

参数

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

  • 第一个参数,就是当前正在遍历的元素
  • 第二个参数,就是当前正在遍历的元素的索引
  • 第三个参数,就是正在遍历的数组
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
arr.forEach(function(value, index, obj){
console.log("value = " + value);
console.log("index = " + index);
console.log("obj = " + obj);
});

例子:
var arr = [1,2,3,4]

arr.forEach(function(value, index, obj){
document.write("value = " + value);
document.write("index = " + index);
document.write("obj = " + obj);
});


/*value = 1 index = 0 obj = 1,2,3,4
value = 2 index = 1 obj = 1,2,3,4
value = 3 index = 2 obj = 1,2,3,4
value = 4 index = 3 obj = 1,2,3,4*/

call apply

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是指定的那个对象

arguments

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

  • 函数的上下文对象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();

Data Math

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方法

好的,以下是整理后的生成MD表格:

方法 描述
Date() 返回当日的日期和时间。
getDate() 从 Date 对象返回一个月中的某一天(1 ~ 31)。
getDay() 从 Date 对象返回一星期中的某一天(0 ~ 6)。
getMonth() 从 Date 对象返回月份(0 ~ 11)。
getFullYear() 从 Date 对象以四位数字返回年份。
getYear() 请使用 getFullYear() 方法代替。
getHours() 返回 Date 对象的小时(0 ~ 23)。
getMinutes() 返回 Date 对象的分钟(0 ~ 59)。
getSeconds() 返回 Date 对象的秒数(0 ~ 59)。
getMilliseconds() 返回 Date 对象的毫秒数(0 ~ 999)。
getTime() 返回 1970 年 1 月 1 日至今的毫秒数。
getTimezoneOffset() 返回本地时间与格林威治标准时间(GMT)的分钟差。
getUTCDate() 根据世界时从 Date 对象返回月中的某一天(1 ~ 31)。
getUTCDay() 根据世界时从 Date 对象返回周中的某一天(0 ~ 6)。
getUTCMonth() 根据世界时从 Date 对象返回月份(0 ~ 11)。
getUTCFullYear() 根据世界时从 Date 对象返回四位的年份。
getUTCHours() 根据世界时返回 Date 对象的小时(0 ~ 23)。
getUTCMinutes() 根据世界时返回 Date 对象的分钟(0 ~ 59)。
getUTCSeconds() 根据世界时返回 Date 对象的秒数(0 ~ 59)。
getUTCMilliseconds() 根据世界时返回 Date 对象的毫秒数(0 ~ 999)。
parse() 返回 1970 年 1 月 1 日午夜到指定日期(字符串)的毫秒数。
parse() 返回 1970 年 1 月 1 日午夜到指定日期(字符串)的毫秒数。

Math

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

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

属性:

属性 描述
Math.E 返回算术常量 e,即自然对数的底数(约等于2.718)。
Math.LN2 返回 2 的自然对数(约等于0.693)。
Math.LN10 返回 10 的自然对数(约等于2.302)。
Math.LOG2E 返回以 2 为底的 e 的对数(约等于 1.414)。
Math.LOG10E 返回以 10 为底的 e 的对数(约等于0.434)。
Math.PI 返回圆周率(约等于3.14159)。
Math.SQRT1_2 返回 2 的平方根的倒数(约等于 0.707)。
Math.SQRT2 返回 2 的平方根(约等于 1.414)。

方法

好的,以下是整理后的生成MD表格:

方法 描述
Math.abs(x) 返回数的绝对值。
Math.acos(x) 返回数的反余弦值。
Math.asin(x) 返回数的反正弦值。
Math.atan(x) 以介于 -PI/2 与 PI/2 弧度之间的数值返回 x 的反正切值。
Math.atan2(y, x) 返回从 x 轴到点 (y, x) 的角度(介于 -PI/2 与 PI/2 弧度之间)。
Math.ceil(x) 对数进行上舍入。
Math.cos(x) 返回数的余弦。
Math.exp(x) 返回 e 的指数。
Math.floor(x) 对数进行下舍入。
Math.log(x) 返回数的自然对数(底为 e)。
Math.max(x, y, z, …) 返回 x 和 y 中的最大值。
Math.min(x, y, z, …) 返回 x 和 y 中的最小值。
Math.pow(x, y) 返回 x 的 y 次幂。
Math.random() 返回 0 到 1 之间的随机数。
Math.round(x) 把数四舍五入为最接近的整数。
Math.sin(x) 返回数的正弦。
Math.sqrt(x) 返回数的平方根。
Math.tan(x) 返回角的正切。
Math.toSource() 返回该对象的源代码。
Math.valueOf() 返回 Math 对象的原始值。

包装类和字符串

三大包装类

基本数据类型: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

字符串方法

字符串在底层是以字符数组的形式保存的:["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.

正则表达式

正则对象

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

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

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

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

正则方法

正则表达式的方法: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

正则语法

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

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

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

使用|表示或者的意思

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

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

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

[a-z]任意小写字母

[A-Z]任意大写字母

[A-z]任意字母

[0-9]任意数字

[^ ]除了:

1
2
3
4
5
reg = /[^a]/; // 除了a以外的字符
result = reg.test("a");
console.log(result); // false
result = reg.test("b");
console.log(result); // 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 的字符串。
?=n 匹配任何其后紧接指定字符 n 的字符串。
?^n 匹配任何其后没有紧接指定字符 n 的字符串。

元字符

以下是将图片中的内容转换为Markdown格式的表格:

元字符 描述
. 查找单个字符,除了换行和行结束符。
\w 查找单字字符。
\W 查找非单字字符。
\d 查找数字。
\D 查找非数字字符。
\s 查找空白字符。
\S 查找非空白字符。
\b 匹配单词边界。
\B 匹配非单词边界。
\0 查找 NUL 字符。
\n 查找换行符。
\f 查找换页符。
\r 查找回车符。
\t 查找制表符。
\v 查找垂直制表符。
\xxx 查找以八进制数xxx 规定的字符。
\xdd 查找以十六进制数 dd规定的字符。
\uxxxx 查找以十六进制数xxxx规定的 Unicode 字符。

和方法的结合

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
// split和正则表达式结合
var reg = /[0-9]/;
var str = '让3我们4荡起5双桨';
result = str.split(reg);
// 将字符串按照其中数字分组
document.write(result);//让,我们,荡起,双桨


// search和正则表达式
var reg = /[A-z]/;
var str = '让3a我e荡起fef双桨';
result = str.search(reg);
//搜索字符串中字母的位置
document.write(result);//2


// match和正则表达式
var reg = /[A-z]/g;
var str = '让3a我e荡起fef双桨';
result = str.match(reg);
//提取字符串中字母,g使用全局,提取所有的字母
document.write(result);//a,e,f,e,f(返回的是数组)

// replace和正则表达式
var reg = /[A-z]/g;
var str = '让3a我e荡起fef双桨';
result = str.replace(reg,'@');
//使用@替换字符串中字母
document.write(result);//让3@我@荡起@@@双桨

DOM操作

DOM模型

DOM树

节点

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

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

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

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

文档节点(Document)

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

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

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

元素节点(Element)

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

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

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

文本节点(Text)

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

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

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

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

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

属性节点(Attr)

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

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

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

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

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

事件

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

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

事件

文档的加载

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

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对象已经加载完毕了

DOM查询

获取元素节点

通过document对象调用

  • 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
    });

获取元素节点的子节点

通过具体的元素节点调用

  • 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
    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 () {

  • children属性可以获取当前元素的所有子元素

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    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
    });

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代表页面中所有的元素

document.all代表页面中所有的元素

1
2
3
4
5
6
7
8
9
10
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("*");

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

DOM增删改

document.createElement()

可以用于创建一个元素节点对象,它需要一个标签名作为参数,将会根据该标签名创建元素节点对象,并将创建好的对象作为返回值返回

document.createTextNode()

可以用来创建一个文本节点对象,它需要一个文本内容作为参数,将会根据该内容创建文本节点,并将新的节点返回

appendChild()

向一个父节点中添加一个新的子节点,用法:父节点.appendChild(子节点);

insertBefore()

可以在指定的子节点前插入新的子节点,语法:父节点.insertBefore(新节点, 旧节点);

replaceChild()

可以使用指定的子节点替换已有的子节点,语法:父节点.replaceChild(新节点, 旧节点);

removeChild()

可以删除一个子节点,语法:父节点.removeChild(子节点);子节点.parentNode.removeChild(子节点);

操作内联样式

修改元素内联样式

通过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"

通过 style 属性设置的样式都是内联样式,而内联样式有较高的优先级,所以通过JS修改的样式往往会立即显示

但是如果在样式中写了!important,则此时样式会有最高的优先级,即使通过JS也不能覆盖该样式,此时将会导致JS修改样式失效,所以尽量不要为样式添加!important

读取元素内联样式

通过 JS 读取元素的内联样式,语法:元素.style.样式名

通过style属性设置和读取的都是内联样式,无法读取样式表中的样式

读取元素样式

获取元素的当前显示的样式,语法:元素.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

不过,在其他浏览器中可以使用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(255, 0,

通过currentStylegetComputedStyle()读取到的样式都是只读的,不能修改,如果要修改必须通过style属性

那么我就只能自己写个函数,来兼容所有浏览器

1
2
3
4
5
6
7
8
9
10
11
// 自定义兼容所有浏览器获取元素样式的方法
function getStyle(obj, name) {
// 判断是否有getComputedStyle方法
if (window.getComputedStyle) {
// 正常浏览器的方式
return getComputedStyle(obj, null)[name];
} else {
// IE的方式
return obj.currentStyle[name];
}
}

其他样式相关的属性

clientwidth、clientHeight

这两个属性可以获取元素的可见宽度和高度

这些属性都是不带px的,返回都是一个数字,可以直接进行计算

会获取元素宽度和高度,包括内容区和内边距

这些属性都是只读的,不能修改(改只有一种方式,就是通过元素.style.样式 = 样式值

offsetwidth、offsetHeight

获取元素的整个的宽度和高度,包括内容区、内边距和边框

offsetParent

可以用来获取当前元素的定位父元素

会获取到离当前元素最近的开启了定位(只要position不是sticky)的祖先元素

如果所有的祖先元素都没有开启定位,则返回body

offsetLeft、offsetTop

当前元素相对于其定位父元素的水平或垂直偏移量

scrollHeight、scrollWidth

可以获取元素整个滚动区域的宽度和高度

事件

  • 当事件的响应函数被触发时,浏览器每次都会将一个事件对象作为实参传递进响应函数
  • 在事件对象中封装了当前事件相关的一切信息,比如:鼠标的坐标、键盘哪个按键被按下、鼠标滚轮滚动的方向。。。

练习:鼠标坐标显示

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;
}

效果

内置浏览器

效果

事件的冒泡(Bubble)

所谓的冒泡指的就是事件的向上传导,当后代元素上的事件被触发时,其祖先元素的相同事件也会被触发

在开发中大部分情况冒泡都是有用的,如果不希望发生事件冒泡可以通过事件对象来取消冒泡

可以将事件对象的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;
};

事件的委派(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);
};
};

为每一个超链接都绑定了一个单击响应函数,这种操作比较麻烦

而且这些操作只能为已有的超链接设置事件,而新添加的超链接必须重新绑定

我们希望,只绑定一次事件,即可应用到多个的元素上,即使元素是后添加的

我们可以尝试将其绑定给元素的共同的祖先元素

这样当后代元素上的事件触发时,会一直冒泡到祖先元素,从而通过祖先元素的响应函数来处理事件

事件委派是利用了冒泡,通过委派可以减少事件绑定的次数,提高程序的性能

但是也有个问题,给整个 ul 绑定的单击响应事件,ul 是块元素,在超链接所在行点击任何位置都会触发事件

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("事件委派超链接");
}
}

这里将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("事件委派超链接");
}
};

事件的绑定(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 需要用什么方法替代呢?

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 中没有报错,但是执行顺序却是相反的,而且其他浏览器中直接就不行了

封装一个方法来兼容不同的浏览器

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);
}
}
1
addEventListener()`中的`this`是绑定事件的对象,`attachEvent()`中的`this`是`window`,需要统一两个方法`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);
});
}
}

事件的传播

关于事件的传播网景公司和微软公司有不同的理解

  • 微软公司认为事件应该是由内向外传播,也就是当事件触发时,应该先触发当前元素上的事件,然后再向当前元素的祖先元素上传播,也就说件应该在 冒泡阶段 执行
  • 网景公司认为事件应该是由外向内传播的,也就是当前事件触发时,应该先触发当前元素的最外层的祖先元素的事件,然后在向内传播给后代元素
  • W3C综合了两个公司的方案,将事件传播分成了三个阶段
    1. 捕获阶段:在捕获阶段时从最外层的祖先元素,向目标元素进行事件的捕获,但是默认此时不会触发事件
    2. 目标阶段:事件捕获到目标元素,捕获结束开始在目标元素上触发事件
    3. 冒泡阶段:事件从目标元素向他的祖先元素传递,依次触发祖先元素上的事件

image-20210808155725476

如果希望在捕获阶段就触发事件,可以将addEventListener()的第三个参数设置为true

一般情况下我们不会希望在捕获阶段触发事件,所以这个参数一般都是false

IE8 及以下的浏览器中没有捕获阶段

拖拽

拖拽的流程

  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;
};
};

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

键盘和滚动事件

滚轮事件

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);
});
}
}

效果

滚动事件

键盘事件

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操作

BOM

Window

代表的是整个 浏览器的窗口,同时 window 也是网页中的全局对象

代表的当前 浏览器的信息,通过该对象可以来识别不同的浏览器

Location

代表当前 浏览器的地址栏信息,通过 Location 可以获取地址栏信息,或者操作浏览器跳转页面

History

代表 浏览器的历史记录,可以通过该对象来操作浏览器的历史记录由于隐私原因

该对象不能获取到具体的历史记录,只能操作 浏览器向前或向后翻页,而且该操作只在当次访问时有效

Screen

代表用户的 屏幕的信息,通过该对象可以获取到用户的显示器的相关的信息

由于历史原因,Navigator对象中的大部分属性都已经不能帮助我们识别浏览器了

在 IE11 中已经将微软和 IE 相关的标识都已经去除了,所以我们基本已经不能通过userAgent来识别一个浏览器是否是 IE 了

还有个ActiveXObject也是 IE 中所特有的,我们可以根据这个来做判断

1
2
3
4
5
6
// 利用`ActiveXObject`是 IE 中特有的属性,以及通过`window.属性 == undefined`特点来判断是否是 IE
if(window.ActiveXObject){
alert("IE浏览器");
} else{
alert("非IE浏览器");
}

ActiveXObject在ie11中是特殊对象,转为bool值后返回false,使用新方法判断:

1
console.log("ActiveXObject" in window); // true

浏览器信息判断:

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浏览器");
}

History

length

length属性,可以获取到当次访问的链接数量

1
2
3
4
alert(history.length);
// 访问History页面:1
// 访问Test02页面并跳转至History页面:2
// 访问Test01页面并跳转至History页面:3

back()

可以用来回退到上一个页面,作用和浏览器的回退按钮一样

1
history.back();

forward()

可以跳转下一个页面,作用和浏览器的前进按钮一样

1
history.forward();

go()

可以用来跳转到指定的页面,它需要一个整数作为参数

  • 1:表示向前跳转一个页面,相当于forward()
  • 2:表示向前跳转两个页面
  • -1:表示向后跳转一个页面,相当于back()
  • -2:表示向后跳转两个页面

Location

如果直接打印location,则可以获取到地址栏的信息(当前页面的完整路径)

1
alert(location); // http://127.0.0.1:8848/Demo/17-04-Location.html

如果直接将location属性修改为一个完整的路径,或相对路径则我们页面会自动跳转到该路径,并且会生成相应的历史记录

1
location = "http://www.baidu.com";

assign()

用来跳转到其他的页面,作用和直接修改location一样

会生成历史记录, 能使用回退按钮回退

1
location.assign("http://www.baidu.com");

replace()

可以使用一个新的页面替换当前页面,调用完毕也会跳转页面

不会生成历史记录,不能使用回退按钮回退

1
location.replace("17-03-History.html");

reload()

用于重新加载当前页面,作用和刷新按钮(F5)一样

1
location.reload();

如果在方法中传递一个true,作为参数,则会强制清空缓存刷新页面(Ctrl + F5)

定时器的应用

定时调用

JS 的程序的执行速度是非常非常快的如果希望一段程序,可以每间隔一段时间执行一次,可以使用定时调用

setInterval()

定时调用,可以将一个函数,每隔一段时间执行一次

参数:

  1. 回调函数,该函数会每隔一段时间被调用一次
  2. 每次调用间隔的时间,单位是毫秒

返回值:返回一个Number类型的数据,这个数字用来作为定时器的唯一标识

1
2
3
4
5
var num = 1;
info = document.getElementById("info");
setInterval(function(){
info.innerHTML = num++;
}, 1000);

clearInterval()

可以用来关闭一个定时器,方法中需要一个定时器的标识作为参数,这样将关闭标识对应的定时器

1
2
3
4
5
6
var timer = setInterval(function(){
info.innerHTML = num++;
if(num > 100){
clearInterval(timer);
}
}, 10);

延时调用

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);

类的操作

修改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
7
8
9
10
11
// 参数:
// obj 要添加class属性的元素
// cn 要添加的class值
function addClass(obj, cn){
obj.className += " " + cn;
}
//.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属性

JSON操作

JSON(JavaScript Object Notation,js对象表示法)就是一个特殊格式的字符串,这个字符串可以被任意的语言所识别,并且可以转换为任意语言中的对象,JSON在开发中主要用来数据的交互

  • 将JSON字符串转换为JS中的对象JSON.parse()
  • 将js对象转换未JSON字符串JSON.stringify()
  • 定义一串字符串格式的代码,执行并返回结果eval()

JSON分类

  • 对象{}
  • 数组[]

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 及以下的浏览器中不支持,所以在这些浏览器中调用时会报错

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 代码

但是在开发中尽量不要使用,首先它的执行性能比较差,然后它还具有安全隐患

兼容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: "孙悟空"}