目录
- JavaScript简介
- JavaScript语句、标识符
- 常量、字面量、变量
- 引用JavaScript代码
- 在``标签中写入js语句
- 引入本地独立JS文件或网络来源文件
- 可以将js代码编写到指定属性中
- JavaScript注释与常见输出方式
- 数据类型
- typeof运算符(一般用来判断基本类型)
- 类型转换
- 算术运算
- 赋值运算
- 运算符之比较运算符
- 逻辑运算符--逻辑与、或、非
- 一元+、-运算符
- 运算符的优先级
- 条件语句之 if语句
- 条件语句之 if...else
- 条件语句之 switch
- 三元运算符
- 循环语句之for
- for循环语句实操
- 循环语句之while
- break 语句和 continue 语句
- 字符串
- 字符串方法charAt()
- 字符串方法concat()
- 字符串方法_substring()
- 字符串方法_substr()
- 字符串方法_indexOf()
- 字符串方法_trim()
- 字符串方法_split()
- 数组
- 函数
- 代码调试debug
- 严格模式
- 对象
- this对象
- call()与apply()
- bind()
- Map对象
- Set对象
- Window对象
- Math对象
- Date对象
- 类
- DOM概述
- document对象_方法/获取元素
- document对象_方法/创建元素
- Element对象_属性
- Element获取元素位置
- CSS操作
- 事件处理程序
- 事件类型之鼠标事件
- Event事件对象
- 事件类型之键盘事件
- 事件类型之表单事件
- 事件代理(事件委托)
- 定时器之`setTimeout()`
- 定时器之`setInterval()`
- 防抖(debounce)
- 节流(throttle)
JavaScript简介
JavaScript历史
1995年JavaScript问世,最初的主要用途是代替Perl等服务器端语言处理输入验证。网景公司在其Navigator浏览器中加入JavaScript以实现此功能。
ECMAScript是JavaScript的标准,在日常场合,这两个词是可以互换的。
JavaScript 是一种轻量级的脚本语言。所谓“脚本语言”,指的是它不具备开发操作系统的能力,而是只用来编写控制其他大型应用程序的“脚本”。
JavaScript 是一种嵌入式(embedded)语言。它本身提供的核心语法不算很多
JavaScript版本
实时效果反馈
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变量中并不存储任何字面量,而是存储值的内存地址。当有多个变量具有相同的值时,他们存储了该值相同的地址,所以同一个字面量只在内存中存储一份,这样更节省空间。
变量的作用域
作用域指的是一个变量的可见区域
作用域有两种:
- 全局作用域
- 全局作用域在网页运行时创建,在网页关闭时消耗
- 所有直接编写到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()
变量的重新赋值
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
,但不会报错
2
引用JavaScript代码
在<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代码编写到指定属性中
<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注释与常见输出方式
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 语言的每一个值,都属于某一种数据类型。JavaScript 的数据类型,共有六种。(ES6 又新增了第七种 Symbol 类型的值和第八种 BigInt类型,当前课程暂不涉及)
数据类型分类
原始类型(基础类型)
Javascript中一共有7种原始值。原始值一旦创建就不可修改,但变量可以重新赋值
- 数值(Number)
- 大整数(Bigint)
- 字符串(String)
- 布尔值(Boolean)
- 空值(Null)
- 未定义(Undefined)
- 符号(Symbol)
- 数值(Number)
- 在JS中所有的整数和浮点数都是Number类型
- JS中的数值并不是无限大的,当数值超过一定范围后会显示近似值
- Infinity 是一个特殊的数值表示无穷
- 因为JS不能精确表示范围大的数值且做小数运算时不够精确,所以在JS中进行一些精度比较高的运算时要十分注意
- NaN 也是一个特殊的数值,表示非法的数值
其他进制的数字: - 二进制 0b
- 八进制 0o
- 十六进制 0x
- 大整数(BigInt)
- 大整数用来表示一些比较大的整数
- 大整数使用n结尾,它可以表示的数字范围是无限大。
a = 99999999999999999999999999999999999999999999999999n
- BigInt与Number不能混合计算,BigInt只能和BigInt计算
- 字符串
- 转义字符
\
" --> "
’ --> ’
\ -->
\t --> 制表符,相当于一个Tab键
\n --> 换行 - 模板字符串
- 使用反单引号` 来表示模板字符串,模板字符串能保存换行,Tab,空格等这些格式
- 模板字符串中可以嵌入变量
- 空值 (Null)
- 空值用来表示空对象
- 使用typeof检查一个空值时会返回"object",所以typeof无法检测空值
- 未定义(Undefined)
- 当声明一个变量而没有赋值时,它的值就是Undefined
- 使用typeof检查一个Undefined类型的值时,会返回 “undefined”
- 符号(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
合成类型(复合类型)
对象:因为一个对象往往是多个原始类型的值的合成,可以看作是一个存放各种值的容器。对象里各种值用,
隔开
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(变量)
- 隐式转换:!!变量
下面介绍显式类型转换
转换成字符串
- 调用toString()方法将其他类型转换为字符串
null和undefined中没有toString()方法,所以它们调用toString()时会报错
let a = 10;
console.log(typeof a, a)//10 number
a = a.toString() // 创建了字符串类型的"10",并把其赋给a。注意要将它赋给a才会使变量a的值改变
console.log(typeof a, a)//10 string
- 调用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
转换成数值
- 使用Number()函数来将其他类型转换为数值
转换的情况:
- 字符串:
- 如果字符串是一个合法的数字,则会自动转换为对应的数字
- 如果字符串不是合法数字,则转换为NaN
- 如果字符串是空串或纯空格的字符串,则转换为0
- 布尔值:
- true转换为1,false转换为0
- null 转换为 0
- undefined 转换为 NaN
- 专门用来将字符串转换为数值的两个方法
- parseInt() —— 将一个字符串转换为一个整数
- 解析时,会自左向右读取一个字符串,直到读取到字符串中所有的有效的整数
- 也可以使用parseInt()来对一个数字进行取整(这时会先将数字转换成字符串,在转换为整数)
- parseFloat() —— 将一个字符串转换为浮点数
- 解析时,会自左向右读取一个字符串,直到读取完字符串左侧的浮点数
转换成布尔值
使用Boolean()函数来将其他类型转换为布尔值
转换的情况:
- 数字:
- 0 和 NaN 转换为false
- 其余是true
- 字符串:
- 空串转换为 false(只含空格的字符串不是空串)
- 其余是true(包含只含空格的字符串)
- null和undefined 都转换为 false
- 对象:对象会转换为true
总之,所有表示空性的没有的错误的值都会转换为false,比如 0、NaN、空串、null、undefined、false
算术运算
加减乘除运算符
加减乘除运算符就是基本的数学运算符效果
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
赋值运算
赋值运算符(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代码块,表示不满足条件时,所要执行的代码。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-s4SjJ7cq-1675909867689)(imgs/image-20211019151237163.png)]
if (m === 3) {
// 满足条件时,执行的语句
} else {
// 不满足条件时,执行的语句
}
多个if…else连接
对同一个变量进行多次判断时,多个if…else语句可以连写在一起。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rNfTi99S-1675909867690)(uploading…)].png)
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结构
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
三元运算符
JavaScript还有一个三元运算符(即该运算符需要三个运算子)?:
,也可以用于逻辑判断。
(条件) ? 表达式1 : 表达式2
这个三元运算符可以被视为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语句后面的括号里面,有三个表达式。
-
初始化表达式(initialize):确定循环变量的初始值,只在循环开始时执行一次。
-
布尔表达式(test):每轮循环开始时,都要执行这个条件表达式,只有值为真,才继续进行循环。
-
迭代因子(increment):每轮循环的最后一个操作,通常用来递增循环变量。
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 + " ");
}
}
打印九九乘法表
for (var i = 1; i <= 9; i++) {
for (var j = 1; j <= i; j++) {
document.write(j + "X" + i + "=" + i * j + " ");
}
document.write("<br/>");
}
运行结果如下
循环语句之while
While语句包括一个循环条件和一段代码块,只要条件为真,就不断循环执行代码块。
while (条件) {
语句;
}
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 语句
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);
/*跳到这*/}
实时效果反馈
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属性返回字符串的长度,该属性也是无法改变的
var s = 'itbaizhan';
s.length // 9
实时效果反馈
1. 下列字符串,横线处应该填写的代码是:
"Did she say ___"Hello___"?"
A “” ‘’
B ‘’ “”
C \ \
D “” “”
答案
1=>C
字符串方法charAt()
charAt
方法返回指定位置的字符,参数是从0
开始编号的
var s = new String('itbaizhan');
s.charAt(1) // "t"
s.charAt(s.length - 1) // "n"
如果参数为负数,或大于等于字符串的长度,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()
是有区别的,concat()不管遇到什么类型,直接连接成字符串,+
如果遇到数值类型会做运算,遇到字符(串)会连接字符(串)
concat
方法用于连接两个字符串,返回一个新字符串,不改变原字符串
var s1 = 'itbaizhan';
var s2 = 'sxt';
s1.concat(s2) // "itbaizhansxt"
s1 // "itbaizhan"
该方法可以接受多个参数,参数之间,
隔开
'sxt'.concat('itbaizhan', 'bjsxt') // "sxtitbaizhanbjsxt"
如果参数不是字符串,concat
方法会将其先转为字符串,然后再连接
var one = 1;
var two = 2;
var three = '3';
''.concat(one, two, three) // "123"
实时效果反馈
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
字符串方法_substring()
substring
方法用于从原字符串取出子字符串并返回,不改变原字符串。第一个参数表示子字符串的开始位置(包含),第二个位置表示结束位置(返回结果不含结束位置)
'itbaizhan'.substring(0, 2) // "it"
如果省略第二个参数,则表示子字符串一直到原字符串的结束
'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
字符串方法_substr()
substr
方法用于从原字符串取出子字符串并返回,不改变原字符串,跟substring
方法的作用相同
substr
方法的第一个参数是子字符串的开始位置(从0开始计算,包含开始位置),第二个参数是子字符串的长度
'itbaizhan'.substr(2, 7); // baizhan
如果省略第二个参数,则表示子字符串一直到原字符串的结束
'itbaizhan'.substr(2) // "baizhan"
如果第一个参数是负数,表示倒数计算的字符位置。如果第二个参数是负数,将被自动转为0,因此会返回空字符串
'itbaizhan'.substr(-7) // "baizhan"
'itbaizhan'.substr(4, -1) // ""
实时效果反馈
1. 下列字符串方法substr的应用,代码输出结果是:
'itbaizhan'.substr(2, -2)
A “”
B itbaizhan
C itbai
D bai
答案
1=>A
字符串方法_indexOf()
工作中常用
indexOf
方法用于确定一个字符串在另一个字符串中第一次出现的位置,返回结果是匹配开始的位置。如果返回-1
,就表示不匹配
'hello world'.indexOf('o') // 4
'itbaizhan'.indexOf('sxt') // -1
indexOf
方法还可以接受第二个参数,表示从该位置开始向后匹配(包含该位置)
'hello world'.indexOf('o', 6) // 7
实时效果反馈
1. 下列字符串方法indexOf的应用,代码输出结果是:
'itbaizhan'.indexOf("z",6)
A -1
B itbaizhan
C 0
D 5
答案
1=>A
字符串方法_trim()
trim
方法用于去除字符串两端的空格(不能去除中间的空格),返回一个新字符串,不改变原字符串
' hello world '.trim()
// "hello world"
该方法去除的不仅是空格,还包括制表符(\t
、\v
)、换行符(\n
)和回车符(\r
)
'\r\nitbaizhan \t'.trim() // 'itbaizhan'
ES6扩展方法,trimEnd()
和trimStart()
方法
" itbaizhan ".trimEnd(); // itbaizhan
" itbaizhan ".trimStart(); // 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
字符串方法_split()
split
方法按照给定规则分割字符串,返回一个由分割出来的子字符串组成的数组,不改变原字符串
'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
数组
数组(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
数组的遍历
- for循环或while循环
var a = ['sxt', 'baizhan', 'it'];
// for循环
for(var i = 0; i < a.length; i++) {
console.log(a[i]);
}
// while循环
var i = 0;
while (i < a.length) {
console.log(a[i]);
i++;
}
- for…in遍历数组
var a = ['sxt', 'baizhan', 'it'];
for (var i in a) {//i是数组下标/索引的值
console.log(a[i]);
}
- for…of遍历数组
for-of语句可以用来遍历可迭代对象
语法:
for(变量 of 可迭代的对象){//变量的值是元素值
语句…
}
执行流程:for-of的循环体会执行多次,数组中有几个元素就会执行几次,每次执行时都会将一个元素值赋值给变量
const arr = ["孙悟空", "猪八戒", "沙和尚", "唐僧"]
for(let value of arr){
console.log(value)
}
for(let value of "hello"){
console.log(value)
}
实时效果反馈
1. 下列遍历数组的操作中,画横线的地方应该填写的代码是:
var arr = ["尚学堂", 100, true];
for (var i = 0; i < ___; i++) {
console.log(arr[i]);
}
A arr
B 10
C length
D arr.length
答案
1=>D
数组方法
数组方法有很多,详见MDN_数组方法
- 静态方法:通过
类.方法
调用 - 实例方法:通过
实例/对象.方法
调用
除此之外,数组还有静态属性和实例属性
下面介绍常用的数组方法
数组静态方法_Array.isArray()
Array.isArray
方法返回一个布尔值,表示参数是否为数组。它可以弥补typeof
运算符的不足
因为typeof对于数组的返回值是object,所以用typeof()无法判断数组类型
var arr = ["尚学堂", 100, true];
console.log(typeof arr); // object
var arr = ['sxt', 'baizhan', 'it'];
Array.isArray(arr) // true
实时效果反馈
1. 下列代码输出分别是?:
var arr = ["尚学堂", 100, true];
var str = "itbaizhan";
console.log(typeof arr);
console.log(Array.isArray(arr));
console.log(typeof str);
console.log(Array.isArray(str));
A object false string false
B object true string true
C object true false false
D object true string false
答案
1=>D
数组方法_indexOf()
indexOf
从前往后搜索,方法返回给定元素在数组中第一次出现的位置,如果没有出现则返回-1
var arr = ['a', 'b', 'c'];
arr.indexOf('b') // 1
arr.indexOf('y') // -1
indexOf
方法还可以接受第二个参数,表示搜索的开始位置
['学', '序', 'i'].indexOf('学', 1) // -1
实时效果反馈
1. 下列关于数组方法,运行正确结果是:
var arr = ["尚学堂", "it", "itbaizhan","it"];
arr.indexOf("it",2)
A -1
B 1
C 3
D undefined
答案
1=>B
数组.lastIndexOf(数组元素,搜索起始索引)
lastIndexOf()
- 从后往前搜索
- 获取元素在数组中最后一次出现的位置
- 参数:
- 要查询的元素
- 查询的起始位置
- 返回值:找到了则返回元素的索引,没有找到返回-1
let arr = ["孙悟空", "猪八戒", "沙和尚", "唐僧", "沙和尚"]
console.log(arr.lastIndexOf("沙和尚", 3))//2,从唐僧开始往前搜索
at()
- 可以根据索引获取数组中的指定元素,这一点的作用与数组下标的中括号
[]
相同 - at可以接收负索引作为参数,中括号
[]
中的下标不能是负数,否则返回的是undefined
arr.at(-n)等效于arr(arr.length-n)
const arr = ["孙悟空", "猪八戒", "沙和尚", "唐僧"]
//以下2种方法等效
console.log(arr.at(-2))//打印数组倒数第二个元素
console.log(arr[arr.length - 2])//打印数组倒数第二个元素
数组方法_concat()
concat
方法用于多个数组的合并。它将新数组的成员,添加到原数组成员的后部,然后返回一个新数组,原数组不变
如果直接用+
连接数组,数组连接后会变成字符串
['hello'].concat(['world'])
// ["hello", "world"]
['hello'].concat(['world'], ['!'])
// ["hello", "world", "!"]
除了数组作为参数,concat
也接受其他类型的值作为参数,添加到目标数组尾部。
[1, 2, 3].concat(4, 5, 6)
// [1, 2, 3, 4, 5, 6]
应用场景
上拉加载,合并数据
实时效果反馈
1. 下列关于数组输出的结果是:
[1, 2, 3].concat(4, 5, 6,[7,8,9])
A [1, 2, 3, 4, 5, 6]
B [1, 2, 3, 4, 5, 6, [7, 8, 9]]
C [1, 2, 3, 4, 5, 6, 7, 8, 9]
D [1, 2, 3, 4, 5, 6][7, 8, 9]
答案
1=>C
数组方法_join()
join()
是将数组所有成员连接为一个以指定分隔符分隔的字符串返回。如果不提供参数,默认用英文逗号分隔,不改变原数组
- 注意不指定参数、指定参数为空、指定参数为空格这三个概念是不一样的,它们的运行结果也不一样
如下所示
var arr = ["1","2","3","4"];
console.log(arr.join());//不指定参数默认英文逗号`,`隔开
console.log(arr.join(""));//直接连接数组各成员,不间隔
console.log(arr.join(" "));//空格隔开
运行结果如下
var a = [1, 2, 3, 4];
a.join(' ') // '1 2 3 4'
a.join(' | ') // "1 | 2 | 3 | 4"
a.join() // "1,2,3,4"
如果数组成员是undefined
或null
或空位,会被转成空字符串
[undefined, null].join('#')
// '#'
['a',, 'b'].join('-')
// 'a--b'
数组的join
配合字符串的split
可以实现数组与字符串的互换
var arr = ["a","b","c"];
var myArr = arr.join("");
console.log(myArr);
console.log(myArr.split(""));
实时效果反馈
1. 下列关于数组输出的结果是:
var arr = ["尚学堂","百战程序员"];
var myArr = arr.join("");
console.log(myArr);
console.log(myArr.split(""));
A 尚学堂百战程序员 [“尚学堂”,“百战程序员”]
B 尚学堂百战程序员 [‘尚’, ‘学’, ‘堂’, ‘百’, ‘战’, ‘程’, ‘序’, ‘员’]
C [“尚学堂”,“百战程序员”] 尚学堂百战程序员
D [‘尚’, ‘学’, ‘堂’, ‘百’, ‘战’, ‘程’, ‘序’, ‘员’] 尚学堂百战程序员
答案
1=>B
数组方法_push()/pop()
push
方法用于在数组的末端添加一个或多个元素,并返回添加新元素后的数组长度。注意,该方法会改变原数组
var arr = [];
arr.push("尚学堂") // 1
arr.push('itbaizhan') // 2
arr.push(true, {}) // 4
arr // [尚学堂, 'itbaizhan', true, {}]
pop
方法用于删除数组的最后一个元素,并返回被删除的元素。注意,该方法会改变原数组
var arr = ['尚学堂', 'itbaizhan', 'WEB前端'];
arr.pop() // 'WEB前端'
arr // ['尚学堂', 'itbaizhan']
实时效果反馈
1. 下列关于数组输出的结果是:
var arr = [];
arr.push('尚学堂', 'itbaizhan');
arr.push('WEB前端');
arr.pop();
arr.push('尚学堂');
console.log(arr);
A ['尚学堂', 'itbaizhan', '尚学堂']
B ['尚学堂', 'itbaizhan', 'WEB前端']
C ['尚学堂', 'itbaizhan']
D ['尚学堂', 'WEB前端']
答案
1=>A
数组方法_shift()/unshift()
shift
方法用于删除数组的第一个元素,并返回该元素。注意,该方法会改变原数组。与pop类似
var arr = ['尚学堂', 'itbaizhan', 'WEB前端'];
arr.shift() // '尚学堂'
arr // ['itbaizhan', 'WEB前端']
shift
方法可以遍历并清空一个数组
var list = [1, 2, 3, 4, 5, 6];
var item;
while (item = list.shift()) {
console.log(item);
}
list // []
unshift
方法用于在数组的第一个位置添加一个或多个元素,并返回添加新元素后的数组长度。注意,该方法会改变原数组
var arr = ['尚学堂', 'itbaizhan', 'WEB前端'];
arr.unshift('baizhan'); // 4
arr // ['baizhan', '尚学堂', 'itbaizhan', 'WEB前端']
unshift
方法可以接受多个参数,这些参数都会添加到目标数组头部
var arr = [ '尚学堂', 'itbaizhan' ];
arr.unshift('WEB前端', 'baizhan') // 4
arr // [ 'WEB前端', 'baizhan', '尚学堂', 'itbaizhan' ]
实时效果反馈
1. 下列关于数组输出的结果是:
var arr = [];
arr.unshift('尚学堂', 'itbaizhan');
arr.unshift('WEB前端');
arr.shift();
arr.unshift('尚学堂');
console.log(arr);
A ['尚学堂', '尚学堂', 'itbaizhan']
B ['尚学堂', 'itbaizhan', 'WEB前端']
C ['尚学堂', 'itbaizhan']
D ['尚学堂', 'WEB前端']
答案
1=>A
数组方法_reverse()
reverse
方法用于颠倒排列数组元素,返回改变后的数组。注意,该方法将改变原数组
var a = ['a', 'b', 'c'];
a.reverse() // ["c", "b", "a"]
a // ["c", "b", "a"]
注意:字符串没有reverse()方法,字符串要实现反转排列,需利用split()转换成数组,再用reverse()翻转,最后用join()转换回字符串
var str = "hello";
str.split("").reverse().join("")
实时效果反馈
1. 下列关于数组输出的结果是:
var str = "hello";
str.split("").reverse().join("-");
A olleh
B hello
C ‘o-l-l-e-h’
D ‘h-e-l-l-o’
答案
1=>C
数组.slice()
slice()
- 用来截取数组(非破坏性方法),不影响原数组
- 参数:
- 截取的起始位置(包括该位置)
- 截取的结束位置(不包括该位置)
- 第二个参数可以省略不写,如果省略则会一直截取到最后
- 索引可以是负值
如果将两个参数全都省略,则可以对数组进行浅拷贝(浅复制)
arr = ["孙悟空", "猪八戒", "沙和尚", "唐僧"]
console.log(arr.slice(0, 2))
console.log(arr.slice(1, 3))
console.log(arr.slice(1, -1))
console.log(arr.slice())//浅拷贝
console.log(arr)
数组.sort()
sort()
- sort用来对数组进行排序(会对改变原数组)
- sort默认会将数组升序排列
- 注意sort默认会按照Unicode编码进行排序,所以如果直接通过sort对数字进行排序,可能会得到一个不正确的结果
- 对于数字组成的数组,可以传递一个回调函数作为参数,通过回调函数来指定排序规则
- (a, b) => a - b 升序排列
- (a, b) => b - a 降序排列
- 用sort()排序比我们自己写的排序函数性能要好,推荐使用sort()排序
let arr = ["a", "c", "e", "f", "d", "b"]
arr.sort()
console.log(arr)
arr = [2, 3, 1, 9, 0, 4, 5, 7, 8, 6, 10]
arr.sort()//在不传递回调函数作为参数时,sort()对数字类型的数组排序并不准确
console.log(arr)
//需要记住传递以下2种类型的回调函数来对数字型数组排序
arr.sort((a, b) => a - b)//升序排列
console.log(arr)
arr.sort((a, b) => b - a)//降序排列
console.log(arr)
数组.forEach()
forEach()
- 用来遍历数组
- 它需要一个回调函数作为参数,数组中有几个元素,回调函数就会被调用几次,每次调用,都会将数组中的数据作为参数传递
- 回调函数中有三个参数:
- element 当前的元素
- index 当前元素的索引
- array 被遍历的数组
arr = ["孙悟空", "猪八戒", "沙和尚", "唐僧"]
//回调函数中的参数名不固定,比如也可以是
// arr.forEach((a,b,c) => {
// console.log(c)//因为该数组有4个元素,所以打印4次数组
// })
arr.forEach((element,index,array) => {
console.log(array)//因为该数组有4个元素,所以打印4次数组
})
//打印索引和元素
arr.forEach((element, index) => console.log(index, element))
注意
//以下这种写法本来是想打印整个数组,结果打印的是数组元素
arr = ["孙悟空", "猪八戒", "沙和尚", "唐僧"]
arr.forEach((array) => {
console.log(array)//因为该数组有4个元素,所以打印4次数组
})
预期输出
实际输出
错因:受到了参数名的误导,以上代码中array并不是回调函数中代表数组的第3个参数,因为array前面没有其他参数,所以它应该是代表数组元素的第一个参数,所以打印的是数组元素,而不是预期的整个数组
数组.filter()
filter()
- 将数组中符合条件的元素保存到一个新数组中返回
- 需要一个回调函数作为参数,会为每一个元素去调用回调函数,返回值为真时将元素添加到新数组
- 非破坏性方法,不会影响原数组
- 回调函数中有三个参数:
- element 当前的元素
- index 当前元素的索引
- array 被遍历的数组
arr = [1, 2, 3, 4, 5, 6, 7, 8]
// 获取数组中的所有偶数
let result = arr.filter(ele => ele%2===0)
console.log(result)
result=arr.filter(ele=>ele>5)
console.log(result)
console.log(arr)//不破坏原数组
通过filter()生成的新数组元素来自于原数组,filter()的作用是筛选原数组中的元素保存到新数组
数组.map()
map()
- 根据当前数组生成一个新数组
- 需要一个回调函数作为参数,回调函数的返回值会成为新数组中的元素
- 非破坏性方法不会影响原数组
- 回调函数中有三个参数:
- element 当前的元素
- index 当前元素的索引
- array 被遍历的数组
arr = [1, 2, 3, 4, 5, 6, 7, 8]
result = arr.map((ele) => ele * 2)//新数组的元素是原数组元素的2倍
arr = ["孙悟空", "猪八戒", "沙和尚"]
result = arr.map((ele) => "<li>" + ele + "</li>")//将原数组的元素加入到列表中
数组.reduce()
reduce()
- 可以用来将一个数组中的所有元素整合为一个值
- 参数:
- 回调函数,通过回调函数来指定合并的规则
- 可选参数,表示初始值,即回调函数中第一个参数的初始值
arr = [1, 2, 3, 4, 5, 6, 7, 8]
/*首先数组前2个元素分别作为回调函数的参数a,b,然后再将回调函数的返回值作为a,
下一个数组元素作为参数b,以此类推,直至数组最后一个元素作为作为b*/
result = arr.reduce((a, b) => {//求数组所有元素的和
/*
1, 2
3, 3
6, 4
10, 5
······
*/
console.log(a, b)
return a + b
})
console.log(result)
console.log("---------")
result = arr.reduce((a, b) => {//a的初始值为10
console.log(a, b)
return a + b}, 10)//10是reduce()的第2个参数
console.log(result)//这个结果比上个结果多10
console.log("---------")
result=arr.reduce((a,b)=>a*b)//求数组所有元素的乘积,利用这个函数可以实现阶乘
console.log(result)
数组的解构赋值
解构赋值:顾名思义就是将其拆开分别赋值
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
// 解构数组时,可以使用...来设置获取多余的元素。这里剩下的值赋值给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)
数组的复制
对象的复制一定是产生了新的对象
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指向同一个地址,没有新建独立的对象,所以不是复制
运行结果
const arr3 = arr.slice()// 当调用slice时,会产生一个新的数组对象,从而完成对数组的复制
console.log(arr3)
arr3[0] = "唐僧"
console.log(arr)//未被修改
console.log(arr3)
运行结果
浅拷贝与深拷贝
未拷贝的存储情况
浅拷贝(shallow copy)
- 通常对对象的拷贝都是浅拷贝
- 浅拷贝只复制一层
- 如果对象中存储的数据是原始值,那么无拷贝深浅之分
- 浅拷贝只会对对象本身进行复制,不会复制对象中的属性(或元素),这样可以节约存储,提高性能
浅拷贝的存储情况
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也被修改
利用...
展开运算符也能实现浅复制
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)
- 深拷贝指不仅复制对象本身,还复制对象中的属性和元素
- 因为性能问题,通常情况不太使用深拷贝
深拷贝存储情况
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:"猪八戒"}]
对象的复制
对象的复制
- 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}
使用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))
函数
函数是一段可以反复调用的代码块
函数的声明
function 命令: function命令声明的代码区块,就是一个函数。function命令后面是函数名,函数名后面是一对圆括号,里面是传入函数的参数。函数体放在大括号里面。
函数的定义方式:
- 函数声明
function 函数名(){
语句…
} - 函数表达式
const/var/let 函数名 = function(){
语句…
} - 箭头函数
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() {}
函数参数
- 参数:
- 如果实参和形参数量相同,则对应的实参赋值给对应的形参
- 如果实参多于形参,则多余的实参不会使用
- 如果形参多于实参,则多余的形参为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)
函数作为参数
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("我是箭头函数"))
arguments对象
- arguments用来存储函数的实参,无论用户是否定义形参,实参都会存储到arguments对象中,可以通过该对象直接访问实参
- arguments是函数中又一个隐含参数
function fn(){
console.log(arguments)
}
fn()
- 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)
- 通过arguments,可以不受参数数量的限制(即调用时可以传入任意数量的参数)从而更加灵活地创建函数
例如实现一个求和函数
function sum() {
let result = 0
for (let num of arguments) {
result += num
}
return result
}
这样可以实现传入任意数量的参数求和,而不是固定数量的参数求和
不足之处:arguments只是类数组对象,不能调用Array方法,不能配合其他参数使用,而且在调用不是我们自己定义的函数时我们不知道该函数是否是不需要传参的函数
可变参数
为了弥补arguments的不足之处,现在更常用的是可变参数
可变参数,在定义函数时可以将参数指定为可变参数
格式:function(...args){函数体}
- 可变参数可以接收任意数量实参,并将他们统一存储到一个数组中返回
- 可变参数的作用和arguments基本是一致,但是也具有一些不同点:
- 可变参数的名字可以自己指定
- 可变参数就是一个数组,可以直接使用数组的方法
- 可变参数可以配合其他参数一起使用
- 当可变参数和普通参数一起使用时,需要将可变参数写到最后
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")
函数的作用域
函数的作用域,在函数创建时就已经确定了(词法作用域),和调用的位置无关
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()
函数返回值
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()
橙色输出表示函数的返回值
闭包
问题:如何创建一个函数,使得第一次调用时打印1,第二次调用打印2,以此类推。
答:可以利用函数的闭包
闭包:闭包是一个函数,闭包就是能访问到函数外部作用域中变量的函数
什么时候使用:当我们需要隐藏一些不希望被外部访问的内容时就可以使用闭包来把变量藏到外层函数里
构成闭包的要件:
- 函数的嵌套
- 内部函数要引用外部函数中的变量
- 内部函数要作为返回值返回
function outer(){
let num = 0 // 位于函数作用域中
return () => {
num++
console.log(num)
}
}
const newFn = outer()
console.log(newFn)
闭包的生命周期:
- 闭包在外部函数调用时产生,外部函数每次调用都会产生一个全新独立的闭包
- 在内部函数丢失时销毁(内部函数被垃圾回收了,闭包才会消失)
function outer2(){
let num = 0
return () => {
num++
console.log(num)
}
}
let fn1 = outer2() // 外部函数被调用,创建独立闭包
let fn2 = outer2() // 外部函数被调用,创建独立闭包
fn1 = null//闭包销毁
fn2 = null//闭包销毁
注意事项:闭包主要用来隐藏一些不希望被外部访问的内容,因为每次调用外部函数时都会产生一个全新独立的闭包,这就意味着闭包需要占用一定的内存空间
相较于类来说,闭包比较浪费内存空间(因为类可以使用原型存储共用的属性和方法,而闭包不能)
- 需要执行次数较少时,使用闭包
- 需要大量创建实例时,使用类
由于js中类的this不太稳定,遇到以上情况更多的是使用闭包
代码调试debug
- 可以用debugger关键字设置断点
- 可以通过浏览器选项检查->源代码里查看代码运行过程。在右侧有监测变量、查看作用域等功能。可以点击行号左侧设置断点
- 页面代码未执行完时标题栏显示表示加载中的转圈
严格模式
JS运行代码的模式有两种:
- 正常模式:默认情况下代码都运行在正常模式中,在正常模式,语法检查并不严格。它的原则是能不报错的地方尽量不报错。这种处理方式导致代码的运行性能较差
- 严格模式
- 在严格模式下,语法检查变得严格
1.禁止一些语法
2.更容易报错
3.提升了性能
在开发中,应该尽量使用严格模式,这样可以将一些隐藏的问题消灭在萌芽阶段,同时也能提升代码的运行性能
用"use strict"
开启严格模式
- 在严格模式下,语法检查变得严格
"use strict" // 全局的严格模式,写在js代码的最前面
let a = 10
console.log(a)
function fn(){
"use strict" //如果只想在特定区域使用严格模式,可以在该代码块的开头添加`"use strict"`,比如函数的严格的模式在左花括号后添加严格模式的关键字
//代码块
}
对象
面向对象编程(OOP)
- 程序是干嘛的?
-程序就是对现实世界的抽象(照片就是对人的抽象) - 对象是干嘛的?
- 一个事物抽象到程序中后就变成了对象
- 在程序的世界中,一切皆对象
- 面向对象的编程
- 面向对象的编程指,程序中的所有操作都是通过对象来完成
- 做任何事情之前都需要先找到它的对象,然后通过对象来完成各种操作
对象(object)是 JavaScript 语言的核心概念,也是最重要的数据类型
简单说,对象就是一组**“键值对”(key-value)的集合**,是一种无序的复合数据集合
//创建对象user1,user2,user3
var user1=new Object();
var user2=Object();
let mySymbol = Symbol()
let user3 = {
name:"孙悟空",
age:18,
["gender"]:"男",
[mySymbol]:"特殊的属性",
hello:{
a:1,
b:true
}
}
对象的每一个键名又称为“属性”(property),它的“键值”可以是任何数据类型,比如数组,对象,函数,字符串,布尔类型等。
如果一个属性的值为函数,通常把这个属性称为“方法”,它可以像函数那样调用
函数和方法是同一个意思
var user = {
getName: function (name) {
return name;
}
};
user.getName("it")
var obj=new Object(); //创建对象
obj.name = "孙悟空" //向对象中添加属性:对象.属性名 = 属性值
obj.age = 18//创建属性方法一
obj["gender"] = "男"//创建属性方法二
let str = "address"
obj[str] = "花果山" // 等价于 obj["address"] = "花果山",使用[]操作属性时可以用变量,变量就不需要加引号了
obj.name="Tom Sun" // 修改属性
delete obj.name // 删除属性,delete是运算符
obj.f = Object()//对象的属性值可以是对象
obj.f.name = "猪八戒"
obj.f.age = 28
console.log(obj)
console.log("name" in obj)//用in运算符来检查对象中是否含有某个属性,语法: 属性名 in obj,如果有返回true,没有返回false
运行结果
属性名
通常属性名就是一个字符串,没有变量命名的限制,所以可以用关键字命名,也可以数字开头
但是如果属性名有太多特殊字符,不能直接使用,需要使用[""]
来设置
虽然如此,但强烈建议属性名也按照标识符的规范命名,建议使用驼峰命名法
var obj=new Object();
obj.name='Code6E';
obj.if="以if关键字命名属性"//不建议
obj["$*&^#%()"]="特殊字符的属性名";//不建议
console.log(obj["$*&^#%()"]);//控制台输出"特殊字符的属性名"
运行结果
也可以用symbol()作为属性名,获取这种属性时,也必须使用symbol, 使用symbol添加的属性,通常是那些不希望被外界访问的属性
let mySymbol = Symbol()
let newSymbol = Symbol()
obj[mySymbol] = "通过symbol添加的属性"
console.log(obj[mySymbol])//mySymbol相当于一把钥匙,只能通过mySymbol去读取symbol()属性的值
console.log(obj[newSymbol])//取不到结果,因为只能用创建symbol()属性时的变量名取symbol属性值
如果属性的值还是一个对象,就形成了链式引用
var user = {
name:"itbaizhan",
age:13,
container:{
frontEnd:["Web前端","Android","iOS"],
backEnd:["Java","Python"]
}
}
user.container.frontEnd // ["Web前端","Android","iOS"]
枚举对象中的属性
枚举属性,指将对象中的所有的属性全部获取
- 语法:
for(let 变量 in 对象){
语句…
} - for-in的循环体会执行多次,有几个属性就会执行几次,每次执行时,都会将一个属性名赋值给我们所定义的变量
- 注意:并不是所有的属性都可以枚举,比如使用符号添加的属性不能被枚举
let obj = {
name:'孙悟空',
age:18,
gender:"男",
address:"花果山",
[Symbol()]:"测试的属性" // 符号添加的属性不能被枚举
}
for(let propName in obj){
console.log(propName, obj[propName])//不能写成obj.propName,这样写会显示undefined,因为.不能识别变量
}
运行结果
对象的解构赋值
对象的解构赋值与数组类似,但有些语法需要注意
const obj = { name: "孙悟空", age: 18, gender: "男" }
// 声明变量同时解构对象
let { name, age, gender } = obj
// 或 声明与解构赋值分开写
// let name, age, gender
// 分开写时为避免被JS解析器误认为是给语句块赋值而报错,可以在花括号外加小括号来避免这种误解
// ({ name, age, gender } = obj)
// 没有的属性返回undefined
let { address } = obj
console.log(name, age, gender)
// 这里取了别名。address:d="花果山"表示先在obj中寻找address属性,如果找到了就赋值给d,没有找到就取默认值"花果山"给d
let {name:a, age:b, gender:c, address:d="花果山"} = obj
console.log(a, b, c, d)
对象的序列化
对象的序列化
- JS中的对象使用时都是存在于计算机的内存中的
- 序列化指将对象转换为一个可以存储的格式,在JS中对象的序列化通常是将一个对象转换为字符串(JSON字符串)
- 序列化的用途(对象转换为字符串有什么用):
- 对象转换为字符串后,可以将字符串在不同的语言之间进行传递,甚至人可以直接对字符串进行读写操作,使得JS对象可以在不同的语言之间传递
- 用途:
- 作为数据交换的格式
- 用来编写配置文件
- 如何进行序列化:
- 在JS中有一个工具类 JSON (JavaScript Object Notation) JS对象表示法
- JS对象序列化后会转换为一个字符串,这个字符串我们称其为JSON字符串
- 也可以手动的编写JSON字符串,很多程序的配置文件就是使用JSON编写的
- 编写JSON的注意事项:
- JSON字符串有两种类型:
JSON对象 ‘{}’
JSON数组 ‘[]’ - JSON字符串的属性名必须使用双引号引起来(因为不是所有语言支持单引号引出字符串,但几乎都支持双引号引出字符串)。因为内部属性用了双引号,为了不发生双引号的配对错误,整个JSON字符串一般就用单引号引起
- JSON中可以使用的属性值(元素),一般是所有语言都支持的纯数据,因为函数不是纯数据,其他语言并不是都有bigint、NAN、undefined,所以它们不能作为转化为JSON字符串的对象中的属性值
- 数字(Number)
- 字符串(String) 必须使用双引号
- 布尔值(Boolean)
- 空值(Null)
- 对象(Object {})
- 数组(Array [])
- JSON的格式和JS对象的格式基本上一致的,
注意:JSON字符串如果属性是最后一个,则不要再加逗号,
- JSON字符串有两种类型:
- JSON.stringify() 可以将一个对象转换为JSON字符串
- JSON.parse() 可以将一个JSON格式的字符串转换为JS对象
const obj = {
name: "孙悟空",
age: 18,
}
// 将obj转换为JSON字符串
const str = JSON.stringify(obj) //JSON.stringify() 可以将一个对象转换为JSON字符串
const obj2 = JSON.parse(str) // JSON.parse() 可以将一个JSON格式的字符串转换为JS对象
console.log(obj)
console.log(str) // {"name":"孙悟空","age":18}
console.log(obj2)
const str2 = '["hello", true, []]'
console.log(str2)
对象的存储
- 原始值都属于不可变类型,一旦创建就无法修改
- 在内存中不会创建重复的原始值
let a = 10
let b = 10
a = 12 // 当我们为一个变量重新赋值时,绝对不会影响其他变量, 只是改变了变量中存储的地址
console.log("a =", a)
console.log("b =", b)
- 对象属于可变类型
- 当对两个对象进行相等或全等比较时,比较的是对象的内存地址
- 如果有两个变量同时指向一个对象,通过一个变量修改对象时,对另外一个变量也会产生影响
let obj = Object()
obj.name = "孙悟空"
obj.age = 18
let obj2 = Object()
let obj3 = Object()
console.log(obj2 == obj3) // false
let obj4 = obj
console.log(obj === obj4)//true
obj4.name = "猪八戒" // 当修改一个对象时,所有指向该对象的变量都会收到影响
console.log("obj", obj)
console.log("obj4", obj4)
在使用变量存储对象时,很容易因为改变变量指向的对象,提高代码的复杂度, 所以多数情况下,声明存储对象的变量时会使用const(是否使用const声明对象看具体需求)
const只是禁止变量被重新赋值,不影响修改对象
const obj={
name:"孙悟空"};
var obj2=obj;
obj2.name="猪八戒";//修改对象
console.log(obj);
var obj3={};
obj=obj3;//报错
实时效果反馈
1. 下列关于对象描述正确的是:
A 对象是条件语句,负责进行判断
B 对象是一段反复调用的代码块
C 对象就是一组“键值对”(key-value)的集合
D 对象是按次序排列的一组值。每个值的位置都有编号(从0开始)
答案
1=>C
B是对函数的描述,D是对数组的描述
this对象
- 函数在执行时,JS解析器每次都会传递进一个隐含的参数,这个参数就叫做 this
- this会指向一个对象,this所指向的对象会根据函数调用方式的不同而不同
- 以函数形式调用时,this指向的是window
- 以方法的形式调用时,this指向的是调用方法的对象
- 通过this可以在方法中引用调用方法的对象
function fn() {
// console.log(this === window)
console.log("fn打印", this)
}
const obj = { name: "孙悟空" }
obj.test = fn
const obj2 = { name: "猪八戒", test: fn }
fn()//这里以函数形式调用函数且fn()方法属于window对象,所以这里this指向window对象
window.fn()//这里以函数形式调用函数。这种写法与fn()等效
obj.test() // {name:"孙悟空",test:fn()}
obj2.test() // {name:"猪八戒", test:fn()}
const obj3 = {
name: "沙和尚",
sayHello: function () {
console.log(this.name)
},
}
const obj4 = {
name: "唐僧",
sayHello: function(){
console.log(this.name)
}
}
// 为两个对象添加一个方法,可以打印自己的名字
obj3.sayHello()
obj4.sayHello()
箭头函数的this
箭头函数的this由外层作用域决定,和它的调用方式无关
function fn() {
console.log("fn -->", this)
}
const fn2 = () => {
console.log("fn2 -->", this) // 总是window
}
fn() // window
fn2() // window
const obj = {
name:"孙悟空",
fn, // fn:fn,对象的方法可以省略属性名而直接写函数
fn2,
sayHello(){
console.log(this.name)
// function t(){
// console.log("t -->", this)
// }
// t()
const t2 = () => {
console.log("t2 -->", this)
}
t2()
}
}
obj.fn() // obj
obj.fn2() // window
obj.sayHello()
call()与apply()
根据函数调用方式的不同,this的值也不同:
- 以函数形式调用,this是window
- 以方法形式调用,this是调用方法的对象
- 构造函数中,this是新建的对象
- 箭头函数没有自己的this,由外层作用域决定
- 通过call和apply调用的函数,它们的第一个参数就是函数的this
function fn() {
console.log("函数执行了~", this)
}
// 以函数形式调用,this是window
fn()
const obj = { name: "孙悟空", fn }
// 以方法形式调用,this是调用方法的对象
obj.fn()
调用函数除了通过函数名()
这种形式外,还可以通过其他的方式来调用函数
比如,我们可以通过调用函数的call()或apply()方法来调用函数
格式:函数.call()
或函数.apply()
- call 和 apply除了可以调用函数,还可以用来指定函数中的this
- call和apply的第一个参数,将会成为函数的this
- 通过call方法调用函数,函数的实参直接在第一个参数后一个一个地列出来
- 通过apply方法调用函数,函数的实参需要通过一个数组传递
fn.call(obj)
fn.apply(obj)
function fn2(a, b) {
console.log("a =", a, "b =", b, this)
}
fn2.call(obj, "hello", true)
fn2.apply(obj, ["hello", true])
平常不用指定函数的this对象时就以函数名()
形式直接调用函数,如果需要指定不是函数默认的this对象,就用函数名().call()
或函数名().apply()
来调用对象,用call()还是apply()就看参数类型,如果参数是需要自己一个个指定就用call(), 如果参数是数组就用apply()
bind()
bind() 是函数的方法,可以用来创建一个新的函数, 不影响原来的函数
- bind可以为新函数绑定this(绑定后不可修改)
- bind可以为新函数绑定参数(绑定后不可修改)
function fn(a, b, c) {
console.log("fn执行了~~~~", this)
console.log(a, b, c)
}
const obj = {name:"孙悟空"}
// newFn()的this被指定为obj,参数固定为10,20,30, 后期无法修改。不影响原来的函数
const newFn1 = fn.bind(obj, 10, 20, 30)
newFn1()
const newFn2 = fn.bind(obj, 10)
newFn2()
箭头函数没有自身的this,它的this由外层作用域决定,
- 也无法通过call apply 和 bind修改箭头函数的this(因为它本身没有this)
- 箭头函数中没有arguments
//这里箭头函数的this为外层作用域的this,即window()
const arrowFn = () => {
console.log(this)
}
// 无法修改
arrowFn.call(obj)
// 无法修改
const newArrowFn = arrowFn.bind(obj)
newArrowFn()
class MyClass {
fn = () => {
console.log(this)
}
}
//这里箭头函数的this为外层作用域的this,即实例mc的this
const mc = new MyClass()
// 无法修改
mc.fn.call(window)
Map对象
- Map用来存储键值对结构的数据(key-value),之前也提过键在对象中是唯一的
- Object中存储的数据就可以认为是一种键值对结构
- Map和Object的主要区别:
- Object中的属性名只能是字符串或符号(Symbol()),如果传递了一个其他类型的属性名,JS解释器会自动将其转换为字符串
- Map中任何类型的值都可以称为数据的key
const obj2 = {}
// Object()对象的属性名默认是省略了引号,特殊属性名需要用中括号`[]`括起
const obj = {
"name":"孙悟空",
'age':18,
[Symbol()]:"哈哈",
[obj2]:"嘻嘻"// 因为Object()对象的属性名(键)不能是对象,这里会将属性名转换成[object Object]
}
console.log(obj)
// 以下不管[]内是什么样的对象,一律会转换成字符串"object Object",所以都能读取到属性值"嘻
console.log(obj[obj2])
console.log(obj[{}])
创建Map对象:new Map()
属性和方法:
- map.size() 获取map中键值对的数量
- map.set(key, value) 向map中添加键值对
- map.get(key) 根据键key获取值, 不是通过
map.key
或map[key]
获取值,否则会返回undefined - map.delete(key) 删除指定数据
- map.has(key) 检查map中是否包含指定键
- map.clear() 删除全部的键值对
// 创建一个Map
const map = new Map()
map.set("name", "孙悟空")
map.set(obj2, "呵呵")
map.set(NaN, "哈哈哈")
map.delete(NaN)
// map.clear()
console.log(map)
console.log(map.get(obj2))
console.log(map.has("name"))
- 将map转换为数组
arr = Array.from(map)
或arr = [...map]
const map = new Map()
map.set("name", "孙悟空")
map.set("age", 18)
map.set({}, "呵呵")
// 将map转换为数组
// const arr = Array.from(map) // [["name","孙悟空"],["age",18],[{},"呵呵"]]
const arr = [...map]
console.log(arr)
- 可以用二维数组去创建Map对象
- map.keys() - 获取map的所有的键key
- map.values() - 获取map的所有的值value
- map.entries() -获取map的所有的键值对entry
//可以用二维数组去创建Map对象
const map2 = new Map([
["name", "猪八戒"],
["age", 18],
[{}, () => {}],
])
console.log(map2)
- 遍历Map对象
const map = new Map()
map.set("name", "孙悟空")
map.set("age", 18)
map.set({}, "呵呵")
// 将map对象的每一个键值对解构分别赋值给key,value
for (const [key, value] of map) {
// const [key, value] = entry
console.log(key, value)
}
// 可以用数组的forEach(element, index, array)方法遍历Map对象,对于Map对象forEach(value, key, map)它的参数分别为第一个参数表示值,第二个参数表示键,这里没有写出的第三个参数表示遍历的Map对象本身
map.forEach((value, key)=>{
console.log(value, key)
})
for(const key of map.keys()){
console.log(key)
Set对象
- Set用来创建一个集合
- 它的功能和数组类似,不同点在于Set中不能存储重复的数据
- Set对象本质上是键值对相同的Map对象,因为键不能重复,所以Set中没有重复的元素
创建Set对象
new Set()
new Set(数组)
Set对象的属性与方法- size 元素数量
- add() 添加元素
- has() 检查元素
- delete() 删除元素
// 创建一个Set
const set = new Set()
// 向set中添加数据
set.add(10)
set.add("孙悟空")
set.add(10)//重复的10加不进去
console.log(set)
// 遍历Set对象的元素
for(const item of set){
console.log(item)
}
// Set对象本质上是键值对相同的Map对象, Set对象可以使用一些(不是全部)Map对象的方法
for(const item of set.values()){
console.log(item)
}
// 将set转换成数组
const arr = [...set]
console.log(arr)
// 可以通过Set对象实现数组去重。思路:重复的数组->通过Set()函数转换成没有重复元素的Set对象->再把该
Set对象转换成数组
const arr2 = [1,2,3,2,1,3,4,5,4,6,7,7,8,9,10]
const set2 = new Set(arr2)
console.log([...set2])
Window对象
- 在浏览器中,浏览器为我们提供了一个window对象,可以直接访问
- window对象代表的是浏览器窗口,通过该对象可以对浏览器窗口进行各种操作,除此之外window对象还负责存储JS中的内置对象(如Number())和浏览器的宿主对象(如console,document)
- window对象的属性可以通过window对象访问,也可以直接访问。例如
window.alert(123)
与alert(123)
等效,window.console.log("哈哈")
与console.log("哈哈")
等效 - 函数可以认为是window对象的方法
- 用var声明的全局变量可以认为是window对象的属性
alert(window)
运行结果
window.a = 10 // 向window对象中添加的属性会自动成为全局变量(即在最外层用var声明的变量)
console.log(a)//10
let b=33
console.log(window.b)//控制台输出undefined, 使用let声明的变量不会存储在window对象中,而存在一个秘密的小地方(无法通过window.c访问)
function fn2(){
//var d = 10 // var虽然没有块作用域,但有函数作用域
d = 10 //在局部作用域中,如果没有使用var或let声明变量,相当于window.d, 所以变量会自动成为window对象的属性, 也就是全局变量
}
fn2()
console.log(d)//10
Math对象
Math是 JavaScript 的原生对象,提供各种数学功能。
- Math是一个工具类,不是构造函数,只是能使用Math提供的方法,即不能通过new Math()来创建Math()对象,
let math= new Math()
这样写会报错。 - Math中为我们提供了数学运算相关的一些常量和方法
- 常量:
Math.PI 圆周率 - 方法:
Math.abs() 求一个数的绝对值
Math.min() 求多个值中的最小值
Math.max() 求多个值中的最大值
Math.pow() 求x的y次幂
Math.sqrt() 求一个数的平方根
Math.floor() 向下取整
Math.ceil() 向上取整
Math.round() 四舍五入取整
Math.trunc() 直接去除小数位
Math.random() 生成一个0-1之间的随机数
Math.abs()
Math.abs
方法返回参数值的绝对值
Math.abs(1) // 1
Math.abs(-1) // 1
Math.max(),Math.min()
Math.max
方法返回参数之中最大的那个值,Math.min
返回最小的那个值。如果参数为空, Math.min
返回Infinity
, Math.max
返回-Infinity
。
Math.max(2, -1, 5) // 5
Math.min(2, -1, 5) // -1
Math.min() // Infinity
Math.max() // -Infinity
Math.floor(),Math.ceil()
Math.floor
方法返回小于参数值的最大整数,floor原意是地板
Math.floor(3.2) // 3
Math.floor(-3.2) // -4
Math.ceil
方法返回大于参数值的最小整数, ceil原意是天花板
Math.ceil(3.2) // 4
Math.ceil(-3.2) // -3
Math.random()
Math.random()
返回0到1之间的一个伪随机数,0=<Math.random()<1
Math.random() // 0.28525367438365223
任意范围的随机数生成函数如下
function getRandomArbitrary(min, max) {
return Math.random() * (max - min) + min;
}
getRandomArbitrary(5, 10)
实时效果反馈
1. 下列代码获得一个非负整数,横线处应该填写的内容是:
function ToInteger(x) {
x = Number(x);
return ___(___(x));
}
ToInteger(-10.4); // 向下取整:10
A Math.floor
Math.abs
B Math.ceil
Math.abs
C Math.ceil
Math.min
D Math.floor
Math.min
答案
1=>A
Date对象
详情参考MDN
Date
对象是 JavaScript 原生的时间库。它以1970年1月1日00:00:00作为时间的零点,可以表示的时间范围是前后各1亿天(单位为毫秒)
1s=1000ms
在JS中所有的和时间相关的数据都由Date对象来表示
时间戳
因为时间的单位与进制比较多,为了便于计算,计算机底层使用的都是时间戳
时间戳是指格林威治时间1970年01月01日00时00分00秒(相当于北京时间1970年01月01日08时00分00秒)起至现在的总毫秒数。
格林威治和北京时间就是时区的不同
Unix是20世纪70年代初出现的一个操作系统,Unix认为1970年1月1日0点是时间纪元。JavaScript也就遵循了这一约束
Date对象的方法
- 实例.getTime():返回实例距离1970年1月1日00:00:00 UTC 的毫秒数
Date.now()
:返回当前时间距离1970年1月1日 00:00:00 UTC 的毫秒数- 实例.getDate():返回实例对象对应每个月的几号(从1开始)
- 实例.getDay():返回星期几,星期日为0,星期一为1,以此类推
- 实例.getFullYear():返回四位的年份
- 实例.getMonth():返回月份的索引(0表示1月,11表示12月)
- 实例.getHours():返回小时(0-23)
- 实例.getMilliseconds():返回毫秒(0-999)
- 实例.getMinutes():返回分钟(0-59)
- 实例.getSeconds():返回秒(0-59)
如果想创建一个指定时间的Date对象,可以以let d = new Date(时间字符串/时间参数)
的形式创建
字符串格式有:月/日/年 时:分:秒
、年-月-日T时:分:秒
Date()函数的参数分别为:Date(年, 月, 日, 时, 分, 秒, 毫秒)
示例代码如下
d = new Date("2019-12-23T23:34:35")
d = new Date("12/23/2019 23:34:35")
d = new Date(2019, 11, 23, 23, 34, 35)//注意参数形式时月份是从0开始,0对应1月,1对应2月,以此类推... 个人喜欢这种写法,不容易搞混
let date = new Date() // 直接通过new Date()创建时间对象时,实例对象存储的时间是被创建的时间,即该条语句执行的时间
var d = new Date('January 6, 2022');
d.getDate() // 6
d.getMonth() // 0
d.getYear() // 122
d.getFullYear() // 2022
编写函数获得本年度剩余天数
function leftDays() {
var today = new Date();
var endYear = new Date(today.getFullYear(), 11, 31, 23, 59, 59, 999);//年,月,日,时,分,秒,毫秒
var msPerDay = 24 * 60 * 60 * 1000;//一天的毫秒数
return Math.round((endYear.getTime() - today.getTime()) / msPerDay);//endYear-today得到的是秒数,不是标准的时间戳,所以要用getTime函数
}
实时效果反馈
1. 下列代码计算本年度剩余天数,划横线处应该填写代码是:
function leftDays() {
var today = new Date();
var endYear = new Date(today.___, 11, 31, 23, 59, 59, 999);
var msPerDay = 24 * 60 * 60 * 1000;
return Math.round((endYear.___ - today.___) / msPerDay);
}
A getTime() getDate() getTime()
B getTime() getTime() getDate()
C getFullYear() getTime() getDate()
D getFullYear() getTime() getTime()
答案
1=>D
日期的格式化
上面通过getDate()、getDay()、getFullYear、getMonth()、getHours()、toLocaleString()等方法获取当地日期时间再转化成相应的时间格式比较麻烦。Date对象内置了获取指定格式日期时间的方法
这里只列出一部分,详见MDN
toLocaleDateString():方法返回指定日期对象日期部分的字符串,该字符串格式因不同语言而不同。
toLocaleTimeString():返回指定日期对象时间部分的字符串,该字符串格式因不同语言而不同。
toLocaleString():返回指定日期对象的日期时间的字符串,该字符串格式因不同语言而不同
功能:可以将一个日期转换为本地时间格式的字符串
- 参数:
- 描述语言和国家信息的字符串
zh-CN 中文中国
zh-HK 中文香港
zh-TW 中文台湾
en-US 英文美国 - 需要一个对象作为参数,在对象中可以通过对象的属性来对日期的格式进行配置
dateStyle 日期的风格
timeStyle 时间的风格
full
long
medium
short
hour12 是否采用12小时值
true
false
weekday 星期的显示方式
long
short
narrow
year
numeric
2-digit
- 描述语言和国家信息的字符串
const d = new Date()
let result1 = d.toLocaleDateString() // 将日期转换为本地格式的字符串
console.log(result1)
result1 = d.toLocaleTimeString() // 将时间转换为本地格式的字符串
console.log(result1)
result2 = d.toLocaleString("zh-CN", {
//对象参数中没写相应的属性则不会显示相应的时间,比如没写有关年份的属性则不会显示年份
year: "numeric",
month: "long",
day: "2-digit",
weekday: "short",
})
console.log(result2)
类
类与对象的关系:类是对象的模板/抽象,对象是类的实例/具体
使用Object创建对象的问题:
- 无法区分出不同类型的对象
- 不方便批量创建对象
在JS中可以通过类(class)来解决这个问题: - 将对象中的属性和方法直接定义在类中,就可以直接通过类来创建对象
- 通过同一个类创建的对象,我们称为同类对象。可以使用instanceof来检查一个对象是否是由某个类创建
语法:
- 创建类
class 类名 {} // 类名要使用大驼峰命名
或
const 类名 = class {} - 通过类创建对象
let 变量名 = new 类()
// const Person = class {}
// Person类专门用来创建人的对象
class Person{
}
// Dog类式专门用来创建狗的对象
class Dog{
}
const p1 = new Person() // 调用构造函数创建对象
const p2 = new Person()
const d1 = new Dog()
const d2 = new Dog()
console.log(p1 instanceof Person) // true
console.log(d1 instanceof Person) // false
类的属性
类的代码块,默认就是严格模式。类的代码块是用来设置对象的属性的,不是什么代码都能写。比如在类中不能写let b;
类中的属性分为:
- 实例属性:实例属性只能通过实例访问
- 类属性:使用static声明的属性,是静态属性(类属性)。静态属性只能通过类去访问
class Person{
name = "孙悟空" // Person的实例属性name p1.name
age = 18 // 实例属性只能通过实例访问 p1.age
static test = "test静态属性" // 使用static声明的属性,是静态属性(类属性) Person.test
static hh = "静态属性" // 静态属性只能通过类去访问 Person.hh
}
const p1 = new Person()
const p2 = new Person()
console.log(p1)
console.log(p2)
类的方法
同样的,类的方法有实例方法和静态方法(类方法)
class Person{
name = "孙悟空"
// sayHello = function(){
// } // 添加方法的一种方式
sayHello(){
console.log('实例方法' + this)
} // 实例方法,通过实例来调用,实例方法中this就是当前实例
static test(){
console.log("我是静态方法", this)
} // 静态方法(类方法),用static声明,通过类来调用,静态方法中this指向的是当前类
}
const p1 = new Person()
// console.log(p1)
Person.test()
p1.sayHello()
构造函数
当我们在类中直接指定实例属性的值时,意味着我们创建的所有对象的属性都是这个值
class Person{
name="孙悟空"
age=18
gender="男"
sayHello(){
console.log(this.name)
}
}
const p1 = new Person()
const p2 = new Person()
const p3 = new Person()
//实例的属性都相同,这样不够灵活
console.log(p1)
console.log(p2)
console.log(p3)
这时可以在类中添加构造函数constructor(),构造函数的名字是固定的。构造函数会在我们调用类创建对象时执行
class Person{
constructor(name, age, gender){
// console.log("构造函数执行了~", name, age, gender)
// 可以在构造函数中,为实例属性进行赋值
// 在构造函数中,this表示当前所创建的对象
this.name = name
this.age = age
this.gender = gender
}
}
const p1 = new Person("孙悟空", 18, "男")
const p2 = new Person("猪八戒", 28, "男")
const p3 = new Person("沙和尚", 38, "男")
console.log(p1)
console.log(p2)
console.log(p3)
封装
面向对象的特点:封装、继承和多态
封装主要用来保证数据的安全
“装”:对象就是一个用来存储不同属性的容器
“封”:对象不仅存储属性,还要负责数据的安全
直接添加到对象中的属性,并不安全,因为它们可以被任意的读取与修改
如何确保数据的安全:
1.私有化数据:将需要保护的数据设置为私有,只能在类内部使用。格式为在变量前加#
,私有变量需要在类内部先声明才能使用
2.提供setter和getter方法来开放和控制对数据的操作
属性设置私有,通过getter setter方法操作属性带来的好处
- 可以控制属性的读写权限。想让私有变量不可读不可写就可以不提供getter和setter方法
- 可以在方法中对属性的值进行验证。setter方法中可以添加代码来控制值被修改的条件
通过getter和setter方法来操作属性的常用格式
get 属性名(){
return this.#属性
}
set 属性名(参数){
this.#属性 = 参数
}
class Person {
// #address = "花果山" // 实例使用#开头就变成了私有属性,私有属性只能在类内部访问
#name
#age
#gender
constructor(name, age, gender) {
this.#name = name
this.#age = age
this.#gender = gender
}
//以下5个函数是读取与修改属性的老方法
sayHello() {
console.log(this.#name)
}
// getter方法,用来读取属性
getName(){// p1.getName(),以这种形式读取属性
return this.#name
}
// setter方法,用来设置属性
setName(name){// p1.setName('猪八戒'),以这种形式修改属性
this.#name = name
}
getAge(){
return this.#age
}
setAge(age){
if(age >= 0){//控制年龄为非负数
this.#age = age
}
}
//以下2个函数是读取与修改属性的新方法,这样可以直接以属性而不是函数的形式读取和修改属性
get gender(){//p1.gender
return this.#gender
}
set gender(gender){//p1.gender = "女",以这种形式修改属性
this.#gender = gender
}
}
const p1 = new Person("孙悟空", 18, "男")//创建新对象的参数会传递到构造函数
p1.setAge(-11) //年龄不被修改
console.log(p1.gender)
多态
多态
- 在JS中不会检查参数的类型,所以这就意味着任何类型的数据都可以作为参数传递
- 要调用某个函数,实参对象无需指定的类型,只要对象满足某些条件即可
- 对于程序来说,如果一个东西走路像鸭子,叫起来像鸭子,那么它就是鸭子
- 多态为我们提供了灵活性
class Person{
constructor(name){
this.name = name
}
}
class Dog{
constructor(name){
this.name = name
}
}
const dog = new Dog('旺财')
const person = new Person("孙悟空")
function sayHello(obj){
// if(obj instanceof Person){//不检查对象的类型,这种特点叫做多态
console.log("Hello,"+obj.name)
// }
}
sayHello(dog)
继承
继承
- 可以通过extends关键来完成继承
- 当一个类继承另一个类时,就相当于将另一个类中的代码复制到了当前类中(简单理解)
- 继承发生时,被继承的类称为 父类(超类),继承的类称为 子类
- 通过继承可以减少重复的代码,并且可以在不修改一个类的前提对其进行扩展
面向对象编程的3大特点:
封装 —— 安全性
继承 —— 扩展性
多态 —— 灵活性
class Animal{
constructor(name){
this.name = name
}
sayHello(){
console.log("动物在叫~")
}
}
class Dog extends Animal{
// 在子类中,可以通过创建同名方法来重写父类的方法
sayHello(){
console.log("汪汪汪")
}
}
class Cat extends Animal{
// 重写构造函数
constructor(name, age){
// 重写构造函数时,构造函数的第一行代码必须为super()
super(name) //需要传递参数给父类时,在super中写上相应的参数,调用父类的构造函数
this.age = age
}
sayHello(){
// 调用一下父类的sayHello
super.sayHello() // 在方法中可以使用super来引用父类的方法
console.log("喵喵喵")
}
}
const dog = new Dog("旺财")
const cat = new Cat("汤姆", 3)
dog.sayHello()
cat.sayHello()
console.log(dog)
console.log(cat)
对象的结构
对象中存储属性的区域实际有两个:
- 对象自身
- 直接通过对象所添加的属性,位于对象自身中
- 在类中通过 x = y 的形式添加的属性,位于对象自身中
- 原型对象(prototype)
- 对象中还有一些内容,会存储到原型对象里
- 在对象中会有一个属性用来存储原型对象,这个属性叫做__proto__
- 原型对象也负责为对象存储属性,当我们访问对象中的属性时,会优先访问对象自身的属性,对象自身不包含该属性时,才会去原型对象中寻找
- 会添加到原型对象中的情况:
- 在类中通过xxx(){}方式添加的方法,位于原型中。而xxx = function(){}这种不会添加到原型对象中
- 主动向原型中添加的属性或方法
class Person {
name = "孙悟空"//存到对象自身中
age = 18
// constructor(){
// this.gender = "男"
// }
sayHello() {//存到原型对象中
console.log("Hello,我是", this.name)
}
}
const p = new Person()
// p.address = "花果山"
// p.sayHello = "hello"
console.log(p.sayHello)
原型
访问一个对象的原型对象的方法
对象.__proto__
,注意这种方法不要以对象.__proto__ = 值
这种方式赋值,这样会产生意想不到的后果类.prototype
,这是以类的属性方式去访问原型Object.getPrototypeOf(对象/实例)
。这种方法避免了轻易被赋值的问题
原型对象中的数据:- 对象中的部分属性、方法等
- constructor (对象的构造函数)
class Person {
name = "孙悟空"
age = 18
sayHello() {
console.log("Hello,我是", this.name)
}
}
const p = new Person()
const obj = {} //这是直接创建的对象,所以obj的父类是Object,obj.__proto__即Object.prototype,为原型链的尽头
实例/对象.proto=类.prototype
原型对象也有原型,这样就构成了一条原型链,根据对象的复杂程度不同,原型链的长度也不同
- p对象的原型链:
p.__proto__ = Person.prototype --> Person.prototype .__proto__=Object.__proto__ --> Object.__proto__.__proto__ = null
- obj对象的原型链:
obj.__proto__ = Object.prototype --> Object.prototype .__proto__ = null
Object.__proto__是原型链的尽头
原型链:取对象属性时,会优先读取对象自身属性,如果对象中有,则使用,没有则去对象的原型中寻找,如果原型中有,则使用,没有则去原型的原型中寻找,直到找到Object对象的原型(Object的原型没有原型(为null)),如果依然没有找到,则返回undefined
class Person {
name = "孙悟空"
age = 18
sayHello() {
console.log("Hello,我是", this.name)
}
}
class Dog {}
const p = new Person()
const p2 = new Person()
console.log(p==p2)//false
console.log(p.__proto__ === p2.__proto__)//true
p.sayHello = "hello"
const d = new Dog()
const d2 = new Dog()
class Animal{
}
class Cat extends Animal{
}
class TomCat extends Cat{
}
const cat = new Cat()
// TomCat --> cat --> Animal --> Object --> Object原型 --> null
// cat --> Animal --> Object --> Object原型 --> null
// p.__proto__=Person.__proto__ --> object --> Object原型 --> null
console.log(cat.__proto__)//Animal
console.log(cat.__proto__.__proto__)//Object
console.log(cat.__proto__.__proto__.__proto__);//Object.__proto__即Object的原型
console.log(cat.__proto__.__proto__.__proto__.__proto__)//null
所有的同类对象它们的原型对象都是同一个,也就意味着,同类对象的原型链是一样的
原型的作用
原型就相当于是一个公共的区域,可以被所有该类实例访问,可以将该类实例中,所有的公共属性(方法)统一存储到原型中, 这样我们只需要创建一个属性,即可被所有实例访问
JS中继承就是通过原型来实现的,当继承时,子类的原型就是一个父类的实例
在对象中有些值是对象独有的,像属性(name,age,gender)每个对象都应该有自己值,但是有些值对于每个对象来说都是一样的,像部分方法和属性,对于一样的值没必要重复的创建
原型链与作用域链的区别
- 作用域链,是找变量的链,找不到会报错。寻找顺序是从内层到外层
- 原型链,是找属性的链,找不到会返回undefined。寻找顺序是从外层到内层
修改原型
大部分情况下,我们不需要修改原型对象。修改原型时,最好通过通过类去修改
不要通过类的实例去修改原型,原因如下:
- 修改一个对象的原型会影响所有同类对象,这么做不安全
- 这样修改原型得先创建实例,麻烦
修改原型时,最好通过通过类去修改,原因如下
- 一修改就是修改所有实例的原型
- 无需创建实例即可完成对类的修改
修改原型的原则:
- 原型尽量不要手动改
- 要改也不要通过实例对象去改
- 通过 类.prototype 属性去修改
- 最好不要直接给prototype赋值
class Person {
name = "孙悟空"
age = 18
sayHello() {//以声明形式创建的方法会自动存储到原型中
console.log("Hello,我是", this.name)
}
}
Person.prototype.fly = () => {//对类的原型的修改最好紧接着类的定义
console.log("我在飞!")
}
class Dog{
}
const p = new Person()
const p2 = new Person()
//通过对象修改原型,向原型中添加方法,修改后所有同类实例都能访问该方法,但不建议这样做
p.__proto__.run = () => {
console.log('我在跑~')
}
p.__proto__ = new Dog() // 直接为对象赋值了一个新的原型,不建议这样做
console.log(p)//p的原型变成了Dog
console.log(p2)//p2的原型还是Object
//p.run()//报错,因为p的原型已经修改成了Dog,Dog里面没有run()函数
p2.run()//可以调用,因为p2的原型还未被修改
console.log(Person.prototype) // 访问Person实例的原型对象
console.log(Object.getPrototypeOf(p2))
console.log(Person.prototype===p2.__proto__)//
p2.fly()
运行结果
运算符instanceOf,in和实例方法对象/实例.hasOwnProperty
和静态方法Object.hasOwn(对象,"属性/方法")
instanceof 用来检查一个对象是否是一个类的实例
- instanceof检查的是对象的原型链上是否有该类实例,只要原型链上有该类实例,就会返回true
- Dog -> Animal的实例 -> Object实例 -> Object原型
- Object是所有对象的原型,所以任何和对象和Object进行instanceof运算都会返回true
in:使用in运算符检查属性时,无论属性在对象自身还是在原型中,都会返回true
对象.hasOwnProperty(“属性名/方法名”) (官方不推荐使用)
- 这是一个实例方法
- 用来检查一个对象的自身是否含有某个属性,如果属性或方法
Object.hasOwn(对象, “属性名/方法名”) (官方推荐使用)
- 这是一个静态方法
- 用来检查一个对象的自身是否含有某个属性
原因如下截图:对于null对象没有继承,用对象.hasOwnProperty("属性名/方法名")
会报错,用Object.hasOwn(对象, "属性名/方法名")
不会报错,在大多数情况下2者其实差不多
class Animal {}
class Dog extends Animal {}
const dog = new Dog()
console.log(dog instanceof Dog) // true
console.log(dog instanceof Animal) // true
console.log(dog instanceof Object) // true
const obj = new Object()
class Person {
name = "孙悟空"
age = 18
sayHello() {
console.log("Hello,我是", this.name)
}
}
const p = new Person()
console.log("sayHello" in p)//true
console.log(p.hasOwnProperty("sayHello"))//false
console.log(p.__proto__.__proto__.hasOwnProperty("hasOwnProperty"))//true,hasOwnProperty()是p原型的原型中的方法
console.log(Object.hasOwn(p, "sayHello"))
旧类(了解就行)
旧类就是早期javascript定义类的方法
早期JS中,直接通过函数来定义类
- 一个函数如果直接调用 xxx() 那么这个函数就是一个普通函数
- 一个函数如果通过new调用 new xxx() 那么这个函数就是一个构造函数
/*
等价于:
class Person{
}
*/
//用立即函数将分散的代码集合到一起执行
var Person = (function () {
function Person(name, age) {
// 在构造函数中,this表示新建的对象
this.name = name
this.age = age
// this.sayHello = function(){//这样添加的方法不会添加到原型中,导致每个类需重复创建方法代码,代码复用性较差
// console.log(this.name)
// }
}
// 所以通过这种方法向原型中添加属性/方法
Person.prototype.sayHello = function () {
console.log(this.name)
}
// 静态属性,添加到构造函数中
Person.staticProperty = "xxx"
// 静态方法,添加到构造函数中
Person.staticMethod = function () {}
return Person//记得返回类
})()
const p = new Person("孙悟空", 18)
console.log(p)
var Animal = (function(){
function Animal(){
}
return Animal
})()
var Cat = (function(){
function Cat(){
}
// 通过这种方法继承Animal
Cat.prototype = new Animal()
return Cat
})()
var cat = new Cat()
console.log(cat)
new运算符
new运算符是创建对象时要使用的运算符
- 使用new时,到底发生了哪些事,详见以下MDN链接:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/new
- 当使用new去调用一个函数时,这个函数将会作为构造函数调用,简单来说,使用new调用函数时,将会发生这些事:
- 创建一个普通的对象(Object对象 {}), 为了方便,称其为"新对象"
var newInstance = {}
- 将构造函数的prototype属性赋值给新对象的原型
newInstance.__proto__ = MyClass.prototype
- 如果有参数,将实参传递给构造函数,并且将新对象设置为函数中的this
-
- 如果构造函数返回的是一个非原始值,则该值会作为new运算的返回值(不要这么做)
- 如果构造函数的返回值是一个原始值或者没有指定返回值,则新的对象将会作为返回值
- 通常我们不会为构造函数指定返回值
- 创建一个普通的对象(Object对象 {}), 为了方便,称其为"新对象"
function MyClass(){//旧类
// var newInstance = {}
// newInstance.__proto__ = MyClass.prototype
}
var mc = new MyClass()
// console.log(mc)
class Person{//新类
constructor(){
}
}
new Person()
面向对象本质就是,编写代码时所有的操作都是通过对象来进行的。
- 找对象
- 搞对象
学习对象:
- 明确这个对象代表什么,有什么用
- 如何获取到这个对象
- 如何使用这个对象(对象中的属性和方法)
对象的分类:
- 内建对象:由ES标准所定义的对象,比如 Object Function String Number …
- 宿主对象(宿主就是JS的运行环境)
- 由浏览器提供的对象
- BOM、DOM
- 自定义对象:由开发人员自己创建的对象
DOM概述
DOM 是 JavaScript 操作网页的接口,全称为“文档对象模型”(Document Object Model)。它的作用是将网页转为一个 JavaScript 对象,从而可以用脚本进行各种操作(比如对元素增删内容)
浏览器会根据 DOM 模型,将结构化文档HTML解析成一系列的节点,再由这些节点组成一个树状结构(DOM Tree)。所有的节点和最终的树状结构,都有规范的对外接口
DOM 只是一个接口规范,可以用各种语言实现。所以严格地说,DOM 不是 JavaScript 语法的一部分,但是 DOM 操作是 JavaScript 最常见的任务,离开了 DOM,JavaScript 就无法控制网页。另一方面,JavaScript 也是最常用于 DOM 操作的语言
节点
DOM 的最小组成单位叫做节点(node)。文档的树形结构(DOM 树),就是由各种不同类型的节点组成。每个节点可以看作是文档树的一片叶子
节点的类型有七种
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对象_方法/获取元素
document.getElementsByTagName()
document.getElementsByTagName
方法搜索 HTML 标签名,返回符合条件的元素。它的返回值是一个类似数组对象(HTMLCollection
实例),可以实时反映 HTML 文档的变化。如果没有任何匹配的元素,就返回一个空集
var paras = document.getElementsByTagName('p');
如果传入*
,就可以返回文档中所有 HTML 元素
var allElements = document.getElementsByTagName('*');
document.getElementsByClassName()
document.getElementsByClassName
方法返回一个类似数组的对象(HTMLCollection
实例),包括了所有class
名字符合指定条件的元素,元素的变化实时反映在返回结果中
var elements = document.getElementsByClassName(names);
由于class
是保留字,所以 JavaScript 一律使用className
表示 CSS 的class
参数可以是多个class
,它们之间使用空格分隔
var elements = document.getElementsByClassName('foo bar');
document.getElementsByName()
document.getElementsByName
方法用于选择拥有name
属性的 HTML 元素(比如<form>
、<radio>
、<img>
等),返回一个类似数组的的对象(NodeList
实例),因为name
属性相同的元素可能不止一个
// 表单为 <form name="itbaizhan"></form>
var forms = document.getElementsByName('itbaizhan');
document.getElementById()
document.getElementById
方法返回匹配指定id
属性的元素节点。如果没有发现匹配的节点,则返回null
var elem = document.getElementById('para1');//因为id是唯一的,所以此处是Element
console.log(elem);//这里直接打印标签内容,而不是数组对象了
注意,该方法的参数是大小写敏感的。比如,如果某个节点的id
属性是main
,那么document.getElementById('Main')
将返回null
document.querySelector()
document.querySelector
方法接受一个 CSS 选择器作为参数,返回匹配该选择器的元素节点。如果有多个节点满足匹配条件,则返回第一个匹配的节点。如果没有发现匹配的节点,则返回null
var el1 = document.querySelector('.myclass');
document.querySelectorAll()
document.querySelectorAll
方法与querySelector
用法类似,区别是返回一个NodeList
对象,包含所有匹配给定选择器的节点
var elementList = document.querySelectorAll('.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对象_方法/创建元素
document.createElement()
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对象_属性
Element对象对应网页的 HTML 元素。每一个 HTML 元素,在 DOM 树上都会转化成一个Element节点对象(以下简称元素节点)
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
Element.innerHTML
Element.innerHTML
属性返回一个字符串,等同于该元素包含的所有 HTML 代码。该属性可读写,常用来设置某个节点的内容。它能改写所有元素节点的内容,包括<HTML>
和<body>
元素
console.log(element.innerHTML);//读取元素element的内容
console.log(element.innerHTML='hello');//将hello写入元素element内容中
Element.innerText
innerText
和innerHTML
类似,不同的是innerHTML
可以识别元素/标签,而innerText
无法识别元素,会把标签识别成字符串
实时效果反馈
1. 下列代码为div元素动态添加一个class,画横线处应该填写的内容是:
var div = document.getElementById('myDiv');
div.classList.___('myCssClass');
A remove
B add
C toggle
D contains
答案
1=>B
Element获取元素位置
属性 | 描述 |
---|---|
clientHeight | 获取元素高度包括padding 和溢出的不可见内容,但是不包括border 、margin |
clientWidth | 获取元素宽度包括padding 和溢出的不可见内容,但是不包括border 、margin |
scrollHeight | 元素总高度,它包括padding ,但是不包括border 、margin 和溢出的不可见内容 |
scrollWidth | 元素总宽度,它包括padding ,但是不包括border 、margin 和溢出的不可见内容 |
scrollLeft | 元素的水平滚动条向右滚动的像素数量 |
scrollTop | 元素的垂直滚动条向下滚动的像素数量 |
offsetHeight | 元素的 CSS 垂直高度(单位像素),包括元素本身的高度、padding 和 border |
offsetWidth | 元素的 CSS 水平宽度(单位像素),包括元素本身的高度、padding 和 border |
offsetLeft | 到定位父级左边界的间距 |
offsetTop | 到定位父级上边界的间距 |
Element.clientHeight,Element.clientWidth
Element.clientHeight
属性返回一个整数值,表示元素节点的 CSS 高度(单位像素),只对块级元素生效,对于行内元素返回0
。如果块级元素没有设置 CSS 高度,则返回实际高度
除了元素本身的高度,它还包括padding
部分,但是不包括border
、margin
。如果有水平滚动条,还要减去水平滚动条的高度。注意,这个值始终是整数,如果是小数会被四舍五入。
Element.clientWidth
属性返回元素节点的 CSS 宽度,同样只对块级元素有效,也是只包括元素本身的宽度和padding
,如果有垂直滚动条,还要减去垂直滚动条的宽度。
document.documentElement
的clientHeight
属性,返回当前视口的高度(即浏览器窗口的高度)。document.body
的高度则是网页内容总高度。
// 视口高度
document.documentElement.clientHeight
// 网页内容总高度
document.body.clientHeight
Element.scrollHeight,Element.scrollWidth
Element.scrollHeight
属性返回一个整数值(小数会四舍五入),表示当前元素的总高度(单位像素),它包括padding
,但是不包括border
、margin
以及水平滚动条的高度(如果有水平滚动条的话)
Element.scrollWidth
属性表示当前元素的总宽度(单位像素),其他地方都与scrollHeight
属性类似。这两个属性只读
整张网页的总高度可以从document.documentElement
或document.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.clientHeight
和Element.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
CSS操作
HTML
元素的 style
属性
操作 CSS 样式最简单的方法,就是使用网页元素节点的setAttribute
方法直接操作网页元素的style
属性
div.setAttribute(
'style',//属性名
"background-color:red;border:1px solid black;"//属性值
);
元素节点的style
属性
元素.属性.属性值类型=属性值。
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';
cssText
属性
var divStyle = document.querySelector('div').style;
divStyle.cssText = 'background-color: red;'
+ 'border: 1px solid black;'
+ 'height: 100px;'
+ 'width: 100px;';
实时效果反馈
1. 下列是设置样式的代码,横线处应该填写的代码是:
var divStyle = document.querySelector('div');
divStyle.___.backgroundColor = 'red';
A style
B cssText
C setAttribute
D getAttribute
答案
1=>A
事件处理程序
事件处理程序分为:
- HTML事件处理
- DOM0级事件处理
- 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级事件处理
优点: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级事件处理
优点: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
事件类型之鼠标事件
鼠标事件
鼠标事件指与鼠标相关的事件,具体的事件主要有以下一些
- click:按下鼠标时触发
- dblclick:在同一个元素上双击鼠标时触发
- mousedown:按下鼠标键时触发
- mouseup:释放按下的鼠标键时触发
- mousemove:当鼠标在节点内部移动时触发。当鼠标持续移动时,该事件会连触发。
- mouseenter:鼠标进入一个节点时触发,进入子节点不会触发这个事件
- mouseleave:鼠标离开一个节点时触发,离开父节点不会触发这个事件
- mouseover:鼠标进入一个节点时触发,进入子节点会再一次触发这个事件
- mouseout:鼠标离开一个节点时触发,离开父节点也会触发这个事件
- 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事件对象
事件发生以后,会产生一个事件对象,作为参数传给监听函数。
Event对象属性
- Event.Target
- Event.type
Event.target
Event.target属性返回事件当前所在的节点
// HTML代码为
// <p id="para">Hello</p>
function setColor(e) {
console.log(this === e.target);
e.target.style.color = 'red';
}
para.addEventListener('click', setColor);
Event.type
Event.type属性返回一个字符串,表示事件类型。事件的类型是在生成事件的时候。该属性只读
Event对象方法
- Event.preventDefault()
- Event.stopPropagation()
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");
}
实时效果反馈
1. 下列关于阻止默认事件方法正确的是:
A stopPropagation()
B preventDefault()
C target
D currentTarget
答案
1=>B
事件类型之键盘事件
键盘事件由用户击打键盘触发,主要有keydown、keypress、keyup三个事件
- keydown:按下键盘时触发。
- keypress:按下有值的键时触发,即按下 Ctrl、Alt、Shift、Meta 这样无值的键,这个事件不会触发。对于有值的键,按下时先触发keydown事件,再触发这个事件。
- 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
事件类型之表单事件
表单事件是在使用表单元素及输入框元素可以监听的一系列事件
- input事件
- select事件
- Change事件
- reset事件
- 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
事件代理(事件委托)
由于事件会在冒泡阶段向上传播到父节点,因此可以把子节点的监听函数定义在父节点上,由父节点的监听函数统一处理多个子元素的事件。这种方法叫做事件的代理(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. 下列是关于事件代理的处理,横线处应该填写的代码是:
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
定时器之setTimeout()
JavaScript 提供定时执行代码的功能,叫做定时器(timer),主要由setTimeout()
和setInterval()
这两个函数来完成。它们向任务队列添加定时任务
setTimeout
函数用来指定某个函数或某段代码,在多少毫秒之后执行。它返回一个整数,表示定时器的编号,以后可以用来取消这个定时器。
var timerId = setTimeout(函数名|代码块, 延迟时间);
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
防抖(debounce)
防抖严格算起来应该属于性能优化的知识,但实际上遇到的频率相当高,处理不当或者放任不管就容易引起浏览器卡死。
从滚动条监听的例子说起
function showTop () {
var scrollTop = document.documentElement.scrollTop;
console.log('滚动条位置:' + scrollTop);
}
window.onscroll = showTop
在运行的时候会发现存在一个问题:这个函数的默认执行频率,太!高!了!。 高到什么程度呢?以chrome为例,我们可以点击选中一个页面的滚动条,然后点击一次键盘的【向下方向键】,会发现函数执行了8-9次!
然而实际上我们并不需要如此高频的反馈,毕竟浏览器的性能是有限的,不应该浪费在这里,所以接着讨论如何优化这种场景。
基于上述场景,首先提出第一种思路:在第一次触发事件时,不立即执行函数,而是给出一个期限值比如200ms,然后
- 如果在200ms内没有再次触发滚动事件,那么就执行函数
- 如果在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)
没完全懂
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dqDeh6M1-1675909867747)(imgs/image-20211109104718862.png)]
节流严格算起来应该属于性能优化的知识,但实际上遇到的频率相当高,处理不当或者放任不管就容易引起浏览器卡死
继续思考,使用上面的防抖方案来处理问题的结果是
如果在限定时间段内,不断触发滚动事件(比如某个用户闲着无聊,按住滚动不断的拖来拖去),只要不停止触发,理论上就永远不会输出当前距离顶部的距离
但是如果产品同学的期望处理方案是:即使用户不断拖动滚动条,也能在某个时间间隔之后给出反馈呢?
其实很简单:我们可以设计一种类似控制阀门一样定期开放的函数,也就是让函数执行一次后,在某个时间段内暂时失效,过了这段时间后再重新激活(类似于技能冷却时间)
效果:如果短时间内大量触发同一事件,那么在函数执行一次之后,该函数在指定的时间期限内不再工作,直至过了这段时间才重新生效
实现
这里借助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的时间间隔,持续输出当前位置和顶部的距离
讲完了这两个技巧,下面介绍一下平时开发中常遇到的场景:
- 搜索框input事件,例如要支持输入实时搜索可以使用节流方案(间隔一段时间就必须查询相关内容),或者实现输入间隔大于某个值(如500ms),就当做用户输入完成,然后开始搜索,具体使用哪种方案要看业务需求
- 页面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