JavaScript

theme: condensed-night-purple

JavaScript简介

JavaScript历史

1995年JavaScript问世,最初的主要用途是代替Perl等服务器端语言处理输入验证。网景公司在其Navigator浏览器中加入JavaScript以实现此功能。

ECMAScript是JavaScript的标准,在日常场合,这两个词是可以互换的。

JavaScript 是一种轻量级的脚本语言。所谓“脚本语言”,指的是它不具备开发操作系统的能力,而是只用来编写控制其他大型应用程序的“脚本”。

JavaScript 是一种嵌入式(embedded)语言。它本身提供的核心语法不算很多
JavaScript版本SNAGHTML4acb84a.PNG

实时效果反馈

1. ECMAScript和JavaScript关系:

A JavaScript是ECMAScript的父级

B JavaScript是ECMAScript的子级

C 不存在ECMAScript这个名字

D 前者是后者的规格,后者是前者的一种实现

2. 以下哪个不是JavaScript的优点:

A JavaScript操控浏览器的能力

B JavaScript广泛的使用领域

C JavaScript易学性

D JavaScript可以实现操作系统

答案

1=>D 2=>D

JavaScript语句、标识符

js中多个空格和换行会被忽略掉,只会变成一个空格,利用该特点可对代码进行格式化

语句

JavaScript 程序的单位是行(line),也就是一行一行地执行。一般情况下,每一行就是一个语句

var num = 10;

语句以分号结尾,一个分号就表示一个语句结束

标识符

标识符(identifier)指的是用来识别各种值的合法名称。最常见的标识符就是变量名

标识符是由:字母、美元符号($)、下划线(_)和数字组成,其中数字不能开头

温馨提示

中文是合法的标识符,可以用作变量名(不推荐)·

JavaScript保留关键字

以下关键字不需要强行记忆!

JavaScript有一些保留字,不能用作标识符:arguments、break、case、catch、class、const、continue、debugger、default、delete、do、else、enum、eval、export、extends、false、finally、for、function、if、implements、import、in、instanceof、interface、let、new、null、package、private、protected、public、return、static、super、switch、this、throw、true、try、typeof、var、void、while、with、yield。

实时效果反馈

1. 以下哪个命名是正确的:

A var const = 10;

B var 10Num = 20;

C var @A = 30;

D var age=20;

2. 以下哪个是标识符命名规则:

A 字母、美元符号($)、下划线(_)和数字

B 字母、美元符号($)、下划线(_)和数字,其中数字不能开头

C 字母、美元符号($)、百分号(%)和数字,其中数字不能开头

D 字母、美元符号($)、下划线(_)和特殊符号

答案

1=>D 2=>B

常量、字面量、变量

常量

用const声明常量,常量只能赋值一次,重复赋值会报错。一般用大写字母表示常量。
除了常规的常量,有一些对象类型的数据也会声明为常量

const PI = 3.1415926

字面量

字面量其实就是一个值,它所代表的含义就是它字面的意思,比如 1 2 3 'hello' true null, 在js中所有字面量都可以直接使用,但是直接使用字面量并不方便

var num = 10;

变量

变量的内存结构

不同于java和C语言中的变量直接将字面量值存到变量中,与python类似,javascript变量中并不存储任何字面量,而是存储值的内存地址。当有多个变量具有相同的值时,他们存储了该值相同的地址,所以同一个字面量只在内存中存储一份,这样更节省空间。image

变量的作用域

作用域指的是一个变量的可见区域
作用域有两种:

  • 全局作用域
    • 全局作用域在网页运行时创建,在网页关闭时消耗
    • 所有直接编写到script标签中的代码都位于全局作用域中
    • 全局作用域中的变量是全局变量,可以在任意位置访问
  • 局部作用域
    • 块作用域
      • 块作用域是一种局部作用域
      • 块作用域在代码块执行时创建,代码块执行完毕它就销毁
      • 在块作用域中声明的变量是局部变量,只能在块内部访问,外部无法访问
    • 函数作用域:在函数中定义的变量是函数作用域
var a=1;//全局作用域
{
var g=6//var声明的变量没有块级作用域,所以该变量仍在全局有效
let b=2;//块级作用域
}
console.log(g)//6
function f(){
var c=3//var声明的变量虽然没有块级作用域,但有函数作用域
let h=3//函数作用域
}
d=4;//等效于winndow.d=4;全局作用域
window.e=5//全局作用域

在开发中应该尽量减少直接在全局作用域中编写代码
变量尽量编写在局部作用域中
用let声明的变量,可以使用{}来创建块作用域
用var声明的变量,可以使用函数来创建函数作用域

作用域链

当我们使用一个变量时,JS解释器会优先在当前作用域中寻找变量,如果找到了则直接使用,如果没找到,则去上一层作用域中寻找,直至找到变量
如果一直到全局作用域都没找到,则报错 xxx is not defined

{
            let a = "第一代码块中的a"
            {
				let a = "第二代码块中的a"
				console.log(a)
            }
        }
        let b = 33
        function fn(){
            let b = 44
            function f1(){
                let b = 55
                console.log(b)
            }
            f1()
        }
        fn()

image

变量的重新赋值
var num = 10;
num = 20;

提升

  • 变量提升(没啥用)
    JavaScript 引擎的工作方式是,先解析代码,获取所有被声明的变量,然后再一行一行地运行。这造成的结果,就是所有的变量的声明(不是赋值)语句,都会被提升到代码的头部,这就叫做变量提升(hoisting)。
console.log(num);//undefined
var num = 10;

等效于

var num;
console.log(num);//undefined
num = 10;

let声明的变量实际也会提升,但是在赋值之前解释器禁止对该变量的访问。所以会报错

  • 函数提升
    使用函数声明创建的函数,会在其他代码执行前被创建,所以我们可以在函数声明前调用函数

实时效果反馈

1. 以下代码打印正确的是:

console.log(num);
var num = 10;

A 10

B 错误

C undefined

D num

2. 写出代码执行结果

console.log(a)  // 5

            var a = 1

            console.log(a) // 1

            function a() {
                alert(2)
            }
			
			function a() {
                alert(5)
            }

            console.log(a) // 1

            var a = 3

            console.log(a) // 3

            var a = function () {
                alert(4)
            }

            console.log(a) // 4

            var a

            console.log(a) // 4

提示:考察代码的执行顺序。变量会提升声明,函数会提升定义。所以a首先被赋值为函数。函数的提升按照代码的书写顺序,后面的函数赋值覆盖了前面的函数赋值,所以首先打印的是f a(){console.log(5)}而不是f a(){console.log(2)}答案

1=>C 只声明变量未给变量赋值,执行打印命令会显示undefined,但不会报错
2image

引用JavaScript代码

image

<script></script>标签中写入js语句

<body></body><head></head>中利用<script></script>标签写入js语句
嵌入在<body></body>与嵌入在<head></head>标签中的区别:嵌入在<head></head>中需要加载完js代码才能看到后面需要加载的页面,嵌入在<body></body>中则不加载js仍能看到大致页面

<body>
	<script>
		var age = 20
	</script>
</body>
或
<head>
	<script>
		var age = 20
	</script>
</head>

引入本地独立JS文件或网络来源文件

可以在<body></body><head></head>中通过<script></script>引用js文件

<body>
	<script type="text/javascript" src="./itbaizhan.js"></script>//src属性值为文件地址或网址
	<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.min.js">	</script>
</body>

注意引用了js文件就不能在标签中写入js代码,若需要在该标签中写入代码,则需在另外中写入代码

可以将js代码编写到指定属性中

<body>
	<button onclick="alert('你点我干嘛')">你点我呀</button>
	<a href="javascript:alert('你点我干嘛');">超链接</a>   <!--这里需要加`javascript:`前缀,不然会以为是地址。因为<a>标签的href属性的本质是把href的值放入浏览器地址栏,所以可以把`javascript:alert('你点我干嘛');`放入地址栏,这样也能执行js代码-->
</body>

实时效果反馈

1. 以下哪种不是JS文件引入到HTML文件中的方式:

A JS嵌入到HTML文件中

B 引入本地独立JS文件

C 引入网络来源的JS文件

D JS嵌入到CSS文件中

2. 以下代码是哪种JS引入到HTML文件中的方式:

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

A JS嵌入到HTML文件中

B 引入本地独立JS文件

C 引入网络来源的JS文件

D JS嵌入到CSS文件中

答案

1=>D 2=>B

JavaScript注释与常见输出方式

image

JavaScript注释(CSS注释与JS注释相同)

源码中注释是不被引擎所解释的,它的作用是对代码进行解释。Javascript 提供两种注释的写法:一种是单行注释,用//起头;另一种是多行注释,放在//之间。

// 这是单行注释

/*
 这是
 多行
 注释
*/

嵌入在HTML文件中的注释

<!-- 注释 -->

温馨提示

注释的快捷键:ctrl + /

JavaScript输出方式

JavaScript有很多种输出方式,都可以让我们更直观的看到程序运行的结果

// 在浏览器中弹出一个对话框,然后把要输出的内容展示出来,alert都是把要输出的内容首先转换为字符串然后在输出的
alert("要输出的内容");
//在页面中输出
document.write("要输出的内容"); 
// 在控制台输出内容,这种方式使用最多
console.log("要输出的内容");

实时效果反馈

1. 下述代码横线处应填写的代码:

document.___("要输出的内容");

A document

B alert

C log

D write

2. 下述代码横线处应填写的代码:

console.___("要输出的内容");

A document

B alert

C log

D write

答案

1=>D 2=>C

数据类型

JavaScript是弱数据类型: JavaScript也不知道变量到底属于那种数据类型,只有赋值了才清楚
坑: 使用表单、prompt 获取过来的数据默认是字符串类型的,此时就不能直接简单的进行加法运算console.log('1'+2)输出12
面试常问题:

JavaScript有哪几种基本的数据类型?image

数据类型分类

JavaScript 语言的每一个值,都属于某一种数据类型。JavaScript 的数据类型,共有六种。(ES6 又新增了第七种 Symbol 类型的值和第八种 BigInt类型,当前课程暂不涉及)

原始类型(基础类型)

image
Javascript中一共有7种原始值。原始值一旦创建就不可修改,但变量可以重新赋值

  • 数值(Number)
  • 大整数(Bigint)
  • 字符串(String)
  • 布尔值(Boolean)
  • 空值(Null)
  • 未定义(Undefined)
  • 符号(Symbol)
  1. 数值(Number)
  • 在JS中所有的整数和浮点数都是Number类型
  • JS中的数值并不是无限大的,当数值超过一定范围后会显示近似值
  • Infinity 是一个特殊的数值表示无穷
  • 因为JS不能精确表示范围大的数值且做小数运算时不够精确,所以在JS中进行一些精度比较高的运算时要十分注意
  • NaN 也是一个特殊的数值,表示非法的数值
    其他进制的数字:
  • 二进制 0b
  • 八进制 0o
  • 十六进制 0x

