学习笔记:JavaScript(尚硅谷)

介绍

  • 本文主要记录在学习尚硅谷的 JavaScript 课程时的一些笔记
  • 在线手册点击 这里
  • 尚硅谷前端学科全套课程请点击这里进行下载,提取码:afyt

一、基础知识

1.简介

  • JavaScript 主要用于处理网页中的前端验证

  • ECMAScript 是如今 JavaScript 都遵循的一个标准

    • 缺陷:没有模块系统、标准库较少、没有标准接口、缺乏管理系统
  • JS 的组成:
    js01.png

2.特点

  • 解释型语言

  • 类似于 C 和 Java 的语法结构

  • 动态语言

  • 基于原型的面向对象

3.JS 编写位置

(1).内联样式

  • 直接将 js 代码编写到标签的 onclick 的属性中去

1
<button onclick="alert('讨厌,你点我干嘛');">点我一下</button>
  • 也可以直接将 js 代码编写到超链接的 href 属性中,这样点击的时候就会执行 js 代码了

1
<a href="javascript:alert('让你点就点!');">你也点我一下</a>
  • 这样属于结构与行为耦合,不推荐使用

(2).内部样式

  • 使用 <script> 标签来编写 js 代码

1
2
3
<script type="text/javascript">
alert("我是script中的代码");
</script>

(3).外部样式

  • 使用 <script> 标签来引入外部的 js 文件

1
<script type="text/javascript" src="./js/index.js"></script>
  • 该标签一旦用于引入外部文件,就不能在编写代码了,如果需要可以重新创建一个 <script> 标签

4.基本语法

  • JS 中严格区分大小写

  • JS 中每一条语句都以 ; 结尾

  • JS 中会忽略多个空格和换行

5.常量和变量

(1).字面量(常量)

  • 都是一些不可改变的值

  • 字面量都是可以直接使用的,但一般不直接使用

(2).变量

  • 变量可以用来保存字面量,而且变量的值是可以任意改变的,故开发中都是通过变量去保存一个字面量的,很少直接使用字面量

  • 声明变量的方法:在 JS 中使用 var 关键字来声明一个变量

1
2
3
4
var a;
a = 123;
简写为:var a = 123;
console.log(a);
  • 可以通过遍历对字面量进行描述

  • 注意:使用 var 关键字声明的变量,会在所有的代码执行之前被声明但是不会赋值

(3).数据类型

  • 数据类型就是字面量的类型,在 JS 中一共有6种数据类型,如下:
    String:字符串
    Number:数值
    Boolean:布尔值
    Null:空值
    Undefined:未定义
    Object:对象

  • 前面5个是基本(值)数据类型,最后一个是对象(引用)数据类型

String

  • 在 JS 中字符串需要使用引号引起来

  • 引号不能嵌套,可以外单内双,外双内单

  • 在字符串中可以使用 \ 作为转义字符,表示特殊符号时可以使用转义字符来转义

Number

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

  • 可以使用一个运算符 typeof 来检查一个变量的类型

1
2
var a = 123;
console.log(typeof a);
  • JS 中可以表示数字的最大值,即 Number.MAX_VALUE ;如果使用 Number 表示的数字超过最大值,则会返回一个 Infinity ,表示正无穷(如:Number.MAX_VALUE * Number.MAX_VALUE);而加个负号,则会返回一个 -Infinity,表示负无穷

  • Infinity 就是一个字面量

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

  • JS 中可以表示数字的最小值,即 Number.MIN_VALUE注意:这表示一个0以上的最小值

  • 在 JS 中整数的运算基本可以保证精确,而浮点数的运算可能得到一个不精确的结果

1
2
var a = 123 + 456;
var b = 0.1 + 0.2;

Boolean

  • 布尔值只有两个,真与假

1
var bool = true;
  • 使用 typeof 检查一个布尔值时,显示 boolean

Null

  • Null 类型的值只有一个,就是 null,专门用来表示一个为空的对象

  • 使用 typeof 检查一个 Null 时,显示 object

Undefined

  • Undefined 类型的值只有一个,就是 undefined

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

1
2
var b;
console.log(b);

Object

  • 引用数据类型

  • 含有两个特别的对象——Function和Array

(4).强制数据类型转换

  • 指将一个数据类型强制转换为其他的数据类型,主要是将其他的数据类型转换为 String、Number、Boolean

转换为 String

  • 方式一:调用被转换数据类型的 toString() 方法,该方法不会影响到原变量。注意:null 和 undefined 这两个值没有 toString() 方法

1
2
3
4
5
var a = 123;
//调用a的toString()方法
var b = a.toString();
或:
var a = a.toString();
  • 方式二:调用 String() 函数并将被转换的数据作为参数传递给函数。对于 Number 和 Boolean 实际上就是调用的 toString() 方法;而对于 null 和 undefined ,它会直接转换为字符串

1
2
var a = 123;
a = String(a);

转换为 Number

  • 方式一:使用 Number() 函数来将 a 转换为 Number类型

  • 字符串 - > 数字:

    • 若是纯数字的字符串,则转换为数字
    • 若字符串中有非数字的内容,则转换为 NaN
    • 若字符串是空串或是一个全是空格的字符串,则转换为0
  • 布尔 - > 数字:

    • true 转成1,false 转成0
  • Null - > 数字:为0

  • Undefined - > 数字:为NaN

1
2
var a = "123 ";
a = Number(a);
  • 方式二:使用 parseInt() 函数来将一个字符串转换为一个整数;使用 parseFloat() 函数来将一个字符串转换为一个浮点数

  • 对于非 String 类型使用以上两个函数会先将其转换为 String 类型,然后再操作(因此,该方式只适用于字符串类型)

1
2
3
4
a = "123px ";
b = "123.456 ";
a = parseInt(a);
b = parseFloat(b);
  • 为了避免有些浏览器将字符串 070 转数字时当8进制解析,可以在 parseInt() 中传递第二个参数,来制定数字的进制

1
2
a = "070";
a = parseInt(a,10);

转换为 Boolean

  • 使用 Boolean() 函数来将其他数据类型转换为 Boolean

  • 数字 - > 布尔:除了 0 和 NaN,其余的都是 true

  • 字符串 - > 布尔:除了空串,其余的都是 true

  • Null和Undefined - > 布尔:都是 false

  • 对象 - > 布尔:为 true

(5).隐式的数据类型转换

  • 将任意的数据类型转换为字符串可以在该数据类型后面加一个空串,实际上也是调用了 String() 函数

  • 将任意的数据类型转换为数字可以在该数据类型后面 -0 或 *1 或 /1,实际上也是调用了 Number() 函数

  • 将任意的数据类型转换为数字还可以使用一元运算符的 + ,原理和 Number() 函数一样

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

(6).其他进制的数字表示

  • 在 JS 中表示16进制的数字,则需要以 0x 开头

  • 在 JS 中表示8进制的数字,则需要以0开头

  • 在 JS 中表示2进制的数字,则需要以 0b 开头,但有些浏览器并不支持

6.标识符

在 JS 中所有可以由我们自主命令的都为标识符,如:变量名,函数名,属性名

标识符规则:

  1. 标识符可以含有 字母、数字、_、$

  2. 标识符不能以数字开头

  3. 标识符不能是 ES 中的关键字或保留字

  4. 标识符一般采用驼峰命名法(首字母小写,每个单词开头大写)

JS 中的关键字或保留字有:
js02.png

JS 中其他不建议使用的标识符
js03.png

JS 底层保存标识符时实际上是采用的 Unicode 编码的方式

7.运算符

  • 运算符都会返回一个结果的

  • typeof 可以获得一个值的类型,将该值的类型以字符串的形式返回

(1).算术运算符:+ - * / %

  • 当对非 Number 类型的值进行运算时,都会将这些值转为 Number 再进行计算

  • 任何值和 NaN 做运算,都是 NaN
    如果对两个字符串进行加法运算,则会进行拼串操作

  • 任何的值和字符串做加法运算时,都会先转换为字符串,然后再执行拼串操作

  • 任何值做 - * / 运算时都会自动转换为 Number

(2).一元运算符

  • +:正号,结果不变

  • -:负号,用于取反

(3).自增自减运算符

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

  • a++ 是指原值,而 ++a 是指 a+1 的值

(4).逻辑运算符

  • ! 表示,即为取反操作

  • 若对非布尔值进行取反,则将其转换为布尔值,再取反

  • && 表示,JS 中的与是短路与

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

  • 如果两个值都为 true ,则返回后边的

  • 如果两个值中有 false,则返回靠前的 false

  • ||表示,JS 中的或是短路或

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

  • 如果第一个值为 true,则直接返回第一个值

  • 如果第一个值为 false,则直接返回第二个值

(5).赋值运算符

  • =+=-=*=/=%=

(6).关系运算符

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

  • ><>=<=

  • 非数值的情况下:

  • 会将非数值转换为数值,然后再比较

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

  • 如果符号两端的值都是字符串时,不会将其转换为数值,而是分别比较字符串中字符的 Unicode 编码,比较时是一位一位进行比较,如果两位一样,则比较下一位(比较中文时没有意义)

  • 注意:比较两个字符串型的数字时,一定一定要转型

(7).相等运算符

  • 通过相等运算符可以比较两个值是否相等,相等返回 true,不相等返回 false

  • ==!=

  • 如果值的类型不同,则会自动进行类型转换,将其转换为相同的类型,然后再比较

  • 注意:

  • Undefined 衍生自 Null,所以这两个值进行相等判断时,会返回 true

  • NaN 不和任何值相等,包括本身,因此 JS 中可以通过isNaN() 函数来判断一个值是否是 NaN

  • === 为全等,用来判断两个值是否全等,和相等类似,但它不会自动进行类型转换

  • !== 为不全等,用来判断两个值是否不全等,和不等类似,但它不会自动进行类型转换

(8).条件(三元)运算符

  • 语法:条件表达式?语句1:语句2;

  • 流程:先对条件表达式进行求值,若该值为 true ,则执行语句1,反之语句2

  • 可以嵌套来判断3个值中的最大值,但不推荐使用

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

(9).逗号运算符

  • 使用 , 可以分割多个语句,一般在声明多个变量时使用

(10).in 运算符

  • 通过该运算符可以检查一个对象中是否含有指定的属性,如果有则返回 true,没有则返回 false

  • 语法:“属性名” in 对象

(11).运算符的优先级

  • 具体可以看如下图:
    js04.png

8.Unicode编码表

  • 在 JS 中需要在字符串中使用转义字符输出 Unicode 编码,即 \u四位编码

  • 在网页中使用转义字符输出 Unicode 编码,即 &#编码(这里的编码是10进制的)

9.代码块

  • 在 JS 中可以使用 {} 来为语句进行分组,同一个大括号中的语句为一组语句,要么都执行,要么都不执行

  • 代码块的后面不需要分号

  • JS 中的代码块只具有分组的作用

10.垃圾回收(GC)

  • 当一个对象没有任何的变量或属性对它进行引用时,我们将永远无法操作该对象,此时这种对象就是垃圾,垃圾过多会占用大量的内存空间,导致程序运行变慢,所以垃圾必须清理

  • 在 JS 中拥有自动的垃圾回收机制,会自动将这些垃圾对象从内存中销毁

  • 我们只需要将不再使用的对象设置为 null 即可 obj = null;

二、流程控制语句

1.if —— 条件判断语句

  • 语法1: if(条件表达式) 语句 ,多条执行语句用代码块包含(单条执行语句也建议使用)

  • 语法2:

1
2
3
4
5
if(条件表达式){
语句
}else{
语句
}
  • 语法3:可以多个 else if,该语法一旦有一个语句执行成功,剩余的语句都不会执行

1
2
3
4
5
6
7
if(条件表达式){
语句
}else if(条件表达式){
语句
}else{
语句
}
  • 注意:一般需要对非法数值进行处理,可以在 if···else 再嵌套 if···else 来进行处理

1
2
3
4
5
6
7
8
9
10
11
12
13
if(grade > 100 || grade < 0 || isNaN(grade)){
alert("输入非法数值!");
}else{
if(grade < 60){
alert("很遗憾,没有任何奖励!");
}else if(grade < 80){
alert("奖励一本参考书");
}else if(grade < 99){
alert("奖励一台ipad");
}else{
alert("奖励一台宝马");
}
}

2.switch —— 条件分支语句

  • 该语句会依次将 switch 后的条件表达式和 case 后的条件表达式进行全等比较

  • 语法如下:

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

3.循环语句

(1).while

  • 用法如下:

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

(2).do···while

  • 用法如下:

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

(3).for

  • 用法如下:

1
2
3
for(初始化表达式;条件表达式;更新表达式){
语句;
}
  • for 循环中的三个部分都可以省略,也可以写在外部;若不写任何表达式,只写两个分号,则为死循环

!质数练习中的思维:

  • 如果无法通过余数不为0来进行判断,那么可以逆向思维,用余数为0来判断。可以使用中间变量来取出所需要的值

  • 为了提高性能,可以适当的使用 break

  • 实际上,内层循环有一部分的运算是毫无意义的,当检验到一个数的开方处时,后面的数值其实已经都进行过判断了,所以内层循环就不需要一直运算到i,只要运算到i的开方即可,即:

1
2
3
for(var j=2;j<=Math.sqrt(i);j++){
代码;
}

!九九乘法表中的思维:

  • 在使用for循环实现九九乘法表时,发现虽然在每个式子中间加了空格,但其实在前几行是上下不对齐的,这样并不美观

  • 解决方法就是,在 document 函数中前后用双引号与加号填入 <span> 标签,然后对 <span> 标签进行相应的css设置,如下:

1
2
3
4
span{
display: inline-block;
width: 100px;
}

