JavaScript
2022年8月1日
一.javascript的组成
1.ECMAScript:定义了javascript的语法规范,描述了语言的基本语法和数据类型
2.BOM(Brower Object Model):浏览器对象模型,有一套成熟的可以操作浏览器的方法,通过BOM可以操作浏览器,比如:弹出对话框,跳转页面,获取分辨率等
3.DOM(Document Object Model):文档对象模型,有一套成熟的可以操作页面元素的方法,通过DOM可以操作页面中的元素,比如:增加一个div,减少一个div,给div添加样式,换个位置等。
4.总结:javascript就是通过固定的语法,去操作浏览器和标签结构来实现网页上的各种效果。
二.javascript的书写位置
1.行内:写在标签里面的js代码,需要依赖用户的行为来触发这个代码的(不推荐)。
2.内嵌:写在html的script标签中,会在页面打开的直接执行代码。在页面任意位置添加一对scirpt标签,js代码就写在这对标签里面,一个页面可以有多个scirpt标签。
3.外链:只要新建一个.js后缀的文件,在里面就可以写js代码,把写好的js代码通过html文件的script标签的src引入就可以。在页面打开的时候直接触发(推荐)
三.javascript的注释
1.单行://
一般是用来描述下一行代码的作用的。
windows快捷键(ctrl+/)
mac的快捷键:command+/)
2.多行: / ** /
一般用来写一大段话,获取注释一段代码.
可以直接写 / ** /,然后在两个*之间写注释
window的快捷键: shift+alt+a(默认)
mac的快捷键:shift+option+a
四.变量
1.变量就是在程序中保存数据的一个容器,也可以说是程序内存中申请的一块用来存放数据的空间。
2.声明一个变量并赋值,我们称之为变量的初始化。
3.在js中不声明,可以直接使用变量。 eg:qq=100;(不提倡)
4.name在某些浏览器有特殊含义,建议不要用其作为变量名。
定义变量: var 变量名(变量名和var之间必须有空格)
给变量赋值: 变量名 = 数据
定义变量并赋值:var 变量名 = 数据
二.变量名的命名规则
1.只能是字母,数字,英文下划线和美元符号
2.严格区分大小写
3.不能以数字开头,不能使用中文汉字命名
4.不能有空格
5.不能是关键字或者保留字(eg:var)
6.变量名的命名规范
7.尽量语义化(变量名尽量有意义)
8.尽量驼峰命名(由多个单词组成的时候,从第二个单词开始首字母大写 eg:myFriendName)
注意:1.一个变量里面只能存储一个值
2 .当再次给一个变量赋值的时候,前面一次的就没有了
3 .变量名区分大小写(js区分大小写)
4 .js代码每一句用分号结尾或者换行结束
五.数据类型:指的是我们存储在内存中的数据的类型。分为两大类:基本数据类型和复杂数据类型
一.基本数据类型
1.数值类型(number)
一切数字都是数值类型
一个特殊的数字,非数字:NaN
js中数值的最大值:Number.Max_value
js中数值的最小值:Number.Min_value
infinity 无穷大
-infinity 无穷小
2.字符串类型(string)
被引号包裹的数据(可以是单引号也可以是双引号,要成对就可以)
字符串引号嵌套(外双内单,内双外单)
3.布尔类型(boolean)
只有两个:true 或 false
4.null类型(null)
有一个,就是null,表示空的意思
5.undefined类型(undefined)
只有一个,就是undefined,表示没值的意思
二.复杂数据类型 - 下周讲
1.数组
2.对象
3.....
六.判断数据类型:使用typeof 关键字来进行判断
语法:typeof(变量)
语法:typeof 变量
结果:是一个表示数据类型的字符串('number'/'string'/'boolean'/'undefined'/'null')
注意:只是定义了,没有赋值,数据类型为undefined。(eg:var a;)
七.其他数据类型转数值类型
一.比如:数值类型转字符串,字符串转布尔值,布尔值转数字等,其他数据类型转数值类型 - 我们自己调用方法显式转换。
1.Number(数据)
可以把一个变量强制转换成数值类型,返回数据转换成的具体的数字
可以转换小数
可以转换布尔值
可以转换null
遇到不可转换的都返回NaN
当==两边进行比大小时,==两边默认转化成Number进行比较。
2.parseInt(数据) - 取整(可以去除单位)
可以把一个变量强制转换成数值类型,返回数据转换成的具体的数字
从第一位开始检查,是数字就转换,直到一个不是数字的内容
开头不是数字,那么直接返回NaN
不认识小数点,只能保留整数
3.parseFloat(数据)(可以去除单位)
可以把一个变量强制转换成数值类型,返回数据转换成的具体的数字
从第一位开始检查,是数字就转换,直到一个不是数字的内容
开头不是数字,那么直接返回NaN
认识一次小数点
二.其他数据类型转数值类型 - 隐蔽的浏览器自动遇到一些特殊情况给我们转换
4.除了加法以外的数学运算符
加法以外的数学运算要求两边都是数字
浏览器就自动给我们吧运算符两边的转数字,再参与运算
浏览器内部会调用Number帮我们偷偷的转
但加法没有这个能力
八.其他数据类型转字符串类型
一.其他数据类型转字符串类型 - 我自己调用方法来获取转换的结果
1.变量.toString()
就可以把变量转换成字符串类型
有一些数据类型不能使用这个方法,比如 undefined和null,会报错(因为他们后面不能接 .的)
2.String(变量)
所有数据类型都可以转换成字符串
二.其他数据类型转字符串类型 - 浏览器根据一些特殊情况转换字符串
3.使用加法运算
在js里面,+ 有两个含义
如果 + 两边都是数值类型就是表示做加法运算
如果 + 两边有任意一边是字符串,就会把两边都自动转换字符串,然后进行字符串拼接
九.其他数据类型转布尔值
Boolean(变量)
除了0,null,"",undefined,NaN转换结果是false,其他的都是true
拓展:Boolean(Nan==Nan) 结果为false。
var $myName = true(正确),数据类型为boolean
十.运算符:在代码库里面进行运算的时候使用的符号,不光只是数学运算符,我们的js里面的运算符有很多种
一.数学运算符:+,-,*,/,%
1.+
只有符号两边都是数值的时候才会进行加法运算
只要符号任意一边是字符串类型,就是进行字符串拼接
2.-
会执行减法运算
js会自动把符号两边转数值类型做减法
3.*
会执行乘法运算
js会自动把符号两边转数值类型做乘法
4./
会执行除法运算
js会自动把符号两边转数值类型做除法
5.%
会执行求余数运算
js会自动把符号两边转数值类型求余数
二.赋值运算符
1.=
就是把 = 右边的数据赋值给等号左边的变量名
var num = 100;就是把100赋值给num变量,那么num变量里面的值就是100
2.+=
比如: a += 10 等价于: a = a+10
3.-=
比如: a -= 10 等价于: a = a-10
4.*=
比如: a *= 10 等价于: a = a*10
5./=
比如: a /= 10 等价于: a = a/10
6.%=
比如: a %= 10 等价于: a = a%10
三.比较运算符
1.==
比较符号两边的值“是否“相等,不管数据类型,只比较值
eg:1=='1' 两个的值是一样的,所以得到true
2.===
比较符号两边的值和数据类型”是否“都相等
eg:1==='1' 两个的值虽然一样,但是数据类型不同,所以得到false
3.!=
比较符号两边的值是否不等
eg:1!='1'因为两边的值是相等的,所以比较他们不相等的时候得到false
4.!==
比较符号两边的值和类型是否不等
eg:1!=='1' 因为两边的数据类似确实不一样,所以得到true
5.>=
比较左边的值是否大于或等于右边的值
eg:1>=1 true; eg:1>=0 true; eg:1>=2 false
6.<=
比较左边的值是否小于等于右边的值
eg:1<=1 true; eg:1<=0 false; eg:1<=2 true
7.>
比较左边的值是否大于右边的值
eg:1>1 false; eg: 1>0 true; eg:1>2 false
8.<
比较左边的值是否小于右边的值
eg:1<1 false; eg:1<0 false; eg: 1<2 true
四.逻辑运算符
1.&&
进行且运算
符号左边必须位true,并且右边必须也是true,结果才是true
只要有一边不是true,那么就会返回false
2.||
进行或运算
符号的左边为true或者右边为true,都会返回true
只有两边都是false的时候才返回false
3.!
进行取反运算
本身是true的,返回false
本身是false的,返回true
五.自增和自减运算符
1.自增运算符(一元运算符:运算符一边有数据)
语法:++
表示自增运算
分成两种,++前置(++在数据前面),++后置(++在数据后面)
++前置,会把数据先+1,然后再返回
++后置,会先返回数据,再+1
2.自减运算符(一元运算符:运算符一边有数据)
语法:--
表示自减运算
分成两种,--前置(--在数据前面),--后置(--在数据后面)
--前置,会把数据先-1,然后再返回
--后置,会先返回数据,再-1
六.前置和后置的区别
区别只影响当前行
后置就是先返回num的值,再加1,给num赋值
前置就是先+1,赋值给num,再返回num的值
在下一行就不受影响了
注意:
1. js并不擅长做小数运算,建议做整数运算。
2. .toFixed(n)n为保留几位小数,可以实现四舍五入。
拓展:页面上获取到的所有值都是字符串。
prompt()为输入框 —浏览器弹出输入框,用户可输入;
“prompt 输入的,都是字符串型的数据。要变成数字型,需要自己转换”
eg:var name = prompt(’请输入你的名字‘) 即把输入框的值保存起来。
2022年8月2日 逻辑分支
我们的js代码都是顺序执行的(从上到下),逻辑分支就是根据我们设定好的条件来决定要不要执行某些代码
一.if分支语句:通过一个if语句来决定代码执行与否
if(条件){
要执行的代码// 条件为true执行添加后面{}里面的代码
}
二.if … else 语句:通过if条件来判断,执行哪一个{}里面的代码
特点:两个{}里面的代码一定有一个会执行,如果{}里面只有一行代码可以省略{}
if(条件){
要执行的代码// 条件为true执行条件后面{}里面的代码
}else{
要执行的代码// 条件为false执行else后面{}里面的代码
}
三.if…else if …语句:可以通过if和else if来设置多个条件进行判断
特点:会从头开始依次判断条件,如果条件1为true,就不进行后面的判断了,只有前面的条件不为true,才进行后面的条件判断。多个{},只会有一个被执行,一旦有一个条件为true了,后面的就不再判断了
if(条件1){
// 条件1为true执行条件1后面{}里面的代码
}else if(条件2){
// 条件2为true执行条件2后面{}里面的代码
}
四.if…else if …else 语句
特点: 和之前的if else if … 基本一致,只不过是在所有条件都不满足的时候,执行最后else后面的{}里面的代码。如果前面有一个条件满足了,那么后面的就都不会执行了。
如果条件1为true,就不进行后面的判断了
只有前面的条件不为true,才进行后面的条件判断
if(条件1){
// 条件1为true执行条件1后面{}里面的代码
}else if(条件2){
// 条件2为true执行条件2后面{}里面的代码
}else{
// 前面所有条件都不满足的时候执行else后面{}里的代码
}
五.switch分支语句:也是条件判断语句的一种,主要用于判断一个变量
特点:判断某一个变量全等于某一个值的时候使用(break是一个关键字)
注意:case后面的值必须是确定的,不能有范围,但是可以在:下写if条件判断。
switch(变量){
case 情况1:
// 变量全等于情况1的时候执行此次代码
break; // 后面的代码就不继续执行了
case 情况2:
// 变量全等于情况2的时候执行此次代码
break; // 后面的代码就不继续执行了
default:
// 前面的情况都不符合就执行此
}
注意:当不写break时,叫做switch的穿透,即无法跳出当前case,而后面的代码就不用进行条件判断了,依次执行,一直执行到下一个break结束或者执行到最后一行,运行完代码。
六.三元运算符:就是用两个符号组成一个运算符(类似if else语句)
语法:条件?条件true返回的值:条件false返回的值
七.扩展:逻辑运算符
1.&& 且运算
进行且运算
只有左边和右边都是true的时候结果才是true
运算的时候,只有左边有true才会进行右边的运算
运算的时候,如果左边的是false,就不进行右边的运算
且运算的返回值是最后运算的那个
注意:只有0,'',null,undefined,NaN会把看成false,其他可以看成true。
2.|| 或运算
进行或运算
只要左边和右边有一个是true结果就是true
运算的时候,如果左边是true,就不看右边了
运算的时候,如果左边是false,就看右边了
且运算的返回值是最后运算的那个
3.! 取反操作
进行取反操作
结果一定是布尔值
如果是true取反就是false
如果是false取反就是true
2022年8月3日 循环结构
一.介绍
1.顺序结构:代码从上到下依次执行
2.分支结构:代码根据给定的条件选择执行其中的代码
3.循环结构: 就是根据某些给出的条件,重复执行同一段代码
4.循环必须有固定的内容组成:1.初始条件2.条件的判断3.自身的改变4.要执行的代码
二.while循环结构
1.中文是当的意思,就是当条件满足时就执行代码,一旦不满足了,就不执行了
2.语法: while(条件){条件为true要执行的代码}
3.当条件满足的时候,就执行{}里面的代码,执行完继续进行条件判断,条件满足就执行{}里面的代码,直到条件为false的时候,while循环就完了
4.如果没有自身的改变,那么就会一直循环下去,叫做死循环
eg:1.初始化条件 var num = 1;
2.条件判断 while(num<11){}
3.自身的改变 num++;
4.要执行的代码 console.log('这是你工作的第'+num+'年,给你加薪5%')。
三.do …while循环
1.类似while循环
2.while循环先进行条件判断,条件为true才执行{}里面的代码,为false就不执行{}里面的代码
3.但是do while循环是,先不管条件,先执行一次,然后再进行条件判断
4.语法: do{要执行的代码,至少会执行一次}while(条件)
eg: 案例:你来公司的工资是10K,每工作1年,年底涨薪5%,问第10年年底,你的工资是多少?
1.思路
2.初始条件 year = 1
3.判断条件 year <= 10
4.自身的改变 year++
5.要执行的代码:
-需要在循环外定义一个变量记录初始工资: salary = 10K
-每次执行代码重新赋值 salary = salary*(1+0.05)
var salary = 10000;
/* 1 初始条件 */
var year = 1
do {
/* 2 要执行的代码 */
salary = salary *1.05;
/* 3 自身的改变 */
year++
}while(year<11)
五.for循环
1.for循环:和while和do while都不太一样的循环结构,道理是一样,都要那四个条件
2.语法: for(1初始条件;2判断条件;4自身的改变){3要执行的代码}
3.代码执行过程:
-先初始化条件
-然后进行循环的条件判断
-如果符合条件,执行{}里面的代码
-执行自身的改变的代码
-在进行循环条件判断
-如果符合条件,执行{}里面的代码
-执行自身的改变的代码
-在进行循环条件判断,以此类推,直到判断条件为false,for循环就结束了
-总结顺序: 1-234-234-234-...-2为false就结束了
4.注意:for循环中两个分号不能少。
六.拓展:逗号操作符
逗号操作符:对它的每个操作数求值(从左到右),并返回最后一个操作数的值。
在console.log()的括号中有逗号,表示输出多个。
七.break关键字
break 终止循环
1.在循环没有进行完毕的时候,因为我设置的条件为true,提前终止循环
2.比如:我要吃5个包子,吃到第三个的时候,吃不下了,我就停止了吃包子这个事情。
3.要终止循环,就可以使用break关键字
八.continue关键字
continue 结束本次循环(必须放在循环中)
1.在循环中,把循环的本次跳过去,继续执行后续的循环(即本次循环后面的代码都不执行,放在代码前面)
2.比如:我要吃5个包子,吃到第三个的时候,第三个掉地上了,跳过第三个不吃了,继续吃第四个和第五个
2022年8月4日 函数(上)
解释:我们代码里面所说的函数和我们上学的时候学习的什么三角函数、二次函数之类的不是一个东西
一.函数的概念
1.对于 js 来说,函数就是把任意一段代码放在一个盒子里面
2.在我想要让这段代码执行的时候,直接执行这个盒子里面的代码就行
3.先看一段代码
// 这个是我们以前写的一段代码
for (var i = 0; i < 10; i++) {
console.log(i)
}
// 函数,这个 {} 就是那个 “盒子”
function fn() {
// 这个函数我们以前写的代码
for (var i = 0; i < 10; i++) {
console.log(i)
}
}
二.函数的两个阶段(重点)
按照我们刚才的说法,两个阶段:
1.放在盒子里面
2.让盒子里面的代码执行
三.函数定义阶段(函数在定义阶段不会执行)
1.定义阶段就是我们把代码放在盒子里面
2.我们就要学习怎么放进去,也就是书写一个函数
3.我们有两种定义方式:
声明式和赋值式
四.声明式
-
使用
function
这个关键字来声明一个函数 -
语法:
function fn() { // 一段代码 } // function: 声明函数的关键字,表示接下来是一个函数了 // fn: 函数的名字,我们自己定义的(遵循变量名的命名规则和命名规范) // (): 必须写,是用来放参数的位置(一会我们再聊) // {}: 就是我们用来放一段代码的位置(也就是我们刚才说的 “盒子”)
五.赋值式
-
其实就是和我们使用
var
关键字是一个道理了 -
首先使用
var
定义一个变量,把一个函数当作值直接赋值给这个变量就可以了 -
语法:
var fn = function () { // 一段代码 } // 不需要在 function 后面书写函数的名字了,因为在前面已经有了
六.函数调用阶段
1.就是让盒子里面的代码执行一下
2.让函数执行
3.两种定义函数的方式不同,但是调用函数的方式都以一样的
七.调用一个函数
-
函数调用就是直接写
函数名()
就可以了// 声明式函数 function fn() { console.log('我是 fn 函数') } // 调用函数 fn() // 赋值式函数 var fn2 = function () { console.log('我是 fn2 函数') } // 调用函数 fn()
-
针对没有函数名的函数(匿名函数):就是直接在你要调用的函数后面加() eg:匿名函数的调用:(匿名函数)(),只能调用一次
// 定义一个匿名函数 // 定义函数就是为了调用,调用需要函数名 // 你定义函数没有函数,后期就无法调用 // 只能在定义以后立即调用 // 加在函数体上的(),表示这个函数是一个整体,是一个匿名函数,不是为了调用函数的 // 加在函数体后面的(),才是表示调用函数 (function (){ console.log('我是一个匿名函数') })()
注意: 定义完一个函数以后,如果没有函数调用,那么写在 {} 里面的代码没有意义,只有调用以后才会执行
八.调用上的区别
-
虽然两种定义方式的调用都是一样的,但是还是有一些区别的
-
声明式函数: 调用可以在 定义之前或者定义之后
// 可以调用 fn() // 声明式函数 function fn() { console.log('我是 fn 函数') } // 可以调用 fn()
-
赋值式函数: 调用只能在 定义之后
// 会报错 fn() // 赋值式函数 var fn = function () { console.log('我是 fn 函数') } // 可以调用 fn()
九.函数的参数(重点)
-
我们在定义函数和调用函数的时候都出现过
()
-
现在我们就来说一下这个
()
的作用 -
就是用来放参数的位置
-
参数分为两种 行参 和 实参
// 声明式 function fn(行参写在这里) { // 一段代码 } fn(实参写在这里) // 赋值式函数 var fn = function (行参写在这里) { // 一段代码 } fn(实参写在这里)
十.行参和实参的作用
-
行参
-
就是在函数内部可以使用的变量,在函数外部不能使用
-
每写一个单词,就相当于在函数内部定义了一个可以使用的变量(遵循变量名的命名规则和命名规范)
-
多个单词之间以
,
分隔// 书写一个参数 function fn(num) { // 在函数内部就可以使用 num 这个变量 } var fn1 = function (num) { // 在函数内部就可以使用 num 这个变量 } // 书写两个参数 function fun(num1, num2) { // 在函数内部就可以使用 num1 和 num2 这两个变量 } var fun1 = function (num1, num2) { // 在函数内部就可以使用 num1 和 num2 这两个变量 }
-
如果只有行参的话,那么在函数内部使用的值个变量是没有值的,也就是
undefined
-
行参的值是在函数调用的时候由实参决定的
-
-
实参
-
在函数调用的时候给行参赋值的
-
也就是说,在调用的时候是给一个实际的内容的
function fn(num) { // 函数内部可以使用 num } // 这个函数的本次调用,书写的实参是 100 // 那么本次调用的时候函数内部的 num 就是 100 fn(100) // 这个函数的本次调用,书写的实参是 200 // 那么本次调用的时候函数内部的 num 就是 200 fn(200)
-
函数内部的行参的值,由函数调用的时候传递的实参决定
-
多个参数的时候,是按照顺序一一对应的
function fn(num1, num2) { // 函数内部可以使用 num1 和 num2 } // 函数本次调用的时候,书写的参数是 100 和 200 // 那么本次调用的时候,函数内部的 num1 就是 100,num2 就是 200 fn(100, 200)
-
3.函数里面有一个变量,记录了所有的你传入的实参,这个变量就是arguments,只有在函数中有这个预先定义好的变量
代码如下:
function fn(a,b,c){
console.log(a,b,c)
// 在函数里面有预先定义好的变量arguments
// 调用函数的时候,能接受到传入的所有实参
console.log(arguments)
}
// 实参在函数调用的时候给形参赋值
fn(11,22,33,'hello','world')
十一.参数个数的关系
-
行参比实参少
-
因为是按照顺序一一对应的
-
行参少就会拿不到实参给的值,所以在函数内部就没有办法用到这个值
function fn(num1, num2) { // 函数内部可以使用 num1 和 num2 } // 本次调用的时候,传递了两个实参,100 200 和 300 // 100 对应了 num1,200 对应了 num2,300 没有对应的变量 // 所以在函数内部就没有办法依靠变量来使用 300 这个值 fn(100, 200, 300)
-
-
行参比实参多
-
因为是按照顺序一一对应的
-
所以多出来的行参就是没有值的,就是
undefined
function fn(num1, num2, num3) { // 函数内部可以使用 num1 num2 和 num3 } // 本次调用的时候,传递了两个实参,100 和 200 // 就分别对应了 num1 和 num2 // 而 num3 没有实参和其对应,那么 num3 的值就是 undefined fn(100, 200)
-
十二.函数的return(重点)
- return 返回的意思,其实就是给函数一个 返回值 和 终断函数
- 未写return,返回值为undefined
十三.终断函数
-
当我开始执行函数以后,函数内部的代码就会从上到下的依次执行
-
必须要等到函数内的代码执行完毕
-
而
return
关键字就是可以在函数中间的位置停掉,让后面的代码不在继续执行function fn() { console.log(1) console.log(2) console.log(3) // 写了 return 以后,后面的 4 和 5 就不会继续执行了 return console.log(4) console.log(5) } // 函数调用 fn()
十四.返回值
-
函数调用本身也是一个表达式,表达式就应该有一个值出现
-
现在的函数执行完毕之后,是不会有结果出现的
// 比如 1 + 2 是一个表达式,那么 这个表达式的结果就是 3 console.log(1 + 2) // 3 function fn() { // 执行代码 } // fn() 也是一个表达式,这个表达式就没有结果出现 console.log(fn()) // undefined
-
return
关键字就是可以给函数执行完毕一个结果function fn() { // 执行代码 return 100 } // 此时,fn() 这个表达式执行完毕之后就有结果出现了 console.log(fn()) // 100
- 我们可以在函数内部使用
return
关键把任何内容当作这个函数运行后的结果
- 我们可以在函数内部使用
十五.函数的优点
- 函数就是对一段代码的封装,在我们想调用的时候调用
- 函数的几个优点
- 封装代码,使代码更加简洁
- 复用,在重复功能的时候直接调用就好
- 代码执行时机,随时可以在我们想要执行的时候执行
十六.预解析(重点)(函数是一等公民,在函数名和变量名相同时,先执行函数)
- 预解析 其实就是聊聊 js 代码的编译和执行
- js 是一个解释型语言,就是在代码执行之前,先对代码进行通读和解释,然后在执行代码
- 也就是说,我们的 js 代码在运行的时候,会经历两个环节 解释代码 和 执行代码
十七.解释代码
-
因为是在所有代码执行之前进行解释,所以叫做 预解析(预解释)
-
需要解释的内容有两个
-
声明式函数
- 在内存中先声明有一个变量名是函数名,并且这个名字代表的内容是一个函数
-
var
关键字
- 在内存中先声明有一个变量名(只提升变量名,不提升赋值)
注意:函数预解析过后,执行过程中不再执行,即跳过。
-
-
看下面一段代码
fn() console.log(num) function fn() { console.log('我是 fn 函数') } var num = 100
-
经过预解析之后可以变形为
function fn() { console.log('我是 fn 函数') } var num fn() console.log(num) num = 100
-
赋值是函数会按照
var
关键字的规则进行预解析
2022年8月5日 函数(下)
一.作用域
就是一个变量可以生效的范围
变量不是在所有的地方都可以使用的,这个变量的生效范围就是作用域
1.全局作用域
== 全局作用域是最大的作用域
== 在全局作用域定义的变量可以在任何地方使用
== 页面打开的时候,浏览器会自动给我们生成一个全局作用域window
== 这个作用域会一直存在,直到浏览器关闭就销毁了
2.局部作用域
== 局部作用域就是在全局作用域下面又开辟出来的一个相对小一些的作用域
== 在局部作用域中定义的变量只能在这个局部作用域起作用
== 在js中只有函数能生成一个局部作用域,别的都不行
== 每一个函数,都是一个局部作用域
3.作用域链
==包含了函数被创建的作用域中可使用的数据的集合,称为函数的作用域链。
==作用域链决定了哪些数据能被函数访问。
eg:举个例子
// 下面两个变量都是存在全局作用域下面的
var num = 100;
var num2 = 200;
function fn(){
// 下面的这个变量就是一个fn局部作用域里面的变量
// 只能在函数fn内部使用
var num3 = 300;
console.log(num3) ;// 可以打印
}
fn()
// console.log(num3) ;// 报错
二.变量的使用规则,访问规则以及赋值规则。
1.变量的使用规则
== 有了作用域,变量就有了使用范围,也就又了使用规则
== 变量使用规则分为两种:访问规则和赋值规则
2.访问规则
== 当我想获取一个变量的值的时候,我们管这个行为叫做访问
2.1获取变量的规则
== 首先:在自己的作用域内部查找,如果有,就直接拿来使用
== 如果没有,就去上一级作用域查找,如果有,就拿来使用
== 如果没有,就继续去上一级作用域查找,依次类推
== 如果一直到全局作用域都没有这个变量,那么就会报错,(xxx is not defined)
== 变量的访问规则业叫做变量作用域的查找机制
3.赋值规则
== 当你想给一个变量赋值的时候,那么就先要找到这个变量,再给他赋值
3.1变量赋值规则
== 现在自己作用域内部查找,有就直接赋值
== 没有就去上一级作用域查找,有就直接赋值
== 在没有就继续去上一级作用域查找,有就直接赋值
== 如果一直到全局作用域都没有,那么就把这个变量定义成全局变量,再给他赋值
4.注意:作用域的查找机制只能是向上级找,不能向下级找
注意:在函数里面定义变量的使用,不要忘记写var,否则会变成全局变量
三.递归函数
在编程世界里面,递归就是一个自己调用自己的手段。递归函数:一个函数内部,调用了自己,循环往复。
其实递归和循环很类似,需要有初始条件,自增,执行代码,条件判断的,不然就是一个没有尽头的递归函数,我们佳作死递归
eg1:// 简单实现一个递归
// 需求: 求1到5的累加和
// 书写一个函数sum,可以实现任意数字到5的累加和
function sum(n){
if(n==5){
return 5;
}else {
return n + sum(n+1)
}
}
alert(sum(5))
/*n=5 求5到5的累计和,就是5 ; sum(5)
n=4 求4 + 5累加和,就是 4+sum(5) ; sum(4)
n=3 求3 + 4的累计和,就是3+sum(4); 只要能确保sum(4)的结果是对的,那么sum(3)
n=2 求2 + 3的累计和,就是2+sum(3); 只要能确保sum(3)的结果是对的,那么sum(2)
n=1 求1 + 2的累计和,就是1+sum(2); 只要能确保sum(2)的结果是对的,那么sum(1)*/
eg2:// 案例:求斐波那契数列的第n项的值
// 斐波那契数列: 1、1、2、3、5、8、13、21、34、...
function feibo(n){
if(n===1){
return 1;
}else if(n===2){
return 1;
}else{
return feibo(n-1)+feibo(n-2)
}
}
alert(feibo(10))
// n=1 返回1
// n=2 返回1
// n=3 前两项的和 feibo(3) = feibo(2)+feibo(1)
// n=4 前两项的和 feibo(4) = feibo(3)+feibo(2)
// n=5 前两项的和 feibo(5) = feibo(4)+feibo(3)
四.数组
1.什么是数组:
== 字面意思就是数据的集合
== 也就i是我们把一些数据放在一个盒子里面,按照顺序排好
== [12,'hello','str',null,undefined,NaN]
== 这个东西就是一个数组,存储写一些数据的集合
2.创建一个数组
== 数组就是一个[]
== 在[]里面存储着各种各样的数据,按照顺序依次排好
==字面量创建一个数组:直接使用[]的方式创建一个数组 eg:var arr = [];var arr2 = [1,'hello',2,3]
==内置构造函数创建数组:使用js的内置构造函数Array创建一个数组 eg:var arr4=new Array();var arr4 = new Array(1,'hello',2,3);var arr4=new Array(10),创建一个数组长度为10的数组。(can'shu)
3.数组的length
== length:长度的意思
== length就是表示数组的长度,数组里面有多少个成员,length就是多少
4.数组的索引
== 索引,也叫做下标,是指一个成员在数组里面排在第一个位置
== 注意:在所有的编程语言中,索引都是从0开始
== 在js里面也是一样,索引从0开始
== 要获取数组的成员的值,需要通过索引来获取,语法: 数组[索引]
5.for循环遍历数组
== 因为数组的索引可以后去数组中的成员值
== 数组的索引又是按照0到length-1顺序排列
== 我们就可以使用for循环来循环数组,因为for循环我们可以设置成0到length-1递增
== 我们把这个行为叫做遍历
五.对象
1.对象是一个复杂数据类型
== 也是一个盒子,里面存储了一些数据
== {}就是一个盒子
== {name:'kk',age:18,height:180,weight:120,love:'rap',hate:'写代码'}
== 这里的{}和函数的{}不一样
== 函数里面的是写代码的,对象里面是写一些数据的
== 对象是一个无序的键值对的集合
== {} 里面的一个键值对我们可以叫做一个成员
== {} 一个成员里面的键,我们也可以叫做成员名
== {} 一个成员里面的值,我们可以叫做成员值
2.创建一个对象
字面量的方式创建一个对象
== 就是用{}来创建一个对象
== 每一个键值对之间用 逗号,分隔
== 键和值之间是 冒号:
内置构造函数创建对象
== js里面内置了对象的构造函数Object,用于创建一个对象
== 语法: var obj = new Object()
3.操作对象的方法
获取对象的成员值:
== 对象.成员名 这个成员名是一个字符串
== 对象[成员名] 这个成员名是一个变量名(一定要给成员名声明一个变量名)
增加/修改成员
== 对象.成员名 = 成员值
== 对象[成员名] = 成员值
删除成员
== delete 对象.成员名 eg:delete obj2.name
== delete 对象[成员名] eg:delete human[a]
eg:
-
字面量的方式创建一个对象
// 创建一个空对象 var obj = {} // 像对象中添加成员 obj.name = 'Jack' obj.age = 18 // 查询对象中的成员值 console.log(obj.name) // 修改对象中的成员值 obj.name ="lucy" // 删除对象中的成员 delete obj.name
-
内置构造函数的方式创建对象
- Object 是 js 内置给我们的构造函数,用于创建一个对象使用的
// 创建一个空对象 var obj = new Object() // 向对象中添加成员 obj.name = 'Rose' obj.age = 20 // 查询对象中的成员值 console.log(obj.name) // 修改对象中的成员值 obj.name ="lucy" // 删除对象中的成员 delete obj.name
六.for…in循环遍历对象中的成员和成员值
// 创建一个空对象
var obj = new Object()
// 向对象中添加成员
obj.name = 'Rose'
obj.age = 20
for(var key in obj){
console.log(key)
console.log(obj[key])
}
- for in 循环的遍历是按照对象中有多少成员来决定了
- 有多少成员,就会执行多少次
key
是我们自己定义的一个变量- 在每次循环的过程中,key 就代表着对象中某一个成员的 属性名
七.数据类型之间存储的区别(重点)
- 既然我们区分了基本数据类型和复杂数据类型
- 那么他们之间就一定会存在一些区别
- 他们最大的区别就是在存储上的区别
- 我们的存储空间分成两种 栈 和 堆
- 栈: 主要存储基本数据类型的内容
- 堆: 主要存储复杂数据类型的内容
八.基本数据类型在内存中的存储情况
var num = 100
,在内存中的存储情况- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EezzH7pK-1661353959166)(H:\Typora\data\TyporaPages\image-20220805192031400.png)]
- 直接在 栈空间 内有存储一个数据
九.复杂数据类型在内存中的存储情况
-
下面这个 对象 的存储
var obj = { name: 'Jack', age: 18, gender: '男' }
-
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CacU42YP-1661353959189)(H:\Typora\data\TyporaPages\image-20220805192049037.png)]
-
复杂数据类型的存储
- 在堆里面开辟一个存储空间
- 把数据存储到存储空间内
- 把存储空间的地址赋值给栈里面的变量
-
这就是数据类型之间存储的区别
十.数据类型之间的比较
-
基本数据类型是 值 之间的比较
var num = 1 var str = '1' console.log(num == str) // true
-
复杂数据类型是 地址 之间的比较
var obj = { name: 'Jack' } var obj2 = { name: 'Jack' } console.log(obj == obj2) // false
- 因为我们创建了两个对象,那么就会在 堆空间 里面开辟两个存储空间存储数据(两个地址)
- 虽然存储的内容是一样的,那么也是两个存储空间,两个地址
- 复杂数据类型之间就是地址的比较,所以
obj
和obj2
两个变量的地址不一样 - 所以我们得到的就是
false
2022年8月8日 数组
快速清空数组
- arr.length=0;
- 错误写法:arr=‘ ’(这样是定义为空字符串)
数组的排序
- 排序,就是把一个乱序的数组,通过我们的处理,让他变成一个有序的数组
- 今天我们讲解两种方式来排序一个数组 冒泡排序 和 选择排序
冒泡排序
-
先遍历数组,让挨着的两个进行比较,如果前一个比后一个大,那么就把两个换个位置
-
数组遍历一遍以后,那么最后一个数字就是最大的那个了
-
然后进行第二遍的遍历,还是按照之前的规则,第二大的数字就会跑到倒数第二的位置
-
以此类推,最后就会按照顺序把数组排好了
-
我们先来准备一个乱序的数组
var arr = [3, 1, 5, 6, 4, 9, 7, 2, 8]
- 接下来我们就会用代码让数组排序
-
先不着急循环,先来看数组里面内容换个位置
// 假定我现在要让数组中的第 0 项和第 1 项换个位置 // 需要借助第三个变量 var tmp = arr[0] arr[0] = arr[1] arr[1] = tmp
-
第一次遍历数组,把最大的放到最后面去
for (var i = 0; i < arr.length; i++) { // 判断,如果数组中的当前一个比后一个大,那么两个交换一下位置 if (arr[i] > arr[i + 1]) { var tmp = arr[i] arr[i] = arr[i + 1] arr[i + 1] = tmp } } // 遍历完毕以后,数组就会变成 [3, 1, 5, 6, 4, 7, 2, 8, 9]
- 第一次结束以后,数组中的最后一个,就会是最大的那个数字
- 然后我们把上面的这段代码执行多次。数组有多少项就执行多少次
-
按照数组的长度来遍历多少次
for (var j = 0; j < arr.length; j++) { for (var i = 0; i < arr.length; i++) { // 判断,如果数组中的当前一个比后一个大,那么两个交换一下位置 if (arr[i] > arr[i + 1]) { var tmp = arr[i] arr[i] = arr[i + 1] arr[i + 1] = tmp } } } // 结束以后,数组就排序好了
-
给一些优化
-
想象一个问题,假设数组长度是 9,第八次排完以后
-
后面八个数字已经按照顺序排列好了,剩下的那个最小的一定是在最前面
-
那么第九次就已经没有意义了,因为最小的已经在最前面了,不会再有任何换位置出现了
-
那么我们第九次遍历就不需要了,所以我们可以减少一次
for (var j = 0; j < arr.length - 1; j++) { for (var i = 0; i < arr.length; i++) { // 判断,如果数组中的当前一个比后一个大,那么两个交换一下位置 if (arr[i] > arr[i + 1]) { var tmp = arr[i] arr[i] = arr[i + 1] arr[i + 1] = tmp } } }
-
第二个问题,第一次的时候,已经把最大的数字放在最后面了
-
那么第二次的时候,其实倒数第二个和最后一个就不用比了
-
因为我们就是要把倒数第二大的放在倒数第二的位置,即使比较了,也不会换位置
-
第三次就要倒数第三个数字就不用再和后两个比较了
-
以此类推,那么其实每次遍历的时候,就遍历当前次数 - 1 次
for (var j = 0; j < arr.length - 1; j++) { for (var i = 0; i < arr.length - 1 - j; i++) { // 判断,如果数组中的当前一个比后一个大,那么两个交换一下位置 if (arr[i] > arr[i + 1]) { var tmp = arr[i] arr[i] = arr[i + 1] arr[i + 1] = tmp } } }
-
-
至此,一个冒泡排序就完成了
-
选择排序
-
先假定数组中的第 0 个就是最小的数字的索引
-
然后遍历数组,只要有一个数字比我小,那么就替换之前记录的索引
-
知道数组遍历结束后,就能找到最小的那个索引,然后让最小的索引换到第 0 个的位置
-
再来第二趟遍历,假定第 1 个是最小的数字的索引
-
在遍历一次数组,找到比我小的那个数字的索引
-
遍历结束后换个位置
-
依次类推,也可以把数组排序好
-
准备一个数组
var arr = [3, 1, 5, 6, 4, 9, 7, 2, 8]
-
假定数组中的第 0 个是最小数字的索引
var minIndex = 0
-
遍历数组,判断,只要数字比我小,那么就替换掉原先记录的索引
var minIndex = 0 for (var i = 0; i < arr.length; i++) { if (arr[i] < arr[minIndex]) { minIndex = i } } // 遍历结束后找到最小的索引 // 让第 minIndex 个和第 0 个交换 var tmp = arr[minIndex] arr[minIndex] = arr[0] arr[0] = tmp
-
按照数组的长度重复执行上面的代码
for (var j = 0; j < arr.length; j++) { // 因为第一遍的时候假定第 0 个,第二遍的时候假定第 1 个 // 所以我们要假定第 j 个就行 var minIndex = j // 因为之前已经把最小的放在最前面了,后面的循环就不需要判断前面的了 // 直接从 j + 1 开始 for (var i = j + 1; i < arr.length; i++) { if (arr[i] < arr[minIndex]) { minIndex = i } } // 遍历结束后找到最小的索引 // 第一堂的时候是和第 0 个交换,第二趟的时候是和第 1 个交换 // 我们直接和第 j 个交换就行 var tmp = arr[minIndex] arr[minIndex] = arr[j] arr[j] = tmp }
-
一些优化
-
和之前一样,倒数第二次排序完毕以后,就已经排好了,最后一次没有必要了
for (var j = 0; j < arr.length - 1; j++) { var minIndex = j for (var i = j + 1; i < arr.length; i++) { if (arr[i] < arr[minIndex]) { minIndex = i } } var tmp = arr[minIndex] arr[minIndex] = arr[j] arr[j] = tmp }
-
在交换变量之前,可以判断一下,如果我们遍历后得到的索引和当前的索引一直
-
那么就证明当前这个就是目前最小的,那么就没有必要交换
-
做一我们要判断,最小作引和当前作引不一样的时候,才交换
for (var j = 0; j < arr.length - 1; j++) { var minIndex = j for (var i = j + 1; i < arr.length; i++) { if (arr[i] < arr[minIndex]) { minIndex = i } } if (minIndex !== j) { var tmp = arr[minIndex] arr[minIndex] = arr[j] arr[j] = tmp } }
-
-
至此,选择排序完成
-
数组的常用方法
-
数组是一个复杂数据类型,我们在操作它的时候就不能再想基本数据类型一样操作了
-
比如我们想改变一个数组
// 创建一个数组 var arr = [1, 2, 3] // 我们想把数组变成只有 1 和 2 arr = [1, 2]
- 这样肯定是不合理,因为这样不是在改变之前的数组
- 相当于心弄了一个数组给到 arr 这个变量了
- 相当于把 arr 里面存储的地址给换了,也就是把存储空间换掉了,而不是在之前的空间里面修改
- 所以我们就需要借助一些方法,在不改变存储空间的情况下,把存储空间里面的数据改变了
数组常用方法之 push
-
push
是用来在数组的末尾追加一个元素 -
语法: 数组.push(追加的元素1,追加的元素2,…)
-
返回值: 增加完以后数组的长度
-
在原数组修改
var arr = [1, 2, 3] // 使用 push 方法追加一个元素在末尾 arr.push(4) console.log(arr) // [1, 2, 3, 4]
数组常用方法之 pop
-
pop
是用来删除数组末尾的一个元素 -
语法: 数组.pop()
-
返回值:被删除的那个元素
-
在原数组修改
var arr = [1, 2, 3] // 使用 pop 方法删除末尾的一个元素 arr.pop() console.log(arr) // [1, 2]
数组常用方法之 unshift
-
unshift
是在数组的最前面添加一个元素 -
语法: 数组.unshift(追加的元素1,追加的元素2,…)
-
返回值: 增加完以后数组的长度
-
在原数组修改
var arr = [1, 2, 3] // 使用 unshift 方法想数组的最前面添加一个元素 arr.unshift(4) console.log(arr) // [4, 1, 2, 3]
数组常用方法之 shift
-
shift
是删除数组最前面的一个元素 -
用来删除数组的最前面一个元素
-
语法: 数组.shift()
-
在原数组修改
var arr = [1, 2, 3] // 使用 shift 方法删除数组最前面的一个元素 arr.shift() console.log(arr) // [2, 3]
数组常用方法之 splice
-
splice
是截取数组中的某些内容,按照数组的索引来截取 -
语法:
splice(从哪一个索引位置开始,截取多少个,替换的新元素)
(第三个参数可以不写) -
返回值:被删除的元素的集合,即使只有一个,也放在盒子里面
-
在原数组修改
-
注意:可以设置截取两个,替换的元素可以实现在索引值处增加。
var arr = [1, 2, 3, 4, 5] // 使用 splice 方法截取数组 arr.splice(1, 2) console.log(arr) // [1, 4, 5]
arr.splice(1, 2)
表示从索引 1 开始截取 2 个内容- 第三个参数没有写,就是没有新内容替换掉截取位置
var arr = [1, 2, 3, 4, 5] // 使用 splice 方法截取数组 arr.splice(1, 2, '我是新内容') console.log(arr) // [1, '我是新内容', 4, 5]
arr.splice(1, 2, '我是新内容')
表示从索引 1 开始截取 2 个内容- 然后用第三个参数把截取完空出来的位置填充
- 新内容可以有多个,逗号隔开
数组常用方法之 reverse
-
reverse
是用来反转数组使用的 -
语法: arr.reverse()
-
返回值: 修改好的原数组
var arr = [1, 2, 3] // 使用 reverse 方法来反转数组 arr.reverse() console.log(arr) // [3, 2, 1]
数组常用方法之 sort
-
sort
是用来给数组排序的 -
语法: arr.sort(排序规则函数) 注意:如果不写排序规则函数,就是按照字符串的规则进行排序,而不是按数字大小排序。
-
数组排序,修改的是原数组
-
返回值:排好序的原数组。
var arr = [2, 3, 1] // 使用 sort 方法给数组排序 arr.sort() console.log(arr) // [1, 2, 3]
arr2.sort(function(a,b){ /*如果返回值是负数 ,那么 a 会被排列到b之前 如果返回值是0 , a 和 b 的相对位置不变 如果返回值是正数 , b 会被排列到 a 之前*/ if(a>b){ return -1; // return b-a }else if(a<b){ return 1; // return b-a }else{ return 0 // return b-a } // 上面的代码可以简写成 return b-a(从大到小),(从小到大是a-b) })
- 这个只是一个基本的简单用法
数组常用方法之 concat
-
concat
是把多个数组进行拼接 -
语法: arr1.concat(arr2,arr3,…)
-
和之前的方法有一些不一样的地方,就是
concat
不会改变原始数组,而是返回一个新的数组var arr = [1, 2, 3] // 使用 concat 方法拼接数组 var newArr = arr.concat([4, 5, 6]) console.log(arr) // [1, 2, 3] console.log(newArr) // [1, 2, 3, 4, 5, 6]
- 注意: concat 方法不会改变原始数组
数组常用方法之 join
-
join
是把数组里面的每一项内容链接起来,变成一个字符串 -
语法: arr.join(‘链接符’)
-
可以自己定义每一项之间链接的内容
join(要以什么内容链接)
-
不会改变原始数组,而是把链接好的字符串返回
-
没有写连接符,默认用逗号链接。
-
注意:如果想要实现将字符串中的split方法实现的单个字符串的数组拼接起来成为字符串,拼接的连接符为空字符串即可。
var arr = [1, 2, 3] // 使用 join 链接数组 var str = arr.join('-') console.log(arr) // [1, 2, 3] console.log(str) // 1-2-3
- 注意: join 方法不会改变原始数组,而是返回链接好的字符串
ES5 中常见的数组常用方法
- 之前我们讲过的数组常用方法都是 ES3 的方法
- 今天来说一些 ES5 中的方法
indexOf
-
indexOf
用来找到数组中某一项的索引 -
语法:
indexOf(你要找的数组中的项)
var arr = [1, 2, 3, 4, 5] // 使用 indexOf 超找数组中的某一项 var index = arr.indexOf(3) console.log(index) // 2
- 我们要找的是数组中值为 3 的那一项
- 返回的就是值为 3 的那一项在该数组中的索引
-
如果你要找的内容在数组中没有,那么就会返回 -1
var arr = [1, 2, 3, 4, 5] // 使用 indexOf 超找数组中的某一项 var index = arr.indexOf(10) console.log(index) // -1
- 你要找的值在数组中不存在,那么就会返回 -1
lastIndexOf
- 找到数组中某一个元素的索引,从末尾开始查找
- 语法: arr.indexOf(你要找的数组元素)
- 返回值: 找到的第一个数组元素的索引,如果没找到就返回-1
forEach
-
和 for 循环一个作用,就是用来遍历数组的
-
语法:
arr.forEach(function (item, index, arr) {})
var arr = [1, 2, 3] // 使用 forEach 遍历数组 arr.forEach(function (item, index, arr) { // item 就是数组中的每一项 // index 就是数组的索引 // arr 就是原始数组 console.log('数组的第 ' + index + ' 项的值是 ' + item + ',原始数组是', arr) })
- 此处有两个函数,一个是forEach函数,一个是作为参数的函数,为了便于区分,我们给作为参数的函数取了名字,叫做回调函数
- forEach() 的时候传递的那个函数,会根据数组的长度执行
- 数组的长度是多少,这个函数就会执行多少回
- 未写return,返回值为undefined
map
-
和 forEach 类似,只不过可以对数组中的每一项进行操作,返回一个新的数组
-
语法: arr.map(function(item,index,arr){}
-
返回值:一个新的数组
var arr = [1, 2, 3] // 使用 map 遍历数组 var newArr = arr.map(function (item, index, arr) { // item 就是数组中的每一项 // index 就是数组的索引 // arr 就是原始数组 return item + 10 }) console.log(newArr) // [11, 12, 13]
filter
-
和 map 的使用方式类似,按照我们的条件来筛选数组
-
把原始数组中满足条件的筛选出来,组成一个新的数组返回
var arr = [1, 2, 3] // 使用 filter 过滤数组 var newArr = arr.filter(function (item, index, arr) { // item 就是数组中的每一项 // index 就是数组的索引 // arr 就是原始数组 return item > 1 }) console.log(newArr) // [2, 3]
- 我们设置的条件就是
> 1
- 返回的新数组就会是原始数组中所有
> 1
的项
- 我们设置的条件就是
every
- 测试一个数组内的所有元素是否都能通过某个指定函数的测试
- 语法:arr.every(callback)
- callback的三个参数:ele,index,arr
- 返回值:布尔值(都符合返回true,否则返回false)
var arr = [1, 30, 39, 29, 10, 13];
console.log(arr.every(function(item){
return item>0
}));// true
some
- 测试数组中是不是至少有 1 个元素通过了被提供的函数测试
- 语法:arr.some(callback)
- callback的三个参数:ele,index,arr
- 返回值:布尔值(如果有元素符合条件返回true,否则返回false)
var arr = [1, 30, 39, 29, 10, 13];
console.log(arr.some(function(item){
return item>10
}));// true
reduce
- 对数组中的每个元素按序执行一个回调函数
- 每一次会将先前元素的计算结果作为参数传入回调函数
- 最后将其结果汇总为单个返回值
- 语法:arr.reduce(callbackFn, [initialValue])
- 返回值:使用回调函数遍历整个数组后的结果。
- callbackFn的参数
- previousValue:上一次调用 callbackFn 时的返回值。在第一次调用时,若指定了初始值 initialValue,其值则为 initialValue,否则为数组索引为 0 的元素 array[0]
- currentIndex:数组中正在处理的元素的索引。若指定了初始值 initialValue,则起始索引号为 0,否则从索引 1 起始
- array:用于遍历的数组
const array1 = [1, 2, 3, 4];
// 0 + 1 + 2 + 3 + 4
const initialValue = 0;
const sumWithInitial = array1.reduce(
function(previousValue, currentValue){
return previousValue + currentValue
},
initialValue
);
console.log(sumWithInitial);// 10
函数参数传递基本数据类型和复杂数据类型的区别
-
之前我们知道了,基本数据类型和复杂数据类型在存储上是有区别的
-
那么他们在赋值之间也是有区别的
-
基本数据类型之间的赋值
var num = 10 var num2 = num num2 = 200 console.log(num) // 100 console.log(num2) // 200
- 相当于是把 num 的值复制了一份一摸一样的给了 num2 变量
- 赋值以后两个在没有关系
-
复杂数据类型之间的赋值
var obj = { name: 'Jack' } var obj2 = obj obj2.name = 'Rose' console.log(obj.name) // Rose console.log(obj2.name) // Rose
- 因为复杂数据类型,变量存储的是地址,真实内容在 堆空间 内存储
- 所以赋值的时候相当于把 obj 存储的那个地址复制了一份给到了 obj2 变量
- 现在 obj 和 obj2 两个变量存储的地址一样,指向一个内存空间
- 所以使用 obj2 这个变量修改空间内的内容,obj 指向的空间也会跟着改变了
函数的参数
-
函数的参数也是赋值的之中,在函数调用的时候,实参给行参赋值
-
和之前变量赋值的规则是一样的
-
函数传递基本数据类型
function fn(n) { n = 200 console.log(n) // 200 } var num = 100 fn(num) console.log(num) // 100
- 和之前变量赋值的时候一样,在把 num 的值复制了一份一摸一样的给到了函数内部的行参 n
- 两个之间在没有任何关系了
-
函数传递复杂数据类型
function fn(o) { o.name = 'Rose' console.log(o.name) // Rose } var obj = { name: 'Jack' } fn(obj) console.log(obj.name) // Rose
- 和之前变量赋值的时候一样,把 obj 内存储的地址复制了一份一摸一样的给到函数内部的行参 o
- 函数外部的 obj 和函数内部的行参 o,存储的是一个地址,指向的是一个存储空间
- 所以两个变量操作的是一个存储空间
- 在函数内部改变了空间内的数据
- obj 看到的也是改变以后的内容
2022年8月9日 字符串
数组去重案例
方法一:
function norepeat(arr){
// 定义一个新数组,存储没有重复的数组元素
var result = [];
// 遍历数组 arr
// 如果遍历到的元素在result里面不存在,就从末尾加入
// 如果遍历到的元素在result里面存在, 就什么都不做
arr.forEach(function(item){
if(result.indexOf(item)==-1){
result.push(item)
}
})
// 返回新数组
return result;
}
console.log(norepeat([12,321,4,213,2,3,132,32,32,4,32,4,324]))
方法二:(因为遍历对象的key值会不同,遇到相同的成员名,key值被替换,即实现了去重)
function norepeat(arr){
// 定义一个对象,记录每个数组元素的出现
var obj = {};
// 遍历数组arr
arr.forEach(function(item){
// 记录每个出现的元素
obj[item] = 1
})
// 把对象中的成员遍历,
var result = [];
for(var key in obj){
// 把遍历到的成员名放到新数组里面
result.push(key)
}
return result;
}
console.log(norepeat(['hello','world','lucy','kily','lucy','lilei','kily']))
严格模式(选择性扩展)
- 我们都知道 js 是一个相对不很严谨的语言
- 而且开发的时候,一些代码也不是很严格要求
- 而严格模式就是对开发的时候写的一些内容做了要求
开启严格模式
-
想开启严格模式,直接在代码最开始的位置写上字符串
use strict
<script> 'use strict' // 下面代码书写就要按照严格模式来书写 </script>
严格模式的规则
-
声明变量必须有
var
关键字'use strict' var num = 100 num2 = 200 // 这个就会报错
- 之前了解过,在声明变量的时候,如果没有 var 关键字,那么按照作用域的规则会自动定义成全局变量
- 严格模式下不可以,会报错
-
函数的行参不可以重复
'use strtic' function fn(p1, p1) {} // 直接就会报错
- 在非严格模式下,函数两个行参一样,是不会报错的,只不过就是相当于在函数内部只有一个变量了
- 但是在严格模式下会报错
-
声明式函数调用的时候函数内部没有 this
'use strtic' function fn() { console.log(this) // undefined } fn()
- 本身,全局声明式函数在调用的时候,函数内部的 this 是指向 window 的
- 在严格模式下,是没有 this 的
创建字符串(了解)
-
我们创建字符串也分为两种方法 字面量 和 构造函数
-
字面量:
var str = 'hello'
-
构造函数创建
var str = new String('hello')
ASCII 字符集(了解)
- 我们都知道,计算机只能存储
0101010
这样的二进制数字 - 那么我们的
a ~ z
/A ~ Z
/ ``/@
/… 之类的内容也有由二进制数字组成的 - 我们可以简单的理解为,
a ~ z
/A ~ Z
/ ``/@
/… 之类的内容都有一个自己的编号,然后在计算机存储的时候,是存储的这些编号,我们看的时候,也是通过这些编号在解析成我们要看到的内容给我们看到
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oUPLx9MJ-1661353959190)(http://8f5843e2874c48668cb250413f8fd4d6/?ynotemdtimestamp=1660007011466)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0MDjgPTf-1661353959191)(http://ef1c22e48df64aa2ae5614cb4757aeba/?ynotemdtimestamp=1660007011466)]
- 上面的就是 ASCII 对照表,我们只需要知道他是这么存储的就好
unicode 编码
- 我们看到了,ASCII 只有这 128 个字符的编码结构
- 但是因为 ASCII 出现的比较早,而且是美国发明的,早起时候这些内容就够用了
- 因为存储一些英文的内容,传递一些英文的文章什么的都够用了
- 那么对于这个世界来说肯定是不够用的
- 因为我们的汉字没有办法存储,包括一些其他国家的语言也没有办法存储
- 所以就出现了 unicode 编码,也叫(万国码,统一码)
- unicode 对照表就是一个和 ASCII 一样的对照表,只不过变得很大很大,因为存储的内容特别的多
- 而且包含了世界上大部分国家的文字,所以我们的文字和字符现在在存储的时候,都是按照 unicode 编码转换成数字进行存储
- 我们的 UTF-8 就是一种 8 位的unicode字符集
- 比较的时候,尽量统一类型,统一字符串类型,比较的就是字符串编码的大小
字符串的常用方法
- 我们操作字符串,也有一堆的方法来帮助我们操作
- 字符串和数组有一个一样的地方,也是按照索引来排列的
length
- 这个字符串里面有多少个字符串
- 语法: str.length
charAt
-
charAt(索引)
是找到字符串中指定索引位置的内容返回(str[索引]也可以实现) -
语法:str.charAt(索引)
var str = 'Jack' // 使用 charAt 找到字符串中的某一个内容 var index = str.charAt(2) console.log(index) // c
- 因为字符串也是按照索引进行排列的,也是同样从 0 开始
- 所以索引 2 的位置就是 c
-
如果没有对应的索引,那么就会返回 空字符串
var str = 'Jack' // 使用 charAt 找到字符串中的某一个内容 var index = str.charAt(10) console.log(index) // ''
- 这个字符串根本没有索引 10 的位置
- 所以就会返回一个空字符串
''
charCodeAt
-
charCodeAt(索引)
就是返回对应索引位置的 unicode 编码,如果没有找到返回NaN -
语法:str.charCodeAt(索引)
var str = 'Jack' // 使用 charAt 找到字符串中的某一个内容 var index = str.charCodeAt(0) console.log(index) // 74
- 因为
J
在 unicode 对照表里面存储的是 74,所以就会返回 74
- 因为
indexOf
-
indexOf
就是按照字符找到对应的索引,如果没有找到返回-1。 -
语法:str.indexOf(字符)
var str = 'Jack' // 使用 indexOf 找到对应的索引 var index = str.indexOf('J') console.log(index) // 0
- 因为字符
J
在字符串Jack
中的索引位置是 0 - 所以会返回 0
- 因为字符
lastIndexOf
- 第一次找到的字符串中指定字符的索引,从最后一个字符开始找
- 语法:str.lastIndexOf(字符)
substring
-
substring
是用来截取字符串使用的 -
语法:
substring(从哪个索引开始,到哪个索引截止)
,包含开始索引,不包含结束索引(包前不包后),返回截取好的字符串。 -
只写开始索引的话,默认截取到末尾
-
截止索引大于字符串长度的话,也是截取到末尾。
var str = 'hello' // 01234 // 使用 substring 截取字符串 var newStr = str.substring(1, 3) console.log(newStr) // el
- 从索引 1 开始,到索引 3 截止,包含前面的索引不包含后面的索引
- 所以返回的是 el
substr
-
substr
也是用来截取字符串的 -
语法:
substr(从哪个索引开始,截取多少个)
-
只写开始索引的话,默认截取到末尾
-
截取长度大于字符串长度的话,也是截取到末尾。
var str = 'hello' // 01234 // 使用 substr 截取字符串 var newStr = str.substr(1, 3) console.log(newStr) // ell
- 这个方法和
substring
不一样的是,第二个参数是截取多少个 - 从索引 1 开始,截取 3 个,所以得到的是
ell
- 这个方法和
slice(数组也可以用)
- slice也可以用来截取字符串的,包含startIndex,不包含endIndex,返回截取好的字符串
- 语法:str.slice(startIndex从哪个索引开始,endIndex截取到哪个索引结束)
- 索引可以是负数, -3就表示倒数第三个,就表示索引: str.length-3
- 只写开始索引的话,默认截取到末尾
- 截取长度大于字符串长度的话,也是截取到末尾。
toLowerCase 和 toUpperCase
-
这两个方法分别使用用来给字符串转成 小写字母 和 大写字母 的
var str = hello // 使用 toUpperCase 转换成大写 var upper = str.toUpperCase() console.log(upper) // HELLO // 使用 toLowerCase 转换成小写 var lower = upper.toLowerCase() console.log(lower) // hello
split
- split是把字符串分割成子字符串数组
- 语法:
str.split('分隔符')
- 分隔符不写则返回的数组包含一个由整个字符串组成的元素
- 注意:如果想要实现将字符串的每个字符输出为单独的数组,拼接的分隔符为空字符串即可
trim
- 删除字符串的两端空白字符
- 语法:
str.trim()
trimStart
- 删除字符串的开头空格
- 语法:
str.trimStart()
或者str.trimLeft()
- trimLeft() 是此方法的别名。
trimEnd
- 移除字符串的末端空白字符
- 语法:
str.trimEnd()
或者str.trimRight()
- trimRight是trimEnd的别名
2022年8月10日 Math和Date
转义符
js里面有一些有特殊含义的符号
// 比如:"",''
// 我们可以在字符串中使用转移符,\
// 可以把有意思的字符转成没有特殊含义的
// 可以把没有特殊含义的转换有特殊含义的
常见转义符(字符串转义字符,都是用\开头,需要写在引号里。)
1.\n 换行
2.\\ 输出\
3.\' 输出单引号
4.\" 输出双引号
5.\t tab缩进
6.\b 空格(b是blank的意思)
Math 和 Date
- Math 是 js 的一个内置对象,提供了一堆的方法帮助我们操作 数字
- Date 是 js 的一个内置对象,提供了一堆的方法帮助我们操作 时间
Math
- 没有什么多余的东西,就是一堆的方法来操作数字
random
-
Math.random()
这个方法是用来生成一个0 ~ 1
之间的随机数 -
每次执行生成的数字都不一样,但是一定是
0 ~ 1
之间的 -
生成的数字包含 0 ,但是不包含 1
var num = Math.random() console.log(num) // 得到一个随机数
round
-
Math.round()
是将一个小数 四舍五入 变成一个整数var num = 10.1 console.log(Math.round(num)) // 10 var num2 = 10.6 console.log(Math.round(num2)) // 11
abs
-
Math.abs()
是返回一个数字的绝对值var num = -10 console.log(math.abs(num)) // 10
ceil
-
Math.ceil()
是将一个小数 向上取整 得到的整数var num = 10.1 console.log(Math.ceil(num)) // 11 var num2 = 10.9 console.log(Math.ceil(num2)) // 11
floor
-
Math.floor()
是将一个小数 向下取整 的到的整数var num = 10.1 console.log(Math.floor(num)) // 10 var num2 = 10.9 console.log(Math.floor(num2)) // 10
max
-
Math.max()
得到的是你传入的几个数字之中最大的那个数字console.log(Math.max(1, 2, 3, 4, 5)) // 5
min
-
Math.min()
得到的是你传入的几个数字之中最小的那个数字console.log(Math.min(1, 2, 3, 4, 5)) // 1
PI
-
Math.PI
得到的是π
的值,也就是3.1415936...
console.log(Math.PI) // 3.141592653589793
- 因为计算机的计算精度问题,只能得到小数点后 15 位
- 使用 Math.PI 的时候,是不需要加 () 的
pow
- Math.pow(x,y) 得到x的y次方
console.log(Math.pow(2,4);// 16
sqrt
- Math.sqrt(x) 返回x的平方根
- x为负值,则 sqrt 返回NaN
console.log(Math.sqrt(4))//2
数字转换进制
-
八进制:0~7 程序里数字前面加0表示八进制
var num=010; console.log(num);//num=8
-
十六进制:0~9 a~f 程序里数字前加0x表十六进制。
-
toString()
方法可以在数字转成字符串的时候给出一个进制数-
语法:
toString(你要转换的进制)
var num = 12 console.log(num.toString(16)) // 'C'
-
Date
- js 提供的内置构造函数,专门用来获取时间的
new Date()
-
new Date()
在不传递参数的情况下是默认返回当前时间var time = new Date() console.log(time) // 当前时间 Fri Mar 01 2019 13:11:23 GMT+0800 (中国标准时间)
-
new Date()
在传入参数的时候,可以获取到一个你传递进去的时间var time = new Date('2019-03-03 13:11:11') console.log(time) // Sun Mar 03 2019 13:11:11 GMT+0800 (中国标准时间)
-
new Date()
传递的参数有多种情况-
传递两个数字,第一个表示年,第二个表示月份
var time = new Date(2019, 00) // 月份从 0 开始计数,0 表示 1月,11 表示 12月 console.log(time) // Tue Jan 01 2019 00:00:00 GMT+0800 (中国标准时间)
-
传递三个数字,前两个不变,第三个表示该月份的第几天,从 1 到 31
var time = new Date(2019, 00, 05) console.log(time) // Sat Jan 05 2019 00:00:00 GMT+0800 (中国标准时间)
-
传递四个数字,前三个不变,第四个表示当天的几点,从 0 到 23
var time = new Date(2019, 00, 05, 22) console.log(time) // Sat Jan 05 2019 22:00:00 GMT+0800 (中国标准时间)
-
传递五个数字,前四个不变,第五个表示的是该小时的多少分钟,从 0 到 59
var time = new Date(2019, 00, 05, 22, 33) console.log(time) // Sat Jan 05 2019 22:33:00 GMT+0800 (中国标准时间)
-
传递六个数字,前五个不变,第六个表示该分钟的多少秒,从 0 到 59
var time = new Date(2019, 00, 05, 22, 33, 55) console.log(time) // Sat Jan 05 2019 22:33:55 GMT+0800 (中国标准时间)
-
传入字符串的形式
console.log(new Date('2019')) // Tue Jan 01 2019 08:00:00 GMT+0800 (中国标准时间) console.log(new Date('2019-02')) // Fri Feb 01 2019 08:00:00 GMT+0800 (中国标准时间) console.log(new Date('2019-02-03')) // Sun Feb 03 2019 08:00:00 GMT+0800 (中国标准时间) console.log(new Date('2019-02-03 13:')) // Sun Feb 03 2019 13:00:00 GMT+0800 (中国标准时间) console.log(new Date('2019-02-03 13:13:')) // Sun Feb 03 2019 13:13:00 GMT+0800 (中国标准时间) console.log(new Date('2019-02-03 13:13:13')) // Sun Feb 03 2019 13:13:13 GMT+0800 (中国标准时间)
-
将日期字符串格式化成指定内容
- 比如我们得到的时间字符串是
Sun Feb 03 2019 13:13:13 GMT+0800 (中国标准时间)
- 我指向得到这个日期中是那一年,我们就要靠截取字符串的形式得到
- 但是现在 js 为我们提供了一系列的方法来得到里面的指定内容
toLocaleString
-
语法: toLocaleString()
-
把日期格式转换成字符串格式
var date = new Date() console.log(date.toLocaleString());// 2022/8/10 10:59:46
getFullYear
-
getFullYear()
方式是得到指定字符串中的哪一年var time = new Date(2019, 03, 03, 08, 00, 22) console.log(time.getFullYear()) // 2019
getMonth
-
getMonth()
方法是得到指定字符串中的哪一个月份var time = new Date(2019, 03, 03, 08, 00, 22) console.log(time.getMonth()) // 3
- 这里要有一个注意的地方
- 月份是从 0 开始数的
- 0 表示 1月,1 表示 2月,依此类推
getDate
-
getDate()
方法是得到指定字符串中的哪一天var time = new Date(2019, 03, 03, 08, 00, 22) console.log(time.getDate()) // 3
getHours
-
getHours()
方法是得到指定字符串中的哪小时var time = new Date(2019, 03, 03, 08, 00, 22) console.log(time.getHours()) // 8
getMinutes
-
getMinutes()
方法是得到指定字符串中的哪分钟var time = new Date(2019, 03, 03, 08, 00, 22) console.log(time.getMinutes()) // 0
getSeconds
-
getSeconds()
方法是得到指定字符串中的哪秒钟var time = new Date(2019, 03, 03, 08, 00, 22) console.log(time.getSeconds()) // 22
getDay
-
getDay()
方法是得到指定字符串当前日期是一周中的第几天(周日是 0,周六是 6)var time = new Date(2019, 03, 08, 08, 00, 22) console.log(time.getDay()) // 1
getTime
-
getTime()
方法是得到执行时间到格林威治时间
的毫秒数var time = new Date(2019, 03, 08, 08, 00, 22) console.log(time.getTime()) // 1554681622000
获取时间差
- 是指获取两个时间点之间相差的时间
- 在 js 中是不能用时间直接做 减法 的
- 我们需要一些特殊的操作
- 在编程的世界里面,有一个特殊的时间,是
1970年01月01日00时00分00秒
- 这个时间我们叫做
格林威治时间
- 所有的编程世界里面,这个时间都是一样的,而且
格林威治时间
的数字是 0 - 从
格林威治时间
开始,每经过1毫秒,数字就会 + 1 - 所以我们可以获取到任意一个时间节点到
格林威治时间
的毫秒数 - 然后在用两个毫秒数相减,就能得到两个时间点之间相差的毫秒数
- 我们在通过这个毫秒数得到准确的时间
计算时间差
- 例如:我们现在计算一下
2019-01-01 00:00:00
到2019-01-03 04:55:34
的时间差
-
先获取两个时间点到
格林威治时间
的毫秒数var time1 = new Date('2019-01-01 00:00:00') var time2 = new Date('2019-01-03 04:55:34') time1 = time1.getTime() time2 = time2.getTime() console.log(time1) // 1546272000000 console.log(time2) // 1546462534000
-
两个时间相减,得到两个时间点之间相差的毫秒数
var differenceTime = time2 - time1 console.log(differenceTime) // 190534000
- 现在我们计算出了两个时间点之间相差的毫秒数
-
把我们计算的毫秒数换算成时间
-
先计算出有多少天
-
以为一天是
1000 * 60 * 60 * 24
毫秒 -
用总的毫秒数除以一天的毫秒数,就能得到多少天了
var time1 = new Date('2019-01-01 00:00:00') var time2 = new Date('2019-01-03 04:55:34') time1 = time1.getTime() time2 = time2.getTime() var differenceTime = time2 - time1 // 计算整的天数 var day = differenceTime / (1000 * 60 * 60 * 24) // 2.20525462962963 day = Math.ceil(day) // 2
- 因为得到的是有小数的天数,我们向下取整,得到有多少个整的天数
-
使用
differenceTime
减去两天所包含的毫秒数,剩下的就是不够一天的毫秒数 -
用不够一天的毫秒数计算出有多少个小时
-
因为一个小时是
1000 * 60 * 60
毫秒 -
用不够一天的毫秒数除以一小时的毫秒数,就能得到多少小时了
var time1 = new Date('2019-01-01 00:00:00') var time2 = new Date('2019-01-03 04:55:34') time1 = time1.getTime() time2 = time2.getTime() var differenceTime = time2 - time1 // 计算整的天数 var day = differenceTime / (1000 * 60 * 60 * 24) // 2.20525462962963 day = Math.floor(day) // 2 // 计算整的小时数 var afterHours = differenceTime - (1000 * 60 * 60 * 24 * 2) var hours = afterHours / (1000 * 60 * 60) hours = Math.floor(hours) // 4
- 和刚才一样的道理,我们需要向下取整
-
同理,使用
afterHours
- 4个小时包含的毫秒数,剩下的就是不够一个小时的毫秒数 -
用不够一个小时的毫秒数计算出有多少分钟
-
因为一分钟是
1000 * 60
毫秒 -
用不够一个小时的毫秒数除以一分钟的毫秒数就能得到多少分钟了
var time1 = new Date('2019-01-01 00:00:00') var time2 = new Date('2019-01-03 04:55:34') time1 = time1.getTime() time2 = time2.getTime() var differenceTime = time2 - time1 // 计算整的天数 var day = differenceTime / (1000 * 60 * 60 * 24) // 2.20525462962963 day = Math.floor(day) // 2 // 计算整的小时数 var afterHours = differenceTime - (1000 * 60 * 60 * 24 * 2) var hours = afterHours / (1000 * 60 * 60) hours = Math.floor(hours) // 4 // 计算整分钟数 var afterMinutes = afterHours - (1000 * 60 * 60 * 4) var minutes = afterMinutes / (1000 * 60) minutes = Math.floor(minutes) // 55
-
和之前一样的道理计算出秒
var time1 = new Date('2019-01-01 00:00:00') var time2 = new Date('2019-01-03 04:55:34') time1 = time1.getTime() time2 = time2.getTime() var differenceTime = time2 - time1 // 计算整的天数 var day = differenceTime / (1000 * 60 * 60 * 24) // 2.20525462962963 day = Math.floor(day) // 2 // 计算整的小时数 var afterHours = differenceTime - (1000 * 60 * 60 * 24 * 2) var hours = afterHours / (1000 * 60 * 60) hours = Math.floor(hours) // 4 // 计算整分钟数 var afterMinutes = afterHours - (1000 * 60 * 60 * 4) var minutes = afterMinutes / (1000 * 60) minutes = Math.floor(minutes) // 55 // 计算整秒数 var afterSeconds = afterMinutes - (1000 * 60 * 55) var seconds = afterSeconds / 1000 seconds = Math.floor(seconds) // 34
-
最后,同理减去整秒的数,剩下的就是毫秒数
var time1 = new Date('2019-01-01 00:00:00') var time2 = new Date('2019-01-03 04:55:34') time1 = time1.getTime() time2 = time2.getTime() var differenceTime = time2 - time1 // 计算整的天数 var day = differenceTime / (1000 * 60 * 60 * 24) // 2.20525462962963 day = Math.floor(day) // 2 // 计算整的小时数 var afterHours = differenceTime - (1000 * 60 * 60 * 24 * 2) var hours = afterHours / (1000 * 60 * 60) hours = Math.floor(hours) // 4 // 计算整分钟数 var afterMinutes = afterHours - (1000 * 60 * 60 * 4) var minutes = afterMinutes / (1000 * 60) minutes = Math.floor(minutes) // 55 // 计算整秒数 var afterSeconds = afterMinutes - (1000 * 60 * 55) var seconds = afterSeconds / 1000 seconds = Math.floor(seconds) // 34 // 计算毫秒数 var milliSeconds = afterSeconds - (1000 * 34) // 0
-
最后我们把结果输出一下就可以了
document.write('2019-01-01 00:00:00 和 2019-01-03 04:55:34 之间相差') document.write(day + '天' + hours + '小时' + minutes + '分钟' + seconds + '秒' + milliSeconds + '毫秒')
-
set方法
- setDate()
- setFullYear()
- setMonth()
- setUTCDate() - 用来指定日期,1-31
- setHours()
- setMinutes()
- setSeconds()
- setMilliseconds()
- setTime()
定时器
- 在 js 里面,有两种定时器,倒计时定时器 和 间隔定时器
倒计时定时器
-
倒计时多少时间以后执行函数
-
语法:
setTimeout(要执行的函数,多长时间以后执行)
-
会在你设定的时间以后,执行函数
var timerId = setTimeout(function () { console.log('我执行了') }, 1000) console.log(timerId) // 1
- 时间是按照毫秒进行计算的,1000 毫秒就是 1秒钟
- 所以会在页面打开 1 秒钟以后执行函数
- 只执行一次,就不在执行了
- 返回值是,当前这个定时器是页面中的第几个定时器
间隔定时器
-
每间隔多少时间就执行一次函数
-
语法:
setInterval(要执行的函数,间隔多少时间)
var timerId = setInterval(function () { console.log('我执行了') }, 1000)
- 时间和刚才一样,是按照毫秒进行计算的
- 每间隔 1 秒钟执行一次函数
- 只要不关闭,会一直执行
- 返回值是,当前这个定时器是页面中的第几个定时器
定时器的返回值
-
设置定时器的时候,他的返回值是部分
setTimeout
和setInterval
的 -
只要有一个定时器,那么就是一个数字
var timerId = setTimeout(function () { console.log('倒计时定时器') }, 1000) var timerId2 = setInterval(function () { console.log('间隔定时器') }, 1000) console.log(timerId) // 1 console.log(timerId2) // 2
关闭定时器
-
我们刚才提到过一个 timerId,是表示这个定时器是页面上的第几个定时器
-
这个 timerId 就是用来关闭定时器的数字
-
我们有两个方法来关闭定时器
clearTimeout
和clearInterval
var timerId = setTimeout(function () { console.log('倒计时定时器') }, 1000) clearTimeout(timerId)
- 关闭以后,定时器就不会在执行了
var timerId2 = setInterval(function () { console.log('间隔定时器') }, 1000) clearInterval(timerId2)
- 关闭以后定时器就不会在执行了
-
原则上是
clearTimeout
关闭setTimeout
clearInterval
关闭setInterval
-
但是其实是可以通用的,他们可以混着使用
var timerId = setTimeout(function () { console.log('倒计时定时器') }, 1000) // 关闭倒计时定时器 clearInterval(timerId) var timerId2 = setInterval(function () { console.log('间隔定时器') }, 1000) // 关闭间隔定时器 clearTimeout(timerId2)
异步代码执行机制
我们写的js代码分为两种
1.同步代码: 类似银行的vip客户,来了就办业务
不是异步的就都是同步代码
2.异步代码: 类似银行的普通客户,需要排队叫号办业务
== 目前我们学过的异步代码只有一种
== 就是定时器里面要执行的函数里面的代码(ding'shi'q)
3.代码执行顺序
== 先执行全部的同步代码
== 再执行排队的异步代码
console.log('js start');// 同步代码
// 定义变量,并给变量赋值一个函数是同步代码
var fn = function(){
// 由于fn里面的代码时定时器要执行的
// 所以要等待所有的同步代码执行完,并且时间到了才执行
console.log('我是定时器里面的函数')
}
console.log('world');// 同步代码
// 定义一个定时器,就相当于取号,是同步代码
setTimeout(fn,1000)
console.log('hello');// 同步代码
console.log('js end');// 同步代码
2022年8月11日 BOM/DOM(上)
- 今天开始使用 js 去操作浏览器和页面中的 html 元素了
BOM
- BOM(Browser Object Model): 浏览器对象模型
- 其实就是操作浏览器的一些能力
- 我们可以操作哪些内容
- 获取窗口的大小
- 操作浏览器进行页面跳转
- 获取当前浏览器地址栏的信息
- 操作浏览器的滚动条
- 让浏览器出现一个弹出框
- BOM 的核心就是 window 对象
- window 是浏览器内置的一个对象,里面包含着操作浏览器的方法
获取浏览器窗口的尺寸
-
innerHeight
和innerWidth
-
这两个方法分别是用来获取浏览器窗口的宽度和高度(包含滚动条的)
var windowHeight = window.innerHeight console.log(windowHeight) var windowWidth = window.innerWidth console.log(windowWidth)
浏览器的弹出层
-
alert
是在浏览器弹出一个提示框window.alert('我是一个提示框')
- 这个弹出层知识一个提示内容,只有一个确定按钮
- 点击确定按钮以后,这个提示框就消失了
-
confirm
是在浏览器弹出一个询问框var boo = window.confirm('我是一个询问框') console.log(boo)
-
这个弹出层有一个询问信息和两个按钮
-
当你点击确定的时候,就会得到 true
-
当你点击取消的时候,就会得到 false
-
prompt
是在浏览器弹出一个输入框var str = window.prompt('请输入内容') console.log(str)
-
这个弹出层有一个输入框和两个按钮
-
当你点击取消的时候,得到的是 null
-
当你点击确定的时候得到的就是你输入的内容
浏览器的地址信息
- 在 window 中有一个对象叫做
location
- 就是专门用来存储浏览器的地址栏内的信息的
location.href
-
location.href
这个属性存储的是浏览器地址栏内 url 地址的信息console.log(window.location.href)
- 会把中文变成url 编码的格式
-
location.href
这个属性也可以给他赋值location.href = './index.html' // 这个就会跳转页面到后面你给的那个地址
location.reload
-
location.reload()
这个方法会重新加载一遍页面,就相当于刷新是一个道理window.location.reload()
- 注意: 不要写在全局,不然浏览器就会一直处在刷新状态
浏览器的历史记录
- window 中有一个对象叫做
history
- 是专门用来存储历史记录信息的
history.back
-
history.back
是用来会退历史记录的,就是回到前一个页面,就相当于浏览器上的 ⬅️ 按钮 -
语法:history.back()
window.history.back()
- 前提是你要有上一条记录,不然就是一直在这个页面,也不会回退
history.forword
-
history.forword
是去到下一个历史记录里面,也就是去到下一个页面,就相当于浏览器上的 ➡️ 按钮window.history.forward()
- 前提是你要之前有过回退操作,不然的话你现在就是最后一个页面,没有下一个
history.go
- 和前面两个的作用一样,如果参数是正数表示前进,如果参数是负数表示后退 (eg:2就是前进两页,-1就是回到上一页)
- 语法:history.go()
- 前提也是得有能前进和后退的页面数
浏览器的 onload 事件
-
这个不在是对象了,而是一个事件
-
表示这个事件会在浏览器的所有资源加载完成后触发
window.onload = function () { console.log('页面已经加载完毕') }
在 html 页面中把 js 写在 head 里面
<html>
<head>
<meta charset="UTF-8" />
<script>
// 这个代码执行的时候,body 还没有加载
// 这个时候我们就获取不到 body 中的那个 div
// 就需要使用 window.onload 事件
window.onload = function () {
// 这个函数会在页面加载完毕以后在执行
// 那么这个时候页面的 DOM 元素都已经加载了,我们就可以获取 div 了
console.log(box)
}
</script>
</head>
<body>
<div></div>
</body>
</html>
在 html 页面中把 js 写在 body 最后面
<html>
<head>
<meta charset="UTF-8" />
</head>
<body>
<div></div>
<script>
// 这个代码执行的时候,body 已经加载完毕了
// 在这里就可以获取到 div,写不写 window.onload 就无所谓了
window.onload = function () {
// 这个函数会在页面加载完毕以后在执行
// 那么这个时候页面的 DOM 元素都已经加载了,我们就可以获取 div 了
console.log(box)
}
</script>
</body>
</html>
浏览器的 onresize事件
-
这个 onresize 事件是当浏览器的大小改变的的时候触发
window.onresize = function () { console.log('浏览器大小改变了') }
浏览器的 onscroll 事件
-
这个 onscroll 事件是当浏览器的滚动条滚动的时候触发
-
或者鼠标滚轮滚动的时候触发
window.onscroll = function () { console.log('浏览器滚动了') }
- 注意:前提是页面的高度要超过浏览器的可是窗口才可以
浏览器滚动的距离
- 浏览器内的内容即然可以滚动,那么我们就可以获取到浏览器滚动的距离
scrollTop
-
获取的是页面向上滚动的距离
-
一共有两个获取方式
document.body.scrollTop
document.documentElement.scrollTop
window.onscroll = function () { console.log(document.body.scrollTop) console.log(document.documentElement.scrollTop) }
-
两个都是获取页面向上滚动的距离
-
兼容写法:document.body.scrollTop+document.documentElement.scrollTop document.body.scrollTop||document.documentElement.scrollTop
-
区别:
- IE 浏览器
- 没有
DOCTYPE
声明的时候,用这两个都行 - 有
DOCTYPE
声明的时候,只能用document.documentElement.scrollTop
- 没有
- Chrome 和 FireFox
- 没有
DOCTYPE
声明的时候,用document.body.scrollTop
- 有
DOCTYPE
声明的时候,用document.documentElement.scrollTop
- 没有
- Safari
- 两个都不用,使用一个单独的方法
window.pageYOffset
- 两个都不用,使用一个单独的方法
- IE 浏览器
-
案例:实现点击按钮,滚动到指定位置。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> <style> div{ position: fixed; bottom:10px; right:10px; width: 100px; height: 100px; line-height: 100px; cursor: pointer; font-size: 20px; background-color: brown; color:white; text-align: center; } body{ height:10000px; } </style> </head> <body> <!-- 凡是在html中的内容都是字符串 --> <!-- div被点击的时候从字符串"goTop()",取出里面的js代码来执行 --> <div onclick="goTop()">回到顶部</div> <script> /* 需求: 1 "点击"回到顶部按钮 2 浏览器"向上的滚动距离"变成0 */ // 先定义一个函数,这个函数不调用不执行 // 只有当回到顶部按钮的点击事件触发的时候,有浏览器调用 function goTop(){ // 方法1:给scrollTop赋值为0,滚动距离"瞬间"变成0 // document.documentElement.scrollTop = 0; // 方法2: window上有一个方法scrollTo可以实现滚动到指定位置 // 语法: // window.scrollTo(x,y) // window.scrollTo(0,0) // window.scrollTo({top:'向上滚动的距离',left:'向左滚动的距离',behavior:'smooth'}) window.scrollTo({ top:0, left:0, behavior:'smooth' }) } </script> </body> </html>
scrollLeft
-
获取页面向左滚动的距离
-
也是两个方法
-
document.body.scrollLeft
-
document.documentElementLeft
window.onscroll = function () { console.log(document.body.scrollLeft) console.log(document.documentElement.scrollLeft) }
-
两个之间的区别和之前的
scrollTop
一样
-
pageXOffset和pageYOffset(相当于scrollX 和scrollY)
- x指的是向左滚动的距离,y是向上滚动的距离。
- 高版本浏览器都支持
- 都是只读的,不能赋值。
window.scrollTo()
-
window中的方法scrollTo可以实现滚动到指定位置
-
语法:window.scrollTo(x,y)
window.scrollTo(0,0)
(也可以用对象,其中
x
是文档中的横轴坐标(left),y
是文档中的纵轴坐标(top)。behavior
类型 String,表示滚动行为,支持参数 smooth(平滑滚动),instant(瞬间滚动),默认值 auto) window.scrollTo({top:‘向上滚动的距离’,left:‘向左滚动的距离’,behavior:‘smooth’})
window.open
- 用于(浏览器或iframe或标签页)打开窗口
- 语法:window.open(新窗口的url地址,新窗口的名称)
- 返回值: 控制新窗口的对象
window.close
- 用于关闭窗口
- 语法:window.close() 关闭当前窗口;窗口对象.close() 关闭指定的窗口
本地存储 localStorage /sessionStorage的基本使用
一.解释:
1.浏览器专门开辟了一个空间
2.用于存储一些数据,这些数据都是字符串格式
3.同一个网站存储的数据,这个网站下面的所有页面都可以获取和修改
4.比如,我们使用 file:// 打开的页面都可以共享这个存储空间,file://1.html存储在本地存储或会话存储里面的内容,file://2.html也可以获取和修改
二.本地存储和会话存储的区别
== 本地存储只要不手动删除就一直存在
== 会话存储关闭浏览器就消失了
-
一.本地存储: window.localStorage
1.在本地存储存储一条数据 语法:window.localStorage.setItem(key,value) key:这条数据的名称(键) value:这条数据的值,你如果写的不是字符串,浏览器会通过toString强转再存储 window.localStorage.setItem('name','lucy') window.localStorage.setItem('age',12) 2.修改本地存储的一条数据 由于key不能重复,再设置一次就是修改 window.localStorage.setItem('name','志强') 3.删除本地存储的一条数据 语法:window.localStorage.removeItem(key) window.localStorage.removeItem('name') 4.获取一个键对应的本地存储值 语法:window.localStorage.getItem(key) console.log(window.localStorage.getItem('age')) 5.清空所有本地存储的数据 window.localStorage.clear() 本地存储localStorage是一个window上的对象 console.log(window.localStorage)
-
会话存储:window.sessionStorage
会话存储:sessionStorage 1.设置 window.sessionStorage.setItem('username','jack') window.sessionStorage.setItem('password','123456') 2.修改 = 重新设置 window.sessionStorage.setItem('password','admin') 3.删除 window.sessionStorage.removeItem('username') 4.获取 console.log(sessionStorage.getItem('password')) 5.清空 sessionStorage.clear()
DOM(上)
- DOM(Document Object Model): 文档对象模型
- 其实就是操作 html 中的标签的一些能力
- 我们可以操作哪些内容
- 获取一个元素
- 移除一个元素
- 创建一个元素
- 向页面里面添加一个元素
- 给元素绑定一些事件
- 获取元素的属性
- 给元素添加一些 css 样式
- …
- DOM 的核心对象就是 docuemnt 对象
- document 对象是浏览器内置的一个对象,里面存储着专门用来操作元素的各种方法
- DOM: 页面中的标签,我们通过 js 获取到以后,就把这个对象叫做 DOM 对象
获取一个元素
- 通过 js 代码来获取页面中的标签
- 获取到以后我们就可以操作这些标签了
- js获取到标签叫做dom元素或dom节点或dom对象
getElementById()
-
getElementById('要获取的元素的id')
是通过标签的 id 名称来获取标签的 -
因为在一个页面中 id 是唯一的,所以获取到的就是一个元素
<body> <div id="box"></div> <script> var box = document.getElementById('box') console.log(box) // <div></div> </script> </body>
- 获取到的就是页面中的那个 id 为 box 的 div 标签
getElementsByClassName()
-
getElementsByClassName('要获取的元素集合的类名')
是用过标签的 class 名称来获取标签的 -
因为页面中可能有多个元素的 class 名称一样,所以获取到的是一组元素
-
用document调用表示在整个页面中获取
-
用dom元素(某个标签下)调用表示在这个dom元素中获取
-
哪怕你获取的 class 只有一个,那也是获取一组元素,只不过这一组中只有一个 DOM 元素而已
<body> <div calss="box"></div> <script> var box = document.getElementsByClassName('box') console.log(box) // [<div></div>] console.log(box[0]) // <div></div> </script> </body>
- 获取到的是一组元素,是一个长得和数组一样的数据结构,但是不是数组,是伪数组
- 这个一组数据也是按照索引排列的,所以我们想要准确的拿到这个 div,需要用索引(0开始)来获取
getElementsByTagName()
-
getElementsByTagName('要获取的元素的标签名')
是用过标签的 标签 名称来获取标签的 -
因为页面中可能有多个元素的标签名称一样,所以获取到的是一组元素
-
用document调用表示在整个页面中获取
-
用dom元素(某个标签下)调用表示在这个dom元素中获取
-
哪怕真的只有一个这个标签名,那么也是获取一组元素,只不过这一组中只有一个 DOM 元素而已
<body> <div></div> <script> var box = document.getElementsByTagName('div') console.log(box) // [<div></div>] console.log(box[0]) // <div></div> </script> </body>
- 和
getElementsByClassName
一样,获取到的是一个长得很像数组的元素 - 必须要用索引(0开始)才能得到准确的 DOM 元素
- 和
querySelector()
-
querySelector('要获取的元素的css选择器')
是按照选择器的方式来获取元素 -
也就是说,按照我们写 css 的时候的选择器来获取
-
用document调用表示在整个页面中获取
-
用dom元素(某个标签下)调用表示在这个dom元素中获取
-
这个方法只能获取到一个元素,并且是页面中第一个满足条件的元素
console.log(document.querySelector('div')) // 获取页面中的第一个 div 元素 console.log(docuemnt.querySelector('.box')) // 获取页面中第一个有 box 类名的元素 console.log(document.querySelector('#box')) // 获取页面中第一个 id 名为 box 的元素
注意:返回的是元素,不是集合
querySelectorAll()
-
querySelectorAll('要获取的元素集合的css选择器')
是按照选择器的方式来获取元素 -
这个方法能获取到所有满足条件的元素,以一个伪数组的形式返回
-
用document调用表示在整个页面中获取
-
用dom元素(某个标签下)调用表示在这个dom元素中获取
console.log(document.querySelectorAll('div')) // 获取页面中的所有的 div 元素 console.log(docuemnt.querySelectorAll('.box')) // 获取页面中所有有 box 类名的元素
- 获取到的是一组数据,也是需要用索引来获取到准确的每一个 DOM 元素
2022年8月12日 Dom下
操作元素内容
innerHTML(认识标签)
-
获取元素内部的 HTML 结构
<body> <div> <p> <span>hello</span> </p> </div> <script> var div = document.querySelector('div') console.log(div.innerHTML) /* <p> <span>hello</span> </p> */ </script> </body>
-
设置元素的内容
<body> <div></div> <script> var div = document.querySelector('div') div.innerHTML = '<p>hello</p>' </script> </body>
- 设置完以后,页面中的 div 元素里面就会嵌套一个 p 元素
innerText(不认识标签)
-
获取元素内部的文本(只能获取到文本内容,获取不到 html 标签)
<body> <div> <p> <span>hello</span> </p> </div> <script> var div = document.querySelector('div') console.log(div.innerText) // hello </script> </body>
-
可以设置元素内部的文本
<body> <div></div> <script> var div = document.querySelector('div') div.innerText = '<p>hello</p>' </script> </body>
- 设置完毕以后,会把
<p>hello</p>
当作一个文本出现在 div 元素里面,而不会把 p 解析成标签
- 设置完毕以后,会把
value
- 获取表单元素的value值
var input = document.getElementsByTagName('input')[0]
// 获取表单元素的value值
console.log(input.value)
- 设置表单元素的value值
input.value = "我设置的value值"
// 案例:获取下拉列表被选中的值
var select = document.querySelector('select')
// 获取下拉列表被选中的值
console.log(select.value);// 2
// 设置下拉列表被选中的值
select.value = '3'
操作元素属性
只有对标签有特殊含义的属性在js对象和标签中都有
-
<!-- html标签 --> <!-- 对于标签没有特殊含义的:a aa 只显示在html标签中 --> <!-- data-info和data-my-banji都是html5标准的自定义属性 --> <img src="./1.jpg" alt="xxx" title="yyy" a="1" aa="234" data-info="qf" data-my-banji="sz2209">
原生属性的操作
-
原生属性:有特殊含义的属性(eg:img里的src)
-
语法: dom.属性名 = 属性值 (操作对象的键值对方法一样)
// 操作dom元素的属性 var img = document.querySelector('img') // 给dom对象添加键值对,这个dom对象上添加的键值对aaa没有特殊含义,只显示在dom对象上 img.aaa = "hello" // 以对象格式输出dom元素 console.dir(img)
自定义属性的操作
- 自定义属性:没有特殊含义的属性
- html5出了新标准,如果要定义自定义属性,建议使用 data-xxx
getAttribute
-
获取元素的某个属性(包括自定义属性)
-
语法:dom.getAttribute(‘标签上的属性名’)
<body> <div a="100" class="box"></div> <script> var div = document.querySelector('div') console.log(div.getAttribute('a')) // 100 console.log(div.getAttribute('class')) // box </script> </body>
setAttribute
-
给元素设置一个属性(包括自定义属性)
-
语法:dom.setAttribute(‘标签上的属性名’,‘标签上的属性值’)
<body> <div></div> <script> var div = document.querySelector('div') div.setAttribute('a', 100) div.setAttribute('class', 'box') console.log(div) // <div a="100" class="box"></div> </script> </body>
removeAttribute
-
直接移除元素的某个属性
-
语法:dom.removeAttribute(‘标签上的属性名’)
<body> <div a="100" class="box"></div> <script> var div = document.querySelector('div') div.removeAttribute('class') console.log(div) // <div a="100"></div> </script> </body>
html5属性data-xxx
-
html5出了新标准,如果要定义自定义属性,建议使用 data-xxx
// 1-3html5标准的标签自定义属性 // 如果自定义属性: data-xx => dom.dataset.xx // 如果自定义属性: data-xx-yy-zz => dom.dataset.xxYyZz // 获取 console.log(img.dataset.info) console.log(img.dataset.myBanji) // 设置 img.dataset.info = "xyz"
操作元素样式
style
-
专门用来给元素添加 css 样式的
-
注意:添加的都是行内样式,dom.style只能获取元素的行内样式,而且获取到的是字符串
-
语法:dom.style.样式名称 = 样式值
<body> <div></div> <script> var div = document.querySelector('div') div.style.width = "100px" div.style.height = "100px" div.style.backgroundColor = "pink" console.log(div) // <div style="width: 100px; height: 100px; background-color: pink;"></div> </script> </body>
- 页面中的 div 就会变成一个宽高都是100,背景颜色是粉色
- 注意:如果样式名称有-,又驼峰命名 : background-color写成backgroundColor,margin-left写成marginLeft
获取元素的非行间样式
-
我们在操作 DOM 的时候,很重要的一点就是要操作元素的 css 样式
-
那么在操作 css 样式的时候,我们避免不了就要获取元素的样式
-
之前我们说过可以用
元素.style.xxx
来获取 -
但是这个方法只能获取到元素 行间样式,也就是写在行内的样式
<style> div { width: 100px; } </style> <body> <div style="height: 100px;"> <p>我是一个 p 标签</p> </div> <script> var oDiv = document.querySelector('div') console.log(oDiv.style.height) // 100px console.log(oDIv.style.width) // '' </script> </body>
-
不管是外链式还是内嵌式,我们都获取不到该元素的样式
-
这里我们就要使用方法来获取了 getComputedStyle 和 currentStyle
-
这两个方法的作用是一样的,只不过一个在 非 IE 浏览器,一个在 IE 浏览器
getComputedStyle
-
获取元素的非行内样式
-
语法:
window.getComputedStyle(元素, null).要获取的属性
-
返回值是一个样式值,是字符串
<style> div { width: 100px; } </style> <body> <div style="height: 100px;"> <p>我是一个 p 标签</p> </div> <script> var oDiv = document.querySelector('div') console.log(window.getComputedStyle(oDiv).width) // 100px console.log(window.getComputedStyle(oDiv).height) // 100px </script> </body>
- 这个方法获取行间样式和非行间样式都可以
总结
- 获取样式: 使用window.getComputedStyle(dom元素,null).要获取的样式名称
- 设置样式: 使用dom元素.style.样式名 = 样式值
操作元素类名
className
-
专门用来操作元素的 类名的
<body> <div class="box"></div> <script> var div = document.querySelector('div') console.log(div.className) // box </script> </body>
-
也可以设置元素的类名,不过是全覆盖式的操作
<body> <div class="box"></div> <script> var div = document.querySelector('div') div.className = 'test' console.log(div) // <div class="test"></div> </script> </body>
- 在设置的时候,不管之前有没有类名,都会全部被设置的值覆盖
classList
-
是一个只读属性,返回一个元素的类属性集合
-
添加类名语法: 元素.classList.add(‘要添加的类名1’,‘要添加的类名2’,…)
-
删除类名语法: 元素.classList.rmeove(‘要删除的类名1’,‘要删除的类名2’,…)
-
切换类名语法: 元素.classList.toggle(‘要切换的类名’),注意:有则移除,无则添加
-
判断是否包含某个类名语法: 元素.classList.contains(‘要判断的类名’)
const div = document.createElement('div');
div.className = 'foo';
// 初始状态:<div class="foo"></div>
// 使用 classList API 移除、添加类值
div.classList.remove("foo");
div.classList.add("anotherclass");
// <div class="anotherclass"></div>
// 如果 visible 类值已存在,则移除它,否则添加它
div.classList.toggle("visible");
// i<10为true才执行toggle类名visible
div.classList.toggle("visible", i < 10 );
console.log(div.classList.contains("foo"));
// 添加或移除多个类值
div.classList.add("foo", "bar", "baz");
div.classList.remove("foo", "bar", "baz");
// 将类值 "foo" 替换成 "bar"
div.classList.replace("foo", "bar");
2022年8月15日 DOM操作
获取元素偏移量(只读,会四舍五入取整)
offsetLeft 和 offsetTop
- 获取的是元左边的偏移量和上边的偏移量
- 分成两个情况来看
- 没有定位的情况下
- 获取元素边框外侧到页面内侧的距离(即页面顶部,不是窗口尺寸顶部)
- 有定位的情况下
- 获取元素边框外侧到定位父级边框内侧的距离(其实就是我们写的 left 和 top 值)
clientLeft 和 clientTop
- 获取元素边框的尺寸,获取的是元素左边框和上边框的尺寸
获取元素尺寸(会四舍五入取整 ,只读的)
offsetWidth 和 offsetHeight
- 获取元素
内容宽高 + padding宽高 + border宽高
的和
clientWidth 和 clientHeight
- 获取元素: 内容+padding
拓展:文档获取小数
console.log(box.getBoundingClientRect())
总结:元素的尺寸和偏移量,和鼠标没有关系
offsetWidth 获取元素的宽度 = content+padding+border
offsetHeight 获取元素的宽度 = content+padding+border
clientWidth 获取元素的宽度 = content+padding
clientHeight 获取元素的宽度 = content+padding
clientLeft 获取元素的左边框宽度
clientTop 获取元素的上边框宽度
offsetLeft 获取元素左侧偏移量
offsetTop 获取元素上侧偏移量
操作 DOM 节点
- 我们所说的操作无非就是 增删改查(CRUD)
- 创建一个节点(因为向页面中增加之前,我们需要先创建一个节点出来)
- 向页面中增加一个节点
- 删除页面中的某一个节点
- 修改页面中的某一个节点
- 获取页面中的某一个节点
- 克隆页面中的某一个节点
创建一个节点
-
createElement
:用于创建一个元素节点// 创建一个 div 元素节点 var oDiv = document.createElement('div') console.log(oDiv) // <div></div>
- 创建出来的就是一个可以使用的 div 元素
-
createTextNode
:用于创建一个文本节点// 创建一个文本节点 var oText = document.createTextNode('我是一个文本') console.log(oText) // "我是一个文本"
向页面中加入一个节点
-
appendChild
:是向一个元素节点的末尾追加一个节点 -
语法:
父节点.appendChild(要插入的子节点)
// 创建一个 div 元素节点 var oDiv = document.createElement('div') var oText = document.createTextNode('我是一个文本') // 向 div 中追加一个文本节点 oDiv.appendChild(oText) console.log(oDiv) // <div>我是一个文本</div>
-
insertBefore
:向某一个节点前插入一个节点 -
语法:
父节点.insertBefore(要插入的节点,插入在哪一个节点的前面)
-
语法:
父节点.insertBefore(要插入的节点,null)
等价于把要插入的节点插入到父元素的末尾 -
第二个参数不写,作用与append.child一样
<body> <div> <p>我是一个 p 标签</p> </div> <script> var oDiv = document.querySelector('div') var oP = oDiv.querySelector('p') // 创建一个元素节点 var oSpan = document.createElement('span') // 将这个元素节点添加到 div 下的 p 的前面 oDiv.insertBefore(oSpan, oP) console.log(oDiv) /* <div> <span></span> <p>我是一个 p 标签</p> </div> */ </script> </body>
删除页面中的某一个节点
-
removeChild
:移除某一节点下的某一个节点 -
语法:
父节点.removeChild(要移除的字节点)
<body> <div> <p>我是一个 p 标签</p> </div> <script> var oDiv = document.querySelector('div') var oP = oDiv.querySelector('p') // 移除 div 下面的 p 标签 oDiv.removeChild(oP) console.log(oDiv) // <div></div> </script> </body>
-
remove() 自杀,移除自己
-
语法: 要移除的元素节点.remove()
删除结点的方法总结:
1.父节点.removeChild(要移除的子节点) - 删除子节点
2.要移除的元素节点.remove() - 删除自己
3.元素.innerHTML = '' - 清空自己的所有内容
修改页面中的某一个节点
-
replaceChild
:将页面中的某一个节点替换掉 -
语法:
父节点.replaceChild(新节点,旧节点)
<body> <div> <p>我是一个 p 标签</p> </div> <script> var oDiv = document.querySelector('div') var oP = oDiv.querySelector('p') // 创建一个 span 节点 var oSpan = document.createElement('span') // 向 span 元素中加点文字 oSpan.innerHTML = '我是新创建的 span 标签' // 用创建的 span 标签替换原先 div 下的 p 标签 oDiv.replaceChild(oSpan, oP) console.log(oDiv) /* <div> <span>我是新创建的 span 标签</span> </div> */ </script> </body>
获取页面中的节点
- query等方法,getElement等方法
- 还可以通过节点关系获取节点
- dom.children: 获取dom的所有子元素节点集合
- dom.parentNode:获取dom的父元素节点
- dom.parentElement:获取dom的父元素节点
- 几个特殊节点获取快捷方式
- document.body(获取body标签)
- document.documentElement(获取html标签)
- document.title = xxx(可以自己设置)
- document.doctype(获取文档类型节点)
- document.head (获取head节点)
克隆节点
- 语法:var dupNode = node.cloneNode(deep);
- 语法: 要克隆的节点.cloneNode(是否克隆内容)
- 是否克隆内容,这个参数是布尔值
- 返回调用该方法的节点的一个副本(克隆好的节点)
- 参数:
- node:将要被克隆的节点
- dupNode:克隆生成的副本节点
- deep: 可选,是否采用深度克隆,如果为 true,则该节点的所有后代节点也都会被克隆,如果为 false,则只克隆该节点本身,不克隆节点(标签)内容
2022年8月16日 事件(上)
- 之前我们简单的了解过一些事件,比如
onclick
/onload
/onscroll
/ … - 今天开始,我们详细的学习一些 事件
什么是事件
-
一个事件由什么东西组成
- 触发谁的事件:事件源
- 触发什么事件:事件类型
- 触发以后做什么:事件处理函数
var oDiv = document.querySelector('div') oDiv.onclick = function () {} // 谁来触发事件 => oDiv => 这个事件的事件源就是 oDiv // 触发什么事件 => onclick => 这个事件类型就是 click // 触发之后做什么 => function () {} => 这个事件的处理函数
- 我们想要在点击 div 以后做什么事情,就把我们要做的事情写在事件处理函数里面
var oDiv = document.querySelector('div') oDiv.onclick = function () { console.log('你点击了 div') }
- 当我们点击 div 的时候,就会执行事件处理函数内部的代码
- 每点击一次,就会执行一次事件处理函数
事件的绑定方式
-
我们现在给一个注册事件都是使用
onxxx
的方式,这是dom0级的事件绑定方式 -
这个方式不是很好,只能给一个元素注册一个事件
-
一旦写了第二个事件,那么第一个就被覆盖了
oDiv.onclick = function () { console.log('我是第一个事件') } oDiv.onclick = function () { console.log('我是第二个事件') }
- 当你点击的时候,只会执行第二个,第一个就没有了
-
我们还有一种dom2级的事件监听的方式去给元素绑定事件
-
使用
addEventListener
的方式添加- 这个方法不兼容,在 IE 里面要使用
attachEvent
- 这个方法不兼容,在 IE 里面要使用
事件监听
-
addEventListener
: 非 IE 6 7 8 下使用 -
语法:
元素.addEventListener('事件类型', 事件处理函数, 冒泡还是捕获)
(一般第三个参数默认是)oDiv.addEventListener('click', function () { console.log('我是第一个事件') }, false) oDiv.addEventListener('click', function () { console.log('我是第二个事件') }, false)
- 当你点击 div 的时候,两个函数都会执行,并且会按照你注册的顺序执行
- 先打印
我是第一个事件
再打印我是第二个事件
- 注意: 事件类型的时候不要写 on,点击事件就是 click,不是 onclick
-
attachEvent
:IE6 7 8 下使用 -
语法:
元素.attachEvent('事件类型', 事件处理函数)
oDiv.attachEvent('onclick', function () { console.log('我是第一个事件') }) oDiv.attachEvent('onclick', function () { console.log('我是第二个事件') })
- 当你点击 div 的时候,两个函数都会执行,并且会按照你注册的顺序倒叙执行
- 先打印
我是第二个事件
再打印我是第一个事件
- 注意: 事件类型的时候要写 on,点击事件就行 onclick
两个方式的区别
- 注册事件的时候事件类型参数的书写
addEventListener
: 不用写 onattachEvent
: 要写 on
- 参数个数
addEventListener
: 一般是三个常用参数attachEvent
: 两个参数
- 执行顺序
addEventListener
: 顺序注册,顺序执行attachEvent
: 顺序注册,倒叙执行
- 适用浏览器
addEventListener
: 非 IE 7 8 的浏览器attachEvent
: IE 7 8 浏览器
事件解绑的方式
-
语法:dom2级:
元素.removeEventListener('事件类型', 事件处理函数, 冒泡还是捕获)
-
dom0级:同类事件会覆盖 语法:元素.onxxxx = null
-
默认是冒泡
-
用于接触指定元素的事件监听器
事件派发的方式
- 语法:
元素.dispatchEvent(事件对象)
- 向一个指定的事件目标派发一个事件,可以是原生事件也可以是自定义事件
// 创建一个click事件对象
var event = new Event('click');
box.addEventListener('click',function(){
alert(1)
})
box.onclick = function(){
alert(2)
}
setTimeout(function(){
// 派发点击事件
box.dispatchEvent(event)
},2000)
事件对象
-
什么是事件对象?
-
就是当你触发了一个事件以后,对该事件的一些描述信息
-
例如:
- 你触发一个点击事件的时候,你点在哪个位置了,坐标是多少
- 你触发一个键盘事件的时候,你按的是哪个按钮
- …
-
每一个事件都会有一个对应的对象来描述这些信息,我们就把这个对象叫做 事件对象
-
浏览器给了我们一个 黑盒子,叫做
window.event
,就是对事件信息的所有描述- 比如点击事件
- 你点在了
0,0
位置,那么你得到的这个事件对象里面对应的就会有这个点位的属性 - 你点在了
10, 10
位置,那么你得到的这个事件对象里面对应的就会有这个点位的属性 - …
oDiv.onclick = function () { console.log(window.event.X轴坐标点信息) console.log(window.event.Y轴坐标点信息) }
-
这个玩意很好用,但是一般来说,好用的东西就会有 兼容性问题
-
在
IE低版本
里面这个东西好用,但是在高版本IE
和Chrome
里面不好使了 -
我们就得用另一种方式来获取 事件对象
-
在每一个事件处理函数的行参位置,默认第一个就是 事件对象
oDiv.onclick = function (e) { // e 就是和 IE 的 window.event 一样的东西 console.log(e.X轴坐标点信息) console.log(e.Y轴坐标点信息) }
-
综上所述,我们以后在每一个事件里面,想获取事件对象的时候,都用兼容写法
oDiv.onclick = function (e) { e = e || window.event console.log(e.X轴坐标点信息) console.log(e.Y轴坐标点信息) }
点击事件的光标坐标点获取
- 刚才即然说了,可以获取到坐标点,那么接下来我们就学习一下怎么获取坐标点
- 我们的每一个点击事件的坐标点都不是一对,因为要有一个相对的坐标系
- 例如:
- 相对事件源(你点击的元素)
- 相对页面
- 相对浏览器窗口
- …
- 因为都不一样,所以我们获取的 事件对象 里面的属性也不一样
相对于你点击的元素来说
-
offsetX
和offsetY
-
是相对于你点击的元素的边框内侧开始计算(是点击的位置以被点击的元素为参考的坐标)
<style> * { margin: 0; padding: 0; } div { width: 300px; height: 300px; padding: 20px; border: 10px solid #333; margin: 20px 0 0 30px; } </style> <body> <div></div> <script> var oDiv = document.querySelector('div') // 注册点击事件 oDiv.onclick = function (e) { // 事件对象兼容写法 e = e || window.event console.log(e.offsetX) console.log(e.offsetY) } </script> </body>
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-opUc7JJX-1661353959192)(H:\Typora\data\TyporaPages\image-20220817200452477.png)]
相对于浏览器窗口你点击的坐标点
-
clientX
和clientY
-
是相对于浏览器窗口来计算的,不管你页面滚动到什么情况,都是根据窗口来计算坐标(是点击的位置以窗口可视区域为参考的坐标)
<style> * { margin: 0; padding: 0; } body { width: 2000px; height: 2000px; } div { width: 300px; height: 300px; padding: 20px; border: 10px solid #333; margin: 20px 0 0 30px; } </style> <body> <div></div> <script> var oDiv = document.querySelector('div') // 注册点击事件 oDiv.onclick = function (e) { // 事件对象兼容写法 e = e || window.event console.log(e.clientX) console.log(e.clientY) } </script> </body>
相对于页面你点击的坐标点
-
pageX
和pageY
-
是相对于整个页面的坐标点,不管有没有滚动,都是相对于页面拿到的坐标点(点击位置以页面为参考的坐标,页面即你写的页面)
<style> * { margin: 0; padding: 0; } body { width: 2000px; height: 2000px; } div { width: 300px; height: 300px; padding: 20px; border: 10px solid #333; margin: 20px 0 0 30px; } </style> <body> <div></div> <script> var oDiv = document.querySelector('div') // 注册点击事件 oDiv.onclick = function (e) { // 事件对象兼容写法 e = e || window.event console.log(e.pageX) console.log(e.pageY) } </script> </body>
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-L7IhMQ7A-1661353959193)(H:\Typora\data\TyporaPages\image-20220817200430487.png)]
- 根据页面左上角来说
- margin-left 是 30
- 左边框是 10
- 左右 padding 各是 20
- 内容区域是 300
- pageX : 300 + 20 + 20 + 10 + 30 = 380
- margin-top 是 20
- 上边框是 10
- 上下 padding 各是 20
- 内容区域是 300
- pageY : 300 + 20 + 20 + 10 + 20 = 270
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-O7wswdE9-1661353959194)(H:\Typora\data\TyporaPages\image-20220816193239668.png)]
点击按键信息(了解)
- 我们的鼠标一般都有两个按键,一个左键一个右键
- 我们的事件对象里面也有这个信息,确定你点击的是左键还是右键
- 我们使用
事件对象.button
来获取信息 0
为鼠标左键,1
为鼠标滚轮,2
为鼠标右键
常见的事件(了解)
- 我们在写页面的时候经常用到的一些事件
- 大致分为几类,浏览器事件 / 鼠标事件 / 键盘事件 / 表单事件 / 触摸事件
- 不需要都记住,但是大概要知道
浏览器事件
load
: 页面全部资源加载完毕scroll
: 浏览器滚动的时候触发resize
: 页面大小改变- …
鼠标事件
click
:点击事件dblclick
:双击事件contextmenu
: 右键单击事件mousedown
:鼠标左键按下事件mouseup
:鼠标左键抬起事件mousemove
:鼠标移动mouseover
:鼠标移入事件mouseout
:鼠标移出事件mouseenter
:鼠标移入事件mouseleave
:鼠标移出事件- …
键盘事件
keyup
: 键盘抬起事件keydown
: 键盘按下事件(不识别大小写,可以识别一些功能按键)keypress
: 键盘按下再抬起事件(识别大小写,不识别一些功能按键)- …
表单事件
change
: 表单内容改变事件input
: 表单内容输入事件submit
: 表单提交事件blur
:表单元素失去焦点事件focus
:表单元素获取焦点事件- …
触摸事件
touchstart
: 触摸开始事件touchend
: 触摸结束事件touchmove
: 触摸移动事件- …
键盘事件
-
刚才了解了一下鼠标事件,现在来聊聊键盘事件
-
我们在键盘事件里面最主要的就是要做两个事情
- 判断点击的是哪个按键
- 有没有组合按键,shift + a / ctrl + b / …
-
我们先要明确一个问题,就是是不是所有元素都可以绑定键盘事件
- 我们说事件有一个关键的东西是,该事件是由谁来触发的
- 一个 div 元素在页面上,我怎么能让一个键盘事件触发在 div 上
- 所以说,我们一般只给能在页面上选中的元素(表单元素) 和
document
来绑定键盘事件
document.onkeyup = function () { // code.. } oInput.onkeyup = function () { // code.. }
确定按键
-
我们的键盘上每一个按键都有一个自己独立的编码
-
我们就是靠这个编码来确定我们按下的是哪个按键的
-
我们通过
事件对象.keyCode
或者事件对象.which
来获取 -
为什么要有两个呢,是因为 FireFox2.0 不支持
keycode
所以要用which
document.keyup = function (e) { // 事件对象的兼容写法 e = e || window.event // 获取键盘码的兼容写法 var keyCode = e.keyCode || e.which console.log(keyCode) }
常见的键盘码(了解)
- 8: 删除键(delete)
- 9: 制表符(tab)
- 13: 回车键(ebter)
- 16: 上档键(shift)
- 17: ctrl 键
- 18: alt 键
- 27: 取消键(esc)
- 32: 空格键(space)
- …
组合按键
-
组合案件最主要的就是
alt
/shift
/ctrl
/win
三个按键 -
在我点击某一个按键的时候判断一下这三个键有没有按下,有就是组合了,没有就是没有组合
-
事件对象里面也为我们提供了三个属性
altKey
:alt 键按下得到 true,否则得到 falseshiftKey
:shift 键按下得到 true,否则得到 falsectrlKey
:ctrl 键按下得到 true,否则得到 falsemetaKey
:win 键按下得到 true,否则得到 false
-
我们就可以通过这三个属性来判断是否按下了
document.onkeyup = function (e) { e = e || window.event keyCode = e.keyCode || e.which if (e.altKey && keyCode === 65) { console.log('你同时按下了 alt 和 a') } }
2022年8月17日 事件(下)
拓展:事件处理函数是异步的(代码有同步和异步,异步: 定时器的回调函数,事件处理函数;同步: 非异步就是同步)异步的意思就是,先取号排队,再执行。
- 今天来聊一聊浏览器事件的执行机制
- 什么是浏览器事件的执行机制呢?
- 思考一个问题?
- 当一个大盒子嵌套一个小盒子的时候,并且两个盒子都有点击事件
- 你点击里面的小盒子,外面的大盒子上的点击事件要不要执行
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gnukEfRz-1661353959194)(H:\Typora\data\TyporaPages\image-20220817200603263.png)]
事件的传播
- 就像上面那个图片一样,我们点击在红色盒子身上的同时,也是点击在了粉色盒子上
- 这个是既定事实,那么两个盒子的点击事件都会触发
- 这个就叫做事件的传播
- 当元素触发一个事件的时候,其父元素也会触发相同的事件,父元素的父元素也会触发相同的事件
- 就像上面的图片一样
- 点击在红色盒子上的时候,会触发红色盒子的点击事件
- 也是点击在了粉色的盒子上,也会触发粉色盒子的点击事件
- 也是点击在了 body 上,也会触发 body 的点击事件
- 也是点击在了 html 上,也会触发 html 的点击事件
- 也是点击在了 document 上,也会触发 document 的点击事件
- 也是点击在了 window 上,也会触发 window 的点击事件
- 也就是说,页面上任何一个元素触发事件,都会一层一层最终导致 window 的相同事件触发,前提是各层级元素得有注册相同的事件,不然不会触发
- 在事件传播的过程中,有一些注意的点:
- 只会传播同类事件
- 只会从点击元素开始按照 html 的结构逐层向上元素的事件会被触发
- 内部元素不管有没有该事件,只要上层元素有该事件,那么上层元素的事件就会被触发
- 到现在,我们已经了解了事件的传播,我们再来思考一个问题
- 事件确实会从自己开始,到 window 的所有相同事件都会触发
- 是因为我们点在自己身上,也确实逐层的点在了直至 window 的每一个元素身上
- 但是到底是先点在自己身上,还是先点在了 window 身上呢
- 先点在自己身上,就是先执行自己的事件处理函数,逐层向上最后执行 window 的事件处理函数
- 反之,则是先执行 window 的事件处理函数,逐层向下最后执行自己身上的事件处理函数
冒泡、捕获、目标
- 我们刚才聊过了,每一个事件,都是有可能从自己到 window ,有可能要执行多个同类型事件
- 那么这个执行的顺序就有一些说法了
目标
- 你是点击在哪个元素身上了,那么这个事件的 目标 就是什么
- 语法: 事件对象.target 事件对象.srcElement
冒泡
- 就是从事件 目标 的事件处理函数开始,依次向外(上),直到 window 的事件处理函数触发
- 也就是从下向上的执行事件处理函数
- 语法: 元素.addEventListener(‘事件类型’,事件处理函数,是否捕获默认否)
- 第三个参数默认是false(即不写也为冒泡)
- 就是执行事件处理函数在冒泡的时候发生
捕获
- 就是从 window 的事件处理函数开始,依次向内(下),直到事件 目标 的事件处理函数执行
- 也就是从上向下的执行事件处理函数
- 语法: 元素.addEventListener(‘事件类型’,事件处理函数,是否捕获默认否)
- 第三个参数如果是true
- 就是执行事件处理函数在捕获的时候发生
冒泡和捕获的区别
- 就是在事件的传播中,多个同类型事件处理函数的执行顺序不同
- 要是同时写的话,先捕获后冒泡(因为默认为冒泡,所以需要捕获的话,所有元素都得写true)
阻止事件传播
- 到我为止阻止传播,我可以被传播到
- (标准浏览器)语法:事件对象.stopPropagation()
- (ie678)语法:事件对象.cancelBubble = true;
事件委托
- 原理:事件冒泡
- 就是把我要做的事情委托给别人来做
- 因为我们的冒泡机制,点击子元素的时候,也会同步触发父元素的相同事件
- 所以我们就可以把子元素的事件委托给父元素来做
好处:可以减少事件绑定的次数,可以给动态生成的元素绑定事件
书写事件委托:元素的事件只能委托给html结构上的父级或者父级的父级的同样的事件上,父级或者父级的父级必须是存在的
事件触发
-
点击子元素的时候,不管子元素有没有点击事件,只要父元素有点击事件,那么就可以触发父元素的点击事件
<body> <ul> <li>1</li> <li>2</li> <li>3</li> </ul> <script> var oUl = docuemnt.querySelector('ul') oUl.addEventListener('click', function (e) { console.log('我是 ul 的点击事件,我被触发了') }) </script> </body>
- 像上面一段代码,当你点击 ul 的时候肯定会触发
- 但是当你点击 li 的时候,其实也会触发
target
-
target 这个属性是事件对象里面的属性,表示你点击的目标
-
当你触发点击事件的时候,你点击在哪个元素上,target 就是哪个元素
-
这个 target 也不兼容,在 IE 下要使用 srcElement
<body> <ul> <li>1</li> <li>2</li> <li>3</li> </ul> <script> var oUl = docuemnt.querySelector('ul') oUl.addEventListener('click', function (e) { e = e || window.event var target = e.target || e.srcElement console.log(target) }) </script> </body>
- 上面的代码,当你点击 ul 的时候,target 就是 ul
- 当你点击在 li 上面的时候,target 就是 li
委托
-
这个时候,当我们点击 li 的时候,也可以触发 ul 的点事件
-
并且在事件内不,我们也可以拿到你点击的到底是 ul 还是 li
-
这个时候,我们就可以把 li 的事件委托给 ul 来做
<body> <ul> <li>1</li> <li>2</li> <li>3</li> </ul> <script> var oUl = docuemnt.querySelector('ul') oUl.addEventListener('click', function (e) { e = e || window.event var target = e.target || e.srcElement // 判断你点击的是 li if (target.nodeName.toUpperCase === 'LI') { // 确定点击的是 li // 因为当你点击在 ul 上面的时候,nodeName 应该是 'UL' // 去做点击 li 的时候该做的事情了 console.log('我是 li,我被点击了') } }) </script> </body>
- 上面的代码,我们就可以把 li 要做的事情委托给 ul 来做
总结
- 为什么要用事件委托
- 我页面上本身没有 li
- 我通过代码添加了一些 li
- 添加进来的 li 是没有点击事件的
- 我每次动态的操作完 li 以后都要从新给 li 绑定一次点击事件
- 比较麻烦
- 这个时候只要委托给 ul 就可以了
- 因为新加进来的 li 也是 ul 的子元素,点击的时候也可以触发 ul 的点击事件
- 事件委托的书写
- 元素的事件只能委托给结构父级或者再结构父级的同样的事件上
- li 的点击事件,就不能委托给 ul 的鼠标移入事件
- li 的点击事件,只能委托给 ul 或者在高父级的点击事件上
默认行为
- 默认行为,就是不用我们注册,它自己就存在的事情
- 比如我们点击鼠标右键的时候,会自动弹出一个菜单
- 比如我们点击 a 标签的时候,我们不需要注册点击事件,他自己就会跳转页面
- 比如: 我们点击鼠标右键的时候,会自动弹出一个菜单
- 我们点击submit按钮,触发表单的跳转行为
- 这些不需要我们注册就能实现的事情,我们叫做 默认事件
阻止默认行为
-
有的时候,我们不希望浏览器执行默认事件
- 比如我给 a 标签绑定了一个点击事件,我点击你的时候希望你能告诉我你的地址是什么,而不是直接跳转链接
- 那么我们就要把 a 标签原先的默认事件阻止,不让他执行默认事件
-
我们有两个方法来阻止默认事件
e.preventDefault()
: 非 IE 使用(标准浏览器)e.returnValue = false
:IE 使用
-
我们阻止默认事件的时候也要写一个兼容的写法
<a href="https://www.baidu.com">点击我试试</a> <script> var oA = document.querySelector('a') a.addEventListener('click', function (e) { e = e || window.event console.log(this.href) e.preventDefault ? e.preventDefault() : e.returnValue = false }) </script>
- 这样写完以后,你点击 a 标签的时候,就不会跳转链接了
- 而是会在控制台打印出 a 标签的 href 属性的值
- dom0可以设置return false阻止默认行为
封装事件库
- 移动端事件
- 事件对象.touchstart: 手指触摸屏幕时触发
- 事件对象.touchmove: 手指在屏幕上移动时触发
- 事件对象.touchend: 手指离开屏幕的时候触发
- 移动端tap事件(自己封装)
- 轻触事件和click事件的区别(click有延迟)
- 移动端左滑和右滑事件(自己封装)
鼠标的移入和移出
-
第一套: mouseover mouseout (不建议使用)
-
和普通事件一样,会冒泡
-
当一个元素移入,另一个元素移出事件触发
eg:
<style> .outer{ width: 400px; height: 400px; background-color: brown; margin:50px auto; position: relative; } .middle{ width: 300px; height: 300px; position: absolute; top:50px; left:50px; background-color: pink; } </style> </head> <body> <div class="outer"> <div class="middle"> </div> </div>
var middle = document.querySelector('.middle') var outer = document.querySelector('.outer') middle.onmouseover = function(){ console.log('middle 鼠标移入') } middle.onmouseout = function(){ console.log('middle 鼠标移出') } outer.onmouseover = function(e){ console.log(e) console.log('outer 鼠标移入') } outer.onmouseout = function(){ console.log('outer 鼠标移出') }
-
-
第二套: mouseenter mouseleave (推荐使用)
-
和普通事件不一样,天生不会冒泡
var middle = document.querySelector('.middle') var outer = document.querySelector('.outer') middle.addEventListener('mouseenter',function(){ console.log('middle 移入') }) middle.addEventListener('mouseleave',function(){ console.log('middle 移出') }) outer.addEventListener('mouseenter',function(e){ console.log(e) console.log('outer 移入') }) outer.addEventListener('mouseleave',function(){ console.log('outer 移出') })
-
2022年8月18日 ES5和ES6
JSON 方法
-
json
是一种文件的格式,在各种语言中都识别 -
json
是一种特殊的字符串格式,本质是一个字符串 -
需要传递数据的时候可以把数据转成json字符串格式再进行传递
-
凡是字符串使用双引号,对象和数组里面的值可以是数值类型,字符串,对象,布尔,null,undefined,数组
var jsonObj = '{ "name": "Jack", "age": 18, "gender": "男" }' var jsonArr = '[{ "name": "Jack", "age": 18, "gender": "男" }, { "name": "Jack", "age": 18, "gender": "男" }, { "name": "Jack", "age": 18, "gender": "男" }]'
-
就是对象内部的
key
和value
都用双引号包裹的字符串(必须是双引号)
JSON的两个方法
- 我们有两个方法可以使用
json.parse
是将json字符串转成js的对象或者数组 前端接收后端给我们的数据json.stringify
是将 js 的对象或者数组转换成为 json 格式的字符串 前端要把数据给后端
JSON.parse
-
JSON.parse
是将 json 格式的字符串转换为 js 的对象或者数组 -
语法: JSON.parse(json字符串)
-
返回值: js对象或者数组
var jsonObj = '{ "name": "Jack", "age": 18, "gender": "男" }' var jsonArr = '[{ "name": "Jack", "age": 18, "gender": "男" }, { "name": "Jack", "age": 18, "gender": "男" }, { "name": "Jack", "age": 18, "gender": "男" }]' var obj = JSON.parse(jsonStr) var arr = JSON.parse(jsonArr) console.log(obj) console.log(arr)
obj
就是我们 js 的对象arr
就是我们 js 的数组
JSON.stringify
-
JSON.parse
是要将 js 的对象或者数组转换成为 json 格式的字符串 -
语法: JSON.stringify(js对象或者数组)
-
返回值: json格式的字符串
var obj = { name: 'Jack', age: 18, gender: '男' } var arr = [ { name: 'Jack', age: 18, gender: '男' }, { name: 'Jack', age: 18, gender: '男' }, { name: 'Jack', age: 18, gender: '男' } ] var jsonObj = JSON.stringify(obj) var jsonArr = JSON.stringify(arr) console.log(jsonObj) console.log(jsonArr)
jsonObj
就是 json 格式的对象字符串jsonArr
就是 json 格式的数组字符串
总结:
- 在和其他语言传递数据的时候
- 把我们js的数据格式转json字符串 - JSON.stringify()
- 在其他语言把数据给我的时候,转成js的数据格式 - JSON.parse()
this 关键字
-
每一个函数内部都有一个关键字是
this
-
可以让我们直接使用的
-
重点: 函数内部的 this 只和函数的调用方式有关系,和函数的定义方式没有关系
-
函数内部的 this 指向谁,取决于函数的调用方式
-
全局定义的函数直接调用,
this => window
function fn() { console.log(this) } fn() // 此时 this 指向 window
-
对象内部的方法调用,
this => 调用者
var obj = { fn: function () { console.log(this) } } obj.fn() // 此时 this 指向 obj
-
定时器的处理函数,
this => window
setTimeout(function () { console.log(this) }, 0) // 此时定时器处理函数里面的 this 指向 window
-
事件处理函数,
this => 事件源
div.onclick = function () { console.log(this) } // 当你点击 div 的时候,this 指向 div
-
自调用函数,
this => window
(function () { console.log(this) })() // 此时 this 指向 window
-
改变this的指向:call 和 apply 和 bind
- 刚才我们说过的都是函数的基本调用方式里面的 this 指向
- 我们还有三个可以忽略函数本身的 this 指向转而指向别的地方
- 这三个方法就是 call / apply / bind
- 是强行改变 this 指向的方法,this在函数调用时确定
call
-
call
方法是附加在函数调用后面使用,可以忽略函数本身的 this 指向 -
语法:
函数名.call(指定this指向,要给函数传递的参数1,要给函数传递的参数2, ...)
-
如果指定this的指向为null就是不改变this的指向
var obj = { name: 'Jack' } function fn(a, b) { console.log(this) console.log(a) console.log(b) } fn(1, 2) fn.call(obj, 1, 2)
fn()
的时候,函数内部的 this 指向 windowfn.call(obj, 1, 2)
的时候,函数内部的 this 就指向了 obj 这个对象- 使用 call 方法的时候
- 会立即执行函数
- 第一个参数是你要改变的函数内部的 this 指向
- 第二个参数开始,依次是向函数传递参数
apply
-
apply
方法是附加在函数调用后面使用,可以忽略函数本身的 this 指向 -
语法:
函数名.apply(要改变的 this 指向,[要给函数传递的参数1, 要给函数传递的参数2, ...])
-
如果指定this的指向为null就是不改变this的指向
var obj = { name: 'Jack' } function fn(a, b) { console.log(this) console.log(a) console.log(b) } fn(1, 2) fn.call(obj, [1, 2])
fn()
的时候,函数内部的 this 指向 windowfn.apply(obj, [1, 2])
的时候,函数内部的 this 就指向了 obj 这个对象- 使用 apply 方法的时候
- 会立即执行函数
- 第一个参数是你要改变的函数内部的 this 指向
- 第二个参数是一个 数组,数组里面的每一项依次是向函数传递的参数
bind
-
bind
方法是附加在函数调用后面使用,可以忽略函数本身的 this 指向 -
和 call / apply 有一些不一样,就是不会立即执行函数,而是返回一个已经改变了 this 指向的函数
-
语法:
var 新函数 = 旧函数名.bind(要改变的 this 指向); newFn(传递参数)
var obj = { name: 'Jack' } function fn(a, b) { console.log(this) console.log(a) console.log(b) } fn(1, 2) var newFn = fn.bind(obj) newFn(1, 2)
- bind 调用的时候,不会执行 fn 这个函数,而是返回一个新的函数
- 这个新的函数就是一个改变了 this 指向以后的 fn 函数
fn(1, 2)
的时候 this 指向 windownewFn(1, 2)
的时候执行的是一个和 fn 一摸一样的函数,只不过里面的 this 指向改成了 obj- 不管怎么调用,this都是你指定的
案例:求数组最大值
var arr = [213,2130,3,32,4,32,4,32,213,213,3,4,32,43];
// 回忆求最大值: Math.max(213,213,3,32,4,32,4,32,213,213,3,4,32,43)
console.log(Math.max.apply(null,arr))
ES6新增的内容
- 我们所说的 ES6 其实就是在 js 语法的发展过程中的一个版本而已
- 比如我们使用的微信
- 最早的版本是没有支付功能的
- 随着时间的流逝,后来出现了一个版本,这个版本里面有支付功能了
- ECMAScript 就是 js 的语法
- 以前的版本没有某些功能
- 在 ES6 这个版本的时候增加了一些功能
let 和 const 关键字
-
我们以前都是使用
var
关键字来声明变量的 -
在 ES6 的时候,多了两个关键字
let
和const
,也是用来声明变量的 -
只不过和 var 有一些区别
let
和const
不允许重复声明变量
// 使用 var 的时候重复声明变量是没问题的,只不过就是后面会把前面覆盖掉 var num = 100 var num = 200
// 使用 let 重复声明变量的时候就会报错了 let num = 100 let num = 200 // 这里就会报错了
// 使用 const 重复声明变量的时候就会报错 const num = 100 const num = 200 // 这里就会报错了
-
let
和const
声明的变量不会在预解析的时候解析(也就是没有变量提升)// 因为预解析(变量提升)的原因,在前面是有这个变量的,只不过没有赋值 console.log(num) // undefined var num = 100
// 因为 let 不会进行预解析(变量提升),所以直接报错了 console.log(num) // undefined let num = 100
// 因为 const 不会进行预解析(变量提升),所以直接报错了 console.log(num) // undefined const num = 100
-
let
和const
声明的变量会被所有代码块限制作用范围// var 声明的变量只有函数能限制其作用域,其他的不能限制 if (true) { var num = 100 } console.log(num) // 100
// let 声明的变量,除了函数可以限制,所有的代码块都可以限制其作用域(if/while/for/...) if (true) { let num = 100 console.log(num) // 100 } console.log(num) // 报错
// const 声明的变量,除了函数可以限制,所有的代码块都可以限制其作用域(if/while/for/...) if (true) { const num = 100 console.log(num) // 100 } console.log(num) // 报错
-
let
和const
的区别-
let
声明的变量的值可以改变,const
声明的变量的值不可以改变(基于上面的情况const定义的变量不能重复赋值,所以也把const定义的变量叫做常量)let num = 100 num = 200 console.log(num) // 200
const num = 100 num = 200 // 这里就会报错了,因为 const 声明的变量值不可以改变(我们也叫做常量)
-
let
声明的时候可以不赋值,const
声明的时候必须赋值let num num = 100 console.log(num) // 100
const num // 这里就会报错了,因为 const 声明的时候必须赋值
-
箭头函数
-
箭头函数是 ES6 里面一个简写函数的语法方式
-
重点: 箭头函数只能简写函数表达式(赋值式函数),不能简写声明式函数
function fn() {} // 不能简写 const fun = function () {} // 可以简写 const obj = { fn: function () {} // 可以简写 }
-
语法:
(函数的形参1,形参2,...) => { 函数体内要执行的代码 }
-
原来函数怎么调用,现在也怎么调用,就是:函数名()
const fn = function (a, b) { console.log(a) console.log(b) } // 可以使用箭头函数写成 const fun = (a, b) => { console.log(a) console.log(b) }
const obj = { fn: function (a, b) { console.log(a) console.log(b) } } // 可以使用箭头函数写成 const obj2 = { fn: (a, b) => { console.log(a) console.log(b) } }
箭头函数的特殊性
-
只能简写赋值式函数
-
箭头函数内部没有 this,箭头函数的 this 是上下文的 this
// 在箭头函数定义的位置往上数,这一行是可以打印出 this 的 // 因为这里的 this 是 window // 所以箭头函数内部的 this 就是 window const obj = { fn: function () { console.log(this) }, // 这个位置是箭头函数的上一行,但是不能打印出 this fun: () => { // 箭头函数内部的 this 是书写箭头函数的上一行一个可以打印出 this 的位置 console.log(this) } } obj.fn() obj.fun()
- 按照我们之前的 this 指向来判断,两个都应该指向 obj
- 但是 fun 因为是箭头函数,所以 this 不指向 obj,而是指向 fun 的外层,就是 window
-
箭头函数内部没有
arguments
这个参数集合const obj = { fn: function () { console.log(arguments) }, fun: () => { console.log(arguments) } } obj.fn(1, 2, 3) // 会打印一个伪数组 [1, 2, 3] obj.fun(1, 2, 3) // 会直接报错
-
函数的行参只有一个的时候可以不写
()
其余情况必须写const obj = { fn: () => { console.log('没有参数,必须写小括号') }, fn2: a => { console.log('一个行参,可以不写小括号') }, fn3: (a, b) => { console.log('两个或两个以上参数,必须写小括号') } }
-
函数体只有一行代码的时候,可以不写
{}
,并且会自动 returnconst obj = { fn: a => { return a + 10 }, fun: a => a + 10 } console.log(fn(10)) // 20 console.log(fun(10)) // 20
函数传递参数的时候的默认值
-
我们在定义函数的时候,有的时候需要一个默认值出现
-
就是当我不传递参数的时候,使用默认值,传递参数了就使用传递的参数
-
如果形参有多个,其中不是所有的都有默认值,有默认值的要放在形参的最后面
-
语法:
== function fn(形参1=默认值,形参2=默认值){}
== (形参1=默认值,形参2=默认值)=>{}
-
注意
== 如果箭头函数只有一个形参,但是设置了默认值,就不能省略()
function fn(a) { a = a || 10 console.log(a) } fn() // 不传递参数的时候,函数内部的 a 就是 10 fn(20) // 传递了参数 20 的时候,函数内部的 a 就是 20
- 在 ES6 中我们可以直接把默认值写在函数的行参位置
function fn(a = 10) { console.log(a) } fn() // 不传递参数的时候,函数内部的 a 就是 10 fn(20) // 传递了参数 20 的时候,函数内部的 a 就是 20
- 这个默认值的方式箭头函数也可以使用
const fn = (a = 10) => { console.log(a) } fn() // 不传递参数的时候,函数内部的 a 就是 10 fn(20) // 传递了参数 20 的时候,函数内部的 a 就是 20
- 注意: 箭头函数如果你需要使用默认值的话,那么一个参数的时候也需要写 ()
解构赋值
- 解构赋值,就是快速的从对象或者数组中取出成员的一个语法方式
解构对象
-
快速的从对象中获取成员
-
{} 是专门用来解构对象使用的
// ES5 的方法向得到对象中的成员 const obj = { name: 'Jack', age: 18, gender: '男' } let name = obj.name let age = obj.age let gender = obj.gender
// 解构赋值的方式从对象中获取成员 const obj = { name: 'Jack', age: 18, gender: '男' } // 前面的 {} 表示我要从 obj 这个对象中获取成员了 // name age gender 都得是 obj 中有的成员 // obj 必须是一个对象 let { name, age, gender } = obj
解构数组
-
快速的从数组中获取成员
-
[] 是专门用来解构数组使用的
// ES5 的方式从数组中获取成员 const arr = ['Jack', 'Rose', 'Tom'] let a = arr[0] let b = arr[1] let c = arr[2]
// 使用解构赋值的方式从数组中获取成员 const arr = ['Jack', 'Rose', 'Tom'] // 前面的 [] 表示要从 arr 这个数组中获取成员了 // a b c 分别对应这数组中的索引 0 1 2 // arr 必须是一个数组 let [a, b, c] = arr
注意
{}
是专门解构对象使用的[]
是专门解构数组使用的- 不能混用
模版字符串
-
ES5 中我们表示字符串的时候使用
''
或者""
-
在 ES6 中,我们还有一个东西可以表示字符串,就是 ``(反引号)
let str = `hello world` console.log(typeof str) // string
-
和单引号好友双引号的区别
-
反引号可以换行书写
// 这个单引号或者双引号不能换行,换行就会报错了 let str = 'hello world' // 下面这个就报错了 let str2 = 'hello world'
let str = ` hello world ` console.log(str) // 是可以使用的
-
反引号可以直接在字符串里面拼接变量
// ES5 需要字符串拼接变量的时候 let num = 100 let str = 'hello' + num + 'world' + num console.log(str) // hello100world100 // 直接写在字符串里面不好使 let str2 = 'hellonumworldnum' console.log(str2) // hellonumworldnum
// 模版字符串拼接变量 let num = 100 let str = `hello${num}world${num}` console.log(str) // hello100world100
- 在字符串里面的
${}
就是用来书写变量的位置
- 在字符串里面的
-
展开运算符
-
ES6 里面号新添加了一个运算符
...
,叫做展开运算符 1.可以合并数组
2.可以合并对象
3.可以传递函数实参
4.可以展示数组
-
作用是把数组展开
let arr = [1, 2, 3, 4, 5] console.log(...arr) // 1 2 3 4 5
-
合并数组的时候可以使用
let arr = [1, 2, 3, 4] let arr2 = [...arr, 5] console.log(arr2)
-
也可以合并对象使用
let obj = { name: 'Jack', age: 18 } let obj2 = { ...obj, gender: '男' } console.log(obj2)
-
在函数传递参数的时候也可以使用
let arr = [1, 2, 3] function fn(a, b, c) { console.log(a) console.log(b) console.log(c) } fn(...arr) // 等价于 fn(1, 2, 3)
Set
- Set对象是值的集合,类似数组
- Set中的元素只会出现一次,即 Set 中的元素是唯一的
- 常用方法
var mySet = new Set(['a','b']);
mySet.size;//返回set中元素的个数
mySet.add('foo')
// 在Set对象尾部添加一个元素
mySet.clear()
// 移除Set对象内的所有元素。
mySet.delete('foo')
// 移除Set中与这个值相等的元素,返回一个布尔值
mySet.has(value)
// 返回一个布尔值,表示该值在Set中存在与否。
案例: 数组去重
var arr = [23,421,4,32,12,3,213,23,6,243,32,432,4];
// 定义一个set类型,参数就是这个arr
var newSet = new Set(arr);
var result = [...newSet];
console.log(result)
Map
- Map对象保存键值对,只不过,这些键值对是有序的,而且键名可以是任意类型
- 任何值都可以作为一个键或一个值
- 常用方法
- size:返回Map对象的键/值对的数量。
- clear():移除Map对象的所有键/值对 。
- delete(key):移除键值对。
- get(key):返回键对应的值,如果不存在,则返回undefined。
- has(key):返回一个布尔值,表示Map实例是否包含键对应的值。
- set(key, value):设置Map对象中键的值。返回该Map对象。
let myMap = new Map( [["key1", "value1"], ["key2", "value2"]]);
let keyObj = {};
let keyFunc = function() {};
let keyString = 'a string';
// 添加键
myMap.set(keyString, "和键'a string'关联的值");
myMap.set(keyObj, "和键keyObj关联的值");
myMap.set(keyFunc, "和键keyFunc关联的值");
myMap.size; // 3
// 读取值
myMap.get(keyString); // "和键'a string'关联的值"
myMap.get(keyObj); // "和键keyObj关联的值"
myMap.get(keyFunc); // "和键keyFunc关联的值"
myMap.get('a string'); // "和键'a string'关联的值"
// 因为keyString === 'a string'
myMap.get({}); // undefined, 因为keyObj !== {}
myMap.get(function() {}); // undefined, 因为keyFunc !== function () {}
for … of
- 可以遍历array,map,set等数据类型
- 不可以遍历object
const array1 = ['a', 'b', 'c'];
for (const element of array1) {
// array1里面有几个元素,{}里面的代码就执行几次
// element就是每次循环的值
// array1就是要遍历的数据
console.log(element);
}
// a
// b
// c
模块化语法 import / export
<body>
<!-- 以前当我们要引入别人写好的js文件的使用,使用script标签引入 -->
<!-- <script src="./public.js"></script> -->
<script>
// 引入以后,被引入的js文件里面定义的变量,方法在当前文件就都可以使用了
// console.log(rand)
// console.log(getColor)
// 这种引入方法不好
// 如果public.js不是我写的,里面定义的变量可能和我自己的代码的变量有冲突
// var rand = 123;
// console.log(rand)
</script>
<!-- 现在我们使用模块化语法导入js模块(js文件) -->
<!-- 如果要使用模块化语法,设置script的type='module' -->
<!-- 如果要打开这个html文件要以http形式打开 -->
<script type="module">
// 1 导入外部的js文件,导入以后这个js文件会执行一次
// 里面的变量不能使用
// import "./public.js"
// 2 导入外部的js文件,导入以后这个js文件会执行一次
// 然后把js文件中export default的变量赋值给aaa
// import aaa from "./public.js"
// console.log(aaa)
// 3 导入外部的js文件,导入以后这个js文件会执行一次
// 然后把js文件中export的变量赋值给同名的变量
// 如果要重命名要用as
import {rand as aaa} from "./public.js"
console.log(aaa)
</script>
</body>
public.js
// 书写函数rand,求m和n之间的随机整数
export function rand(m,n){
var min = Math.min(m,n)
var max = Math.max(m,n)
min = Math.ceil(min)
max = Math.floor(max)
return min+Math.floor(Math.random()*(max-min+1));
}
// 编写一个函数,可以返回一个随机颜色#21321a
export function getColor(){
var color = '#'
for(var i=0;i<6;i++){
var num = Math.floor(Math.random()*16);
var str = num.toString(16)
color += str; // color = color+str;
}
return color;
}
console.log('public.js执行了')
// 如果有多个变量要给引入的文件使用,在要导出的变量前面加export
2022年8月19日 正则表达式
-
正则表达式,又名 “规则表达式”,简称正则
-
由我们自己来书写 “规则”,专门用来检测 字符串 是否符合 “规则” 使用的
-
我们使用一些特殊的字符或者符号定义一个 “规则公式”,然后用我们定义好的 “规则公式” 去检测字符串是不是合格
var reg = /\d+/ // 字符串中是否包含一个以上的数字 var str1 = '123' var str2 = 'abc' console.log(reg.test(str1)) // true console.log(reg.test(str2)) // false
- 上面的变量
reg
就是定制好的规则 - 检测
str1
这个字符串的时候,符合规则 - 检测
str2
这个字符串的时候,不符合规则
- 上面的变量
创建一个正则表达式
- 想制定 “规则”,必须要按照人家要求的方式来制定
- 把一些字母和符号写在
//
中间的东西,叫做正则表达式,比如/abcdefg/
- 创建正则表达式有两个方式 字面量 和 构造函数创建
字面量创建
// 下面就是字面量创建一个正则表达式,表示要求字符串里面要有abcdefg
var reg = /abcdefg/
- 这个正则表达式就可以去检测字符串了
- 不用打引号
构造函数创建
// 下面就是构造函数创建一个正则表达式,表示要求字符串里面要有abcdefg
var reg = new RegExp('abcdefg')
console.log(reg) // /abcdefg/
- 使用构造函数方式创建的和字面量创建的,得到的结果一样
- 需要打引号(eg:假设想要输出\d,\会转译,所以得再加一个\)
正则表达式里面的符号
- 知道了怎么创建一个正则表达式以后,我们就来详细的说一下正则表达式里面涉及到的一些符号了
元字符
-
.
: 匹配非换行的任意字符 -
\
: 转译符号,把有意义的 符号 转换成没有意义的 字符,把没有意义的 字符 转换成有意义的 符号 -
\s
: 匹配空白字符(空格/制表符/…) -
\S
: 匹配非空白字符 -
\d
: 匹配数字 -
\D
: 匹配非数字 -
\w
: 匹配数字字母下划线 -
\W
: 匹配非数字字母下划线 -
有了元字符我们就可以简单的制定一些规则了
var reg = /\s/ var str = 'a b' var str2 = 'ab' console.log(reg.test(str)) // true console.log(reg.test(str2)) // false
var reg = /\d/ var str = 'abc1' var str2 = 'abc' console.log(reg.test(str)) // true console.log(reg.test(str2)) // false
var reg = /\w/ var str = 'a1' var str2 = '#@$' console.log(reg.test(str)) // true console.log(reg.test(str2)) // false
限定符(只要有就行)
-
*
: 前一个内容重复至少 0 次,也就是可以出现 0 ~ 正无穷 次 -
+
: 前一个内容重复至少 1 次,也就是可以出现 1 ~ 正无穷 次 -
?
: 前一个内容重复 0 或者 1 次,也就是可以出现 0 ~ 1 次 -
{n}
: 前一个内容重复 n 次,也就是必须出现 n 次 -
{n,}
: 前一个内容至少出现 n 次,也就是可以出现 n ~ 正无穷 次 -
{n,m}
: 前一个内容至少出现 n 次至多出现 m 次,也就是出现 n ~ m 次 -
限定符是配合元字符使用的
// 下面正则表示验证数字出现 0 ~ 正无穷次都可以 var reg = /\d*/ var str = 'abc' var str2 = 'abc1' var str3 = 'abc123' console.log(reg.test(str)) // true console.log(reg.test(str2)) // true console.log(reg.test(str3)) // true
// 下面正则表示验证数字出现 1 ~ 正无穷次都可以 var reg = /\d+/ var str = 'abc' var str2 = 'abc1' var str3 = 'abc123' console.log(reg.test(str)) // false console.log(reg.test(str2)) // true console.log(reg.test(str3)) // true
// 下面正则表示验证数字出现 0 ~ 1 次都可以 var reg = /\d?/ var str = 'abc' var str2 = 'abc1' console.log(reg.test(str)) // true console.log(reg.test(str2)) // true
// 下面正则表示验证数字必须出现 3 次 var reg = /\d{3}/ var str = 'abc' var str2 = 'abc1' var str3 = 'abc123' console.log(reg.test(str)) // false console.log(reg.test(str2)) // false console.log(reg.test(str3)) // true
// 下面正则表示验证数字出现 3 ~ 正无穷次 var reg = /\d{3,}/ var str = 'abc' var str2 = 'abc1' var str3 = 'abc123' var str4 = 'abcd1234567' console.log(reg.test(str)) // false console.log(reg.test(str2)) // false console.log(reg.test(str3)) // true console.log(reg.test(str4)) // true
// 下面正则表示验证数字只能出现 3 ~ 5 次 var reg = /\d{3,5}/ var str = 'abc' var str2 = 'abc1' var str3 = 'abc123' var str4 = 'abc12345' console.log(reg.test(str)) // false console.log(reg.test(str2)) // false console.log(reg.test(str3)) // true console.log(reg.test(str4)) // true
边界符
-
边界符是限定字符串的开始和结束的
-
^
: 表示他后面的内容必须在开头 -
$
: 表示他前面的内容必须在结尾 -
^$
:表示有且仅有他们中间的内容,也就是精确匹配// 下面表示从开头到结尾只能有数字,并且出现 3 ~ 5 次 var reg = /^\d{3,5}$/ var str = 'abc' var str2 = 'abc123' var str3 = '1' var str4 = '1234567' var str5 = '123' var str6 = '12345' console.log(reg.test(str)) // false console.log(reg.test(str2)) // false console.log(reg.test(str3)) // false console.log(reg.test(str4)) // false console.log(reg.test(str5)) // true console.log(reg.test(str6)) // true
特殊符号
-
()
: 限定一组元素 -
[]
: 字符集合,表示写在[]
里面的任意一个都行 -
[^]
: 反字符集合,表示写在[^]
里面之外的任意一个都行 -
-
: 范围,比如a-z
表示从字母 a 到字母 z 都可以 -
|
: 或,正则里面的或a|b
表示字母 a 或者 b 都可以 -
现在我们就可以把若干符号组合在一起使用了
// 1.下面是一个简单的邮箱验证 // 非_$开头,任意字符出现至少6次,一个@符号,(163|126|qq|sina)中的任意一个,一个点,(com|cn|net)中的任意一个 var reg = /^[^_$].{6,}@(163|126|qq|sina)\.(com|cn|net)$/
//2.以字母开头,数字结尾,中间任意一个字符 let reg = /^[a-zA-Z].\d$/ let str = "h1" let str2 = "h12" console.log(reg.test(str));// false console.log(reg.test(str2));// true
// 3 写出中国人姓名正则,2-4个中文 let h1 = document.querySelector('h1') // 中文基本汉字范围是 \u4E00-\u9FA5(\u表示声明编码) h1.innerHTML = '\u9FA5' // 写出中国人姓名正则 let reg = /^[\u4E00-\u9FA5]{2,4}$/ let str = "lucy"; let str2 = "志强" let str3 = "梦洁1" console.log(reg.test(str));// false console.log(reg.test(str2));// true console.log(reg.test(str3));// false
标示符
-
i
: 表示忽略大小写- 这个 i 是写在正则的最后面的
- eg:
/\w/i
- 就是在正则匹配的时候不去区分大小写
-
g
: 表示全局匹配(指的就是把全部的找出来)- 这个 g 是写在正则的最后面的
- eg:
/\w/g
- 就是全局匹配字母数字下划线
-
m
: 表示多行匹配-
这个 m是写在正则的最后面的
-
eg:
/\w/m
-
加m是多行匹配,每行的开头都算开头,每行的结尾都算结尾
var str="This is an\n antzone good"; var reg=/an$/; console.log(str.match(reg)); // 匹配不到 var reg2=/an$/m; console.log(str.match(reg2)); // 能匹配到,进行了多行匹配,行的结尾也算结尾了 var reg3 = /^b/; var str = 'test\nbbs'; console.log(str.match(reg3)); // 匹配不到 var reg4 = /^b/m; console.log(str.match(reg4)); // 能匹配到,进行了多行匹配,行的开始也算开始了
-
正则表达式的方法
- 正则提供了一些方法给我们使用
- 用来检测和捕获字符串中的内容的
test
-
test
是用来检测字符串是否符合我们正则的标准,符合返回true,不符合返回false -
语法:
正则.test(字符串)
-
返回值:布尔值
console.log(/\d+/.test('123')) // true console.log(/\d+/.test('abc')) // false
exec
-
exec
是把字符串中符合条件的内容捕获出来 -
语法:
正则.exec(字符串)
-
返回值: 把字符串中符合正则要求的第一项以及一些其他信息,以数组的形式返回。没找到的话,返回null
-
如果用了全局匹配:多次调用会连续匹配后面的符合条件
-
如果没有全局匹配:只匹配第一项
var reg = /\d{3}/ var str = 'hello123world456你好789' var res = reg.exec(str) console.log(res) /* ["123", index: 5, input: "hello123world456你好789", groups: undefined] 0: "123" groups: undefined index: 5 input: "hello123world456你好789" length: 1 __proto__: Array(0) */
- 数组第 0 项就是匹配到的字符串内容
- index 属性表示从字符串的索引几开始是匹配的到字符串
字符串的方法(扩展)
- 字符串中有一些方法也是可以和正则一起使用的
search
-
search
是查找字符串中是否有满足正则条件的内容 -
语法:
字符串.search(正则)
-
返回值 : 有的话返回第一个匹配的元素的索引,没有返回值-1
-
是否全局匹配没有区别
var reg = /\d{3}/ var str = 'hello123' var str2 = 'hello' console.log(str.search(reg)) // 5 console.log(str2.search(reg)) // -1
match
-
match
找到字符串中符合正则条件的内容返回 -
语法:
字符串.match(正则)
-
返回值 :
- 没有标示符 g 的时候,是和 exec 方法一样,把字符串中符合正则要求的第一项以及一些其他信息,以数组的形式返回
- 有标示符 g 的时候,是返回一个数组,里面是匹配到的每一项
var reg = /\d{3}/ var str = 'hello123world456' var str2 = 'hello' console.log(str.match(reg)) // ["123", index: 5, input: "hello123wor456", groups: undefined] console.log(str2.match(reg)) // null
var reg = /\d{3}/g var str = 'hello123world456' var str2 = 'hello' console.log(str.match(reg)) // ["123", "456"] console.log(str2.match(reg)) // null
replace
-
replace
是将字符串中满足正则条件的字符串替换掉 -
语法:
字符串.replace(正则,要替换的字符串)
-
返回值 : 替换后的字符串
-
没有标示符g:只替换一次
-
有标示符g:所有符合条件的都替换掉
var reg = /\d{3}/ var str = 'hello123world456' var str2 = 'hello' console.log(str.replace(reg)) // hello666world456 console.log(str2.replace(reg)) // hello
var reg = /\d{3}/g var str = 'hello123world456' var str2 = 'hello' console.log(str.replace(reg)) // hello666world666 console.log(str2.replace(reg)) // hello
正则表达式的两个特性
- 当正则表达式中包含能接受重复的限定符时,通常的行为是(在使整个表达式能得到匹配的前提下)匹配尽可能多的字符
- 贪婪性 - 默认启动
- 就是在整个正则表达式匹配成功的前提下,尽可能多的去匹配,取最大匹配值的字符串
- 懒惰性 - 加?启动
- 懒惰模式就是在整个正则表达式匹配成功的前提下,尽可能少的去匹配
*? 重复任意次,但尽可能少重复
+? 重复1次或更多次,但尽可能少重复
?? 重复0次或1次,但尽可能少重复
{n,m}? 重复n到m次,但尽可能少重复
{n,}? 重复n次以上,但尽可能少重复
- 懒惰模式就是在整个正则表达式匹配成功的前提下,尽可能少的去匹配
var str = "aabab"
var reg = /a.*b/ // 贪婪
console.log(str.replace(reg,'1')); //1
var reg2 = /a.*?b/ // 懒惰
console.log(str.replace(reg2,'1'));// 1ab
// 案例:
let reg3 = /\d*/
console.log(str.replace(reg3,''));// 'hello123world'
// 最开始匹配了空字符串就已经成功了,就不会继续匹配了(因为默认认为每个字符之间都有个空字符串)
2022年8月22日 运动函数和轮播图
DOM动画效果
- 让一个元素从左至右进行运动
<div id="box"></div>
var box = document.getElementById("box");
var t = null;
t = setInterval(function(){
//书写运动的属性
})
2.案例
<script>
/*
需求:页面一打开,div匀速从left=0运动到left=800,速度为5px
原理: 设置定时器,每隔20ms,让div的定位的left = div的原来的left + 速度
*/
// 获取运动的元素
let oDiv = document.querySelector('div');
// 设置定时器,每隔20ms
let timer = setInterval(()=>{
// 元素.offsetLeft: 元素距离最近的有定位的父元素的left(就是css中的left的值)
// div的定位的left = div的原来的left + 速度
oDiv.style.left = oDiv.offsetLeft + 5 + "px"
// 如何让元素停下来,left = 800
if(oDiv.offsetLeft===800){
// 停止定时器,停止运动
clearInterval(timer)
}
},20)
</script>
运动的终止条件
t = setInterval(function(){终止条件})
// 元素的属性值 === 目标点
if(dom.attr === target){
clearInterval(t);
}
//开启定时器,首先要记得清理定时器。
案例.
<script>
/*
需求分析
1 点击按钮button
2 div运动到800停止
*/
// 获取元素
let btn = document.querySelector('button');// 按钮
let oDiv = document.querySelector('div');// 要运动的div
let timer;
// 点击按钮button
btn.addEventListener('click',()=>{
// 要用定时器先清上次点击产生定时器
clearInterval(timer)
// div开始运动就是开启定时器
timer = setInterval(()=>{
// 判断是否到达目标
if(oDiv.offsetLeft === 800){
// 清除定时器就是不再进行下一次的20ms
// 但是本地的20ms的代码要执行完成
clearInterval(timer);
return;
}
// 每隔20ms,div的left = div原来的left+速度
oDiv.style.left = oDiv.offsetLeft + 1+"px"
},20)
})
// 用户点击按钮是不可控制的
// 分两种情况
// 1 到达目标了,用户还是要点击
// 开始运动前就判断是否到达目标,到达就停止定时器和本次的定位
// 2 没有到达目标,用户又点击了
// 点击一次开一个定时器
// 点1次 : 0 20ms 40ms 每20ms增加1px timer = 1
// 点第2次: 10 30ms 50ms 70ms 每20ms增加1px timer = 2
// 点第3次: 5 25ms 45ms 65ms 每20ms增加1px timer = 3
// 定时器的使用原则: 一个物体同时只能被一个定时器控制
// 也就是要用定时器先清定时器
运动的三要素
- 起始点
一个运动的起始点其实就是当前元素的位置,我们通过API获取当前元素的位置,让这个位置作为运动的起始。
- 目标
- 速度
DOM动画效果封装
单属性运动框架:(缓动)
function move( ele , attr , target){
// 1. 关闭开启定时器;
clearInterval( ele.timer );
ele.timer = setInterval( function(){
// 2. 计算速度;
if(attr === "opacity"){
var iNow = parseInt(getComputedStyle(ele)[attr] * 100); //0 ~ 100
}else{
var iNow = parseInt(getComputedStyle(ele)[attr]); //100
}
var speed = (target - iNow) / 10;
// 3. 速度取整;
if(speed > 0){
speed = Math.ceil(speed);
}else{
speed = Math.floor(speed);
}
if(attr === "opacity"){
ele.style[attr] = (iNow + speed) / 100 ;
}else{
ele.style[attr] = iNow + speed + "px";
}
// 4. 终止条件;
if(iNow === target){
clearInterval(ele.timer);
}
} , 50)
}
多属性运动框架
-
多属性缓动运动框架
function animate(dom,target,fn){ clearInterval(dom.timer); dom.timer = setInterval(()=>{ let flag = true; for(let attr in target){ let current if(attr=='opacity'){ current = parseInt(getComputedStyle(dom)[attr]*100) }else{ current = parseInt(getComputedStyle(dom)[attr]); } let speed = (target[attr] - current)/10 speed = speed>0?Math.ceil(speed):Math.floor(speed); let next = current + speed; if(attr=='opacity'){ dom.style[attr] = next/100 }else{ dom.style[attr] = next+"px"; } if(next!=target[attr]){ flag = false } } if(flag){ clearInterval(dom.timer) if(fn){ fn() } } },20) }
左右切换轮播图书写
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
*{
margin:0;
padding:0
}
.box{
width: 500px;
height: 200px;
padding:10px;
border:1px solid #ccc;
margin:100px auto;
position: relative;
}
.imgs{
width: 500px;
height: 200px;
position: relative;
overflow: hidden;
}
ul{
width: 2500px;
list-style: none;
position: absolute;
left:0;
}
li{
list-style: none;
float: left;
}
img{
display: block;
width: 500px;
height: 200px;
}
.prev,.next{
position: absolute;
height: 50px;
width: 30px;
text-align: center;
line-height: 50px;
font-size: 30px;
font-weight: 900;
top:50%;
margin-top:-25px;
background-color: #ccc;
color:white;
cursor: pointer;
}
.prev{
left:10px;
}
.next{
right:10px;
}
</style>
</head>
<body>
<div class="box">
<div class="imgs">
<ul>
<li>
<img src="./images/1.jpg" alt="">
</li>
<li>
<img src="./images/2.jpg" alt="">
</li>
<li>
<img src="./images/3.jpg" alt="">
</li>
<li>
<img src="./images/4.jpg" alt="">
</li>
<li>
<img src="./images/5.jpg" alt="">
</li>
</ul>
</div>
<div class="arrows">
<div class="prev"><</div>
<div class="next">></div>
</div>
</div>
<script type="module">
import {move} from "./public.js"
/*
需求分析:
1 点击prev按钮,缓动到上一张
2 点击next按钮,缓动到下一张
*/
// 获取元素
let prev = document.querySelector('.prev');// 上一张按钮
let next = document.querySelector('.next');// 下一张按钮
let ul = document.querySelector('ul');// 要运动的元素
// 定义记录当前显示的li的索引
let current = 0;
// 定义记录一个图片宽度的变量
let width = document.querySelector('img').offsetWidth;
// 1 点击prev按钮
prev.onclick = function(){
let nextIndex = current - 1;
if(nextIndex>=0){
// 只有索引是0或以上才能看
// 0 left = 0
// 1 left = -width+"px"
// 2 left = -2*width+'px'
move(ul,'left',-width*nextIndex,()=>{
// 运动完以后更新当前索引值
current = nextIndex;
});
}
}
// 2 点击next按钮
next.onclick = function(){
let nextIndex = current + 1;
if(nextIndex<=ul.children.length-1){
move(ul,'left',-width*nextIndex,()=>{
// 运动完以后更新当前索引值
current = nextIndex
})
}
}
</script>
</body>
</html>
Swiper 介绍
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<link rel="stylesheet" href="./swiper.min.css">
<style>
.qf1{
width:400px;
height:200px;
border:1px solid #ccc;
margin:10px auto;
}
.qf2{
width:800px;
height:100px;
border:1px solid red;
margin:10px auto;
}
</style>
</head>
<body>
<div class="swiper-container qf1">
<div class="swiper-wrapper">
<div class="swiper-slide">Slide 1</div>
<div class="swiper-slide">Slide 2</div>
<div class="swiper-slide">Slide 3</div>
</div>
</div>
<hr>
<div class="swiper-container qf2">
<div class="swiper-wrapper">
<div class="swiper-slide">Slide 1</div>
<div class="swiper-slide">Slide 2</div>
<div class="swiper-slide">Slide 3</div>
<div class="swiper-slide">Slide 4</div>
<div class="swiper-slide">Slide 5</div>
<div class="swiper-slide">Slide 6</div>
</div>
<!-- 如果需要分页器 -->
<div class="swiper-pagination qf2-p"></div>
<!-- 如果需要导航按钮 -->
<div class="swiper-button-prev qf2-prev "></div>
<div class="swiper-button-next qf2-next"></div>
<!-- 如果需要滚动条 -->
<div class="swiper-scrollbar qf2-scrollbar"></div>
</div>
<script src="./swiper.min.js"></script>
<!-- 引入swiper.min.js就多了Swiper变量 -->
<script>
// swiper插件
// 第一步:下载5.4.5 https://www.swiper.com.cn/index.html
// 第二步: 引入swiper.min.js 和 swiper.min.css
// 第三步:写html结构
// 第四步:写css样式
// 第五步: 初始化轮播图
new Swiper('.qf1')
new Swiper('.qf2',{
direction: 'vertical', // 垂直切换选项
pagination:{
el:'.qf2-p'
},
navigation:{
prevEl:'.qf2-prev',
nextEl:'.qf2-next'
},
scrollbar: {
el: '.qf2-scrollbar',
}
})
</script>
</body>
</html>
2022年8月23日 面向对象
- 首先,我们要明确,面向对象不是语法,是一个思想,是一种 编程模式
- 面向: 面(脸),向(朝着)
- 面向过程: 脸朝着过程 =》 关注着过程的编程模式
- 面向对象: 脸朝着对象 =》 关注着对象的编程模式
- 实现一个效果
- 在面向过程的时候,我们要关注每一个元素,每一个元素之间的关系,顺序,。。。
- 在面向过程的时候,我们要关注的就是找到一个对象来帮我做这个事情,我等待结果
- 例子 🌰: 我要吃面条
- 面向过程
- 用多少面粉
- 用多少水
- 怎么和面
- 怎么切面条
- 做开水
- 煮面
- 吃面
- 面向对象
- 找到一个面馆
- 叫一碗面
- 等着吃
- 面向对象就是对面向过程的封装
- 面向过程
- 我们以前的编程思想是,每一个功能,都按照需求一步一步的逐步完成
- 我们以后的编程思想是,每一个功能,都先创造一个 面馆,这个 面馆 能帮我们作出一个 面(完成这个功能的对象),然后用 面馆 创造出一个 面,我们只要等到结果就好了
创建对象的方式
- 因为面向对象就是一个找到对象的过程
- 所以我们先要了解如何创建一个对象
调用系统内置的构造函数创建对象
-
js 给我们内置了一个 Object 构造函数
-
这个构造函数就是用来创造对象的
-
当 构造函数 和 new 关键字连用的时候,就可以为我们创造出一个对象
-
因为 js 是一个动态的语言,那么我们就可以动态的向对象中添加成员了
// 就能得到一个空对象 var o1 = new Object() // 正常操作对象 o1.name = 'Jack' o1.age = 18 o1.gender = '男'
字面量的方式创建一个对象
-
直接使用字面量的形式,也就是直接写
{}
-
可以在写的时候就添加好成员,也可以动态的添加
// 字面量方式创建对象 var o1 = { name: 'Jack', age: 18, gender: '男' } // 再来一个 var o2 = {} o2.name = 'Rose' o2.age = 20 o2.gender = '女'
使用工厂函数的方式创建对象
-
先书写一个工厂函数
-
这个工厂函数里面可以创造出一个对象,并且给对象添加一些属性,还能把对象返回
-
使用这个工厂函数创造对象
// 1. 先创建一个工厂函数 function createObj() { // 手动创建一个对象 var obj = new Object() // 手动的向对象中添加成员 obj.name = 'Jack' obj.age = 18 obj.gender = '男' // 手动返回一个对象 return obj } // 2. 使用这个工厂函数创建对象 var o1 = createObj() var o2 = createObj()
使用自定义构造函数创建对象
-
工厂函数需要经历三个步骤
- 手动创建对象
- 手动添加成员
- 手动返回对象
-
构造函数会比工厂函数简单一下
- 自动创建对象
- 手动添加成员
- 自动返回对象
-
先书写一个构造函数
-
在构造函数内向对象添加一些成员
-
使用这个构造函数创造一个对象(和 new 连用)
-
构造函数可以创建对象,并且创建一个带有属性和方法的对象
-
面向对象就是要想办法找到一个有属性和方法的对象
-
面向对象就是我们自己制造 构造函数 的过程
// 1. 先创造一个构造函数 function Person(name, gender) { this.age = 18 this.name = name this.gender = gender } // 2. 使用构造函数创建对象 var p1 = new Person('Jack', 'man') var p2 = new Person('Rose', 'woman')
构造函数详解
- 我们了解了对象的创建方式
- 我们的面向对象就是要么能直接得到一个对象
- 要么就弄出一个能创造对象的东西,我们自己创造对象
- 我们的构造函数就能创造对象,所以接下来我们就详细聊聊 构造函数
构造函数的基本使用
-
和普通函数一样,只不过 调用的时候要和 new 连用,不然就是一个普通函数调用
function Person() {} var o1 = new Person() // 能得到一个空对象 var o2 = Person() // 什么也得不到,这个就是普通函数调用
- 注意: 不写 new 的时候就是普通函数调用,没有创造对象的能力
-
首字母大写(只是为了区分你希望这个函数作为构造函数调用)
function person() {} var o1 = new person() // 能得到一个对象 function Person() {} var o2 = new Person() // 能得到一个对象
- 注意: 首字母不大写,只要和 new 连用,就有创造对象的能力
-
当调用的时候如果不需要传递参数可以不写
()
,建议都写上function Person() {} var o1 = new Person() // 能得到一个空对象 var o2 = new Person // 能得到一个空对象
- 注意: 如果不需要传递参数,那么可以不写 (),如果传递参数就必须写
-
构造函数内部的 this,由于和 new 连用的关系,是指向当前实例对象的
function Person() { console.log(this) } var o1 = new Person() // 本次调用的时候,this => o1 var o2 = new Person() // 本次调用的时候,this => o2
- 注意: 每次 new 的时候,函数内部的 this 都是指向当前这次的实例化对象
-
因为构造函数会自动返回一个对象,所以构造函数内部不要写 return
- 你如果 return 一个基本数据类型,那么写了没有意义
- 如果你 return 一个引用数据类型,那么构造函数本身的意义就没有了
使用构造函数创建一个对象
-
我们在使用构造函数的时候,可以通过一些代码和内容来向当前的对象中添加一些内容
function Person() { this.name = 'Jack' this.age = 18 } var o1 = new Person() var o2 = new Person()
- 我们得到的两个对象里面都有自己的成员 name 和 age
-
我们在写构造函数的时候,是不是也可以添加一些方法进去呢?
function Person() { this.name = 'Jack' this.age = 18 this.sayHi = function () { console.log('hello constructor') } } var o1 = new Person() var o2 = new Person()
- 显然是可以的,我们的到的两个对象中都有
sayHi
这个函数 - 也都可以正常调用
- 显然是可以的,我们的到的两个对象中都有
-
但是这样好不好呢?缺点在哪里?
function Person() { this.name = 'Jack' this.age = 18 this.sayHi = function () { console.log('hello constructor') } } // 第一次 new 的时候, Person 这个函数要执行一遍 // 执行一遍就会创造一个新的函数,并且把函数地址赋值给 this.sayHi var o1 = new Person() // 第二次 new 的时候, Person 这个函数要执行一遍 // 执行一遍就会创造一个新的函数,并且把函数地址赋值给 this.sayHi var o2 = new Person()
- 这样的话,那么我们两个对象内的
sayHi
函数就是一个代码一模一样,功能一模一样 - 但是是两个空间函数,占用两个内存空间
- 也就是说
o1.sayHi
是一个地址,o2.sayHi
是一个地址 - 所以我们执行
console.log(o1 === o2.sayHi)
的到的结果是false
- 缺点: 一模一样的函数出现了两次,占用了两个空间地址
- 这样的话,那么我们两个对象内的
-
怎么解决这个问题呢?
- 就需要用到一个东西,叫做 原型
原型
- 原型的出现,就是为了解决 构造函数的缺点
- 也就是给我们提供了一个给对象添加函数的方法
- 不然构造函数只能给对象添加属性,不能合理的添加函数就太 LOW 了
prototype
-
每一个函数天生自带一个成员,叫做 prototype,是一个对象空间
-
即然每一个函数都有,构造函数也是函数,构造函数也有这个对象空间
-
这个
prototype
对象空间可以由函数名来访问function Person() {} console.log(Person.prototype) // 是一个对象
- 即然是个对象,那么我们就可以向里面放入一些东西
function Person() {} Person.prototype.name = 'prototype' Person.prototype.sayHi = function () {}
-
我们发现了一个叫做
prototype
的空间是和函数有关联的 -
并且可以向里面存储一些东西
-
重点: 在函数的 prototype 里面存储的内容,不是给函数使用的,是给函数的每一个实例化对象使用的
-
那实例化对象怎么能使用?
proto
-
每一个对象都天生自带一个成员,叫做
__proto__
,是一个对象空间 -
即然每一个对象都有,实例化对象也是对象,那么每一个实例化对象也有这个成员
-
这个
__proto__
对象空间是给每一个对象使用的 -
当你访问一个对象中的成员的时候
- 如果这个对象自己本身有这个成员,那么就会直接给你结果
- 如果没有,就会去
__proto__
这个对象空间里面找,里面有的话就给你结果 - 未完待续。。。
-
那么这个
__proto__
又指向哪里呢?- 这个对象是由哪个构造函数 new 出来的,
- 那么这个对象的
__proto__
就指向这个构造函数的prototype
function Person() {} var p1 = new Person() console.log(p1.__proto__ === Person.prototype) // true
- 我们发现实例化对象的
__proto__
和所属的构造函数的prototype
是一个对象空间 - 我们可以通过构造函数名称来向
prototype
中添加成员 - 对象在访问的时候自己没有,可以自动去自己的
__proto__
中查找 - 那么,我们之前构造函数的缺点就可以解决了
- 我们可以把函数放在构造函数的
prototype
中 - 实例化对象访问的时候,自己没有,就会自动去
__proto__
中找 - 那么也可以使用了
- 我们可以把函数放在构造函数的
function Person() {} Person.prototype.sayHi = function () { console.log('hello Person') } var p1 = new Person() p1.sayHi()
p1
自己没有sayHi
方法,就会去自己的__proto__
中查找p1.__proto__
就是Person.prototype
- 我们又向
Person.prototype
中添加了sayHi
方法 - 所以
p1.sayHi
就可以执行了
-
到这里,当我们实例化多个对象的时候,每个对象里面都没有方法
- 都是去所属的构造函数的
protottype
中查找 - 那么每一个对象使用的函数,其实都是同一个函数
- 那么就解决了我们构造函数的缺点
function Person() {} Person.prototype.sayHi = function () { console.log('hello') } var p1 = new Person() var p2 = new Person() console.log(p1.sayHi === p2.sayHi)
p1
是Person
的一个实例p2
是Person
的一个实例- 也就是说
p1.__proto__
和p2.__proto__
指向的都是Person.prototype
- 当
p1
去调用sayHi
方法的时候是去Person.prototype
中找 - 当
p2
去调用sayHi
方法的时候是去Person.prototype
中找 - 那么两个实例化对象就是找到的一个方法,也是执行的一个方法
- 都是去所属的构造函数的
-
结论
- 当我们写构造函数的时候
- 属性我们直接写在构造函数体内
- 方法我们写在原型上
2022年8月24日 原型和原型链
原型链
- 我们刚才聊过构造函数了,也聊了原型
- 那么问题出现了,我们说构造函数的
prototype
是一个对象 - 又说了每一个对象都天生自带一个
__proto__
属性 - 那么 构造函数的 prototype 里面的
__proto__
属性又指向哪里呢?
一个对象所属的构造函数
-
每一个对象都有一个自己所属的构造函数
-
比如: 数组
// 数组本身也是一个对象 var arr = [] var arr2 = new Array()
- 以上两种方式都是创造一个数组
- 我们就说数组所属的构造函数就是
Array
-
比如: 函数
// 函数本身也是一个对象 var fn = function () {} var fun = new Function()
- 以上两种方式都是创造一个函数
- 我们就说函数所属的构造函数就是
Function
constructor
- 对象的
__proto__
里面也有一个成员叫做constructor
- 这个属性就是指向当前这个对象所属的构造函数
链状结构
- 当一个对象我们不知道准确的是谁构造的时候,我们呢就把它看成
Object
的实例化对象 - 也就是说,我们的 构造函数 的 prototype 的
__proto__
指向的是Object.prototype
- 那么
Object.prototype
也是个对象,那么它的__proto__
又指向谁呢? - 因为
Object
的 js 中的顶级构造函数,我们有一句话叫 万物皆对象 - 所以
Object.prototype
就到顶了,Object.prototype
的__proto__
就是 null
原型链的访问原则
- 我们之前说过,访问一个对象的成员的时候,自己没有就会去
__proto__
中找 - 接下来就是,如果
__proto__
里面没有就再去__proto__
里面找 - 一直找到
Object.prototype
里面都没有,那么就会返回undefiend
对象的赋值
- 到这里,我们就会觉得,如果是赋值的话,那么也会按照原型链的规则来
- 但是: 并不是!并不是!并不是! 重要的事情说三遍
- 赋值的时候,就是直接给对象自己本身赋值
- 如果原先有就是修改
- 原先没有就是添加
- 不会和
__proto__
有关系
es6的类语法
语法:
class 类名 {
constructor(形参1,形参2,){
}
函数名1(){
}
函数名2(){
}
}
class Person {
constructor(name,age){
// constructor就类似es5语法的构造函数
this.name = name;
this.age = age;
}
// 可以在这里定义函数,就是在原型上添加函数
sayHi(){
console.log('hello world')
}
}
// 实例化对象
let p1 = new Person('jack',18)
console.log(p1)
总结
- 到了这里,我们就发现了面向对象的思想模式了
- 当我想完成一个功能的时候
- 先看看内置构造函数有没有能给我提供一个完成功能对象的能力
- 如果没有,我们就自己写一个构造函数,能创造出一个完成功能的对象
- 然后在用我们写的构造函数 new 一个对象出来,帮助我们完成功能就行了
- 比如: tab选项卡
- 我们要一个对象
- 对象包含一个属性:是每一个点击的按钮
- 对象包含一个属性:是每一个切换的盒子
- 对象包含一个方法:是点击按钮能切换盒子的能力
- 那么我们就需要自己写一个构造函数,要求 new 出来的对象有这些内容就好了
- 然后在用构造函数 new 一个对象就行了