JavaScript大全
一、JavaScript基础
1.JS包括ES、DOM、BOM
JavaScript 是一种运行在客户端(浏览器)的编程语言
ECMAScript( 基础语法 )、web APIs (DOM、BOM)
2.JS的三种写法:
(1)内部:写在 <script> </script>
里面
(2)外部:引用外部js <script src='my.js’> </script>
(3)内联:vue框架中<button onclick="alert('hello')">点击<button>
3.JS的输入输出语法
<script>
//输出语法
// 语法1
// 作用:向body内输出内容 注意: 如果输出的内容写的是标签, 也会被解析成网页元素
document.write('<div>向body内输出内容</div>')
//语法2
alert('警告弹窗')
//语法3
console.log('控制台输出')
//输入语法
// 显示一个对话框,对话框中包含一条文字信息,用来提示用户输入文字
prompt('请输入')
</script>
4.变量
计算机中用来存储数据的“容器”,简单理解是一个
个的盒子。用来存放数据的。注意变量指的是容器而不是数据。
4.1变量的基本使用
<script>
//1.声明一个年龄变量
let age
//2.赋值
age = 18
//3.是通过变量名来获得变量里面的数据
alert(age)
//写法二:变量的声明并赋值
let num =18
</script>
4.2更新变量:
变量赋值后,还可以通过简单地给它一个不同的值来更新它
let a = 20
a = 30
console.log(a)
//注意:let不能多次声明一个变量,输出会报错
//var 声明过的变量可以重复声明(不合理)
let a=20
let a=30 //(报错)
var a=10
var a=20 //(不报错。但不合理)
案例:变量案例- 交换变量的值:核心思路:使用一个 临时变量 用来做中间存储
(3)变量本质:是程序在内存中申请的一块用来存放数据的小空间
(4)变量命名规则与规范
1. 不能用关键字
关键字:有特殊含义的字符,JavaScript 内置的一些英语词汇。例如:let、var、if、for等
2. 只能用下划线、字母、数字、$组成,且数字不能开头
3. 字母严格区分大小写,如 Age 和 age 是不同的变量
4. 遵守小驼峰命名法:
第一个单词首字母小写,后面每个单词首字母大写。例:userName
(5)let和var的区别
var 声明:
1. 可以先使用 在声明 (不合理)
2. var 声明过的变量可以重复声明(不合理)
3. 比如变量提升、全局变量、没有块级作用域等等
//这里先声明未赋值,结果是undefined
var num console.log(num) num=10
//可以先使用 在声明,结果不报错是10
num=10 console.log(num) var num
//可以重复声明,结果是30,后面的覆盖前面的
var num=20 var num=30 console.log(num)
5.数组
//定义数组
let arr=['小明','小红','小林']
//数组取值
console.log(arr[0])
//数组长度 数组长度=索引号+1
console.log(arr.length)
6.常量
(1)使用 const 声明的变量称为“常量”。
(2)当某个变量永远不会改变的时候,就可以使用 const 来声明,而不是let。
(3)常量不允许重新赋值,声明的时候必须赋值(初始化)
常量初始化就是声明并赋值(只声明不赋值就会报错)
//常量 不允许改值 结果报错
const PI=3.14
console.log(PI)
PI=3.15
console.log(PI)
//常量声明时必须赋值 结果报错
const PI
7.数据类型
JS 数据类型整体分为两大类:
(1)基本数据类型
- number 数字型
- string 字符串型
- boolean 布尔型
- undefined 未定义型
- null 空类型
(2)引用数据类型
- object 对象
7.1 number
(1)+:求和
-:求差
*:求积
/:求商
%:取模(取余数) 开发中经常作为某个数字是否被整除
(2)可以用()提升优先级
(3)特殊存在:NaN 代表一个计算错误。它是一个不正确的或者一个未定义的数学操作所得到的结果。NaN 是粘性的。任何对 NaN 的操作都会返回 NaN
(4)i++ ++i
符号前置 =>先加1 再使用(快捷记忆: ++在前 先加)
符号后置 =>先使用 再加1(快捷记忆: ++在后后加)
7.2 string
注意事项:
1.无论单引号或是双引号必须成对使用
2.单引号/双引号可以互相嵌套,但是不以自已嵌套自已 (口诀: 外双内单,或者外单内双)
3.必要时可以使用转义符\,输出单引号或双引号。
console.log("引入\'林清远\'作者")
//引入'林清轩`作者
字符串拼接:
+运算符 可以实现字符串的拼接。
口诀:数字相加,字符相连
//字符串拼接 +
let age=18
document.write('我今年'+age+'岁了')
//模板字符串 外面用反引号`` 里面${变量名}
let age=18
document.write(`我今年${age}岁了`)
7.3 boolean
true false
7.4 undefined
声明了未赋值就undefined
7.5 null
JavaScript 中的 null 仅仅是一个代表“无”、“空”或“值未知”的特殊值
console.log(undefined+1) //NAN
console.log(null+1) //1 因为null表示一个空
//将来有个变量里面存放的是一个对象,但是对象还没创建好,可以先给个null
let obj=null
console.log(obj)// null
(1)null 和 undefined 区别:
undefined 表示没有赋值
null 表示赋值了,但是内容为空
8.数据检测
通过 typeof 关键字检测数据类型
let a=2
console.log(typeof a)
9.类型转换
JavaScript是弱数据类型: JavaScript也不知道变量到底属于那种数据类型,只有赋值了才清楚。
坑: 使用表单、prompt 获取过来的数据默认是字符串类型的,此时就不能直接简单的进行加法运算。
9.1隐式转换
某些运算符被执行时,系统内部自动将数据类型进行转换,这种转换称为隐式转换。
(1) +号两边只要有一个是字符串,都会把另外一个转成字符串
(2) 除了+以外的算术运算符 比如 - * / 等都会把数据转成数字类型
(3) +号作为正号解析可以转换成数字型
<!-- 某些运算符被执行时,系统内部自动将数据类型进行转换,这种转换称为隐式转换 -->
<script>
console.log(11 + 11) //22
// + 两边只要有一个是字符串,都会把另外一个转成字符串
console.log('11' + 11) //1111
console.log(11 - 11) //0
//除了+以外的算术运算符 比如 - * / 等都会把数据转成数字类型
console.log('11' - 11) //0
console.log(1 * 1) //1
console.log('1' * 1) //1
console.log(typeof '123') //string
!!!字符串转数字类型隐式转换 console.log(typeof + '123') //number 这种情况就是正数
console.log(+'11' + 11) //22
</script>
9.2显式转换
(1)Number(数据) 只认数字
console.log(Number('123'))//123
console.log(Number('dog'))//NaN
(2)parseInt(数据): 只保留整数 只认数字
console.log(parseInt('12px'))//12
console.log(parseInt('12.35px'))//12
console.log(parseInt('12aaa'))//12
console.log(parseInt('aaa12'))//NaN 从下标0开始识别数字
(3)parseFloat(数据):可以保留小数 只认数字
console.log(parseFloat('12.35px'))//12.35
console.log(parseFloat('12aaa'))//12
console.log(parseFloat('aaa12'))//NaN 从下标0开始识别数字
(4)String(数据)
(5)变量.toString()(进制) //toString()是一个函数
10.数组
如果有多个数据可以用数组保存起来,然后放到一个变量中,管理非常方便
10.1遍历数组(重点)
let nums=[10,20,30,40,50]
for(let i=0;i<nums.length;i++){
document.write(nums[i])
}
//累加
let nums=[10,20,30,40,50]
let sum=0
for(let i=0;i<nums.length;i++){
sum+=nums[i]
}
console.log(`和为${sum}`)
console.log(`平均值为${sum}/nums.length`)
10.2 操作数组
(1)查:查询数组数据
数组[下标]
或者我们称为访问数组数据
(2)改:重新赋值
数组[下标] = 新值
(3)增:数组添加新的数据
arr.push(新增的内容) //往末尾加
arr.unshift(新增的内容) //往首位加
(4)删:删除数组中数据
arr.pop() //往末尾删
arr.shift() //往首位删
arr.splice(操作的下标,删除的个数)
10.3 arr.push()
方法将一个或多个元素添加到数组的末尾,并返回该数组的新长度 (重点)
10.4 arr.unshift(新增的内容)
方法将一个或多个元素添加到数组的开头,并返回该数组的新长度
10.5 arr. pop()
方法从数组中删除最后一个元素,并返回该元素的值
10.6 arr. shift()
方法从数组中删除第一个元素,并返回该元素的值
案例:筛选出大于10的数,放置新数组
let arr=[1,5,9,18,2,66,85,15,21]
let newArr=[]
for(let i=0;i<arr.length;i++){
if(arr[i]>=10)
//push到新数组去
newArr.push(arr[i])
}
console.log(newArr)
let arr=['red','green']
arr.pop()//删除最后一个元素
console.log(arr)//['red']
console.log(arr.pop)//['green']
10.7 数组. splice() 方法 删除指定元素(常用)
arr.splice(起始位置,删除几个元素)
参数1:指定修改的开始位置(从0计数)
参数2:可选的。 如果省略则默认从指定的起始位置删除到最后
参数3:可选的。 插入的元素
splice(start, deleteCount, item1, item2, itemN)
const months = ['Jan', 'March', 'April', 'June'];
months.splice(1, 0, 'Feb');
// Inserts at index 1
console.log(months);
// Expected output: Array ["Jan", "Feb", "March", "April", "June"]
months.splice(4, 1, 'May');
// Replaces 1 element at index 4
console.log(months);
// Expected output: Array ["Jan", "Feb", "March", "April", "May"]
10.8 数组. sort() 方法可以排序
let arr = [4, 2, 5, 1, 3]
// 1.升序排列写法
arr.sort(function (a, b) {
return a - b
})
console.log(arr) // [1, 2, 3, 4, 5]
// 降序排列写法
arr.sort(function (a, b) {
return b - a
})
console.log(arr) // [5, 4, 3, 2, 1]
10.9 数组. filter()方法过滤
filter(callbackFn)
为数组中的每个元素执行的函数。它应该返回一个真值以将元素保留在结果数组中,否则返回一个假值。
- 例子
const computedTables = useMemo(() => {
const filterData = lists.filter(({id}) => {
if ((iniData && id === iniData.tableId) || value.tables.findIndex(({tableId}) => id === tableId) === -1 )
{
return true
} else {
return false }
})
return filterData }, [value.tables, lists, iniData])
这段代码是使用 React 中的 useMemo 钩子来优化计算结果的性能。代码中定义了一个叫做 computedTables 的常量,它通过 useMemo 方法返回一个经过计算和筛选的 tables 数据。每当依赖项(value.tables, lists, iniData)发生变化时,useMemo 钩子会重新计算 computedTables 的值,并返回新的结果。
具体而言,代码通过对 lists 数组进行筛选,保留满足特定条件的元素。条件是:对于 lists 数组中的每个元素,如果满足以下条件之一,则保留该元素:
- iniData 存在且当前元素的 id 等于 iniData 的 tableId;
- value.tables 数组中不存在任何一个 tableId 等于当前元素的 id。
符合条件的元素将被返回到 filterData 数组中,并作为最终的计算结果返回。这样可以避免在每次组件重新渲染时都重新计算 filteredTables,从而提高性能和减少不必要的计算开销。
11.函数
函数是什么:function执行特定任务的代码块
为什么:精简代码方便复用,可以实现代码复用,提高开发效率
函数本身是不会执行的,要调用函数才执行
11.1 函数的使用
(1)命名规范:用 can能否 has是否有 is是 get获取值 set修改值 load加载数据 动词命名
(2)有实参优先执行参数,没有参数,就执行默认参数
function getSum(num1,num2){ //形参
document.write(num1+num2)
}
getSum(10,20)//实参
//这个默认值只会在缺少实参参数传递时 才会被执行,所以有参数会优先执行传递过来的实参, 否则默认为
undefined
function getSum(x=0,y=0){
document.write(x+y)
}
getSum()// 0
getSum(1,2)// 3
(3)案例:求n~m的累加和
function getSum(n=0,m=0){
let sum=0
for(let i=n;i<=m;i++){
sum+=i
}
console.log(sum)
}
let num1=+prompt('请输入起始值')
let num1=+prompt('请输入结束值')
getSum(num1,num2)//实参可以是变量``
(4)返回值
1. 在函数体中使用 return 关键字能将内部的执行结果交给函数外部使用
2. return 后面代码不会再被执行,会立即结束当前函数,所以 return 后面的数据不要换行写
3. return函数可以没有 return,这种情况函数默认返回值为 undefined
function getArrSum(arr=[]){
let sum=0
for(let i=0;i<arr.length;i++)
{
sum+=arr[i]
}
return sum
}
//1。用户不输值,参数undifine的情况
getArrSum()//这里是length报错,有些有加减运算的就是undifine与undifine操作,就是NaN,所以参数要赋初始值 arr=[]
//2.return关键字 返回的值 返回的是给函数调用者,也就是getArrSum(),一定要加小括号,这样才是调用
//3.如果没有return ,此函数调用返回的是 undefined
//求和函数写法
function getSum(n=0,m=0){
return n+m
}
let sum = getSum(5,8)//实参可以是变量
console.log(sum)
- 为什么要让函数有返回值
(1) 函数执行后得到结果,结果是调用者想要拿到的(一句话,函数内部不
需要输出结果,而是返回结果)
(2)对执行结果的扩展性更高,可以让其他的程序使用这个结果
- 函数细节补充
(1)两个相同的函数后面的会覆盖前面的函数
(2)在Javascript中 实参的个数和形参的个数可以不一致
如果形参过多 会自动填上undefined (了解即可)
如果实参过多 那么多余的实参会被忽略 (函数内部有一个arguments,里面装着所有的实参)
(3)函数一旦碰到return就不会在往下执行了 函数的结束用return
- 案例:求最大值函数
function getArrValue(arr=[]){
//(1)先准备一个max变量存放数组第一个值
let max=arr[0]
//(2)遍历比较
for(let i=1;i<arr.length;i++){
if(max<arr[i]){
max=arr[i]
}
}
//(3)返回值
return max
}
//返回值返回给调用者
let MAX=getArrValue([11,3,55,7,29])
console.log(MAX)
function getArrValue(arr=[]){
//(1)先准备一个max变量存放数组第一个值
let max=arr[0]
//(2)遍历比较
for(let i=1;i<arr.length;i++){
//最大值
if(max<arr[i]){
max=arr[i]
}
//最小值
if(min>arr[i]){
min=arr[i]
}
}
//(3)返回的是多个数据,存多个数据就用数组或者对象
return [max,min] // [55,3]
return {max,min} //{max:55,min:3}
}
//返回值返回给调用者
let newArr=getArrValue([11,3,55,7,29])
console.log(`数组的最大值是:${newArr[0]}`)
console.log(`数组的最大值是:${newArr[1]}`)
return 多个值,要不用数组存 return [max,min],要不用对象存 return {max,min}。
而不是 return (max,min),括号return的是最后一个值。
11.2 作用域
(1)全局变量:函数外部let 的变量,全局变量在任何区域都可以访问和修改
(2)局部变量:函数内部let的变量,局部变量只能在当前函数内部访问和修改
(3)在不同作用域下,可能存在变量命名冲突的情况,到底改执行谁呢?
let num =10
function fn(){
let num=20
console.log(num)
}
fn()
//let不允许在同一作用域下重复定义,但是这里是一个全局作用域,一个局部就可以
//!!!访问原则:在能够访问到的情况下 先局部, 局部没有在找全局
访问原则:在能够访问到的情况下 先局部, 局部没有在找全局
作用域链:采取就近原则的方式来查找变量最终的值
function f1(){
let num=123
function f2(){
console.log(num)
}
f2()
}
let num=456
f1()------------------>//结果是123
function f1(){
let num=123
function f2(){
let num=0
console.log(num)
}
f2()
}
let num=456
f1()------------------>//结果是0
11.3 匿名函数
(1) 函数表达式
匿名函数,没有名字的函数, 无法直接使用
function(){}
将匿名函数赋值给一个变量,并且通过变量名称进行调用 我们将这个称为函数表达式
//函数表达式
let fn = function(x,y){
console.log(x+y)
}
fn(1,2)
function fn(1,2)
//之前的具名函数
fn()---------------->具名函数的调用可以写在任何位置
function fn(){
console.log(1)
}
fn()
具名函数的调用可以写在任何位置
函数表达式,必须先声明函数表达式,再调用
<body>
<button>点击我</button>
<script>
let btn=document.querySelector('button')
btn.onclick=function(){//匿名函数
alert('我是匿名函数')
}
</script>
</body>
总结:
//函数表达式 //匿名函数
let fn=function(x,y){
return x+y
}
//具名函数
function fn(x,y){
return x+y
}
//箭头函数
const fn=(x,y)=>{
return x+y
}
(2)立即执行函数
-
立即执行函数是什么?
立即执行函数就是声明一个匿名函数,并马上调用这个匿名函数。(立即执行函数是不需要调用的,在定义时就立即执行了)
-
立即执行函数有什么用途
创建一个独立的作用域,这个作用域里面的变量,外面访问不到(即避免"变量污染")
什么是立即执行函数
来自 [MDN] 的回答是
IIFE( 立即调用函数表达式)是一个在定义时就会立即执行的 JavaScript 函数)。
这样就能形成一个 块级作用域 效果
(function () {
// 块级作用域
})();
这在没有块级作用域的 ES3 时代,是相当普遍的做法
以前有个有名的面试题,如下所示:
for (var i = 0; i < 5; i++) {
setTimeout(function () {
console.log(i);
}, 1000 * i);
}
结果是什么,5,5,5,5,5,而且是在每 1 秒打印一个 5
问,有什么方法让它的结果是 1,2,3,4,5
我们分析一下为什么会在一开始的时候打印 5,这是因为 setTimeout 是异步,要塞进异步队列中,所以一开始先循环,循环完了再执行 setTimeout(func, wait)。
所以执行顺序是
for (var i = 0; i < 5; i++) {
// 赋值 setTimeout(function() { console.log(i) }, 1000 * i)
// i 1,2,3,4,5
}
// setTimeout 延迟执行,var i被统一赋值为5
setTimeout(function () {
console.log(5);
}, 1000 * 1);
setTimeout(function () {
console.log(5);
}, 1000 * 2);
setTimeout(function () {
console.log(5);
}, 1000 * 3);
setTimeout(function () {
console.log(5);
}, 1000 * 4);
setTimeout(function () {
console.log(5);
}, 1000 * 5);
又因为,for() {} 不会形成块级作用域,所以会拿最后的值也就是 5 来给每一个 func 中的 console.log(i) 赋值,最后导致了这样的打印结果
分析完后,我们要思考一下,怎么保住 setTimeout 中的变量 i,通常的办法是通过作用域来保护,例如用块级作用域来保护 i ,方法是用 let 代替 var。
for (let i = 0; i < 5; i++) {
// 将 var 改成 let 即可
setTimeout(function () {
console.log(i);
}, 1000 * i);
}
或者用函数作用域来保护,因为函数作用域内的变量,函数外不能访问
for (var i = 0; i < 5; i++) {
(function (j) {
setTimeout(function () {
console.log(j);
}, 1000 * j);
})(i);
}
用 let 的方法的伪代码类似于立即执行函数。代码理解为:
每传入一个变量 i,并立即执行 setTimeout ,执行完毕一次后,for 循环中的 i 变为 1,再执行 setTimeout,这样就达到了效果
其原面试题为什么会出现这种结果,本质是 JavaScript 中的 for 循环不能保护 i 被改变,即 for 循环不能形成块级作用域。
通过这题我们能清晰的认知到立即执行函数的用处:定义时就会立即执行的函数
(function(){
let num=10
})();
(function(){
let num=20
})();
//随便重复声明都互不影响
(function(x,y){
console.log(x+y)
})(1,2);
两种写法
(function(){}());
(function(){})();
多个立即执行函数需要用分号隔开。
//综合案例:秒数转换
<script>
//1.用户输入
let ss = +prompt('请输入秒数')
//2.封装函数
function getTime(t) { //形参
// console.log(t) //总的秒数
let h = parseInt(t / 60 / 60 % 24)
let m = parseInt(t / 60 % 60)
let s = parseInt(t % 60)
console.log(h, m, s)
h < 10 ? '0' + h : h //小于10补0
m < 10 ? '0' + m : m //小于10补0
s < 10 ? '0' + s : s //小于10补0
return `转换后是${h}小时${m}分${s}秒`
}
let str = getTime(ss) //实参
document.write(str)
</script>
12. 逻辑中断
1. && 一假则假,就后面不执行了
(应用场景:visible && < Form >....)没有数据或者没有点击弹框就不执行后面的表单
console.log(false&&20)//false
console.log(undefined&&20)//undefined 只是看成是false,但是返回还是原值
console.log(null&&20)//null
console.log(0&&20)//0
console.log(10&&20)//10
2. || 一真则真,后面面就不执行了
function getSum(x,y){
x=x||0
y=y||0
console.log(x+y)
}
原因:通过左边能得到整个式子的结果,因此没必要再判断右边
运算结果:无论 && 还是 || ,运算结果都是最后被执行的表达式值,一般用在变量赋值
3. ?? 为未定义(undifine)就执行后面的
( 应用场景:JSON.parse(tag??"[]") )
//前面为undifine时,就执行后面的,赋空值
12.1转换布尔值
记忆: ‘’ 、0、undefined、null、false、NaN 转换为布尔值后都是false, 其余则为 true
(应用场景:项目中,const cbf=!!cb?()=>{} 如果用户传的不是布尔值,是undifine情况就无法判断,但是通过隐式转换 !undifine = true !!undifine = false 是false就执行后面的回调函数)
隐式转换:
- 有字符串的加法 “” + 1 ,结果是 “1”
- 减法 - (像大多数数学运算一样)只能用于数字,它会使空字符串 “” 转换为 0
- null 经过数字转换之后会变为 0
- undefined 经过数字转换之后会变为 NaN
(1) +号两边只要有一个是字符串,都会把另外一个转成字符串
(2) 除了+以外的算术运算符 比如 - * / 等都会把数据转成数字类型
(3) +号作为**正号**解析可以转换成数字型
Number(null) //0
Number(undefined) //NaN
Number("hello") //NaN 因为它找不到对应的值
Number("") //0
Number(true) //1
Number("000011") //11 直接忽略前面的0
当然在平时项目中不用这么麻烦,常用的是用 +xx 来进行数字转换
+null //0
+fales //0
+undefined //NaN
+{} //NaN
+"" //0
+[] //0
+[0] //0
+[1] //1
+[1548] //1548 +对数组来说,里面有数字就是去数组的括号;没有数字就是0
12.2 typeof() 和 valueOf() 的区别及用法
typeof( )函数是判断类型,valueOf( )函数是得出来原始值。
举例:
var a = [2,3];
console.log(typeof(a)) // object (判断出a是对象类型的)
console.log(a.valueOf()); // [2,3] (获取参数a的值)
但是是一个经典的bug,a是数组,但是typeof()函数是把数组类型判断为对象object的,要判断是否为数组可以用Array.isArray()
函数,返回的是布尔值。
(null同理用typeof判断也是Object类型)
用法:
var a = [2,3];
console.log(Array.isArray(a)); // true
13.对象
对象:JavaScript里的一种数据类型,引用数据类型。对象是属性和方法组成的。
13.1 对象的使用:
let 对象名={ }
let 对象名=new Object()
对象本质是无序的数据集合, 操作数据无非就是 增 删 改 查
(1)查 对象.属性
(2)改 对象.属性=值
(3)增 对象.新属性名=新值
(4)删 delete 对象名.属性名
【对象的另一种查找】
let person={
'user-name':'佩奇',
age:18,
gender:'女',
123:'王德发'
}
person['user-name']// 佩奇
person['age']//18
person[123]//王德发
!!!属性中带字符串的
对象查 person['user-name'] 一定要加''
对象['属性'] 方式, 单引号和双引号都阔以,也可以用于其他正常属性
[]语法里面的值如果不添加引号 默认会当成变量解析
没有必要的时候直接使用点语法, 在需要解析变量的时候使用 [] 语法
对象【属性名】,【】里面可以是数字,字符串,symbol类型!!!
没有必要的时候直接使用点语法, 在需要解析变量的时候使用 [ ] 语法查找
例如:
//将上述的 'user-name' 属性赋给变量 a
let a = 'user-name'
person[a] 动态变量解析
在对象外面的叫函数,在对象里面的叫方法
【方法调用】
let obj={
uname:'刘德华',
age:18,
song:function(){
document.write('hi~')
}
}
obj.song()
let obj={
uname:'刘德华',
age:18,
song:function(x,y){
console.log(x+y)
},
dance:function(){ }
}
obj.song(1,2)//对象中方法的调用
13.2对象的遍历
for in 我们不推荐遍历数组
let arr =['pink','red','blue']
for(let k in arr){
console.log(k) //打印出的是数组的下标 索引号 但是是字符串 '0' '1'....
console.log(arr[k])//这个可以遍历 但是也是字符串
}
! ! ! 用 for in遍历对象
let obj={
uname:'刘德华',
age:18,
gender:'女'
}
//遍历对象
for (let k in obj){
console.log(k) //结果属性名 'uname' 'age'
}
//k里面存的是字符串!!!
conselo.log(obj.k) //报错 k里面存的字符串,直接对象.k会出问题
conselo.log(obj.'uname') //报错 k='uname'
console.log(obj[k]) //用属性是字符串语法去输出就没有问题,因为k本身带了字符串的,[]里面不用加''
!!!一定记住遍历对象中: k 是获得对象的属性名, 对象名[k] 是获得 属性值
【获得对象属性是 k】
【获得对象值是 obj[k]】
!!!遍历数组对象
<script>
//遍历数组对象
let student = [{
age: 18,
name: '小红',
gender: '女'
}, {
age: 20,
name: '小花',
gender: '女'
}, {
age: 19,
name: '小明',
gender: '男'
}]
//遍历数组用for循环,用for in 遍历对象
for (let i = 0; i < student.length; i++) {
console.log(i) //下标索引号 结果 0 1 2
console.log(student[i]) //每个对象 结果 { age: 18, name: '小红', gender: '女' } { age: 20, name: '小花', gender: '女' } { age: 19, name: '小明', gender: '男' }
console.loh(student[i].name) //对象属性 结果 小红 小花 小明
}
//当然也可以用map循环数组 (箭头函数,回调函数一定记得要写return)
student.map(item=>{return item.name}) //["小红","小花","小明"]
//不写return就简写,去大括号
student.map(item=>item.name)
</script>
<script>
//1.数据准备
let student = [{
age: 18,
name: '小红',
gender: '女'
}, {
age: 20,
name: '小花',
gender: '女'
}, {
age: 19,
name: '小明',
gender: '男'
}]
//2.渲染页面
//遍历数组对象
for (let i = 0; i < student.length; i++) {
document.write(`<tr>
<td>${i+1}</td>
<td>${student[i].name}</td>
<td>${student[i].gender}</td>
<td>${student[i].age}</td>
</tr>`)
}
</script>
13.3内置对象-Math
Math对象包含的方法有:
random:生成0-1之间的随机数(包含0不包括1) Math.random()
ceil:向上取整 Math.ceil(1.5) //2
floor:向下取整 这个有点与parseInt(12.1)//12 parseInt('12px') //12 里面可以是字符串
max:找最大数
min:找最小数
pow:幂运算
abs:绝对值
内置对象-生成任意范围随机数
//Math.random() 随机数函数, 返回一个0 - 1之间,并且包括0不包括1的随机小数 [0, 1) 左闭右开
如何生成0-10的随机数呢?
Math.floor(Math.random() * (10 + 1)) //0到10倍,因为要取到10,加一个1,0-11 又要整数floor向下取整
Math.floor(Math.random() * (11))
案例:数组中随机抽一个
let arr=['red','green','blue']
//let random=Math.floor(Math.random()*3)
let random=Math.floor(Math.random()*arr.length)//0-3 数组下标 0,1,2
console.log(arr[random])
如何生成5-10的随机数?
Math.floor(Math.random() * (5 + 1)) + 5
如何生成N-M之间的随机数
Math.floor(Math.random() * (M - N + 1)) + N
案例:随机取N~M的整数
function getRandom(N,M){
return Math.floor(Math.random() * (M - N + 1)) + N
}
console.log(getRandom(4,8))
随机点名案例:
需求:请把 [‘赵云’, ‘黄忠’, ‘关羽’, ‘张飞’, ‘马超’, ‘刘备’, ‘曹操’] 随机显示一个名字到页面中,但是
不允许重复显示
let arr=[‘赵云’, ‘黄忠’, ‘关羽’, ‘张飞’, ‘马超’, ‘刘备’, ‘曹操’]
//得到一个随机数,作为数组的索引号,这个随机数0-6
let random=Math.floor(Math.random()*arr.length)
//页面输出数组里面的元素
console.log(arr[random])
//随机一个,删一个
arr.splice(random,1)
猜数字游戏:
需求:程序随机生成 1~10 之间的一个数字,用户输入一个数字
//1.随机生成一个数字1-10
function getRandom(N,M){
return Math.floor(Math.random() * (M - N + 1)) + N
}
console.log(getRandom(1,10))
//2.用户输入一个值
let num=+prompt('请输入你猜的数字:')
//3.判断输出
while(true){
if(num>random){
aler('大了')
}else if(num<random){
aler('小了')
}else{
alert('对了')
break//猜对,退出循环
}
}
案例改进:设定三次,三次没猜对就直接退出
let flag=true
for(let i=1;i<=3;i++){
let num=+prompt('请输入1~10之间的数字')
if(num>random){
aler('大了')
}else if(num<random){
aler('小了')
}else{
flag=false
alert('对了')
break//猜对,退出循环
}
}
if(flag){
alert('次数已用完')
}
综合案例
<div></div>
<!-- 生成随机颜色需求:该函数接收一个布尔类型参数,表示颜色的格式是十六进制还是rgb格式。 -->
<script>
// 1.随机颜色函数
function getColor(flag = true) { //形参
if (flag) { //这里if要写活的判断,if(true)直接就定死了,因为你写了参数的嘛,肯定亚奥传参啊,比如flag有什么用
//3.判断如果是true,就是#ffffff形式
let arr = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f']
let str = '#'
for (let i = 0; i <= 6; i++) {
let random = Math.floor(Math.random() * arr.length)
str += arr[random]
}
return str
} else {
//4.判断如果是false,就是rgb(255,255,255)
let r = Math.floor(Math.random() * 256)
let g = Math.floor(Math.random() * 256)
let b = Math.floor(Math.random() * 256)
return `rgb(${r},${g},${b})`
}
}
//2.调用函数
//getColor(false)你光写个这个,没有打印,就调用函数,当然没有打印出来洛
console.log(getColor(true)) //实参
console.log(getColor(false))
console.log(getColor())
//获取div的背景样式
const box = document.querySelector('div')
box.style.backgroundColor = getColor()
</script>
14.基本数据类型和引用数据类型
简单类型又叫做基本数据类型或者值类型,复杂类型又叫做引用类型。
(1)值类型:简单数据类型/基本数据类型,在存储时变量中存储的是值本身,因此叫做值类型
string ,number,boolean,undefined,null
(2)引用类型:复杂数据类型,在存储时变量中存储的仅仅是地址(引用),因此叫做引用数据类型
通过 new 关键字创建的对象(系统对象、自定义对象),如 Object、Array、Date等
简单数据类型存放到栈里面
引用数据类型存放到堆里面
二、web APIS
1.let var const
(1)以后声明变量我们优先使用哪个?
const
有了变量先给const,如果发现它后面是要被修改的,再改为let
(2)为什么const声明的对象可以修改里面的属性?
因为对象是引用类型,里面存储的是地址,只要地址不变,就不会报错
建议数组和对象使用 const 来声明
(3)什么时候使用let声明变量?
如果基本数据类型的值或者引用类型的地址发生变化的时候,需要用let
比如 一个变量进行加减运算,比如 for循环中的 i++
2.DOM
文档对象模型,DOM是浏览器提供的一套专门用来 操作网页内容 的功能
DOM作用:开发网页内容特效和实现用户交互
DOM树:文档树直观的体现了标签与标签之间的关系
2.1 DOM对象(重要)
【1】DOM对象:浏览器根据html标签生成的 JS对象
所有的标签属性都可以在这个对象上面找到
修改这个对象的属性会自动映射到标签身上
【2】DOM的核心思想
把网页内容当做对象来处理
【3】document 对象
是 DOM 里提供的一个对象,整个页面最大的对象
所以它提供的属性和方法都是用来访问和操作网页内容的
例:document.write()
网页所有内容都在document里面
2.2 获取DOM对象
根据CSS选择器来获取DOM元素 (重点)
(1)选择匹配的第一个元素
语法:document.querySelector(‘css选择器’)
参数: 包含一个或多个有效的CSS选择器 字符串
返回值: CSS选择器匹配的第一个元素,一个 HTMLElement对象。如果没有匹配到,则返回null。
document.querySelector('div')
document.querySelector('#nav')
document.querySelector('.box')
document.querySelector('div')
document.querySelector('ul li')
(2) 选择匹配的多个元素
语法:document.querySelectorAll(‘css选择器’)
参数: 包含一个或多个有效的CSS选择器 字符串
返回值: CSS选择器匹配的NodeList 对象集合
(3)总结:
01.获取一个DOM元素我们使用谁?能直接操作修改吗?
querySelector() 可以
02.获取多个DOM元素我们使用谁?能直接修改吗? 如果不能可以怎么做到修改
querySelectorAll() 不可以, 只能通过遍历的方式一次给里面的元素做修改
03.document.querySelectorAll(‘css选择器’)
得到的是一个伪数组:
有长度有索引号的数组
但是没有 pop() push() 等数组方法
想要得到里面的每一个对象,则需要遍历(for)的方式获得
哪怕只有一个元素,通过querySelectAll() 获取过来的也是一个伪数组,里面只有一个元素而已
const lis=document.querySelectorAll('.nav li')
for(let i=0;i<lis.length;i++){
console.log(lis[i])//每一个小li对象
}
- 其他获取DOM元素方法(了解)
//根据Id获取一个元素
document.getElementById("nav")
//根据标签获取一类元素 获取页面上所有的div
document.getElementsByTagName("div")
//根基类名获取元素 获取页面所有类名为我w的
document.getElemntsByClassName("w")
3.操作元素内容
目标:能够修改元素的文本更换内容
如果想要修改标签元素的里面的内容,则可以使用如下几种方式:
3.1 对象.innerText 属性
将文本内容添加/更新到任意标签位置
显示纯文本,不解析标签
<div class='box'>我是文字内容</div>
//1.获取元素
const box=document.querySelector('.box')
//2.修改文字内容 对象.innerText
console.log(box.innerText) // 获取文字内容
box.innerText='我是一个盒子' // 修改文字内容
3.2 对象.innerHTML 属性
将文本内容添加/更新到任意标签位置
会解析标签,多标签建议使用模板字符
<div class='box'>我是文字内容</div>
//1.获取元素
const box=document.querySelector('.box')
//2.修改文字内容 对象.innerText
console.log(box.innerText) // 获取文字内容
box.innerText='<strong>我是一个盒子' // 修改文字内容
综合案例:需求:从数组随机抽取一等奖、二等奖和三等奖,显示到对应的标签里面。
<body>
<div class="wrapper">
<strong>年会抽奖</strong>
<h1>一等奖:<span id="one">???</span></h1>
<h3>二等奖:<span id="two">???</span></h3>
<h5>三等奖:<span id="three">???</span></h5>
</div>
<script>
//一等奖
//声明数组
const personArr = ['周杰伦', '刘德华', '周星驰', 'Pink老师', '张学友']
//随机抽
let random = Math.floor(Math.random() * personArr.length)
// console.log(personArr[random])
//获取对象
let one = document.querySelector('#one')
//操作对象
one.innerHTML = personArr[random]
//抽完删
personArr.splice(random, 1)
//二等奖
//随机抽
let random2 = Math.floor(Math.random() * personArr.length)
// console.log(personArr[random])
//获取对象
let two = document.querySelector('#two')
//操作对象
two.innerHTML = personArr[random2]
//抽完删
personArr.splice(random2, 1)
//三等奖
//随机抽
let random3 = Math.floor(Math.random() * personArr.length)
// console.log(personArr[random])
//获取对象
let three = document.querySelector('#three')
//操作对象
three.innerHTML = personArr[random3]
//抽完删
personArr.splice(random3, 1)
</script>
</body>
4.操作元素常用属性
4.1 操作元素常用属性
还可以通过 JS 设置/修改标签元素属性,比如通过 src更换 图片
最常见的属性比如: href、title、src 等
语法:对象.属性=值
//1.获取元素
const img=document.querySelector('img')
//2.修改图片对象的属性
img.src='./images/b02.jpg'
img.title='刘德华'
//案例:随机显示图片
<img src="./images/1.webp">
<script>
//取到N~M的随机整数
function getRandom(N, M) {
return Math.floor(Math.random() * (M - N + 1)) + N
}
//1.获取图片对象
const img = document.querySelector('img')
//2.随机得到序号
const random = getRandom(1, 6)
//3.更换路径
img.src = `./images/${random}.webp`
</script>
4.2 操作元素样式属性
还可以通过 JS 设置/修改标签元素的样式属性。
比如通过 轮播图小圆点自动更换颜色样式
点击按钮可以滚动图片,这是移动的图片的位置 left 等等
(1)通过 style 属性操作CSS
.box{
width:200px;
height:200px
background-color:pink
}
//1.获取元素
const box=document.querySelector('.box')
//2.修改样式属性 对象.style.样式属性='值' 别忘了跟单位
box.style.width='300px'
//!!!多组单词的采取 小驼峰命名法
box.style.backgroundColor='red'
box.style.borderTop='2px solid blue'
综合案例:页面刷新,页面随机更换背景图片
<style>
body {
background: url(./images/desktop_1.jpg) no-repeat top center/cover;
}
</style>
</head>
<body>
<script>
// console.log(document.body)
// 取到 N ~ M 的随机整数
function getRandom(N, M) {
return Math.floor(Math.random() * (M - N + 1)) + N
}
// 随机数
const random = getRandom(1, 10)
//标签选择body, 因为body是唯一的标签,可以直接写 document.body.style
document.body.style.backgroundImage = `url(./images/desktop_${random}.jpg)`
</script>
</body>
(2)操作类名(className) 操作CSS
如果修改的样式比较多,直接通过style属性修改比较繁琐,我们可以通过借助于css类名的形式。
-
由于class是关键字, 所以使用className去代替
-
className是使用新值换旧值, 如果需要添加一个类,需要保留之前的类名
.box{ width:200px; height:200px background-color:pink } <div></div> //1.获取元素 const big=docunment.querySelector('div') //2.添加类名 big.className='box' //多个的话 big.className='box nav'
(3)通过 classList 操作类控制CSS
为了解决className 容易覆盖以前的类名,我们可以通过classList方式追加和删除类名
//1.获取元素
const big=docunment.querySelector('div')
//2.修改样式
//2.1 追加类 add() 类名不加点,并且是字符串
big.classList.add('active')
//2.2 删除类 remove() 类名不加点,并且是字符串
big.classList.remove('box')
//2.3 切换类 toggle() 有还是没有,有就删掉,没有就加上
big.classList.toggle('active')
4.3 操作表单元素 属性
表单很多情况,也需要修改属性,比如点击眼睛,可以看到密码,本质是把表单类型转换为文本框
正常的有属性有取值的 跟其他的标签属性没有任何区别
获取: DOM对象.属性名
设置: DOM对象.属性名 = 新值
<input type="text" value="电脑">
//1.获取元素
const uname=document.querySelector('input')
//2.获取值
consloe.log(uname.value)//电脑
consloe.log(uname.type)
//3.设置表单值
uname.value='手机'
uname.type='password'
表单属性中添加就有效果,移除就没有效果,一律使用布尔值表示 如果为true 代表添加了该属性 如果是false 代表移除了该属性
比如: disabled、checked、selected
4.4 自定义属性
标准属性: 标签天生自带的属性 比如class id title等, 可以直接使用点语法操作比如: disabled、checked、
selected
自定义属性:
在html5中推出来了专门的data-自定义属性
在标签上一律以data-开头
在DOM对象上一律以dataset对象方式获取 / / dataset表示data的一个集合
<body>
<div data-id="1” data-spm="不知道">1</div>
<div data-id="2">2</div>
<div data-id="3">3</div>
<div data-id="4">4</div>
<div data-id="5">5</div>
<script>
const one = document,querySelector('div')
console.log(one.dataset.id) // 1
console.log(one.dataset.spm ) // 不知道
</script>
</body>
5.定时器-间歇函数
网页中经常会需要一种功能:每隔一段时间需要自动执行一段代码,不需要我们手动去触发
例如:网页中的倒计时
1. 开启定时器
setInterval(函数,间隔时间)
//第一种写法
setInterval(function(){
console.log('一秒执行一次')
},1000)
//第二种写法
function fn(){
console.log('一秒执行一次')
}
//setInterval(函数名,间隔时间)
setInterval(fn,1000)
//这里的函数不加括号,而是直接写函数名,fn()这样表示函数立即执行,但是定时器里面的函数,是每隔一段时间自动调用,所以不加括号。
定时器返回的是一个id数字
每一个定时器都是独一无二的的,都有自己的id,所以关定时器不要关错了
2. 关闭定时器
clearInterval(变量名)
let m=setInterval(function(){
console.log('一秒执行一次')
},1000)
clearInterval(m)
案例:阅读注册协议
需求:按钮60秒之后才可以使用
分析:
①:开始先把按钮禁用(
disabled 属性)
②:一定要获取元素
③:函数内处理逻辑
秒数开始减减
按钮里面的文字跟着一起变化
如果秒数等于0 停止定时器 里面文字变为 同意 最后 按钮可以点击
<!-- disabled按钮禁用 -->
<button class="btn" disabled>我已经阅读用户协议(60)</button>
<script>
//1.获取元素
let btn = document.querySelector('.btn')
//2.倒计时
let i = 60
//2.1开启定时器
let n = setInterval(function() {
i--
btn.innerHTML = `我已经阅读用户协议(${i})`
if (i === 0){
clearInterval(n) //关闭定时器
//定时器停了,我就可以开按钮
btn.disabled = false
btn.innerHTML = '同意'
}
}, 1000)
</script>
案例:轮播图定时版
//2.获取元素
const img = document.querySelector('.slider-wrapper img')
const p = document.querySelector('.slider-footer p')
let i = 0 //信号量 控制图片张数
//3.定时器
setInterval(function() {
i++
//大于数组长度,i又变回0
if (i >= sliderData.length) {
i = 0
}
//随机版是sliderData[random]
//定时版是自动加1,sliderData[i]
img.src = sliderData[i].url
p.innerHTML = sliderData[i].title
//先移除小li上的active quer这里要.active remove这里不用.active
document.querySelector('.slider-indicator .active').classList.remove('active')
//只让当前li添加active
document.querySelector(`.slider-indicator li:nth-child(${i+1})`).classList.add('active')
}, 1000)
6.事件监听
就是让程序检测是否有事件产生,一旦有事件触发,就立即调用一个函数做出响应,也称为 绑定事件或者注册事件。
比如鼠标经过显示下拉菜单,比如点击可以播放轮播图等等
元素对象.addEventListener(‘事件类型’,要执行的函数)
事件监听三要素:
Ø 事件源: 那个dom元素被事件触发了,要获取dom元素
Ø 事件类型: 用什么方式触发,比如鼠标单击 click、鼠标经过 mouseover 等
Ø 事件调用的函数: 要做什么事
<button>点击</button>
<script>
// 需求: 点击了按钮,弹出一个对活框
// 1.事件源 按钮
// 2.事件类型 点击鼠标 cLick 字符串
// 3.事件处理程序 弹出对话框
const btn = document.querySelector('button')
btn.addEventListener('click', function (){
alert('你早呀~')
})
</script>
注意:
- 事件类型要加引号
- 函数是点击之后再去执行,每次点击都会执行一次
案例:关闭广告
<div class="box">
我是广告
<div class="box1">X</div>
</div>
<script>
// 1. 获取事件源,要操作的dom
const box1 = document.querySelector('.box1')
// 关闭的是大盒子
const box = document.querySelector('.box')
// 2. 事件侦听
box1.addEventListener('click', function() {
box.style.display = 'none'
})
</script>
综合案例:随机点名案例
业务分析:
① 点击开始按钮随机抽取数组的一个数据,放到页面中
② 点击结束按钮删除数组当前抽取的一个数据
③ 当抽取到最后一个数据的时候,两个按钮同时禁用(写点开始里面,只剩最后一个数据不用抽了)
核心:利用定时器快速展示,停止定时器结束展示
<h2>随机点名</h2>
<div class="box">
<span>名字是:</span>
<div class="qs">这里显示姓名</div>
</div>
<div class="btns">
<button class="start">开始</button>
<button class="end">结束</button>
</div>
<script>
// 数据数组
const arr = ['马超', '黄忠', '赵云', '关羽', '张飞']
//定时器的全局变量,这样关闭定时器才能生效
let timeId = 0
//随机号要全局的random,因为下面关闭定时器也要用
let random = 0
//获取开始按钮
const start = document.querySelector('.start')
//获取元素
const qs = document.querySelector('.qs')
//开始按钮事件监听
start.addEventListener('click', function() {
//开始按钮定时器
timeId = setInterval(function() {
//随机抽取
random = Math.floor(Math.random() * arr.length)
qs.innerHTML = arr[random]
}, 35)
//如果数组里面只有一个值了,就不用抽了,解决最后空数组undefined的问题
if (arr.length === 1) {
start.disabled = true
end.disabled = true
}
})
//获取关闭按钮
const end = document.querySelector('.end')
//关闭定时器 clearInterval(变量名)
end.addEventListener('click', function() {
clearInterval(timeId)
//结束了,可以删除当前抽取的元素,
arr.splice(random, 1)
})
</script>
事件监听版本
DOM L0 事件源.on事件 = function() { } 例:btn.onClick=function(){}
DOM L2 事件源.addEventListener(事件, 事件处理函数)
区别:on方式会被覆盖,addEventListener方式可绑定多次,拥有事件更多特性,推荐使用
7.事件类型
const div=document.querySelector('div')
//鼠标经过
div.addEventLister('mouseenter',function(){
alert('我是盒子')
})
div.addEventLister('mouseleave',function(){
alert('我走了')
})
综合案例:轮播图点击切换
需求:当点击左右的按钮,可以切换轮播图
分析:
①:右侧按钮点击,变量++,如果大于等于8,则复原0
②:左侧按钮点击,变量–,如果小于0,则复原最后一张
③:鼠标经过暂停定时器
④:鼠标离开开启定时器
//获取元素
let img = document.querySelector('.slider-wrapper img')
let p = document.querySelector('.slider-footer p')
let footer = document.querySelector('.slider-footer')
//业务1 右侧按钮
//1.1获取按钮
let next = document.querySelector('.next')
//信号量
let i = 0
//1.2注册点击事件
next.addEventListener('click', function() {
i++
//1.5判断
if (i >= data.length) {
i = 0
}
commom()
})
//业务2 左侧按钮
//1.1获取按钮
let prev = document.querySelector('.prev')
//1.2注册点击事件
prev.addEventListener('click', function() {
i--
//1.5判断
if (i <= 0) {
i = data.length - 1
}
commom()
})
//左右按钮代码复用部分
function commom() {
//1.3渲染对应的数据
img.src = data[i].url
p.innerHTML = data[i].title
footer.style.backgroundColor = data[i].color
//1.4更换小圆点 先移除以前的小圆点 当前的小圆点再追加
document.querySelector('.slider-indicator .active').classList.remove('active')
document.querySelector(`.slider-indicator li:nth-child(${i+1})`).classList.add('active')
}
//3.自动播放模块
let v = setInterval(function() {
next.click() //js自动调用点击事件
}, 1000)
//4.鼠标经过大盒子,停止定时器
const slider = document.querySelector('.slider')
//注册鼠标事件
slider.addEventListener('mouseenter', function() {
//停止定时器
clearInterval(v)
})
//5.鼠标离开大盒子,开启定时器
//注册鼠标经过事件
slider.addEventListener('mouseleave', function() {
v = setInterval(function() {
next.click()
}, 1000)
})
</script>
小米搜索框案例
需求:当表单得到焦点,显示下拉菜单,失去焦点隐藏下来菜单
分析:
①:开始下拉菜单要进行隐藏
②:表单获得焦点 focus,则显示下拉菜单,并且文本框变色(添加类)
③:表单失去焦点,反向操作
<script>
//1.获取元素
const input = document.querySelector('[type=search]')
const ul = document.querySelector('.result-list')
//2.监听事件,获得焦点
input.addEventListener('focus', function() {
//ul显示
ul.style.display = 'block'
//添加一个带有颜色边框的类名
input.classList.add('search')
})
//3.监听事件,失去焦点
input.addEventListener('blur', function() {
ul.style.display = 'none' //ul隐藏
input.classList.remove('search') //移除样式
})
</script>
评论字数统计
需求:用户输入文字,可以计算用户输入的字数
分析:
①:判断用输入事件 input
②:不断取得文本框里面的字符长度, 文本域.value.length
③:把获得数字给下面文本框
<script>
const tx = document.querySelector('#tx')
const total = document.querySelector('.total')
// 1. 当我们文本域获得了焦点,就让 total 显示出来
tx.addEventListener('focus', function () {
total.style.opacity = 1
})
// 2. 当我们文本域失去了焦点,就让 total 隐藏出来
tx.addEventListener('blur', function () {
total.style.opacity = 0
})
// 3. 检测用户输入
tx.addEventListener('input', function () {
// console.log(tx.value.length) 得到输入的长度
total.innerHTML = `${tx.value.length}/200字`
})
// const str = 'andy'
// console.log(str.length)
</script>
8.事件对象
在事件绑定的回调函数的第一个参数就是事件对象, 一般命名为event、ev、e
常见事件对象属性
部分常用属性
(1)type 获取当前的事件类型
(2)clientX/clientY 获取光标相对于浏览器可见窗口左上角的位置
(3) offsetX/offsetY 获取光标相对于当前DOM元素左上角的位置
(4) key 用户按下的键盘键的值 现在不提倡使用keyCode
例子:e.key === ‘Enter’
案例:评论回车发布
需求:按下回车键盘,可以发布信息
分析:
①:用到按下键盘事件 keydown 或者 keyup 都可以
②:如果用户按下的是回车键盘,则发布信息
③:让留言信息模块显示,把拿到的数据渲染到对应标签内部
<script>
const tx = document.querySelector('#tx')
const total = document.querySelector('.total')
const item = document.querySelector('.item')
const text = document.querySelector('.text')
// 1. 当我们文本域获得了焦点,就让 total 显示出来
tx.addEventListener('focus', function() {
total.style.opacity = 1
})
// 2. 当我们文本域失去了焦点,就让 total 隐藏出来
tx.addEventListener('blur', function() {
total.style.opacity = 0
})
// 3. 检测用户输入
tx.addEventListener('input', function() {
// console.log(tx.value.length) 得到输入的长度
total.innerHTML = `${tx.value.length}/200字`
})
// 4. 按下回车发布评论
tx.addEventListener('keyup', function(e) {
// 只有按下的是回车键,才会触发
// console.log(e.key)
if (e.key === 'Enter') {
// 如果用户输入的不为空就显示和打印
if (tx.value.trim() != '') { //trim()方法清空左右的空格
// console.log(11)
item.style.display = 'block'
// console.log(tx.value) // 用户输入的内容
text.innerHTML = tx.value
}
// 等我们按下回车,结束,清空文本域
tx.value = ''
// 按下回车之后,就要把 字符统计 复原
total.innerHTML = '0/200字'
}
})
</script>
9.环境对象
环境对象: 指的是函数内部特殊的变量 this ,它代表着当前函数运行时所处的环境
函数的调用方式不同,this 指代的对象也不同
【谁调用, this 就是谁】 是判断 this 指向的粗略规则
直接用函数,其实相当于是 window.函数,所以 this 指代 window,普通函数里面的this指向window
10.回调函数
如果将函数 A 做为参数传递给函数 B 时,我们称函数 A 为回调函数
简单理解: 当一个函数当做参数来传递给另外一个函数的时候,这个函数就是回调函数
function fn(){
console.log('我是回调函数')
}
//fn 传递给了setInterval,fn就是回调函数
setInterval(fn,1000)
box.addEventListener('click',function(){
console.log('我是回调函数')
})
综合案例:Tab栏切换
需求:鼠标经过不同的选项卡,底部可以显示 不同的内容
分析:
①:主要核心是类的切换, 设定一个当前类,可以让当前元素高亮
②:鼠标经过当前选项卡,先移除其余元素身上的当前类,而只给当前元素添加类,
③:注意,当前类只能有一个
综合案例:表单全选反选按钮
<script>
// 1. 获取大复选框
const checkAll = document.querySelector('#checkAll')
// 2. 获取所有的小复选框
const cks = document.querySelectorAll('.ck')
// 3. 点击大复选框后再遍历里面的小复选框 注册事件
checkAll.addEventListener('click', function() {
// 得到当前大复选框的选中状态
// console.log(checkAll.checked) // 得到 是 true 或者是 false
// 4. 遍历所有的小复选框 让小复选框的checked = 大复选框的 checked
for (let i = 0; i < cks.length; i++) {
cks[i].checked = this.checked
// cks[i].checked = this.checked 可以写成this.checked,因为这里的this是checkAll调用的,this就指向checkAll
}
})
// 5. 小复选框控制大复选框
for (let i = 0; i < cks.length; i++) {
// 5.1 给所有的小复选框添加点击事件
cks[i].addEventListener('click', function() {
// 判断选中的小复选框个数 是不是等于 总的小复选框个数
// 一定要写到点击里面,因为每次要获得最新的个数
// console.log(document.querySelectorAll('.ck:checked').length)
// console.log(document.querySelectorAll('.ck:checked').length === cks.length)
checkAll.checked = document.querySelectorAll('.ck:checked').length === cks.length
})
}
</script>
全选文本框案例1
需求:用户点击全选,则下面复选框全部选择,取消全选则全部取消
分析:
①:全选复选框点击,可以得到当前按钮的 checked
②:把下面所有的小复选框状态checked,改为和全选复选框一致
//大复选框控制小复选框
// 1. 获取大复选框
const checkAll = document.querySelector('#checkAll')
// 2. 获取所有的小复选框
const cks = document.querySelectorAll('.ck')
// 3. 点击大复选框后再遍历里面的小复选框 注册事件
checkAll.addEventListener('click', function() {
// 得到当前大复选框的选中状态
// console.log(checkAll.checked) // 得到 是 true 或者是 false
// 4. 遍历所有的小复选框 让小复选框的checked = 大复选框的 checked
for (let i = 0; i < cks.length; i++) {
cks[i].checked = this.checked //大复选框控制小复选框
// cks[i].checked = this.checked 可以写成this.checked,因为这里的this是checkAll调用的,this就指向checkAll
}
})
全选文本框案例2
需求:用户点击全选,则下面复选框全部选择,取消全选则全部取消,文字对应变化
分析:
①:遍历下面的所有的checkbox,添加点击事件
②:检查小复选框选中的个数,是不是等于 小复选框总的个数,
③: 把结果给 全选按钮
④: 利用css 复选框选择器 input:checked
// 5. 小复选框控制大复选框
for (let i = 0; i < cks.length; i++) {
// 5.1 给所有的小复选框添加点击事件
cks[i].addEventListener('click', function() {
// 判断选中的小复选框个数 是不是等于 总的小复选框个数
// 一定要写到点击里面,因为每次要获得最新的个数
// console.log(document.querySelectorAll('.ck:checked').length)
// console.log(document.querySelectorAll('.ck:checked').length === cks.length)//已经选中的复选框长度是否等于小复选框总的长度
checkAll.checked = document.querySelectorAll('.ck:checked').length === cks.length //得到的结果是true or false,小复选框控制大复选框
})
}
11.事件流
事件流指的是事件完整执行过程中的流动路径
说明:假设页面里有个div,当触发事件时,会经历两个阶段,分别是捕获阶段(从外到里)、冒泡阶段(从里到外)
简单来说:捕获阶段是 从父到子 冒泡阶段是从子到父
实际工作都是使用事件冒泡为主
11.1事件冒泡
当一个元素触发事件后,会依次向上调用所有父级元素的 同名事件
事件冒泡是默认存在的,事件监听第三个参数是 false,或者默认都是冒泡
const father = document .querySelector(' .father" )
const son = document,querySelector('.son")
document.addEventListener("click",function () {
alert('我是爷爷")
}
fa.addEventListener( 'click', function (){
alert("我是爸爸")
}
son.addEventListener("click", function (e){
alert("我是儿子")
}
11.2阻止冒泡
(1) 阻止冒泡如何做? 事件对象.stopPropagation()
const father = document .querySelector(' .father" )
const son = document,querySelector('.son")
document.addEventListener("click",function () {
alert('我是爷爷")
}
fa.addEventListener( 'click', function (){
alert("我是爸爸")
}
// 需要事件对象
son.addEventListener("click", function (e){
alert("我是儿子")
}
//阻止片泡
e.stoppropagation()
我们某些情况下需要阻止默认行为的发生,比如 阻止 链接的跳转,表单域跳转
(2) 阻止元素默认行为如何做? e.preventDefault()
<form action="http://www.baidu.com>
<input type="submit"value="提交">
</form>
<script>
const form = document.querySelector( 'form')
form.addEventListener( 'click', function (e) {
// 阻止表单默认提交行为
e.preventDefault()
})
11.3 事件解绑
on事件方式,直接使用null覆盖偶就可以实现事件的解绑
// 绑定事件
btn.onclick = function () {
alert("点击了)
}
// 解绑事件
btn.onclick = null
addEventListener方式,必须使用:
removeEventListener(事件类型, 事件处理函数, [获取捕获或者冒泡阶段])
function fn() {
alert("点击了")
}
// 绑定事件
btn.addEventListener('click',fn)
// 解绑事件
btn.removeEventListener('click',fn)
注意:匿名函数无法被解绑
11.4鼠标经过事件的区别
鼠标经过事件:
(1) mouseover 和 mouseout 会有冒泡效果
(2) mouseenter 和 mouseleave 没有冒泡效果 (推荐)
11.5两种注册事件的区别
(1)传统on注册
-
同一个对象,后面注册的事件会覆盖前面注册(同一个事件)
-
直接使用null覆盖偶就可以实现事件的解绑
-
都是冒泡阶段执行的
(2)事件监听注册
-
语法:
addEventListener(事件类型, 事件处理函数, 是否使用捕获)
-
后面注册的事件不会覆盖前面注册的事件(同一个事件)
-
可以通过第三个参数去确定是在冒泡或者捕获阶段执行
-
必须使用removeEventListener(事件类型, 事件处理函数, 获取捕获或者冒泡阶段)
-
匿名函数无法被解绑
12.事件委托
思考:
- 如果同时给多个元素注册事件,我们怎么做的?
for循环注册事件
<ul>
<li>我是第1个小li</li>
<li>我是第2个小li</1i>
<li>我是第3个小li</li>
<li>我是第4个小li</li>
<li>我是第5个小li</li>
</u1>
const lis = document.querySelectorA11( 'ul li')
for (let i = ; i < lis.length; i++) {
lis[i].addEventListener('click',function () {
alert(我被点击了)
})
}
- 有没有一种技巧 注册一次事件就能完成以上效果呢?
事件委托是利用事件流的特征解决一些开发需求的知识技巧
- 优点:减少注册次数,可以提高程序性能
- 原理:事件委托其实是利用事件冒泡的特点。
- 给父元素注册事件,当我们触发子元素的时候,会冒泡到父元素身上,从而触发父元素的事件
- 实现:事件对象.target. tagName 可以获得真正触发事件的元素
目标:能够说出事件委托的好处
ul.addEventListener(‘click’ , function(){}) 执行父级点击事件
const ul = document.querySelector( 'ul')
//事件委托是委托给父级
ul.addEventListener('click',function (e) {
if (e.target.tagName === 'LI') {//真正触发的元素
this.style.color = 'pink'
}
})
案例:事件委托tab栏切换
需求:优化程序,将tab切换案例改为事件委托写法
思路:
①:给a的父级 注册点击事件,采取事件委托方式
②: 如果点击的是A , 则进行排他思想,删除添加类
③: 注意判断的方式 利用 e.target.tagName
④: 因为没有索引号了,所以这里我们可以自定义属性,给5个链接添加序号
⑤: 下面大盒子获取索引号的方式 e.target.dataset.id 号, 然后进行排他思想
<body>
<div class="tab">
<div class="tab-nav">
<h3>每日特价</h3>
<ul>
//自定义属性
<li><a class="active" href="javascript:;" data-id='0'>精选</a></li>
<li><a href="javascript:;" data-id='1'>美食</a></li>
<li><a href="javascript:;" data-id='2'>百货</a></li>
<li><a href="javascript:;" data-id='3'>个护</a></li>
<li><a href="javascript:;" data-id='4'>预告</a></li>
</ul>
</div>
<div class="tab-content">
<div class="item active"><img src="./images/tab00.png" alt="" /></div>
<div class="item"><img src="./images/tab01.png" alt="" /></div>
<div class="item"><img src="./images/tab02.png" alt="" /></div>
<div class="item"><img src="./images/tab03.png" alt="" /></div>
<div class="item"><img src="./images/tab04.png" alt="" /></div>
</div>
</div>
<script>
//采取事件委托的形式tab栏切换
//1.获取ul父元素,因为ul只有一个
const ul = document.querySelector('.tab-nav ul')
//2.添加事件
ul.addEventListener('click', function(e) {
// console.log(e.target.tagName) //e.target.tagName 是我们点击的那个对象的 标签名
if (e.target.tagName === 'A') {
//排他思想,先移除原来的active
document.querySelector('.tab-nav .active').classList.remove('active')
//当前元素添加active
//this 指向的是ul 不能用this
e.target.classList.add('active')
//下面大盒子区域
console.log(e.target.dataset.id)
const i = +e.target.dataset.id //前面写个加号转成数字型
//排他思想,先移除
document.querySelector('.tab-content .active').classList.remove('active') //classlist的属性不要点
//对应的大盒子添加active 这里之所以上面的a链接与下面的item对应,是因为a链接也有i或者id数组标号,下面的item也有class的数组标号,比如.item:nth-child(${i+1})
document.querySelector(`.tab-content .item:nth-child(${i + 1})`).classList.add('active') //classlist的属性不要点
}
})
</script>
</body>
</html>
<!-- 自定义属性
<li><a href="javascript:;" data-id='0'>美食</a></li>
<script>
const div=document.querySelector('div')
console.log(div.dataset.id)//0 dataset是所有自定义属性的集合
</script>
前面鼠标事件就有i来for循环得到每个dom,现在没有i了,直接自定义属性找id
-->
13.其他事件
13.1 页面加载事件
(1) 事件名:load
加载外部资源(如图片、外联CSS和JavaScript等)加载完毕时触发的事件
为什么要学?
有些时候需要等页面资源全部处理完了做一些事情
老代码喜欢把 script 写在 head 中,这时候直接找 dom 元素找不到
监听页面所有资源加载完毕:
给 window 添加 load 事件
//等待页面所有资源加载完毕,就回去执行回调函数
window.addEventListener('load',function(){
const btn=document.querySelector('button')
btn.addEventListener('click',function(){
alert(11)
})
})
注意:不光可以监听整个页面资源加载完毕,也可以针对某个资源绑定load事件
img.addEventListener('load',function(){})
(2) 事件名:DOMContentLoaded
当初始的 HTML 文档被完全加载和解析完成之后,DOMContentLoaded 事件被触发,而无需等待样式表、图像等完全加载
监听页面DOM加载完毕:
给 document 添加 DOMContentLoaded 事件
//等待页面所有DOM加载完毕,就回去执行回调函数
document.addEventListener('DOMContentLoaded',function(){
const btn=document.querySelector('button')
btn.addEventListener('click',function(){
alert(11)
})
})
13.2 页面滚动事件 事件名:scroll
滚动条在滚动的时候持续触发的事件
为什么要学?
很多网页需要检测用户把页面滚动到某个区域后做一些处理, 比如固定导航栏,比如返回顶部监听整个页面滚动
//页面滚动事件
window.addEventListener('scroll',function(){
console.log('滚动了')
})
给 window 或 document 添加 scroll 事件,监听某个元素的内部滚动直接给某个元素加即可
scrollLeft和scrollTop (属性)
获取被卷去的大小, 获取元素内容往左、往上滚出去看不到的距离
这两个值是可读写的
document.documentElement .scrollTop=800
(滚动条初始值就在800px处)
尽量在scroll事件里面获取被卷去的距离
// 页面滚动事件
window.addEventListener( 'scroll', function () {
// document.documentELement 是htmL元素获取方式
const n = document.documentElement .scrollTop
console.log(n) //得到的是数字型的,不带单位
})
综合案例:小兔仙电梯
需求:当页面滚动大于300像素的距离时候,就显示侧边栏,否则隐藏侧边栏
分析:
①:需要用到页面滚动事件
②:检测页面被卷去的头部,如果大于300,就让侧边栏显示
③:显示和隐藏配合css过渡,利用opacity加渐变效果
// 获取元素
const entry = document.querySelector('.xtx_entry')
const elevator = document.querySelector('.xtx-elevator')
// 1. 当页面滚动大于 300像素,就显示 电梯导航
// 2. 给页面添加滚动事件
window.addEventListener('scroll', function () {
// 被卷去的头部大于 300
const n = document.documentElement.scrollTop
// if (n >= 300) {
// elevator.style.opacity = 1
// } else {
// elevator.style.opacity = 0
// }
// 简写
elevator.style.opacity = n >= entry.offsetTop ? 1 : 0
})
// 点击返回页面顶部
const backTop = document.querySelector('#backTop')
backTop.addEventListener('click', function () {
// 可读写
// document.documentElement.scrollTop = 0
// window.scrollTo(x, y)
window.scrollTo(0, 0)
})
页面滚动事件-滚动到指定的坐标 scrollTo() 方法可把内容滚动到指定的坐标
语法:元素.scrollTo(x, y)
13.3 页面尺寸事件
13.3.1 resize
会在窗口尺寸改变的时候触发事件:resize
检测屏幕宽度:
window.addEventListener('resize', function ()
let w = document.documentElement.clientwidth
console.log(w)
})
13.3.2 clientWidth和clientHeight
获取元素的可见部分宽高(不包含边框,margin,滚动条等)
13.3.3 offsetWidth和offsetHeight
获取宽高: 获取元素的自身宽高、包含元素自身设置的宽高、padding、border
获取出来的是数值,方便计算
注意: 获取的是可视宽高, 如果盒子是隐藏的,获取的结果是0
13.3.4 offsetLeft和offsetTop
获取位置:获取元素距离自己定位父级元素的左、上距离。注意是只读属性
案例:仿京东导航栏滚动隐藏:
总结:
综合案例
电梯导航
补充:属性选择器
14.日期对象
获取当前时间
const date= new Date( )
获取指定时间
const date= new Date('2008-8-8')
14.1 日期对象方法
注意:从0开始的都要加1
案例
需求:将当前时间以:YYYY-MM-DD HH:mm 形式显示在页面 2008-08-08 08:08
①:调用日期对象方法进行转换
②:记得数字要补0
③:字符串拼接后,通过 innerText 给 标签
<div></div>
<script>
const div = document.querySelector('div')
function getMyDate() {
const date = new Date()
let h = date.getHours()
let m = date.getMinutes()
let s = date.getSeconds()
h = h < 10 ? '0' + h : h
m = m < 10 ? '0' + m : m
s = s < 10 ? '0' + s : s
return `今天是${date.getFullYear()}年${date.getMonth()+1}月${date.getDay()}日 ${h}:${m}:${s}`
}
div.innerHTML = getMyDate()
//让时间动起来 加定时器
setInterval(function() {
div.innerHTML = getMyDate()
}, 1000)
</script>
14.2 时间戳
使用场景: 如果计算倒计时效果,前面方法无法直接计算,需要借助于时间戳完成
什么是时间戳: 是指1970年01月01日00时00分00秒起至现在的毫秒数,它是一种特殊的计量时间的方式
算法:
-
将来的时间戳 - 现在的时间戳 = 剩余时间毫秒数
-
剩余时间毫秒数 转换为 剩余时间的 年月日时分秒 就是 倒计时时间
-
比如 将来时间戳 2000ms - 现在时间戳 1000ms = 1000ms, 1000ms 转换为就是 0小时0分1秒
三种方式获取时间戳:
es中获取的时间戳是以毫秒为单位的。
// 1. 实例化
const date = new Date()
// 2. 获取时间戳
console.log(date.getTime())
// 还有一种获取时间戳的方法
console.log(+new Date())
// 还有一种获取时间戳的方法
console.log(Date.now())
获取时间戳的三种方法:
-
getTime():可以返回指定时间的时间戳
-
+new Date() :重点记住 +new Date() 因为可以返回当前时间戳或者指定的时间戳
-
Date.now() :无需实例化, 但是只能得到当前的时间戳, 而前面两种可以返回指定时间的时间戳
案例:毕业倒计时效果
需求:计算到下课还有多少时间
分析:
①:用将来时间减去现在时间就是剩余的时间
②:核心: 使用将来的时间戳减去现在的时间戳
③:把剩余的时间转换为 天 时 分 秒
注意:通过时间戳得到是毫秒,需要转换为秒在计算转换公式:
- d = parseInt(总秒数/ 60/60 /24); // 计算天数
- h = parseInt(总秒数/ 60/60 %24) // 计算小时
- m = parseInt(总秒数 /60 %60 ); // 计算分数
- s = parseInt(总秒数%60); // 计算当前秒数
<body>
<div class="countdown">
<p class="next">今天是2222年2月22日</p>
<p class="title">下班倒计时</p>
<p class="clock">
<span id="hour">00</span>
<i>:</i>
<span id="minutes">25</span>
<i>:</i>
<span id="scond">20</span>
</p>
<p class="tips">18:30:00下课</p>
</div>
<script>
// 随机颜色函数
// 1. 自定义一个随机颜色函数
function getRandomColor(flag = true) {
if (flag) {
// 3. 如果是true 则返回 #ffffff
let str = '#'
let arr = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f']
// 利用for循环随机抽6次 累加到 str里面
for (let i = 1; i <= 6; i++) {
// 每次要随机从数组里面抽取一个
// random 是数组的索引号 是随机的
let random = Math.floor(Math.random() * arr.length)
// str = str + arr[random]
str += arr[random]
}
return str
} else {
// 4. 否则是 false 则返回 rgb(255,255,255)
let r = Math.floor(Math.random() * 256) // 55
let g = Math.floor(Math.random() * 256) // 89
let b = Math.floor(Math.random() * 256) // 255
return `rgb(${r},${g},${b})`
}
}
// 页面刷新随机得到颜色
const countdown = document.querySelector('.countdown')
countdown.style.backgroundColor = getRandomColor()
// 函数封装 getCountTime
function getCountTime() {
// 1. 得到当前的时间戳
const now = +new Date()
// 2. 得到将来的时间戳
const last = +new Date('2022-4-1 18:30:00')
// console.log(now, last)
// 3. 得到剩余的时间戳 count 记得转换为 秒数
const count = (last - now) / 1000
// console.log(count)
// 4. 转换为时分秒
// h = parseInt(总秒数 / 60 / 60 % 24) // 计算小时
// m = parseInt(总秒数 / 60 % 60); // 计算分数
// s = parseInt(总秒数 % 60);
// let d = parseInt(count / 60 / 60 / 24) // 计算当前秒数
let h = parseInt(count / 60 / 60 % 24)
h = h < 10 ? '0' + h : h
let m = parseInt(count / 60 % 60)
m = m < 10 ? '0' + m : m
let s = parseInt(count % 60)
s = s < 10 ? '0' + s : s
console.log(h, m, s)
// 5. 把时分秒写到对应的盒子里面
document.querySelector('#hour').innerHTML = h
document.querySelector('#minutes').innerHTML = m
document.querySelector('#scond').innerHTML = s
}
// 先调用一次
getCountTime()
// 开启定时器
setInterval(getCountTime, 1000)
</script>
</body>
15.节点操作
-
什么是DOM 节点?
DOM树里每一个内容都称之为节点
-
DOM节点的分类?
-
元素节点: 比如 div标签
-
属性节点: 比如 class属性
-
文本节点: 比如标签里面的文字
我们重点记住那个节点?
元素节点
可以更好的让我们理清标签元素之间的关系
15.1 查找节点
1.父节点查找:
子元素.parentNode
<body>
<div class="yeye">
<div class="dad">
<div class="baby">x</div>
</div>
</div>
<script>
const baby = document.querySelector('.baby')
console.log(baby) // 返回dom对象
console.log(baby.parentNode) // 返回dom对象
console.log(baby.parentNode.parentNode) // 返回dom对象,爸爸得爸爸=>爷爷
</script>
</body>
案例:关闭广告
<body>
<div class="box">
我是广告
<div class="box1">X</div>
</div>
<div class="box">
我是广告
<div class="box1">X</div>
</div>
<div class="box">
我是广告
<div class="box1">X</div>
</div>
<script>
// // 1. 获取事件源
// const box1 = document.querySelector('.box1')
// // 2. 事件侦听
// box1.addEventListener('click', function () {
// this.parentNode.style.display = 'none'
//
// 1. 获取三个关闭按钮
const closeBtn = document.querySelectorAll('.box1')
for (let i = 0; i < closeBtn.length; i++) {
closeBtn[i].addEventListener('click', function () {
// 关闭我的爸爸 所以只关闭当前的父元素
this.parentNode.style.display = 'none'
})
}
</script>
</body>
2.子节点查找:
父亲.children 属性 (重点)
3.兄弟关系查找:
(1) 下一个兄弟节点
nextElementSibling 属性
(2)上一个兄弟节点
previousElementSibling 属性
<body>
<ul>
<li>1</li>
<li>2</li>
<li>3</li>
<li>4</li>
<li>5</li>
</ul>
<script>
// const ul = document.querySelector('ul') // ul
// console.log(ul.children) // 得到伪数组 选择的是 亲儿子
const li2 = document.querySelector('ul li:nth-child(2)')
console.log(li2.previousElementSibling) // 上一个兄弟
console.log(li2.nextElementSibling) // 下一个兄弟
</script>
</body>
15.2增加节点
1.创建节点
document.createElement(“标签名”)
2.追加节点
(1)要想在界面看到,还得插入到某个父元素中 。插入到父元素的最后一个子元素:
父元素.appenChild(要插入的元素)
(2)插入到父元素中某个子元素的前面
父元素.insertBefore(要插入的元素,在哪个元素前面)
<body>
<ul>
<li>我是老大</li>
</ul>
<script>
// // 1. 创建节点
// const div = document.createElement('div')
// // console.log(div)
// 2. 追加节点 作为最后一个子元素
// document.body.appendChild(div)
const ul = document.querySelector('ul')
const li = document.createElement('li')
li.innerHTML = '我是li'
// ul.appendChild(li)
// ul.children
// 3. 追加节点
// insertBefore(插入的元素, 放到哪个元素的前面)
ul.insertBefore(li, ul.children[0])
</script>
</body>
案例:学成在线案例渲染
3.克隆节点
特殊情况下,我们新增节点,按照如下操作:
-
复制一个原有的节点
-
把复制的节点放入到指定的元素内部
元素.cloneNode(布尔值) -
cloneNode会克隆出一个跟原标签一样的元素,括号内传入布尔值
-
若为true,则代表克隆时会包含后代节点一起克隆
-
若为false,则代表克隆时不包含后代节点
-
默认为false
15.3删除节点
若一个节点在页面中已不需要时,可以删除它
在 JavaScript 原生DOM操作中,要删除元素必须通过父元素删除
父元素.removeChild(要删除的元素)
-
如不存在父子关系则删除不成功
-
删除节点和隐藏节点(display:none) 有区别的: 隐藏节点还是存在的,但是删除,则从html中删除节点
16.M端事件
移动端也有自己独特的地方。比如触屏事件 touch(也称触摸事件),Android 和 IOS 都有。
touch 对象代表一个触摸点。触摸点可能是一根手指,也可能是一根触摸笔。触屏事件可响应用户手指(或触控笔)对屏幕或者触控板操作。
常见的触屏事件如下:
17.swiper插件
https://www.swiper.com.cn/
综合案例:学生信息表案例
核心思路:
①: 声明一个空的数组
②: 点击录入,根据相关数据,生成对象,追加到数组里面
③: 根据数组数据渲染页面-表格的行
④: 点击删除按钮,删除的是对应数组里面的数据
⑤: 再次根据数组的数据,渲染页面
18.BOM
BOM(Browser Object Model ) 是浏览器对象模型
window对象是一个全局对象,也可以说是JavaScript中的顶级对象
像document、alert()、console.log()这些都是window的属性,基本BOM的属性和方法都是window的。
所有通过var定义在全局作用域中的变量、函数都会变成window对象的属性和方法
window对象下的属性和方法调用的时候可以省略window
19.定时器-延时函数setTimeout
JavaScript 内置的一个用来让代码延迟执行的函数,叫 setTimeout
(1)延时函数: setTimeout(回调函数,等待的毫秒数)
setTimeout 仅仅只执行一次,所以可以理解为就是把一段代码延迟执行, 平时省略window
(2)清除延时函数: clearTimeout(函数名):
let timer=setTimeout(回调函数,等待的毫秒数)
clearTimeout(timer)
注意点
-
延时器需要等待,所以后面的代码先执行
-
每一次调用延时器都会产生一个新的延时器
(3)两种定时器对比: 执行的次数
setTimeout(回调函数,等待的毫秒数) 延时函数: 执行一次
setInterval(函数,间隔时间) 间歇函数:每隔一段时间就执行一次,除非手动清除
20.JS执行机制
两个都是 111 333 222
(1)JavaScript 语言的一大特点就是单线程,也就是说,同一个时间只能做一件事。
这是因为 Javascript 这门脚本语言诞生的使命所致——JavaScript 是为处理页面中用户的交互,以及操作DOM 而诞生的。比如我们对某个 DOM 元素进行添加和删除操作,不能同时进行。 应该先进行添加,之后再删除。
单线程就意味着,所有任务需要排队,前一个任务结束,才会执行后一个任务。这样所导致的问题是:
如果 JS 执行的时间过长(就比如上述的setTimeout),
这样就会造成页面的渲染不连贯,导致页面渲染加载阻塞的感觉。
(2)为了解决这个问题,利用多核 CPU 的计算能力,HTML5 提出 Web Worker 标准,允许 JavaScript 脚本创建多个线程。于是,JS 中出现了同步和异步。
- 同步
前一个任务结束后再执行后一个任务,程序的执行顺序与任务的排列顺序是一致的、同步的。比如做饭的同
步做法:我们要烧水煮饭,等水开了(10分钟之后),再去切菜,炒菜。
- 异步
你在做一件事情时,因为这件事情会花费很长时间,在做这件事的同时,你还可以去处理其他事
情。比如做饭的异步做法,我们在烧水的同时,利用这10分钟,去切菜,炒菜。
他们的本质区别: 这条流水线上各个流程的执行顺序不同。
(3)同步任务
同步任务都在主线程上执行,形成一个执行栈。
异步任务
JS 的异步是通过回调函数实现的。异步任务相关添加到任务队列中(任务队列也称为消息队列)。
一般而言,异步任务有以下三种类型:
1、普通事件,如 click、resize 等
2、资源加载,如 load、error 等
3、定时器,包括 setInterval、setTimeout 等
(4)js中的执行机制( 重要!)
- 先执行执行栈中的同步任务。
- 异步任务放入任务队列中。
- 一旦执行栈中的所有同步任务执行完毕,系统就会按次序读取任务队列中的异步任务,于是被读取的异步任务结束等待状态,进入执行栈,开始执行
由于主线程不断的重复获得任务、执行任务、再获取任务、再执行,所以这种机制被称为事件循环( event loop )。
思考:
1 2 4 3 或者 1 2 3 4
要看click事件和setTimeout哪个先执行(如果用户3秒后才click,那就是setTimeout先执行)
21.location对象
location 的数据类型是对象,它拆分并保存了 URL 地址的各个组成部分
常用属性和方法:
Ø href 属性获取完整的 URL 地址,对其赋值时用于地址的跳转
Ø search 属性获取地址中携带的参数,符号 ?后面部分
Ø hash 属性获取地址中的啥希值,符号 # 后面部分
Ø reload 方法用来刷新当前页面,传入参数 true 时表示强制刷新(相当于Ctrl + f5)
(1)location.href
案例:5秒钟之后跳转的页面
需求:用户点击可以跳转,如果不点击,则5秒之后自动跳转,要求里面有秒数倒计时
分析:
①:目标元素是链接
②:利用定时器设置数字倒计时
③:时间到了,自动跳转到新的页面
(2)location.search
(3)location.hash
后期vue路由的铺垫,经常用于不刷新页面,显示不同页面,比如 网易云音乐
(4)location.reload
reload 方法用来刷新当前页面,传入参数 true 时表示强制刷新
22.navigator对象
navigator的数据类型是对象,该对象下记录了浏览器自身的相关信息
常用属性和方法:
通过 userAgent 检测浏览器的版本及平台
23.histroy对象
history 的数据类型是对象,主要管理历史记录, 该对象与浏览器地址栏的操作相对应,如前进、后退、历史记录等
常用属性和方法:
history 对象一般在实际开发中比较少用,但是会在一些 OA 办公系统中见到。
24.本地存储介绍
1、数据存储在用户浏览器中
2、设置、读取方便、甚至页面刷新不丢失数据
3、容量较大,sessionStorage和localStorage约 5M 左右
24.1本地存储分类- localStorage
作用: 可以将数据永久存储在本地(用户的电脑), 除非手动删除,否则关闭页面也会存在
特性:
可以多窗口(页面)共享(同一浏览器可以共享)
以键值对的形式存储使用
三大API
存储数据:localStorage.setItem(key, value)
获取数据:localStorage.getItem(key)
删除数据:localStorage.removeItem(key)
//1.存储
//localStorage.setItem(键, 值)
localStorage.setItem('uname','刘德华')
//2.获取方式 都要加冒号
console.log(localStorage.getItem('uname')
//3.删除
localStorage.removeItem('uname')
//4.改
localStorage.setItem('uname','周润发')
浏览器存的都是字符串类型的。
24.2本地存储分类- sessionStorage
特性:
- 生命周期为关闭浏览器窗口
- 在同一个窗口(页面)下数据可以共享
- 以键值对的形式存储使用
- 用法跟localStorage 基本相同
24.3存储复杂数据类型
(1)本地只能存储字符串,无法存储复杂数据类型
解决: 需要将复杂数据类型转换成JSON字符串,在存储到本地
语法:JSON.stringify(复杂数据类型)
将复杂数据转换成JSON字符串 存储 本地存储中
(2)问题: 因为本地存储里面取出来的是字符串,不是对象,无法直接使用
解决: 把取出来的字符串转换为对象
语法:JSON.parse(JSON字符串)
将JSON字符串转换成对象 取出 时候使用
总结:存取复杂数据类型
- 存:JSON.stringify(obj)将复杂数据类型转成字符串
- 取:JSON.pares(obj) 将字符串转成对象类型
25.综合案例——学生就业信息表
25.1 数组map方法
map 可以遍历数组处理数据,并且返回新的数组 (遍历数组,对数据进行加工)
const arr=["red","blue","pink"]
const newArr=arr.map((item,index)=>{
console.log(item)//数组元素
console.log(index)//索引号
return item+'颜色'
})
console.log(newArr)//["red颜色","blue颜色","pink颜色"]
map重点在于有返回值,forEach没有返回值
25.2 数组join方法
- 作用: join()方法用于把数组中的所有元素转换一个字符串
- 语法:
const arr=["red","blue","pink"]
console.log(arr.join(''))// redbLuegreen
console.log(arr.join(','))//red,bLue,green
console.log(arr.join())//red,bLue,green
- 参数: 数组元素是通过参数里面指定的分隔符进行分隔的,空字符串 (‘’),则所有元素之间都没有何字符。什么都不传默认以逗号拼接
(最好不要用map去循环遍历数组,因为违背了map设计的初衷了,map是遍历处理数组,返回一个新数组)
26.正则表达式
正则表达式(Regular Expression)是用于匹配字符串中字符组合的模式。在 JavaScript中,正则表达式也是对象
通常用来查找、替换那些符合正则表达式的文本
场景:
1.如验证表单
2.过滤掉页面内容中的一些敏感词(替换)
3.从字符串中获取我们想要的特定部分(提取)等 。
(1)语法:
-
定义规则 const 变量名=/ 表达式 /
-
查找
2.1 test() 方法 用来查看正则表达式与指定的字符串是否
如果正则表达式与指定的字符串匹配 ,返回true,否则false
2.2 exec() 方法 在一个指定字符串中执行一个搜索匹配
如果匹配成功,exec() 方法返回一个数组,否则返回null
26.1 元字符
是一些具有特殊含义的字符,可以极大提高了灵活性和强大的匹配功能,规定用户只能输入英文26个英文字母,换成元字符写法: [a-z]
参考文档:
MDN:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Guide/Regular_Expressions
正则测试工具: http://tool.oschina.net/regex
(1)元字符分类
- 边界符(表示位置,开头和结尾,必须用什么开头,用什么结尾)
- 量词 (表示重复次数)
- 字符类 (比如 \d 表示 0~9)
26.1.1 边界符
正则表达式中的边界符(位置符)用来提示字符所处的位置,主要有两个字符
如果 ^ 和 $ 在一起,表示必须是精确匹配。
(必须一模一样)
console.log(/^哈$/.test('哈'))//true
console.log(/^哈$/.test('哈哈'))//false
26.1.2 量词
量词用来 设定某个模式出现的次数
注意: 逗号左右两侧千万不要出现空格
26.1.3 字符类 (比如 \d 表示 0~9)
(1) [ ] 匹配字符集合:后面的字符串只要包含 abc 中任意一个字符,都返回 true 。
注意:加了精确匹配也就是 /^ $/ 这种就是从[ ]这个范围内只选一个
(2) [ ] 里面加上 - 连字符。使用连字符 - 表示一个范围
[a-z] 表示 a 到 z 26个英文字母都可以
[a-zA-Z] 表示大小写都可以
[0-9] 表示 0~9 的数字都可以
注意:加了精确匹配也就是 /^ $/ 这种就是从[ ]这个范围内只选一个
注意:{4,}重复的是离它最近的
(3) [ ] 里面加上 ^ 取反符号
【^a-z】匹配除了小写字母以外的字符。注意要写到中括号里面
(4) . 匹配除换行符之外的任何单个字符
(5) 预定义
案例:
26.2 修饰符
修饰符约束正则执行的某些细节行为,如是否区分大小写、是否支持多行匹配等
语法: / 表达式 / 修饰符
i 是单词 ignore 的缩写,正则匹配时字母不区分大小写
g 是单词 global 的缩写,匹配所有满足正则表达式的结果
替换 replace 替换
注意:如果不写全局,就只能匹配一个
27.综合案例
注意:通过一个变量来控制用户重复点击!
这就是为什么前面的效验函数要返回一个状态了(true or false)
28.阶段性案例
三、JS进阶
1.作用域
1.1局部作用域
局部作用域分为函数作用域和块作用域。
1. 函数作用域:
在函数内部声明的变量只能在函数内部被访问,外部无法直接访问
function getSum(){
//函数内部是函数作用域 属于局部变量
const num=10
}
console.log(num)//此处报错 函数外部不能使用局部作用域变量
- 函数内部声明的变量,在函数外部无法被访问
- 函数的参数也是函数内部的局部变量
- 不同函数内部声明的变量无法互相访问
- 函数执行完毕后,函数内部的变量实际被清空了
2. 块作用域:
在 JavaScript 中使用 { } 包裹的代码称为代码块,代码块内部声明的变量外部将【有可能】无法被访问。
for(let t=1;t<=6;t++){
//t 只能在该代码块中被访问
console.log(t)//正常
}
//超出了t的作用域
console.log(t)//报错
总结:
- let 声明的变量会产生块作用域,var 不会产生块作用域
- const 声明的常量也会产生块作用域
- 不同代码块之间的变量无法互相访问
- 推荐使用 let 或 const
1.2全局作用域
<script>
标签 和 .js 文件 的【最外层】就是所谓的全局作用域,在此声明的变量在函数内部也可以被访问。
全局作用域中声明的变量,任何其它作用域都可以被访问
<script>
//全局作用域
//全局作用域下声明了num变量
const num=10
function fn(){
//函数内部可以使用全部作用域的变量
console.log(num)
}
//此处全局作用域
</script>
注意:
- 为 window 对象动态添加的属性默认也是全局的,不推荐!
- 函数中未使用任何关键字声明的变量为全局变量,不推荐!!!
- 尽可能少的声明全局变量,防止全局变量被污染
2.作用域链
<script>
//全局作用域
let a=1
let b=2
//局部作用域
function f(){
let a=1
//局部作用域
function g(){
a=2
console.log(a)
}
g() //调用g
}
f() //调用f
</script>
结果是:2 就近原则
作用域链本质上是底层的变量查找机制。
- 在函数被执行时,会优先查找当前函数作用域中查找变量
- 如果当前作用域查找不到则会依次逐级查找父级作用域直到全局作用域
总结:
- 嵌套关系的作用域串联起来形成了作用域链
- 相同作用域链中按着从小到大的规则查找变量
- 子作用域能够访问父作用域,父级作用域无法访问子级作用域
3.垃圾回收机制
1. 什么是垃圾回收机制?
垃圾回收机制(Garbage Collection) 简称 GC
JS中内存的分配和回收都是自动完成的,内存在不使用的时候会被垃圾回收器自动回收。
正因为垃圾回收器的存在,许多人认为JS不用太关心内存管理的问题
但如果不了解JS的内存管理机制,我们同样非常容易成内存泄漏(内存无法被回收)的情况
不再用到的内存,没有及时释放,就叫做内存泄漏
2.内存的生命周期
JS环境中分配的内存, 一般有如下生命周期:
- 内存分配:当我们声明变量、函数、对象的时候,系统会自动为他们分配内存
- 内存使用:即读写内存,也就是使用变量、函数等
- 内存回收:使用完毕,由垃圾回收自动回收不再使用的内存
说明:
- 全局变量一般不会回收(关闭页面回收);
- 一般情况下局部变量的值, 不用了, 会被自动回收,无法回收的叫内存泄漏
拓展-JS垃圾回收机制-算法说明
堆栈空间分配区别:
- 栈(操作系统): 由操作系统自动分配释放函数的参数值、局部变量等,基本数据类型放到栈里面。
- 堆(操作系统): 一般由程序员分配释放,若程序员不释放,由垃圾回收机制回收。复杂数据类型放到堆里面。
下面介绍两种常见的浏览器垃圾回收算法: 引用计数法 和 标记清除法
3.1 引用计数
IE采用的引用计数算法, 定义“内存不再使用”,就是看一个对象是否有指向它的引用,没有引用了就回收对象
算法:
- 跟踪记录被引用的次数
- 如果被引用了一次,那么就记录次数1,多次引用会累加 ++
- 如果减少一个引用就减1 –
- 如果引用次数是0 ,则释放内存
但它却存在一个致命的问题:嵌套引用(循环引用)
如果两个对象相互引用,尽管他们已不再使用,垃圾回收器不会进行回收,导致内存泄露。
function fn(){
let o1={}
let o2={}
o1.a=o2
o2.a=o1
return "引用计数无法回收"
}
fn()
因为他们的引用次数永远不会是0。这样的相互引用如果说很大量的存在就会导致大量的内存泄露
3.2 标记清除法
现代的浏览器已经不再使用引用计数算法了。
现代浏览器通用的大多是基于标记清除算法的某些改进算法,总体思想都是一致的。
核心:
- 标记清除算法将 “不再使用的对象” 定义为 “无法达到的对象” 。
- 就是从 根部 (在JS中就是全局对象)出发定时扫描内存中的对象。 凡是能从 根部到达 的对象,都是还需要使用的。
- 那些无法由根部出发触及到的对象被标记为不再使用,稍后进行回收。
4.闭包
概念:一个函数对周围状态的引用捆绑在一起,内层函数中访问到其外层函数的作用域
(一个函数对周围状态的引用捆绑在一起。
实际案例:解决用户重复点击问题,用一个变量进行控制)
简单理解:闭包 = 内层函数 + 外层函数的变量
闭包实质是让变量持久化
function outer(){
const a=1
function f(){
console.log(a)
}
f()
}
outer()
里层函数运用到外层的变量,两个捆绑到一起就叫闭包。如果里层函数没有用到外层变量,两个是独立的,就不交闭包。
// 常见的闭包的形式 外部可以访问使用 函数内部的交量
function outer() (
let a = 10
function fn() {
console.log(a)
}
return fn //闭包的基本格式 return 一个函数
}
// outer() === fn === function fn(){}
//const fun = function fn() { }
const fun = outer()
fun() // 调用函数,结果是10
// 外面要使用这个 10
函数外部要使用函数内部的变量,就是闭包,闭包的实质就是让变量持久化。此处就是外面调用内部函数里面的变量10。
闭包作用:封闭数据,提供操作,外部也可以访问函数内部的变量
闭包的基本格式(重要):
function outer(){
let i=1
function fn(){
console.log(i)
}
return fn //闭包的基本格式 return 一个函数
}
const fun=outer()
fun()//1
//外部函数使用内部函数的变量
//简约语法
function outer(){
let i=1
return function(){ //闭包的基本格式 return 一个函数
console.log(i)
}
}
const fun=outer()
fun()//1
闭包应用:实现数据的私有
比如,我们要做一个统计函数调用的次数,函数调用一次,就++
let count =1
function fn(){
count++
console.log(`函数调用${count}次`)
}
fn()//2
fn()//3
(因为这里的count是全局变量,都可以使用,但是很容易被修改)
function fn(){
let count =1
function fun(){
count++
console.log(`函数调用${count}次`)
}
return fun
}
const result=fn()
result()//2
result()//3
这样就实现了数据私有,无法直接修改count
总结:
- 怎么理解闭包?
闭包 = 内层函数 + 外层函数的变量
- 闭包的作用?
封闭数据,实现数据私有,外部也可以访问函数内部的变量
闭包很有用,因为它允许将函数与其所操作的某些数据(环境)关联起来
- 闭包可能引起的问题?
内存泄漏
【闭包理解】
- 这里的函数f()可以访问到父级作用域是因为作用域链的原因
function outer(){
const a=1
function f(){
console.log(a)
}
f()
}
outer()
2.闭包的妙用:
案例:解决用户连续点击问题 (重要!!!)
const getData = async () => {
return new Promise((resolve,reject)=>{
let e=setTimeout(()=>{
resolve(123)
clearTimeout(e)
},1000)
})
}
const getClick=()=>{
let isPadding:Boolean=false;
const fn = () =>{ //闭包的基本语法:闭包是函数套函数,所以要函数返回函数,也就是定义两个函数,具名函数要return 函数名,匿名函数直接 return function(){}
if(isPadding=true) return; //如果状态是true就不执行getData()点击的事件
isPadding=true;//false时点击了变为true(在它是false时,改变状态,一定在getData执行之前变成true,不然就没有机会变成true了。)
//getData().then .catch 发送请求是异步的,如果点击了,状态变为true,异步请求的服务还没有返回回来,就一直执行前面两步代码,(true 不执行,变为true,又不执行,)重复执行前两步
getData().then(res=>{ //这里异步请求返回了,代表一次请求结束了
alert(res);
isPadding=false;//状态变为false,又可以点击了,又可以执行了。
})
.catch(res=>{
isPadding=false;
});
};
return fn;//具名函数要return 函数名
}
const getFun=getClick();//把闭包赋给一个常量,直接使用。
这里为什么要用闭包:闭包就是一个函数对周围状态的引用捆绑在一起。内层函数中访问到其外层函数的作用域
2.闭包的妙用:
- 实现数据私有化:
例如解决用户重复点击的问题,需要一个变量进行控制点击状态,isPadding=true or false
,在闭包里面声明,该变量内存地址始终不变,实现了数据私有化,不被全局变量改变、避免全局变量污染。
- 变量持久化:
函数fn()一直在使用父级作用域的isPadding变量,导致变量一直在使用,根据标记清除法,不会被垃圾回收掉,使变量持久化。
- 函数缓存(记忆函数)
对于计算量大的函数应用场景,闭包有缓存,避免重复计算执行
5.变量提升
变量提升是 JavaScript 中比较“奇怪”的现象,它允许在变量声明之前即被访问(仅存在于var声明变量)
注意:
-
变量在未声明即被访问时会报语法错误
-
变量在var声明之前即被访问,变量的值为 undefined
-
let/const 声明的变量不存在变量提升
-
变量提升出现在相同作用域当中
-
实际开发中推荐先声明再访问变量
说明
JS初学者经常花很多时间才能习惯变量提升,还经常出现一些意想不到的bug,正因为如此,ES6 引入了块级作用域,
用let 或者 const声明变量,让代码写法更加规范和人性化
总结:
-
用哪个关键字声明变量会有变量提升?
var
-
变量提升是什么流程?
- 先把var 变量提升到当前作用域于最前面
- 只提升变量声明, 不提升变量赋值
- 然后依次执行代码
我们不建议使用var声明变量
6.函数提升
函数提升与变量提升比较类似,是指函数在声明之前即可被调用
1.具名函数是存在函数提升的,声明了一个函数,可以在任何地方调用此函数
//调用函数
foo()
//声明函数
function foo(){
console.log("声明之前被调用...")
}
2.函数表达式不存在函数提升 函数表达式必须先声明后调用。
箭头函数和匿名函数赋值给了一个变量,就是函数表达式,就不存在函数提升
const fn=function(){
console.log(111)
}
fn() //结果正常 111
fn()//报错,函数表达式不存在函数提升
const fn=function(){
console.log(111)
}
//同理:箭头函数
const fn=()=>{
console.log(111)
}
fn()//结果正常 111
fn()//报错,函数表达式不存在函数提升
const fn=()=>{
console.log(111)
}
3.var 声明的函数表达式
bar()//错误 相当于undifine()
var bar=function(){
console.log(111)
}
//此段代码的执行顺序
var bar //var存在变量提升,提升在此作用域最前面
bar() //此时函数只声明了,但没赋值(没有函数方法),也就是undifine,undifine()报错!
bar=function(){
console.log(111)
}
总结:
- 函数提升能够使函数的声明调用更灵活
- 函数表达式不存在提升的现象
- 函数提升出现在相同作用域当中
7.函数参数
产品需求: 写一个求和函数
不管用户传入几个实参,都要把和求出来
getSum(2,3)
getSum(1,2,3)
getSum(1,2,3,4,5,6)
形参我改咋写?
1. 动态参数
arguments 是函数内部内置的伪数组变量,它包含了调用函数时传入的所有实参
//求和函数,计算所有参数的和
function sum(){
let s = 0
for(let i=0;i<arguments.length;i++){
s+=arguments[i]
}
console.log(s)
}
//调用求和函数
sum(5,10)//两个参数
sum(1,2,3)//三个参数
总结:
-
arguments 是一个伪数组,只存在于函数中
-
arguments 的作用是动态获取函数的实参
-
可以通过for循环依次得到传递过来的实参
(因为是伪数组,所以不能用map等数组的常用方法,且箭头函数不能使用)
2. 剩余参数
剩余参数允许我们将一个不定数量的参数表示为一个数组
function getSum(...other){
//other 得到[1,2,3]
console.log(other)
}
getSum(1,2,3)
-
… 是语法符号,置于最末函数形参之前,用于获取多余的实参
-
借助 … 获取的剩余实参,是个 真数组
function config(baseURL,...other){//接的时候要写...
console.log(baseURL) //'http://baidu.com'
console.log(other)//使用的时候不用写... //['get','json']
}
//调用函数
config('http://baidu.com','get','json')
开发中,还是提倡多使用 剩余参数。
3.展开运算符 (与剩余参数相似的)
展开运算符(…),将一个数组进行展开
const arr=[1,2,3,4]
console.log(...arr) //1 2 3 4
说明:
- 不会修改原数组
展开运算符(…),将一个数组进行展开
典型运用场景: 求数组最大值(最小值)、合并数组等
const arr=[1,5,3,8,2]
console.log(...arr)//1 5 3 8 2
console.log(Math.max(...arr))//8
console.log(Math.min(...arr))//1
//合并数组
const arr1=[1,2,3]
const arr2=[4,5,6]
cosnt arr3=[...arr1,...arr2]
展开运算符 or 剩余参数
- 剩余参数:函数参数使用,得到真数组
function getSum(...other){
//得到[1,2,3]
console.log(other)
}
getSum(1,2,3)
- 展开运算符:数组中使用,数组展开
const arr=[1,5,3,8,2]
console.log(...arr)//1 5 3 8 2
写法上的区别要注意:一个是形参的时候写…,一个是调用的时候写…(实参)
8.箭头函数(重要)
目的:引入箭头函数的目的是更简短的函数写法并且不绑定this,箭头函数的语法比函数表达式更简洁
//箭头函数简洁的语法非常适合嵌入函数的场景
let arr=[1,2,3]
console.log(arr.map.(function(item){return item+1}));
console.log(arr.map.((item)=>{return item+1}));
使用场景:箭头函数更适用于那些本来需要匿名函数的地方,也就是更适合写函数表达式的地方。如果要声明一个有名字的新函数,还是正常写普通函数。
8.1. 基本语法
- 语法1:基本写法
//普通函数
const fn = function(){
console.log("我是普通函数")
}
fn()
//箭头函数
const fn=()=>{
console.log("我是箭头函数")
}
fn()
- 语法2:只有一个参数可以省略小括号
//普通函数
const fn = function(x){
return x++
}
fn(1)
//箭头函数
const fn=x=>{
return x++
}
fn(1)
- 语法3:如果函数体只有一行代码,可以省略大括号,写到一行上,并且无需写 return 直接返回值
//普通函数
const fn = function(x,y){
return x+y
}
fn(1,2)
//箭头函数
const fn=(x,y)=> x+y
fn(1,2)
//更简洁的语法
const form=document.querySelector('form')
form.addEventListener('click',e=>e.preventDefault())//这里本来是匿名函数的,现在用箭头函数取代了
- 语法4:加括号的函数体返回对象字面量表达式(如果想返回一个对象,就加括号)
总结:
-
箭头函数属于表达式函数,因此不存在函数提升
-
箭头函数只有一个参数时可以省略圆括号 ()
-
箭头函数函数体只有一行代码时可以省略花括号 {},并自动做为返回值被返回
-
加括号的函数体返回对象字面量表达式
8.2. 箭头函数参数
-
普通函数有arguments 动态参数
-
箭头函数没有 arguments 动态参数,但是有 剩余参数 …args
const getSum=(...args)=>{
let sum=0
for(let i=0;i<args.length;i++){
sum+=args[i]
}
return sum //注意函数体有多行代码需要return
}
console.log(getSum(1,2,3))//6
8.3. 箭头函数this
在箭头函数出现之前,每一个新函数根据它是被如何调用的来定义这个函数的this值, 非常令人讨厌。
箭头函数不会创建自己的this,它只会从自己的作用域链的上一层沿用this。
console.log(this)//此处为window
const sayHi=function(){
console.log(this)//普通函数指向调用者 此处为window
}
sayHi()//实际上是window.sayHi,我们只是把它省略了
btn.addEventListener('click',function(){
console.log(this)//当前this 指向btn
)}
总结:
-
箭头函数里面有this吗?
箭头函数不会创建自己的this,它只会从自己的作用域链的上一层沿用this
-
DOM事件回调函数推荐使用箭头函数吗?
不太推荐,特别是需要用到this的时候 事件回调函数使用箭头函数时,this 为全局的 window
9.解构赋值
解构赋值是一种快速为变量赋值的简洁语法,本质上仍然是为变量赋值
9.1 数组解构
数组解构是将数组的单元值快速批量赋值给一系列变量的简洁语法。
基本语法:
-
赋值运算符 = 左侧的 [ ] 用于批量声明变量,右侧数组的单元值将被赋值给左侧的变量
-
变量的顺序对应数组单元值的位置依次进行赋值操作
const arr=[100,60,80]
const max=arr[0]
const min=arr[1]
const avg=arr[2]
//解构赋值
const arr=[100,60,80]
const [max,min,avg]=arr
//长什么样,解构就什么样
const [a,b,[c,d]]=[1,2,[4,7]]
console.log(c)//4
基本语法:典型应用交互2个变量
(优化了之前两个变量交换的流程)
还有一些情况:
- 变量少 单元值多的情况
- 利用剩余参数解决变量少 单元值多的情况
- 防止有undefined传递单元值的情况,可以设置默认值
- 按需导入,忽略某些返回值
- 支持多维数组的结构
总结:
1. 变量的数量大于单元值数量时,多余的变量将被赋值为?
undefined
//变量多,单元值少
const [a,b,c,d]=['花花','和叶','萌兰']
console.log(a)//花花
console.log(b)//和叶
console.log(c)//萌兰
console.log(d)//undefined
2. 变量的数量小于单元值数量时,可以通过什么剩余获取所有的值?
剩余参数… 获取剩余单元值,但只能置于最末位
//利用剩余参数 变量少,单元值多
const [a,b,...c]=['花花','和叶','萌兰','飞云']
console.log(a)//花花
console.log(b)//和叶
console.log(c)//['萌兰','飞云']
9.2对象解构(常用)
对象解构是将对象属性和方法快速批量赋值给一系列变量的简洁语法
1.基本语法:
- 赋值运算符 = 左侧的 {} 用于批量声明变量,右侧对象的属性值将被赋值给左侧的变量
- 对象属性的值将被赋值给与属性名相同的变量
- 注意解构的变量名不要和外面的变量名冲突否则报错
- 对象中找不到与变量名一致的属性时变量值为 undefined
// 普通对象
const user = {
name:'小明',
age: 18
};
// 批量声明变量 name age
// 同时将数组单元值 小明 18 依次赋值给变量 name age
//等价于 const name=user.name const age=user.age
const {name, age} = user //注意解构的变量名要与对象的属性名相同!!!
console.log(name) // 小明
console.log(age) // 18
2.给新的变量名赋值
可以从一个对象中提取变量并同时修改新的变量名
// 普通对象
const user = {
name:小明
age: 18
// 把原来的 name 变量重新命名为 uname
const {name:uname,age }= user
console.1og(uname)//小明
console.log(age) // 18
【注意】
重命名: name:uname
旧变量名:新变量名
赋值:pageIndex=pageSize
被赋值变量=值
3.数组对象解构
const pig =[
{
name:佩奇
age: 6
}
]
const [{name,age}]=pig
console.log(name,age)
4.多级对象解构:
const pig = {
name: '佩奇'
family: {
mother:"猪妈妈"
father: "猪爸爸"
sister:"乔治"
}
age: 6
}
const {name,family:{mother,father,sister}}=pig
console.1og(name)//佩奇
console.log(mother) // 猪妈妈
console.log(father) // 猪爸爸
console.log(sister) // 乔治
5.多级数组对象解构
const pig = [{
name: '佩奇'
family: {
mother:"猪妈妈"
father: "猪爸爸"
sister:"乔治"
}
age: 6
}
]
//注意:对象没有顺序,解构改变位置也没有关系
//const [{name,age,family:{mother,father,sister}}]=pig
const [{name,family:{mother,father,sister}},age]=pig
console.1og(name)//佩奇
console.log(mother) // 猪妈妈
console.log(father) // 猪爸爸
console.log(sister) // 乔治
常用接口返回的数据,取有用的数据
10.遍历数组 forEach 方法 (重点)
- forEach() 方法用于调用数组的每个元素,并将元素传递给回调函数
被遍历的数组.forEach(function (当前数组元素,当前元素索引号){
//函数体
})
注意:
- forEach 主要是遍历数组,不返回新数组,map会返回加工后的新数组
- 参数当前数组元素是必须要写的,索引号可选。
- 被遍历的对象越是复杂,越要用forEach
const pig = [{
name: '佩奇'
family: {
mother:"猪妈妈"
father: "猪爸爸"
sister:"乔治"
}
age: 6
},
"华华",
{old:88}
]
pig.forEach((item,index)=>{
console.log(item) //{ name: '佩奇', family: { mother: '猪妈妈', father: '猪爸爸', sister:"乔治" }, age: 6 } '华华' {old:88}
console.log(index)
})
11.筛选数组 filter 方法 (重点)
- filter()方法创建一个新的数组,新数组中的元素是通过检查指定数组中符合条件的所有元素
- 主要使用场景:筛选数组符合条件的元素,并返回筛选之后元素的新数组
- 语法:
被遍历的数组.filter(function (currentValue,index){
return 筛选条件
})
// 筛选数组中人于30的元系
const score = [10,50, 3, 40, 33]
const re = score.filter(function (item) {
return item > 39
})
console.log(re) // [50,40,33]
12.深入对象
12.1创建对象三种方式
1.利用对象字面量创建对象
const obj ={
name:"佩奇"
}
2. 利用 new Object 创建对象
const obj=new Object({
name:"佩奇"
})
console.log(obj)
3. 利用构造函数创建对象
使用场景:常规的 {…} 语法允许创建一个对象。比如我们创建了佩奇的对象,继续创建乔治的对象还需要重新写一遍,此时可以通过构造函数来快速创建多个类似的对象
构造函数在技术上是常规函数。
不过有两个约定:
-
它们的命名以大写字母开头。
-
它们只能由 “new” 操作符来执行。
function Pig(name,age){
//这是对象的属性 这是形参
this.name = name
this.age = age
}
console.log(new Pig('佩奇',6)) //Pig { uname: '佩奇', age: 6 }
说明:
- 使用 new 关键字调用函数的行为被称为实例化
- 实例化构造函数时没有参数时可以省略 ()
- 构造函数内部无需写return,返回值即为新创建的对象
- 构造函数内部的 return 返回的值无效,所以不要写return
- new Object() new Date() 也是实例化构造函数
实例化执行过程
说明:
- 创建新对象
- 构造函数this指向新对象
- 执行构造函数代码,修改this,添加新的属性
- 返回新对象
//1.创建构造函数
function Pig(name){
this.name=name
}
//2.new 关键字调用函数
//new Pig("佩奇")
//接受创建对象
const peppa=new Pig("佩奇")
console.log(peppa)// {name:"佩奇"}
12.2 实例成员&静态成员
实例成员:
通过构造函数创建的对象称为实例对象,实例对象中的属性和方法称为实例成员。
//1.实例成员:实例对象上的属性和方法属于实例成员
function Pig(name){
this.name=name
}
const a=new Pig('佩奇')
const b=new Pig('乔治')
console.log(a=b) //false 构造函数创建的实例对象彼此独立互不影响
a.name='小猪佩奇'//实例属性
a.sayHi=()=>{ //实例方法
console.log('hi')
}
console.log(a)// Pig{name:"小猪佩奇",sayHi:f}
console.log(b)// Pig{name:"乔治"}
说明:
- 实例对象的属性和方法即为实例成员
- 为构造函数传入参数,动态创建结构相同但值不同的对象
- 构造函数创建的实例对象彼此独立互不影响。
静态成员:
构造函数的属性和方法被称为静态成员
//构造函数
function Person(name,age){
//省略实例成员
}
//静态属性
Person.eyes=2
Person.arms=2
//静态方法
Person.walk=function(){
console.log("走路")
//this 指向Person
console.log(this.eyes)
}
说明:
- 构造函数的属性和方法被称为静态成员
- 一般公共特征的属性或方法静态成员设置为静态成员
- 静态成员方法中的 this 指向构造函数本身
总结:
- 什么是实例成员?
实例对象的属性和方法即为实例成员- 什么是静态成员?
构造函数的属性和方法被称为静态成员
13.内置构造函数
在 JavaScript 中最主要的数据类型有 6 种:
基本数据类型:
字符串、数值、布尔、undefined、null
引用类型:
对象
在js中一般只有对象这种引用数据类型(复杂类型)才有属性和方法,但是很多时候number和string这些基本数据类型也有自己的属性方法,是因为js底层完成,把简单数据类型包装为了引用数据类型
const str='red'
console.log(red.length)
//js底层完成,把简单数据类型包装为了引用数据类型
//基于构成函数创建 基本包装类型
const str=new String('red')
其实字符串、数值、布尔、等基本类型也都有专门的构造函数,这些我们称为包装类型
JS中几乎所有的数据都可以基于构成函数创建。
13.1 Object
-
什么是静态方法?
只能给构造函数使用的方法 比如
Object.keys()
,实例成员无法调用; -
Object.keys()
方法的作用是什么 ?获取对象中所有属性 (键)
const o={name:'佩奇',age:6}
//获得对象的所有键,并且返回的是一个数组
const arr=Objiect.keys(o) //注意:是Objiect.keys,不是o.keys,实例成员无法调用;
console.log(arr)//['name','age']
返回的是一个数组
-
Object.values()
方法的作用是什么 ?获取对象中所有属性值 (值)
const o={name:'佩奇',age:6}
//获得对象的所有键,并且返回的是一个数组
const arr=Objiect.values(o)
console.log(arr)//['佩奇',6]
返回的是一个数组
4.Object.assign()
静态方法常用于对象拷贝
//拷贝对象 把o 拷贝给obj
const o={name:'佩奇',age:6}
const obj={}
Object.assign(obj,o)//被拷贝的放在后面
console.log(obj)//{name:"佩奇",age:6}
使用:经常使用的场景给对象添加属性
const o={name:'佩奇',age:6}
Object.assign(o,{sex:"女"})
console.log(o)//{name:"佩奇",age:6,sex:"女"}
总结:
在es6出来之前遍历对象,获取对象的属性和值,需要用 for in
遍历对象;es6推出了Object.keys()、bject.values()
操作方便;
13.2 Array
创建数组(创建数组建议使用字面量创建,不用 Array构造函数创建)
01. let array = [1, 2, 3, 4, 5];
02. let array = new Array(1, 2, 3, 4, 5)
数组常用核心方法:
13.1.1 reduce
- 作用:reduce 返回函数累计处理的结果,经常用于求和等。
reduce()
方法对数组中的每个元素按序执行一个提供的 reducer 函数,每一次运行 reducer 会将先前元素的计算结果作为参数传入,最后将其结果汇总为单个返回值。
语法:
两个参数,一个是回调函数,一个是初始值(可选)
回调函数里面有两个参数(一个是上一次的值,一个是当前值)
reduce(callbackFn)//回调函数
reduce(callbackFn, initialValue)//初始值
reduce(()=>{},初始值) //如果不写初始值,默认以第一个值为初始值
arr.reduce(function(){},起始值)
arr.reduce(function(上一次值,当前值){},初始值)
用法:
- 无初始值
//无初始值
const array = [15, 16, 17, 18, 19];
function reducer(accumulator, currentValue, index) {
const returns = accumulator + currentValue;
console.log(
`accumulator: ${accumulator}, currentValue: ${currentValue}, index: ${index}, returns: ${returns}`,
);
return returns;
}
array.reduce(reducer);
次数 | accumulator | currentValue | index | 返回值 |
---|---|---|---|---|
第一次调用 | 15 | 16 | 1 | 31 |
第二次调用 | 31 | 17 | 2 | 48 |
第三次调用 | 48 | 18 | 3 | 66 |
第四次调用 | 66 | 19 | 4 | 85 |
- 有初始值
//有初始值
[15, 16, 17, 18, 19].reduce((accumulator, currentValue) => accumulator + currentValue, 10);//初始值10
次数 | accumulator | currentValue | index | 返回值 |
---|---|---|---|---|
第一次调用 | 10 | 15 | 0 | 25 |
第二次调用 | 25 | 16 | 1 | 41 |
第三次调用 | 41 | 17 | 2 | 58 |
第四次调用 | 58 | 18 | 3 | 76 |
第五次调用 | 76 | 19 | 4 | 95 |
const arr=[1,2,3,4,5,6]
console.log(arr.reduce((pre,cur)=>pre+cur,10))//31
console.log(arr.reduce((pre,cur)=>pre+cur))//21
应用一:
const data=[
{
name:'aa',
salary:10000
},
{
name:'bb',
salary:12000
},
{
name:'cc',
salary:13000
},
]
//注意点1: 初始值问题
data.reduce((pre,cur)=>{
console.log(pre)//{ name: 'aa',salary: 10000 }
})//如果不写初始值,pre是一个对象,对象不可以累加
//注意点2:如果没声明变量承接,返回的pre是上一次的值,结果是: 22000
data.reduce((pre,cur)=>{
return pre += cur.salary
},0))//结果是: 22000
//注意点3:声明变量承接,结果是:35000
const a = data.reduce((pre,cur)=>{
console.log(pre)//0
console.log(cur)//当前对象
return pre += cur.salary
},0))
console.log(a);//结果是: 35000
应用二:数组去重:
const myArray = ["a", "b", "a", "b", "c", "e", "e", "c", "d", "d", "d", "d"];
const newArray = myArray.reduce((pre, cur) => {
if (!pre.includes(cur)) {
return [...pre, cur];//...pre展开运算符,[...pre, cur]合并数组
}
return pre;
}, []);//初始值为一个空数组
console.log(newArray);
总结:数组常见方法-其他方法
实例方法 join 数组元素拼接为字符串,返回字符串(重点)。
实例方法 find 查找元素,返回符合测试条件的第一个数组元素值,否则返回undifine。
实例方法 every 方法测试一个数组内的所有元素是否都能通过指定函数的测试。它返回一个布尔值。
实例方法 some 检测数组中的元素是否满足指定条件,如果在数组中找到一个元素使得提供的函数返回 true,则返回 true;否则返回 false。它不会修改数组。
实例方法 concat 合并两个数组,返回生成新数组。
实例方法 sort 对原数组单元值排序。
实例方法 splice 删除或替换原数组单元。
实例方法 reverse 反转数组。
实例方法 findIndex 查找元素的索引值,否则返回-1。
- find和filter的区别
const arr = [1,2,3,4,5,6,7,8,9];
console.log(arr.filter((item)=>{return item>5})); //[ 6, 7, 8, 9 ]
console.log(arr.find((item)=>{return item>5})); //6
1、通过一个测试功能
-
find() 返回第一个元素。(一个)
-
filter()返回一个包含所有通过测试函数的元素的新数组。 (所有符合的)
2、如果没有满足的测试函数
-
find() 返回未定义。
-
filter() 返回一个空数组。
练习:
.find() 返回的是符合条件的那一个对象
const a=data.find((item)=>{
return item.name="aa"
})
console.log(a)//{ name: 'aa',salary: 10000}
//返回的是符合条件的那一个对象
Object.values() 返回的是一个数组,jion() 数组转字符串
const obj={name:'奈雪' ,count:6}
console.log(Object.values(obj)) //[亲雪,6 ]
//数组转字符串
console.log(Object.values(obj).join('/')) // 雪/6
Array.from( ) 把伪数组转换为真数组
<ul>
<li>1</li>
<li>2</li>
<li>3</li>
</ul>
<script>
// Array.from(Lis) 把伪数组转换为真数组
const lis = document.querySelectorA11( 'ul li')//获取的是伪数组
// console.log(lis)//伪数组
// Lis.pop() 报错 //伪数组没有pop等方法
const liss = Array.from(lis)//把伪数组转换为真数组
liss.pop()//3
console.log(liss)//[1,2]
</script>
13.2 String
练习:
- 字符串的分割
split
- split与join相反,split字符串转数组,join数组转字符串
//split与join相反,split字符串转数组,join数组转字符串
const str="2024-1-4"
console.log(str.split("-'))//[ '2024', '1', '4' ]
- 字符串的截取
substring
substring(开始的索引号[, 结束的索引号])
- 如果省略 结束的索引号,默认取到最后
- 结束的索引号不包含想要截取的部分
const str =今天又要下雨了
console.Log(str.substring(4,6))//下雨
3.startswith
判断是不是以某个字符开头
console.Log(str.startWith("今"))//true
4.includes
判断某个字符是不是包含在一个字符串里面
console.Log(str.startWith("下雨"))//true
console.Log(str.startWith("下雨",6))//false
13.3 Number
常用方法:
toFixed()
设置保留小数位的长度
const num=12.368
//保留两位小数 四舍五入
console.log(num.toFixed(2))//12.37
14.编程思想
14.1 面向过程介绍
-
面向过程就是分析出解决问题所需要的步骤,然后用函数把这些步骤一步一步实现,使用的时候再一个一个的依次调用就可以了。
-
面向过程,就是按照我们分析好了的步骤,按照步骤解决问题。
14.2 面向对象介绍
-
面向对象是把事务分解成为一个个对象,然后由对象之间分工与合作。(封装性,继承性,多态性)
-
面向对象是以对象功能来划分问题,而不是步骤。
15.构造函数
封装是面向对象思想中比较重要的一部分,js面向对象可以通过构造函数实现的封装。
同样的将变量和函数组合到了一起并能通过 this 实现数据的共享,所不同的是借助构造函数创建出来的实例对象之间是彼此不影响的
//创建一些公共的属性和方法
function Star(uname,age){
this.uname=uname
this.age=age
this.sing=function(){
console.log("唱歌")
}
}
//实例对象,获得了构造函数中封装的所有逻辑
const people1=new Start("刘德华",18)
const people2=new Start("张学友",28)
people1.sing()=== people2.sing()//false 这两个不相等
总结:
- 构造函数体现了面向对象的封装特性;
- 构造函数实例创建的对象彼此独立、互不影响
people1.sing()=== people2.sing()//false
- 构造函数存在浪费内存的问题
16.原型
原型是解决构造函数存在浪费内存的问题。
//1.公共的属性写在构造函数里
function Start(uname,age){
this.uname=uname
this.age=age
}
//2.公共的方法写到原型对象上
Star.prototype.sing=function(){
console.log('唱歌')
}
const people1=new Star("刘德华",55)
const people1=new Star("张学友",58)
- 构造函数通过原型分配的函数是所有对象所共享的。
- JavaScript 规定,每一个构造函数都有一个 prototype 属性,指向另一个对象,所以我们也称为原型对象。
Star.prototype//prototype就是原型
- 这个对象可以挂载函数,对象实例化不会多次创建原型上函数,节约内存。
- 我们可以把那些不变的方法,直接定义在 prototype 对象上,这样所有对象的实例就可以共享这些方法。
//2.公共的方法写到原型对象上
Star.prototype.sing=function(){
console.log('唱歌')
}
- 构造函数和原型对象中的this 都指向实例化的对象。
16.1 原型- this指向
构造函数和原型对象中的this都指向实例化的对象
let that
function Person(name){
this.name = name
that = this //构造函数的this
}
const o = new Person(
console.log(that === o) // true 构造函数的this指向实例化的对象
let that
function Person(name){
this.name = name
}
Person.prototype.sing = function () {
that = this //原型对象中的this
}
const o = new Person()
o.sing()
console.log(that === o) // true 原型对象中的this指向实例化的对象
案例:自己定义 给数组扩展求最大值方法和求和方法
const arr=[1,2,3]
Array.prototype.max=function(arr){
return Math.max(...arr)//this指向实例对象,这里写...this也可,相当于(...[1,2,3])
}
console.log(arr.max())
Array.prototype.sum=function(){
return this.reduce((pre,item)=>pre+item,0) //this指向实例对象
}
const.log([1,2,3].sum())
16.2 constructor 属性
- constructor属性在prototype原型对象中的
- constructor属性的作用是什么?
指向该原型对象的构造函数(重新指向它爹)
如果有多个对象的方法,我们可以给原型对象采取对象形式赋值.但是这样就会覆盖构造函数原型对象原来的内容,这样修改后的原型对象 constructor 就不再指向当前构造函数了此时,我们可以在修改后的原型对象中,添加一个 constructor 指向原来的构造函数。
function Star(name){
this.name = name
}
Star.prototype = { //这里是= 相当于是赋值操作,然后下面的实例对象不知道谁创造了它,不知道指向谁了。
sing: function () {console.log('唱歌') }
dance: function () {console.log('跳舞')}
}
console.log(star.prototype.constructor) // 指向Object
给原型对象采取对象形式赋值. 但是这样就会覆盖构造函数原型对象原来的内容。
function Star(name){
this.name = name
}
Star.prototype = {
constructor:Star,//此时,我们加一个 constructor,指向实例对象的构造函数
sing: function () {console.log('唱歌') }
dance: function () {console.log('跳舞')}
}
console.log(star.prototype.constructor) // 指向 Star
此时,我们可以在修改后的原型对象中,添加一个 constructor 指向原来的构造函数。(指向它自己的爹)
16.3 对象原型
思考:构造函数可以创建实例对象,构造函数还有一个原型对象,一些公共的属性或者方法放到这个原型对象身上但是为啥实例对象可以访问原型对象里面的属性和方法呢?
对象都会有一个属性 __ proto __ 指向构造函数的 prototype 原型对象,之所以我们对象可以使用构造函数 prototype 原型对象的属性和方法,就是因为对象有 __ proto __ 原型的存在。
注意:
_proto_
是JS非标准属性[[prototype]]
和_proto_
意义相同- 用来表明当前实例对象指向哪个原型对象prototype
function Star(){ }//构造函数
const aa=new Star()//实例对象
//对象原型_proto_指向 构造函数的原型对象
console.log(aa._proto_===Star.prototype)//true
_proto_
对象原型里面也有一个constructor
属性,指向创建该实例对象的构造函数
console.log(aa._proto_.constructor===Star)//true
总结:
1.prototype是什么? 哪里来的?
原型(原型对象)
构造函数都自动有原型
2.constructor属性在哪里? 作用干啥的?
prototype原型和对象原型_proto_里面都有
都指向创建实例对象/原型的构造函数
3._proto_属性在哪里? 指向谁?
在实例对象里面
指向原型prototype
16.4 原型继承
继承是面向对象编程的另一个特征,通过继承进一步提升代码封装的程度,JavaScript 中大多是借助原型对象实现继承的特性。
1.先创建两个构造函数
//女人 构造函数
function Woman(){
this.eays=2
this.head=1
}
const red=new Woman()
console.log(red)
//男人 构造函数
function Man(){
this.eays=2
this.head=1
}
const blue=new Man()
console.log(blue)
2.通过观察,男人和女人都是人,都有公共的特征,我们继续抽取
- 2.1 抽取Person
- 2.2 公共属性放在原型上
- 2.3 通过原型上的constructor又给指回原来的构造函数
//继续抽取 公共的属性放在原型上
const Person={
eays:2
head:1
}
//女人 构造函数 想要继承Person
function Woman(){
}
//公共的属性放在原型上 Woman通过原型来继承Person
Woman.prototype=Person
//但是woman继承了对象(相当于Person对象直接进行了覆盖)把Woman原来的constructor给覆盖没了
//通过原型上的constructor又给指回原来的构造函数
Woman.prototype.constructor=Woman
const red=new Woman(
console.log(red)
//男人 构造函数 想要继承Person
function Man(){
}
Man.prototype=Person
Man.prototype.constructor=Man
const blue=new Man()
console.log(blue)
继承会覆盖构造函数(Woman)原来的constructor,所以通过原型上的constructor又给指回原来的构造函数,Woman构造函数又有了的constructor
3.给女人添加一个生孩子的方法
const Person={
eays:2
head:1
}
function Woman(){
}
Woman.prototype=Person
Woman.prototype.constructor=Woman
//给女人添加一个生孩子的方法
Woman.prototype.baby=function(){
console.log('宝贝')
}
const red=new Woman(
console.log(red)
function Man(){
}
Man.prototype=Person
Man.prototype.constructor=Man
const blue=new Man()
console.log(blue)
问题:男人和女人都有生孩子的方法,因为他们都指向同一个对象
4.如何解决?如果两个指向不同的对象,就不会相互影响了
const Person1={
eays:2
head:1
}
const Person2={
eays:2
head:1
}
function Woman(){
}
Woman.prototype=Person1
Woman.prototype.constructor=Woman
//给女人添加一个生孩子的方法
Woman.prototype.baby=function(){
console.log('宝贝')
}
const red=new Woman(
console.log(red)
function Man(){
}
Man.prototype=Person2
Man.prototype.constructor=Man
const blue=new Man()
console.log(blue)
5。写两个一样的对象太麻烦,如何使两个函数结构一样,对象不一样?
构造函数
//构造函数 new 出来的对象 结构一样,但是对象不一样
function Person(){
this.eyes=2
this.head=1
}
function Woman(){
}
//Woman.prototype=Person1
Woman.prototype=new Person()//new 创建对象
Woman.prototype.constructor=Woman
Woman.prototype.baby=function(){
console.log('宝贝')
}
const red=new Woman(
console.log(red)
function Man(){
}
//Man.prototype=Person2
Man.prototype=new Person()//new 创建对象
Man.prototype.constructor=Man
const blue=new Man()
console.log(blue)
核心代码:子类的原型=new 父类
16.4 原型链
基于原型对象的继承使得不同构造函数的原型对象关联在一起,并且这种关联的关系是一种链状的结构,我们将原型对象的链状结构关系称为原型链
原型链-查找规则
① 当访问一个对象的属性(包括方法)时,首先查找这个对象自身有没有该属性。
② 如果没有就查找它的原型(也就是 __proto__
指向的 prototype
原型对象)
③ 如果还没有就查找原型对象的原型(Object的原型对象)
④ 依此类推一直找到 Object 为止(null)
⑤ __proto__
对象原型的意义就在于为对象成员查找机制提供一个方向,或者说一条路线
⑥ 可以使用 instanceof 运算符用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上console.log(ldh instanceof Person)
console.log(ldh instanceof Object)
理解:就是因为__proto__
的存在,它可以使用这条路线上的任何方法
17.综合案例
- Model框
- open方法
- 写到构造函数的原型对象身上
- 把刚才创建的 modalBox 添加到 页面 body 标签中
- open 打开的本质就是 把创建标签添加到页面中
- 点击按钮,实例化对象,传入对应的参数,并执行 open 方法
- close方法
- 写到构造函数的原型对象身上
- 把刚才创建的 modalBox 从页面 body 标签中 删除
- 需要注意,x关闭按钮是在模态框里面,所以应该是页面显示这个模态框就要绑定事件
页面显示模态框是在 open里面,所以绑定关闭事件也写到 open方法里面
bug解决
- BuG:
多次点击会显示很多的模态框 - 解决:
准备open显示的时候,先判断页面中有没有 modal盒子,有就移除,没有就添加
box&&box.remove()
box有为true,就执行后面的,移除盒子
18.深浅拷贝
开发中我们经常需要复制一个对象。如果直接用赋值会有下面问题:
const pink={
name:'pink',
age:18
}
const red=pink//复制
console.log(red)//{ name: 'pink', age: 18 }
red.name='red'
console.log(red)//{ name: 'red', age: 18 }
console.log(pink)//{ name: 'red', age: 18 } pink对象里面也发送了改变!
因为只复制了地址,并没有一个新的对象,他们指向的还是同一个对象
18.1浅拷贝
浅拷贝对象,对于对象中的基本类型成员变量进行值赋值,就是将基本类型的成员变量的值赋给拷贝对象中对应成员变量,所以如果对其中一个对象的基本类型成员变量进行修改,不会影响另一个对象中对应的成员变量的值。
- 首先浅拷贝和深拷贝只针对引用类型
- 浅拷贝:拷贝的是地址
常见方法:
1.拷贝对象:Object.assgin() / 展开运算符 {…obj} 拷贝对象
2.拷贝数组:Array.prototype.concat() 或者 […arr]
const obj={
uname:'pink',
age:18
}
//浅拷贝
//展开运算符
// const o={} 这里是声明了一个新对象
const o={...obj}//展开运算符,把obj里面的属性浅拷贝进去
console.log(o);//{ uname: 'pink', age: 18 }
o.age=20
console.log(o)//{ uname: 'pink', age: 20 }
console.log(obj);//{ uname: 'pink', age: 18 } 原对象没有被改变
//assign
//声明了一个新对象
const red={}
Object.assign(red,obj)//被拷贝的在后面
red.name='玛丽'
console.log(red);//{ uname: 'pink', age: 18, name: '玛丽' }
console.log(obj);//{ uname: 'pink', age: 18 }
// [...arr]
const arr=['huahua','mimi','kiki']
const o=[...arr]
o.push('miumiu')
console.log(o);//[ 'huahua', 'mimi', 'kiki', 'miumiu' ]
console.log(arr);//[ 'huahua', 'mimi', 'kiki' ]
//concat()
const oo=arr.concat()//[ 'huahua', 'mimi', 'kiki' ]
//或者
const oo=arr.concat("miumiu")
console.log(oo);//[ 'huahua', 'mimi', 'kiki', 'miumiu' ]
concat参数:
数组和/或值,将被合并到一个新的数组中。如果省略了所有 valueN
参数,则 concat
会返回调用此方法的现存数组的一个浅拷贝
const array1 = ['a', 'b', 'c'];
const array2 = ['d', 'e', 'f'];
const array3 = array1.concat(array2);
console.log(array3);// ["a", "b", "c", "d", "e", "f"]
但是浅拷贝只能拷贝单层对象,如果是多层就有问题;
const obj={
uname:'pink',
age:18,
family:{
baby:'mini'
}
}
const o={...obj}
o.family.baby='red'
console.log(o);//{ uname: 'pink', age: 18, family: { baby: 'red' } }
console.log(obj);//{ uname: 'pink', age: 18, family: { baby: 'red' } }
总结:
- 直接赋值和浅拷贝有什么区别?
直接赋值的方法,只要是对象,都会相互影响,因为是直接拷贝对象栈里面的地址
浅拷贝如果是一层对象,不相互影响,如果出现多层对象拷贝还会相互影响
- 浅拷贝怎么理解?
拷贝对象之后,里面的属性值是简单数据类型直接拷贝值
如果属性值是引用数据类型则拷贝的是地址
(为了解决这个问题,有了深拷贝)
18.2深拷贝
-
首先浅拷贝和深拷贝只针对引用类型
-
深拷贝:拷贝的是对象,不是地址
常见方法:
- 通过递归实现深拷贝
- lodash/cloneDeep
- 通过JSON.stringify() 实现
1. 通过递归实现深拷贝
函数递归: 如果一个函数在内部可以调用其本身,那么这个函数就是递归函数
简单理解:
-
函数内部自己调用自己, 这个函数就是递归函数
-
递归函数的作用和循环效果类似
-
由于递归很容易发生“栈溢出”错误(stack overflow),所以必须要加退出条件 return
let num=1
//fn就是递归函数
function fn(){
console.log(`我打印的第${num}次`);
if(num>=6){
return
}
num++
fn()//自己调用自己
}
fn()//调用递归函数
案例:利用递归函数实现 setTimeout 模拟 setInterval效果
需求:
①:页面每隔一秒输出当前的时间
②:输出当前时间可以使用:new Date().toLocaleString()
function getTime(){
const time=new Date().toLocaleString()
console.log(time);
setTimeout(getTime,1000)//自己调用自己
}
getTime()
递归实现深拷贝
//旧对象
const obj={
uname:'pink',
age:18,
hobby:['篮球','足球'],
family:{
baby:'小pink'
}
}
//新对象
const o={}
//拷贝函数
function deepCopy(newObj,oldObj){
for(let k in oldObj){
//k 属性值 oldObj[k] 属性值
//newObj[k]===o.uname
//处理数组问题
if(oldObj[k] instanceof Array){
newObj[k]=[] //处理不了复杂的数据类型,就又调用递归进一层拷贝
deepCopy(newObj[k],oldObj[k])
//处理对象问题
}else if(oldObj[k] instanceof Object){
newObj[k]={}//处理不了复杂的数据类型,就又调用递归进一层拷贝
deepCopy(newObj[k],oldObj[k])
}else{
//相当于把oldObj的属性值赋给新对象的属性
newObj[k]=oldObj[k]
}
}
}
deepCopy(o,obj)//调用
o.age=20
console.log(o)
console.log(obj);
o.hobby[0]='开飞机'
console.log(o);
// 结果:
// {
// uname: 'pink',
// age: 20,
// hobby: [ '篮球', '足球' ],
// family: { baby: '小pink' }
// }
// {
// uname: 'pink',
// age: 18,
// hobby: [ '篮球', '足球' ],
// family: { baby: '小pink' }
// }
// {
// uname: 'pink',
// age: 20,
// hobby: [ '开飞机', '足球' ],
// family: { baby: '小pink' }
// }
理解一:
//对二层数据类型的递归
//1.如果检测到了是数组,递归
// deepCopy([],['篮球','足球'])
//2.再次循环遍历
//执行 newObj[k]=oldObj[k]
理解二:
一定要先处理数组的,再处理对象的,因为万物皆对象(一定要把数组筛走了,再处理对象)
2. js库lodash里面cloneDeep内部实现了深拷贝
//使用lodash,需要先引入库
const o=_.cloneDeep(obj)
3. 通过JSON.stringify()实现
先把对象转换成字符串类型(就是普通字符串类型了),然后再把字符串类型转换成对象类型(就是一个新的对象了,不指向同一个对象)
const o=JSON.parse(JSON.stringify(obj))
console.log(o);
o.family.baby='123'
console.log(o);
//结果:
/**{
uname: 'pink',
age: 18,
hobby: [ '篮球', '足球' ],
family: { baby: '小pink' }
}
{
uname: 'pink',
age: 18,
hobby: [ '篮球', '足球' ],
family: { baby: '123' }
}*/
总结:
实现深拷贝三种方式?
- 自己利用递归函数书写深拷贝
- 利用js库 lodash里面的 _.cloneDeep()
- 利用JSON字符串转换
19.异常处理
19.1 throw 抛异常
function fn(x,y){
if(!x||!y){
//如果用户没有传入x或者y任何一个参数值,就抛异常
throw new Error('参数不能为空!')//不用写return都自动中断
}
return x+y
}
fn()
总结:
- throw 抛出异常信息,程序也会终止执行
- throw 后面跟的是错误提示信息
- Error 对象配合 throw 使用,能够设置更详细的错误信息
抛出异常我们用那个关键字?它会终止程序吗?
throw 关键字
会中止程序
抛出异常经常和谁配合使用?
Error 对象配合 throw 使用
19.2. try /catch 捕获异常
我们可以通过try / catch 捕获错误信息(浏览器提供的错误信息) try 试试 catch 拦住 finally 最后
function fn(){
try{
//可能发送错误得到代码,要写到try
const p=document.querySelector('.p')
p.style.color='red'
}catch(err){
//拦截错误,提示浏览器提供的错误信息,但是不中断程序的执行
//查看错误信息
console.log(err.message);
//终止代码继续执行(需要手动加,因为它自己不会中断)
return//当然这里也可以用throw new Error('错误')代替
}finally{
alert('不管是否有错都会执行')
}
console.log('如果出现错误,我的语句不会执行');
}
fn()
总结:
- try…catch 用于捕获错误信息
- 将预估可能发生错误的代码写在 try 代码段中
- 如果 try 代码段中出现错误后,会执行 catch 代码段,并截获到错误信息
- finally 不管是否有错误,都会执行
捕获异常我们用那3个关键字?可能会出现的错误代码写到谁里面
try catch finally
try
怎么调用错误信息?
利用catch的参数
19.3. debugger
20.处理this
20.1 处理this
1. this指向-普通函数
普通函数的调用方式决定了 this 的值,即【谁调用 this 的值指向谁】
// 普通函数
function sayHi() {
console.log(this)// window
}
//函数表达式
const sayHello = function () {
console.log(this)// window
}
// 函数的调用方式决定了 this 的值
sayHi() // window
window.sayHi()
//普通对象
const user ={
name:'小明',
walk: function () {
console.log(this)
}
// 动志为 user 添加方法
user.sayHi=sayHi
uesr.sayhello=sayHello
// 函数调用方式,决定了 this 的值
user.sayHi() //user
user.sayHe11o() //user
use strict
function fn() {
console.log(this)// undefined
}
fn()
普通函数没有明确调用者时 this 值为 window,严格模式下没有调用者时 this 的值为 undefined
2. this指向-箭头函数
箭头函数中的 this 与普通函数完全不同,也不受调用方式的影响,事实上箭头函数中并不存在 this!
- 箭头函数会默认帮我们绑定外层this的值,所以在箭头函数中 this 的值和外层的 this 是一样的
- 箭头函数中的this引用的就是最近作用域中的this
- 向外层作用域中,一层一层查找this,直到有this的定义
注意情况1:
在开发中【使用箭头函数前需要考虑函数中 this 的值】,事件回调函数使用箭头函数时,this 为全局的 window ;
因此DOM事件回调函数如果里面需要DOM对象的this,则不推荐使用箭头函数
// DOM 节点
const btn = document.queryselector( '.btn')
// 箭头函数 此时 this 指向了 window
btn.addEventListener('click',() =>
console.log(this)
})
// 普通函数 此时 this 指向了 DOM 对象
btn.addEventListener('click',function () {
console.log(this)
})
注意情况2:
同样由于箭头函数 this 的原因,基于原型的面向对象也不推荐采用箭头函数
function Person() {
//原型对像上添加了箭头函数
Person.prototype.walk =()=>{
console.log("人都要走路...")
console.log(this); // window 本来指向实例对象P1的,现在指向window了
}
const p1 = new Person()
p1.walk()
总结:
- 函数内不存在this,沿用上一级的;
- 不适用:构造函数,原型函数,dom事件函数等等
- 适用: 需要使用上层this的地方
- 使用正确的话,它会在很多地方带来方便,后面我们会大量使用慢慢体会
20.2 改变this
JavaScript 中还允许指定函数中 this 的指向,有 3 个方法可以动态指定普通函数中 this 的指向
- call() 了解
使用 call 方法调用函数,同时指定被调用函数中 this 的值
fun.call(thisArg, arg1, arg2, ...)
- thisArg:在 fun 函数运行时指定的 this 值
- arg1,arg2:传递的其他参数
- 返回值就是函数的返回值,因为它就是调用函数
const obj = {
uname :"pink"
}
function fn(x,y) {
console.log(this) // window //使用call指向obj了
console.log(x + y)
}
// 1. 调用函数
// 2.改变 this 指向
fn.call(obj,1,2) //本来指向window的,现在指向obj了
- apply() 理解
使用 apply 方法调用函数,同时指定被调用函数中 this 的值
- thisArg:在fun函数运行时指定的 this 值
- argsArray:传递的值,必须包含在数组里面
- 返回值就是函数的返回值,因为它就是调用函数
- 因此 apply 主要跟数组有关系,比如使用 Math.max() 求数组的最大值
const obj = {
uname :"pink"
}
function fn(x,y) {
console.log(this) // window //使用apply指向obj了
console.log(x + y)
}
// 1. 调用函数
// 2.改变 this 指向
fn.apply(obj,[1,2]) //本来指向window的,现在指向obj了
// 使用场景:数组最大值
//const max = Math.max(1,2,3)
//console.Log(max)
const arr = [100,44,77]
const max = Math.max.apply(Math, arr)
const min = Math.min.apply(Math, arr)
console.log(max, min)
- bind()-重点
bind() 方法不会调用函数。但是能改变函数内部this 指向
语法:
fun.bind(thisArg, arg1, arg2, ...)
-
thisArg:在 fun 函数运行时指定的 this 值
-
arg1,arg2:传递的其他参数
-
返回由指定的 this 值和初始化参数改造的 原函数拷贝 (新函数)
-
因此当我们只是想改变 this 指向,并且不想调用这个函数的时候,可以使用 bind,比如改变定时器内部的this指向
const obj={
age:18
}
function fn(){
console.log(this)
}
// 1.bind 不会调用函数
// 2.能改变this指向
// 3.返回值是个函数,但是这个哈数里面的this是更改过的obj
const fun=fn.bind(obj)
fun()
20.3 call apply bind 总结
相同点:
都可以改变函数内部的this指向.
区别点:
-
call 和 apply 会调用函数, 并且改变函数内部this指向.
-
call 和 apply 传递的参数不一样, call 传递参数 aru1,aru2…形式 apply 必须数组形式[arg]
-
bind 不会调用函数, 可以改变函数内部this指向.
主要应用场景:
-
call 调用函数并且可以传递参数
-
apply 经常跟数组有关系. 比如借助于数学对象实现数组最大值最小值
-
bind 不调用函数,但是还想改变this指向. 比如改变定时器内部的this指向.
需求:有一个按钮,点击里面就禁用,2秒钟之后开启
const btn document.querySelector('button')
btn.addEventListener('click',function(){
//禁用按钮
this.disabled=true
window.setTimeout(function(){
//在这个普通函数里面,我们要this由原来的window改为btn
this.disabled=true
}.bind(this),2000)
})
21. 性能优化
21.1 防抖
防抖 :单位时间内,频繁触发事件,只执行最后一次
使用场景:
-
搜索框搜索输入。只需要用户最后一次输入完,再发送请求
-
手机号、邮箱验证输入检测
案例:利用防抖来处理-鼠标滑过盒子显示文字
<div class="box"></div>
<script>
const box=document.querySelector('.box')
let i=1;
function mouseMove(){
box.innerHTML=i++
//如果存在开销较大的操作,大量数据处理,大量dom操作,可能会卡
}
box.addEventListener('mousemove',mouseMove)
</script>
要求: 鼠标在盒子上移动,鼠标停止之后,500ms后里面的数字就会变化+1
- 解决方法一:
利用Lodash库实现防抖——500毫秒之后采取+1
语法:_.debounce(fun,时间)
//利用Lodash库实现防抖——500毫秒之后采取+1
// 语法:_.debounce(fun,时间)
box.addEventListener('mousemove',_.debounce(mouseMove,500))
- 解决方法二:
手写防抖函数
//手写防抖函数
function debounce(fn,t){
let timer
//return返回一个匿名函数
//原因:debounce(mouseMove,500)是一个带参数的函数,它一调用就只执行一次(因为函数调用只执行一次),相当于鼠标移动一次就调用了,然后下次鼠标再次移动就不会调用了。
//所以要给他返回一个匿名函数,每次执行就会返回一个函数,就再次调用
return function(){
if(timer) clearTimeout(timer)
timer=setTimeout(function(){
fn()
},t)
}
}
box.addEventListener('mousemove',debounce(mouseMove,500))
21.2 节流
节流:单位时间内,频繁触发事件,只执行一次
高频事件:鼠标移动mousemove、页面尺寸缩放resize、滚动条滚动scroll等等
案例:利用节流来处理-鼠标滑过盒子显示文字
要求:鼠标在盒子上移动,不管移动多少次,每隔500ms才+1
- 解决方法一:
利用Lodash库实现节流——每隔500ms才+1
语法:_.throttle(fun,时间) 在wait秒内最多执行func一次函数
box.addEventListener('mousemove',_.throttle(mouseMove,500))
- 解决方法二:
function throttle(fn,t){
let timer=null
return function(){
if(!timer){
timer=setTimeout(function(){
fn()
//清空定时器
//不能用clearTimeout(timer)清空,定时器在执行期间是不能清空的
timer=null//所有赋一个空值,下一次判断就是 !null
},t)
}
}
}
box.addEventListener('mousemove',throttle(mouseMove,5000))
总结:
- 防抖的应用场景注重结果,比如搜索框搜索输入。
- 节流的应用场景注重过程,比如高频事件:鼠标移动mousemove、页面尺寸缩放resize、滚动条滚动scroll等等。
完结:随时有重头再来的勇气!