4.break & continue

(1).break

  • 用于退出 switch 和循环语句,他会立即终止离他最近的那个循环语句

  • 可以为循环语句创建一个 label,来标识当前的循环,语法为:label: 循环语句 ,使用 break 语句时,在 break 后跟上 label,这样 break 会结束指定的循环,而不是最近的

  • 灵活使用 break 可以提高性能

(2).continue

  • 用于跳过当次循环,默认只会对离他最近的循环起作用

  • 跳过指定循环与 break 类似,使用 label

三、对象

1.基础

  • 即 Object,为引用数据类型,之前的都为基本数据类型,基本数据类型都是单一的值,值和值之间没有任何的联系

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

  • 一个对象代表现实中的一个事物

  • 对象由属性和方法(属性值是函数)组成

2.分类

(1).内建对象

  • 由ES标准中定义的对象,在任何的ES的实现中都可以使用,如:Math、String、Number、Function、Object

(2).宿主对象

  • 由JS的运行环境提供的对象,目前来讲主要指由浏览器提供的对象,如:BOM、DOM

(3).自定义对象

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

3.基本操作

(1).创建对象

  • 使用 new 关键字调用构造函数

1
var obj = new Object();

(2).向对象添加属性

  • 对象中保存的值为属性

  • 语法:对象.属性名 = 属性值

1
obj.name = "孙悟空";

(3).读取对象的属性

  • 语法:对象.属性名

  • 注意:如果读取对象中没有的属性,不会报错但会返回 undefined

(4).修改对象的属性值

  • 语法:对象.属性名 = 属性值

(5).删除对象的属性

  • 语法:delete 对象.属性名

4.属性名和属性值

  • 对象的属性名不强制要求遵循标识符的规范,尽量避免

  • 如果使用特殊的属性名,不能采用点的方式,需要使用该方法 —— 语法为:对象[“属性名”] = 属性值;读取时也使用该方式

1
2
obj["123"] = 456;
console.log(obj["123"]);
  • JS 中的属性值可以是任意的数据类型,包括对象与函数

  • 使用 in 运算符检查是否含有指定属性,如下:

1
2
3
var obj = new Object();
obj.name = "孙悟空";
console.log("name" in obj);

5.基本与引用数据类型

  • 基本数据类型的值是直接在栈内存中存储的,值与值之间是独立存在的

  • 引用数据类型中的对象是保存到堆内存中,每创建一个对象,就会在堆内存中开辟一个新的空间,用来保存属性名与属性值,在栈内存中变量存的是对象的名字,值保存的是对象的内存地址,如下:
    js05

  • 当比较两个引用数据类型时,比较的是地址的值!!!

6.对象字面量

  • 使用对象字面量可以在创建对象的时候,直接指定对象中的属性

  • 语法:

1
2
3
4
5
6
7
8
9
10
var obj = {属性名:属性值,属性名:属性值....};	//可以有无数个,最后一个不加逗号
格式美化如下:
var obj = {
name:"猪八戒",
age:18,
gender:"男",
test:{
name:"沙和尚"
}
}
  • 对象字面量的属性名可以加引号也可以不加,如果要使用一些特殊的名字,则必须加引号

7.函数作为对象的属性

  • 如果一个函数作为一个对象的属性保存,则称为该对象的方法

  • 调用这个函数即为调用对象的方法

8.枚举对象中的属性

  • 使用 for in 语句,语法为:

1
2
for(var 变量n in 对象){
}
  • 每次执行时,会将对象中的一个属性的名字赋值给变量,所以取对象的属性与属性值可以如下操作:

1
2
3
4
for(var n in obj){
console.log(n); //取对象的属性
console.log(obj[n]); //取对象的属性值
}

四、内建对象

1.函数对象

(1).简介

  • 函数也是个对象,函数中可以封装一些功能,在需要时可以执行这些功能

1).使用构造函数创建函数对象

  • 创建一个函数对象,语法如下:

1
var fun = new Function("代码");
  • 将要封装的代码以字符串的形式传递给构造函数

  • 调用函数的语法:函数对象(),如:fun()

  • !注意:开发中不常用

2).使用函数声明来创建函数

  • 语法:

1
2
3
function 函数名([形参1,形参2,形参....]){		//这里中括号是可选的意思,实际开发中不写
语句...
}
  • 调用方法:函数名()

  • 注意:使用该方式创建的函数会在所有的代码执行之前就被创建

3).使用函数表达式创建函数

  • 声明匿名函数然后赋值给了变量,语法:

1
2
3
var 函数名 = function([形参1,形参2,形参....]){		//这里中括号是可选的意思,实际开发中不写
语句...
};

(2).函数的参数

  • 可以在函数的()中指定一个或多个形参,之间使用 , 隔开

  • 在调用函数时,可以在()中指定实参,如下:

1
2
3
4
function sum(a,b){
console.log(a+b);
}
sum(1,2);
  • 调用函数解析器时不会检查实参的类型,因此可能会接收到非法的参数

!对象也可以作为函数的实参

  • 当需要传入多个参数时,可以使用对象来进行封装,如下:

1
2
3
4
5
6
7
8
9
function sayHello(o){
console.log("我是"+o.name+",我今年"+o.age+"岁了,我住在"+o.address+"。");
}
var obj = {
name:"张砚耕",
age:23,
address:"大同大学"
};
sayHello(obj);

!函数也可以作为函数的实参

  • fun(mianji(10)); :调用函数,相当于使用的函数的返回值

  • fun(mianji); :函数对象,相当于直接使用函数对象

(3).返回值

  • 可以使用 return 来设置函数的返回值,且其后的语句不会执行

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

1
2
3
4
5
6
function sum(a,b,c){
var d = a + b + c;
return d;
}
var result = sum(1,2,3);
console.log(result);

(4).立即执行函数(IIFE)

  • 函数定义完以后,立即被调用,称为立即执行函数

  • 将匿名函数放在括号中,就不会出现报错,最后再加一个括号,就变成了立即执行函数:

1
2
3
(function(){
alert("我是匿名函数!");
})();

(5).作用域

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

  • JS 中分为全局作用域和函数作用域

1).全局作用域

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

  • 在页面打开时创建,在页面关闭时销毁

  • 在该作用域中有一个全局对象 window ,代表浏览器的窗口

  • 在该作用域中,创建的变量都会作为 window 对象的属性保存,而创建的函数都会作为方法保存

2).函数作用域

  • 在函数调用时创建,在函数执行完毕时销毁

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

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

  • 在函数作用域中也有声明提前的特性

  • 在函数中不使用 var 声明的变量都是全局变量

  • 定义形参就相当于在函数作用域中声明了变量,所以此时无法访问全局变量下的同名变量

(6).两个隐含参数

1).this

  • 浏览器在调用函数时每次都会向函数内部传递进两个隐含的参数,其中一个是 this

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

  • 根据函数的调用方式的不同,this 会指向不同的对象以函数的形式调用时,this永远都是window
    以方法的形式调用时,this就是调用方法的那个对象(在事件的相应函数中,响应函数是给谁绑定的,this就是谁)以构造函数的形式调用时,this就是新创建的那个对象以call()和apply()方法调用时,this就是指定的那个对象

2).arguments

  • 浏览器在调用函数时每次都会向函数内部传递进两个隐含的参数,其中一个是 arguments

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

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

  • 我们即使不定义形参,也可以通过 arguments 来使用实参,只不过比较麻烦。比如:arguments[0]表示第一个实参,arguments[1]表示第二个实参

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

1
2
3
4
5
function fun(a,b){
console.log(arguments[0]);
console.log(arguments.callee);
}
fun(1,2);

(7).创建对象

1).使用工厂方法创建对象

  • 通过该方法可以大批量的创建对象

  • 该方法主要思想是创建一个对象及属性的容器,然后在需要调用时直接传入相应的参数即可,具体如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function creatPerson(name,age,gender){
//创建一个新的对象
var obj = new Object;
//向对象中添加属性
obj.name = name;
obj.age = age;
obj.gender = gender;
obj.sayName = function(){
alert(this.name);
};
return obj;
}
var obj1 = creatPerson("孙悟空",22,"男");
var obj2 = creatPerson("猪八戒",21,"男");
obj1.sayName();
  • 在创建的函数中需要将自己创建的对象返回

  • 缺点:使用的构造函数都是 Object,所以创建的对象都是 Object,导致无法区分出多种不同类型的对象

2).使用构造函数创建对象

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

区别:普通函数直接调用,构造函数使用new关键字来调用

构造函数的执行流程:

  1. 立刻创建一个新的对象

  2. 将新建的对象设置为函数中的 this,在构造函数中可以使用 this 来引用新建的对象

  3. 逐行执行函数中的代码

  4. 将新建的对象作为返回值返回

构造函数称为类,而其创建的对象称为一类对象,通过一个构造函数创建的对象称之为该类的实例

基础法创建:
  • 如下:

1
2
3
4
5
6
7
8
9
10
11
function Person(name,age,gender){
this.name = name;
this.age = age;
this.gender = gender;
//向对象中添加一个方法
this.sayName = function(){
alert(this.name);
}
}
var per = new Person("孙悟空",18,"男");
console.log(per);
  • 缺点:该方法中的函数每执行一次就会创建一个新的方法,完全可以使所有的对象共享同一个方法

进阶法创建:
  • 如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
function Person(name,age,gender){
this.name = name;
this.age = age;
this.gender = gender;
//向对象中添加一个方法
this.sayName = fun;
}
//将sayName方法在全局作用域中定义
function fun(){
alert(this.name);
}
var per = new Person("孙悟空",18,"男");
console.log(per);
  • 缺点:将函数定义在全局作用域中,污染了全局作用域的命名空间,而且很不安全

原型法创建(完美!):
  • 原型相关内容可以看第8点,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
function Person(name,age,gender){
this.name = name;
this.age = age;
this.gender = gender;
}
//向原型中添加sayName方法
Person.prototype.sayName = function(){
alert(this.name);
}
var per = new Person("孙悟空",18,"男");
console.log(per);
per.sayName();

(8).原型(prototype)

1).基础内容

  • 我们所创建的每一个函数,解析器都会向函数中添加一个属性 prototype,这个属性对应着一个对象,这个对象就是原型对象

  • 如果函数作为普通函数调用时,prototype 没有任何作用;如果函数以构造函数形式调用时,它所创建的对象中都会有一个隐含的属性,指向该构造函数的原型对象,可以通过 __proto__ 来访问该属性(两个下划线)

  • 原型对象就相当于一个公共的区域,所有同一个类的实例都可以访问到这个原型对象,所以向这个原型对象中添加属性即 MyClass.prototype.a = 123;
    js06.png

  • 当我们访问对象的一个属性或方法时,它会先在对象自身中寻找,如果有则直接使用;如果没有则会去原型对象中寻找,如果有则直接使用;如果没有则去原型的原型中寻找,直到找到 Object 对象的原型(它没有原型),如果在 Object 的原型中依然没有找到,则返回 undefined

  • 总结:以后我们创建构造函数时,可以将这些对象共有的属性和方法,统一添加到构造函数的原型对象中,就可以使每个对象都具有这些属性和方法了

2).检查非原型对象中的属性

  • 使用in运算符可以检查到对象中含有某个原型对象的属性,但实际是要检查该对象中有没有属性,所以可以使用对象的 hasOwnProperty() 来检查对象自身是否含有该属性

1
console.log(per.hasOwnProperty("name"));

3).原型对象的toString()方法

  • 当我们直接在页面中打印一个对象时,实际上是输出对象的 toString() 方法的返回值

  • 如果我们希望在输出对象时不输出 [object Object] ,可以为原型对象添加一个 toString() 方法,且方法中需要填写返回值

  • 如果想要输出对象内容,即 Person[name=孙悟空,age=18,gender=男] 这样的格式,如下操作:

1
2
3
Person.prototype.toString = function(){
return "Person[name="+this.name+",age="+this.age+",gender="+this.gender+"]";
}
  • 注意:谷歌浏览器不会输出 toString() 方法,而是会直接输出对象中的内容

(9).常见函数

1).alert()

  • alert() 函数可以控制浏览器弹出一个警告框

  • 语法如下:

1
alert("这是第一条函数");

2).confirm()

  • 用于弹出一个带有确认和取消按钮的提示框

  • 需要一个字符串作为参数,该字符串将会作为提示文字显示出来

  • 如果用户点击确认则返回true,反之返回false

3).document.write()

  • document.write() 函数可以在页面中输出一个内容

  • 语法如下:

1
document.write("页面中的内容");
  • 因为该语句是在网页的 <body> 中显示的,所以如果要在这里面换行的话,是需要使用换行标签的,而不是 \n

4).console.log()

  • console.log() 函数可以在控制台中输出一个内容

  • 语法如下:

1
console.log("控制台中的内容");

5).console.time()

  • console.time() 函数可以开启一个计时器,它需要一个字符串作为参数,这个字符串会作为计时器的标识

  • 该函数与 console.timeEnd() 函数一起使用

  • 语法如下:

1
2
3
console.time("test");
中间代码
console.timeEnd("test");

6).prompt()

  • prompt() 函数可以弹出一个提示框,且提示框中会带有文本框

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

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

  • 语法如下:

1
var test = prompt("请输入数字:");
  • 注意:该函数的返回值是 String 类型的,如果想要转换为数值类型的,则需要在其前面加一个 + 号

1
var num1 = +prompt("请输入变量1:");

7).parseInt()

  • 可以对值进行取整

8).Math.sqrt()

  • 可以对一个数值进行开方

(10).函数的方法