Number相关方法:
详见MDN

  • Number.isInteger() 方法用来判断给定的参数是否为整数。
  • Number.isNaN() 方法确定传递的值是否为Number类型的NaN
    • 在 JavaScript 中,NaN 最特殊的地方就是,我们不能使用相等运算符(== 或 === )来判断一个值是否是 NaN,因为 NaN == NaN 和 NaN === NaN 都会返回 false。因此,必须要有一个判断值是否是 NaN 的方法。
    • 和全局函数 isNaN() 相比,Number.isNaN() 不会自行将参数转换成数字,只有在参数是值为 NaN 的数字时,才会返回 true。
  • Number.parseFloat() 方法可以把一个字符串解析成浮点数。该方法与全局的 parseFloat() 函数相同,并且处于 ECMAScript 6 规范中(用于全局变量的模块化)。
  • Number.parseInt(string, radix) 方法依据指定基数,解析字符串并返回一个整数
    • string要被解析的值。如果参数不是一个字符串,则将其强制转化为字符串。字符串开头的空白符将会被忽略。
    • radix 可选,从 2 到 36 的整数,表示进制的基数。如果超出这个范围,将返回 NaN。假如 radix 未指定或者为 0,除非数字以 0x 或 0X 开头(此时假定为十六进制 16),否则假定为 10(十进制)。
  • Number.toFixed(digits) 返回精确到指定digits位数的字符串(四舍五入),默认参数为0,就是精确到整数位
  1. 大整数(BigInt)
  • 大整数用来表示一些比较大的整数
  • 大整数使用n结尾,它可以表示的数字范围是无限大。a = 99999999999999999999999999999999999999999999999999n
  • BigInt与Number不能混合计算,BigInt只能和BigInt计算
  1. 字符串
  • 转义字符\
    " --> "
    ' --> '
    \ -->
    \t --> 制表符,相当于一个Tab键
    \n --> 换行
  • 模板字符串
    • 使用反单引号` 来表示模板字符串,模板字符串能保存换行,Tab,空格等这些格式
    • 模板字符串中可以通过${变量|js表达式}的形式嵌入变量
  1. 空值 (Null)
  • 空值用来表示空对象
  • 使用typeof检查一个空值时会返回"object",所以typeof无法检测空值
  1. 未定义(Undefined)
  • 当声明一个变量而没有赋值时,它的值就是Undefined
  • 使用typeof检查一个Undefined类型的值时,会返回 "undefined"
  1. 符号(Symbol)
  • 用来创建一个唯一的标识
  • 使用typeof检查符号时会返回 "symbol"
let name = "猪八戒"
let str = `你好,${name}`
let b = 10
console.log(str);//你好,猪八戒
console.log(`b = ${b}`);//b = 10
a = 0b1010
a = 0o10
a = 0xff
console.log(a)//最终打印是十进制
var age = 20;
var name = "尚学堂";//用双或单引号引用的是字符类型
var learn = true;//布尔类型只有2种值,分别为真true,假false
合成类型(复合类型)

对象:因为一个对象往往是多个原始类型的值的合成,可以看作是一个存放各种值的容器。对象里各种值用,隔开image

var user = {
    name:"尚学堂",
    age:20,
    learn:true
}

温馨提示

至于undefined和null,一般将它们看成两个特殊值。

实时效果反馈

1. 以下那个是字符串类型数据:

A var age = 20;

B var name = "尚学堂"

C var learn = true

D var user = {}

2. 以下关于原始数据类型分类正确的是:

A 数值、字符串、布尔值、对象

B 数值、字符串、布尔值、null

C 数值、字符串、布尔值、undefined

D 数值、字符串、布尔值

答案

1=>B 2=>D

typeof运算符(一般用来判断基本类型)

JavaScript 有三种方法,可以确定一个值到底是什么类型。我们现在讲解typeof
注意typeof返回的值类型是字符串格式

数值返回number

typeof 123 // "number"

字符串返回string

typeof '123' // "string"

布尔值返回boolean

typeof false // "boolean"

对象返回object

一般不用来判断是否为object类型,因为其他情况也会返回object类型,比如null,数组(个人认为数组属于特殊的object类型,可以理解为包含了多个数值类型的object)

typeof {} // "object"

null和undefined的区别

在开发中通常认为,null一般代表对象为“没有”,undefined一般代表数值为“没有”,其他用法与含义基本相同

实时效果反馈

1. 下列字符串数据类型的关键字是:

A number

B string

C boolean

D object

2. 以下代码执行结果正确的是:

console.log(typeof 100)

A number

B string

C boolean

D object

答案

1=>B 2=>A

类型转换

类型转换指将一种数据类型转换为其他类型,常用的是将其他类型转换为字符串、数值或布尔值。
类型转换的实质是根据原变量的值创建一个想要转换的类型的新的值。注意要将这个新的值赋给变量才会使变量的值改变
类型转换

  • 转换为字符串
    • 显式转换:String(变量)
    • 隐式转换:变量+ ""
  • 转换为数值
    • 显式转换:Number(变量)
    • 隐式转换:变量=+变量
  • 转换为布尔值
    • 显式转换:Boolean(变量)
    • 隐式转换:!!变量

隐式转换

某些运算符被执行时,系统内部自动将数据类型进行转换,这种转换称为隐式转换
规则:

    • 号两边只要有一个是字符串,都会把另外一个转成字符串
  • 除了+以外的算术运算符 比如 - * / 等都会把数据转成数字类型

缺点:

  • 转换类型不明确,靠经验才能总结
    小技巧:
  • +号作为正号解析时可以转换成数字类型
  • 任何数据和字符串相加结果都是字符串image

显式转换

编写程序时过度依靠系统内部的隐式转换是不严瑾的,因为隐式转换规律并不清晰,大多是靠经验总结的规律。
为了避免因隐式转换带来的问题,通常是对数据进行显示转换
概念:自己写代码告诉系统该转成什么类型
转换为数字型

  • Number(数据)

    • 转成数字类型
    • 如果字符串内容里有非数字,转换失败时结果为 NaN(Not a Number)即不是一个数字
    • NaN也是number类型的数据,代表非数字
  • parseInt(数据)

    • 只保留整数,只有数字开头时生效,非数字开头时会返回NANimage
  • parseFloat(数据)

    • 可以保留小数,只有数字开头时生效,非数字开头时会返回NANimage
  • 转换为字符型:

    • String(数据)
    • 变量.toString(进制),进制有2,8,10,16image

转换成字符串

  1. 调用toString()方法将其他类型转换为字符串
    null和undefined中没有toString()方法,所以它们调用toString()时会报错
let a = 10;
console.log(typeof a, a)//number 10
a = a.toString() // 创建了字符串类型的"10",并把其赋给a。注意要将它赋给a才会使变量a的值改变
console.log(typeof a, a)//string 10
  1. 调用String()函数将其他类型转换为字符串
let b=null;
console.log(typeof b,b)//object null
b=String(b);
console.log(typeof b,b)//string null

let c=undefined;
console.log(typeof b,b)//undefined undefined
b=String(b);
console.log(typeof b,b)//string undefined

转换成数值

  1. 使用Number()函数来将其他类型转换为数值
    转换的情况:
  • 字符串:
    • 如果字符串是一个合法的数字,则会自动转换为对应的数字
    • 如果字符串不是合法数字,则转换为NaN
    • 如果字符串是空串或纯空格的字符串,则转换为0
  • 布尔值:
    • true转换为1,false转换为0
    • null 转换为 0
    • undefined 转换为 NaN
  1. 专门用来将字符串转换为数值的两个方法
  • parseInt() —— 将一个字符串转换为一个整数
    • 解析时,会自左向右读取一个字符串,直到读取到字符串中所有的有效的整数
    • 也可以使用parseInt()来对一个数字进行取整(这时会先将数字转换成字符串,在转换为整数)
  • parseFloat() —— 将一个字符串转换为浮点数
    • 解析时,会自左向右读取一个字符串,直到读取完字符串左侧的浮点数

转换成布尔值

使用Boolean()函数来将其他类型转换为布尔值
转换的情况:

  • 数字:
    • 0 和 NaN 转换为false
    • 其余是true
  • 字符串:
    • 空串转换为 false(只含空格的字符串不是空串)
    • 其余是true(包含只含空格的字符串)
  • null和undefined 都转换为 false
  • 对象:对象会转换为true
    总之,所有表示空性的没有的错误的值都会转换为false,比如 0、NaN、空串、null、undefined、false

算术运算

image

加减乘除运算符

加减乘除运算符就是基本的数学运算符效果

10 + 10; // 20
100 - 10; // 90
10 * 2; //20
10 / 5; 2

余数运算符

余数运算符是比较常用的,因为在逻辑思维上寻找规律,余数运算符是很好用的

13 % 5 // 3

自增和自减运算符

自增和自减运算符,是一元运算符,只需要一个运算子。它们的作用是将运算子首先转为数值,然后加上1或者减去1

var x = 1;
var y = 1;
++x // 2
--y // 0

自增和自减运算符有一个需要注意的地方,就是放在变量之后,会先返回变量操作前的值,再进行自增/自减操作;放在变量之前,会先进行自增/自减操作,再返回变量操作后的值。

var x = 1;
var y = 1;

x++ // 1
++y // 2
var x = 10;
var y = 20;
console.log(x++ + y);// 30

幂运算

10 ** 4//10的4次方

不同数据类型做加减法

JS是一门弱类型语言,当进行运算时会通过自动的类型转换来完成运算
除了含字符串的加法,其他运算的操作数是非数值时,都会转换为数值然后再运算。

a = 10 - '5' // 10 - 5
a = 10 + true // 10 + 1
a = 5 + null // 5 + 0
a = 6 - undefined // 6 - NaN=NaN

当任意一个值和字符串做加法运算时,它会先将其他值转换为字符串,然后再做拼串的操作
·

a = 'hello' + 'world'//'helloworld'
a = '1' + 2 // "1" + "2"//'12'

可以利用这一特点将其他类型转换成字符串类型,通过为任意类型 + 一个空串''的形式来将其转换为字符串, 其原理和String()函数相同,但使用起来更加简洁

a = true//boolean类型,非string
a = a + ''
console.log(typeof a, a)//string true,转换成了string类型

实时效果反馈

1. 下列代码执行结果正确的是:

var x = 5;
var y = 5;
console.log(x++  +  --y);

A 9

B 10

C 11

D 12

2. 关于自增自减运算符描述正确的是 :

A 自增和自减运算符,是二元运算符,只需要二个运算子

B 自增和自减运算符,就是加法和减法运算符

C 自增和自减运算符,是一元运算符,只需要一个运算子

D 自增和自减运算符,符号在前或者在后都是一样的

答案

1=>A 2=>C

赋值运算

image
赋值运算符(Assignment Operators)用于给变量赋值

最常见的赋值运算符,当然就是等号(=)

// 将 1 赋值给变量 x
var x = 1;

// 将变量 y 的值赋值给变量 x
var x = y;

赋值运算符还可以与其他运算符结合,形成变体。下面是与算术运算符的结合

x += y// 等同于 x = x + y
x -= y// 等同于 x = x - y
x *= y// 等同于 x = x * y
x **=y// 等同于 x = x **y
x /= y// 等同于 x = x / y
x %= y// 等同于 x = x % y

空赋值??=,只有当变量的值为null或undefined时才会对变量进行赋值

a = 5
a ??= 101
console.log(a) //5
a=null
a ??= 101
console.log(a) //101

实时效果反馈

1.下列代码的运行结果是 :

var num1 = 10;
var num2 = 20;
console.log(num1 += num2);

A 10

B 20

C 30

D 40

2. 下列代码的运行结果是:

var num1 = 12;
var num2 = num1;		
console.log(++num1 + num2);

A 20

B 25

C 30

D 35

答案

1=>C 2=>B

运算符之比较运算符

比较运算符用于比较两个值的大小,然后返回一个布尔值,表示是否满足指定的条件。

2 > 1 // true

JavaScript 一共提供了8个比较运算符。

比较运算符描述
<小于运算符
>大于运算符
<=小于或等于运算符
>=大于或等于运算符
==相等运算符
===严格相等运算符
!=不相等运算符
!==严格不相等运算符
注意:当对非数值进行关系运算时,它会先将前转换为数值然后再比较。当关系运算符的两端是两个字符串,它不会将字符串转换为数值,而是逐位的比较字符的Unicode编码
利用这个特点可以对字符串按照字母排序
==
  • 相等运算符,只判断值是否相等
  • 使用相等运算符比较两个不同类型的值时,会先转换为相同的类型(通常转换为数值)然后再比较值是否相同
  • null和undefined进行相等比较时会返回true
  • NaN不和任何值相等,包括它自身===
  • 全等运算符,只有值和类型都相等时才返回true
  • 不会自动转换类型,如果两个值的类型不同直接返回false
  • null和undefined进行全等比较时会返回false!=
  • 不等,用来检查两个值是否不相等
  • 会自动的进行类型转换!==
  • 不全等,比较两个值是否不全等
  • 不会自动转换类型
x=10;
y='10'
console.log(x==y);//ture,因为值相等
console.log(x===y);//false,虽然值相等,但类型不相等
console.log(x!=y);//false,因为x,y值相等
console.log(x!==y);//true,因为x,y类型不同

高频经典面试题

”和“=”的区别

  • ==双等只比较值
  • ===三等比较值和类型

实时效果反馈

1.下列那个选项是比较运算符 :

A =

B ==

C +=

D ++

2. 下列关于""和"="运算符的描述正确的是:

A 双等比较值,三等也是比较值

B 双等比较值和类型,三等比较值

C 双等比较值和类型,三等比较值和类型

D 双等比较值,三等比较值和类型

答案

1=>B 2=>D

逻辑运算符--逻辑与、或、非

取反运算符(!)

布尔值取反
!true // false
!false // true
非布尔值取反

对于非布尔值,取反运算符会将其转为布尔值。可以这样记忆,以下六个值取反后为true,其他值都为false。

温馨提示

undefined
null
false
0
NaN
空字符串('')

!undefined // true
!null // true
!0 // true
!NaN // true
!"" // true

!54 // false
!'hello' // false

且运算符(&&)

注意短路现象:对于&&, 如果第一个值为false,则不执行右侧表达式, 对于||, 如果第一个值为true,则不执行右侧表达式
多个条件都要满足

console.log(10 < 20 && 10 >5); // true

或运算符(||)

满足一个条件即可

console.log(10 < 20 || 10 < 5); // true

实时效果反馈

1.下列代码运行结果是 :

!undefined
!'hello'

A true true

B false false

C true false

D false true

2. 下列代码中运行结果是:

var num = 0;
++num;
console.log(!num);

A 0

B true

C 1

D false

答案

1=>C 2=>D

一元+、-运算符

  • + 正号
    • 不会改变数值的符号
  • -负号
    • 可以对数值进行符号位取反
      当我们对非数值类型进行正负运算时,会先将其转换为数值然后再运算

运算符的优先级

可以通过优先级的表格来查询运算符的优先级https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Operator_Precedence#table
在表格中位置越靠上的优先级越高,优先级越高越先执行,优先级一样自左向右执行
优先级我们不需要记忆,甚至表格都不需要看
因为()拥有最高的优先级,使用运算符时,如果遇到拿不准的,可以直接通过()来改变优先级即可

条件语句之 if语句

if结构先判断一个表达式的布尔值,然后根据布尔值的真伪,执行不同的语句。所谓布尔值,指的是 JavaScript 的两个特殊值,true表示真,false表示伪。

if语句语法规范

if (布尔值){
   语句; 
}

需要注意的是,“布尔值”往往由一个条件表达式产生的,必须放在圆括号中

var m = 3;
if (m === 3) {
    m++;
}
console.log(m); // 4

注意,if后面的表达式之中,不要混淆赋值表达式(=)、严格相等运算符(=)和相等运算符()。尤其是赋值表达式不具有比较作用。

var x = 1;
var y = 2;
if (x = y) {
    console.log(x);
}

实时效果反馈

1.下列条件语句中,横线处应填写代码是 :

var zhangsan = 34;
var lisi = 30;
if(zhangsan ___ lisi){
    console.log("张三更大"); // 张三更大
}

A <

B =

C ==

D >

2. 下列代码运行结果是:

var num1 = "10"
var num2 = 10;
if(num1 ___ num2){
    console.log("num1等于num2"); // 无打印
}

A >=

B =

C ==

D ===

答案

1=>D 2=>D

条件语句之 if...else

if...else基本结构

if代码块后面,还可以跟一个else代码块,表示不满足条件时,所要执行的代码。

image-20211019151237163

if (m === 3) {
  // 满足条件时,执行的语句
} else {
  // 不满足条件时,执行的语句
}

多个if...else连接

对同一个变量进行多次判断时,多个if...else语句可以连写在一起。image.png)image

if (m === 0) {
  // ...
} else if (m === 1) {
  // ...
} else if (m === 2) {
  // ...
} else {
  // ...
}

if...else嵌套

var eat = true;
var food = "猪肉炖粉条";
if (eat) {
    if (food == "双椒鱼头") {
        console.log('双椒鱼头');	
    }else{
        console.log("猪肉炖粉条");
    }
} else {
    console.log('不吃了');
}

else代码块总是与离自己最近的那个未配对的if语句配对。

实时效果反馈1. 下列代码运行结果是:

var day = 3;
if(day === 1){
    console.log("再向虎山行");
}else if(day === 2){
    console.log("路漫漫");
}else if(day === 3){
    console.log("野茫茫");
}else if(day === 4){
    console.log("曙光在前头");
}else if(day === 5){
    console.log("归心似箭");
}else{
    console.log("胜利大逃亡");
}

A 再向虎山行

B 野茫茫

C 曙光在前头

D 胜利大逃亡

答案

1=>D 2=>B

条件语句之 switch

多个if...else连在一起使用的时候,可以转为使用更方便的switch结构image

switch (fruit) {
  case "banana":
    // ...
    break;
  case "apple":
    // ...
    break;
  default:
    // ...
}

需要注意的是,每个case代码块内部的break语句不能少,否则会接下去执行下一个case代码块,而不是跳出switch结构。

var x = 1;

switch (x) {
  case 1:
    console.log('x 等于1');
  case 2:
    console.log('x 等于2');
  default:
    console.log('x 等于其他值');
}
// x等于1
// x等于2
// x等于其他值

实时效果反馈

1.下列swtich语句中,横线处应填写代码是 :

var x = 4;
switch (x) {
    case 1:
        console.log('x 等于1');
        break
    case 2:
        console.log('x 等于2');
        ____
    default:
        console.log('x 等于其他值'); // 打印x等于其他值
}

A swtich

B case

C break

D default

2. 下列代码运行结果是:

var x = 2;
switch (x) {
    case 1:
        console.log('x 等于1');
    case 2:
        console.log('x 等于2');
    default:
        console.log('x 等于其他值');
}

A x 等于1 x 等于2 x 等于其他值

B x 等于1 x 等于2

C x 等于2 x 等于其他值

D x 等于1 x 等于其他值

答案

1=>C 2=>C

三元运算符

image

JavaScript还有一个三元运算符(即该运算符需要三个运算子)?:,也可以用于逻辑判断。

(条件) ? 表达式1 : 表达式2
image-20211020125704664

这个三元运算符可以被视为if...else...的简写形式,因此可以用于多种场合。

判断一个整数是奇数与偶数

if...else语句
var n = 100;
if (n % 2 === 0) {
  console.log("偶数");
} else {
  console.log("奇数");
}
三元运算符
var n = 100;
n % 2 === 0 ? '偶数' : '奇数'

实时效果反馈

1.以下代码是三元运算符,横线处应该填写 :

n % 2 === 0 ___ '偶数' ___ '奇数'

A && ||

B ? :

C >= <=

D == ===

2. 下列关于三元运算符表达式描述正确的是:

A if(条件){}else{}

B switch(条件){}

C if(条件){}

D (条件) ? 表达式1 : 表达式2

答案

1=>B 2=>D

循环语句之for

循环语句用于重复执行某个操作

for语句就是循环命令,可以指定循环的起点、终点和终止条件。它的格式如下

for (初始化表达式; 条件; 迭代因子) {
  语句
}

for语句后面的括号里面,有三个表达式。

  1. 初始化表达式(initialize):确定循环变量的初始值,只在循环开始时执行一次。

  2. 布尔表达式(test):每轮循环开始时,都要执行这个条件表达式,只有值为真,才继续进行循环。

  3. 迭代因子(increment):每轮循环的最后一个操作,通常用来递增循环变量。

    image-20211020125018230
var x = 3;
for (var i = 0; i < x; i++) {
  console.log(i);
}

for语句的三个表达式,可以省略任何一个,也可以全部省略。

for ( ; ; ){
  console.log('Hello World');
}

温馨提示

如果三个都省略,结果就导致了一个无限循环(死循环)

实时效果反馈

1.以下代码是一个循环语句,横线处应该填写 :

var x = 3;
for (var i = 0; i __ x; i__) {
  console.log(i);  // 输出:0,1,2
}

A == ++

B < ++

C > ++

D < ==

2. 下列关于循环语句描述错误的是:

A 初始化表达式:确定循环变量的初始值,只在循环开始时执行一次。

B 布尔表达式:每轮循环开始时,都要执行条件表达式,只有值为真,才继续进行循环

C 迭代因子:每轮循环的最后一个操作,通常用来递增/递减循环变量

D 三个表达式全部省略,不会出现无限循环(死循环)

答案

1=>B 2=>D

for循环语句实操

循环输出1~100之间数字的和

var sum=0;
for(var i=1;i<=100;i++){
    sum+=i;
}
console.log(sum);

循环输出1000以内的奇数

for(i = 0 ; i<1000; i ++){
    if( i % 2 ==1){
        console.log( i + " ");   
    }
}

打印九九乘法表

image

for (var i = 1; i <= 9; i++) {
            for (var j = 1; j <= i; j++) {
                document.write(j + "X" + i + "=" + i * j + " ");
            }
            document.write("<br/>");

}

运行结果如下image

循环语句之while

While语句包括一个循环条件和一段代码块,只要条件为真,就不断循环执行代码块。

while (条件) {
  语句;
}

image

while例子

var i = 0;
while (i < 100) {
  console.log('i 当前为:' + i);
  i = i + 1;
}

下面的例子是一个无限循环,因为循环条件总是为真

while (true) {
  console.log('Hello, world');
}

所有for循环,都可以改写成while循环

// for
var x = 3;
for (var i = 0; i < x; i++) {
  console.log(i);
}

// while
var x = 3;
var i = 0;
while (i < x) {
  console.log(i);
  i++;
}

实时效果反馈

1.以下代码是一个循环语句,横线处应该填写 :

var i = 0;
while (i ___ 100) {
  console.log('i 当前为:' + i);  // 结束输出到100
  i = i + 1;
}

A <

B <=

C >

D >=

2. 下列代码,运行结果是 :

while (true) {
  console.log('Hello, world');
}

A 输出一次"Hello, world"

B 没有任何输出

C 无限次输出"Hello, world"(死循环)

D 输出两次"Hello, world"

答案

1=>B 2=>C

break 语句和 continue 语句

image
break语句和continue语句都具有跳转作用,可以让代码不按既有的顺序执行

break

break语句用于跳出代码块或循环,注意不是跳出判断语句块。跳转到结束花括号的右边

for (var i = 0; i < 5; i++) {
    if (i === 3){
        break;
    }
    console.log(i);
}/*跳到这*/

continue

continue语句用于立即终止本轮循环,返回循环结构的头部,开始下一轮循环。跳转到结束花括号的左边

for (var i = 0; i < 5; i++) {
    if (i === 3){
        continue;
    }
    console.log(i);
/*跳到这*/}

image实时效果反馈

1.break和continue的区别 :

A break和continue一样,都是跳出本次循环,进行下次循环

B break跳出本次循环,进入下次循环,continue跳出整个循环

C break跳出整个循环,continue跳出本次循环,进入下次循环

D break和continue一样,跳出全部循环

2. 以下代码,横线处应该填写的内容是:

for (var i = 0; i < 5; i++) {
    if (i === 3){
        ___;
    }
    console.log(i); // 输出:0,1,2,4
}

A i++

B i--

C break

D continue

答案

1=>C 2=>D

字符串

字符串就是零个或多个排在一起的字符,放在单引号或双引号之中。字符串下标从0开始

'智云学社'
"智云学社"

单引号字符串的内部,使用双引号。双引号字符串的内部,使用单引号

'key = "value"'
"It's a long 智云学社"

如果要在单引号字符串的内部,使用单引号,就必须在内部的单引号前面加上反斜杠,用来转义。双引号字符串内部使用双引号,也是如此

'Did she say \'Hello\'?'
// "Did she say 'Hello'?"

"Did she say \"Hello\"?"
// "Did she say "Hello"?"

温馨提示

字符串默认只能写在一行内,分成多行将会报错

coding时想要将长字符串分成多行书写(不是显示),可以在每一行的尾部使用反斜杠。但实际页面的显示效果不是换行而是空格

var longString = 'Long \
long \
string';
longString
// "Long long long string"

字符串类似元素均为字符的数组,有length属性, 并且一些数组可以使用的方法 字符串也能使用,它与数组关系密切,常常会遇到字符串与数组互相转换的需求。
字符串被赋值后就无法通过非赋值的方式改变(字符串可以被赋值多次),所以它的属性不可改变(比如length属性,但是数组的length属性可以改变),它的方法都是非破坏性方法,即不会改变原字符串

length 属性

length属性返回字符串的长度,该属性也是无法改变的

var s = 'itbaizhan';
s.length // 9

实时效果反馈

1. 下列字符串,横线处应该填写的代码是:

"Did she say ___"Hello___"?"

A "" ''

B '' ""

C \ \

D "" ""

答案

1=>C

字符串方法

字符串的方法都是非破坏性方法,有很多,有些方法数组也能用,详见MDN

charAt() at()

我们可以以str[下标]这种形式获取字符串的某个字符,这种方式不能接受负索引。也可以用charAt()或at()方法获取字符,charAt()不能接受负索引,at()可以接受负索引

let str = "hello hello how are you"
str.[0] // "h"
str.charAt(1) // "e"
str.at(-1) // "u"

如果charAt()的参数为负数,或大于等于字符串的长度,则返回空字符串。//python是可以使用负数指定字符串中的字符的,比如-1表示字符串中倒数第一个字符

'itbaizhan'.charAt(-1) // ""
'itbaizhan'.charAt(9) // ""

实时效果反馈

1. 下列字符串方法charAt的应用,代码输出结果是:

'itbaizhan'.charAt(9);

A o

B 0

C hello

D ""

答案

1=>D

concat()

其实+可以起到和concat()同样的效果,它们都有连接字符串的作用,日常更习惯使用++=

concat方法用于连接两个字符串,返回一个新字符串,不改变原字符串

var s1 = 'it';
var s2 = 'sxt';

s1.concat(s2) // "itsxt"
s1 // "it"

该方法可以接受多个参数,参数之间,隔开

'sxt'.concat('itbaizhan', 'bjsxt') // "sxtitbaizhanbjsxt"

如果参数不是字符串,concat方法会将其先转为字符串,然后再连接。运算符+也是这样, 遇到一个为字符 一个为数值的操作数时,会把数值转换为字符再连接

var one = 1;
var two = 2;
var three = '3';
''.concat(one, two, three) // "123"
''.concat(1,2,3) // "123"
console.log('1'+2) // 12

实时效果反馈

1. 下列字符串方法concat的应用,代码输出结果是:

var one = 1;
var two = 2;
var three = '3';

console.log(''.concat(one, two, three) )
console.log(one + two + three )

A 123 33

B 123 123

C 33 33

D 6 6

答案

1=>A

includes() indexOf() lastIndexOf()

以下3种方法都是检查字符串中是否含有某内容,参数均为1.要查询的内容 2.查询的起始下标(含该下标)

  1. str.includes(检查内容,起始下标)
    • 返回布尔值
  2. str.indexOf(检查内容,起始下标)
    • 从前往后查
    • 若字符串含查询内容,返回该内容起始下标,否则返回-1
  3. str.lastIndexOf(检查内容,起始下标)
    • 从后往前查
    • 若字符串含查询内容,返回该内容起始下标,否则返回-1

工作中常用imageindexOf方法用于确定一个字符串在另一个字符串中第一次出现的位置,返回结果是匹配开始的位置。如果返回-1,就表示不匹配

'hello world'.indexOf('o') // 4
'itbaizhan'.indexOf('') // 2
let str = "hello hello how are you"
str.lastIndexOf("hello") // 6,查找到的是第2个hello
str.includes("hello") // true

indexOf方法还可以接受第二个参数,表示从该位置开始向后匹配(包含该位置)

'hello world'.indexOf('o', 6) // 7

实时效果反馈

1. 下列字符串方法indexOf的应用,代码输出结果是:

'itbaizhan'.indexOf("z",6)

A -1

B itbaizhan

C 0

D 5

答案

1=>A

startsWith() endsWith()

str.startsWith(检查内容)
- 检查一个字符串是否以指定内容开头,返回布尔值
str.endsWith(检查内容)
- 检查一个字符串是否以指定内容结尾,返回布尔值

let str = "hello hello how are you"
console.log(str.endsWith("you"))// true
console.log(str.startsWith("you"))// false
padStart() padEnd()

通过添加指定的内容,使字符串保持某个长度。padStart()表示向前补齐,padEnd()表示向后补齐
str.padStart(补齐后的位数,补齐的内容)
str.padEnd(补齐后的位数,补齐的内容)

str = "100"
console.log(str.padStart(7, "0")) // "0000100"
console.log(str.padEnd(7, "0")) // "1000000"
repeat()

repeat() 是一个通用方法,也就是它的调用者可以不是一个字符串对象。str.repeat(重复次数)重复指定次数字符串,重复次数为0时返回空字符串
参数应该是有限的正整数

str = "100"
console.log(str.repeat(0)) // ""
console.log(str.repeat(2)) // "100100"
console.log(str.repeat(2.5)) // "100100" 自动将参数转换成整数
"abc".repeat(-1)     // RangeError: repeat count must be positive and less than inifinity
"abc".repeat(1/0)    // RangeError: repeat count must be positive and less than inifinity
replace() replaceAll()

使用一个新字符串替换指定内容
str.replace(指定内容,新字符串)
- 使用一个新字符串替换一个指定内容,替换 从前往后查找到的第一个 指定内容
str.replaceAll(指定内容,新字符串)
- 使用一个新字符串替换所有指定内容

let str = "hello hello how are you"
let result = str.replace("hello", "abc")// "abc hello how are you"
result = str.replaceAll("hello", "abc")// "abc abc how are you"
slice() substring()

slice(起始下标,结束下标)和substring(下标1,下标2)的作用都是从原字符串取出子字符串并返回。截取范围包括起始下标,不包括结束下标。区别在于substring不要求第一个参数下标一定小于第二个参数下标。比如
str.substring(15,12)仍可以截取长度大于等于14的字符串,而slice(15,12)不能

let str = "hello hello how are you"
str.slice(0,2) // "he"
str.substring(0, 2) // "he"
str.slice(2,0) // ""
str.substring(2, 0) // "he"

如果省略第二个参数,则表示子字符串一直到原字符串的结束

'itbaizhan'.substring(2) // "baizhan"

如果第一个参数大于第二个参数,substring方法会自动更换两个参数的位置

'itbaizhan'.substring(9, 2) // "baizhan"
// 等同于
'itbaizhan'.substring(2, 9) // "baizhan"

如果参数是负数,substring方法会自动将负数转为0

'itbaizhan'.substring(-3) // "itbaizhan"
'itbaizhan'.substring(2, -3) // "it"

实时效果反馈

1. 下列字符串方法substring的应用,代码输出结果是:

'itbaizhan'.substring(5, -3)

A ""

B itbaizhan

C itbai

D bai

答案

1=>C

split()

split(作为分隔依据的字符)方法按照给定规则分割字符串,返回一个由分割出来的子字符串组成的数组
与之互逆的是数组方法join()

'it|sxt|baizhan'.split('|') // ["it", "sxt", "baizhan"]

如果分割规则为空字符串,则返回的数组元素是原字符串的每一个字符。

'a|b|c'.split('') // ["a", "|",  "b","|", "c"]

如果省略参数,则返回数组的唯一成员就是原字符串

'it|sxt|bz'.split() // [it|sxt|bz]

split方法还可以接受第二个参数,限定返回数组的最大成员数。

'it|sxt|bz'.split('|', 0) // []
'it|sxt|bz'.split('|', 1) // ["it"]
'it|sxt|bz'.split('|', 2) // ["it", "sxt"]
'it|sxt|bz'.split('|', 3) // ["it", "sxt", "bz"]
'it|sxt|bz'.split('|', 4) // ["it", "sxt", "bz"]

实时效果反馈

1. 下列字符串操作代码,执行结果是:

"itbaizhan".split("");

A [itbaizhan]

B ['it','haizhan']

C ['i', 't', 'b', 'a', 'i', 'z', 'h', 'a', 'n']

D 'i', 't', 'b', 'a', 'i', 'z', 'h', 'a', 'n'

答案

1=>C

toLowerCase() toUpperCase()

str.toLowerCase()
- 将字符串转换为小写
str.toUpperCase()
- 将字符串转换为大写

let str = "abcdABCD"
result = str.toLowerCase() // abcdabcd
str.toUpperCase() // ABCDABCD
trim() trimStart() trimEnd()

trimEnd()trimStart()为ES6扩展方法
str.trim()
- 去除字符串前后空格
str.trimStart()
- 去除字符串开始空格
str.trimEnd()
- 去除字符串结束空格trim方法用于去除字符串两端的空格(不能去除中间的空格),返回一个新字符串,不改变原字符串

let str = "    ab  c     "
str.trim() // "ab  c"
str.trimStart() // "ab  c     "
str.trimEnd() // "    ab  c"

该方法去除的不仅是空格,还包括制表符(\t\v)、换行符(\n)和回车符(\r)

'\r\nitbaizhan \t'.trim() // 'itbaizhan'

实时效果反馈

1. 下列字符串方法trim的应用,代码输出结果是:

'\r\n itbaizhan \t  '.trim()

A \r\n itbaizhan \t

B \r\nitbaizhan\t

C \r\nitbaizhan \t

D itbaizhan

答案

1=>D

模板字符串

使用场景

  • 拼接字符串和变量
  • 在没有它之前,要拼接变量比较麻烦(因为需要比较多的“+”号)image

语法

  • “``”(反引号)
  • 内容拼接变量时,用 ${ } 包住变量image

正则表达式(RegExp)

正则表达式不是JS独有的内容,大部分语言都支持正则表达式
JS中正则表达式使用得不是那么多,我们可以尽量避免使用正则表达式
在JS中,正则表达式就是RegExp对象,RegExp 对象用于将文本与一个模式匹配
正则表达式(regular expressions, 规则表达式)

  • 正则表达式用来定义字符串匹配的规则
  • 通过这个规则计算机可以检查一个字符串是否符合规则,或者将字符串中符合规则的内容提取出来
  • 正则表达式也是JS中的一个对象,所以要使用正则表达式,需要先创建正则表达式的对象

创建正则表达式

// 通过构造函数创建正则表达式,RegExp() 可以接收两个参数(字符串) 1.正则表达式 2.匹配模式
let reg = new RegExp("a", "i")//以不区分大小写的方式匹配含"a"的字符串
//或
// 使用字面量创建正则表达式,`/正则表达式/匹配模式`
let reg = /a/i

注意:以构造函数创建正则表达式,构造函数里面的字符串转换成正则表达式字面量的过程也有一个转义的过程

let reg = new RegExp("\w")//相当于reg = /w/
let reg = new RegExp("\\w")//相当于reg = /\w/

用RegExp对象的实例方法test()检测字符串是否匹配正则表达式

let reg = new RegExp("a")
let str = "a"
let result = reg.test(str) // true
result = reg.test("b") // false
result = reg.test("abc") // true
result = reg.test("bcabc") // true

方法

  1. 正则表达式.test(字符串):判断字符串是否匹配正则表达式,返回布尔值
  2. 正则表达式.exec(字符串): 提取字符串里匹配正则表达式的部分,返回一个数组array,array[0]为匹配的字符串部分,array[1]为正则表达式的第1个分组,array[2]为正则表达式的第2个分组,以此类推

例:提取出str中符合axc格式的内容,即字符串开头时a,末尾是c,中间为任意字母

// 提取出str中符合axc格式的内容
let str = "abcaecafcacc"
// g表示全局匹配
 //"[a-z]c"是分组1,[a-z]是分组2
let re = /a(([a-z])c)/ig // JS没有规定表示匹配模式的字母的书写顺序
let result = re.exec(str) //每执行一次返回一个数组
console.log(result)
 // 最后匹配结束时会返回null给result,循环结束
 while(result){
     console.log(result[0], result[1], result[2])
     result = re.exec(str)
 }

image

语法

匹配模式
i: 不区分字母大小写
g: 全局模式匹配,如果不写这个用exec()方法提取匹配的字符串,默认是只提取匹配的第一个

正则表达式中的特殊字符
1.在正则表达式中大部分字符都可以直接写
2.| 在正则表达式中表示或,它是整体的或
3.[] 表示或(字符集)[a-z] 任意的小写字母[A-Z] 任意的大写字母[a-zA-Z] 任意的字母[0-9]任意数字
4.[^] 表示除了[^x] 除了x其他字符都可以
5. . 表示除了换行外的任意字符
6. 在正则表达式中使用\作为转义字符
7. 其他的字符集
\w 任意的单词字符(就是单词中会出现的字符) [A-Za-z0-9_]
\W 除了单词字符 [^A-Za-z0-9_]
\d 任意数字 [0-9]
\D 除了数字 [^0-9]
\s 空格
\S 除了空格
\b 单词边界
\B 除了单词边界
8. 开头和结尾^ 表示字符串的开头$ 表示字符串的结尾/^字符串$/表示完全匹配该字符串
9. 量词
{m} m个
{m,} 至少m个
{m,n} m-n个
+ 一个及以上,相当于{1,}
* 0个及以上, 即任意数量的a
? 0-1个,即有或没有,相当于{0,1}

let re = /abc|bcd/ // "|"是整体的或, 表示abc或bcd
re = /[ab]/ // 表示a或b
re = /[A-Za-z][A-Z]/  //只是第1处忽略大小写
re = /[a-z]/i // 匹配模式i表示忽略大小写,这样写就是整个正则表达式忽略大小写
re = /[^a-z]/ // 匹配除小写字母以外内容的字符串
console.log(re.test(aH))// ture, 因为"aH"包含除小写字母以外的字符
re = /./
console.log(re.test("\n")) // false
console.log(re.test("\r")) //false
re = /\./ //匹配含"."的字符串
re = /^a/ // 开始位置是a
console.log(re.test("a")) //true
console.log(re.test("ab")) //true
console.log(re.test("ba")) //false
re = /a$/ // 结束位置是a
console.log(re.test("a")) //true
console.log(re.test("ab")) //false
console.log(re.test("ba")) //true
re = /^a$/ // 只匹配字母a,完全匹配,要求字符串必须和正则表达式完全一致
console.log(re.test("aa")) //false
console.log(re.test("a")) //true
re = /^abc$/
console.log(re.test("abcabc")) //false
console.log(re.test("abc")) //true

let re = /ab{3}/ // 相当于/abbb/
re = /(ab){3}/ // 相当于/ababab/, 如果需要整体重复,就要在这个整体上加括号`()`
re = /^a{3}$/ // 相当于/^aaa$/
re = /^(ab){3}$/  // 相当于/^ababab$/
re = /^[a-z]{3}$/  //完全匹配3个字母
re = /^[a-z]{1,}$/ // 完全匹配1个及以上的字母
re = /^[a-z]{1,3}$/ // 完全匹配1-3个字母

re = /^ba+$/
re.test("b") //false
re.test("ba") //true
re.test("baa") //true

re = /^ba*$/
re.test("b") //true
re.test("ba") //true
re.test("baa") //true
            
re = /^ba?$/
re.test("b") //true
re.test("ba") //true
re.test("baa") //false

练习

  1. 从字符串中找出手机号码,并且手机号中间4位用*代替
let str="dajsdh13715678903jasdlakdkjg13457890657djashdjka13811678908sdadadasd";
//首先确定号码的格式为:1    3到9之间   任意数字 x 9,然后用正则表达式表达出来
let reg = /(1[3-9]\d)\d{4}(\d{4})/g; // 注意别忘了写匹配模式"g"(全局匹配),不写的话会陷入死循环
let r=reg.exec(str);
while(r){
    console.log(r[1]+"****"+r[2])
    r=reg.exec(str)
}
  1. 判断字符串是否是手机号
    这种情况只需完全匹配
let re = /^1[3-9]\d{9}$/
console.log(re.test("13456789042"))

与正则表达式关系密切的字符串方法

split()

split()可以根据正则表达式来对一个字符串进行拆分
没有正则表达式作为split()的参数前,只能根据固定的字符(串)分割字符串

let str = "a@b@c@d"
let result = str.split("@") // ['a','b','c','d']

有了正则表达式后,可以根据符合一定规律的字符(串)分割字符串

str = "孙悟空abc猪八戒adc沙和尚"
result = str.split(/a[bd]c/) // ["孙悟空","猪八戒","沙和尚"]

search()的作用类似于indexOf(), 区别在于search()支持正则表达式作为参数,它可以去搜索符合正则表达式的内容第一次在字符串中出现的位置。返回值是第一次出现的索引,没有搜索到则返回-1

str ="dajsdh13715678903jasdlakdkjg13457890657djashdjka13811678908sdadadasd"
result = str.search(/1[3-9]\d{9}/)
console.log(result) // 6
replace() replaceAll()

replace()和replaceAll()都是根据正则表达式替换字符串中的指定内容

str ="dajsdh13715678903jasdlakdkjg13457890657djashdjka13811678908sdadadasd"
// 如果是全局匹配replace()与replaceAll()的功能就基本一致了,replace()此时会替换所有匹配的字符
result = str.replace(/1[3-9]\d{9}/g, "哈哈哈")
result = str.replaceAll(/1[3-9]\d{9}/g, "哈哈哈") // 注意replaceAll()仍要求字符串使用全局匹配模式g

以回调函数的形式替换匹配的字符串,这样就可以实现用各自相应的字符替换不同的字符

// 定义转义 HTML 字符的函数
function htmlEscape(htmlStr){
    // 别忘了将htmlStr.replace()的返回值返回给htmlEscape()
    return htmlStr.replace(/<|"|>|&/g,(match)=>{
        switch(match){
            case '<':
                return '&lt;'
            case '"':
                return '&quot;'
            case '>':
                return '&gt;'
            case '&':
                return '&amp;'
        }
    })
}
console.log(htmlEscape("<h1 title="abc">这是h1标签<span>123&nbsp;</span></h1>"))

image

match() matchAll()

match()
- 根据正则表达式去匹配字符串中符合要求的内容
- 与RegExp对象的方法exec()相似, exec()由RegExp实例调用,match()由String实例调用
- match()返回一个数组(与exec()返回的数组不一样),match()返回的数组元素是所有匹配的字符串
- 所以match()的功能相较于exec()的功能更简单,它不能看到正则表达式的匹配分组
matchAll()
- 作用同match(), 该方法仍要求正则表达式为全局匹配g
- 它返回的是一个迭代器(迭代器需要遍历才能看到其中的内容, 遍历出的内容能看到正则表达式的匹配分组)

result = str.match(/1[3-9]\d{9}/g)
console.log(result);
//注意不要忘了g
result = str.matchAll(/1[3-9](\d{9})/g)
console.log(result);
// 迭代器需要遍历才能看到其中的内容
for(let item of result){
    console.log(item)
}

image

数组

数组(Array)

  • 数组也是一种复合数据类型,在数组可以存储多个不同类型的数据
  • 数组中存储的是有序的数据,数组中的每个数据都有一个唯一的索引,可以通过索引来操作获取数据
  • 数组中存储的数据叫做元素
  • 索引(index)是一组从0开始的整数
  • 创建数组: 可以通过arr=new Array()来创建数组,也可以通过arr=[]来创建数组
  • 向数组中添加元素: 数组[索引] = 元素
  • 读取数组中的元素: 数组[索引], 如果读取了一个不存在的元素,不会报错而是返回undefined
  • 用typeof运算符返回数组的类型是Object
  • 超出数组长度范围时返回undefined
  • length
    • 获取的实际值就是数组的最大索引 + 1
    • 向数组最后添加元素: 数组[数组.length] = 元素
    • length是可以修改的
const obj = { name: "孙悟空", age: 18 }

            const arr = new Array()
            const arr2 = [1, 2, 3, 4, 5] // 数组字面量

            arr[0] = 10
            arr[1] = 22
            arr[2] = 44
            arr[3] = 88
            arr[4] = 99

            // 使用数组时,应该避免非连续数组,因为它性能不好
            // arr[100] = 99

            // console.log(arr[101])//undefined

            // console.log(typeof arr) // object

            // console.log(arr.length)

            arr[arr.length] = 33
            arr[arr.length] = 55

            arr.length = 5

            console.log(arr)

任何类型的数据,都可以放入数组

var arr = [ 100, [1, 2, 3],false ];

如果数组的元素还是数组,就形成了多维数组

var a = [[1, 2], [3, 4]];
a[0][1] // 2
a[1][1] // 4

实时效果反馈

1. 下列关于数组的操作,输出结果是:

var arr = [ 'sxt', 'baizhan', 'it' ];
console.log(arr[5])

A 0

B []

C undefined

D ""

答案

1=>C

数组的解构赋值

解构赋值:顾名思义就是将其拆开分别赋值

let a,b,c
// 传统的将arr值分别赋给变量的方法
    // a = arr[0]
    // b = arr[1]
    // c = arr[2]
;[a, b, c] = arr // 解构赋值,句首加分号是为了与前面的语句隔离开,防止JS解析器默认加分号时加错位置
let [d, e, f, g] = ["唐僧", "白骨精", "蜘蛛精", "玉兔精"] // 声明同时解构
console.log(d, e, f, g)
//g会被赋值为undefined
;[d, e, f, g] = [1, 2, 3]//此时d=1, e=2, f=3, g=undefined
// 若f,g没有被赋值,则默认f = 77, g = 10
;[d, e, f = 77, g = 10] = [1, 2, 3]//此时d=1, e=2, f=3, g=10
// 若f,g没有被赋值,则默认f = 77, g为原来的值
;[d, e, f = 77, g = g] = [1, 2, 3]//此时d=1, e=2, f=3, g=10

image

// 解构数组时,可以使用...来设置获取多余的元素。这里剩下的值赋值给n3
let [n1, n2, ...n3] = [4, 5, 6, 7]
console.log(n1, n2, n3)
function fn(){
    return ["二郎神", "猪八戒"]
}
// 数组作为函数的返回值时也可以解构赋值
let [name1, name2] = fn()
console.log(name1, name2)
// 可以通过解构赋值来快速交换两个变量的值
let a1 = 10
let a2 = 20
//以前交换2个变量的操作,需要借助另外一个变量
// let temp = a1
// a1 = a2
// a2 = temp
console.log(a1, a2)
//等号右侧是字面量[20, 10],等号左侧是变量
;[a1, a2] = [a2, a1] 
console.log(a1, a2)
const arr2 = ["孙悟空", "猪八戒"]
console.log(arr2)
;[arr2[0], arr2[1]] = [arr2[1], arr2[0]]
console.log(arr2)

//二维数组的解构赋值
const arr3 = [["孙悟空", 18, "男"], ["猪八戒" ,28, "男"]]
let [[name, age, gender], obj] = arr3
console.log(name, age, gender, obj)

image

浅拷贝与深拷贝

未拷贝的存储情况image

浅拷贝(shallow copy)

  • 通常对对象的拷贝都是浅拷贝
  • 浅拷贝只复制一层
  • 如果对象中存储的数据是原始值,那么无拷贝深浅之分
  • 浅拷贝只会对对象本身进行复制,不会复制对象中的属性(或元素),这样可以节约存储,提高性能
    浅拷贝的存储情况image
const arr = [{name:"孙悟空"}, {name:"猪八戒"}]
        const arr2 = arr.slice() // 浅拷贝
        console.log(arr===arr2)//false
        console.log(arr[0]===arr2[0])//ture
        arr[0].name='白骨精'
        console.log(arr)
        console.log(arr2)//虽然数组不同,但指向的数组元素相同,所以arr2也被修改

image
利用...展开运算符也能实现浅复制

const arr = ["孙悟空", "猪八戒", "沙和尚"]
arr2=[...arr]//等效于arr2 = [arr[0], arr[1], arr[2]]

... (展开运算符)

  • 可以将一个数组中的元素展开到另一个数组中或者作为函数的参数传递
  • 通过它也可以对数组进行浅复制
    对于第1点的说明如以下代码所示
function sum(a, b, c) {
                return a + b + c
            }
            const arr4 = [10, 20, 30]
            let result
            result = sum(arr4[0], arr4[1], arr4[2])//这样传递参数更麻烦
            result = sum(...arr4)//这样传递参数更方便

深拷贝(deep copy)

  • 深拷贝指不仅复制对象本身,还复制对象中的属性和元素
  • 因为性能问题,通常情况不太使用深拷贝
    深拷贝存储情况image
const arr = [{name:"孙悟空"}, {name:"猪八戒"}]
        const arr3 = structuredClone(arr) // 专门用来深拷贝的方法
        console.log(arr===arr3)//false
        console.log(arr[0]===arr3[0])//false
        arr[0].name='白骨精'
        console.log(arr)//[{name:"白骨精"}, {name:"猪八戒"}]
        console.log(arr3)//[{name:"孙悟空"}, {name:"猪八戒"}]

image

数组的复制

对象的复制一定是产生了新的对象

const arr = ["孙悟空", "猪八戒", "沙和尚"]
        const arr2 = arr  // 不是复制
        console.log(arr)
        console.log(arr2)
        arr2[0] = "唐僧"//2个数组都会被修改,说明没有复制
        console.log(arr)
        console.log(arr2)
        console.log(arr === arr2)//ture,2个数组严格相等,说明没有复制

arr与arr2指向同一个地址,没有新建独立的对象,所以不是复制image
运行结果image

const arr3 = arr.slice()// 当调用slice时,会产生一个新的数组对象,从而完成对数组的复制
        console.log(arr3)
        arr3[0] = "唐僧"
        console.log(arr)//未被修改
        console.log(arr3)

image
运行结果image

对象的复制

对象的复制

  • Object.assign(目标对象, 被复制的对象),这个也是浅复制
  • 将被复制对象中的属性复制到目标对象里,返回值为复制后的目标对象
const obj = { name: "孙悟空", age: 18 }
            const obj2 = { address: "花果山", age: 28 }
            //obj2 = Object.assign({}, obj)与Object.assign(obj2, obj)等效
            Object.assign(obj2, obj)//目标属性相对于被复制对象多出来的属性会被保留,同名属性被覆盖,此处即adress保留,age值更新
            console.log(obj2)
            const obj3 = { address: "高老庄", ...obj, age: 48 } //后面的同名属性age会覆盖前面的age属性
            console.log(obj3)
            obj3 = { address: "高老庄", age: 48,...obj}

image

使用JSON进行深复制

const obj = {
    name: "孙悟空",
    friend: {
        name: "猪八戒",
    },
}
// 对obj进行浅复制
const obj2 = Object.assign({}, obj)
// 对obj进行深复制
const obj3 = structuredClone(obj)
// 利用JSON来完成深复制
const str = JSON.stringify(obj)
const obj4 = JSON.parse(str)
// 可以简写成一句话const obj5 = JSON.parse(JSON.stringify(obj))

image

垃圾回收

垃圾回收(Garbage collection)

  • 和生活一样,生活时间长了以后会产生生活垃圾,程序运行一段时间后也会产生垃圾
  • 在程序的世界中,什么是垃圾?
    • 如果一个对象没有任何的变量对其进行引用,那么这个对象就是一个垃圾
    • 垃圾对象的存在,会严重的影响程序的性能
    • 在JS中有自动的垃圾回收机制,垃圾对象会被解释器自动回收,我们无需手动处理
    • 我们能做的是将不再使用的变量设置为null,这样能让不再使用的对象成为垃圾给JS清理
let obj = {name:"孙悟空"} // 让obj指向一个对象,该对象此时不是垃圾
let obj2 = obj // 让obj2也指向该对象
obj = null //obj此时不指向该对象,但该对象不是垃圾,因为obj2这时还在指向该对象
obj2 = null //最后唯一指向该对象的obj2此时不再指向该对象,该对象成为垃圾,被JS回收

以上语句执行的过程图如下
1image
2image
3image
4image

函数

image
函数是一段可以反复调用的代码块

函数的声明

function 命令: function命令声明的代码区块,就是一个函数。function命令后面是函数名,函数名后面是一对圆括号,里面是传入函数的参数。函数体放在大括号里面。
函数的定义方式:

  1. 函数声明
    function 函数名(){
    语句...
    }
  2. 函数表达式
    const/var/let 函数名 = function(){
    语句...
    }
  3. 箭头函数
    const/var/let 函数名=() => {
    语句...
    }
function fn(){
            console.log("函数声明所定义的函数~")
        }

        const fn2 = function(){
            console.log("函数表达式")
        }

        const fn3 = () => {
            console.log("箭头函数")
        }

        const fn4 = () => console.log("箭头函数")//箭头函数体中只有一个参数时可以省略括号
		const fn5 = x =>console.log(x);//箭头函数只有一个参数时可以省略括号

函数名的提升

JavaScript 引擎将函数名视同变量名,所以采用function命令声明函数时,整个函数会像变量声明一样,被提升到代码头部

add();

function add() {}

函数参数

  • 参数:
  1. 如果实参和形参数量相同,则对应的实参赋值给对应的形参
  2. 如果实参多于形参,则多余的实参不会使用
  3. 如果形参多于实参,则多余的形参为undefined
  • 参数的类型
    • JS中不会检查参数的类型,可以传递任何类型的值作为参数
function square(x) {
	console.log(x * x);
}

square(2) // 4
square(3) // 9

箭头函数的参数

  • 当箭头函数中只有一个参数时,可以省略()
  • 定义参数时,可以为参数指定默认值
  • 默认值,会在没有对应实参时生效
const fn2 = x => {
	console.log("x =", x);
}
fn2(0)
const fn3 = (a=10, b=20, c=30) => {
	console.log("a =", a);
	console.log("b =", b);
	console.log("c =", c);
}
fn3(1, 2)

image

函数作为参数

function f1(a){
            a()
        }
        function f2(a){
            console.log("a =", a)
        }

        function fn1(){
            console.log("我是函数")
        }
        
        f1(fn1)
        f1(function(){
            console.log("我是匿名函数~")
        })
        f1(()=>console.log("我是箭头函数"))

        f2(fn1)
        f2(function(){
            console.log("我是匿名函数~")
        })
        f2(()=>console.log("我是箭头函数"))

image

arguments对象
  • arguments用来存储函数的实参,无论用户是否定义形参,实参都会存储到arguments对象中,可以通过该对象直接访问实参
  • arguments是函数中又一个隐含参数
function fn(){
	console.log(arguments)
}
fn()

imageimage

  • arguments是一个类数组对象, “类数组”意味着有 长度 属性, 并且属性的索引是从零开始的,因为它不是数组(只是类数组对象),所以它没有Array的内置方法,例如 forEach() 和 map()都是没有的,和数组相似,可以通过索引来读取元素,也可以通过for循环变量,但是它不是一个数组对象
function(){
	//arguments不是数组对象
	console.log(Array.isArray(arguments))
	//可以用for in, for of遍历arguments这个类数组对象
	for (let i = 0; i < arguments.length; i++) {
	    console.log(arguments[i])
	}
	for (let v in arguments){
	    console.log(arguments[i])
	}
	for(let v of arguments){
	    console.log(v)
	}
	arguments.forEach((ele) => console.log(ele))
	console.log(arguments)
}

fn(1, 10, 33)

image

  • 通过arguments,可以不受参数数量的限制(即调用时可以传入任意数量的参数)从而更加灵活地创建函数
    例如实现一个求和函数
function sum() {
    let result = 0
    for (let num of arguments) {
        result += num
    }
    return result
}

这样可以实现传入任意数量的参数求和,而不是固定数量的参数求和image

不足之处:arguments只是类数组对象,不能调用Array方法,不能配合其他参数使用,而且在调用不是我们自己定义的函数时我们不知道该函数是否是不需要传参的函数

可变参数

为了弥补arguments的不足之处,现在更常用的是可变参数
可变参数,在定义函数时可以将参数指定为可变参数
格式:function(...args){函数体}

  • 可变参数可以接收任意数量实参,并将他们统一存储到一个数组中返回
  • 可变参数的作用和arguments基本是一致,但是也具有一些不同点:
    1. 可变参数的名字可以自己指定
    2. 可变参数就是一个数组,可以直接使用数组的方法
    3. 可变参数可以配合其他参数一起使用
    4. 当可变参数和普通参数一起使用时,需要将可变参数写到最后
function fn2(...abc) {
    console.log(abc)
}
//可变参数可以直接使用Array方法,这里就更简洁地实现了不定数量的参数求和
function sum2(...num) {
    return num.reduce((a, b) => a + b
}
// 可变参数和普通参数一起使用时
function fn3(a, b, ...args) {
    // for (let v of arguments) {
    //     console.log(v)
    // }
    console.log(args)
}
fn3(123, 456, "hello", true, "1111")

image

函数的作用域

函数的作用域,在函数创建时就已经确定了(词法作用域),和调用的位置无关

let a = "全局变量a"
        function fn(){
            console.log(a)//函数的作用域在函数创建时就已经确定了,所以这里的变量a是指全局变量a
        }
        function fn2(){
            let a = "fn2中的a"
            fn()
        }
        fn2()

        function fn3(){
            let a = "fn3中的a"
            function fn4(){
                console.log(a)
            }
            return fn4
        }
        let fn4 = fn3()
        fn4()

image

函数返回值

JavaScript函数提供两个接口实现与外界的交互,其中参数作为入口,接收外界信息;返回值作为出口,把运算结果反馈给外界。
关键字return后表达式的值为函数的返回值,函数体中return后面的代码不会执行

  • 任何值都可以作为返回值使用(包括对象和函数之类)
  • 如果return后不跟任何值,则相当于返回undefined
  • 如果不写return,那么函数的返回值依然是undefined
  • return一执行函数立即结束
function getName(name){
    return name;
}

var myName = getName("itbaizhan")
console.log(myName); // itbaizhan
箭头函数的返回值
  • 当函数体只有一个return语句时,箭头函数的返回值可以直接写在箭头后
    const sum = (a, b) => a + b//函数sum()的返回值为a+b
  • 如果直接在箭头后设置对象字面量为返回值时,对象字面量必须使用()括起来
    const fn = () => ({name:"孙悟空"});实时效果反馈

1. 下列函数执行输出“itbaizhan”,横线处应该填写的代码是:

function getName(name) {
    ___;
}
var myName = getName("itbaizhan")
console.log(myName); // itbaizhan

A return name

B return

C name

D return "itbaizhan"

答案

1=>A

立即执行函数

在开发中应该尽量减少直接在全局作用域中编写代码
变量尽量编写在局部作用域中
用let声明的变量,可以使用{}来创建块作用域
用var声明的变量,可以使用函数来创建函数作用域,但是因为var声明的变量创建函数作用域对于只调用一次并且不在乎名字的函数来说书写有些麻烦,所以我们需要只调用一次的匿名函数。这时立即执行函数的表达式是个合适的选择
格式如下

(function{
//代码块
})();//这里需记得加分号,因为此处JS解释器自动添加分号的话容易加错分号
//或
(function{
//代码块
}());

回调函数与高阶函数

如果一个函数是另一个函数的参数,那么这个作为参数的函数就是回调函数,这里cb()就是回调函数
回调函数是系统自动调用的,一般是执行完某项操作或检测到某项变化时系统自动调用
高阶函数

  • 如果一个函数的参数或返回值是函数,则这个函数就称为高阶函数
  • 为什么要将函数作为参数传递?(回调函数有什么作用?)
    • 将函数作为参数,意味着可以对另一个函数动态地传递代码,这样做可以让一个函数实现多种功能,提高了函数的灵活性
class Person {
                constructor(name, age) {
                    this.name = name
                    this.age = age
                }
            }

            const personArr = [
                new Person("孙悟空", 18),
                new Person("沙和尚", 38),
                new Person("红孩儿", 8),
                new Person("白骨精", 16),
            ]
            
            function filter(arr, cb) {
                const newArr = []

                for (let i = 0; i < arr.length; i++) {
                    if (cb(arr[i])) {
                        newArr.push(arr[i])
                    }
                }

                return newArr
            }
            // 我们这种定义回调函数的形式比较少见,通常回调函数都是匿名函数,匿名函数就是没有名字的函数
            // function fn(a) {
            //     return a.name === "孙悟空"
            // }
            result = filter(personArr, a => a.name === "孙悟空")//a是指该匿名函数的参数
            result = filter(personArr, a => a.age >= 18)//筛选大于18岁的元素

            const arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
            result = filter(arr, a => a % 2 === 0)//筛选偶数

            console.log(result)

返回值有函数的高阶函数

function someFn() {
                return "hello"
            }

            function outer(cb){//这个函数在someFn()的基础上增加了记录日志的功能
                return () => {
                    console.log("记录日志~~~~~")
                    //以下2行就可以把函数cb()的返回值传递给outer()
                    const result = cb()
                    return result
                }
            }

            let result = outer(someFn)

            // console.log(result)


            function test(){
                console.log("test~~~~")
                return "test"
            }

            let newTest = outer(test)

            newTest()

image
橙色输出表示函数的返回值

闭包

问题:如何创建一个函数,使得第一次调用时打印1,第二次调用打印2,以此类推。
答:可以利用函数的闭包
闭包:闭包是一个函数,闭包就是能访问到函数外部作用域中变量的函数
什么时候使用:当我们需要隐藏一些不希望被外部访问的内容时就可以使用闭包来把变量藏到外层函数里
构成闭包的要件:

  1. 函数的嵌套
  2. 内部函数要引用外部函数中的变量
  3. 内部函数要作为返回值返回image
function outer(){
                let num = 0 // 位于函数作用域中

                return () => {
                    num++
                    console.log(num)
                }
            }
            const newFn = outer()
            console.log(newFn)

image

闭包的生命周期:

  1. 闭包在外部函数调用时产生,外部函数每次调用都会产生一个全新独立的闭包
  2. 在内部函数丢失时销毁(内部函数被垃圾回收了,闭包才会消失)
function outer2(){
                let num = 0
                return () => {
                    num++
                    console.log(num)
                }
            }

            let fn1 = outer2() // 外部函数被调用,创建独立闭包
            let fn2 = outer2() // 外部函数被调用,创建独立闭包
            fn1 = null//闭包销毁
            fn2 = null//闭包销毁

image
注意事项:闭包主要用来隐藏一些不希望被外部访问的内容,因为每次调用外部函数时都会产生一个全新独立的闭包,这就意味着闭包需要占用一定的内存空间
相较于类来说,闭包比较浪费内存空间(因为类可以使用原型存储共用的属性和方法,而闭包不能)

  • 需要执行次数较少时,使用闭包
  • 需要大量创建实例时,使用类
    由于js中类的this不太稳定,遇到以上情况更多的是使用闭包

代码调试debug

image

  • 可以用debugger关键字设置断点
  • 可以通过浏览器选项检查->源代码里查看代码运行过程。在右侧有监测变量、查看作用域等功能。可以点击行号左侧设置断点
  • 页面代码未执行完时标题栏显示表示加载中的转圈

严格模式

JS运行代码的模式有两种:

  • 正常模式:默认情况下代码都运行在正常模式中,在正常模式,语法检查并不严格。它的原则是能不报错的地方尽量不报错。这种处理方式导致代码的运行性能较差
  • 严格模式
    • 在严格模式下,语法检查变得严格
      1.禁止一些语法
      2.更容易报错
      3.提升了性能
      在开发中,应该尽量使用严格模式,这样可以将一些隐藏的问题消灭在萌芽阶段,同时也能提升代码的运行性能
      "use strict"开启严格模式
"use strict" // 全局的严格模式,写在js代码的最前面
let a = 10
console.log(a)
function fn(){
	"use strict" //如果只想在特定区域使用严格模式,可以在该代码块的开头添加`"use strict"`,比如函数的严格的模式在左花括号后添加严格模式的关键字
	//代码块
}

DOM(Document Object Model)

image
DOM的主要内容:

  1. 元素节点的获取、增、删、改
  2. 属性节点的获取、增、删、改
  3. 文本节点的获取、增、删、改
  4. CSS样式的获取、增、删、改
  5. 添加事件响应函数

DOM 是 JavaScript 操作网页的接口,全称为“文档对象模型”(Document Object Model)。它的作用是将网页转为一个 JavaScript 对象,从而可以用脚本进行各种操作(比如对元素增删内容)
浏览器会根据 DOM 模型,将结构化文档HTML解析成一系列的节点,再由这些节点组成一个树状结构(DOM Tree)。所有的节点和最终的树状结构,都有规范的对外接口
DOM 只是一个接口规范,可以用各种语言实现。所以严格地说,DOM 不是 JavaScript 语法的一部分,但是 DOM 操作是 JavaScript 最常见的任务,离开了 DOM,JavaScript 就无法控制网页。另一方面,JavaScript 也是最常用于 DOM 操作的语言

  • Document:网页
  • Object:网页上的任何节点(Node)都可以是对象
  • Model: 表示网页上各个节点之间的关系

节点

DOM 的最小组成单位叫做节点(node)。文档的树形结构(DOM 树),就是由各种不同类型的节点组成。image-20211027131821279

节点的类型有七种

Document:整个文档树的顶层节点
DocumentType:doctype标签
Element:网页的各种HTML标签
Attribute:网页元素的属性(比如class="right")
Text:标签之间或标签包含的文本
Comment:注释
DocumentFragment:文档的片段

节点树

一个文档的所有节点,按照所在的层级,可以抽象成一种树状结构。这种树状结构就是 DOM 树。它有一个顶层节点,下一层都是顶层节点的子节点,然后子节点又有自己的子节点,就这样层层衍生出一个金字塔结构,倒过来就像一棵树

浏览器原生提供document节点,代表整个文档

document
// 整个文档树

除了根节点,其他节点都有三种层级关系

父节点关系(parentNode):直接的那个上级节点
子节点关系(childNodes):直接的下级节点
同级节点关系(sibling):拥有同一个父节点的节点

Node.nodeType属性

不同节点的nodeType属性值和对应的常量如下

文档节点(document):9,对应常量Node.DOCUMENT_NODE
元素节点(element):1,对应常量Node.ELEMENT_NODE
属性节点(attr):2,对应常量Node.ATTRIBUTE_NODE
文本节点(text):3,对应常量Node.TEXT_NODE
文档片断节点(DocumentFragment):11,对应常量Node.DOCUMENT_FRAGMENT_NODE

document.nodeType // 9
document.nodeType === Node.DOCUMENT_NODE // true

实时效果反馈

1. 下列那个不是节点类型:

A Document

B Element

C Attribute

D Array

答案

1=>D

document对象

image
document对象是一个全局变量, 可以直接使用, document代表的是整个的网页
document对象的原型链:HTMLDocument -> Document -> Node -> EventTarget -> Object.prototype -> null

  • 由继承可知,凡是在原型链上存在的对象的属性和方法都可以通过Document去调用。所以我们查阅属性方法时可以在原型链上去找
  • 部分属性:
    document.documentElement --> html根元素
    document.head --> head元素
    document.title --> title元素
    document.body --> body元素
    document.links --> 获取页面中所有的超链接(<a></a>)
    ...

document获取元素的方法

document.getElementsByTagName()
  • 根据标签名获取一组Element对象(HTMLCollection实例)
  • 返回的结果实时更新
  • document.getElementsByTagName("*") 获取页面中所有的元素
var paras = document.getElementsByTagName('p');
document.getElementsByClassName()
  • 根据元素的class属性值获取一组Element对象
  • 返回的是一个类数组对象(HTMLCollection实例)(类数组除了不能调用数组的方法,其他方面可以当做数组使用,比如可以使用length属性, 通过类数组对象[索引]提取数组元素)
  • 返回的结果实时更新, 即当网页中新添加元素时,它会实时的刷新
var elements = document.getElementsByClassName(names);

由于class是保留字,所以 JavaScript 一律使用className表示 CSS 的class

参数可以是多个class,它们之间使用空格分隔

var elements = document.getElementsByClassName('foo bar');
document.getElementsByName()
  • 根据name属性获取一组Element对象(NodeList实例)
  • 返回结果实时更新
  • 主要用于表单项元素
// 表单为 <form name="itbaizhan"></form>
var forms = document.getElementsByName('itbaizhan');
document.getElementById()

根据id获取并返回一个Element对象(因为id是独一无二的,所以只会返回一个)document.getElementById方法返回匹配指定id属性的元素节点。如果没有发现匹配的节点,则返回null

var elem = document.getElementById('para1');//因为id是唯一的,所以此处是Element
console.log(elem);//这里直接打印标签内容,而不是数组对象了

注意,该方法的参数是大小写敏感的。比如,如果某个节点的id属性是main,那么document.getElementById('Main')将返回null

document.querySelectorAll("选择器名")
  • 所有匹配该指定选择器的元素都会被选择
  • 会返回一个类数组(NodeList对象)(不会实时更新)document.querySelectorAll方法与querySelector用法类似,区别是返回一个,包含所有匹配给定选择器的节点
var elementList = document.querySelectorAll('.myclass');
document.querySelector()

选择匹配该指定选择器的第一个元素,相当于document.querySelectorAll()[0],如果没有发现匹配的节点,则返回null

var el1 = document.querySelector('.myclass');

实时效果反馈

1. 以下那个是通过ID获取元素对象:

A getElementById

B getElementsByTagName

C getElementsByClassName

D getElementsByName

2. 以下那个是通过Class获取元素对象:

A getElementById

B getElementsByTagName

C getElementsByClassName

D getElementsByName

答案

1=>A 2=>C

document方法创建元素

image

document.createElement(标签名)

根据标签名创建一个Element对象

document.createElement()方法用来生成元素节点,并返回该节点

var newDiv = document.createElement('div');
document.createTextNode()

document.createTextNode方法用来生成文本节点(Text实例),并返回该节点。它的参数是文本节点的内容

var newDiv = document.createElement('div');
var newContent = document.createTextNode('Hello');
newDiv.appendChild(newContent);//appendChild()方法可以将newContent的内容放进newDiv中
document.createAttribute()

document.createAttribute方法生成一个新的属性节点(Attr实例),并返回它

var attribute = document.createAttribute(name);
var root = document.getElementById('root');
var it = document.createAttribute('id');
it.value = 'atr';//通过.value给属性赋值,此时属性id="atr"
root.setAttributeNode(it);//将it对应的属性放到root对应的标签中

注意只有将创建的属性放到创建的标签中有setAttribute()方法,其他将创建的元素/标签放入标签,或将创建的内容放入创建的标签是用appendChild()方法

实时效果反馈

1. 下列代码是创建元素,并添加内容,横线处应该填写的内容是:

var newDiv = document.createElement('div');
var newContent = document.____('Hello');
newDiv.appendChild(newContent);

A createElement

B appendChild

C createAttribute

D createTextNode

答案

1=>D

Element对象

image
Element对象对应网页的 HTML 元素。每一个 HTML 元素,在 DOM 树上都会转化成一个Element节点对象(以下简称元素节点)

Element方法

获取元素

Element对象也有上面的document查找元素节点方法
Element实例.getElementsByTagName(标签名)
Element实例.getElementsByClassName(类名)
Element实例.getElementsByName(name属性名)
Element实例.getElementById(id属性名)
Element实例.querySelectorAll("选择器名")
Element实例.querySelector("选择器名")
区别在于用Element实例调用的方法寻找范围是该元素节点内,而document方法寻找范围是整个页面(document)

<div id="box1">
    我是box1
    <span class="s1">我是s1</span>
    <span class="s1">我是s1</span>
</div>
<span class="s1">我是s1</span>
<script>
    const box1 = document.getElementById("box1")
    const spans = box1.getElementsByTagName("span")// 只会寻找box1内的span标签,2个
    console.log(spans)
    console.log(document.getElementsByTagName("span")) // 寻找整个document页面的span标签,3个
    const spans2 = box1.getElementsByClassName("s1") // 只会寻找box1内类为s1的标签,2个
    console.log(spans2)
    console.log(document.getElementsByClassName("s1")) // 会寻找整个document页面中类为s1的标签,3个

image

元素的增删改
  1. Element实例.appendChild(Element对象)
    用于给一个节点添加子节点, 只能添加在父标签的结束标签之前,即添加后是父标签的最后一个孩子
  2. Element实例.insertAdjacentElement("添加位置",Element对象)
    可以向元素的任意位置添加元素,有两个参数:
    1. 要添加的位置

      • beforeend 标签的最后
      • afterend 在元素的后边插入元素(兄弟元素)
      • afterbegin 标签的开始
      • beforebegin 在元素的前边插入元素(兄弟元素)
    2. 要添加的元素

  3. Element实例.insertAdjacentHTML("添加位置","HTML语句")
    可以向元素的任意位置插入HTML语句
  4. 被替换的Element实例.replaceWith(Element对象)
    使用一个元素替换当前元素
  5. Element实例.remove()
    方法用来删除当前元素

示例
实现功能
点击按钮1,向ul中添加一个唐僧
点击按钮2, 创建一个蜘蛛精替换孙悟空

<body>
        <button id="btn01">按钮1</button>
        <button id="btn02">按钮2</button>

        <hr />

        <ul id="list">
            <li id="swk">孙悟空</li>
            <li id="zbj">猪八戒</li>
            <li id="shs">沙和尚</li>
        </ul>

        <script>
            // 获取ul
            const list = document.getElementById("list")

            // 获取按钮
            const btn01 = document.getElementById("btn01")
            btn01.onclick = function () {
                // // 一点击就添加<li id="ts">唐僧</li>

                // // 创建一个li
                // const li = document.createElement("li")
                // // 向li中添加文本
                // li.textContent = "唐僧"
                // // 给li添加id属性
                // li.id = "ts"

                // // appendChild() 用于给一个节点添加子节点
                // list.appendChild(li)
                // // list.insertAdjacentElement("beforeend", li)
                // 直接插入HTML,省去了创建结点
                list.insertAdjacentHTML("beforeend", '<li id="ts">唐僧</li>')
            }

            const btn02 = document.getElementById("btn02")
            btn02.onclick = function(){
                // 创建一个蜘蛛精替换孙悟空
                const li = document.createElement("li")
                li.textContent = "蜘蛛精"
                li.id = "zzj"

                // 获取swk
                const swk = document.getElementById("swk")
                // replaceWith() 使用一个元素替换当前元素
                swk.replaceWith(li)

                // remove()方法用来删除当前元素
                // swk.remove()

            }
        </script>
    </body>
节点的复制

cloneNode()
会复制节点的所有特点包括各种属性,这个方法默认只会复制当前节点,而不会复制节点的子节点,可以传递一个true作为参数,这样该方法也会将元素的子节点一起复制

<body>
    <button id="btn01">点我一下</button>
    <ul id="list1">
        <li id="l1">孙悟空</li>
        <li id="l2">猪八戒</li>
        <li id="l3">沙和尚</li>
    </ul>
    <ul id="list2">
        <li>蜘蛛精</li>
    </ul>
    <script>
        /* 点击按钮后,将id为l1的元素添加list2中 */
        const list2 = document.getElementById("list2")
        const l1 = document.getElementById("l1")
        const btn01 = document.getElementById("btn01")
        btn01.onclick = function () {
            const newL1 = l1.cloneNode(true) // true作为参数就会复制子节点,不写参数或参数为false默认不复制子节点
			//注意要修改复制过来的节点的id
            newL1.id = "newL1"
            list2.appendChild(newL1)
        }
    </script>
</body>

image

文本节点的获取与修改

文本节点在DOM也是一个对象,通常不需要获取对象而是直接通过元素即可完成对其的各种操作
通过textContent innerText innerHTML这3个属性获取与修改文本节点
在DOM中,网页中所有的文本内容都是文本节点对象。我们通常通过获取相关的元素节点(Element对象)来查看文本节点或修改文本节点
这3个都是可读写属性

  1. element.textContent 获取或修改元素中的文本内容
    • 获取的是标签中的内容,不会考虑css样式
    • 读取时不会识别文本里的标签,写入时若有标签写入也只是当做普通文本对待
  2. element.innerText 获取或修改元素中的文本内容
    • innerText获取内容时,会考虑css样式
    • 通过innerText去读取CSS样式,会触发网页的重排(即重新计算CSS样式),所以性能会比textContent差一点
    • 读取时不会识别文本里的标签,写入时若有标签写入也只是当做普通文本对待
  3. element.innerHTML 获取或修改元素中的html代码
    • 返回该元素包含的所有 HTML 代码
    • 可以直接向元素中添加html代码
    • 因为它可以识别html代码,所以用innerHTML插入内容时,有被xss注入的风险
<div id="box1">
    <span style="text-transform: uppercase;">我是box1</span>
</div>
<script>
	const box1 = document.getElementById("box1")
</script>

读取文本节点内容image
修改文本节点内容imageimageimage

属性节点的获取与修改

属性节点(Attr,Attribution的缩写)

  • 在DOM也是一个对象,通常不需要获取对象而是直接通过元素即可完成对其的各种操作
  • 如何操作属性节点:
    方式一(常用):注意这种方式class属性需要使用className来读取
    读取:元素.属性名
    读取一个布尔类型的属性时(比如disabled checked),会返回true或false(样式中写了该属性就是ture, 没写就是false)
    修改:元素.属性名 = 属性值
    方式二:
    读取:元素.getAttribute(属性名)
    修改:元素.setAttribute(属性名, 属性值)
    删除:元素.removeAttribute(属性名)
  • 需要注意的是这两种方式对于布尔类型的属性值的区别(建议自己实践看看,容易搞混)
<input class="a" type="text" name="username" value="admin">
<script>
	// const input = document.getElementsByName("username")[0]
	// 属性选择器,获取name属性值为username的元素
	const input = document.querySelector("[name=username]")
	console.log(input.type)
	console.log(input.getAttribute("type"))
	input.setAttribute("value", "孙悟空")
	input.setAttribute("disabled", "disabled")
</script>

imageimageimage

Element属性

通过Element对象获取其他节点的属性
  1. 获取元素节点element.parentElement获取当前元素的父元素(返回值有2种情况:元素、null)(因为document不是元
    素,所以最终到达它时会返回null)
    这种用得少,更多的是用element.parentNode
    element.children 获取当前元素的子元素
    element.firstElementChild 获取当前元素的第一个子元素
    element.lastElementChild 获取当前元素的最后一个子元素
    element.nextElementSibling 获取当前元素的下一个兄弟元素
    element.previousElementSibling 获取当前元素的前一个兄弟元素
    element.parentNode 获取当前元素的父节点(返回值有2种情况:元素、document)
    element.tagName 获取当前元素的标签名
  2. 获取节点(不用记,这些算是比较老的语法了,现在用得少)
    节点就不仅是元素节点,还包含文本节点(空白的换行、空格等也算文本节点, 标签之间的换行,Tab,多个空格最终都会变成一个空格)
    element.childNodes 获取当前元素的子节点
    element.firstChild获取当前元素的第一个子节点
    element.lastChild获取当前元素的最后一个子节点
    element.nextSibling获取当前元素的下一个兄弟节点
    element.previousSibling获取当前元素的前一个兄弟节点
<div id="box1">
    我是box1
    <span class="s1">我是s1</span>
    <span class="s2">我是s2</span>
</div>
<span class="s3">我是s3</span>
<script>
	const box1 = document.getElementById("box1")
	console.log(box1.children)
	console.log(box1.firstElementChild)
	console.log(box1.lastElementChild)
	console.log(box1.nextElementSibling)
	console.log(box1.previousElementSibling)
	console.log(box1.tagName)
	console.log(box1.childNodes);
	const children = box1.childNodes
	for(let v of children){
	    console.log(v)
	}
</script>

image

Element.id

Element.id属性返回指定元素的id属性,该属性可读写

// HTML 代码为 <p id="foo">
var p = document.querySelector('p');
p.id // "foo"
Element.className

className属性用来读写当前元素节点的class属性。它的值是一个字符串,每个class之间用空格分割

// HTML 代码 <div class="one two three" id="myDiv"></div>
var div = document.getElementById('myDiv');
div.className
Element.classList

classList对象有下列方法

  • add():增加一个 class。
  • remove():移除一个 class。
  • contains():检查当前元素是否包含某个 class。
  • toggle():将某个 class 移入或移出当前元素。存在则移除,不存在则添加
var div = document.getElementById('myDiv');

div.classList.add('myCssClass');
div.classList.add('foo', 'bar');
div.classList.remove('myCssClass');
div.classList.toggle('myCssClass'); // 如果 myCssClass 不存在就加入,否则移除
div.classList.contains('myCssClass'); // 返回 true 或者 false

实时效果反馈

1. 下列代码为div元素动态添加一个class,画横线处应该填写的内容是:

var div = document.getElementById('myDiv');
div.classList.___('myCssClass');

A remove

B add

C toggle

D contains

答案

1=>B

CSS操作

一般用JS修改样式是直接修改内联样式,因为这样更方便,且不用怎么考虑优先级问题

添加或修改样式的方法

HTML 元素的 style 属性

操作 CSS 样式最简单的方法,就是使用网页元素节点的setAttribute方法直接操作网页元素的style属性

div.setAttribute(
  'style',//属性名
  "background-color:red;border:1px solid black;"//属性值
);
元素节点的style属性

image
修改样式的方式:元素节点.style.样式名 = 样式值
这种是直接修改内联样式
因为JS中-是减法的意思,如果样式名中含有-,则需要将样式名修改为驼峰命名法,比如background-color --> backgroundColor

var divStyle = document.querySelector('div').style;

divStyle.backgroundColor = 'red';//概括地讲格式为element.style.background=XXX;
divStyle.border = '1px solid black';
divStyle.width = '100px';
divStyle.height = '100px';
divStyle.fontSize = '10em';

以下代码实现点击按钮变换样式的功能

<head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
    <title>Document</title>
    <style>
        .box1 {
            width: 200px;
            height: 200px;
            background-color: #bfa;
        }
    </style>
</head>
<body>
    <button id="btn">点我一下</button>
    <hr />
    <div class="box1"></div>
    <script>
        // 点击按钮后,修改box1的宽度
        const btn = document.getElementById("btn")
        const box1 = document.querySelector(".box1")
        btn.onclick = function () {
            // 修改box1的样式
            box1.style.width = "400px"
            box1.style.height = "400px"
            box1.style.backgroundColor = "yellow"
        }
    </script>
</body>

image

cssText属性
var divStyle = document.querySelector('div').style;

divStyle.cssText = 'background-color: red;'
  + 'border: 1px solid black;'
  + 'height: 100px;'
  + 'width: 100px;';
通过元素的class属性修改样式

除了直接修改样式外,也可以通过修改class属性来间接的修改样式
通过class修改样式的好处:

  1. 可以一次性修改多个样式,因为可以把多个样式写在一个类选择器中
  2. 对JS和CSS进行解耦,即降低JS与CSS的耦合度

元素.classList 是一个对象,对象中提供了对当前元素的类的各种操作方法

  • 元素.classList.add("class1"[, "class2", "class3"...]) 向元素中添加一个或多个class
  • 元素.classList.remove("class1"[, "class2", "class3"...]) 移除元素中的一个或多个class
  • 元素.classList.toggle("class2") 切换元素中的class, 如果该元素的class属性中有class2,则添加class2选择器,如果没有,则删除class2选择器。类似于开关
  • 元素.classList.replace("class1", "class2") class2替换class1
  • 元素.classList.contains("class3") 返回布尔值,检查元素的class属性中是否包含某个类选择器class3

点击按钮后,修改box1的样式

<head>
......
        <style>
            .box1 {
                width: 200px;
                height: 200px;
                background-color: #bfa;
            }
            .box2{
                background-color: yellow;
                width: 300px;
                height: 500px;
                border: 10px greenyellow solid;
            }
        </style>
    </head>
    <body>
        <button id="btn">点我一下</button>

        <hr />

        <div class="box1 box2 box3 box4"></div>

        <script>
            const btn = document.getElementById("btn")
            const box1 = document.querySelector(".box1")

            btn.onclick = function () {
                // box1.className += " box2"//这种方式修改元素的class属性不太灵活
				// 可以调用classList的方法更灵活地操作元素的class属性
                // box1.classList.add("box2", "box3", "box4")
                // box1.classList.add("box1")

                // box1.classList.remove("box2")
                box1.classList.toggle("box2")
                // box1.classList.replace("box1", "box2")

                let result = box1.classList.contains("box3")

                console.log(result)
            }
        </script>
    </body>

image

实时效果反馈

1. 下列是设置样式的代码,横线处应该填写的代码是:

var divStyle = document.querySelector('div');
divStyle.___.backgroundColor = 'red';

A style

B cssText

C setAttribute

D getAttribute

答案

1=>A

读取样式的方法

元素节点.style.样式名是直接读取内联样式,如果元素没有设置内联样式就会读取不到,很多时候我们需要读取实际生效的样式,所以这种方式不推荐

getComputedStyle()
  • 参数:
    1. 要获取样式的对象
    2. 要获取的伪元素
  • 返回值:
    一个对象,这个对象中包含了当前元素所有的生效的样式,有些样式是计算值,有些不是。比如width:1359.2px; left:auto;
  • 注意:
    样式对象中返回的样式值,不一定能来拿来直接计算,所以拿它来计算时,一定要确保值是可以计算的值
<head>
······
    <style>
        .box1 {
            height: 200px;
            background-color: #bfa;
        }
        .box1::before {
            content: "hello";
            color: red;
        }
    </style>
</head>
<body>
    <button id="btn">点我一下</button>
    <hr />
    <div class="box1"></div>
    <script>
        /* 
            点击按钮后,读取元素的css样式
        */
        const btn = document.getElementById("btn")
        const box1 = document.querySelector(".box1")
        btn.onclick = function () {
            const styleObj = getComputedStyle(box1)
            console.log(styleObj.width)
            console.log(styleObj.left)
            // 通过取整函数parseInt()可以将带单位的值强行转换成带单位可以计算的值
            // console.log(parseInt(styleObj.width) + 100)  //计算的时候记得要去除单位
            // box1.style.width = parseInt(styleObj.width) + 100 + "px" //记得加上单位
            console.log(styleObj.backgroundColor)
            const beforeStyle = getComputedStyle(box1, "::before")
            console.log(beforeStyle.color)
        }
    </script>
</body>

image

Element获取元素位置

以下属性均不带单位
只读属性
元素.clientHeight
元素.clientWidth
- 获取元素内部的宽度和高度(包括content和padding)
元素.offsetHeight
元素.offsetWidth
- 获取元素的可见框的大小(包括内容区、内边距和边框)
元素.scrollHeight
元素.scrollWidth
- 获取元素滚动区域的大小
元素.offsetParent
- 获取元素的定位父元素
- 定位父元素:离当前元素最近的开启了定位的祖先元素,如果所有的元素都没有开启定位则返回body
元素.offsetTop
元素.offsetLeft
- 获取元素相对于其定位父元素的偏移量
可修改属性
元素.scrollTop
元素.scrollLeft
- 获取或设置元素滚动条的偏移量

属性描述
clientHeight获取元素高度,包括padding和溢出的不可见内容,但是不包括bordermargin
clientWidth获取元素宽度,包括padding和溢出的不可见内容,但是不包括bordermargin
scrollHeight元素滚动区域总高度,它包括padding,但是不包括bordermargin和溢出的不可见内容
scrollWidth元素滚动区域总宽度,它包括padding,但是不包括bordermargin和溢出的不可见内容
scrollLeft元素的水平滚动条向右滚动的像素
scrollTop元素的垂直滚动条向下滚动的像素
offsetHeight元素的 CSS 垂直高度(单位像素),包括元素本身的高度、padding 和 border
offsetWidth元素的 CSS 水平宽度(单位像素),包括元素本身的高度、padding 和 border
offsetLeft相对于其定位父元素左边界的间距
offsetTop相对于其定位父元素上边界的间距
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        #box1{
            width: 200px;
            height: 200px;
            padding: 50px;
            margin: 50px;
            border: 10px red solid;
            background-color: #bfa;
            overflow: auto;
        }

        #box2{
            width: 100px;
            height: 500px;
            background-color: orange;
        }

    </style>
</head>
<body>
    <button id="btn">点我一下</button>
    <hr>
    <div>
        <div id="box1">
            <div id="box2"></div>
        </div>
    </div>
    

    <script>

        const btn = document.getElementById("btn")
        const box1 = document.getElementById("box1")

        btn.onclick = function(){
            console.log(box1.scrollHeight)
            console.log(box1.scrollWidth)

            console.log(box1.offsetParent)

            console.log(box1.offsetLeft)
            console.log(box1.offsetTop)

            console.log(box1.scrollHeight)

        }
        

    </script>
</body>

image

Element.clientHeight,Element.clientWidth

Element.clientHeight属性返回一个整数值,表示元素节点的 CSS 高度(单位像素),只对块级元素生效,对于行内元素返回0。如果块级元素没有设置 CSS 高度,则返回实际高度

除了元素本身的高度,它还包括padding部分,但是不包括bordermargin。如果有水平滚动条,还要减去水平滚动条的高度。注意,这个值始终是整数,如果是小数会被四舍五入。

Element.clientWidth属性返回元素节点的 CSS 宽度,同样只对块级元素有效,也是只包括元素本身的宽度和padding,如果有垂直滚动条,还要减去垂直滚动条的宽度。

document.documentElementclientHeight属性,返回当前视口的高度(即浏览器窗口的高度)。document.body的高度则是网页内容总高度。

// 视口高度
document.documentElement.clientHeight

// 网页内容总高度
document.body.clientHeight

Element.scrollHeight,Element.scrollWidth

Element.scrollHeight属性返回一个整数值(小数会四舍五入),表示当前元素的总高度(单位像素),它包括padding,但是不包括bordermargin以及水平滚动条的高度(如果有水平滚动条的话)

Element.scrollWidth属性表示当前元素的总宽度(单位像素),其他地方都与scrollHeight属性类似。这两个属性只读

整张网页的总高度可以从document.documentElementdocument.body上读取

// 返回网页的总高度
document.documentElement.scrollHeight
document.body.scrollHeight
//我所实际运行的结果不相等

Element.scrollLeft,Element.scrollTop

Element.scrollLeft属性表示当前元素的水平滚动条向右侧滚动的像素数量,Element.scrollTop属性表示当前元素的垂直滚动条向下滚动的像素数量。对于那些没有滚动条的网页元素,这两个属性总是等于0

如果要查看整张网页的水平的和垂直的滚动距离,要从document.documentElement元素上读取

document.documentElement.scrollLeft
document.documentElement.scrollTop

Element.offsetHeight,Element.offsetWidth

Element.offsetHeight属性返回一个整数,表示元素的 CSS 垂直高度(单位像素),包括元素本身的高度、padding 和 border,以及水平滚动条的高度(如果存在滚动条)。

Element.offsetWidth属性表示元素的 CSS 水平宽度(单位像素),其他都与Element.offsetHeight一致。

这两个属性都是只读属性,只比Element.clientHeightElement.clientWidth多了边框和滚动条(如果有的话)的高度或宽度。如果元素的 CSS 设为不可见(比如display: none;),则返回0

Element.offsetLeft,Element.offsetTop

Element.offsetLeft返回当前元素左上角相对于Element.offsetParent节点的水平位移,Element.offsetTop返回垂直位移,单位为像素。通常,这两个值是指相对于父节点的位移

<div class="parent">
    <div class="box" id="box"></div>
</div>
.parent{
    width: 200px;
    height: 200px;
    background: red;
    position: relative;
    left: 50px;
    top: 50px;
}

.box{
    width: 100px;
    height: 100px;
    background: yellow;
    position: relative;
    left: 50px;
    top: 50px;
}
var box = document.getElementById("box");
console.log(box.offsetLeft);
console.log(box.offsetTop);

实时效果反馈

1. 下面是获得整个视口的宽度和高度:

document.documentElement.___
document.documentElement.___

A offsetLeft offsetTop

B scrollHeight scrollWidth

C clientLeft clientTop

2. 以下哪个是获得元素的高度,包含内外边距和边框:

A offsetLeft offsetTop

B offsetHeight offsetWidth

C clientLeft clientTop

D clientHeight clientWidth

答案

1=>B 2=>B

事件(event)处理程序

  • 事件就是用户和页面之间发生的交互行为
    比如:点击按钮、鼠标移动、双击按钮、敲击键盘、松开按键...
  • 可以通过为事件绑定响应函数(回调函数),来完成和用户之间的交互
  • 标签本身带有一些事件属性,例如onclick, onmouseenter, ondblclick
    绑定响应函数的方式有:
  1. 可以直接在元素的属性中设置(一个事件只能绑定一个事件处理程序)
//鼠标进入触发事件
<button id="btn" onmouseenter="alert('你点我干嘛~')">点我一下</button>
  1. 可以通过为元素的指定属性设置回调函数的形式来绑定事件(一个事件只能绑定一个事件处理程序)
<button id="btn" ond>点我一下</button>
<script>
	// 获取到按钮对象
	const btn = document.getElementById("btn")
	// 为按钮对象的事件属性设置响应函数
	btn.onclick = function(){
    	alert("我又被点了一下~~")
	}
	// 一个事件只能绑定一个响应函数,所以同一事件后面的响应函数会覆盖前面的
	btn.onclick = function(){
    	alert("1123111")
	}
</script>
  1. 可以通过元素addEventListener()方法来绑定事件,(一个事件可以绑定多个事件处理程序)
<button id="btn" ond>点我一下</button>
<script>
	// 获取到按钮对象
	const btn = document.getElementById("btn")
	// 以事件监听器的形式添加事件处理程序,事件名称没有on,因为on表示“当······的时候”,EventListener本身就有“当······时候”的意思
	btn.addEventListener("click", function(){
    	alert("哈哈哈")
	})
	btn.addEventListener("click", function(){
    	alert("嘻嘻嘻")
	})
	btn.addEventListener("click", function(){
    	alert("呜呜呜")
	})
</script>

事件处理程序分为:

  1. HTML事件处理
  2. DOM0级事件处理
  3. DOM2级事件处理

HTML事件

缺点:HTML与javascript没有分离

<!DOCTYPE html>
<html>
    <head lang="en">
        <meta charset="UTF-8">
        <title>Js事件详解--事件处理</title>
    </head>
    <body>
        <div id="div">
            <button id="btn1" onclick="demo()">按钮</button>
        </div>
        <script>
            function demo(){
                alert("hello html事件处理");
            }
        </script>
    </body>
</html>

DOM0级事件处理

形式:

  • 绑定事件:给DOM元素的事件处理程序属性赋值
  • 移除事件:给DOM元素的事件处理程序属性赋值为null
    优点:HTML与javascript分离了
    缺点:只能添加一个事件处理程序,添加多个事件处理程序时后面的程序会覆盖前面的
    注意这里onclick都是小写的
<body>
    <div id="div">
        <button id="btn1">按钮</button>
    </div>
    <script>
        var btn1=document.getElementById("btn1");
        btn1.onclick=function(){alert("Hello DOM0级事件处理程序1");}//被覆盖掉
        btn1.onclick=function(){alert("Hello DOM0级事件处理程序2");}
    </script>
</body>

DOM2级事件处理

形式:

  • 绑定事件:addEventListener()
  • 移除事件:removeEventListener()
    它们接收 3 个参数:事件名、事件处理函数和一个布尔值,true 表示在捕获阶段调用事件处理程序,false(默认值)表示在冒泡阶段调用事件处理程序

优点:HTML与javascript分离了,并且可以添加多个事件处理程序,触发事件时添加的多个程序都会运行

<body>
    <div id="div">
        <button id="btn1">按钮</button>
    </div>
    <script>
        var btn1=document.getElementById("btn1");
        btn1.addEventListener("click",demo1);
        btn1.addEventListener("click",demo2);
        btn1.addEventListener("click",demo3);
        function demo1(){
            alert("DOM2级事件处理程序1")
        }
        function demo2(){
            alert("DOM2级事件处理程序2")
        }
        function demo3(){
            alert("DOM2级事件处理程序3")
        }
        btn1.removeEventListener("click",demo2);
    </script>
</body>

如果确定触发元素后只添加一个事件,则优先选择DOM0级事件处理方法,如果确定触发元素后添加多个事件,则优先选择DOM2级事件处理方法

实时效果反馈

1. 下列代码,是属于哪种事件处理方式:

btn2.addEventListener('click',function(){
	console.log("触发事件");
})

A HTML事件

B DOM0级事件处理

C DOM2级事件处理

D IE事件处理程序

答案

1=>C

文档加载

网页是自上向下加载的,如果将js代码编写到网页的<head></head>内,js代码在执行时,网页还没有加载完毕,这时会出现无法获取到DOM对象的情况

如何解决这个问题(按执行时间从早到晚排列):

  1. 将script标签编写到body的最后(*****)
<head></head>
<body>
	html代码······
	<script>JS代码······</script>
</body>
  1. 将代码编写到外部的js文件中,然后以defer的形式进行引入(*****)
<head>
	<script defer src="路径"></script>
</head>
<body>
	html代码······
</body>
  1. 将代码编写到document对象的DOMContentLoaded的回调函数中,document的DOMContentLoaded事件会在当前文档加载完毕之后触发js代码
<head>
	<script>
	document.addEventListener("DOMContentLoaded", function () {
		JS代码······
	})
	</script>
</head>
<body>
	html代码······
</body>
  1. 将代码编写到window.onload的回调函数中,window.onload 事件会在窗口中的内容都加载完毕之后才触发js代码
<head>
	<script>
		window.onload = function () {
			JS代码······
		}
		或
		window.addEventListener("load", function () {
			JS代码······
		})
	</script>
</head>
<body>
	html代码······
</body>

事件处理程序练习

  1. 切换图片

  2. 实现按钮功能:全选、取消全选、反选、提交

事件类型之鼠标事件

鼠标事件

鼠标事件指与鼠标相关的事件,具体的事件主要有以下一些

  1. click:按下鼠标时触发
  2. dblclick:在同一个元素上双击鼠标时触发
  3. mousedown:按下鼠标键时触发
  4. mouseup:释放按下的鼠标键时触发
  5. mousemove:当鼠标在节点内部移动时触发。当鼠标持续移动时,该事件会连触发。
  6. mouseenter:鼠标进入一个节点时触发,进入子节点不会触发这个事件
  7. mouseleave:鼠标离开一个节点时触发,离开父节点不会触发这个事件
  8. mouseover:鼠标进入一个节点时触发,进入子节点会再一次触发这个事件
  9. mouseout:鼠标离开一个节点时触发,离开父节点也会触发这个事件
  10. wheel:滚动鼠标的滚轮时触发
var btn1 = document.getElementById("btn1");
btn1.onclick = function(){
    console.log("click事件");
}

温馨提示

这些方法在使用的时候,除了DOM2级事件,都需要添加前缀on

实时效果反馈

1. 下列代码是鼠标事件,划横线处应该填写的是:

// 需求:鼠标在元素内移动,会一直触发事件
var box = document.getElementById("box");
box.___ = function(){
    console.log("事件在元素上会一直触发");
}

A onclick

B onmouseover

C onmousemove

D onmouseenter

答案

1=>C

Event事件对象

  • 事件对象是浏览器在事件触发时所创建的对象,这个对象中封装了事件相关的各种信息
  • 通过事件对象可以获取到事件的详细信息, 比如:鼠标的坐标、键盘的按键状态...
  • 浏览器在创建事件对象后,会将事件对象作为响应函数的参数传递,所以我们可以在事件的回调函数中定义一个形参来接收事件对象
  • 事件对象与响应函数的绑定方式或类型无关。事件发生以后,会产生一个事件对象,作为参数传给监听函数。
  • 在DOM中存在着多种不同类型的事件对象,多种事件对象有一个共同的祖先 Event
    比如PointerEvent的继承关系image
<head>
...
        <style>
            #box1{
                width: 300px;
                height: 300px;
                border: 10px greenyellow solid;
            }

        </style>
    </head>
    <body>
        <div id="box1"></div>

        <script>
            const box1 = document.getElementById("box1")
			// 这里打印出的是指针事件PointerEvent
            box1.onclick = event => {
                console.log(event)
            }

            box1.addEventListener("mousemove", event => {
                console.log(event.clientX, event.clientY)

                box1.textContent = event.clientX + "," + event.clientY
            })
        </script>
    </body>

image
事件对象里包含着事件的信息image

Event对象属性

event.target event.currentTarget
  • event.target 触发事件的对象,注意与this区分
  • event.currentTarget 绑定事件的对象(同除箭头函数外的this),currentTarget===this
Event.type

Event.type属性返回一个字符串,表示事件类型。事件的类型是在生成事件的时候。该属性只读

Event对象方法

Event.preventDefault

Event.preventDefault方法取消浏览器对当前事件的默认行为。比如点击链接后,浏览器默认会跳转到另一个页面,使用这个方法以后,就不会跳转了

btn.onclick = function(e){
    e.preventDefault(); // 阻止默认事件
    console.log("点击A标签");
}
event.stopPropagation()

stopPropagation方法阻止事件在 DOM 中继续传播,防止再触发定义在别的节点上的监听函数,但是不包括在当前节点上其他的事件监听函数

btn.onclick = function(e){
    e.stopPropagation(); // 阻止事件冒泡
    console.log("btn");
}

事件的冒泡(bubble)

  • 事件的冒泡就是指事件的向上层传导
  • 当元素上的某个事件被触发后,其祖先元素上的相同事件也会同时被触发
  • 冒泡的存在大大的简化了代码的编写,但是在一些场景下我们并不希望冒泡存在,不希望事件冒泡时,可以通过event.stopPropagation()来取消冒泡

综合案例

<head>
        <meta charset="UTF-8" />
        <meta http-equiv="X-UA-Compatible" content="IE=edge" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>Document</title>
        <style>
            #box1 {
                width: 300px;
                height: 300px;
                background-color: greenyellow;
            }

            #box2 {
                width: 250px;
                height: 250px;
                background-color: #ff0;
            }

            #box3 {
                width: 200px;
                height: 200px;
                background-color: orange;
            }
        </style>
    </head>
    <body>
        <div id="box1">
            <div id="box2">
                <div id="box3"></div>
            </div>
        </div>

        <a id="chao" href="https://lilichao.com">超链接</a>

        <script>
            const box1 = document.getElementById("box1")
            const box2 = document.getElementById("box2")
            const box3 = document.getElementById("box3")
            const chao = document.getElementById("chao")

            chao.addEventListener("click", (event) => {

                event.preventDefault() // 取消默认行为

                alert("被点了~~~")

            })

            box1.addEventListener("click", function (event) {
                // alert(event)
                /* 
                在事件的响应函数中:
                    event.target 表示的是触发事件的对象
                    this 绑定事件的对象
            */
                // console.log(event.target)
                // console.log(this)

                console.log(event.currentTarget)

                // alert("Hello 我是box1")
            })

            // box2.addEventListener("click", function(event){
            //     event.stopPropagation()
            //     alert("我是box2")
            // })

            // box3.addEventListener("click", function(event){
            //     event.stopPropagation() // 取消事件的传到
            //     alert("我是box3")
            // })
        </script>
    </body>

imageimage

事件的冒泡和元素的样式无关,只和结构相关

<head>
...
        <style>
            #box1 {
                width: 100px;
                height: 100px;
                background-color: greenyellow;
                border-radius: 50%;
                position: absolute;
            }

            #box2 {
                width: 400px;
                height: 400px;
                background-color: orange;
            }

            #box3{
                width: 100px;
                height: 100px;
                background-color: tomato;
            }

            #box4{
                width: 50px;
                height: 50px;
                background-color: skyblue;
                position: absolute;
                bottom: 0;
            }
        </style>
    </head>
    <body>
        <div id="box1"></div>

        <div id="box2"></div>
        <!-- 事件的冒泡和元素的样式无关,只和结构相关。所以即使box4在样式上是在box3的外面,但在结构上还是box3的子节点,所以box4的点击事件仍会传导到box3上 -->
        <div id="box3" onclick="alert(3)">
            <div id="box4" onclick="alert(4)"></div>
        </div>

        <script>
            // 使小绿球可以跟随鼠标一起移动
            const box1 = document.getElementById("box1")
            const box2 = document.getElementById("box2")
            // 注意这里不要写成box1.addEventListener,否则只有在box1范围内移动鼠标小球才会跟随
            document.addEventListener("mousemove", (event) => {
                box1.style.top=event.y+"px";
                box1.style.left=event.x+"px"
            })
            // 鼠标在box2范围内移动小球不会跟随
            box2.addEventListener("mousemove", event => {
                event.stopPropagation()
            })
        </script>
    </body>

image

实时效果反馈

1. 下列关于阻止默认事件方法正确的是:

A stopPropagation()

B preventDefault()

C target

D currentTarget

答案

1=>B

事件代理(事件委派)

由于事件会在冒泡阶段向上传播到父节点,因此可以把子节点的监听函数定义在父节点上,由父节点的监听函数统一处理多个子元素的事件。这种方法叫做事件的代理(delegation)
事件代理可以减少对子元素监听的重复定义
如果希望只绑定一次事件,就可以让所有的某类元素节点,包括当前的和未来新建的这类元素节点都具有该事件,那么可以考虑用事件委派

var ul = document.querySelector('ul');

ul.addEventListener('click', function (event) {
  if (event.target.tagName.toLowerCase() === 'li') {//console.log(event.target.tagName)打印出的是大写字母
    // some code
  }
});

实例:员工行信息的删除与添加
没有用事件委派前,需要循环给表格中的删除链接加上点击事件响应函数,在新添加员工信息后,要给新添加的删除链接单独添加点击事件响应函数。这样不够精简

// 1. 实现删除员工信息操作
    // 获取页面所有的删除链接
    let links = document.links
    function delEmp() {
        // 注意这里不要写成const tr = links[i].parentNode.parentNode,因为这个函数里并不认识links
[i]?
        // 除箭头函数没有自己的this外(箭头函数的this是最近的外层作用域的this),时间响应函数的this是当
前所指的元素节点
        // 获取该行信息
        const tr = this.parentNode.parentNode
        // 注意这里不要写成const name = document.getElementsByTagName("td")[0].textContent,否则每
次获取的都是整片文档的第一个<td></td>元素
        // 获取员工姓名
        const empName = tr.getElementsByTagName("td")[0].textContent
        // 注意不要写成"确认删除【+name+】?",否则变量读取不出
        // 因为alert没有取消按钮,所以用有取消按钮的confirm,confirm返回一个布尔值,点击确定返回ture,
点击取消返回false
        // 点击删除时跳出弹窗提示,防止误操作
        if (confirm("确认删除【" + empName + "】?"))
            tr.remove()
    }
    for (let i = 0; i < links.length; i++) {
        // links[i].addEventListener("click",delEmp)
        // 此处delEmp函数不能加括号,因为这里是将函数对象赋给links[i].onclick,而不是将函数返回值赋给对
象。delEmp就相当于函数的地址,让links[i].onclick指向该函数对象
        // 给每个<a></a>标签设置时间响应函数,这样写减少了重复代码
        links[i].onclick = delEmp
    }
    // 2. 实现添加员工信息功能
    const btn = document.getElementById("btn")
    // 易错点:注意这里易错写成const tbody = document.getElementsByTagName("tbody"),document.
getElementsByTagName()返回的是一个类数组对象,应加[0]表示获取其第一个元素
    // const tbody = document.getElementsByTagName("tbody")[0]
    // 或
    const tbody = document.querySelector("tbody")
    // btn.addEventListener("click",function(){
    btn.onclick = function () {
        const name = document.getElementById("name").value
        const email = document.getElementById("email").value
        const salary = document.getElementById("salary").value
        const tr = document.createElement("tr")
        const nameTd = document.createElement("td")
        const emailTd = document.createElement("td")
        const salaryTd = document.createElement("td")
        nameTd.textContent = name;
        emailTd.textContent = email;
        salaryTd.textContent = salary;
        // tbody.insertAdjacentElement("beforend","tr")
        tbody.appendChild(tr)
        tr.appendChild(nameTd)
        tr.appendChild(emailTd)
        tr.appendChild(salaryTd)
        // 没有用户输入的部分可以使用insertAdjacentHTML方法直接添加HTML
        tr.insertAdjacentHTML("beforeend", '<td><a href="javascript:;">删除</a></td>')
        // 及时给新添加的行绑定删除操作的事件处理程序
        links[links.length-1].onclick=delEmp
    }

思路:可以将事件统一绑定给document,这样点击超链接时由于事件的冒泡,会导致document上的点击事件被触发,这样只绑定一次,所有的超链接都会具有这些事件

// 1. 实现删除员工信息操作
            // 获取页面所有的删除链接
            let links = document.links
            // 事件委派给document
            document.addEventListener("click", event => {
				// 此处加个判断语句来限定只有“删除超链接元素结点”才会触发后面的操作
                if ([...links].includes(event.target)) {
                    const tr = event.target.parentNode.parentNode
                    const empName = tr.firstElementChild.textContent
                    // 或
                    // const empName = tr.getElementsByTagName("td")[0].textContent
                    if (confirm("确认删除【" + empName + "】?"))
                        tr.remove()
                }
            })

            // 2. 实现添加员工信息功能
            const btn = document.getElementById("btn")
            // 易错点:注意这里易错写成const tbody = document.getElementsByTagName("tbody"),document.getElementsByTagName()返回的是一个类数组对象,应加[0]表示获取其第一个元素
            // const tbody = document.getElementsByTagName("tbody")[0]
            // 或
            const tbody = document.querySelector("tbody")

            // btn.addEventListener("click",function(){
            btn.onclick = function () {
                const name = document.getElementById("name").value
                const email = document.getElementById("email").value
                const salary = document.getElementById("salary").value
                const tr = document.createElement("tr")
                const nameTd = document.createElement("td")
                const emailTd = document.createElement("td")
                const salaryTd = document.createElement("td")
                nameTd.textContent = name;
                emailTd.textContent = email;
                salaryTd.textContent = salary;
                // tbody.insertAdjacentElement("beforend","tr")
                tbody.appendChild(tr)
                tr.appendChild(nameTd)
                tr.appendChild(emailTd)
                tr.appendChild(salaryTd)
                // 没有用户输入的部分可以使用insertAdjacentHTML方法直接添加HTML
                tr.insertAdjacentHTML("beforeend", '<td><a href="javascript:;">删除</a></td>')
                // 及时给新添加的行绑定删除操作的事件处理程序
            }

实时效果反馈

1. 下列是关于事件代理的处理,横线处应该填写的代码是:

var parent = document.getElementById("parent");
parent.addEventListener("click",function(e){
    if(e.___.tagName.___ === 'li'){
        console.log(e.target.innerHTML);
    }
})

A target toUpperCase()

B target toLowerCase()

C currentTarget toLowerCase()

D currentTarget toUpperCase()

答案

1=>B

事件的捕获(开发中不常用,但面试可能会问到事件的捕获)

事件的传播机制:

  • 在DOM中,事件的传播可以分为三个阶段:
    1.捕获阶段 (由祖先元素向目标元素进行事件的捕获)
    2.目标阶段 (触发事件的对象)
    3.冒泡阶段 (由目标元素向祖先元素进行事件的冒泡)(默认情况下,响应函数在冒泡阶段触发)
  • 事件的捕获,指事件从外向内的传导,当前元素触发事件以后,会先从document开始向当前元素进行事件的捕获
  • 如果希望在捕获阶段触发响应函数,可以将addEventListener的第三个参数设置为true, 一般情况下我们不希望事件在捕获阶段触发,所有通常都不需要设置第三个参数
#box1 {
    width: 300px;
    height: 300px;
    background-color: greenyellow;
}
#box2 {
    width: 200px;
    height: 200px;
    background-color: orange;
}
#box3 {
    width: 100px;
    height: 100px;
    background-color: tomato;
}
const box1 = document.getElementById("box1")
const box2 = document.getElementById("box2")
const box3 = document.getElementById("box3")
box1.addEventListener("click", event => {
    alert("1" + event.eventPhase) // eventPhase 表示事件触发的阶段, 返回值是数字1或2或3
    //1 捕获阶段 2 目标阶段 3 冒泡阶段
})
box2.addEventListener("click", event => {
    alert("2" + event.eventPhase)
})
box3.addEventListener("click", event => {
    alert("3" + event.eventPhase)
})

事件类型之键盘事件

键盘事件由用户击打键盘触发,主要有keydown、keypress、keyup三个事件

  1. keydown:按下键盘时触发。
  2. keypress:按下有值的键时触发,即按下 Ctrl、Alt、Shift、Meta 这样无值的键,这个事件不会触发。对于有值的键,按下时先触发keydown事件,再触发这个事件。
  3. keyup:松开键盘时触发该事件
username.onkeypress = function(e){
    console.log("keypress事件");
}

event对象

keyCode:唯一标识

var username = document.getElementById("username");
username.onkeydown = function(e){
    if(e.keyCode === 13){
        console.log("回车");
    }
}

实时效果反馈

1. 下列那个不是键盘事件:

A keydown

B keypress

C keyup

D click

答案

1=>D

事件类型之表单事件

表单事件是在使用表单元素及输入框元素可以监听的一系列事件

  1. input事件
  2. select事件
  3. Change事件
  4. reset事件
  5. submit事件

input事件

元素.oninput
input事件当<input>、<select>、<textarea>的值发生变化时触发。对于复选框(<input type=checkbox>)或单选框(<input type=radio>),用户改变选项时,也会触发这个事件

input事件的一个特点,就是会连续触发,比如用户每按下一次按键,就会触发一次input事件。

var username = document.getElementById("username");
username.oninput = function(e){
    console.log(e.target.value);
}

select事件

元素.onselect
select事件当在<input>、<textarea>里面选中文本时触发

// HTML 代码如下
// <input id="test" type="text" value="Select me!" />

var elem = document.getElementById('test');
elem.onselect = function (e) {
  console.log(e.target.value); //打印的是表单成员中的内容,而不是选中的内容
}, false);

Change 事件

元素.onchange
Change事件当<input>、<select>、<textarea>的值发生变化时触发。它与input事件的最大不同,就是不会连续触发,只有当全部修改完成时才会触发,即当按下回车或失去焦点时会触发

var email = document.getElementById("email");
email.onchange = function(e){
    console.log(e.target.value);
}

reset 事件,submit 事件

这两个事件发生在表单对象<form>上,而不是发生在表单的成员上。
当需要把表单的内容重置到默认值时使用表单的reset事件
当需要把表单的内容提交到服务器时使用表单的submit事件,注意submit事件的发生对象是<form>元素,而不是<button>元素,因为提交的是表单,而不是按钮

<form id="myForm" onsubmit="submitHandle">
    <button onclick="resetHandle">重置数据</button>
    <button>提交</button>
</form>
var myForm = document.getElementById("myForm")
function resetHandle(){
    myForm.reset();
}
function submitHandle(){
    console.log("提交");
}

实时效果反馈

1. 下列代码中,横线处应该填写的内容是:

// 需求:用户每次输入都需要触发的事件
var username = document.getElementById("username");
username.___ = function(e){
    console.log(e.target.value);
}

A select

B oninput

C onchange

D submit

答案

1=>B

BOM对象

  • 浏览器对象模型
  • BOM为我们提供了一组对象,通过这组对象可以完成对浏览器的各种操作
  • BOM对象:
    • Window —— 代表浏览器窗口(全局对象)
    • Navigator —— 浏览器的对象(可以用来识别浏览器)
    • Location —— 浏览器的地址栏信息
    • History —— 浏览器的历史记录(控制浏览器前进后退)
    • Screen —— 屏幕的信息
  • BOM对象都是作为window对象的属性保存的,所以可以直接在JS中访问这些对象(注意是小写)image

Navigator —— 浏览器的对象(可以用来识别浏览器),userAgent 返回一个用来描述浏览器信息的字符串

Location

location 表示的是浏览器地址栏的信息

  • 可以直接将location的值修改为一个新的地址,这样会使得网页发生跳转, 比如location="https://xxx.xxx"
  • location.assign() 跳转到一个新的地址
  • location.replace() 跳转到一个新的地址(无法通过回退按钮回退)
  • location.reload() 刷新页面,可以传递一个参数true来强制清缓存刷新
  • location.href 获取当前地址,即导航栏的内容
<button id="btn">点我一下</button>
<input type="text" name="username" />
<script>
    const btn = document.getElementById("btn")
    btn.addEventListener("click", () => {
        console.log(location.href)
        // location = "https://www.lilichao.com"
        // location.assign("https://www.lilichao.com")
        // location.replace("https://www.lilichao.com")
        // location.reload(true)
        // location="https://lilichao.com"
    })

History

history.back()相当于回退按钮的作用
history.forward()相当于前进按钮的作用
history.go(number),可以向前跳转也可以向后跳转,参数是数字,number为正数时表示前进number步,number为负数时表示后退number步

定时器

通过定时器,可以使代码在指定时间后执行
设置定时器的方式有两种:

  1. setTimeout()一段时间后执行,只执行一次
    • 参数:
      1. 回调函数/代码块(要执行的代码)
      2. 间隔的时间(毫秒)
    • 关闭定时器
      clearTimeout(定时器编号)
  2. setInterval() (每间隔一段时间代码就会执行一次)
    • 参数:
      1. 回调函数/代码块(要执行的代码)
      2. 间隔的时间(毫秒)
    • 关闭定时器
      clearInterval(定时器编号)

setTimeout()

JavaScript 提供定时执行代码的功能,叫做定时器(timer),主要由setTimeout()setInterval()这两个函数来完成。它们向任务队列添加定时任务

setTimeout函数用来指定某个函数或某段代码,在多少毫秒之后执行。它返回一个整数,表示定时器的编号,以后可以用来取消这个定时器。
格式

  1. var timerId = setTimeout(函数名|代码块, 延迟时间);
  2. setTimeout(function[, delay, arg1, arg2, ...]);
    参数说明
  • function为函数名或代码块
  • delay表示延迟时间,默认是0,表示立即执行
  • arg1, arg2, ...表示参数,可以是多个参数,这些参数会作为function的参数

setTimeout函数接受两个参数,第一个参数func|code是将要推迟执行的函数名或者一段代码,第二个参数delay是推迟执行的毫秒数

setTimeout(function(){
    console.log("定时器")
},1000)

温馨提示

还有一个需要注意的地方,如果回调函数是对象的方法,那么setTimeout使得方法内部的this关键字指向全局环境window,而不是定义时所在的那个对象

var name = "sxt";
var user = {
    name: "itbaizhan",
    getName: function () {
        setTimeout(function(){
            console.log(this.name);
        },1000)
    }
};
user.getName();

解决方案

var name = "sxt";
var user = {
    name: "itbaizhan",
    getName: function () {
        var that = this;
        setTimeout(function(){
            console.log(that.name);
        },1000)
    }
};
user.getName();

定时器可以进行取消

var id = setTimeout(f, 1000);
clearTimeout(id);

实时效果反馈

1. 以下是关于定时器setTimeout代码,运行结果是:

var name = "sxt";
var user = {
    name: "itbaizhan",
    getName: function () {
        var that = this;
        setTimeout(function(){
            console.log(that.name);
        },1000)
    }
};
user.getName();

A itbaizhan

B sxt

C 报错

D undefined

答案

1=>A

setInterval()

setInterval函数的用法与setTimeout完全一致,区别仅仅在于setInterval指定某个任务每隔一段时间就执行一次,也就是无限次的定时执行

var timer = setInterval(function() {
  console.log(2);
}, 1000)

通过setInterval方法实现网页动画

<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<meta name="viewport" content="width=device-width, initial-scale=1.0">
	<title>Document</title>
	<style>
		#someDiv{
			width: 100px;
			height: 100px;
			background: red;
		}
	</style>
</head>
<body>
	<div id="someDiv"></div>
	<script>
		var div = document.getElementById('someDiv');
		var opacity = 1;
		var fader = setInterval(function() {
		  opacity -= 0.05;
		  if (opacity > 0) {
		    div.style.opacity = opacity;
		  } else {
		    clearInterval(fader);
		  }
		}, 30);

	</script>
</body>
</html>

定时器可以进行取消

var id = setInterval(f, 1000);
clearInterval(id);

实时效果反馈

1. 以下是关于定时器setInterval代码,每秒打印当前时间,横线处填写代码是:

___(function(){
    console.log(new Date().toLocaleTimeString());
},1000)

A getTime

B setTimeout

C setInterval

D function

答案

1=>C

定时器的执行方式

定时器的本质,就是在指定时间后将函数添加到消息队列

  • setTimeout()是一段时间后将函数添加到消息队列中
    调用栈中无其他函数空间时,3s后将定时器的回调函数加入到消息队列中,打印的时间约为3s
console.time()
setTimeout(function(){
    console.timeEnd()
    console.log("定时器执行了~")
}, 3000)

image

执行过程:3s后将定时器的回调函数加入到消息队列中,此时全局空间还未执行完(要6s才能执行完),全局空间占用着调用栈,6s后全局空间执行完毕,调用栈空,消息队列中的回调函数放入调用栈执行。所以打印出的是约6s

console.time()
setTimeout(function(){
    console.timeEnd()
    console.log("定时器执行了~")
}, 3000)
// 使程序卡6s
const begin = Date.now()
while (Date.now() - begin < 6000) {}

image

  • setInterval()是每间隔一段时间就将函数添加到消息队列中,但是如果函数执行的速度比较慢时,就无法确保每次执行的间隔都是一样的。
console.time("间隔")
setInterval(function(){
    console.timeEnd("间隔")
    // console.log("定时器执行了~~")
    alert("定时器执行~")
    console.time("间隔")
}, 3000)

如果alert()这里停顿的时间比较长,就会有大量的定时器回调函数积累在消息队列中,当这个在调用栈中停顿很久的函数空间执行完时,消息队列中的第一个函数就会立马进入调用栈,导致函数执行时间间隔没有达到3simage
所以,想确保函数执行时间间隔一致,一般不用setInterval(), 而是用setTimeout()
希望可以确保函数每次执行都有相同间隔的方法

console.time("间隔")
setTimeout(function fn() {
    console.timeEnd("间隔")
    alert("哈哈")
    console.time("间隔")
    // 这样不管函数执行多久,只有在函数执行完毕时才会再调用一个setTimeout,这样就能保证函数在相同的时间间隔执
行
    setTimeout(fn, 3000)
}, 3000)

image

思考题:执行如下代码,控制台会先打印1还是2?

setTimeout(()=>{
    console.log(1)
}, 0)
console.log(2)

答案:2image
因为console.log(2)属于全局空间,所以它是在调用栈中先执行,虽然console.log(1)在0s时就被加入到消息队列中,但需要等待调用栈中的console.log(2)先执行完才能执行

事件循环

事件循环(event loop)

  • 函数在每次执行时,都会产生一个执行环境
  • 执行环境负责存储函数执行时产生的一切数据
  • 问题:函数的执行环境要存储到哪里呢?
    • 函数执行环境的存储与变量,字面量等的存储是分开的
    • 函数的执行环境存储到了一个叫做调用栈的地方
    • 全局环境存放在栈的最底层
    • 栈,是一种数据结构,特点是后进先出

调用栈

调用栈(call stack)

  • 调用栈负责存储函数的执行环境
  • 当一个函数被调用时,它的执行环境会作为一个栈帧,插入到调用栈的栈顶,函数执行完毕其栈帧会自动从栈中弹出image

消息队列

消息队列

  • 消息队列负责存储将要执行的函数
  • 当我们触发一个事件时,其响应函数并不是直接就添加到调用栈中的,JS引擎是将事件响应函数插入到消息队列中排队,队列是先进先出的数据结构
<button id="btn">点我一下</button>
<button id="btn02">点我一下2</button>
<script>
    function fn() {
        let a = 10
        let b = 20
        function fn2() {
            console.log("fn2")
        }
        fn2()
        console.log("fn~")
    }
    fn()
    console.log(1111)
    const btn = document.getElementById("btn")
    const btn02 = document.getElementById("btn02")
    btn.onclick = function () {
        alert(1111)
        const begin = Date.now()
        while (Date.now() - begin < 3000) {}
    }
    btn02.onclick = function () {
        alert(2222)
    }
</script>

执行过程image

防抖(debounce)

防抖严格算起来应该属于性能优化的知识,但实际上遇到的频率相当高,处理不当或者放任不管就容易引起浏览器卡死。

从滚动条监听的例子说起

function showTop  () {
    var scrollTop = document.documentElement.scrollTop;
    console.log('滚动条位置:' + scrollTop);
}
window.onscroll  = showTop

在运行的时候会发现存在一个问题:这个函数的默认执行频率,太!高!了!。 高到什么程度呢?以chrome为例,我们可以点击选中一个页面的滚动条,然后点击一次键盘的【向下方向键】,会发现函数执行了8-9次!

然而实际上我们并不需要如此高频的反馈,毕竟浏览器的性能是有限的,不应该浪费在这里,所以接着讨论如何优化这种场景。

基于上述场景,首先提出第一种思路:在第一次触发事件时,不立即执行函数,而是给出一个期限值比如200ms,然后

  1. 如果在200ms内没有再次触发滚动事件,那么就执行函数
  2. 如果在200ms内再次触发滚动事件,那么当前的计时取消,重新开始计时

效果:如果短时间内大量触发同一事件,只会执行一次函数

实现:既然前面都提到了计时,那实现的关键就在于setTimeout这个函数,由于还需要一个变量来保存计时,考虑维护全局纯净,可以借助闭包来实现

function debounce(fn,delay){
    let timer = null //借助闭包
    return function() {
        if(timer){
            clearTimeout(timer) //每次滚动间隔在delay时间内就会清除定时器
        }
        timer = setTimeout(fn,delay) //在delay时间内没有再次滚动就会执行函数fn()
    }
}
// 然后是旧代码
function showTop  () {
    var scrollTop = document.documentElement.scrollTop;
    console.log('滚动条位置:' + scrollTop);
}
window.onscroll = debounce(showTop,300)//滚动的时候会触发多次debounce()

到这里,已经把防抖实现了

防抖定义

对于短时间内连续触发的事件(上面的滚动事件),防抖的含义就是让某个时间期限(如上面的1000毫秒)内,事件处理函数只执行一次

实时效果反馈

1. 以下是关于定时器防抖代码,横线处填写代码是:

function debounce(fn,delay){
    let timer = null 
    return function() {
        if(timer){
            _2_(timer) 
        }
        timer = _1_(fn,delay) 
    }
}
function showTop  () {
    var scrollTop = document.documentElement.scrollTop;
    console.log('滚动条位置:' + scrollTop);
}
window.onscroll = debounce(showTop,300)

A setTimeout clearInterval

B setTimeout clearTimeout

C setInterval clearTimeout

D setInterval clearInterval

答案

1=>B

节流(throttle)

没完全懂

image-20211109104718862

节流严格算起来应该属于性能优化的知识,但实际上遇到的频率相当高,处理不当或者放任不管就容易引起浏览器卡死

继续思考,使用上面的防抖方案来处理问题的结果是

如果在限定时间段内,不断触发滚动事件(比如某个用户闲着无聊,按住滚动不断的拖来拖去),只要不停止触发,理论上就永远不会输出当前距离顶部的距离

但是如果产品同学的期望处理方案是:即使用户不断拖动滚动条,也能在某个时间间隔之后给出反馈呢?

其实很简单:我们可以设计一种类似控制阀门一样定期开放的函数,也就是让函数执行一次后,在某个时间段内暂时失效,过了这段时间后再重新激活(类似于技能冷却时间)

效果:如果短时间内大量触发同一事件,那么在函数执行一次之后,该函数在指定的时间期限内不再工作,直至过了这段时间才重新生效

实现

这里借助setTimeout来做一个简单的实现,加上一个状态位valid来表示当前函数是否处于工作状态

function throttle(fn,delay){
    let valid = true
    return function() {
       if(!valid){
           //休息时间 暂不接客
           return false 
       }
       // 工作时间,执行函数并且在间隔期内把状态位设为无效
        valid = false//?
        setTimeout(function(){
            fn()
            valid = true;//?
        }, delay)
    }
}

function showTop  () {
    var scrollTop = document.documentElement.scrollTop;
    console.log('滚动条位置:' + scrollTop);
}
window.onscroll = throttle(showTop,300)

如果一直拖着滚动条进行滚动,那么会以300ms的时间间隔,持续输出当前位置和顶部的距离

讲完了这两个技巧,下面介绍一下平时开发中常遇到的场景:

  1. 搜索框input事件,例如要支持输入实时搜索可以使用节流方案(间隔一段时间就必须查询相关内容),或者实现输入间隔大于某个值(如500ms),就当做用户输入完成,然后开始搜索,具体使用哪种方案要看业务需求
  2. 页面resize事件,常见于需要做页面适配的时候。需要根据最终呈现的页面情况进行dom渲染(这种情形一般是使用防抖,因为只需要判断最后一次的变化情况)

实时效果反馈

1. 以下是关于定时器节流代码,横线处填写代码是:

function throttle(fn,delay){
    let valid = true
    return function() {
       if(_1_){
           return false 
       }
        valid = false
        _2_(function(){
            fn()
            valid = true;
        }, delay)
    }
}

A valid setTimeout

B valid setInterval

C !valid setTimeout

D !valid setInterval

答案

1=>C

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值