JavaScript
NOTE_JS
介绍
✨ 尚硅谷最新版JavaScript基础全套教程完整版 140集实战教学 JS从入门到精通
实战
- ✅ 图片切换
- ✅ 全选练习
- ✅ 添加删除记录
- ✅ 协议注册
- ✅ 拖拽
- ✅ 定时图片切换
- ✅ div移动优化
- ✅ 轮播图
- ✅ 二级菜单
整理难免有误,欢迎大家批评指正!
JS简介
1、什么是语言
计算机就是一个由人来控制的机器,人让它干嘛,它就得干嘛。
我们要学习的语言就是人和计算机交流的工具,人类通过语言来控制、操作计算机。
编程语言和我们说的中文、英文本质上没有区别,只是语法比较特殊。
语言的发展:
- 纸带机:机器语言
- 汇编语言:符号语言
- 现代语言:高级语言
2、JS起源
JavaScript诞生于1995年,它的出现主要是用于处理网页中的前端验证。
所谓的前端验证,就是指检查用户输入的内容是否符合一定的规则。
比如:用户名的长度,密码的长度,邮箱的格式等。
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实现应该由以下三个部分构成:
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 | <script type="text/javascript"> |
可以将js代码编写到外部js文件中,然后通过script
标签引入
1 | <script src="/js/script.js" type="text/javascript"></script> |
script
标签一旦用于引入外部文件了,就不能在编写代码了,即使编写了浏览器也会忽略
如果需要则可以在创建一个新的script
标签用于编写内部代码
2、JS注释
多行注释
多行注释,注释中的内容不会被执行,但是可以在源代码中查看
1 | /* |
单行注释
1 | // 单行注释 |
3、注意点
-
JS中严格区分大小写
-
JS中每一条语句以分号
;
结尾如果不写分号,浏览器会自动添加,但是会消耗一些系统资源,而且有些时候,浏览器会加错分号,所以在开发中分号必须写
-
JS中会忽略多个空格和换行,所以我们可以利用空格和换行对代码进行格式化
4、字面量与变量
字面量
字面量,都是一些不可改变的值
字面量都是可以直接使用,但是我们一般都不会直接使用字面量
变量
变量可以用来保存字面量,而且变量的值是可以任意改变的变量更加方便我们使用
所以在开发中都是通过变量去保存一个字面量,而很少直接使用字面量
可以通过变量对字面量进行描述
1 | // 声明变量: 在js中使用var关键字来声明一个变量 |
5、标识符
在JS中所有的可以由我们自主命名的都可以称为是标识符
例如:变量名、函数名、属性名都属于标识符
命名一个标识符时需要遵守如下的规则:
- 标识符中可以含有字母、数字、_、$
- 标识符不能以数字开头
- 标识符不能是ES中的关键字或保留字
- 标识符一般都采用驼峰命名法
- 首字母小写,每个单词的开头字母大写,其余字母小写
关键字
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 | 对象 |
其中String
、Number
、Boolean
、Null
、Undefined
属于基本数据类型,而Object
属于引用数据类型
String字符串
在JS中,字符串需要使用引号引起来,使用单引号或双引号都可以,但不要混合使用
同一种引号不能嵌套,双引号不能放双引号,单引号不能放单引号
在字符串中我们可以使用\
作为转义字符,当表示一些特殊符号时可以使用\
进行转义
\"
表示"
\'
表示'
\n
表示换行\t
制表符\\
表示\
Number数值
在JS中,所有的数值都是Number
类型,包括整数和浮点数(小数)
可以使用一个运算符typeof
,来检查一个变量的类型。语法:typeof 变量
- 检查字符串时,会返回
string
- 检查数值时,会返回
number
MAX_VALUE
JS中可以表示的数字的最大值 Number.MAX_VALUE=1.7976931348623157e+308
如果使用Number
表示的数字超过了最大值,则会返回一个Infinity
1 | var a = Number.MAX_VALUE * Number.MAX_VALUE; |
MIN_VALUE
大于0的最小值 Number.MIN_VALUE=5e-324
1 | var a = Number.MIN_VALUE * Number.MIN_VALUE; |
Infinity
Infinity
表示正无穷-Infinity
表示负无穷
使用typeof
检查,Infinity
会返回Number
1 | var a = Number.MAX_VALUE * Number.MAX_VALUE; |
NaN
NaN
是一个特殊的数字,表示Not A Number
1 | var a = 'abc' * 'def'; |
使用typeof
检查一个NaN
也会返回number
1 | var a = 'abc' * 'def'; |
运算精度
在JS中整数的运算基本可以保证精确
如果使用JS进行浮点运算,可能得到一个不精确的结果
1 | var a = 0.1 + 0.2; |
所以千万不要使用JS进行对精确度要求比较高的运算
Boolean布尔值
布尔值只有两个,主要用来做逻辑判断
true
表示真false
表示假
使用typeof
检查一个布尔值时,会返回boolean
Null
Null类型的值只有一个,就是null
null
这个值专门用来表示一个为空的对象
使用typeof
检查一个null
值时,会返回object
1 | var a3 = null; |
Undefined
Undefined(未定义)类型的值只有一个,就是undefind
当声明一个变量,但是并不给变量赋值时,它的值就是undefined
使用typeof检查一个undefined
时,也会返回undefined
1 | var a4; |
7、强制类型转换
指将一个数据类型强制转换为其他的数据类型
类型转换主要指,将其他的数据类型,转换为String
、Number
、Boolean
7.1、其他数据类型转换为String
方式一:调用被转换数据类型的toString()方法
该方法不会影响到原变量,它会将转换的结果返回
1 | // Number转换为String |
但是注意:null
和undefined
这两个值没有toString()
,如果调用他们的方法,会报错
1 | // Null转换为String |
方式二:调用String()函数,并将被转换的数据作为参数传递给函数
使用String()
函数做强制类型转换时,对于Number
和Boolean
实际上就是调用的toString()
方法
但是对于null
和undefined
,就不会调用toString()
方法,而是将
null
直接转换为"null"
undefined
直接转换为"undefined"
1 | // Number转换为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
8var 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()
把一个字符串转换为一个整数:可以将一个字符串中的有效整数部分取出来,然后转换为NumberparseFloat()
把一个字符串转换为一个浮点数:可以将一个字符串中的有效小数部分取出来,然后转换为Number- 如果对非String使用
parseInt()
或parseFloat()
,它会先将其转换为String,然后再操作
1 | var a1 = "123"; |
7.3、其他数据类型转换为Boolean
方式一:使用Boolean()
函数
- 数字-—->布尔
- 除了
0
和NaN
,其余的都是true
- 除了
- 字符串-—->布尔
- 除了空串,其余的都是
true
- 除了空串,其余的都是
null
和undefined
都会转换为false
- 对象也会转换为
true
1 | // - 数字-—->布尔 |
方式二:隐式类型转换
为任意的数据类型做两次非运算,即可将其转换为布尔值(下一节会介绍)
1 | var a = "123"; |
8、补充
在js中,如果需要表示16进制的数字,则需要以0x
开头
如果需要表示8进制的数字,则需要以0
开头
如果需要表示2进制的数字,则需要以0b
开头,但是不是所有的浏览器都支持
1 | // 十六进制数字 |
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
20var 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
16var 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 | var a = "10"; |
4、自增和自减
自增++
通过自增可以使变量在自身的基础上增加1
自增分成两种:后++(a++
)和前++(++a
)
无论是a++
还是++a
,都会立即使原变量的值自增1
不同的是a++
和++a
的值不同
a++
是变量的原值(自增前的值)++a
是变量的新值(自增后的值)
1 | var a,b; |
自减–
通过自减可以使变量在自身的基础上减少1
自减分成两种:后–(a--
)和前–(--a
)
无论是a--
还是--a
,都会立即使原变量的值自减1
不同的是a--
和--a
的值不同
a--
是变量的原值(自减前的值)--a
是变量的新值(自减后的值)
1 | var a,b; |
练习
1 | a = 10; |
5、逻辑运算符
JS中为我们提供了三种逻辑运算符
!
非&&
与||
或
非运算
!
可以用来对一个值进行非运算
所谓非运算就是值对一个布尔值进行取反操作,true
变false
,false
变true
- 如果对一个值进行两次取反,它不会变化
- 如果对非布尔值进行运算,则会将其转换为布尔值,然后再取反
所以我们可以利用该特点,来将一个其他的数据类型转换为布尔值
可以为一个任意数据类型取两次反,来将其转换为布尔值,原理和Boolean()
函数一样
1 | var a,b; |
与运算
&&
可以对符号两侧的值进行与运算并返回结果
运算规则
- 两个值中只要有一个值的
false
就返回false
;只有两个值都为true
时,才会返回true
- JS中的“与”属于短路的与,如果第一个值为
false
,则不会检查第二个值
1 | var a; |
或运算
||
可以对符号两侧的值进行或运算并返回结果
运算规则:
- 两个值中只要有一个
true
,就返回true
;如果两个值都为false
,才返回false
- JS中的“或”属于短路的或,如果第一个值为
true
,则不会检查第二个值
1 | var a; |
&&、|| 非布尔值的情况
对于非布尔值进行与或运算时,会先将其转换为布尔值,然后再运算,并且返回原值
与运算
- 如果第一个值为
true
,则必然返回第二个值 - 如果第一个值为
false
,则直接返回第一个值
1 | var result; |
或运算
- 如果第一个值为
true
,则直接返回第一个值 - 如果第一个值为
false
,则返回第二个值
1 | var result; |
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 | console.log(1>true); // false |
8、相等运算符
==
相等
相等运算符用来比较两个值是否相等,如果相等会返回true
,否则返回false
使用==
来做相等运算:当使用==
来比较两个值时,如果值的类型不同,则会自动进行类型转换,将其转换为相同的类型然后在比较
1 | // undefined 衍生自null,所以这两个值做相等判断时,会返回true |
!=
不想等
不相等运算符用来判断两个值是否不相等,如果不相等返回true
,否则返回false
使用!=
来做不相等运算:不相等也会对变量进行自动的类型转换,如果转换后相等它也会返回false
===
全等
用来判断两个值是否全等,它和相等类似,不同的是它不会做自动的类型转换,如果两个值的类型不同,直接返回false
!==
不全等
用来判断两个值是否不全等,和不等类似,不同的是它不会做自动的类型转换,如果两个值的类型不同,直接返回false
1 | console.log("123" === 123); // false |
关于改运算符,可以参考下图
9、条件运算符
条件运算符也叫三元运算符 条件表达式 ? 语句1: 语句2;
执行的流程:条件运算符在执行时,首先对条件表达式进行求值
- 如果该值为
true
,则执行语句1
,并返回执行结果 - 如果该值为
false
,则执行语句2
,并返回执行结果
1 | var a = 30; |
如果条件的表达式的求值结果是一个非布尔值,则会将其转换为布尔值,然后再运算
1 | "hello" ? alert("111111") : alert("2222222"); |
10、运算符优先级
就和数学中一样,在JS中运算符也有优先级,比如:先乘除后加减
在JS中有一个运算符优先级的表,在表中越靠上优先级越高,优先级越高越优先计算,如果优先级一样,则从左往右计算
但是这个表我们并不需要记忆,如果遇到优先级不清楚可以使用()
来改变优先级
. 、[] 、new |
---|
() |
++ 、-- |
! 、~ 、+ (单目)、- (单目)、typeof 、void 、delete |
% 、* 、/ |
+ (双目)、- (双目) |
<< 、>> 、>>> |
< 、<= 、> 、>= |
== 、!== 、=== |
& |
^ |
| |
&& |
|| |
?: |
= 、+= 、-= 、*= 、/= 、%= 、<<= 、>>= 、>>>= 、&= 、^= 、|= |
, |
补充:Unicode编码表
Unicode官网:https://home.unicode.org/
在字符串中使用转义字符输入Unicode编码:\u四位编码
在网页中使用Unicode编码:&#编码;
这里的编码需要的是10进制
流程控制
1、流程控制语句
JS中的程序是从上到下一行一行执行的
通过流程控制语句可以控制程序执行流程,使程序可以根据一定的条件来选择执行
语句的分类:
- 条件判断语句
- 条件分支语句
- 循环语句
2、条件判断语句
使用条件判断语句,可以在执行某个语句之前进行判断
如果条件成立才会执行语句,条件不成立则语句不执行。
if 语句
语法一
1 | if(条件表达式) { |
if
语句在执行时,会先对条件表达式进行求值判断
- 如果条件表达式的值为
true
,则执行if
后的语句 - 如果条件表达式的值为
false
,则不执行if
后的语句
if
语句只能控制紧随其后的那个语句,如果希望if
语句可以控制多条语句,可以将这些语句统一放到代码块中
if
语句后的代码块不是必须的,但是在开发中尽量写上代码块,即使if
后只有一条语句
语法二
1 | if(条件表达式) { |
if...else...
语句执行时,会先对if
后的条件表达式进行求值判断
- 如果该值为
true
,则执行if
后的语句 - 如果该值为
false
,则执行else
后的语句
语法三
1 | if(条件表达式) { |
if...else if...else
语句执行时,会从上到下依次对条件表达式进行求值判断
- 如果值为
true
,则执行当前语句 - 如果值为
false
,则继续向下判断 - 如果所有的条件都不满足,则执行最后一个
else
后的语句 - 该语句中,只会有一个代码块被执行,一旦代码块执行了,则直接结束语句
练习
prompt()
可以弹出一个提示框,该提示框中会带有一个文本框,用户可以在文本框中输入一段内容
该函数需要一个字符串作为参数,该字符串将会作为提示框的提示文字
用户输入的内容将会作为函数的返回值返回,可以定义一个变量来接收该内容
1 | // 练习1 |
其他练习,大家可以自己尝试做下。练习2还是很简单的,跟练习1差不多,无非就是多了几次输入。练习3的话,如果你是初学编程的话,可以尝试做一做,不过个人感觉可以在学完for
循环之后再做,而且这个应该当做简单的算法题。
switch 语句
1 | switch(条件表达式) { |
switch...case..
语句
在执行时会依次将case
后的表达式的值和switch
后的条件表达式的值进行全等比较
- 如果比较结果为
true
,则从当前case
处开始执行代码。当前case
后的所有的代码都会执行,我们可以在case
的后边跟着一个break
关键字,这样可以确保只会执行当前case
后的语句,而不会执行其他的case
- 如果比较结果为
false
,则继续向下比较 - 如果所有的比较结果都为
false
,则只执行default
后的语句
switch
语句和if
语句的功能实际上有重复的,使用switch
可以实现if
的功能,同样使用if
也可以实现switch
的功能,所以我们使用时,可以根据自己的习惯选择
1 | // 对于成绩大于60分的,输出’合格’。低于60分的,输出’不合格’ |
while 语句
循环语句:通过循环语句可以反复的执行一段代码多次
while
循环语法:
1 | while(条件表达式) { |
while
语句在执行时,先对条件表达式进行求值判断
- 如果值为
true
,则执行循环体,循环体执行完毕以后,继续对表达式进行判断 - 如果为
true
,则继续执行循环体,以此类推 - 如果值为
false
,则终止循环
1 | var a = 0; |
像这种将条件表达式为true
的循环,叫做死循环
该循环不会停止,除非浏览器关闭,死循环在开发中慎用。可以使用break
,来终止循环
1 | var i = 0; |
创建一个循环,往往需要三个步骤:
- 创初始化一个变量
- 在循环中设置一个条件表达式
- 定义一个更新表达式,每次更新初始化变量
1 | // 1.创初始化一个变量 |
练习
1 | // 假如投资的年利率为5%,试求从1000块增长到5000块,需要花费多少年 |
do-while 语句
do...while
循环语法:
1 | do{ |
do...while
语句在执行时,会先执行循环体,循环体执行完毕以后,在对while
后的条件表达式进行判断
- 如果结果为
true
,则继续执行循环体,执行完毕继续判断,以此类推 - 如果结果为
false
,则终止循环
实际上这两个语句功能类似,不同的是
while
是先判断后执行,而do...while
会先执行后判断do...while
可以保证循环体至少执行一次,而while
不能
for 语句
for
语句,也是一个循环语句,也称为for
循环
在for
循环中,为我们提供了专门的位置用来放三个表达式:
- 初始化表达式
- 条件表达式
- 更新表达式
for循环的语法:
1 | for(①初始化表达式;②条件表达式;③更新表达式) { |
for
循环的执行流程:
- ①执行初始化表达式,初始化变量(初始化表达式只会执行一次)
- ②执行条件表达式,判断是否执行循环。
- 如果为
true
,则执行④语句 - 如果为
false
,则终止循环
- 如果为
- ③执行更新表达式,更新表达式执行完毕,继续重复②
for
循环中的三个部分都可以省略,也可以写在外部
如果在for
循环中不写任何的表达式,只写两个;
,此时循环是一个死循环会一直执行下去,慎用
1 | for(;;){ |
练习
1 | // 练习1、打印1-100之间所有奇数之和 |
break和continue
不能在if
语句中使用break
和continue
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 | var name = "孙悟空"; |
如果使用基本数据类型的数据,我们所创建的变量都是独立,不能成为一个整体。
对象属于一种复合的数据类型,在对象中可以保存多个不同数据类型的属性。
2、对象的分类
2.1、内建对象
由ES标准中定义的对象,在任何的ES的实现中都可以使用
常见内建对象有以下,都可以直接通过new调用构造函数创建对象实例:
- Object、Function、Array、String、Number、Boolean、Date、RegExp
- Error(EvalError、RangeError、ReferenceError、SyntaxError、TypeError、URIError)
1 | // Math |
2.2、宿主对象
由JS的运行环境提供的对象,目前来讲主要指由浏览器提供的对象
比如 BOM DOM
1 | // console |
JavaScript实现包括三部分:
组成 | 作用 | 地位 | 例子 |
---|---|---|---|
ES(ECMAScript) | 描述JS语法和基本对象 | 核心 | |
DOM(Document Object Model 文档对象模型) | HTML和XML的应用程序接口,处理网页内容的方法和接口 | W3C标准 | document |
BOM(Browser Object Model 浏览器对象模型) | 描述与浏览器进行交互的方法和接口,处理浏览器窗口和框架 | 浏览器厂商对DOM的实现 | window |
DOM
BOM
DOM 和 BOM 的关系
2.3、自定义对象
由开发人员自己创建的对象
使用new
关键字调用的函数,是构造函数constructor
,构造函数是专门用来创建对象的
函数使用typeof
检查一个对象时,会返回object
在对象中保存的值称为属性
- 添加或修改对象属性的语法:
对象.属性名=属性值;
- 读取对象属性的语法:
对象.属性名
- 删除对象属性的语法:
delete 对象.属性名;
1 | var obj = new Object(); |
属性名
对象的属性名不强制要求遵守标识符的规范,什么乱七八糟的名字都可以使用,但是我们使用是还是尽量按照标识符的规范去做
如果要使用特殊的属性名,不能采用.
的方式来操作,而需要使用另一种语法:对象["属性名"]=属性值
,读取时也需要采用这种方式
1 | obj["name"] = "齐天大圣"; |
使用[]
这种形式去操作属性,更加的灵活,在[]
中可以直接传递一个变量,这样变量值是哪个就会读取哪个属性
1 | var n = "nihao"; |
回顾:.
、[]
、new
这几个运算符的优先级是最高的
属性值
JS对象的属性值,可以是任意的数据类型,包括对象
1 | var obj2 = new Object(); |
in
运算符
通过该运算符可以检查一个对象中是否含有指定的属性
如果有则返回true
,没有则返回false
语法:"属性名" in 对象
1 | console.log("test" in obj); // false |
3、基本数据类型和引用数据类型
基本数据类型 String Number Boolean Null Undefined
引用数据类型 Object
基本数据类型
- JS中的变量都是保存到栈内存中的,基本数据类型的值直接在栈内存中存储
- 值与值之间是独立存在,修改一个变量不会影响其他的变量
1 | var a = 1; |
引用数据类型
- 对象是保存到堆内存中的
- 每创建一个新的对象,就会在堆内存中开辟出一个新的空间,而变量保存的是对象的内存地址(对象的引用)
- 如果两个变量保存的是同一个对象引用,当一个通过一个变量修改属性时,另一个也会受到影响
1 | var obj3 = obj; |
比较
- 当比较两个基本数据类型的值时,就是比较值。
- 而比较两个引用数据类型时,它是比较的对象的内存地址,如果两个对象是一摸一样的,但是地址不同,它也会返回
false
1 | var o1 = new Object(); |
4、对象字面量
使用对象字面量,可以在创建对象时,直接指定对象属性的语法:{属性名: 属性值, 属性名: 属性值...}
对象字面量的属性名可以加引号也可以不加(建议不加),如果要使用一些特殊的名字,则必须加引号
属性名和属性值是一组一组的名值对结构,名和值之间使用:
连接,多个名值对之间使用,
隔开
如果一个属性之后没有其他的属性了,就不要写,
了
1 | var obj = { |
5、方法
对象的属性值可以是任何的数据类型,也可以是个函数(下一节知识)
函数也可以称为对象的属性,如果一个函数作为一个对象的属性保存,那么我们称这个函数是这个对象的方法
调用函数就说调用对象的方法,但是它只是名称上的区别没有其他的区别
1 | var obj2 = { |
6、枚举对象中的属性
使用for...in
语句语法:
1 | for(var 变量 in 对象) { |
for...in
语句对象中有几个属性,循环体就会执行几次
每次执行时,会将对象中的一个属性的名字赋值给变量
1 | var obj = { |
函数
1、函数的简介
函数也是一个对象,可以封装一些功能(代码),在需要时可以执行这些功能(代码),可以保存一些代码在需要的时候调用
使用typeof
检查一个函数对象时,会返回function
1 | // 创建一个函数对象 |
使用函数声明来创建一个函数
1 | function 函数名([形参1, 形参2...形参N]) { |
示例
1 | function fun1(){ |
使用函数表达式(匿名函数)来创建一个函数
1 | var 函数名 = function([形参1, 形参2...形参N]) { |
示例
1 | var fun1 = function(){ |
2、函数的参数
定义一个用来求两个数和的函数
可以在函数的()
中来指定一个或多个形参(形式参数)多个形参之间使用,
隔开,声明形参就相当于在函数内部声明了对应的变量
在调用函数时,可以在()
中指定实参(实际参数)
-
调用函数时解析器不会检查实参的类型。所以要注意,是否有可能会接收到非法的参数,如果有可能则需要对参数进行类型的检查
-
调用函数时,解析器也不会检查实参的数量,多余实参不会被赋值。如果实参的数量少于形参的数量,则没有对应实参的形参将是
undefined
1 | // 创建一个函数,用来计算三个数的和 |
3、函数的返回值
可以使用return
来设置函数的返回值语法:return 值
return
后的值将会作为函数的执行结果返回,可以定义一个变量,来接收该结果
在函数中return
后的语句都不会执行
如果return
语句后不跟任何值,就相当于返回一个undefined
;如果函数中不写return
,则也会返回undefined
return
后可以跟任意类型的值
1 | // 创建一个函数,用来计算三个数的和 |
练习
1 | // 1、定义一个函数,判断一个数字是否是偶数,如果是返回true,否则返回false |
实参可以是任意的数据类型,也可以是一个对象。当我们的参数过多时,可以将参数封装到一个对象
1 | function sayHello(o){ |
实参可以是一个对象,也可以是一个函数
1 | function calCirc(radius) { |
calCirc(10)
- 调用函数
- 相当于使用的函数的返回值
calCirc
- 函数对象
- 相当于直接使用函数对象
函数也是一个对象,特殊在其具有功能
break、continue、return对比
break
可以退出当前的循环continue
用于跳过当次循环return
可以结束整个函数
在函数内部再声明一个函数
1 | function fun3(){ |
4、立即执行函数
函数定义完,立即被调用,这种函数叫做立即执行函数
立即执行函数往往只会执行一次
1 | // 函数对象() |
作用域
作用域指一个变量的作用的范围
在JS中一共有两种作用域:
- 全局作用域
- 函数作用域
1、全局作用域
直接编写在script标签中的JS代码,都在全局作用域
全局作用域在页面打开时创建,在页面关闭时销毁
在全局作用域中有一个全局对象window
,它代表的是一个浏览器的窗口,由浏览器创建,可以直接使用
在全局作用域中:
- 创建的变量都会作为window对象的属性保存
- 创建的函数都会作为window对象的方法保存
全局作用域中的变量都是全局变量,在页面的任意的部分都可以访问的到
1 | var a = 3; |
1.1、变量的声明提前
使用var
关键字声明的变量,会在所有的代码执行之前被声明
但是如果声明变量时不适用var
关键字,则变量不会被声明提前
1 | // 1、变量的声明提前 |
1.2、函数的声明提前
使用函数声明形式创建的函数function
1 | 函数(){ |
它会在所有的代码执行之前就被创建,所以我们可以在函数声明前来调用函数
1 | fun1(); // fun1... |
2、函数作用域
调用函数时创建函数作用域,函数执行完毕以后,函数作用域销毁
每调用一次函数就会创建一个新的函数作用域,他们之间是互相独立的
- 在函数作用域中可以访问到全局作用域的变量
- 在全局作用域中无法访问到函数作用域的变量
当在函数作用域操作一个变量时,它会先在自身作用域中寻找,
- 如果有就直接使用
- 如果没有则向上一级作用域中寻找,直到找到全局作用域
- 如果全局作用域中依然没有找到,则会报错
在函数中要访问全局变量可以使用window
对象
1 | var a = 10; |
在函数作用域也有声明提前的特性,使用var
关键字声明的变量,会在函数中所有的代码执行之前被声明
函数声明也会在函数中所有的代码执行之前执行
1 | // 在函数作用域也有声明提前的特性,使用`var`关键字声明的变量,会在函数中所有的代码执行之前被声明 |
在函数中,不适用var
声明的变量都会成为全局变量
1 | // 函数声明且调用 |
定义形参就相当于在函数作用域中声明了变量
1 | var e = 10; |
练习
1 | // 说出以下代码的执行结果 |
3、this
解析器在调用函数每次都会向函数内部传递进一个隐含的参数,这个隐含的参数就是this
this
指向的是一个对象,这个对象我们称为函数执行的上下文对象
根据函数的调用方式的不同,this
会指向不同的对象
- 以函数的形式调用时,
this
永远都是window
- 以方法的形式调用时,
this
就是调用方法的那个对象
1 | // - 以函数的形式调用时,`this`永远都是`window` |
构造函数与原型对象
1、使用工厂方法创建对象
1 | function createPerson(name, age, gender){ |
使用工厂方法创建的对象,使用的构造函数都是Object
所以创建的对象都是Object
这个类型,就导致我们无法区分出多种不同类型的对象
2、构造函数
创建一个构造函数,专门用来创建Person对象的构造函数就是一个普通的函数
创建方式和普通函数没有区别,不同的是构造函数习惯上首字母大写构造函数
和普通函数的区别就是调用方式的不同
- 普通函数是直接调用
- 构造函数需要使用
new
关键字来调用
1 | function Person(){ |
构造函数的执行流程
- 立刻创建一个新的对象
- 将新建的对象设置为函数中
this
,在构造函数中可以使用this
来引用新建的对象 - 逐行执行函数中的代码
- 将新建的对象作为返回值返回
1 | function Dog(){ |
使用同一个构造函数创建的对象,我们称为一类对象,也将一个构造函数称为一个类。
我们将通过一个构造函数创建的对象,称为是该类的实例
使用instanceof
可以检查一个对象是否是一个类的实例语法:对象 instanceof 构造函数
如果是则返回true
,否则返回false
1 | console.log(person1 instanceof Person); //true |
所有的对象都是Object
的后代,所以任何对象和Object
进行instanceof
检查时都会返回true
1 | console.log(person1 instanceof Object); //true |
this
的情况:
- 当以函数的形式调用时,
this
是window
- 当以方法的形式调用时,谁调用方法
this
就是谁 - 当以构造函数的形式调用时,
this
就是新创建的那个对象
构造函数修改
创建一个Person构造函数
在Person构造函数中,为每一个对象都添加了一个sayName方法,目前我们的方法是在构造函数内部创建的
也就是构造函数每执行一次就会创建一个新的sayName方法也是所有实例的sayName都是唯一的
1 | function Person(name, age, gender){ |
这样就导致了构造函数执行一次就会创建一个新的方法,执行10000次就会创建10000个新的方法,而10000个方法都是一模一样的
这是完全没有必要,完全可以使所有的对象共享同一个方法
1 | function Person(name, age, gender){ |
将函数定义在全局作用域,虽然节省了空间,但却污染了全局作用域的命名空间
而且定义在全局作用域中也很不安全
3、原型对象
原型prototype
我们所创建的每一个函数(不论是普通函数还是构造函数),解析器都会向函数中添加一个属性prototype
1 | function Person(){ |
当函数以普通函数的形式调用prototype
时,没有任何作用
当函数以构造函数的形式调用prototype
时,它所创建的对象中都会有一个隐含的属性,指向该构造函数的原型对象,我们可以通过__proto__
来访问该属性
1 | var mc1 = new MyClass(); |
原型对象就相当于一个公共区域,所有同一个类的实例都可以访问到这个原型对象
我们可以将对象中共有的内容,统一设置到原型对象中
1 | // 向MyClass中添加属性a |
当我们访问对象的一个属性或方法时,它会先在对象自身中寻找,如果有则直接使用,如果没有则会去原型对象中寻找,如果找到则直接使用
1 | mc2.a = "456"; |
以后我们创建构造函数时,可以将这些对象共有的属性和方法,统一添加到构造函数的原型对象中
这样不用分别为每一个对象添加,也不会影响到全局作用域,就可以使每个对象都具有这些属性和方法了
hasOwnProperty
1 | function MyClass(){ |
那么,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
当我们直接在页面中打印一个对象时,事件上是输出的对象的toString()
方法的返回值(这里并非视频中所说的那样,有待确认)
如果我们希望在输出对象时不输出[object Object]
,可以为对象添加一个toString()
方法
1 | function Person(name, age, gender){ |
上述只是修改per1对象的toString
方法,不会对其他对象产生影响
如果想要所有对象都执行该方法,可以修改Person原型的toString
1 | console.log(per2.toString()); // [object Object] |
4、垃圾回收(GC)
就像人生活的时间长了会产生垃圾一样,程序运行过程中也会产生垃圾这些垃圾积攒过多以后,会导致程序运行的速度过慢
所以我们需要一个垃圾回收的机制,来处理程序运行过程中产生垃圾
当一个对象没有任何的变量或属性对它进行引用,我们将永远无法操作该对象
此时这种对象就是一个垃圾,这种对算过多会占用大量的内存空间,导致程序运行变慢
在JS中拥有自动的垃圾回收机制,会自动将这些垃圾对象从内存中销毁,我们不需要也不能进行垃圾回收的操作
我们需要做的只是要将不再使用的对象设置null
即可
1 | var obj = new Object(); |
数组
1、数组简介
数组也是一个对象
它和我们普通对象功能类似,也是用来存储一些值的
不同的是普通对象是使用字符串作为属性名的,而数组时使用数字来作为索引操作元素
索引:从0开始的整数就是索引
数组的存储性能比普通对象要好,在开发中我们经常使用数组来存储一些数据
1 | // 创建数组对象 |
向数组中添加元素
语法:数组[索引] = 值
1 | arr[0] = 10; |
读取数组中的元素
语法:数组[索引]
如果读取不存在的索引,不会报错而是返回undefined
1 | console.log(arr[2]); // 22 |
获取数组的长度
可以使用length
属性来获取数组的长度(元素的个数)语法:数组.length
- 对于连续的数组,使用
length
可以获取到数组的长度(元素的个数) - 对于非连续的数组,使用
length
会获取到数组的最大的索引 + 1
1 | console.log(arr.length); // 3 |
尽量不要创建非连续的数组
修改数组的长度
- 如果修改的
length
大于原长度,则多出部分会空出来 - 如果修改的
length
小于原长度,则多出的元素会被删除
1 | arr.length = 100; |
向数组最后一位添加元素
语法:数组[数组.length] = 值;
1 | arr[arr.length] = 22; |
2、创建数组的方式
使用字面量创建数组
语法:[]
1 | var arr1 = []; |
使用字面量创建数组时,可以在创建时就指定数组中的元素
1 | var arr2 = [1,2,3,4,5,10]; |
使用构造函数创建数组
使用构造函数创建数组时,也可以同时添加元素,将要添加的元素作为构造函数的参数传递
元素之间使用,
隔开
1 | var arr3 = new Array(1,2,3,4,5); |
字面量和构造函数只有一个数字时的区别
1 | // 创建一个数组数组中只有一个元素10 |
3、数组元素类型
任意的数据类型
数字、字符串、布尔值、null
、undefined
1 | var arr6 = [2, "13", true, null, undefined]; |
对象
1 | // **也可以是对象** |
函数
1 | arr7 = [function(){alert(1)},function(){alert(2)}]; |
数组
数组中也可以放数组,如下这种数组我们称为二维数组
1 | arr7 = [[1,2,3],[4,5,6],[7,8,9]]; |
4、数组的方法
数组的方法有很多,这里兹介绍常用的几个方法
push()
该方法可以向数组的末尾添加一个或多个元素,并返回数组的新的长度
可以将要添加的元素作为方法的参数传递,这样这些元素将会自动添加到数组的末尾
1 | var result = arr.push("唐三藏"); |
pop()
该方法可以删除数组的最后一个元素,并将被删除的元素作为返回值返回
1 | console.log(arr); // ["孙悟空", "猪八戒", "沙悟净", "唐三藏", "菩提老祖", "地藏菩萨", "弥勒佛"] |
unshift()
向数组开头添加一个或多个元素,并返回新的数组长度
向前边插入元素以后,其他的元素索引会依次调整
1 | console.log(arr); // ["孙悟空", "猪八戒", "沙悟净", "唐三藏", "菩提老祖", "地藏菩萨"] |
shift()
可以删除数组的第一个元素,并将被删除的元素作为返回值返回
1 | console.log(arr); // ["牛魔王", "二郎神", "孙悟空", "猪八戒", "沙悟净", "唐三藏", "菩提老祖", "地藏菩萨"] |
小结
操作 | 添加 | 删除 |
---|---|---|
末尾操作 | push :末尾添加 |
pop :末尾删除 |
开头操作 | unshift :开头添加 |
shift :开头删除 |
slice()
从某个已有的数组返回选定的元素,可以用来从数组提取指定元素
该方法不会改变元素数组,而是将截取到的元素封装到一个新数组中返回参数:
- 截取开始的位置的索引,包含开始索引
- 截取结束的位置的索引,不包含结束索引
1 | result = arr.slice(0,3); |
第二个参数可以省略不写,此时会截取从开始索引往后的所有元素
1 | result = arr.slice(3); |
索引可以传递一个负值,如果传递一个负值,则从后往前计算
- -1 倒数第一个
- -2 倒数第二个
1 | result = arr.slice(4, -1); |
splice()
删除元素,并向数组添加新元素。可以用于删除数组中的指定元素
使用splice()
会影响到原数组,会将指定元素从原数组中删除,并将被删除的元素作为返回值返回
参数:
-
第一个,表示开始位置的索引
-
第二个,表示删除的数量
1
2
3
4
5
6
7
8
9arr = ["牛魔王", "二郎神", "孙悟空", "猪八戒", "沙悟净", "唐三藏", "菩提老祖", "地藏菩萨"];
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 | var arr1 = ["孙悟空", "猪八戒", "沙悟净"]; |
join()
该方法可以将数组转换为一个字符串
该方法不会对原数组产生影响,而是将转换后的字符串作为结果返回
在join()
中可以指定一个字符串作为参数,这个字符串将会成为数组中元素的连接符
如果不指定连接符,则默认使用,
作为连接符
1 | var arr = ["孙悟空", "猪八戒", "沙悟净"]; |
reverse()
该方法用来反转数组(前边的去后边,后边的去前边)
该方法会直接修改原数组
1 | var arr = ["孙悟空", "猪八戒", "沙悟净"]; |
sort()
可以用来对数组中的元素进行排序
也会影响原数组,默认会按照Unicode编码进行排序
1 | var arr = ['f', 'b', 'a', 'h', 'e', 'd']; |
即使对于纯数字的数组,使用sort()
排序时,也会按照Unicode编码来排序
所以对数字进行排序时,可能会得到错误的结果
1 | arr = ['2', '44', '9', '8', '2', '0']; |
我们可以目己采指正排序的现则我们可以在sort()
添加一个回调函数,来指定排序规则
回调函数中需要定义两个形参,浏览器将会分别使用数组中的元素作为实参去调用回调函数
使用哪个元素调用不确定,但是肯定的是在数组中a一定在b前边
浏览器会根据回调函数的返回值来决定元素的顺序,
- 如果返回一个大于0的值,则元素会交换位置
- 如果返回一个小于等于0的值,则元素位置不变
1 | arr = [2, 44, 9, 8, 2, 0, 6]; |
- 如果需要升序排列,则返回
a - b
- 如果需要降序排列,则返回
b - a
1 | arr.sort(function(a,b){ |
小结
- 会对原数组产生影响的方法:
push
、pop
、shift
、unshift
、splice
、reverse
、sort
- 不会对原数组产生影响的方法:
slice
、concat
、join
- 添加元素的方法:
push
、unshift
、splice
- 删除元素的方法:
pop
、shift
、splice
- 替换元素的方法:
splice
- 连接元素的方法:
concat
、join
- 排序方法:
reverse
、sort
5、数组遍历
普通for循环
所谓的遍历数组,就是将数组中所有的元素都取出来
1 | var arr = ["孙悟空", "猪八戒", "沙悟净", "白龙马"]; |
练习
1、准备工作
1 | // 定义Person构造函数 |
2、创建一个函数,可以将perArr中的满18岁的Person提取出来,然后封装到一个新的数组中并返回
1 | function getAdult(perArr){ |
forEach方法
一般我们都是使用for
循环去遍历数组,JS中还为我们提供了一个方法,用来遍历数组forEach()
兼容性
这个方法只支持IE8以上的浏览器,IE8及以下的浏览器均不支持该方法
所以如果需要兼容IE8,则不要使用forEach
,还是使用for
循环来遍历
使用
forEach() 方法需要一个函数作为参数
像这种函数,由我们创建但是不由我们调用的,我们称为回调函数
数组中有几个元素,函数就会执行几次,每次执行时,浏览器会将遍历到的元素
以实参的形式传递进来,我们可以来定义形参,来读取这些内容
参数
浏览器会在回调函数中传递三个参数:
- 第一个参数,就是当前正在遍历的元素
- 第二个参数,就是当前正在遍历的元素的索引
- 第三个参数,就是正在遍历的数组
1 | arr.forEach(function(value, index, obj){ |
练习
数组去重
1 | // 创建一个数组 |
call、apply和argument
call()和apply()
这两个方法都是函数对象的方法,需要通过函数对象来调用
当对函数调用 call()
和apply()
都会调用函数执行
1 | var obj = { |
在调用call()
和apply()
可以将一个对象指定为第一个参数此时这个对象将会成为函数执行时的this
call()
方法可以将实参在对象之后依次传递apply()
方法需要将实参封装到一个数组中统一传递
1 | function fun(a, b){ |
this的情况
- 以函数的形式调用时,
this
永远都是window
- 以方法的形式调用时,
this
是调用方法的对象 - 以构造函数的形式调用时,
this
是新创建的那个对象 - 使用
call
和apply
调用时,this
是指定的那个对象
argument
在调用函数时,浏览器每次都会传递进两个隐含的参数:
- 函数的上下文对象
this
- 封装实参的对象
arguments
arguments
是一个类数组对象(并非数组),可以通过索引来操作数据,也可以获取长度
1 | function fun1(){ |
在调用函数时,我们所传递的实参都会在arguments
中保存
我们即使不定义形参,也可以通过arguments
来使用实参,只不过比较麻烦
arguments[0]
表示第一个实参arguments[1]
表示第二个实参
1 | function fun2(a,b,c){ |
它里边有一个属性叫做callee
,这个属性对应一个函数对象,就是当前正在执行的函数对象
1 | function fun3(){ |
Date和Math
1、Date
在JS中使用Date
对象来表示一个时间
创建一个时间对象
如果直接使用构造函数创建一个Date
对象,则会封装为当前代码执行的时间
1 | // 创建一个Date对象 |
创建一个指定的时间对象
需要在构造函数中传递一个表示时间的字符串作为参数
日期的格式:月/日/年 时:分:秒
1 | d = new Date("08/01/2021 12:34:56"); |
Date方法
getDate()
获取当前日期对象是几日
1 | var date = d.getDate(); |
getDay()
获取当前日期对象时周几,会返回一个0-6的值
- 0 表示 周日
- 1 表示 周一
- …
- 6 表示 周六
1 | var day = d.getDay(); |
getMonth()
获取当前时间对象的月份-会返回一个0-11的值
- 0 表示 1月
- 1 表示 2月
- …
- 11 表示 12月
1 | var month = d.getMonth(); |
getFullYear()
获取当前日期对象的年份
1 | var year = d.getFullYear(); |
getTime()
获取当前日期对象的时间戳
时间戳,指的是从格林威治标准时间的1970年1月1日0时0分0秒到当前日期所花费的毫秒数
计算机底层在保存时间时使用都是时间戳
1 | // 示例:表示从1970年1月1日0时0分0秒到2021年22时25分26秒所花费的毫秒数 |
既然时间是从格林威治标准时间开始计算的,是不是就意味着1970年1月1日0时0分0秒的时间戳就是0呢?
1 | var d2 = new Date("01/01/1970 00:00:00"); |
我们发现事实并非如此,为什么呢?
这是因为我们的系统是中文系统,采用的是东八区时间,如何验证呢?
1 | console.log("距离格林威治时间还有" + -time/1000/60/60 + "小时"); // 距离格林威治时间还有8小时 |
获取当前的时间戳
1 | var currentTime = Date.now(); |
其他
1 | var hour = d.getHours(); |
2、Math
Math
和其他的对象不同,不是一个构造函数
属于一个工具类,不用创建对象,里边封装了数学运算相关的属性和方法
Math对象属性
1 | console.log(Math.E); // 2.718281828459045 |
Math对象方法
Math.abs()
可以用来计算一个数的绝对值
1 | console.log(Math.abs(-1)); // 1 |
Math.ceil()
可以对一个数进行向上取整,小数位只有有值就自动进1
1 | console.log(Math.ceil(1.001)); // 2 |
Math.floor()
可以对一个数进行向下取整,小数部分会被舍掉
1 | console.log(Math.floor(1.999999)); // 1 |
Math.round()
可以对一个数进行四舍五入取整
1 | console.log(Math.round(1.4)); // 1 |
Math.random()
可以用来生成一个0-1之间的随机数(开区间范围:(0, 1)
)
1 | console.log(Math.random()); // 0.9192011449766921 |
生成一个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、三大包装类
基本数据类型:String
、Number
、Boolean
、Null
、Undefined
引用数据类型:Object
在JS中为我们提供了三大包装类,通过这三个包装类可以将基本数据类型的数据转换为对象
String()
可以将基本数据类型字符串转换为String
对象Number()
可以将基本数据类型的数字转换为Number
对象Boolean()
可以将基本数据类型的布尔值转换为Boolean
对象
1 | var str1 = "hello"; |
但是注意:我们在实际应用中不会使用基本数据类型的对象,如果使用基本数据类型的对象,在做一些比较时可能会带来一些不可预期的结果
1 | var n1 = new Number(1); |
方法和属性之能添加给对象,不能添加给基本数据类型(按照视频中的解释,是先将data临时转换为了一个包装类对象,进行了属性赋值操作;打印时又临时转换为了一个新的包装类对象,因为两次不是同一个对象,而且该对象刚刚创建,还没有任何属性和方法,所以是获取不到任何值的)
1 | var data = 4; |
当我们对一些基本数据类型的值去调用属性和方法时,浏览器会临时使用包装类将其转换为对象,然后在调用对象的属性和方法时,浏览器会临时使用包装类将其转换为对象,然后在调用对象的属性和方法调用完以后,在将其转换为基本数据类型
1 | var s = 123; |
2、字符串方法
字符串在底层是以字符数组的形式保存的:["H","e","l","l","o"," ","W","o","r","l","d","."]
1 | var str = "Hello World."; |
length属性
可以用来获取字符串的长度
1 | console.log(str.length); // 12 |
charAt()
可以返回字符串中指定位置的字符,不会对原字符串产生影响
1 | var result = str.charAt(0); |
charCodeAt()
获取指定位置字符的字符编码(Unicode编码),不会对原字符串产生影响
1 | result = str.charCodeAt(0); |
String.formCharCode()
可以根据字符编码去获取字符
1 | result = String.fromCharCode(72); |
concat()
可以用来连接两个或多个字符串,作用和+
一样,不会对原字符串产生影响
1 | result = str.concat("您好","世界"); |
indexof()
该方法可以检索一个字符串中是否含有指定内容,不会对原字符串产生影响
- 如果字符串中含有该内容,则返回其第一次出现的索引
- 如果没有找到指定的内容,则返回
-1
1 | result = str.indexOf("o"); |
可以指定一个第二个参数,指定开始查找的位置
1 | result = str.indexOf("l",3); |
lastIndexof()
该方法的用法和indexOf()
一样,不同的是indexOf
是从前往后找,而lastIndexOf
是从后往前找
但返回的索引是按照从前往后计数的
1 | result = str.lastIndexOf("o"); |
可以指定一个第二个参数,指定开始查找的位置(不过开始位置也是从后往前数的)
1 | result = str.lastIndexOf("l", 6); |
slice()
可以从字符串中截取指定的内容,不会影响原字符串
- 第一个参数,开始位置的索引(包括开始位置)
- 第二个参数,结束位置的索引(不包括结束位置)
1 | result = str.slice(0,2); |
如果省略第二个参数,则会截取到后边所有的
1 | result = str.slice(6); |
也可以传递一个负数作为参数,负数的话将会从后边计算
1 | result = str.slice(6,-1); |
substring()
可以用来截取一个字符串,不会影响原字符串,和slice()
类似
- 第一个参数,开始位置的索引(包括开始位置)
- 第二个参数,结束位置的索引(不包括结束位置)
1 | result = str.substring(0,2); |
不同的是这个方法不能接受负值作为参数,如果传递了一个负值,则默认使用0
而且会自动调整参数的位置,如果第二个参数小于第一个,则自动交换
1 | result = str.substring(1,-1); // 自动调整为str.substring(0,1); |
substr()
用来截取字符串,不会影响原字符串,不过不建议使用
- 第一个参数,截取开始位置的索引
- 第二个参数,截取的长度
1 | result = str.substr(1,3); |
split()
可以将一个字符串拆分为一个数组,不会影响原字符串
需要一个字符串作为参数,将会根据该字符串去拆分数组
1 | result = str.split("o"); |
如果传递一个空串作为参数,则会将每个字符都拆分为数组中的一个元素
1 | result = str.split(""); |
toUpperCase()
将一个字符串转换为大写并返回,不会影响原字符串
1 | result = str.toUpperCase(); |
toLowerCase()
将一个字符串转换为小写并返回,不会影响原字符串
1 | result = str.toLowerCase(); |
正则表达式
正则表达式用于定义一些字符串的规则,计算机可以根据正则表达式,来检查一个字符串是否符合规则,获取将字符串中符合规则的内容提取出来
1、正则对象
语法:var 变量 = new RegExp("正则表达式", "匹配模式");
1 | // 这个正则表达式可以来检查一个字符串中是否含有a |
使用typeof
检查正则对象,会返回object
1 | console.log(typeof reg); // object |
2、正则方法
正则表达式的方法:test()
使用这个方法可以用来检查一个字符串是否符合正则表达式的规则,如果符合则返回true
,否则返回false
1 | var result = reg.test("abd"); |
在构造函数中可以传递一个匹配模式作为第二个参数,可以是
i
ignoreCase,忽略大小写g
global,全局匹配模式
1 | reg = new RegExp("a","i"); |
3、正则语法
使用字面量来创建正则表达式,语法:var 变量 = /正则表达式/匹配模式;
使用字面量的方式创建更加简单;使用构造函数创建更加灵活
1 | reg = /a/i; |
使用|
表示或者的意思
1 | // 创建一个正则表达式,检查一个字符串中是否有a或b或c |
[]
里的内容也是或的关系:[abc] == a|b|c
1 | reg = /[abc]/i; |
[a-z]
任意小写字母
1 | reg = /[a-z]/; |
[A-Z]
任意大写字母
1 | reg = /[A-Z]/; |
[A-z]
任意字母
1 | reg = /[A-z]/; |
[0-9]
任意数字
1 | reg = /[0-9]/; |
**练习:**检查一个字符串中是否含有abc或adc或aec
1 | reg = /a[bde]c/; |
[^ ]
除了
1 | reg = /[^a]/; // 除了a以外的字符 |
[^0-9]
除了数字
1 | reg = /[^0-9]/; |
小结
表达式 | 描述 |
---|---|
[abc] |
查找方括号之间的任何字符 |
[^abc] |
查找任何不在方括号之间的字符 |
[0-9] |
查找任何从0至9的数字 |
[a-z] |
查找任何从小写a到小写z的字符 |
[A-Z] |
查找任何从大写A到大写Z的字符 |
[A-z] |
查找任何从大写A到小写z的字符 |
[ojbk] |
查找给定集合内的任何字符 |
[^ojbk] |
查找给定集合外的任何字符 |
(ed|blue|green) |
查找任何指定的选项 |
量词
通过量词可以设置一个内容出现的次数
量词只对它前边的一个内容起作用
-
{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
5reg = /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
3reg = /ab{3,}c/;
console.log(reg.test("abbbc")); // true
console.log(reg.test("abbbbbc")); // true -
+
至少一个,相当于{1,}
1
2
3
4reg = /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
4reg = /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
4reg = /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 | reg = /^1[3-9][0-9]{9}$/; |
小结
量词 | 描述 |
---|---|
n+ |
匹配任何包含至少一个n的字符串 |
n* |
匹配任何包含零个或多个n的字符串 |
n? |
匹配任何包含零个或一个n的字符串 |
n{X} |
匹配包含X个n的序列的字符串 |
n{X,Y} |
匹配包含X或Y个n的序列的字符串 |
n{X,} |
匹配包含至少X个n的序列的字符串 |
n$ |
匹配任何结尾为n的字符串 |
^n |
匹配任何开头为n的字符串 |
元字符
检查一个字符串中是否含有.
1 | var reg = /./; |
.
表示任意字符
在正则表达式中使用\
作为转义字符
-
\.
来表示.
1
2
3reg = /\./;
console.log(reg.test("ab")); // false
console.log(reg.test("a.b")); // true -
\\
表示\
1
2
3
4
5reg = /\\/;
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 | reg = new RegExp("\."); // 相当于 reg = /./,即包含任意字符 |
如果要使用\
,则需要使用\\
来代替
1 | reg = new RegExp("\\."); // 相当于 reg = /\./,即包含`.` |
如果要使用\\
,则需要使用\\\\
来代替
1 | reg = new RegExp("\\\\."); // 相当于 reg = /\\./,即包含`\任意字符` |
-
\w
任意字母、数字、_
,相当于[A-z0-9_]
1
2
3
4
5reg = /\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
5reg = /\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
5reg = /\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
5reg = /\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
6reg = /\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
6reg = /\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
7reg = /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
5reg = /\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、字符串和正则相关的方法
split()
可以将一个字符串拆分为一个数组,不会影响原字符串
方法中可以传递一个正则表达式作为参数,这样方法将会根据正则表达式去拆分字符串
split()
方法即使不指定全局匹配,也会全都拆分
1 | // 根据任意字母来将字符串拆分 |
search()
可以搜索字符串中是否含有指定内容,不会影响原字符串
如果搜索到指定内容,则会返回第一次出现的索引,如果没有搜索到返回-1
它可以接受一个正则表达式作为参数,然后会根据正则表达式去检索字符串
search()
只会查找第一个,即使设置全局匹配也没用
1 | str = "Hello abc Hello afc agc"; |
match()
可以根据正则表达式,从一个字符串中将符合条件的内容提取出来,不会影响原字符串
默认情况下我们的match只会找到第一个符合要求的内容,找到以后就停止检索
1 | str = "1a2b3c4d5e6A7B8C9D0"; |
可以设置正则表达式为全局匹配模式,这样就会匹配到所有的内容
1 | result = str.match(/[a-z]/g); |
可以为一个正则表达式设置多个匹配模式,且顺序无所谓
1 | result = str.split(/[a-z]/ig); |
match()
会将匹配到的内容封装到一个数组中返回,即使只查询到一个结果
1 | console.log(Array.isArray(result)); // true |
replace()
可以将字符串中指定内容替换为新的内容,不会影响原字符串
参数:
- 被替换的内容,可以接受一个正则表达式作为参数
- 新的内容
1 | result = str.replace("a","@_@"); |
默认只会替换第一个,可以使用正则表达式的全局匹配模式
1 | str = "1a2a3a4a5a6A7B8C9D0"; |
小结
split()
方法用于拆分,即使不指定全局匹配,也会全都拆分search
方法用于搜索,只会查找第一个,即使设置全局匹配也没用match
方法用于提取replace
方法用于替换
**练习:**去除用户输入中的前后空格
1 | // 接收一个用户的输入 |
5、邮件正则
任意字母数字下划线(.任意字母数字下划线){0个或多个}@任意字母数字.任意字母(2-5位)(.任意字母(2-5位)){0个或多个}
1 | var reg = /^\w+(\.\w+)*@[A-z0-9]+(\.[A-z]{2,5})+$/; |
附录:常用正则表达式[^1]
校验数字的表达式
1 | 数字:^[0-9]*$ |
校验字符的表达式
1 | 汉字:^[\u4e00-\u9fa5]{0,}$ |
特殊需求表达式
1 | Email地址:^\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$ |
作者:zxin
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
参考资料
[^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网页文档
对象
对象表示将网页中的每一个部分都转换为了一个对象
模型
使用模型来表示对象之间的关系,这样方便我们获取对象
DOM树体现了节点与节点之间的关系
2、节点
节点Node,是构成我们网页的最基本的组成部分,网页中的每一个部分都可以称为是一个节点
比如:html标签、属性、文本、注释、整个文档等都是一个节点
虽然都是节点,但是实际上他们的具体类型是不同的。比如:
- 标签称为元素节点
- 属性称为属性节点
- 文本称为文本节点
- 文档称为文档节点
节点的类型不同,属性和方法也都不尽相同
节点类型
节点:Node——构成HTML文档最基本的单元
常用节点分为四类
- 文档节点:整个HTML文档
- 元素节点:HTML文档中的HTL标签
- 属性节点:元素的属性
- 文本节点:HTML标签中的文本内容
节点属性
文档节点(Document)
文档节点document
,代表的是整个HTML文档,网页中的所有节点都是它的子节点
document
对象作为window
对象的属性存在的,我们不用获取可以直接使用
通过该对象我们可以在整个文档访问内查找节点对象,并可以通过该对象创建各种节点对象
元素节点(Element)
HTML中的各种标签都是元素节点,这也是我们最常用的一个节点
浏览器会将页面中所有的标签都转换为一个元素节点,我们可以通过document
的方法来获取元素节点
比如:document.getElementById()
根据id属性值获取一个元素节点对象。
文本节点(Text)
文本节点表示的是HTML标签以外的文本内容,任意非HTML的文本都是文本节点
它包括可以字面解释的纯文本内容
文本节点一般是作为元素节点的子节点存在的
获取文本节点时,一般先要获取元素节点,再通过元素节点获取文本节点。例如:元素节点.firstChild;
获取元素节点的第一个子节点,一般为文本节点
属性节点(Attr)
属性节点表示的是标签中的一个一个的属性,这里要注意的是属性节点并非是元素节点的子节点,而是元素节点的一部分
可以通过元素节点来获取指定的属性节点。例如:元素节点.getAttributeNode("属性名");
注意:我们一般不使用属性节点
浏览器已经为我们提供文档节点对象,这个对象是window
属性可以在页面中直接使用,文档节点代表的是整个网页
1 | // 获取button对象 |
3、事件
事件,就是文档或浏览器窗口中发生的一些特定的交互瞬间
JavaScript与HTML之间的交互是通过事件实现的
对于Web应用来说,有下面这些代表性的事件:点击某个元素、将鼠标移动至某个元素上方、按下键盘上某个键,等等
我们可以在事件对应的属性中设置一些js代码,这样当事件被触发时,这些代码将会执行
1 | <button type="button" id="btn" onclick="alert('Fuck');">我是一个按钮</button> |
这种写法我们称为结构和行为耦合,不方便维护,不推荐使用
可以为按钮的对应事件绑定处理函数的形式来响应事件,这样当事件被触发时,其对应的函数将会被调用
1 | // 绑定一个单击事件 |
像这种为单击事件绑定的函数,我们称为单击响应函数
4、文档的加载
当我们把script
标签放到head
中时,会报错UncaughtTypeError: Cannot set property 'innerHTML' of null
,这是为什么呢?
浏览器在加载一个页面时,是按照自上向下的顺序加载的,读取到一行就运行一行,如果将script
标签写到页面的上边,在代码执行时,页面还没有加载,DOM对象也没有加载,会导致无法获取到DOM对象
如果非要这么干,也不是没有办法
onload
事件会在整个页面加载完成之后才触发,可以为window
对象绑定一个onload
事件
1 | window.onload = function(){ |
该事件对应的响应函数将会在页面加载完成之后执行,这样可以确保我们的代码执行时所有的DOM对象已经加载完毕了
5、DOM查询
获取元素节点
通过document对象调用
为了方便,定义一个通用的函数,专门用来为指定元素绑定单击响应函数
1 | // 参数: |
-
getElementById()
通过id属性获取一个元素节点对象1
2
3
4myClick("btn01", function () {
// innerHTML 通过这个属性可以获取到元素内部的html代码
alert(document.getElementById("bj").innerHTML); // 北京
}); -
getElementsByTagName()
通过标签名获取一组元素节点对象1
2
3
4
5
6
7
8
9
10
11
12
13myClick("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
15myClick("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 | <div class="outer"> |
CSS代码
1 | *{ |
JS代码
1 | // 上一张 |
效果
获取元素节点的子节点
通过具体的元素节点调用
-
getElementsByTagName()
方法,返回当前节点的指定标签名后代节点1
2
3
4
5
6
7
8
9
10
11
12myClick("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
27myClick("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
21myClick("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
7document.getElementById("btn062").onclick = function () {
var phone = document.getElementById("phone");
// children属性可以获取当前元素的所有子元素
var lastChild = phone.lastChild;
alert(lastChild); // [object HTMLLIElement]
alert(lastChild.innerHTML); // Windows Phone
});
获取父节点和兄弟节点
通过具体的节点调用
-
parentNode
属性,表示当前节点的父节点1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19myClick("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
21myClick("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
7myClick("btn082", function () {
var android = document.getElementById("android");
// 返回#android的前一个兄弟节点(也可能获取到空白的文本)
var nextSibling = android.nextSibling;
alert(nextSibling); // [object HTMLLIElement]
alert(nextSibling.innerHTML); // Windows Phone
});
6、全选练习
HTML代码
1 | <form method="post" action=""> |
全选
1 | document.getElementById("checkedAllBtn").onclick = function(){ |
全不选
1 | document.getElementById("checkedNoBtn").onclick = function(){ |
反选
1 | document.getElementById("checkedRevBtn").onclick = function(){ |
提交
1 | document.getElementById("sendBtn").onclick = function(){ |
全选/全不选
1 | document.getElementById("checkedAllBox").onclick = function(){ |
items
1 | var flag; |
效果
7、DOM查询的剩余方法
document.body
在document
中有一个属性body
,它保存的是body
的引用
1 | // 注意:如果script标签是定义在head中的,则这里需要window.onload = function(){}包裹,否则会出现null的情况 |
document.documentElement
document.documentElement
保存的是html
根标签
1 | var html = document.documentElement; |
document.all
document.all
代表页面中所有的元素
1 | var all = document.all; |
document.getElementsByClassName()
根据元素的class
属性值查询一组元素节点对象
getElementsByClassName()
可以根据class
属性值获取一组元素节点对象,但是该方法不支持IE8及以下的浏览器
1 | var boxs = document.getElementsByClassName("box"); |
document.querySelector()
需要一个选择器的字符串作为参数,可以根据一个CSS选择器来查询一个元素节点对象
虽然IE8中没有getElementsByClassName()
但是可以使用querySelector()
代替
使用该方法总会返回唯一的一个元素,如果满足条件的元素有多个,那么它只会返回第一个
1 | var div = document.querySelector(".box div"); |
document.querySelectorAll()
该方法和querySelector()
用法类似,不的是它会将符合条件的元素封装到一个数组中返回
即使符合条件的元素只有一个,它也会返回数组
1 | boxs = document.querySelectorAll(".box"); |
8、DOM增删改
document.createElement()
可以用于创建一个元素节点对象,它需要一个标签名作为参数,将会根据该标签名创建元素节点对象,并将创建好的对象作为返回值返回
document.createTextNode()
可以用来创建一个文本节点对象,它需要一个文本内容作为参数,将会根据该内容创建文本节点,并将新的节点返回
appendChild()
向一个父节点中添加一个新的子节点,用法:父节点.appendChild(子节点);
insertBefore()
可以在指定的子节点前插入新的子节点,语法:父节点.insertBefore(新节点, 旧节点);
replaceChild()
可以使用指定的子节点替换已有的子节点,语法:父节点.replaceChild(新节点, 旧节点);
removeChild()
可以删除一个子节点,语法:父节点.removeChild(子节点);
、子节点.parentNode.removeChild(子节点);
1 | // 创建一个"广州"节点,添加到#city下 |
9、增删练习
准备
HTML代码
1 | <table id="employeeTable"> |
JS代码
1 | // a的单击相应函数 |
添加优化
结合createElement
和innerHTML
,优化修改上述添加代码逻辑
1 | document.getElementById("addEmpButton").onclick = function() { |
a的索引问题
上述中,我们为每个a都添加了单击响应函数,使用了this
获取遍历中的a元素,通过this.parentNode.parentNode
获取了 tr 元素,如果这里改成aList[i].parentNode.parentNode
,能够拿到 tr 元素吗?
看起来好像毫无悬念,但实际上是拿不到的,这是为什么呢?
我们可以改造下 for 循环中 a 元素的单击相应函数,打印下每次拿到的 i
1 | for (var i = 0; i < aList.length; i++) { |
会发现,每次打印的结果都是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 | box1.style.height = "200px"; |
注意:如果CSS的样式名中含有一,这种名称在JS中是不合法的,比如background-color
需要将这种样式名修改为驼峰命名法,去掉-
,然后将-
后的字母大写
1 | // box1.style.background-color = "red"; // Uncaught SyntaxError: Invalid left-hand side in assignment |
在 w3school 手册中,可以查看到每个样式所对应的 JS 代码
我们通过 style 属性设置的样式都是内联样式,而内联样式有较高的优先级,所以通过JS修改的样式往往会立即显示
但是如果在样式中写了!important
,则此时样式会有最高的优先级,即使通过JS也不能覆盖该样式,此时将会导致JS修改样式失效,所以尽量不要为样式添加!important
我们给 background-color
设置!important
之后,通过 box1.style.backgroundColor = "red";
设置的样式就“废”了
1 | background-color: yellow !important; |
读取元素内联样式
通过 JS 读取元素的内联样式,语法:元素.style.样式名
通过style属性设置和读取的都是内联样式,无法读取样式表中的样式
1 | alert(box1.style.height); // |
别急,耐心往下看
读取元素样式
获取元素的当前显示的样式,语法:元素.currentStyle.样式名
它可以用来读取当前元素正在显示的样式,如果当前元素没有设置该样式,则获取它的默认值
1 | alert(box1.currentStyle.height); // 100px |
不过currentstyle
只有 IE 浏览器支持,其他的浏览器都不支持。我们在 IE 中测试是可行的,在 Chrome 或 Edge 中报错的:UncaughtTypeError: Cannot read property 'height' of undefined
不过,在其他浏览器中可以使用getComputedStyle()
,这个方法来获取元素当前的样式
这个方法是window
的方法,可以直接使用,需要两个参数
- 第一个:要获取样式的元素
- 第二个:可以传递一个伪元素,一般都传
null
该方法会返回一个对象,对象中封装了当前元素对应的样式
可以通过对象.样式名
来读取样式,如果获取的样式没有设置,则会获取到真实的值,而不是默认值
比如:没有设置 width,它不会获取到 auto,而是一个长度
但是该方法不支持IE8及以下的浏览器
1 | var obj = getComputedStyle(box1, null); |
那么问题来了,如果想要兼容IE8及以下的浏览器,就会陷入一个两难的境地, 该怎么办呢?
通过currentStyle
和getComputedStyle()
读取到的样式都是只读的,不能修改,如果要修改必须通过style
属性
那么我就只能自己写个函数,来兼容所有浏览器
1 | // 自定义兼容所有浏览器获取元素样式的方法 |
测试结果
Hbuilder内置浏览器
Chrome
Edge
IE11
IE8
怎么 IE8 还是不行,提示“getComputedStyle”未定义
?
这是因为执行到 if 语句时,会先在 function 中找,找不到会在全局作用域中找,全局作用域中也找不到getComputedStyle
,就会报错了
那么怎么解决这个问题呢?
我们先改造一下 function 代码,将getComputedStyle
改成window.getComputedStyle
1 | function getStyle(obj, name) { |
效果
为什么呢?
因为变量找不到会报错,而属性找不到返回的是undefined
而不会报错,这样就可以利用undefined != true
的特点,执行 else 中的代码
同理,下面代码同样可以判断,只不过,会优先走currentStyle
的方式,而我们希望的优先走getComputedStyle
方法,所以不建议用
1 | function getStyle(obj, name) { |
那么上述代码有没有优化或者说简化的空间呢?当然,我们可以使用三元运算符对其进行精简
1 | function getStyle(obj, name) { |
三元运算符更加简洁,if-else 的方式更加清晰,建议使用 if-else 的方式,不过本质上是一样的,看个人习惯
11、其他样式相关的属性
clientWidth、clientHeight
这两个属性可以获取元素的可见宽度和高度
这些属性都是不带px
的,返回都是一个数字,可以直接进行计算
会获取元素宽度和高度,包括内容区和内边距
这些属性都是只读的,不能修改(改只有一种方式,就是通过元素.style.样式 = 样式值
)
1 | // #box1 { |
offsetWidth、offsetHeight
获取元素的整个的宽度和高度,包括内容区、内边距和边框
1 | // #box1 { |
offsetParent
可以用来获取当前元素的定位父元素
会获取到离当前元素最近的开启了定位(只要position
不是sticky
)的祖先元素
如果所有的祖先元素都没有开启定位,则返回body
1 | // <div id="box1"></div> |
offsetLeft、offsetTop
当前元素相对于其定位父元素的水平或垂直偏移量
1 | //<div id="box3"> |
scrollHeight、scrollWidth
可以获取元素整个滚动区域的宽度和高度
1 | // #box4 { |
scrollLeft、scrollTop
可以获取水平或垂直滚动条滚动的距离
1 | // #box4 { |
看这么一个问题,打印如下值,将水平和垂直滚动条滚动到底
1 | alert(box4.clientHeight + ", " + (box4.scrollHeight - box4.scrollTop)); // 283, 283.20001220703125 |
PS:我这里打印的结果存在小数点,不知为何
- 当满足
scrollHeight - scrollTop == clientHeight
,说明垂直滚动条滚动到底了 - 当满足
scrollWidth - scrollLeft == clientwidth
,说明水平滚动条滚动到底
那么这个原理有什么用呢?
爱到底到底,管我什么事 有些网站注册时会有一个 霸王条款 用户协议,要确保用户阅读协议了,才允许注册。那问题来了,怎么确保用户阅读了协议呢?就是利用了上述原理,当滚动条拖至最底部时,就可以注册了。
那么接下来,我们就做一个 霸王条款 用户协议
练习
HTML 代码
1 | <div id="outer"> |
CSS 代码
1 | #outer { |
JS 代码
1 | // 为滚动条绑定事件,就是为有滚动条的元素绑定事件 |
效果
事件对象
1、事件对象
<前情提要>
事件对象
- 当事件的响应函数被触发时,浏览器每次都会将一个事件对象作为实参传递进响应函数
- 在事件对象中封装了当前事件相关的一切信息,比如:鼠标的坐标、键盘哪个按键被按下、鼠标滚轮滚动的方向。。。
事件属性
鼠标/键盘属性
<练习1:当鼠标在areaDiv中移动时,在showMsg中来显示鼠标的坐标>
HTML 代码
1 | <div id="areaDiv"></div> |
CSS 代码
1 | #areaDiv { |
JS 代码
1 | var areaDiv = document.getElementById("areaDiv"); |
效果
内置浏览器
Chrome
Edge
IE11
IE8
在IE8中,响应函数被触发时,浏览器不会传递事件对象
在IE8及以下的浏览器中,是将事件对象作为window
对象的属性保存的
那么按照之前学习到的思路,我们可以对其进行兼容性改造
1 | var x; |
IE8测试
感觉上述代码不优雅,对上述代码进行二次改造
1 | //if (!event) { |
<练习2:div跟随鼠标移动>
1 | // 兼容性写法 |
但是,当我们给body设置一个较大height
属性值时,会发现一个问题,就是鼠标指针与 div 之间存在一定距离
这是为什么呢?
clientX
和clientY
用于获取鼠标在当前的可见窗口的坐标 div 的偏移量,是相对于整个页面的pageX
和pageY
可以获取鼠标相对于当前页面的坐标,但是这个两个属性在IE8中不支持,所以如果需要兼容IE8,则不要使用
1 | var left = event.pageX; |
再试下效果
貌似好了哈,那直接测试下 IE8?
这要怎么办?
我们现在给 body 设置了一个height
,红色框表示可见区域大小,蓝色框表示 body 的实际区域大小
既然我们没办法使用pageX
和pageY
兼容IE8,那暂时只能使用clientX
和clientY
了,而clientX
和clientY
是按照可见区域大小计算的,那让 div 的水平和垂直偏移量也按照可见区域大小计算不就行了吗?但是我们又暂时没办法让 div 总是参考可见区域大小的原点作为定位的原点,难道就没有办法了吗?
我们之前学习过,scrollTop
表示滚动条的垂直滚动距离,而div位置原点 与鼠标指针原点的差距应该刚好是滚动条垂直滚动的距离,那么是不是可以利用这两个属性来“弥补” 这两者之间的距离差呢?
1 | box1.style.top = (document.body.scrollTop + top - box1.clientHeight / 2) + "px"; |
发现还是不行,要知道我们是给 body 设置的height
属性,之所以出现滚动条是因为 body 的父元素容不下 body 了,所以应该获取谁的scrollTop
属性?body 的父元素,即 html
1 | box1.style.top = (document.documentElement.scrollTop + top - box1.clientHeight / 2) + "px"; |
Chrome
IE8
在视频中,测试的结果是Chrome和火狐等浏览器获取scrollTop
的对象不一致,需要做兼容
chrome认为浏览器的滚动条是body的,可以通过body.scrollTop来获取火狐等浏览器认为浏览器的滚动条是html的,
1 | var st = document.body.scrollTop || document.documentElement.scrollTop; |
但是不知道什么原因(浏览器对scrollTop
和scrollLeft
都统一兼容了?毕竟视频是几年前的了),我这里并没有这个问题,所以上述问题存疑,待考究,后面以我实际代码测试结果为准
同理,当水平方向有滚动条时,也要消除水平方向上的距离差,所以综合代码如下
1 | box1.style.left = (document.documentElement.scrollLeft + left - box1.clientWidth / 2) + "px"; |
我这里通过documentElement
获取的scrollLeft
和scrollTop
在 Chrome、Edge、IE11、IE8 中均正常
2、事件的冒泡(Bubble)
HTML 代码
1 | <div id="box1"> |
CSS 代码
1 | #box1{ |
JS 代码
1 | document.getElementById("s1").onclick = function(){ |
所谓的冒泡指的就是事件的向上传导,当后代元素上的事件被触发时,其祖先元素的相同事件也会被触发
在开发中大部分情况冒泡都是有用的,如果不希望发生事件冒泡可以通过事件对象来取消冒泡
可以将事件对象的cancelBubble
设置为true
,即可取消冒泡
1 | document.getElementById("s1").onclick = function(event){ |
3、事件的委派(Delegate)
HTML 代码
1 | <button type="button" id="btn">Add</button> |
JS 代码
1 | function clickFun(){ |
这里我们为每一个超链接都绑定了一个单击响应函数,这种操作比较麻烦
而且这些操作只能为已有的超链接设置事件,而新添加的超链接必须重新绑定
我们希望,只绑定一次事件,即可应用到多个的元素上,即使元素是后添加的
我们可以尝试将其绑定给元素的共同的祖先元素
1 | ulDiv.onclick = function(){ |
事件委派是指将事件统一绑定给元素的共同的祖先元素
这样当后代元素上的事件触发时,会一直冒泡到祖先元素,从而通过祖先元素的响应函数来处理事件
事件委派是利用了冒泡,通过委派可以减少事件绑定的次数,提高程序的性能
但是也有个问题,我们是给整个 ul 绑定的单击响应事件,ul 是块元素,在超链接所在行点击任何位置都会触发事件
那怎么办呢?我们就需要再加一层判断: 如果触发事件的对象是我们期望的元素,则执行否则不执行
那怎么知道触发事件的对象是什么呢?
1 | ulDiv.onclick = function(event){ |
但是这种写法有点问题,当其class
属性有多个时,就不对了
1 | <li><a href="javascript:;" class="link hello">超链接1</a></li> <!-- 失效 --> |
我这里将tagName
代替className
作为判断条件进行判断
1 | ulDiv.onclick = function(event){ |
4、事件的绑定(Bind)
on事件名
使用对象.事件 = 函数
的形式绑定响应函数,它只能同时为一个元素的一个事件绑定一个响应函数
不能绑定多个,如果绑定了多个,则后边会覆盖掉前边的
1 | var btn = document.getElementById("btn"); |
addEventListener()
addEventListener()
通过这个方法也可以为元素绑定响应函数,参数:
- 事件的字符串,不要
on
- 回调函数,当事件触发时该函数会被调用
- 是否在捕获阶段触发事件,需要一个布尔值,一般都传
false
使用addEventListener()
可以同时为一个元素的相同事件同时绑定多个响应函数
这样当事件被触发时,响应函数将会按昭函数的绑定顺序执行
1 | btn.addEventListener("click", function(){ |
我们直接在 IE8 中进行测试,这个方法不支持IE8及以下的浏览器
那说了半天,IE8 需要用什么方法替代呢?
attachEvent()
attachEvent()
在 IE8 中可以用来绑定事件,参数:
- 事件的字符串,要
on
- 回调函数
1 | btn.attachEvent("onclick", function(){ |
继续测试,在 IE8 中没有报错,但是执行顺序却是相反的,而且其他浏览器中直接就不行了
总结: 这个方法也可以同时为一个事件绑定多个处理函数,不同的是它是后绑定先执行,执行顺序和addEventListener()
相反
看起来,我们还是要自己封装一个方法来兼容不同的浏览器
1 | // 定义一个函数,用来为指定元素绑定响应函数 |
我们调用下只能自定义的bind
函数
1 | bind(btn, "click", function() { |
测试下效果,发现在 IE8 和其他浏览器中均支持
好,我们接着再看个问题
1 | bind(btn, "click", function() { |
测试发现,在 Chrome 中打印的是[object HTMLButtonElement]
而在 IE8 中打印的却是[object window]
addEventListener()
中的this
是绑定事件的对象,attachEvent()
中的this
是window
,需要统一两个方法this
我们之前讲过call
和apply
方法,this
是指定的那个对象,是不是可以利用call
或者apply
方法对bind
函数进行优化呢?
1 | function bind(obj, eventStr, callback) { |
5、事件的传播
关于事件的传播网景公司和微软公司有不同的理解
- 微软公司认为事件应该是由内向外传播,也就是当事件触发时,应该先触发当前元素上的事件,然后再向当前元素的祖先元素上传播,也就说件应该在 冒泡阶段 执行
- 网景公司认为事件应该是由外向内传播的,也就是当前事件触发时,应该先触发当前元素的最外层的祖先元素的事件,然后在向内传播给后代元素
- W3C综合了两个公司的方案,将事件传播分成了三个阶段
- 捕获阶段:在捕获阶段时从最外层的祖先元素,向目标元素进行事件的捕获,但是默认此时不会触发事件
- 目标阶段:事件捕获到目标元素,捕获结束开始在目标元素上触发事件
- 冒泡阶段:事件从目标元素向他的祖先元素传递,依次触发祖先元素上的事件
如果希望在捕获阶段就触发事件,可以将addEventListener()
的第三个参数设置为true
一般情况下我们不会希望在捕获阶段触发事件,所以这个参数一般都是false
IE8 及以下的浏览器中没有捕获阶段
6、拖拽
拖拽的流程
- 当鼠标在被拖拽元素上按下时,开始拖拽
onmousedown
- 当鼠标移动时被拖拽元素跟随鼠标移动
onmousemove
- 当鼠标松开时,被拖拽元素固定在当前位置
onmouseup
HTML 代码
1 | <div id="box1"></div> |
CSS 代码
1 | #box1 { |
JS 代码
1 | var box1 = document.getElementById("box1"); |
效果
当我们拖拽一个网页中的内容时,浏览器会默认去搜索引擎中搜索内容,此时会导致拖拽功能的异常,这个是浏览器提供的默认行为
如果不希望发生这个行为,则可以通过return false
来取消默认行为
但是这招对 IE8 不起作用
那有什么方法可以兼容 IE8 呢?我们先接着往下看
setCapture()
1 | var btn1 = document.getElementById("btn1"); |
我们点击 btn2 按钮,发现只有刷新后的第一次点击的提示为1,再次点击就变成了2
我们可以利用setCapture()
方法对 IE8 浏览器的默认行为进行限制吗?当拖拽元素时捕获事件,取消拖拽时释放对事件的捕获
1 | var box1 = document.getElementById("box1"); |
测试在 IE8 中的效果
如果我想拖动 div2呢?这个时候我们需要封装一个函数,方便我们直接传参调用
1 | // 拖拽方法封装成一个函数 |
HTML 代码
1 | <div id="box1"></div> |
JS 代码调用函数
1 | var box1 = document.getElementById("box1"); |
效果
Chrome
IE8
滚轮事件与键盘事件
1、滚轮事件
onmousewheel、DOMMouseScroll
onmousewheel
:鼠标滚轮滚动的事件,会在滚轮滚动时触发,但是火狐不支持该属性
DOMMouseScroll
:在火狐中使用DOMMouseScroll
来绑定滚动事件,注意该事件需要通过addEventListener()
函数来绑定
event.wheelDelta、event.detail
event.wheelDelta
:可以获取鼠标滚轮滚动的方向:向上滚(120),向下滚(-120),这个值我们不看大小,只看正负
event.detail
:wheelDelta
这个属性火狐中不支持,在火狐中使用event.detail
来获取滚动的方向:向上滚(-3),向下滚(3)
return false、event.preventDefault()
当滚轮滚动时,如果浏览器有滚动条,滚动条会随之滚动,这是浏览器的默认行为
如果不希望发生,则可以使用return false
来取消默认行为
使用addEventListener()
方法绑定响应函数,取消默认行为时不能使用return false
,需要使用event
来取消默认行为
但是 IE8 不支持event.preventDefault()
这个玩意,如果直接调用会报错
1 | window.onload = function() { |
效果
2、键盘事件
onkeydown、onkeyup
onkeydown
按键被按下
- 如果一直按着某个按键不松手,则事件会一直触发
- 连续触发时,第一次和第二次之间会间隔稍微长一点,其他的会非常的快,这种设计是为了防止误操作的发生
onkeyup
按键被松开
键盘事件一般都会绑定给一些可以获取到焦点的对象或者是document
键盘事件属性
可以通过keyCode
来获取按键的编码,通过它可以判断哪个按键被按下
除了keyCode
,事件对象中还提供了几个属性altKey
、ctrlKey
、shiftKey
这个三个用来判断alt
、ctrl
和shift
是否被按下,如果按下则返回true
,否则返回false
<练习:键盘移动div>
1 | // 定义速度 |
效果
BOM
1、BOM
BOM:浏览器对象模型
BOM 可以使我们通过 JS 来操作浏览器
在 BOM 中为我们提供了一组对象,用来完成对浏览器的操作 BOM 对象
Window
代表的是整个 浏览器的窗口,同时 window 也是网页中的全局对象
Navigator
代表的当前 浏览器的信息,通过该对象可以来识别不同的浏览器
Location
代表当前 浏览器的地址栏信息,通过 Location 可以获取地址栏信息,或者操作浏览器跳转页面
History
代表 浏览器的历史记录,可以通过该对象来操作浏览器的历史记录由于隐私原因
该对象不能获取到具体的历史记录,只能操作 浏览器向前或向后翻页,而且该操作只在当次访问时有效
Screen
代表用户的 屏幕的信息,通过该对象可以获取到用户的显示器的相关的信息
这些 BOM 对象在浏览器中都是作为 window 对象的属性保存的,可以通过 window 对象来使用,也可以直接使用
1 | console.log(window); // [object Window] |
2、Navigator
由于历史原因,Navigator
对象中的大部分属性都已经不能帮助我们识别浏览器了
1 | console.log(navigator.appName); //Chrome/Firefox/Edge/IE11:Netscape;IE10及以下:Microsoft Internet Explorer |
那既然如此,我们要怎么判断不同的浏览器呢?
一般我们只会使用userAgent
来判断浏览器的信息,userAgent
是一个字符串
这个字符串中包含有用来描述浏览器信息的内容,不同的浏览器会有不同的userAgent
1 | console.log(navigator.userAgent); |
我们可以根据userAgent
中特有的标识符来判断是哪个浏览器
1 | var ua = navigator.userAgent; |
在 IE11 中已经将微软和 IE 相关的标识都已经去除了,所以我们基本已经不能通过userAgent
来识别一个浏览器是否是 IE 了
那么,我们要怎么判断统一是否是 IE 呢?
还是要 找特殊 ,我们根据之前知识,知道currentStyle
和attchEvent
是 IE 所特有的
除此之外,还有个ActiveXObject
也是 IE 中所特有的,我们可以根据这个来做判断
1 | // 利用`ActiveXObject`是 IE 中特有的属性,以及通过`window.属性 == undefined`特点来判断是否是 IE |
我们直接在 IE 中进行测试
不是说ActiveXObject
也是 IE 中所特有的吗?怎么不行呢?
我们在 IE11 中打印下window.ActiveXObject
是否等于true
1 | // 利用两次使用`!!`将任意值转换成bool值 |
What? 这?
别急,我们换种方式,利用in
来判断 window 中是否包含某个属性
1 | console.log("ActiveXObject" in window); // true |
我们来完善下对 IE11 的判断逻辑
1 | var ua = navigator.userAgent; |
3、Location
length
length
属性,可以获取到当次访问的链接数量
1 | alert(history.length); |
back()
可以用来回退到上一个页面,作用和浏览器的回退按钮一样
1 | history.back(); |
forward()
可以跳转下一个页面,作用和浏览器的前进按钮一样
1 | history.forward(); |
go()
可以用来跳转到指定的页面,它需要一个整数作为参数
- 1:表示向前跳转一个页面,相当于
forward()
- 2:表示向前跳转两个页面
- -1:表示向后跳转一个页面,相当于
back()
- -2:表示向后跳转两个页面
1 | history.go(2); |
1 | history.go(-2); |
4、Location
如果直接打印location
,则可以获取到地址栏的信息(当前页面的完整路径)
1 | alert(location); // http://127.0.0.1:8848/Demo/17-04-Location.html |
如果直接将location
属性修改为一个完整的路径,或相对路径则我们页面会自动跳转到该路径,并且会生成相应的历史记录
1 | location = "http://www.baidu.com"; |
1 | location = "17-03-History.html"; |
其他属性方法
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)
1 | location.reload(true); |
定时调用与延时调用
1、定时调用
JS 的程序的执行速度是非常非常快的如果希望一段程序,可以每间隔一段时间执行一次,可以使用定时调用
setInterval()
定时调用,可以将一个函数,每隔一段时间执行一次
参数:
- 回调函数,该函数会每隔一段时间被调用一次
- 每次调用间隔的时间,单位是毫秒
返回值:返回一个Number
类型的数据,这个数字用来作为定时器的唯一标识
1 | var num = 1; |
1 | setInterval(function(){ |
clearInterval()
可以用来关闭一个定时器,方法中需要一个定时器的标识作为参数,这样将关闭标识对应的定时器
1 | var timer = setInterval(function(){ |
<练习1:定时图片切换>
HTML 代码
1 | <img src="img/1.jpg" id="img" /><br> |
JS 代码
1 | var btnStart = document.getElementById("btnStart"); |
效果
注意点一:循环切换图片
当索引超过最大索引时,需要将索引重置,以达到轮播图片之目的
1 | // if(index >= imgArr.length){ |
注意点二:不点击开始,而直接点击结束
clearInterval()
可以接收任意参数
- 如果参数是一个有效的定时器的标识,则停止对应的定时器
- 如果参数不是一个有效的标识,则什么也不做
即使没有点开始,timer 为 undefined 也不会报错,可以放心大胆的去使用
注意点三:多次点击开始按钮导致切换速度过快问题
目前,我们每点击一次按钮,就会开启一个定时器,点击多次就会开启多个定时器
这就导致图片的切换速度过快,并且我们只能关闭最后一次开启的定时器
在开启定时器之前,需要将当前元素上的其他定时器关闭
<练习2:div移动优化>
1 | // 定义速度 |
效果
优化思路
- 定时器控制方向,键盘按下控制速度和捕获方向,键盘松开清空速度和方向
这就好比一辆汽车,速度就像汽车的油门,定时器就像汽车的方向盘,而键盘就像汽车的离合和档位
油门一直在踩着,发动机就一直匀速运转,就能保证速度一直存在,启动或转向就不会出现卡顿的现象
当键盘按下时,就是松离合换挡位;当键盘松开时,就是踩离合
不过,跟现实世界不同的是,JS 的世界没有惯性,所以只要松离合,div 就不会再移动了
2、延时调用
setTimeout()、clearTimeout()
延时调用,延时调用一个函数不马上执行,而是隔一段时间以后在执行,而且只会执行一次
延时调用和定时调用的区别:定时调用会执行多次,而延时调用只会执行一次
延时调用和定时调用实际上是可以互相代替的,在开发中可以根据自己需要去选择
1 | var num = 1; |
3、定时器的应用(一)
<练习:点击按钮div移动>
HTML 代码
1 | <button type="button" id="btn1">点击按钮box1向右移动</button> |
CSS 代码
1 | * { |
JS 代码
1 | // 自定义兼容所有浏览器获取元素样式的方法 |
优化1:封装移动方法
1 | // 封装移动方法 |
优化2:智能判断方向
1 | function move(obj, target, speed) { |
优化3:消除多个div影响
目前我们的定时器的标识由全局变量 timer 保存,所有的执行正在执行的定时器都在这个变量中保存
那么我们就不能定义全局的了,而是需要向执行动画的对象中添加一个 timer 属性,用来保存它自己的定时器的标识
1 | function move(obj, target, speed) { |
这样,执行动画的对象之间就不会再互相产生影响了
优化4:支持多属性
只需要将left
相关的属性改为变量传入
1 | // obj:要执行动画的对象 |
调用修改后的函数
1 | btn1.onclick = function() { |
优化5:添加回调函数
1 | // obj:要执行动画的对象 |
调用回调函数
1 | btn4.onclick = function() { |
优化6:封装JS文件
新建 js 文件夹,新建 tools.js 文件,复制 move 相关方法
1 | // 自定义兼容所有浏览器获取元素样式的方法 |
最后再引入 js 文件,大功告成
1 | <script src="js/tools.js" type="text/javascript" charset="utf-8"></script> |
4、定时器应用(二)
<练习:轮播图>
HTML 代码
1 | <div id="outer"> |
CSS 代码
1 | /* 去除浏览器默认样式 */ |
JS 代码
1 | window.onload = function() { |
5、类的操作
修改class属性
HTML 代码
1 | <button type="button" id="btn1">点击按钮修改box1样式</button> |
CSS 代码
1 | .b1{ |
JS 代码
1 | box1.style.width = "200px"; |
通过style
属性来修改元素的样式,每修改一个样式,浏览器就需要重新渲染一次页面
这样执行的性能是比较差的,而且这种形式当我们要修改多个样式时,也不太方便
那怎么办呢?
我们可以先事先定义好一个 class 属性,里面写好我们需要变化的样式
1 | .b2{ |
然后在 JS 中修改className
属性即可
1 | box1.className = "b2"; |
效果是一样的
我们可以通过修改元素的class
属性来间接的修改样式
这样一来,我们只需要修改一次,即可同时修改多个样式
浏览器只需要重新渲染页面一次,性能比较好,并且这种方式,可以使表现和行为进一步的分离
添加class属性
我们可以在此样式基础之上,定义一个函数,用来向一个元素中添加指定的 class 属性值
1 | // 参数: |
1 | //.b3{ |
但是也存在一个问题,虽然从效果上来看没有什么不同,但多次点击后会重复添加相同的 class 属性,而这个操作是多余的
我们就需要在写一个函数来判断是否已经存在 class 属性
1 | function hasClass(obj, cn) { |
删除class属性
删除一个元素中的指定的 class 属性
1 | function removeClass(obj, cn) { |
切换class属性
1 | // toggleClass可以用来切换一个类 |
<练习:二级菜单>
HTML 代码
1 | <div id="my_menu" class="sdmenu"> |
CSS 代码
1 | @charset "utf-8"; |
JS 代码
1 | // 为兼容IE8,用querySelectorAll |
添加动画的过渡效果
1 | var beginHeight; |
因为我们执行动画前添加了一个内联高度,而内联属性的优先级是最高的
当添加collapsed
的 class 属性后不会起作用,因此同时需要在动画执行完毕后去除内联样式
1 | move(thisNode, "height", endHeight, 30, function(){ |
我们只对展开添加了动画效果,折叠时并没有添加动画
因为添加动画的逻辑是一致的,所以这里我们可以封装一个函数,用来执行带有动画效果的折叠和展开动作
1 | // 带有动画效果的折叠和展开动作 |
调用 toggleMenu 函数
1 | for (var i = 0; i < menuSpan.length; i++) { |
JSON
JS 中的对象只有 JS 自己认识,其他的语言都不认识
JSON
就是一个特殊格式的字符串,这个字符串可以被任意的语言所识别,并且可以转换为任意语言中的对象,JSON
在开发中主要用来数据的交互
JSON简介
JavaScript Object Notation,JS 对象表示法
JSON
和 JS 对象的格式一样,只不过 JSON
字符串中的属性名必须加双引号,其他的和JS语法一致
JSON分类
对象{}
数组[]
1 | var obj = { |
JSON中允许的值
- 字符串
- 数值
- 布尔值
null
- 对象
- 数组
1 | // json对象可以包含json数组 |
JSON和JS间转换
在 JS 中,为我们提供了一个工具类,就叫JSON
这个对象可以帮助我们将一个JSON
转换为 JS 对象,也可以将一个 JS 对象转换JSON
JSON.parse()
可以将JSON
字符串转换为 JS 中的对象
需要一个JSON
字符串作为参数,会将该字符串转换为 JS 对象并返回
1 | var jsonObj = JSON.parse(jsonObjStr); |
JSON.stringify()
可以将一个 JS 对象转换为JSON
字符串
需要一个 JS 对象作为参数,会返回一个JSON
字符串
1 | var obj2 = { |
JSON
对象在 IE7 及以下的浏览器中不支持,所以在这些浏览器中调用时会报错
eval()
这个函数可以用来执行一段字符串形式的 JS 代码,并将执行结果返回
1 | var str = 'alert("hello")'; |
如果使用eval()
执行的字符串中含有{}
,它会将{}
当成是代码块
1 | var jsonObjStr = '{"name": "孙悟空","age": 1000,"gender": "男"}'; |
如果不希望将其当成代码块解析,则需要在字符串前后各加一个()
1 | var jsonObjStr = '{"name": "孙悟空","age": 1000,"gender": "男"}'; |
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: "孙悟空"} |
一开始介绍JSON
时,说JSON
字符串中的属性名必须加双引号
如果我就是不加呢?
1 | var jsonObjStr = '{name: "孙悟空","age": 1000,"gender": "男"}'; |
那就是一堆错误等着你了