1).call()和apply()

  • 这两个是函数对象的方法,需要通过函数对象来调用(即没有括号)

  • 可以让一个函数成为指定任意对象的方法进行调用

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

  • call() 方法可以将实参在对象之后依次传递

1
2
3
4
5
6
7
8
9
10
11
function fun(a,b){
console.log("a = "+a);
console.log("b = "+b);
}
var obj = {
name: "obj",
sayName: function(){
alert(this.name);
}
};
fun.call(obj,3,4);
  • apply() 方法需要将实参封装到一个数组中统一传递

1
2
3
4
5
6
7
8
9
10
11
function fun(a,b){
console.log("a = "+a);
console.log("b = "+b);
}
var obj = {
name: "obj",
sayName: function(){
alert(this.name);
}
};
fun.apply(obj,[3,4]);

2).bind()

  • 该方法主要将函数绑定到某个对象中,可以解决 this 相关问题

1
2
3
function().bind(this);
// 等价于
this.function()

2.数组对象

(1).简介

  • 数组也是对象,也是用来存储一些值,不同的是,普通对象是使用字符串作为属性名的,而数组是使用数字来作为索引操作元素

  • 作用:数组的存储性能比普通对象好,开发中常用

(2).操作

  • 创建数组对象

1
2
3
var arr = new Array();		//创建一个数组
var arr2 = new Array(10,20,30); //创建时直接指定数组中元素
arr2 = new Array(10); //当只传入一个整数时代表创建一个长度为10的数组
  • 向数组中添加元素,语法:数组[索引] = 值

1
arr[0] = 10;
  • 读取数组中的元素,语法:数组[索引] 。如果读取不存在的索引,不会报错反而会返回 undefined

1
console.log(arr[0]);
  • 获取数组的长度,可以使用 length 属性来获取数组的长度,语法:数组.length

    • 对于连续的数组,使用 length 可以获取到数组的长度(元素的个数);对于非连续的数组,使用 length 会获取到数组的最大的索引+1
1
console.log(arr.length);
  • 修改length。如果修改的length大于原长度,则多出部分会空出来;如果修改的length小于原长度,则多出部分会被删除

1
arr.length = 10;
  • 向数组的最后一个位置添加元素,语法:数组[数组.length] = 值;

1
arr[arr.length] = 70;

(3).数组字面量

  • 使用字面量创建数组,如下:

1
2
3
var arr = [];
var arr = [1,2,3,4]; //创建时直接指定数组中元素
arr = [10]; //当只传入一个整数时代表创建一个数组只有一个元素10
  • 数组中的元素可以是任意的数据类型,即

1
2
3
4
5
6
7
8
9
arr = ["hello",1,true,null,undefined];
//可以是对象
var obj = {name:"孙悟空"};
arr[arr.length] = obj;
arr = [{name:"孙悟空"},{name:"猪八戒"},{name:"沙和尚"}];
//可以是函数
arr = [function(){},function(){},function(){}];
//可以是数组,即二维数组
arr = [[1,2,3],[4,5,6],[7,8,9]];

(4).数组的方法

1).push()

  • 向数组的末尾添加一个或多个元素,并返回数组的新长度

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

1
2
var arr = ["小A","小B","小C"];
arr.push("小D","小E");

2).pop()

  • 删除数组的最后一个元素,并将删除的元素作为返回值返回

1
2
var arr = ["小A","小B","小C"];
arr.pop();

3).unshift()

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

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

1
2
var arr = ["小A","小B","小C"];
arr.unshift("小D","小E");

4).shift()

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

1
2
var arr = ["小A","小B","小C"];
arr.shift();

5).slice()

  • 可以用来从数组中提取指定元素,该方法不会改变原数组,而是将截取到的元素封装到一个新数组中返回

  • 该方法总共有两个参数:第一个参数:截取开始的位置的索引,包含开始索引(左闭)第二个参数:截取结束的位置的索引,不包含结束索引(右开)

  • 以上参数中的索引也可以传递一个负值,就会从后往前计算,-1为倒数第一个,-2为倒数第二个

1
2
var arr = ["小A","小B","小C"];
var result = arr.slice(0,1);

6).splice()

  • 可以用于删除数组中的指定元素并在相应位置插入需要的元素,该方法会影响到原数组,会将指定元素从原数组中删除并将被删除的元素作为返回值返回

  • 该方法固定参数有两个,剩余参数由具体情况而定第一个参数:表示开始位置的索引第二个参数:表示删除的数量第三个及以后参数:可以传递一些新的元素,这些元素将会自动插入到开始位置索引的前边

  • 如下:

1
2
var arr = ["小A","小B","小C"];
var result = arr.splice(0,1,"小AA");

7).concat()

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

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

1
2
3
var arr1 = ["小A","小B","小C"];
var arr2 = ["小D","小E","小F"];
var result = arr.concat(arr1,arr2,"小G");

8).join()

  • 可以将数组转换为一个字符串

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

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

1
2
3
4
5
var arr = ["小A","小B","小C"];
var result = arr.join("-");
console.log(result);
输出如下:
小A-小B-小C

9).reverse()

  • 可以用来反转数组

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

1
2
var arr = ["小A","小B","小C"];
arr.reverse();

10).sort()

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

  • 该方法会影响原数组,默认按照 Unicode 编码进行排序

1
2
var arr = ["C","B","A"];
arr.sort();
  • 及时对于纯数字的数组,使用该方法排序时,也会按照 Unicode 编码进行排序,所以对于数字进行排序时,可能会得到错误的结果

!自行指定排序规则
  • 在 sort() 中添加一个回调函数来指定排序规则,该回调函数中需要定义两个形参,浏览器将会分别使用数组中的元素作为实参去调用回调函数,即根据相邻元素,第一个一定在第二个的前边

  • 浏览器会根据回调函数的返回值来决定元素的位置返回一个大于0的值,元素交换位置返回一个小于0的值,元素位置不变返回0,两元素相等,元素位置不变

  • 升序排列时,直接返回 a-b ;降序排列时,直接返回 b-a

1
2
3
4
5
6
7
var arr = [5,2,3,1,4];
arr.sort(function(a,b){
//升序排列
return a-b;
//降序排列
return b-a;
});

11).forEach()

  • 用来遍历数组,且该方法只支持IE8以上的浏览器

  • 该方法需要一个函数作为参数,像这种函数,由我们创建但是不由我们调用的称为 回调函数

  • 数组中有几个元素函数就会执行几次,每次执行时,浏览器会将遍历到的元素以实参的形式传递进来,我们可以定义形参来读取这些内容

  • 浏览器会在回调函数中传递三个函数:第一个参数,是当前正在遍历的元素第二个参数,是当前正在遍历的元素的索引第三个参数,是正在遍历的数组

  • 具体方法使用看下面的遍历部分

12).indexOf

  • 数组的 indexOf() 方法和字符串的类似,点击 这里查看

(5).数组的遍历

  • 使用for循环

1
2
3
for(i=0;i<arr.length;i++){
console.log(arr[i]);
}
  • 使用forEach()方法

1
2
3
arr.forEach(function(value,index,obj){
console.log(value);
});

3.Date对象

(1).简介

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

(2).操作

1).创建一个Date对象

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

1
2
var d = new Date();
console.log(d);

2).创建一个指定时间的时间对象

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

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

1
2
var d1 = new Date("07/07/2020 07:07:07");
console.log(d1);

(3).Date的方法

1).getDate()

  • 获取当前日期对象是几日

1
2
var date = d1.getDate();
console.log(date);

2).getDay()

  • 获取当前日期对象是周几,会返回0-6的值

1
2
var date = d1.getDay();
console.log(date);

3).getMonth()

  • 获取当前日期对象的月份,会返回0-11的值

1
2
var date = d1.getMonth();
console.log(date);

4).getFullYear()

  • 获取当前日期对象的年份

1
2
var date = d1.getFullYear();
console.log(date);

5).getTime()

  • 获取当前日期对象的时间戳(从1970年1月1日到当前日期所花费的毫秒数)

1
2
var date = d1.getTime();
console.log(date);
  • 如果时间为1970年1月1日0时0分0秒,得出的时间戳应该为负数(因为时区缘故)

  • 直接获取当前时间的时间戳

1
2
time = Date.now();
console.log(time);
  • 通过时间戳,可以测试代码执行性能

1
2
3
4
5
6
var start = Date.now();
for(i=0;i<100;i++){
console.log(i);
}
var end = Date.now();
console.log(end - start);

4.Math对象

(1).简介

  • Math 不是一个构造函数,它属于工具类不用创建对象,它里边包含了数学运算相关的属性和方法

(2).属性

  • 具体属性如下图:
    js07.png

(3).Math的方法

1).random()

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

  • 如果想要生成一个0-x之间的随机数(开区间),则可以如下实现:

1
console.log(Math.random()*x);
  • 如果想要生成一个0-x之间的随机数(闭区间),则可以通过四舍五入法来实现:

1
console.log(Math.round(Math.random()*x));
  • 如果想要生成一个x-y之间的随机数(闭区间),则可以通过四舍五入法来实现:

1
console.log(Math.round(Math.random()*(y-x)+x));

2).other

  • 其余方法如下图:
    js08.png

五、宿主对象

1.DOM

(1).简介

  • DOM
    js12.png

  • 模型:
    js13.png

(2).节点

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

  • 类型不同:文档节点:整个HTML文档元素节点:HTML标签属性节点:元素的属性文本节点:标签中的文本

  • 节点的属性:
    js14.png

  • 浏览器已经为我们提供了文档节点对象,这个对象是 Window 属性,可以在页面中直接使用,文档节点代表的是整个网页

(3).事件

  • 就是用户和浏览器之间的交互行为,如:点击按钮,鼠标移动,关闭窗口

a).编写位置

  • 我们可以在事件对应的属性中设置一些JS代码,这样当事件被处罚时,这些代码将会执行(但不推荐)

1
<button id="btn" onclick="alert('你还点!');">我是一个按钮</button>
  • 可以为按钮的对应事件绑定处理函数(称为xx相应函数)的形式来响应事件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
在body标签中编写时:
<script type="text/javascript">
var btn = document.getElementById("btn");
btn.onclick = function(){
alert("别点了!");
};
</script>
在head标签中编写时:
<script type="text/javascript">
window.onload = function(){
var btn = document.getElementById("btn");
btn.onclick = function(){
alert("别点了!");
}
};
</script>

b).onclick 事件

  • 会在执行点击时触发该事件

  • 为按钮绑定一个 onclick 事件

1
2
3
btn.onclick = function(){
alert("你好");
}

c).onload 事件

  • 会在整个页面加载完成之后才触发,这样可以确保我们的代码执行时所有的 DOM 对象已经加载完毕了

  • 为 window 绑定一个 onload 事件

1
2
3
window.onload = function(){
alert("你好!");
};

d).onscroll 事件

  • 该事件会在元素的滚动条滚动时触发

  • 为某元素绑定一个 onscroll 事件

1
2
3
info.onscroll = function(){
alert("你好!");
}

e).onmousemove 事件

  • 该事件将会在元素中移动时被触发

1
2
3
div.onmousemove = function(){
alert("你好!");
}

f).onmousedown/up 事件

  • 该事件将会在元素被鼠标点击与松开时触发

g).onmousewheel/onwheel 事件

  • 该事件将会给元素绑定一个鼠标滚轮滚动的事件,前者火狐不支持后者IE不支持

  • 相关的事件对象在 这里

h).onkeydown/up 事件

  • 表示键盘被按下/松开

  • 键盘事件一般都给绑定给一些可以获取到焦点的对象或者document

  • 对于前者,如果一直按着某个键不松手,则事件会一直触发,且第一次和第二次之间会间隔长一些,是为了防止误操作

  • 相关的事件对象在 这里

(4).事件的对象

  • 当事件的响应函数被触发时,浏览器每次都会将一个事件对象作为实参传递进响应函数

  • 在事件对象中封装了当前事件相关的一切信息。比如:鼠标的坐标、键盘哪个键被按下、鼠标滚轮滚动的方向

  • 在IE8及以下的浏览器中,响应函数被触发时,浏览器不会传递事件对象,而是将事件对象作为 window 对象的属性保存的,所以需要使用 window.event 来调用

a).clientX/Y 属性

  • 可以获取鼠标指针在当前的可见窗口的水平/垂直坐标

b).pageX/Y 属性

  • 可以获取鼠标相对于当前页面的坐标

  • 不支持IE8及以下

  • 关于该属性和上一个属性的区别如下图:
    js15.png

c).target 属性

  • 表示触发事件的对象是谁

d).wheelDelta 属性

  • 可以获取鼠标滚轮滚动的方向,向上滚是正值,向下滚是负值

  • 而且一般需要取消浏览器的默认行为,即浏览器会跟随鼠标滚轮上下移动

  • 但是这个属性火狐中不支持需要使用 event.deltaY 属性来获取,但正负值刚好相反需要使用 DOMMouseScroll 来绑定滚动事件,该事件需要通过 addEventListener() 函数来绑定,然后使用 event.detai 属性来获取,但正负值刚好相反

  • 火狐方法二解决办法如下:

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
<script type="text/javascript">
window.onload = function(){
var box1 = document.getElementById("box1");
box1.onmousewheel = function(event){
event = event || window.event;
if(event.wheelDelta > 0 || event.detail < 0){
//鼠标滚轮向上滚
box1.style.height = box1.clientHeight - 10 + "px";
}else{
//鼠标滚轮向下滚
box1.style.height = box1.clientHeight + 10 + "px";
}
event.preventDefault && event.preventDefault();
return false;
};
bind(box1,"DOMMouseScroll",box1.onmousewheel)
};
function bind(obj,eventStr,callback){
if(obj.addEventListener){
//大部分浏览器兼容的方式
obj.addEventListener(eventStr,callback,false);
}else{
//IE8及以下
obj.attachEvent("on"+eventStr,function(){
//匿名函数,用来将回调函数更改为用户调用而不是浏览器调用
//即更改this为对象而不是window
callback.call(obj);
});
}
}
</script>

e).keyCode/altKey/ctrlKey/shiftKey 属性

  • 可以通过 keyCode 来获取按键的编码

  • 若想要判断两个键同时按下时,可以使用后三个进行获取相应按键的编码

(5).事件的冒泡

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

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

  • event.cancelBubble = true; 即可取消冒泡

(6).事件的委派

  • 指将事件统一绑定给元素的共同的祖先元素,这样当后代元素上的事件触发时,会一直冒泡到祖先元素,从而通过祖先元素的响应函数来处理事件

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

(7).事件的绑定

  • 传统的 对象.事件 = 函数 绑定响应函数只能同时为一个元素的一个事件绑定一个响应函数,不能绑定多个,如果绑定了多个则后边的会覆盖掉前面的

  • 通过以下两个方法可以同时绑定多个响应函数

a).addEventListener()

  • 可以同时为一个元素的相同事件同时绑定多个响应函数,按照顺序执行响应函数

  • 语法:对象.addEventListener(参数1,参数2,参数3)

  • 参数如下:

    • 事件的字符串,不加on
    • 回调函数,当事件触发时该函数会被调用
    • 是否在捕获阶段触发事件,需要一个布尔值,一般都传false
  • 注意:该方法不支持 IE8 及以下,且它的 this 是绑定事件的对象

  • 注意:使用该方法绑定响应函数时,取消默认行为时不能使用 retuen false,而应该使用 event.preventDefault() 来取消默认行为,且IE8不支持

b).attachEvent()

  • 可以同时为一个元素的相同事件同时绑定多个响应函数,按照反向顺序执行响应函数

  • 语法:对象.attachEvent(参数1,参数2)

  • 参数如下:
    1.事件的字符串,要加on
    2.回调函数,当事件触发时该函数会被调用

  • 注意:该方法只支持IE 5-10,且它的 this 是 window

!解决this是window的方法
  • 因为attachEvent()是浏览器调用的,所以它的this是window

  • 我们无法控制浏览器的调用方式,所以需要在回调函数参数处写一个匿名函数

  • 在匿名函数中我们再调用回调函数,此时this就是绑定事件的对象了

  • 如下:

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
<script type="text/javascript">
window.onload = function(){
var btn01 = document.getElementById("btn01");
bind(btn01,"click",function(){
alert(1);
});
};
//定义一个函数,用来指定元素绑定响应函数
/*
* 参数:
* obj:要绑定事件的对象
* eventStr:事件的字符串(不要on)
* callback:回调函数
*/
function bind(obj,eventStr,callback){
if(obj.addEventListener){
//大部分浏览器兼容的方式
obj.addEventListener(eventStr,callback,false);
}else{
//IE8及以下
obj.attachEvent("on"+eventStr,function(){
//匿名函数,用来将回调函数更改为用户调用而不是浏览器调用
//即更改this为对象而不是window
callback.call(obj);
});
}
}
</script>

(8).事件的传播

  • 事件的传播一共分为以下三个阶段

  • 如果希望在捕获阶段就触发事件,可以将 addEventListener() 的第三个参数设置为 true,一般情况下我们不会希望在捕获阶段触发事件,所以这个参数一般都是 false

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

a).捕获阶段

  • 在捕获阶段是从最外层的祖先元素向目标元素进行事件的捕获,但是默认不会触发事件

b).目标阶段

  • 事件捕获到目标元素,捕获结束开始在目标元素上触发事件

c).冒泡阶段

  • 事件从目标元素向他的祖先元素传递,依次触发祖先元素上的事件

(9).获取元素节点

a).document.getElementById()

  • 通过id属性获取一个元素节点对象

  • 括号中填写id名,可以获取到指定id的对象

  • 如果需要读取元素节点属性,直接使用 元素.属性名 即可,例如btn01.innerHTML

b).document.getElementsByTagName()

  • 通过标签名获取一组元素节点对象

  • 这个方法会返回一个类数组对象,所有查询到的元素都会封装到对象中

c).document.getElementsByName()

  • 通过name属性获取一组元素节点对象

  • 这个方法会返回一个类数组对象,所有查询到的元素都会封装到对象中

  • 如果需要读取元素节点属性,直接使用 元素.属性名 即可,例如inputs[i].value。但class不能采用这种方式,可以使用 元素.className 来代替

d).document.getElemeentsByClassName()

  • 根据元素的class属性值查询一组元素节点对象,但该方法不支持 IE8及以下的浏览器

e).document.querySelector()

  • 需要一个选择器的字符串作为参数,可以根据一个 CSS选择器来查询一个元素节点对象,支持 IE8及以上

  • 使用该方法总会返回唯一的元素,如果满足条件的元素有多个,只会返回第一个

f).document.querySelectorAll()

  • 该方法类似上面,不同的是会将符合条件的元素封装到一个数组中返回

  • 即使只有一个,也会返回数组

g).document.body

  • 它保存的是body的引用

h).document.documentElement

  • 它保存的是html根标签

i).document.all

  • 它代表页面中所有的元素

(10).获取元素节点的子节点

  • 通过具体的元素节点调用

a).当前节点.getElementById()

  • 方法,返回当前节点的指定标签名后代节点

b).当前节点.childNodes

  • 属性,表示当前节点的所有子节点

  • 该属性会获取包括文本节点在内的所有节点,根据DOM标准,标签间的空白也会当成文本节点

  • 注意:IE8及以下不会将空白文本当成子节点

c).当前节点.children

  • 属性,表示获取当前元素的所有子元素

  • 区别于子节点:不会获取到空白文本

d).当前节点.firstChild

  • 属性,表示当前节点的第一个子节点

e).当前节点.firstElementChild

  • 属性,表示获取当前元素的第一个子元素,不支持IE8及以下

f).当前节点.lastChild

  • 属性,表示当前节点的最后一个子节点

(11).获取父节点和兄弟节点

  • 通过具体的节点调用

a).parentNode

  • 属性,表示当前节点的父节点

b).previousSibling

  • 属性,表示当前节点的前一个兄弟节点

  • 该属性会获取包括文本节点在内的所有节点,根据DOM标准,标签间的空白也会当成文本节点

c).previousElementSibling

  • 属性,表示当前节点的前一个兄弟元素,不支持IE8及以下

c).nextSibling

  • 属性,表示当前节点的后一个兄弟节点

(12).增删查改方法

a).document.createElement()

  • 可以用于创建一个元素节点对象

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

b).document.createTextNode()

  • 可以用来创建一个文本节点对象

  • 它需要一个文本内容作为参数,将会根据内容创建文本节点,并将新的节点返回

c).appendChild()

  • 向一个父节点中添加一个新的子节点

  • 用法:父节点.appendChild(子节点);

d).insertBefore()

  • 可以在指定的子节点前插入新的子节点

  • 语法:父节点.insertBefore(新节点,旧节点);

e).replaceChild()

  • 可以使用指定的子节点替换已有的子节点

  • 语法:父节点.replaceChild(新节点,旧节点);

f).removeChild()

  • 可以删除一个子节点

  • 语法:父节点.removeChild(子节点);

  • 常用语法:子节点.parentNode.removeChild(子节点);

g).innerHTML

  • 属性,获取该元素节点的HTML内容

  • 也可以使用 innerHTML = "修改文本区域"; 来修改非自结束标签内部的文本内容

  • 使用该属性可以进行DOM的增删改的相关操作,一般推荐和上面的结合使用,如下:

1
2
3
4
var city = document.getElementById("city");
var li = document.createElement("li");
li.innerHTML = "广州";
city.appendChild(li);

h).innerText

  • 属性,获取该元素节点的Text内容

(13).操作内联样式

a).修改

  • 可以通过JS修改元素的样式,语法为:元素.style.样式名=样式值

  • 注意:如果 CSS 样式中含有-,则需要将其改为驼峰命名法,即去掉-然后将-之后的字母大写。如:

1
2
3
background-color
需要改成:
backgroundColor
  • 我们通过 style 属性设置的样式都是内联样式,而内联样式有较高的优先级,所以通过JS修改的样式往往会立即显示。但是如果在样式中写了 !important ,则此时样式会有最高的优先级,即使通过JS也不能覆盖该样式,所以会导致 JS 修改样式失效,所以尽量不要添加 !important

b).读取

  • 也可以通过 JS 读取元素的样式,语法为:元素.style.样式名

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

(14).获取元素的样式

a).IE独有

  • 可以获取元素当前显示的样式,语法为:元素.currentStyle.样式名

  • 如果当前元素没有设置该样式,则获取它的默认值

b).其他通用

  • 使用 getComputedStyle() 这个方法来获取元素当前的样式,这个是 window 的方法,可以直接使用

  • 此时需要两个参数。第一个为要获取样式的元素,第二个可以传递一个伪元素,一般都传null

  • 该方法会返回一个对象,对象中封装了当前元素对应的样式,可以通过 对象.样式名 来读取样式,如果获取的样式没有设置,则会获取到真实的值,而不是默认值

  • 不支持IE8及以下

c).兼容方法

  • 首先需要定义一个函数,用来获取指定元素的当前样式

  • 因为IE8及以下没有 getComputedStyle() 方法,所以可以作为条件判断语句的判定条件。这里需要注意一下:如果是只是 getComputedStyle 那么这里为变量,变量如果找不到就会报错;而 window.getComputedStyle 为对象的属性,如果找不到就会返回 undefined。

  • 在相应的域中将获取元素的语句返回即可

1
2
3
4
5
6
7
function getStyle(obj,name){
if(window.getComputedStyle){
return getComputedStyle(obj,null)[name];
}else{
return obj.currentStyle[name];
}
}
  • 在点击函数中,使用函数调用即可

1
getStyle(box1,"width");

(15).其他样式的相关属性

a).clientWidth/Height

  • 可以获取元素的可见宽度和高度,包括内容区和内边距

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

  • 这两个属性都是只读的,不能修改

b).offsetWidth/Height

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

c).offsetParent

  • 可以用来获取到离当前元素最近的开启了定位的祖先元素,如果所有的祖先元素都没有开启定位,则返回body

d).offsetLeft/Top

  • 可以获取当前元素相对于其定位父元素的水平/垂直偏移量

e).scrollWidth/Height

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

f).scrollLeft/Top

  • 可以获取水平/垂直滚动条滚动的距离

  • 当满足 scrollHeight - scrollTop == clientHeight 说明垂直滚动条滚动到底了

  • 当满足 scrollWidth - scrollLeft == clientWidth 说明水平滚动条滚动到底了

2.DOM 案例分析

(1).切换上下图

a).思路

  1. 因为通过点击按钮来切换上下图,所以需要给按钮绑定点击事件

  2. 将图片的路径存放在一个数组中,通过修改 img 的 src 属性来形成切换下一张图的效果

  3. 设置个索引变量,在切换上下图时,自减或自增,并使用 if 语句来控制索引溢出问题

  4. 通过重新复制索引变量还可以形成循环效果

  5. 如果想要提示当前第几张图片的效果,需要通过索引和数组长度来控制总图与当前图

b).代码

  • JS 代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
<script type="text/javascript">
window.onload = function(){
//获取第一张图像
var img = document.getElementsByTagName("img")[0];
//用数组存放三张图片的地址
var imgArr = ["./img/asd1.jpg" ,"./img/asd2.jpg" ,"./img/asd3.jpg"];
var index = 0;
//获取提示文字
var info = document.getElementById("info");
info.innerHTML = (index+1)+"/"+imgArr.length;
//上一张
var perv = document.getElementById("prev");
prev.onclick = function(){
index--;
if(index<0){
//到开头时固定在第一张图片
//index = 0;
//到开头时返回最后一张
index = imgArr.length-1;
}
img.src = imgArr[index];
info.innerHTML = (index+1)+"/"+imgArr.length;
}
//下一张
var next = document.getElementById("next");
next.onclick = function(){
index++;
if(index>imgArr.length-1){
//到末尾时返回第一张
index = 0;
//到末尾时固定在最后一张图片
//index = imgArr.length-1;
}
img.src = imgArr[index];
info.innerHTML = (index+1)+"/"+imgArr.length;
}
}
</script>

(2).实现全选/全不选

a).思路

  • 通过id属性来获取到需要点击的按钮

  • 通过name属性来获取到多个选项并将其封装到一个数组中去

  • 遍历该数组,通过input的CheckBox对象的checked属性来检查是否被选中

  • 全选/全不选时,直接设置true/false

  • 反选时,如果是true就设置为false,如果是false就设置为true

  • 提交时,通过遍历数组找到为true的并封装到另一个数组中,等遍历完后直接打印数组

b).代码

  • 如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
<script type="text/javascript">
window.onload = function(){
function myClick(idStr,fun){
var btn = document.getElementById(idStr);
btn.onclick = fun;
}
var checkedAllBox = document.getElementById("checkedAllBox");
checkedAllBox.onclick = function(){
var items = document.getElementsByName("items");
for(var i=0;i<items.length;i++){
if(items[i].checked){
items[i].checked = false;
}else{
items[i].checked = true;
}
}
}
//1.#checkedAllBtn
myClick("checkedAllBtn",function(){
var items = document.getElementsByName("items");
for(var i=0;i<items.length;i++){
items[i].checked = true;
}
});
//2.#checkedNoBtn
myClick("checkedNoBtn",function(){
var items = document.getElementsByName("items");
for(var i=0;i<items.length;i++){
items[i].checked = false;
}
});
//3.#checkedRevBtn
myClick("checkedRevBtn",function(){
var items = document.getElementsByName("items");
for(var i=0;i<items.length;i++){
if(items[i].checked){
items[i].checked = false;
}else{
items[i].checked = true;
}
}
});
//4.#sendBtn
myClick("sendBtn",function(){
var items = document.getElementsByName("items");
var j = 0;
var itemsArr = [];
for(var i=0;i<items.length;i++){
if(items[i].checked){
itemsArr[j] = items[i].value;
j++;
}
}
alert("你提交的是"+itemsArr);
});
}
</script>

(3).实现删除表格内容

a).思路

  • 这个删除按钮可以使用超链接来使用,其中的href属性可以设置为 javascript:; 来清除默认跳转行为,也可以在响应函数中使用 return false 来清除

  • 首先因为是表格,每行都有数据项,那么每行都应该有个删除按钮,所以在响应函数中应该先使用for循环来遍历出每个超链接

  • 因为超链接存放在了一个数组中,所以可以直接给数组绑定点击事件

  • 根据实际需求,超链接是放在td里的,而删除一行数据的话应该是删除一整个tr,所以需要通过超链接来找到tr

  • 删除元素需要考虑到用户的使用体验,那么在删除之前就应该给出相应的提示——confirm()语句,并将其放入if判断中

b).代码

  • 如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
window.onload = function(){
//点击超链接以后删除一个员工的信息
//获取所有的超链接
var allA = document.getElementsByTagName("a");
for(var i=0;i<allA.length;i++){
allA[i].onclick = function(){
//获取超链接的爷爷元素
//这里的this指的是超链接中的href属性
var tr = this.parentNode.parentNode;
var name = tr.getElementsByTagName("td")[0].innerHTML;
//判断是否删除
var flag = confirm("确定删除"+name+"吗?");
if(flag){
//删除该爷爷元素
tr.parentNode.removeChild(tr);
}
return false;
}
}
};

(4).实现用户保密协议

a).思路

  • 用户保密协议部分需要要求用户阅读完协议才可以勾选同意按钮,并提交注册

  • 首先需要将保密协议溢出部分隐藏掉,使其拖动滚动条才可以查看;并将复选框和提交按钮添加 disabled 属性,将其设置为不可点击

  • 给盒子设置 onscroll 事件,并使用 scrollHeight 重要等式来判断是否拉到了底端

  • 最后将 disabled 属性设置为 false 即可

b).代码

  • 如下:

1
2
3
4
5
6
7
8
9
10
11
12
<script type="text/javascript">
window.onload = function(){
var info = document.getElementById("info");
var inputs = document.getElementsByTagName("input");
info.onscroll = function(){
if(info.scrollHeight -info.scrollTop == info.clientHeight){
inputs[0].disabled = false;
inputs[1].disabled = false;
}
};
};
</script>

(5).获取鼠标坐标并打印

a).思路

  • 需要在第一个盒子里获取鼠标坐标,在第二个盒子里打印鼠标坐标

  • 根据事件的对象内容,需要将一个形参传入响应函数中,然后调用 clientX/Y 属性,最后将坐标写入第二个盒子中

  • 这里要注意兼容IE8及以下

b).代码

  • 如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<script type="text/javascript">
window.onload = function(){
var mouseArea = document.getElementById("mouseArea");
var mouseCoordinate = document.getElementById("mouseCoordinate");
mouseArea.onmousemove = function(event){
//兼容IE8及以下的方法一:
if(!event){
event = window.event;
}
//兼容IE8及以下的方法二(推荐):
event = event || window.event;
var x = event.clientX;
var y = event.clientY;
mouseCoordinate.innerHTML = ("x = " + x + " , y = " + y);
};
};
</script>

(6).盒子跟随鼠标移动

a).思路

  • 首先需要通过事件的对象获取鼠标的坐标

  • 当网页长度/宽度足够长/宽时,因为滚动条的存在使用 clientX 会导致坐标偏移的情况

  • 此时需要获取到 html 的 scrollTop/scrollLeft 属性(其实并不是body的)

  • 将获取到的偏移量设置给盒子的偏移量,计算公式为:盒子的偏移量 = clientX + scrollTop/scrollLeft ,注意 clientX 获取到的是数值,所以需要给其拼串拼一个单位

  • 注意兼容IE8及以下

b).代码

  • 如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<script type="text/javascript">
window.onload = function(){
var box1 = document.getElementById("box1");
document.onmousemove = function(event){
//解决IE8兼容性问题
event = event || window.event;
//获取鼠标的坐标
var left = event.clientX;
var top = event.clientY;
//获取滚动条滚动的长度
var st = document.documentElement.scrollTop;
var sl = document.documentElement.scrollLeft;
//设置div的偏移量
box1.style.left = left + sl + "px";
box1.style.top = top + st + "px";
}
};
</script>

(7).为新添加的超链接绑定响应函数

a).思路

  • 根据事件的冒泡与事件的委派,如果想要给超链接绑定单击响应函数,那么只需要给超链接的祖先元素设置单击响应函数即可

  • 如果想要实现点击按钮添加超链接,那么只需要创建一个li节点,给li节点设置其innerHTML属性,最后将li加入到ul中即可

  • 由于事件的委派可以传递给其下的所有后代元素,所以我们需要通过 event.target 来指定我们希望触发事件的对象(在本例中为超链接)

b).代码

  • 如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<script type="text/javascript">
window.onload = function(){
var ul = document.getElementById("ul");
var btn = document.getElementById("btn");
btn.onclick = function(){
var li = document.createElement("li");
li.innerHTML = "<a href='javascript:;' class='a1'>你点我啊!</a>";
ul.appendChild(li);
};
ul.onclick = function(event){
event = event || window.event;
if(event.target.className == "a1"){
alert("这是ul的单击响应函数!");
}
};
};
</script>

(8).拖拽元素

a).思路

  • 想要实现拖拽功能,需要用到 onmousedown、onmousemove、onmouseup 事件

  • 当我们拖拽一个网页中的内容时,浏览器会默认去搜索引擎中搜索内容,此时会导致拖拽功能的异常,此时可以使用以下两个方法来解决这个问题
    1.在 onmousedown 响应函数的最下边写 return false 来取消默认行为,但不支持IE8及以下
    2.在 onmousedown 开头使用 setCapture 来捕获所有鼠标按下的事件,在 onmouseup 末尾使用 releaseCapture 来取消捕获。但是该方法只在IE中生效,且在谷歌中会报错,所以需要判断才可以正常使用。

b).代码

  • 如下:

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
<script type="text/javascript">
window.onload = function(){
var box1 = document.getElementById("box1");
box1.onmousedown = function(event){
//取消IE浏览器在鼠标点下时的默认行为,并解决谷歌的兼容性问题
if(box1.setCapture){
box1.setCapture();
}
//或这种短路与方式
//box1.setCapture && box1.setCapture();
event = event || window.event;
//获取div的偏移量 鼠标.clientX/Y - 元素.offsetLeft/Top
var ol = event.clientX - box1.offsetLeft;
var ot = event.clientY - box1.offsetTop;
document.onmousemove = function(event){
event = event || window.event;
//获取鼠标的坐标 此时应该是 鼠标的偏移量 - div的偏移量
var left = event.clientX - ol;
var top = event.clientY - ot;
//获取滚动条的坐标
var st = document.documentElement.scrollTop;
var sl = document.documentElement.scrollLeft;
box1.style.left = left + sl + "px";
box1.style.top = top + st + "px";
};
//当页面中有其他元素时,在其他元素上松开鼠标不会固定相应的盒子,所以这里不能给box1绑定响应函数,而应该绑定给document
document.onmouseup = function(){
document.onmousemove = null;
//此时,当松开鼠标以后,该函数并没有停止,点击盒子以外的部分依然会触发,所以需要将onmouseup事件设置为null
document.onmouseup = null;
if(box1.releaseCapture){
box1.releaseCapture();
}
//或这种短路与的方式
//box1.releaseCapture && box1.releaseCapture();
};
//取消其他浏览器在鼠标点下时的默认行为
return fasle;
};
};
</script>

(9).实现方向键控制盒子移动

a).思路

  • 按下方向键控制盒子移动首先需要获取方向键代表的 keyCode

  • 因为盒子无法绑定键盘按下事件,所以需要给 document 绑定

  • 因为涉及到四个按键,所以最好使用 switch

  • 注意:修改 box1 的水平或垂直偏移量时一定要使用 元素.style.属性 来修改

b).代码

  • 如下:

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
<script type="text/javascript">
window.onload = function(){
var box1 = document.getElementById("box1");
document.onkeydown = function(event){
event = event || window.event;
//左上右下分别为:37/38/39/40
//设置移动速度
var speed = 10;
//当按下ctrl时加速
if(event.ctrlKey){
speed = 50;
}
switch(event.keyCode){
case 37:
box1.style.left = box1.offsetLeft - speed + "px";
break;
case 39:
box1.style.left = box1.offsetLeft + speed + "px";
break;
case 38:
box1.style.top = box1.offsetTop - speed + "px";
break;
case 40:
box1.style.top = box1.offsetTop + speed + "px";
break;
}
};
};
</script>

c).注意

  • 这里因为有浏览器的默认行为,所以会导致盒子在移动的时候第一下总会卡顿一下,这时防止用户误操作而设置的

  • 如果想要优化,则需要使用定时器来优化,具体请看这里

3.BOM

(1).简介

  • 是浏览器对象模型,可以通过JS来操作浏览器

  • BOM 中为我们提供了一组对象,用来完成对浏览器的操作

(2).BOM对象

  • 这些 BOM 对象在浏览器中都是作为 Window 对象的属性保存的,可以通过 window 对象来使用,也可以直接使用

a).window

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

a1).window方法之定时调用

  • 如果希望一个程序可以每隔一段时间执行一次,可以使用定时调用

  • setInterval() 为定时调用,可以将一个函数每隔一段时间执行一次,

1
2
3
4
5
6
7
8
参数如下:
1.回调函数
2.每次调用间隔的时间,单位为ms
setInterval(function(){
代码;
},1000);
返回值:
返回一个Number类型的数据,用来作为定时器的唯一标识
  • clearInterval() 可以用来关闭一个定时器,该方法需要一个定时器的标识作为参数,这样将关闭标识对应的定时器。如果参数不是一个有效的标识,则什么也不做

a2).window方法之延时(超时)调用

  • 延时调用时一个函数不马上执行,而是隔一段事件以后再执行,且只会执行一次

  • setTimeout() 为延时调用

1
2
3
4
5
6
7
8
参数如下:
1.回调函数
2.每次调用间隔的时间,单位为ms
setTimeout(function(){
代码;
},1000);
返回值:
返回一个Number类型的数据,用来作为延时调用的唯一标识
  • clearTimeout() 可以用来关闭一个延时调用,该方法需要一个延时调用的标识作为参数,这样将关闭标识对应的延时调用

  • 延时调用和定时调用实际上是可以互相替代的,在开发中可以根据自己的需要去选择

b).navigator

  • 代表的是当前浏览器的信息

  • 通过该对象可以来识别不同的浏览器

  • 该对象中的大部分属性已经无法使用了,除了 userAgent 可以用来判断浏览器的信息,搭配正则表达式来判断

c).location

  • 代表当前浏览器的地址栏信息

  • 通过该对象可以获取地址栏信息,或者操作浏览器跳转页面

  • assign() 方法用来跳转到其他的页面,作用和直接修改 location 一样

  • reload() 方法用于重新加载当前页面,作用和刷新按钮一样。如果在方法中传递一个 true 作为参数,则会强制清空缓存刷新页面

  • replace() 方法可以使用一个新的页面替换当前页面,调用完毕也会跳转页面,不会生成历史记录,即不能使用回退按钮回退

d).history

  • 代表浏览器的历史记录

  • 通过该对象来操作浏览器的历史记录。由于隐私原因,该对象不能获取到具体的历史记录,只能操作浏览器向前或者向后翻页,且该操作只在当次访问时有效

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

  • back() 方法可以用来回退到上一个页面

  • forward() 方法可以用来前进到下一个页面

  • go() 方法可以用来跳转到指定的页面,需要一个整数作为一个参数(1表示向前跳转一个页面,-1表示向后跳转一个页面)

e).screen

  • 代表用户的屏幕信息

  • 通过该对象可以获取到用户的显示器的相关信息

4.BOM 案例分析

(1).定时切换图片

a).实现思路

  • 想要实现切换图片,则需要修改 src 属性,而为了方便,可以把 src 放入一个数组中

  • 有数组以后需要一个索引变量,用来获取到数组中的内容

  • 为了防止图片一直播放,还需要判断图片是否超过最大索引(可以使用if,也可以使用取模运算)

  • 使用定时器来自动切换图片,如果开启定时器事件绑定给了按钮,为了防止连续点击按钮造成多个事件重复发生,需要在点击前先关闭定时器再开启定时器

  • 由于关闭定时器需要获取定时器的标识,如果点击另一个按钮关闭定时器,那么需要将定时器的标识定义在全局中,这样才可以正确获取到在别的按钮中的定时器标识

b).展示与源码

  • 请点击 这里 查看(为了体验到完整功能,请使用PC打开,源码右键“查看网页源代码”即可)

(2).方向键控制盒子移动优化版

a).实现思路

  • 这个是基于 DOM 案例中进行的优化,之前的版本在移动刚开始会卡顿一下,这里使用定时器来进行优化

  • 将定时器定义在全局范围内,让他每30ms运行一次

  • 在键盘按下时进入 switch,即可执行当前语句

  • 在键盘松开时就需要将键盘按钮值设置为一个与其不相关的值来终止移动

  • 若需要更改速度,一定要把速度定义在 window.onload 的全局范围内

b).展示与源码

  • 请点击 这里 查看(为了体验到完整功能,请使用PC打开,源码右键“查看网页源代码”即可)

(3).点击按钮盒子移动到固定点

a).实现思路

  • 首先你得获取到盒子的原始位置,这里创建了一个函数用来获取元素的样式

  • 此时获取到的盒子位置数值带有单位,所以需要使用取整函数来取整——parseInt()

  • 盒子移动的实质就是在盒子原有数值上进行速度的增加

  • 如果盒子到达固定宽度时,需要判断一下,同时考虑不是整数速度时怎么控制停在整数位置

b).展示与源码

  • 请点击 这里 查看(为了体验到完整功能,请使用PC打开,源码右键“查看网页源代码”即可)

(4).点击按钮盒子左右移动到固定点

a).实现思路

  • 该案例是上面那个案例的优化版,需要实现两个按钮控制一个元素左右移动到固定点

  • 因为两个按钮实现的事件都是一致的,所以这里将控制盒子移动的动画封装到一个函数中

  • 为了避免用户自行判断盒子移动方向和速度正负,这里需要判断目标位置和当前位置,来判断左右移和速度正负值

b).展示与源码

  • 请点击 这里 查看(为了体验到完整功能,请使用PC打开,源码右键“查看网页源代码”即可)

(5).点击按钮盒子随意变换移动

a).实现思路

  • 该案例是上面那个案例的再优化版,可以实现点击按钮使得元素随意变换

  • 在上面的封装好的函数里面,又新增了两个参数,分别是属性和回调函数。属性来存放需要变换的长宽高等,回调函数中则可以继续调用该元素的动画效果

  • 而在结束完此次动画后,应该判断是否有回调函数,有就执行,无则跳过

b).展示与源码

  • 请点击 这里 查看(为了体验到完整功能,请使用PC打开,源码右键“查看网页源代码”即可)

(6).综合案例之轮播图

a).实现思路

  • 首先轮播图可以通过无序列表来实现,让 ul 水平排放在页面中央,将需要显示的放在显示区域,其余溢出部分使用 overflow 来设置

  • 注意开启定位元素,其大小是由内容撑开的,所以居中效果和平常不太一样

  • 类似于 ul 的整体宽度并不能在 css 中写死,因为图片的数量是不定的,所以需要在 js 中动态的去改变

  • 图片转动的原理就是修改 ul 的水平偏移量为一个图片的大小,这样它就可以按顺序依次进行轮播

  • 当图片转到最后一张时,我们需要让其重新回到第一张,解决办法就是在最后一张照片后面加第一张照片,在其切换过去的瞬间,使 ul 的水平偏移量瞬间变为0,则其可以正常从第一张开始进行无限轮回

  • 当点击到下面的按钮(即超链接)时,图片应该跟随跳转,这里运用我们之前写好的一个 js 脚本即可实现

  • 如果想要实现自动切换图片,那么需要开启定时器,但此时定时器的自动切换和点击超链接的切换会冲突,所以需要在点击事件刚开始时取消定时器,在点击事件完毕时开启定时器

b).展示与源码

  • 请点击 这里 查看(为了体验到完整功能,请使用PC打开,源码右键“查看网页源代码”即可)

5.类的操作

  • 通过 style 属性来修改元素的样式,每修改一个样式,浏览器就重新渲染一次页面,这样的执行性能是比较差的,而且这种形式当我们要修改多个样式时,也不方便

  • 我们可以通过修改元素的 class 属性来间接修改样式,这样只需修改一次即可同时修改多个样式,浏览器只需要重新渲染页面一次,性能比较好,更有利于行为和表现分离

1
box.className = "b2";

(1).是否含有指定class属性

  • 定义一个函数,判断一个元素中是否含有指定的class属性

  • 具体如下:

1
2
3
4
5
6
7
function hasClass(obj,cn){
//创建一个正则表达式,因为这里是传obj不需要写死,所以需要构造函数的写法
//有时候class属性有很多个,所以需要使用 \b 来设置字符边界
var reg = new RegExp("\\b"+cn+"\\b");
//使用test()方法来检测其中是否有cn
return reg.test(obj.className);
}

(2).添加指定class属性

  • 定义一个函数,用来向一个元素中添加指定的 class 属性

  • 具体如下:

1
2
3
4
5
6
7
8
9
10
11
/*
* 参数:
* obj 要添加的class属性
* cn 要添加的class值
*/
function addClass(obj,cn){
//检查obj中是否含有cn,有的时候就不管,没有就添加
if(!hasClass(obj,cn)){
obj.className += " " + cn;
}
}

(3).删除指定class属性

  • 定义一个函数,删除一个元素中指定的class属性

  • 具体如下:

1
2
3
4
5
6
function removeClass(obj,cn){
//创建一个正则表达式
var reg = new RegExp("\\b"+cn+"\\b");
//删除class,即用空串替换掉原本的class属性
obj.className = obj.className.replace(reg,"");
}

(4).切换一个类的class属性

  • 定义一个函数,可以切换一个类,即有则删除,无则添加

  • 具体如下:

1
2
3
4
5
6
7
function toggleClass(obj,cn){
if(hasClass(obj,cn)){
removeClass(obj,cn);
}else{
addClass(obj,cn);
}
}

6.类的操作 案例分析

(1).二级菜单的实现

a).实现思路

  • 每一个一级菜单都是一个盒子,二级菜单是由众多的a链接组成的

  • 想要实现二级菜单的隐藏,则只需要其高度等于一级菜单的高度即可实现

  • 想要实现二级菜单的显示与否,只要给其增加或删除相应的 class 属性即可

  • 想要鼠标移入非链接处变为可点击状态,那么需要给其添加 cursor 的 css属性

b).展示与源码

  • 请点击 这里 查看(为了体验到完整功能,请使用PC打开,源码右键“查看网页源代码”即可)

六、正则表达式

1.简介

  • 用于定义一些字符串的规则

  • 计算机可以根据正则表达式来检查一个字符串是否符合规则或者将字符串中符合规则的内容提取出来

  • 正则表达式也是一个对象

2.创建正则表达式

  • 使用构造函数创建更加灵活,而使用字面量创建更加方便

(1).使用构造函数创建

  • 语法如下:

1
2
3
4
var 变量 = new RegExp("正则表达式","匹配模式");
匹配模式有两个:
i:忽略大小写
g:全局匹配模式
  • 正则表达式中有个 test() 方法,可以用来检查一个字符串是否符合正则表达式的规则,符合返回 true,之为 false

1
2
3
4
var reg = new RegExp("a");
var str = "abc";
result = reg.test(str);
console.log(result);

(2).使用字面量创建

  • 语法如下:

1
var 变量 = /正则表达式/匹配模式;

3.正则语法

(1).或

  • 使用[]或者|
    [a-z] :任意小写字母
    [A-Z] :任意大写字母
    [A-z] :任意字母(实际并不准确,因为在 Unicode 编码中 A-z 中还有其他符号)
    a[bcd]e :以a开头,以e结尾,中间包含b或c或d
    [0-9] :任意数字

(2).量词

  • {}中加入数字,即可指定出现多少次

  • 量词只对它前边的一个内容起作用,如果需要多个可以给前面多个内容加一个()
    [a{3}] :即为aaa
    [a{1,3}b] :即为ab,aab,aaab
    [a{3,}] :a最少为3次
    [(ab){3}] :即为ababab
    [ab+c] :至少一个,相当于{1,}
    [ab*c] :0个或多个,相当于{0,}
    [ab?c] :0个或1个,相当于

(3).开头与结尾

  • 使用 ^ 来检查一个字符串是否以指定内容开头
    ^a :检查一个字符串是否以a开头

  • 使用 $ 来检查一个字符串是否以指定内容结尾
    a$ :检查一个字符串是否以a结尾

  • 如果在一个正则表达式中同时使用上面两个字符,则要求字符串必须完全符合正则表达式
    ^a$ :表示只含有字符a

(4).除了

  • 使用 ^ 可以表示除了指定内容

  • 区别于上面的开头:
    ^abc :这个指的是以abc开头
    [^abc] :这个指的是除了abc

(4).任意字符

  • 在正则中 . 表示任意字符,如果需要查找字符串中是否含有 . 时需要使用转义字符

(5).转义字符

  • 在正则中 \ 表示转义字符

  • 注意:在使用构造函数时,由于它的参数时一个字符串,而 \ 是字符串中的转义字符,所以如果要使用 \ 则需要使用 \\ 来代替

1
2
var reg = RegExp("\\.");
console.log(reg.test("abc."));
  • 转义字符中比较特殊的:
    \w :查找任意字母、数字、_
    \W :查找除了字母、数字、_
    \d :查找任意的数字
    \D :查找除了数字
    \s :查找空格
    \S :查找除了空格
    \b :查找单词边界
    \B :查找除了单词边界

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

  • 这里的 j-m 部分

  • test() 函数用来检测一个字符串是否匹配某个模式,它返回一个布尔值,语法如下:

1
2
3
regexp.test(str)	即正则表达式模式或者可用标志的正则表达式对象
例如:
/[a-z]/i.test(123a)

5.案例分析

(1).检测手机号是否正确

  • 手机号的规则如下:
    1.第一位为1
    2.第二位为3-9
    3.第三位开始为0-9(9位)
    4.手机号总共为11位

  • 且手机号都是数字开头结尾,所以需要同时使用 ^$ 符号

1
2
3
var phoneStr="17835204401";
var reg = /^1[3-9][0-9]{9}$/;
console.log(reg.test(phoneStr));

(2).去除字符串左右两端的空格

  • 当从用户那里获取到一个字符串时,用户可能无意间在左右两边打上了空格,这时我们可以自动的帮其去除

  • 去除空格可以考虑用空串替换空格,只替换开头结尾可以使用 ^$ ,同时去除可以用 | 和全局匹配模式。如下:

1
2
3
var str = "   abcd efgh   ";
str = str.replace(/^\s*|\s*$/g,"");
console.log(str);

(3).检测邮箱是否准确

  • 在写正则表达式时,可以将需要写的内容分段来写,然后再合在一起,可看下图:
    js11.png

  • 所以根据上述可以这样写出来:

1
\w{3,}   (\.\w{3,})*   @   [a-z0-9]+   (\.[a-z]{2,5}){1,2}
  • 因为只能包含有邮件格式,所以需要加上开头与结尾符号,实际效果如下:

1
2
3
var mailStr = "15035374951@126.com";
var reg = /^\w{3,}(\.\w{3,})*@[a-z0-9]+(\.[a-z]{2,5}){1,2}$/i;
console.log(reg.test(mailStr));

七、包装类

1.简介

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

  • 这三个包装类分别为:String()、Number()、Boolean()

  • 调用方法如下:

1
2
var num = new Number();
console.log(num);
  • 但其实在开发中并不会使用上面的方法调用

2.浏览器的隐式调用

  • 首先,方法和属性只能添加给对象,不能添加给基本数据类型

  • 但是我们对一些基本数据类型的值去调用属性和方法时,浏览器会 临时 使用包装类将其转换为对象,然后再调用对象的属性和方法,如下:

1
2
3
var s = 123;
s = s.toString();
console.log(s);

3.String()

(1).属性

  • 该对象的属性如下:
    js09.png

(2).方法

a.charAt()

  • 可以返回字符串中指定位置的字符

  • 根据索引获取指定的字符

1
2
3
str = "Wrysmile";
var result = str.charAt(6);
console.log(result);

b.charCodeAt()

  • 获取指定位置字符的字符编码(Unicode编码)

1
2
3
str = "Wrysmile";
var result = str.charCodeAt(0);
console.log(result);

c.fromCharCodeAt()

  • 可以根据字符编码来获取字符

  • 该方法需要使用 String 去调用,如下:

1
2
var result = String.fromCharCodeAt(0x2692);
console.log(result);

d.conCat()

  • 可以用来连接两个或多个字符串

  • 作用和+号一样

1
2
3
str = "Wrysmile";
var result = str.conCat("你好");
console.log(result);

e.indexOf()

  • 可以检索一个字符串中是否含有指定内容

  • 如果字符串中含有该内容,则会返回其第一次出现的索引如果没有该内容,则返回 -1

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

1
2
3
str = "Wrysmile";
var result = str.indexOf("s",0);
console.log(result);

f.lastIndexOf()

  • 和上面用法一样,不同的是上者从前往后找,而下者从后往前找

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

1
2
3
str = "Wrysmile";
var result = str.lastIndexOf("s",5);
console.log(result);

g.slice()

  • 可以从字符串中截取指定的内容,不会影响原字符串,而是将截取到的内容返回

  • 参数:第一个:开始位置的索引(包括开始位置)第二个:结束位置的索引(不包括结束位置)

  • 如果省略第二个参数,则会截取到后边所有的,也可以传递一个负数作为参数,负数的话将会从后边计算

1
2
3
str = "Wrysmile";
var result = str.slice(1,5);
console.log(result);

h.substring()

  • 可以用来截取一个字符串

  • 和 slice() 的区别是:
    1.该方法不接受负值作为参数,否则会默认使用0
    2.该方法会自动调整参数的位置,即第二个参数小于第一个参数,会自动交换

i.substr()

  • 用来截取字符串

  • 参数:第一个:开始位置的索引第二个:截取的长度

j.split()

  • 可以将一个字符串拆分成一个数组

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

1
2
3
str = "Wrysmile";
var result = str.split("");
console.log(result);
  • 按字母来拆分成数组,且默认为全局匹配模式,如下:

1
2
3
str = "1a2b3c4d5e6";
var result = str.split(/[A-z]/);
console.log(result);
  • 空串会将每个字符拆分成数组元素

  • 可以搜索字符串中是否含有指定内容

  • 如果搜索到指定内容,则会返回第一次(即使设置全局匹配模式也还是第一个)出现的索引,否则返回-1

  • 但是该方法可以接收一个正则表达式作为参数

1
2
3
str = "abc def ghi adc";
var result = str.search(/a[bdf]c/);
console.log(result);

l.match()

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

  • 默认情况下只会找到第一个符合要求的内容,所以需要设置全局匹配模式来获取所有内容

  • 该方法会将匹配到的内容封装到一个数组中返回,即便只有一个结果

1
2
3
str = "1a2b3c4d5e6A";
var result = str.match(/[A-z]/ig);
console.log(result);

m.replace()

  • 可以将字符串中指定的内容替换为新的内容

  • 参数:
    1.被替换的内容
    2.新的内容,空串为空

  • 默认情况下只会替换第一个内容,所以可以在第一个参数中传入正则表达式

1
2
3
str = "1a2b3c4d5e6A";
var result = str.replace(/[a-z]/ig,"");
console.log(result);

n.other

  • 其他的方法如下:
    js10.png

八、JSON

1.介绍

  • JSON 为 JavaScript Object Notation,即JS对象表示法

  • JSON 就是一个特殊格式的字符串,可以被任意的语言所识别,并且可以转换为任意语言中的对象,在开发中主要用来数据的交互

  • 注意:JSON 字符串中的属性名必须加双引号

2.分类

(1).对象

  • 使用 {} 括起来的,如下:

1
var obj = '{"name":"孙悟空","age":18,"gender":"男"}';

(2).数组

  • 使用 [] 括起来的,如下:

1
var obj = '[1,2,3,"hello"]';

3.允许值

  • JSON 中只允许传入以下值:字符串数值布尔值
    null
    对象数组

  • 示例如下:

1
2
var obj = '{"arr":[1,2,3]}';
var arr = '[{"name":"孙悟空","age":18,"gender":"男"},{"name":"猪八戒","age":18,"gender":"男"}]';

4.JSON工具类

  • 在 JS 中有个工具类,就叫 JSON,可以将一个 JSON 转换为 JS对象,也可以将一个 JS对象转换为 JSON

  • JSON 对象在IE7及以下不支持

(1).JSON.parse()方法

  • 可以将一个 JSON 字符串转换为 JS对象

  • 它需要一个 JSON 字符串作为参数

1
2
3
var obj = '{"name":"孙悟空","age":18,"gender":"男"}';
var o = JSON.parse(obj);
console.log(o);

(2).JSON.stringify()方法

  • 可以将一个 JS对象转换为 JSON 字符串

  • 它需要一个 JS对象作为参数

1
2
3
var obj = {name:"孙悟空",age:18,gender:"男"};
var str = JSON.stringify(obj);
console.log(str);

(3).兼容IE7及以下

  • eval() 这个函数可以用来执行一段字符串形式的 JS 代码,并将执行结果返回

  • 如果使用该函数执行得字符串中含有 {},他会将其当成是代码块。如果不希望将其当成代码块解析,则需要在字符串前后各加一个 ()

1
2
3
var str = '{"name":"孙悟空","age":18,"gender":"男"}';
var obj = eval("("+str+")");
console.log(obj);
  • 注意:eval() 这个函数的功能很强大,可以直接执行一个字符串中的 JS代码,但在开发中尽量不要使用,首先它的性能比较差,而且它还具有安全隐患

!最终方案

  • 如果需要兼容 IE7集以下的 JSON 操作,则可以通过引入一个外部的 JS文件来处理

  • 该 js 文件重新创建了一个 JSON 对象

九、JS高级内容

1.基础深入

(1).数据类型部分

1).undefined 和 null 的区别?

  • undefined 是定义但未赋值

  • null 是定义且赋值了,只是值为 null

2).什么时候给变量赋值为 null?

  • 初始赋值时,表明将要赋值的对象

  • 结束时赋值,让对象成为垃圾对象,被垃圾回收器回收

3).如何判断数据类型?

  • typeof:

    • 特点:判断基本数据类型,返回字符串
    • 作用:判断变量的数据类型
    • 优点:可以判断undefined、数值、字符串、布尔值、function
    • 缺点:不能判断null与object、array与object
  • instanceof:

    • 特点:判断引用数据类型,返回布尔值
    • 作用:判断一个变量是否属于某个对象的实例
  • ===:可以判断undefined、null

(2).常量与变量部分

1).怎么严格区分变量类型与数据类型?

  • 数据的类型包含:基本数据类型和对象数据类型

  • 变量的类型包含:基本数据类型(保存的是基本类型的数据)和引用数据类型(保存的是地址值)

2).在JS调用函数时传递变量参数时,是值传递还是引用传递?

  • 理解1:都是值(基本/地址值)传递

  • 理解2:可能是前者,也可能是后者(地址值)

(3).垃圾回收部分

问:JS引擎如何管理内存?

1).内存生命周期

  • 分配小内存空间,得到它的使用权

  • 存储数据,可以反复进行操作

  • 释放小内存空间

2).释放内存

  • 局部变量:函数执行完毕后自动释放

  • 对象:成为垃圾对象后由垃圾回收器回收

(4).对象部分

1).如何访问对象内部的数据?

  • .属性名:编码简单,有时不能使用

  • ['属性名']:编码麻烦,但可通用

2).什么时候使用第二种访问?

  • 属性名含特殊字符:- 空格

  • 属性名不确定

(5).函数部分

1).如何调用(执行)函数?

  • test():直接调用

  • obj.test():通过对象调用

  • new test():new调用

  • test.call/apply(obj):临时让test成为obj的方法进行调用

2).什么是回调函数?

  • 自定义

  • 没有调用

  • 函数执行

3).常见回调函数有什么?

  • dom事件的回调函数

  • 定时器的回调函数

  • ajax请求回调函数

  • 生命周期回调函数

4).立即执行函数的作用是什么?

  • 隐藏实现

  • 不会污染外部(全局)命名空间

5).this是什么?

  • 任何函数本质上都是通过某个对象来调用的,如果没有直接指定就是window

  • 所有函数内部都有一个变量this

  • 它的值是调用函数的当前对象

6).如何确定this的值?

  • test():window

  • p.test():p

  • new test():新建对象

  • p.call(obj):obj

(6).代码格式部分

  • js一条语句的后面可以不加分号,Vue.js就是一个例子

  • 在下面两种情况下不加分号会有问题:

    • 小括号开头的前一条语句
    • 中方括号开头的前一条语句
  • 解决办法:在行首加分号即可

2.函数高级

(1).原型部分

1).prototype 属性

  • 每个函数都有一个 prototype 属性,他默认指向一个 Object 空对象(即称为原型对象)

  • 原型对象中有一个属性 constructor,它指向函数对象,如下图:
    js16.png

  • 给原型对象添加属性(一般都是方法)的作用:函数的所有实例对象自动拥有原型中的属性(方法)

2).显示与隐式原型

  • 显示原型(属性):每个函数function都有一个 prototype

  • 隐式原型(属性):每个实例对象都有一个 __proto__

  • 对象的隐式原型的值为其对应构造函数的显示原型的值,相关内存结构图如下:
    js17.png

总结:
  • 函数的 prototype 属性:在定义函数时自动添加,默认值是一个空的Object对象

  • 对象的 __proto__ 属性:创建对象时自动添加,默认值为构造函数的prototype属性值

  • 程序员能直接操作显示原型,但不能直接操作隐式原型(ES6之前)

3).原型链

a).基础
  • 访问一个对象的属性时,具体步骤如下:

    • 先在自身属性中查找,找到返回
    • 如果没有,再沿着 __proto__ 这条链向上查找,找到返回
    • 如果最终没找到,返回 undefined
  • 根据上述步骤可以看出,访问对象属性时仅仅只看隐式原型,所以实质上叫做隐式原型链

  • 作用:查找对象的属性或方法

  • 原型链示意图如下:
    js18.png

b).构造函数、原型、实例对象之间的关系
  • 原型对象由 constructor 属性指向其构造函数,结构如下图:
    js19.png

  • 所有函数的 __proto__ 都是一样的,结构如下图:
    js20.png

  • 根据上图可以得知:

    • 函数的显示原型指向的对象默认是空的Object实例对象(但Object不满足)
    • 所有函数都是 Function 的实例(包含Function)
    • Object 的原型对象是原型链的尽头,为 null
c).属性问题
  • 读取对象的属性值时:会自动到原型链中查找

  • 设置对象的属性值时:不会查找原型链,如果当前对象中没有此属性,直接添加此属性并设置其值

  • 方法一般定义在原型中,属性一般通过构造函数定义在对象本身上

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
<script type="text/javascript">
function Fn(){}
Fn.prototype.a = "xxx";
// 对应上面第一句
var fn1 = new Fn();
console.log(fn1,fn1.a);
// 对应上面第二句
var fn2 = new Fn();
fn2.a = "yyy";
console.log(fn2,fn1.a);
// 对应上面第三句
function Person(name,age){
this.name = name;
this.age = age;
}
Person.prototype.setName = function(name){
this.name = name;
}
var p1 = new Person('Tom',18);
p1.setName('Bob');
console.log(p1);
var p2 = new Person('Jack',18);
p2.setName('Cat');
console.log(p2);
</script>
d).instanceof如何判断的?
  • 表达式:A instanceof B

  • 如果B函数的显示原型对象在A对象的原型链上,返回 true,否则返回 false

  1. 案例代码:

1
2
3
4
function Foo(){}
var f1 = new Foo();
console.log(f1 instanceof Foo); //true
console.log(f1 instanceof Object); //true
  • 图片解析:
    js21.png

  1. 案例代码:

1
2
3
4
5
6
function Foo(){}
console.log(Object instanceof Function); //true
console.log(Object instanceof Object); //true
console.log(Function instanceof Function); //true
console.log(Function instanceof Object); //true
console.log(Object instanceof Foo); //false
  • 图片解析:
    js22.png

(2).执行上下文部分

1).变量提升与函数提升

  • 先执行变量提升,后执行函数提升

a).变量声明提升
  • 通过 var 定义(声明)的变量,在定义语句之前就可以访问到

  • 值:undefined

b).函数声明提升
  • 通过 function 声明的函数,在之前就可以直接调用

  • 值:函数定义(对象)

c).出现原因
  • 因为下面的全局执行上下文和函数执行上下文中的预处理操作

2).执行上下文

  • 执行上下文个数规律:n+1,n为执行函数的次数,1为全局执行上下文

a).全局执行上下文
  • 在执行全局代码前将 window 确定为全局执行上下文

  • 对全局数据进行预处理:

    • var 定义的全局变量 ==> undefined,添加为 window 的属性
    • function 声明的全局函数 ==> 赋值(fun),添加为 window 的方法
    • this ==> 赋值(window)
  • 开始执行全局代码

b).函数执行上下文
  • 在调用函数,准备执行函数体之前,创建对应的函数执行上下文对象(虚拟的,存在于栈中)

  • 对局部数据进行预处理:

    • 形参变量 ==> 赋值(实参),添加为执行上下文的属性
    • arguments ==> 赋值(实参列表),添加为执行上下文的属性
    • var 定义的局部变量 ==> undefined,添加为执行上下文的属性
    • function 声明的函数 ==> 赋值(fun),添加为执行上下文的方法
    • this ==> 赋值(调用函数的对象)
  • 开始执行函数体代码

3).执行上下文栈

  • 在全局代码执行前,JS 引擎就会创建一个栈来存储管理所有的执行上下文对象

  • 在全局执行上下文(window)确定后,将其添加到栈中(压栈)

  • 在函数执行上下文创建后,将其添加到栈中(压栈)

  • 在当前函数执行完后,将栈顶的对象移除(出栈)

  • 当所有的代码执行完后,栈中只剩下 window

4).相关面试题

  • 共有四个题,请点击 这里 查看(为了体验到完整功能,请使用PC打开,源码右键“查看网页源代码”即可)

(3).作用域部分

1).分类

  • 全局作用域

  • 函数作用域

  • (ES6增加)块作用域:即大括号作用域

  • 作用域个数规律:n+1,n为函数作用域的个数,1为全局作用域

2).作用

  • 隔离变量,不同作用域下同名变量不会有冲突

3).与执行上下文的区别

a).区别1
  • 全局作用域之外,每个函数都会创建自己的作用域,作用域在函数定义时就确定了,而不是在函数调用时

  • 全局执行上下文环境是在全局作用域确定之后,js代码马上执行之前创建

  • 函数执行上下文环境是在调用函数时,函数体代码执行之前创建

b).区别2
  • 作用域是静态的,只要函数定义好了就一直存在,且不会变化

  • 上下文环境是动态的,调用函数时创建,函数调用结束时上下文环境就会释放

c).联系
  • 上下文环境(对象)是从属于所在的作用域

  • 全局上下文环境 ==> 全局作用域

  • 函数上下文环境 ==> 对应的函数使用域

4).作用域链

  • 多个上下文关系的作用域形成的链,方向是从下向上的(从内到外)

  • 查找变量时就是沿着作用域链来查找的

(4).闭包部分

1).循环遍历加监听

  • 常规方法一:正常的循环遍历,请点击 这里 查看(为了体验到完整功能,请使用PC打开,源码右键“查看网页源代码”即可)

  • 常规方法二:利用立即执行函数来遍历(即闭包),请点击 这里 查看(为了体验到完整功能,请使用PC打开,源码右键“查看网页源代码”即可)

2).理解闭包

a).如何产生闭包?
  • 当一个嵌套的内部(子)函数引用了嵌套的外部(父)函数的变量(函数)时,就产生了闭包

b).闭包到底是什么?
  • 需要使用Chrome调试来查看

  • 理解一:闭包是嵌套的内部函数

  • 理解二:包含被引用变量(函数)的对象,闭包存在于嵌套的内部函数中

c).产生闭包的条件?
  • 函数嵌套

  • 内部函数引用了外部函数的数据(变量/函数)

  • 执行外部函数(貌似目前需执行内部函数才会出现闭包,存疑

3).常见的闭包

  • 将函数作为另一个函数的返回值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function fn1(){
// 此时已经产生了闭包(函数提升,内部函数对象已经创建了)
var a = 2;
function fn2(){
a++;
console.log(a);
}
return fn2;
}
var f = fn1();
// 这里执行的是内部函数,因为fn1中返回了fn2,相当于是将fn2赋值给了f
f();
f();
// 若想要再产生一个闭包,则只需再调用一次外部函数即可
fn1();
  • 将函数作为实参传递给另一个函数调用

1
2
3
4
5
6
function showDeley(msg,time){
setTimeout(function(){
alert(msg);
},time);
}
showDeley('Wrysmile',2000);

4).闭包的作用

  • 在函数执行完后,使用函数内部的变量仍然存活在内存中(延长了局部变量的生命周期)

  • 让函数外部可以操作(读写)到函数内部的数据(变量/函数)

总结
  • 函数执行完后,函数内部声明的局部变量一般是不存在的,存在于闭包中的变量才可能存在

  • 在函数外部不能直接访问函数内部的局部变量,但可以通过闭包让外部操作它

5).闭包的生命周期

  • 产生:在嵌套内部函数定义执行完时就产生了(注意:不是在调用时

  • 死亡:在嵌套的内部函数成为垃圾对象时

6).闭包的应用——自定义JS模块

  • 特点:

    • 具有特定功能的 js 文件
    • 将所有的数据和功能都封装在一个函数内部(私有的)
    • 只向外暴露一个包含n个方法的对象或函数
    • 模块的使用者,只需要通过模块暴露的对象调用方法来实现对应的功能
  • 应用:

    • 请点击 这里 查看(为了体验到完整功能,请使用PC打开,源码右键“查看网页源代码”即可)

7).闭包的缺点

  • 函数执行完后,函数内的局部变量没有释放。占用内存时间会变长(解决办法:及时释放

  • 容易造成内存泄漏解决办法:能不用闭包就不用

8).闭包面试题

  • 请点击 这里 查看(为了体验到完整功能,请使用PC打开,源码右键“查看网页源代码”即可)

3.面向对象高级

(1).对象创建模式

1).Object构造函数模式

  • 方法:先创建空Object对象,再动态添加属性/方法

  • 适用场景:起始时不确定对象内部的数据

  • 问题:语句太多

1
2
3
4
5
6
7
var p = new Object();
p.name = 'Tom';
p.age = 18;
p.setName = function(name){
this.name = name;
}
console.log(p.name,p.age);

2).对象字面量模式

  • 方法:适用{}创建对象,同时指定属性/方法

  • 适用场景:起始时对象内部数据时确定的

  • 问题:如果创建多个对象,有重复代码

1
2
3
4
5
6
7
8
var p = {
name : 'Tomm',
age : 19,
setName : function(name){
this.name = name;
}
}
console.log(p.name,p.age);

3).工厂模式

  • 方法:通过工厂函数动态创建对象并返回

  • 适用场景:需要创建多个对象,但一般不用

  • 问题:对象没有一个具体类型,都是Object类型

  • 工厂函数:返回一个对象的函数就叫做工厂函数

1
2
3
4
5
6
7
8
9
10
11
12
function createPerson(name,age){
var obj = {
name : name,
age : age,
setName : function(name){
this.name = name;
}
};
return obj;
}
var p2 = createPerson('Tommm',20);
console.log(p2.name,p2.age);

4).自定义构造函数模式

  • 方法:自定义构造函数,通过new创建对象

  • 适用场景:需要创建多个类型确定的对象

  • 问题:每个对象都有相同的数据,浪费内存

1
2
3
4
5
6
7
8
9
function Person(name,age){
this.name = name;
this.age = age;
this.setName = function(name){
this.name = name;
}
}
var p3 = new Person('Tommmm',21);
console.log(p3.name,p3.age);

5).构造函数+原型的组合模式

  • 方法:自定义构造函数,属性在函数中初始化,方法添加到原型上

  • 适用场景:需要创建多个类型确定的对象

1
2
3
4
5
6
7
8
9
function Person(name,age){
this.name = name;
this.age = age;
}
Person.prototype.setName = function(name){
this.name = name;
}
var p4 = new Person('Tommmmm',22);
console.log(p4.name,p4.age);

(2).继承模式

1).原型链继承

  • 方法:
    1.定义父类型构造函数
    2.给父类型的原型添加方法
    3.定义子类型构造函数
    4.创建父类型的对象赋值给子类型的原型
    5.将子类型原型的构造属性设置为子类型
    6.给子类型原型添加方法
    7.创建子类型的对象即可调用父类型的方法

  • 关键:子类型的原型为父类型的一个实例对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function Father(){
this.fatherProp = 'Father Property';
}
Father.prototype.showFatherProp = function(){
console.log(this.fatherProp);
};
function Sun(){
this.sunProp = 'Sun Property';
}
Sun.prototype = new Father();
Sun.prototype.constructor = Sun;
Sun.prototype.showSunProp = function(){
console.log(this.sunProp);
};
var sun = new Sun();
sun.showSunProp();
sun.showFatherProp();

2).借用构造函数继承(假的)

  • 方法:
    1.定义父类型构造函数
    2.定义子类型构造函数
    3.在子类型构造函数中调用父类型构造函数

  • 关键:在子类型构造函数中通过 call() 调用父类型构造函数

1
2
3
4
5
6
7
8
9
10
function Person(name,age){
this.name = name;
this.age = age;
}
function Student(name,age,height){
Person.call(this,name,age);
this.height = height;
}
var s = new Student('Tom',18,170);
console.log(s.name,s.age,s.height);

3).组合继承(最优解)

  • 方法:
    1.利用原型链实现对父类型对象的方法继承
    2.利用 call() 借用父类型构造函数初始化相同属性

  • 如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function Person(name,age){
this.name = name;
this.age = age;
}
Person.prototype.setName = function(name){
this.name = name;
};
function Student(name,age,height){
Person.call(this,name,age); // 为了得到属性
this.height = height;
}
Student.prototype = new Person(); // 为了能看到父类型的方法
Student.prototype.constructor = Student; // 修正constructor属性
Student.prototype.setHeight = function(height){
this.height = height;
};
var s = new Student('Tom',18,170);
console.log(s.name,s.age,s.height);
s.setName('Bob');
s.setHeight(171);
console.log(s.name,s.age,s.height);

(3).new一个对象背后做了什么?

  • 创建了一个空对象

  • 给对象设置了 __proto__ ,值为构造函数对象的 prototype 属性值

  • 执行构造函数体(给对象添加属性/方法)

4.线程机制与事件机制

(1).进程与线程

  • 进程:程序的一次执行,它占有一片独有的内存空间

  • 线程:是进程内的一个独立执行单元、是程序执行的一个完整流程、是 CPU 的最小调度单元

  • 线程池(thread pool):保存多个线程对象的容器,实现线程对象的反复利用

1).相关知识

  • 应用程序必须运行在某个进程的某个线程上

  • 一个进程中至少有一个运行的线程为主线程,进程启动后会自动创建

  • 一个进程中也可以同时运行多个线程,即程序是多线程运行的

  • 一个进程内的数据可以供其中的多个线程直接共享

  • 多个进程之间的数据是不能直接共享的

2).相关问题

a).多线程与多进程?
  • 多进程运行:一个应用程序可以同时启动多个实例运行

  • 多线程:在一个进程内,同时有多个线程运行

b).单线程与多线程?
  • 单线程:

    • 优点:顺序编程简单易懂
    • 缺点:效率低
  • 多线程:

    • 优点:能有效提升 CPU 的利用率
    • 缺点:创建多线程开销、线程间切换开销、死锁与状态同步问题
c).JS是单线程还是多线程?
  • js 是单线程运行

  • 使用 H5 中的 Web Workers 可以多线程运行

d).浏览器运行是单线程还是多线程?
  • 都是多线程

e).浏览器运行是单进程还是多进程?
  • 有的是单进程,有的是多进程

(2).浏览器内核

  • 是支撑浏览器运行的最核心的程序,且不同的浏览器可能不一样

  • 内核是由很多模块组成的(前四个为主线程,后三个为分线程):

    • JS 引擎模块:负责 js 程序的编译与运行
    • html、css 文档解析模块:负责页面文本的解析
    • DOM、CSS 模块:负责 dom、css 在内存中的相关处理
    • 布局和渲染模块:负责页面的布局和效果的绘制(内存中的对象)
    • 定时器模块:负责定时器的管理
    • 事件响应模块:负责事件的管理
    • 网络请求模块:负责 ajax 请求

(3).思考定时器

1).定时器真是定时执行的吗?

  • 定时器并不能保证真正定时执行

  • 一般会延迟一丁点(可以接受),有时也会延迟很长时间(不能接受)

2).定时器的回调函数时在分线程执行的吗?

  • 在主线程执行,因为js是单线程

3).定时器是如何实现的?

  • 事件循环模型

(4).思考JS单线程

1).如何证明js执行是单线程的?

  • setTimeout() 的回调函数是在主线程执行的

  • 定时器回调函数只有在运行栈中的代码全部执行完后才有可能执行

2).为什么js要用单线程模式?

  • JS 的单线程与它的用途有关

  • 作为浏览器脚本语言,JS 的主要用途是与用户互动以及操作 DOM

  • 这决定了它只能是单线程,否则会带来很复杂的同步问题

3).代码的分类?

  • 初始化执行代码

  • 回调执行代码

4).js引擎执行代码的基本流程?

  • 先执行初始化代码:包含一些特别的代码

    • 设置定时器
    • 绑定事件监听
    • 发送 ajax 请求
  • 后面在某个时刻才会执行回调代码,回调函数异步执行

(5).事件循环模型

1).模型的两个重要组成部分?

  • 事件管理模块

  • 回调队列

2).模型的运转流程?

  • 执行初始化代码,将事件回调函数交给对应模块管理

  • 当事件发生时,管理模块会将回调函数及其数据添加到回调队列中

  • 只有当初始化代码执行完后(可能要一定时间),才会遍历读取回调队列中的回调函数执行

3).模型原理图

  • 如下图:
    js23.png

4).相关概念

  • 执行栈(execution stack):所有的代码都是在此空间中执行的

  • 浏览器内核(browser core)

  • 回调队列(callback queue):以下均总称为此

    • 任务队列(task queue)
    • 消息队列(message queue)
    • 事件队列(event queue)
  • 事件轮询(event loop):从任务队列中循环取出回调函数放入执行栈中处理(一个接一个)

  • 事件驱动模型(event-driven interaction model)

  • 请求响应模型(request-response model)

(6).H5 Web Workers(多线程)

1).介绍

  • H5 规范提供了 js 分线程的实现,取名为:Web Workers

  • 相关 API:

    • Worker:构造函数,加载分线程执行的js文件
    • Worker.prototype.onmessage:用于接收另一个线程的回调函数
    • Worker.prototype.postMessage:向另外一个线程发送消息
  • 可以将一些大计算量的代码交由 Web Workers 运行而不冻结用户界面,但是子线程完全受主线程控制,且不得操作 DOM,所以它并没有改变 JS 单线程的本质

2).使用

  • 创建在分线程执行的 js 文件,一般命名为 worker.js

1
2
3
4
5
6
7
8
var onmessage = function(event){
// 不能用函数声明
console.log('onMessage()');
// 通过event.data获得发送来的数据
var upper = event.data.toUpperCase();
// 将获取到的数据发送回主线程
postMessage(upper);
}
  • 在主线程中的 js 中发消息并设置回调

1
2
3
4
5
6
7
8
9
// 创建一个Worker对象并向它传递将在新线程中执行的脚本的URL
var worker = new Worker("js/worker.js");
// 接收Worker传过来的数据函数
worker.onmessage = function(event){
// 传过来的数据存放在event.data里面
console.log(event.data);
}
// 向Worker发送数据
worker.postMessage("xxx");

3).图解

  • 如下图:
    js24.png

4).缺点

  • 不能跨域加载 JS

  • worker 内代码不能访问 DOM(更新UI)

  • 不是每个浏览器都支持这个新特性

5).应用之斐波那契数列

  • 不使用该方案求解,当数值较大时会卡住且用户无法操作。请点击 这里 查看(为了体验到完整功能,请使用PC打开,源码右键“查看网页源代码”即可)

  • 使用该方案求解,用户可以进行正常操作。请点击 这里 查看(为了体验到完整功能,请使用PC打开,源码右键“查看网页源代码”即可)

5.引申

(1).内存溢出

  • 一种程序运行出现的错误

  • 当程序运行需要的内存超过了剩余的内存时,就会抛出内存溢出的错误

(2).内存泄漏

  • 占用的内存没有及时释放

  • 内存泄漏积累多了就容易导致内存溢出

常见的内存泄漏

  • 意外的全局变量

1
2
3
4
5
function fn1(){
a = 3;
console.log(a);
}
fn1();
  • 没有及时清理的计时器或回调函数

1
2
3
setInterval(function(){
console.log('----');
},1000);
  • 闭包

1
2
3
4
5
6
7
8
9
function fn2(){
var b = 4;
function fn3(){
console.log(++b);
}
return fn3;
}
var f = fn2();
f();