前端工作笔记(二)
JavaScript 前端笔记
关于JavaScript的注意点
-
JS 会逐行解释代码,有错误会报错,后面不再执行
-
引入 .js 文件(外部文件)与引入 .css 文件有区别,js是通过script标签的src引入(css是通过link标签)
-
内嵌式 JS 书写位置应该写在body的下方(通过script标签),否则会出现一些问题,且一个页面可以书写多个script标签
面试题:谷歌浏览器的js引擎(解释器)是什么?有什么特点?答:V8解释器,速度最快
JavaScript内置的三条输入输出语句
- alert:弹出警示框(提示框)
- console.log :控制台输出,给程序员测试用(循环语句会直接显示循环次数),尤其注意,黑色代表字符串
- prompt:输入框(可以赋值给一个变量,但必须写完整),尤其注意,获取的是字符串(表单也是获取的字符串,可通过控制台输出验证)
变量的声明与赋值
一、声明
在 JS 中,变量的数据类型是根据右边变量值的数据类型来判断的,所以使用var关键字声明即可(鼠标悬停在变量名上,可显示当前数据类型)
注意:typeof 可以检测数据的类型(只能检测简单数据,不能检测复杂数据),注意 Null 的数据类型是object对象类型,undefined(表示不存在)
注意:typeof 检测并返回的数据类型是以字符串形式呈现,控制台输出显示为黑色可以验证
注意:同时声明多个变量,可以只写一个 var,多个变量名之间使用逗号隔开
注意:let 创建的变量不能重复声明(但可以重新赋值)
注意:name是浏览器内置的一个变量,有其它的作用,所以最好不要取name
变量名只能以字母、数字、下划线、$(dollar符)组成,且不能以数字开头(不能有空格,不能是关键字),建议小驼峰命名
变量名的字母相同,但是大小写不同,是两个不同的变量,需严格区分大小写
二、赋值
在 JS 中,给变量赋值时,数字不需要加引号,字符串需要加引号,且推荐单引号
赋值都是自右向左结合性(原因:赋值运算符优先级较低)
关于进制的前导数字
数字前面是0,代表八进制
数字前面是0x,表示十六进制
关于isNaN
NaN非数值(Not a Number)
isNaN(变量名)是不是非数值,返回的是布尔值,需要一个变量来接收
\n 和< br > 在 JS 中分别什么情况有效?
在JS中,绝大数情况使用 \n 表示换行,如字符串拼接、prompt输入框等,但在document.write( );中,\n无效,只能使用< br >表示换行
注意:document.write( );现在大部分情况不再使用
关于字符串需要注意的地方
书写字符串时,引号包含引号,必须遵循 “ 外单内双,外双内单 ” 的原则
模板字符串拼接法:字符串两侧必须是反引号包起来(esc下面那个键),变量用${}括起来
获取字符串长度:变量名.length
数据类型转换
一、转换为字符串(以变量num为例)
- num.toString(括号为空)
- 强制转换:String(num)
- 隐式转换(空字符拼接法):num+' '
二、转换为数字(以变量num为例)
- parseInt(num):把字符串转成整数(并不是转义,而是把字符1转成数字1,小数转换后是整数)
注意:parseInt 方法在某些情况下可以将字符串里的字母剔除掉,但还是建议使用 substr 截取字符串方法
-
parseFloat:把字符串转成小数(并不是转义,而是把字符1.1转成数字1.1,整数和小数都能转换,精确度较高)
-
强制转换:Number(num)
-
隐式转换:与数字进行运算时,使用 “ -(减)、*(乘)、/(除)、比较(大于小于等)”,会自动隐式转换变成数字型
注意:减、乘、除和比较,虽然可以把字符串转换成数字类型,但是本质上那个被转换的变量还是字符串数据类型,下次使用那个变量仍然需要进行再次转换
尤其简便:直接在字符串这个变量前添加一个 + 号,即可快速转换成数字
运算符
JavaScript语言对于数字中的小数,在计算的时候有精度问题。所以在开发中尽量不要直接对JavaScript中的小数进行算术运算
一、自增自减运算符
++i:先自增再参与运算;i--:先参与运算后自减
注意:自增自减运算符只能用在变量上
二、逻辑与逻辑或
逻辑与&&:两真为真,一假则假
逻辑或||:一真即真,两假为假
尤其注意(短路原理):0&&忽略 1||忽略
此处涉及知识点:1.比较运算符返回的是一个布尔值;2.非零值(包括字母)表示真(即1)
注意:&&优先级相对于||逻辑或较高
短路在开发中的实际运用
逻辑或:常用于对数据进行筛选,有第一个就用第一个,没有就用第二个,数据一 || 数据二
逻辑与:常用于条件判断,条件满足时执行后面的程序,条件 && 程序
尤其注意:当单独一个变量( 非 == 表达式形式 )作为判断条件时,会判断该变量本身是否存在(常搭配逻辑与),或判断该变量里的内容是否为空
尤其注意:当单独一个变量( 非 == 表达式形式 )作为判断条件时,且该变量的值为:NaN、null、“ ”、undefined、0 时,结果都为 false
深入理解:
- 变量(或对象形式:对象名.属性名) == xxx:表达式的写法,会默认左边的变量(或对象)已经存在,如果找不到该变量(或对象),就会报错
- 单独一个变量(或对象形式:对象名.属性名):先判断变量(对象)本身是否存在,若变量(对象)本身不存在,则返回 false,再进一步判断变量(对象)里面的值是否满足以上五种情况,如果满足,返回 false
关于 if 语句的注意点
- if(判断条件)除了用关系表达式或逻辑表达式判断为真,也可为非零值(包括字母)
- if 的后面若没有大括号(花括号),只执行紧跟其后的第一条语句,尤其注意,语句以分号结束算作一条
- 等于号带来的问题:判断条件中,表达式是=(赋值运算符)而不是==(关系运算符),会导致表达式为一个非零值,始终只执行第一句语句
补充:三目条件运算符(与 if 语句类似功能,但写法较为简单)
条件表达式?a:b
若为真,返回a,若为假,返回b
进阶用法:条件表达式?( 条件表达式?a:b ):c
解释:把另一个三目条件表达式作为另一个三目条件表达式的其中一个值
If 语句的合并优化
当单纯的 if 语句嵌套 if 语句时(不涉及 else),可以将两者优化成一句,判断条件使用逻辑与合并
// 合并优化前
if (条件一) {
if (条件二) {
执行某些操作;
}
}
// 合并优化后
if (条件一 && 条件二) {
执行某些操作;
}
关于 switch 语句的注意点(开发中极为少用)
switch 语句首先会寻找匹配项,然后从匹配项处开始依次向下执行,直到遇见break跳出(没有break会一直执行匹配项下面的每一句)
注意:default的位置会影响结果
注意:switch()里的值是变量,case的匹配项是一个具体的值
关于 for 循环的注意点
for循环括号()里的2个分号不能省略
循环次数的计算公式:<先转换成<= ,(终值-初值)/步长+1
注意:步长是计数器每次增加或减少的值
for 循环中 break 和 continue 的注意点
- for 循环嵌套 if 语句,if 语句里的 break 是指跳出(终止)离它最近的一个循环
- continue:结束本次循环,即 continue 后面的语句不执行,直接开始下一次循环
注意:for(1;2;3)中的 continue 会回到 3 处再执行一次并判断条件,而break直接终止
数组的使用
创建数组:var arr=[ ];(字面量创建数组最常使用)
数组变量名.length:动态检测数组元素的个数,特殊情况可代替数组的索引值 index
获取数组二维的长度:数组变量名[ i ].length
补充:数组的空值是 empty
注意:数组赋值时需要注意,要添加索引值,不能直接书写数组名赋值(如arr=10;),否则所有的数据都会丢失
注意:push的作用是往数组中添加元素,数组名.push(数组元素值),正常情况括号里填写的是一个具体的值(或者是数组名[索引值]的形式),如果是向数组中添加一个数组,括号里填写的是数组名,此时原数组会变成一个二维数组
尤其注意:push添加元素的优点在于,不用考虑索引,直接追加
算法题(笔试):冒泡排序(思路)
将两个变量值交换,一轮只能将一个数字排到最后,此时需要考虑外层写一个循环,用来控制轮数,里面再写一个循环,用来控制每一轮的交换,由于每一轮共交换几次涉及性能最优,所以要考虑交换的次数,避免性能的浪费,此时细心观察规律即可得到交换次数的公式,最后在满足条件时交换变量值即可(通过temp临时变量存储一个变量的值作为中间媒介)
关于函数的注意点
function 是声明函数的关键字,函数名(也叫方法名)的命名规范要求使用小驼峰
注意:没有函数名的函数被称为匿名函数,需要用一个变量来接收
注意:无论是不是带参函数,调用时一定要有()小括号
注意:函数名有了括号就会立即执行
注意:声明函数时,括号里的是形参,接收的是调用函数时括号里写的具体的值(也称实参)
多个形参用逗号隔开,尤其注意,调用函数的实参个数要与形参个数一致,顺序也一一对应,否则会出问题
注意:函数内置对象 arguments 是伪数组,有length属性和索引值,存储了所有传递过来的实参
尤其注意:多个封装的函数,相互独立开来,呈兄弟关系,如果相互调用,里面被调用的函数无法访问外部函数声明的变量,因为本质上里面被调用的函数是存在外面的,他的程序代码是写在外部,无法拿到另一个封闭的兄弟函数内部的值
深入理解有名函数和匿名函数
匿名函数:function ( ) { 处理程序 }
有名函数:function 函数名 ( ) { 处理程序 }
深入理解:对于有名函数而言,它的函数名相当于 function ( ) { 处理程序 }
调用函数的两种方式:
-
有括号立即调用:函数名 ( )
-
通过事件调用:dom对象 . 事件类型 = 函数名
注意:这里的函数名相当于 function ( ) { 处理程序 },但该函数是在事件触发时调用
何时使用 return 关键字
一、业务需求指定函数内部有输出打印时
只写 ” 函数名(实参)“ 调用即可,因为执行函数时,内部写的console.log 或 alert 已经打印了结果
二、业务需求指定函数内部没有输出打印时
函数内部使用 return 返回结果,由于这个结果在函数内部并没有打印,此时需要在函数体外,声明一个新的变量来接收这个返回的值,最后通过打印变量名来输出结果,或者直接通过 console.log 或 alert 打印 ” 函数名(实参)“
注意:return 只能出现在函数体中,用于结束整个函数体并返回值,后面的语句(函数体范围内)不会被执行
注意:return 一次只能返回一个值,如果想要返回多个值,将返回值以数组的形式返回,即 return [ 值1,值2,值3,值4,值5......]
前期不出错的函数封装方法
- 按照不封装的方法,把代码一行行写好
- 把写好的代码剪切到函数体中
- 找出代码中可能会变化的数据,并把这个数据提取成函数的形参
- 调用函数
JavaScript的作用域
一、全局作用域
定义:整个 script 标签或者时一个单独的 js 文件
注意:全局变量在任何地方都可以使用,因此函数内部也可以使用
二、局部作用域
定义:函数内部,又称函数作用域
注意:每个函数的作用域不同,设置相同的变量名不会冲突
注意:如果在函数内部没有声明,而是直接赋值的变量(即没有 var),属于全局变量(不建议使用),在函数外部想要使用这个变量,必须要先调用函数
助于理解:函数没有调用,并不会执行函数里面的代码,所以无法查找到函数里面的全局变量
作用域链查找:多层作用域嵌套,并且想使用某个变量,首先会在当前函数作用域中查找,如果函数内部本身声明了变量,且变量名和上层作用域乃至全局作用域的变量名一致,遵循就近原则,优先使用自己声明的变量;但如果函数内部本身没有声明变量,就依次向上层查找,一直查找到全局作用域,如果还没有就会报错
三、块级作用域
定义:let 声明的变量拥有块级作用域(如果在 if 或 for 循环的花括号中使用 let 声明变量,那么这个变量只能在 if 或者 for 循环的花括号中使用)
笔试题:程序预解析后的运行结果是什么?
方法:js 引擎会预先将变量声明(不包含赋值)和函数(function)提升到当前作用域前面(注意是当前作用域),然后再逐行解析代码,最后再根据 “作用域链查找” 的规则计算出结果
对象的创建和调用
简单记忆:每一个 { } 花括号就是一个对象,可以存在于数组中
一、字面量创建对象
注意:类似于字面量创建数组,但是跟着的是花括号
var 对象名={
属性名(不需要var声明):属性值, // 多个属性或方法,中间用逗号隔开
方法名(不需要var声明):匿名函数 //没有函数名的函数
}
调用对象里的属性:对象名 . 属性名,或者 对象名 [ ' 属性名 ' ],必须要加引号
调用对象里的方法:对象名 . 方法名(),尤其注意,这个方法是一个函数,是必须要加小括号的
注意:调用对象时的小圆点是 ‘ 的 ’ 的意思
二、构造函数创建对象
注意:类似于创建普通函数,但是调用函数的时候使用了 new 关键字,该函数就创建了空对象,成为一个构造函数
注意:为了区分构造函数和普通函数,通常会将构造函数的函数名首字母大写,形成命名规范
function 构造函数名(形参){
this.属性名=形参; // 多个属性或方法,中间用分号隔开
this.方法名=匿名函数 //没有函数名的函数
}
尤其注意:构造函数的函数名表示泛指的一类名称,并不是具体的对象名,必须依靠调用构造函数使对象实例化
对象实例化:var 实例化的对象名 = new 构造函数名(实参);
注意:this指向的是实例化的对象名,两者等价
调用对象里的属性或者方法:实例化的对象名 . 属性名或方法名
对象赋值对象造成的问题
当把一个对象赋值给另一个对象,会造成,两个对象指向同一个地址,任意一个对象进行属性值的操作修改,会影响另一个对象里的属性值
获取对象的属性名
var 数组变量名 = Object . keys(对象名)
注意:该方法可以获取对象中的所有属性名(并不是属性值),返回的是一个数组,用一个数组变量名接收
对象的遍历及双重遍历
for(变量名 in 对象名){ // 变量名可随便取,代指遍历过程中当前的属性名
// 可正常嵌套if语句等
alert(对象名[变量名]);
}
面试题:能不能使用 for 循环遍历对象?答:可以,但不建议,需要把对象里的属性名改成以数字以 0 开始,并且在对象里另外指定 length 长度
重难点:双重遍历(对象里嵌套对象)
for (第一个变量名 in 对象名) { // 变量名可随便取,代指遍历过程中当前的属性名
//遍历对象里面某个属性值的对象内容
if (第一个变量名 == '某个属性名') {
for (第二个变量名 in 对象名[第一个变量名] 或 对象名['某个属性名'] 或 对象名.某个属性名) {
alert(对象名[第一个变量][第二个变量名]);
}
}
}
尤其注意(极其重要):对象名 . 属性名(小圆点后面不能加引号,固定写法),属性名本质上是字符串,因此(对象名 . 属性名 = 对象名[ ‘ 属性名 ’ ]),变量名存放的是有引号的 ' 属性名 ' (变量名 = ' 属性名 ' ,在作条件判断时必须要加引号),两者可以相互转换,即(对象名 [ ' 属性名 ' ] = 对象名[变量名])
简单记忆:只要是变量,就没有引号,其余都有引号(例外:布尔值的true和false不用加引号)
尤其注意:在对象形式的写法中,如果对象的属性名是一个变量,必须用括号包裹该变量(固定写法),如:Obj.xxx.[变量名] 或 { [ 变量名 ] : 变量值 }
常用的Math内置对象方法
MDN文档查阅:https://developer.mozilla.org/zh-CN/
-
圆周率:Math.PI
-
最大值:Math.max()
-
绝对值:Math.abs()
-
向上取整:Math.ceil()
注意:向上取整常用于分页计算
-
向下取整:Math.floor()
-
四舍五入:Math.round()
注意:四舍五入时,负数说往大取(如-1.5取1)
-
[0,1)的随机数:Math.random()
注意:随机数属于工具类函数,直接使用如下代码即可
//随机函数,包含最大值和最小值
function getRandom(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
注意:处理数字时,通常会保留指定的小数位,使用 toFlxed(保留的小数位数)
关于Date对象的使用
使用的前提条件: Date 对象和 Math 对象不一样,Date是一个构造函数,必须先对象实例化,才能使用其中具体方法和属性(即 new一下)
var 实例化的对象名 = new Date(实参)
注意:无实参获取的是当前时间,有实参传递过去的是自定义时间(必须加引号,且为2021-12-9 12:0:0 的形式)
-
获取年:对象名 . getFullYear();
-
获取月:对象名 . getMonth()+1;
注意:月份返回的值比实际月份小1,所以调用时需要手动加1
- 获取日:对象名 . getDate();
注意:将获取日的返回值赋值给一个变量,这个变量名(一般是dates)不能和实例化的对象名(一般是date)相同,避免变量名取名重复
- 获取周几:对象名 . getDay();
尤其注意:由于周一至周六返回的是1-6,周日返回的是0,所以考虑把 0-7 当作索引值,创建一个周日到周六的数组
容易混淆:Date是日期,Day是周几
- 获取时:对象名 . getHours();
- 获取分:对象名 . getMinutes();
- 获取秒:对象名 . getSeconds();
注意:可利用 moment.js 插件,快速获取年月日,查阅中文文档即可使用
利用Date对象实现倒计时
已知对象实例化有无实参决定了获取的是当前时间还是自定义时间,那么可利用这个规律分别获取当前时间和未来时间
-
var 现在(一般取变量名now) = new Date();
-
var 未来(一般取变量名after) = new Date(实参:自定义时间,选择未来的某一天);
-
计算出总毫秒数:var 总毫秒数(一般取变量名time)=(未来-现在)/1000;
-
利用公式得出剩余天数、剩余小时、剩余分钟,以及剩余秒数
var d=parseInt(times/60/60/24); //获取剩余天数,通常用变量名d接收
d<10?d='0'+d:d=d; //利用三目表达式对最后的值进行补0操作
var h=parseInt(times/60/60%24); //获取剩余小时,通常用变量名h接收
h<10?h='0'+h:h=h; //利用三目表达式对最后的值进行补0操作
var m=parseInt(times/60%60); //获取剩余分钟,通常用变量名m接收
m<10?m='0'+m:m=m; //利用三目表达式对最后的值进行补0操作
var s=parseInt(times%60); //获取剩余秒数,通常用变量名s接收
s<10?s='0'+s:s=s; //利用三目表达式对最后的值进行补0操作
常用的数组内置对象方法
-
数组名 . push():给数组的末尾添加新的数组元素
-
数组名 . unshift():给数组的开头添加新的数组元素
注意:push()和 unshift()这两个给数组追加元素的方法,返回的是长度
-
数组名 . pop(括号里没有参数):删除数组的最后一个元素,一次只能删除一个
-
数组名 . shift(括号里没有参数):删除数组的第一个元素,一次只能删除一个
-
数组名 . splice(从哪个位置开始,删除几个元素):删除数组中一个或多个元素
注意:splice 方法一般配合删除操作,即 splice(删除位置即下标索引,1)
注意:实际上 splice 方法有三个参数,splice(从哪个位置开始,删除几个元素,新增的数据),如果第二个参数为0,则是新增元素
-
数组名 . reverse():对原数组进行翻转
注意:以上所有方法会从根本上改变原数组,是原数组发生变化,而不仅仅是当时输出的结果进行了变化
数组排序的简单方法(极其重要)
可直接调用该方法对原数组进行排序,原数组会发生改变
数组名.sort(function (a, b) {
return a - b; //升序
return b - a; //降序
})
以上所有数组方法,包括数组排序,共七种,被称为数组变更方法,都能触发 Vue 中的 v-for 更新(即 实时渲染数据到页面)
常用的字符串内置对象方法
-
截取字符串:字符串变量名 . substr(' 起始的位置索引号 ',截取的字符数)
-
把字符串转为数组(以字符串的分隔符作为分隔):字符串变量名 . split(‘ 分隔符 ’)
-
把数组转化成字符串(拼接在一起):数组名 . join(' 分隔符 ')
注意:join 里面的参数就是转换好的字符串的分隔符,不传递参数,默认以逗号隔开,一般会利用循环将字符串形式的标签依次追加到同一个数组变量中,然后使用 join 方法将这个数组转换成字符串,通常会使用 join(' ')实现无缝拼接
- 替换字符串:字符串变量名 . replace(被替换的字符串或者正则表达式,要替换的新字符串)
注意:默认只替换第一个匹配的字符串,如果想全局匹配替换多个(g)并忽略大小写(i),需要使用正则表达式,并在这个正则表达式后面加上 gi,如果要替换多种不同的字符串,在正则表达式里面使用 | 符号隔开,常用于敏感词替换,如:字符串变量名 . replace(/ 敏感词1|敏感词2/g,要替换的新字符串)
- 去除字符串两端空格:字符串变量名 . trim()
注意:去除字符串两端空格常使用于表单校验(和 input 框相关),一般会在 input 框内输入文字时做条件判断,如果单纯的判断输入文本不能为空,当只输入空格时依然会显示输入成功,因此判断条件可改成,输入的文本去除两端空格后也不能为空才比较严谨,同理,从 input 框内取出的文字,也需要去除两端空格
- 检索字符串中,是否存在某个字段(字符串形式):字符串变量名 . indexOf( ‘ 需要匹配的字段 ’ )
注意:该字符串变量名也可以是数组形式,里面存放的是字符串类型的数据,同样可以使用 indexOf 方法,当字符串中包含该字段,则返回它在该字符串中首次出现的位置(即索引值),否则返回 -1,常作为判断条件,如检索某个字段是否在白名单内,当大于(或者不等于) -1 时,表示存在
注意:由于字符串的不可变性,以上方法都不会改变原字符串,需要变量接收
replace 方法的进阶使用:正则替换最终版
基础用法:字符串变量名 . replace (被替换的字符串或者正则表达式,要替换的新字符串)
第一个参数,如果是普通字符串,默认只会替换字符串中第一个与之匹配的内容,后面的内容不会再替换
第一个参数,如果是正则表达式,可以使用 gi 全局替换多个并忽略大小写,但正则表达式里面不可以使用变量名(被替换内容是动态改变、不固定的)
因此可以采取正则替换的最终解决方案:
- 利用 RegExp 正则表达式构造函数,创建一个正则对象
const reg = new RegExp(变量形式的被替换的字符串, 'gi')
注意:第一个参数是被替换的内容,这里可以书写变量,第二个参数是匹配模式,即全局匹配多个并忽略大小写
- 将 reg 作为整个被替换的内容,供 replace 方法使用
字符串变量名.replace(reg, 要替换的新字符串)
注意:第二个参数是要替换的新字符串,可以是模板字符串,携带变量使用
白名单的使用详解
- 通常我们会准备一个数组,用于保存白名单信息,形式一般如下:
let 白名单数组名 = [ " 字段名 ",值 ]
- 然后使用 includes 方法或者 indexOf 方法检测是否包含某个数据,并作为判断条件:
// includes 方法
if ( 白名单数组名.includes('字段名'或 值) === true ) {
// 如果包含该字段名或值,则代表存在,返回true,(ture === true)成立,执行某些操作
}
// indexOf 方法
if ( 白名单数组名.indexOf('字段名'或 值) !=== -1 ) {
// 如果包含该字段名或值,则代表存在,返回索引值,(索引值 !=== -1)成立,执行某些操作
}
数组去重的三种方法
一、使用 forEach 搭配 find 方法
// 1.准备一个新数组,用于存放去重后的数据
let 新数组 = []
// 2.使用forEach方法遍历大数组中的所有数据(注意:大数组是数据多的那个数组)
this.大数组.forEach(大数组的item => {
// 3. 当遍历大数组的每一个当前项时,都会使用find方法遍历小数组的每一项(注意:小数组是数据少的那个数组)
let 重复成员 = this.小数组.find(小数组的item => {
// 4.将小数组中的每一项与大数组的当前项进行对比,如果小数组中的当前项等于大数组中的当前项,则代表两者相同,将这个重复成员保存
return 小数组的item.xxx === 大数组的item.xxx
})
// 5.如果这个重复成员变量为 undefined,则代表当前大数组的item项没有重复,此时取反,if括号里就为true,执行里面代码
if(!ret){
// 6.将当前大数组的item项追加到新数组中
新数组.push(大数组的item)
}
})
二、使用 filter 搭配 find 方法
let 新数组 = 大数组.filter((大数组的item) => {
// 过滤器过滤:把(!重复成员)的每一项单独保存成新的数组
return ! 小数组.find((小数组的item) => {
// 将小数组中的每一项与大数组的当前项进行对比,如果小数组中的当前项等于大数组中的当前项,则代表两者相同,返回的是重复成员
return 小数组的item.xxx === 大数组的item.xxx;
});
三、使用 lodash 插件的 differenceBy 方法
-
下载 lodash 包:npm i lodash
-
按需加载 differenceBy 方法(数组对象去重):import { differenceBy } from 'lodash'
-
数组去重方法:let 新数组 = differenceBy ( 大数组,小数组,” 数组对象的属性名(判断条件) “)
数据的单项添加并去重
与上述三种去重方法不同,前三种方法是针对两个成型的、有一定数据内容的数组进行对比去重,而此方法仅适用于单独添加某一项时并去重
// 每次添加数据前,先查找数组中是否存在有没有相同数据,保存它的索引号
const index = this.数组名.indexOf(某一项单个的数据)
if (index !== -1) {
// 如果存在相同数据,通过索引将该数据删除
this.数组名.splice(index, 1)
}
// 最后再将该数据(某一项单个的数据)添加到数组的最后面(或最前面)
this.数组名.push/unshift(val)
Web API—DOM文档对象模型
一、获取元素(即DOM对象,又称节点)的方式:
- 一次获取一个元素:document . querySelector(' 选择器 ');
- 一次获取多个相同的元素:document . querySelectorAll(‘ 选择器 ‘,因为选择的是一类元素,一般是标签名);
注意:querySelector获取元素的方法,选择器的格式必须规范,必须加引号,获取类名写 ’ . 类名 ‘,获取id写 ’ # id名 ‘,获取标签写 ’ 标签名 ‘,且支持复合写法
注意:如果某个标签取了 id 名,则可以不需要通过 DOM 获取该元素,直接使用 id 名就可进行 DOM 操作
注意:document . querySelectorAll(‘ 选择器 ’)返回的是伪数组,需要通过 dom对象[索引号] 取值
尤其注意:document . querySelectorAll(‘ 选择器 ’)哪怕选中的标签只存在一个,它返回的也是伪数组,只不过里面仅有一个标签
注意:也可通过 dom对象 . querySelector(All)获取dom对象里面的子元素
- 获取body元素:document . body
- 获取html元素:document . documentElement
- 获取事件具体触发的元素:e . target
注意:e . target 是当前事件具体发生的那个元素,点击哪个元素,e . target 就指向哪个元素,但事件是给父亲添加(强调,利用事件委托)
-
获取某个已获取的dom对象的父节点:dom对象 . parentNode
-
获取某个已获取的dom对象的子节点:dom对象 . children [ 索引号 ]
尤其注意:dom对象 . children [ 索引号 ] 方法返回的是伪数组,一定要加索引号,且该方法必须页面存在该子元素,才能获取,否则报错
二、事件的使用
(一)事件类型
onclick点击,onmouseover鼠标移入,onmouseout鼠标移出,onfocus获得焦点,onblur失去焦点,onmousemove鼠标移动,onmouseup鼠标松开,onmousedown鼠标按下,onkeydown键盘按下,onkeyup键盘松开
注意:mouseover 只会触发一次,但是 mousemove 会频繁触发
注意:可通过打印 e . keyCode 得到键盘按下的返回值(返回值为 ASCII 码),e . keyCode 多作为判断条件使用,回车是13(常用)
注意:跟文字相关使用 onkeyup,跟游戏相关使用 onkeydown
关于input框为什么必须要使用键盘事件监听的原因:input框如果不使用键盘事件监听,它的value值在页面刷新后是固定不变的,而使用键盘事件监听,每一次敲击键盘,它的value值都在改变,因而可以实现实时更新的效果
额外补充:mouseenter鼠标移入,mouseleave鼠标移除(两者不会冒泡,但是不建议使用,因为兼容性问题,可使用over和out再阻止冒泡代替)
额外补充:onresize屏幕尺寸变化(一般用于检测屏幕尺寸改变时,html的 fontSize 跟着改变,即 flexible.js 原理的实现)
额外补充:ondblclick鼠标双击,常用于实现双击编辑文字的功能,详见《 十四、双击编辑文字的核心思路 》
(二)传统事件
-
通过获取元素的方式获取事件源,即dom对象
-
给获取的这个元素(事件源,又称dom对象)添加触发事件的类型:dom对象 . 事件类型
-
事件处理程序由函数完成(即事件处理函数),并赋值给获取的事件源:dom对象 . 事件类型=function(){ }
(三)高级事件(事件侦听注册(绑定)事件):
dom对象 . addEventListener(' 事件类型 ',function(){ })
注意:里面的事件类型必须加引号,且不带on,这种方式可以给同一个元素添加多个同一类型的事件,可以连续触发并依次执行
(四)事件的复用【尤其重要】
相同的事件类型,有不同的事件处理函数,涉及到事件的复用,需要前往原来的事件当中加一个条件判断
(五)关于this的使用
事件处理函数中的 this 指向当前事件函数所在的元素(即:事件源里面的 this 代表的就是事件源本身)
终极理论:谁调用的函数,this就指向谁(this永远指向当前函数调用者)
(六)关于for循环嵌套事件函数引起的问题
事件处理函数是一个闭包,函数内部无法获取外部循环中准确的变量值(注意是循环中的变量,其它的dom对象和固定的变量值可以正常访问)
如果需要获取外部循环的变量值并可以参与正常的循环,可使用如下代码包裹整个事件:
(function (arg) {
dom对象.事件类型 = function(){ }
})(for循环中的变量i)
注意:事件处理函数里面虽然可以正常使用 i 这个变量,但不能写 i,因为arg就代指外部循环中的变量 i,所以函数里面使用变量必须写成 arg 的形式
可利用如上方法,解决 Tab 栏切换:
//获取所有button按钮
var btns = document.querySelectorAll('.tab_btns input');
//获取所有对应的盒子
var divs = document.querySelectorAll('.tab_cons div');
for (var i = 0; i < btns.length; i++) {
//利用闭包解决无法访问外部循环变量的问题
(function (arg) {
//遍历所有按钮,并添加点击事件
btns[i].onclick = function () {
for (var j = 0; j < btns.length; j++) {
//排它思想,先让所有按钮恢复默认
btns[j].className = '';
//再让其它盒子隐藏
divs[j].className = '';
}
//再单独给点击的按钮设置高亮(类名是active)
this.className = 'active';
//对应的盒子也可以显示出来(类名是current)
divs[arg].className = 'current';
}
})(i)
}
(七)阻止默认行为
当给一些特殊元素添加事件时,如a标签,由于a标签是自带点击跳转的功能,此时可以阻止它的默认行为,在事件处理函数中,括号里的形参为 e,并在这个事件处理函数中加入如下代码:e . preventDefault;
(八)事件嵌套事件引起的问题(尤其重要)
事件嵌套事件,会使每次触发外层事件时,都会给内层事件重复绑定事件,如果内层事件是高级侦听事件或者 jQuery 中带 on 的事件,这类事件有一个特性,即可以多次绑定,依次执行,因此,事件嵌套事件,会导致内层事件被触发时多次执行,且执行的次数等于外层事件触发的次数,有以下解决方案:
- 每次触发外层事件,先移除内层事件,jQuery中使用 jQuery对象 . off(‘ 事件类型 ’),原生 js 中使用 dom对象 . 事件类型 = null,这样便可以保证,每次外层事件触发时,都会先清除所有的内层事件,然后再重新绑定内层事件,因此,无论触发多少次外层事件,内层事件始终只会被绑定一次
- 一般事件嵌套事件出现的情况,都是因为动态创建的元素,无法直接获取,需要在创建该元素的函数内部获取,但事件嵌套事件却又带来一些问题,因此并不建议,可以采取事件委托来处理问题
(九)事件叠加复用引起的问题(尤其重要)
在 css 中,通常会给多个标签同时设置相同的样式,如果其中有一个标签的样式特殊,会单独给这个标签设置或修改某一行样式,不需要再重新书写所有的样式,这便是 css 样式的叠加复用,但是在事件中,不可以为了简单省事而去叠加复用,这样相当于事件重复,会造成一系列问题,必须分开单独设置,保证完全不冲突
三、事件的移除(解绑或删除)
(一)传统事件移除
dom对象 . 事件类型 = null;
(二)高级事件移除(事件侦听方式解绑或删除)
前提:一个高级事件想要移除,在绑定时需要将事件处理函数拆分出来
高级事件绑定时的拆分步骤:
第一步:dom对象 . addEventListener(' 事件类型 ' ,函数名)
第二步:function 函数名{ }
解绑:dom对象 . removeEventListener(' 事件类型 ',函数名)
四、操作dom对象的属性(方法)
格式:dom对象 . 属性(方法)
-
对元素内容修改(替换):dom对象 . innerHTML= ‘ 内容 ’(某个可直接调用且有返回值的函数 或 ‘文字’+变量+‘文字’的形式)
注意:变量没有引号,普通文字有引号,JS 中大多如此
注意:使用 innerHTML 属性,设置内容时会识别 HTML 标签
尤其注意:若想追加内容而不覆盖原有内容,使用+=(即 dom对象 . innerHTML += ‘ 内容 ’)
注意:innerHTML,除了可以修改内容,也可进行读取操作(如,直接打印 dom对象 . innerHTML,获取dom对象里的内容)
注意:dom 对象 . innerHTML = ‘ ’;表示清空某个 dom对象(容器)里面所有的内容
面试题:三种动态创建内容的方式,哪种效率最高?答:innerHTML使用数组形式创建内容的效率最高
-
常用元素和表单元素属性:src、href、style、type、value、checked、selected、disabled、className
特别注意:style 属性只能获取行内样式的值,所以一般只通过style修改值,且通过 style . 属性 = ‘ 属性值 ’ 设置的属性值不能有 - 这个符号
尤其注意:通过 dom对象 . style . cssText = "css样式;css样式;css样式";可以设置多个样式,且以分号隔开,且格式与正常书写的 css 样式一致
注意:checked、selected、disabled 这三个属性的值为布尔值,dom对象 . disable = true;代表禁用,checked常用来判断是否为true,表示勾选状态
注意:className = ‘ ’,引号里面为空,表示移除类名
尤其注意:在外部 js 文件中,使用某个图片或其它资源的路径一定要注意严谨性,虽然 live server 运行页面不会出错,但实际上是因为 live server 没有那么严谨,由于 js 文件是被 HTML 页面引入,所以在 js 中使用路径时,也是从当前的 HTML 页面出发寻找路径,因此要特别注意路径层级关系
-
reset()方法,可以一次性批量清空表单里所有 input 框的内容(重置表单),尤其注意,该方法必须给 form 标签这个DOM对象对象使用,且它是原生 js 独有的方法,在 jQuery 中使用需要先把 jQuery对象转换为 DOM对象
五、自定义属性值
(一)定义自定义属性
格式:dom对象 . setAttribute(‘ 属性名 ’,‘ 属性值 ’)
注意:属性名和属性值必须都要加引号,如果属性值是变量则不需要加引号
(二)自定义属性获取方法
自定义属性:dom对象 . getAttribute(‘ 属性名 ’)
注意:与固有属性获取方法不同,注意区分,固有属性获取方法是:dom对象 . 属性名
特别重要:自定义属性获取的是字符串形式,在作判断条件时,尤其注意,需要加引号
(三)重新修改自定义属性的值
仍然需要使用 set 方法,dom对象 . setAttribute(‘ 属性名 ’,‘ 新的属性值 ’)
(四)移除自定义属性
dom对象 . removeAttribute(‘ 属性名 ’)
六、动态创建节点元素
var dom对象 = document . createElement(‘ HTML标签 ’)
注意:createElement 创建的是一个真正的标签,需要用一个dom对象来接收这个创建的新 HTML 标签
尤其注意:一般动态创建的元素会搭配插入节点的操作,否则这个动态创建的元素只是存在内存中,看不见
尤其重要:动态创建的元素,想要进行元素获取,需要在函数里面才能获取该元素,如果是对该元素添加事件,可以利用事件委托获取该元素
尤其注意:并不是所有动态创建的元素都不能直接获取,如果页面刷新时便已经动态生成好的元素,是可以直接获取的,因为页面已经存在该元素,但如果是需要进行某些操作,后面生成的元素,则需要采取事件委托获取该元素
七、动态插入节点元素
(一)将子元素插入到父元素里面,且插入到末尾:
父元素 . appendChild(被插入的子元素:可以是某个动态创建的节点元素)
注意:原生 js 创建的节点元素需要通过 innerHTML 方法来存入模板字符串,然后再插入,而高级插入法则可以直接插入字符串形式的 HTML代码,方法如下:
-
定义:var 变量名 = 模板字符串
-
高级插入方法:父元素 . insertAdjacentHTML(' beforeend ',被插入的变量名或含 HTML代码的模板字符串)
(二)将子元素插入到父元素里面,且是某个目标元素的前面:
父元素 . insertBefore(被插入的子元素,目标元素)
尤其注意:动态插入节点元素,如果插入的是一个页面上已经存在的元素,会出现剪切效果
(三)删除当前元素:
dom对象 . remove ( )
注意:括号里无内容
容易混淆:remove ( ) 这个方法可以直接删除当前元素,注意和删除自定义属性以及删除高级事件区分
-
删除元素:dom对象 . remove ( )
-
删除自定义属性:dom对象 . removeAttribute(‘ 属性名 ’)
-
删除高级事件:dom对象 . removeEventListener(' 事件类型 ',函数名)
八、动态克隆节点元素
被克隆的节点元素 . cloneNode(true)
容易混淆:注意与 jQuery 中的克隆写法区分,jQuery 中使用 jQuery对象 . clone(true);
尤其重要:克隆方法(无论是 jQuery 还是原生 js 写法)的括号里如果有 true,会完整的复制自身以及所有的子元素,尤其注意会将功能也复制下来
注意:一般轮播图实现无缝滚动的时候,需要复制第一个 li 标签插入到最后,建议使用动态克隆节点元素,方便维护,代码如下:
var last = ul . children[ 0 ] . cloneNode(true);
九、排他算法(重要思想)
给多个dom对象添加事件,在每个事件里面,先用 for 循环遍历所有的dom对象,将它们的全部样式清除,然后再单独给自己设置样式(注意:顺序不能颠倒)
简单记忆:首先排除其他人,然后才设置自己的样式
注意:排他算法常使用于 Tab 栏切换(类似场景:一组元素,点击某一个显示高亮,其它元素恢复默认)
十、DOM事件流(重要理论)
- 捕获阶段:注册事件从外层标签向内层标签获取目标
- 冒泡阶段:当获取到目标后,事件响应后的处理程序会从内到外依次冒泡显示结果(只针对相同的事件类型)
注意:冒泡会导致一些问题,如点击事件,当点击子元素的时候,父元素也同时被点击
十一、冒泡的进阶使用
(一)阻止冒泡
冒泡,有好处也有坏处,如果不需要进行冒泡,则可以阻止冒泡:
在事件处理函数中,括号里的形参为 e,并在这个事件处理函数中加入如下代码:e . stopPropagation();
注意:写在事件处理函数()里的第一个形参就是事件对象,一般用 e 表示
注意:多个子事件同时需要阻止冒泡,由于每个子事件都要添加阻止冒泡的代码,过于繁琐,可以考虑给它们的父元素设置相同类型的事件,并添加阻止冒泡的代码,利用冒泡冒到父元素身上,然后父元素执行阻止冒泡的程序
(二)事件委托
原理:不是给每个子元素都设置事件监听器,而是给它们的父元素设置事件监听器,然后利用冒泡原理,把子元素需要触发的事件委托给父亲来完成,例如点击事件,利用事件对象中的 e . target 找到当前点击的子元素,当点击任意一个子元素时,因为事件会冒泡到父元素上,就会触发父元素的点击事件
尤其重要:如果父元素里的所有子元素通过元素获取变成伪数组,并循环添加事件,那么后面动态创建的元素并不在此之列(没有绑定事件),此时如果想要让动态创建的元素也可以触发事件,那么就把事件委托给父元素
十二、循环精灵图的使用
前提条件:一张竖排显示、每个小图标之间的间距相等的精灵图
- 在 CSS 代码中,先给所有 li 标签设置背景图片为精灵图
- 计算出精灵图中第一个小图标的坐标和第二个小图标的坐标的距离(两者相减即可得出)
- **在 JS 代码中,获取所有 li 标签元素,然后通过操作dom对象,循环给每个 li 元素的 backgroundPosition 设置为:' 0 - ' + index + ' px ' **
- *index 是一个变量,等于 i 小图标之间的距离
var lis = document.querySelectorAll('li');
for (var i = 0; i < lis.length; i++) {
var index = i * 小图标之间的距离;
lis[i].style.backgroundPosition = '0 -' + index + 'px'
}
十三、“ 点击高亮,再点击恢复默认 ” 的实现原理
两个状态的反复切换,一般设置一个 flag 初始值,然后将 flag 作为一个中间变量,if else 作为条件判断,每判断一次,flag 的值发生改变,但是多个元素利用 for 循环同时实现这种反复切换的效果,需要给每个元素都设置自己的 flag 初始值,可通过自定义属性实现,然后再利用 this . flag 只改变自己的中间变量值
十四、双击编辑文字的核心思路
获取当前需要双击的元素,当双击该元素时,在该元素里生成一个 input 文本框,当失去焦点或者按下回车键,把 input 文本框的 value 值赋给原先的元素即可
//双击编辑文字
Dom对象(需要双击的元素,如span).ondblclick = function () {
//获取span标签里的内容
var text = this.innerHTML;
//修改span标签里的内容
this.innerHTML = '<input type="text">'
//获取span标签里的input元素
var input = this.querySelector('input');
//将span标签里的内容赋给input的value
input.value = text;
//优化用户体验,双击后选择input框文字
input.select();
//当移出焦点,将input的value值给span标签
input.onblur = function () {
this.parentNode.innerHTML = this.value;
}
//当触发回车,自动移出焦点
input.onkeyup = function (e) {
if (e.keyCode == 13) {
this.onblur();
}
}
}
十五、event 事件对象(单独总结)
事件对象一般用 e 表示,写在事件处理函数括号里的第一个形参处,存放的是当前事件类型里的全部属性
e . target:鼠标当前点击的元素
e . pageX或Y:鼠标相对于浏览器整个页面的坐标
十六、JavaScript 操作伪元素
在 js 中,无法通过dom对象获取伪元素,但是可以通过某些办法,控制伪元素的样式,在css中设置一个 “ . 类名::before{ css样式 } ” ,利用 className 方法给当前伪元素的真实标签添加类名或移除类名
Web API—BOM文档对象模型
BOM必知:window 是 JavaScript 中的顶级DOM对象
窗口加载事件
window.onload=function(){ 将全部的 js 代码包裹在里面 }
注意:窗口加载事件,会待页面全部渲染好再执行 js 代码,但页面资源较多时,需加载很长时间,一般使用外部 js 文件时,会使用窗口加载事件
延时器和定时器
延时器(只会执行一次):var 延时器名称 = setTimeout(function(){ },毫秒数)
定时器(每隔一定时间执行一次):setInterval(function(){ },毫秒数)
注意:用一个变量名接收这个延时器(定时器),这个变量名就是延时器(定时器)名称
关闭延时器或定时器:clearTimeout(延时器名称),clearInterval(定时器名称)
同步异步(重要理论)
定义:如果当前代码不能立即执行,那就是异步代码;如果可以立即执行,那就是同步代码
执行顺序:js 会把同步代码放到执行栈中,把异步代码放到异步队列中,然后优先执行执行栈中所有的同步代码,等到所有同步代码都执行完毕,再去异步队列中查看是否有异步任务准备好,如果有准备好的异步任务,就会把该异步任务拖到执行栈中执行,执行完再回到异步队列查看有没有准备好的异步任务,不断循环
注意:事件,延时、定时器,ajax请求都属于异步任务
尤其注意:普通函数不是异步任务,因为它是立即调用,立即执行,不会延迟
location 常使用的三个属性
location . href:获取当前页面地址或者跳转指定页面
location . search:获取 ? 号后面的内容(包含 ?),用于参数传递
location . hash:获取 # 号后面的内容(包含 #)
获取元素宽高、屏幕宽高以及元素距离整个页面的距离
获取某个元素距离父元素(有定位)的上边或左边的距离(无单位):dom对象 . offsetTop 和 dom对象 . offsetLeft
注意:若父元素无定位,获取的是距离文档上边和左边的距离
获取某个元素的宽度和高度(无单位):dom对象 . offsetWidth 和 dom对象 . offsetHeight(包含padding 和 border)
尤其重要:offset 系列是只读属性,而style又无法获取非行内样式的值,可以两者搭配,获取属性用 offset,更改属性用 style
获取屏幕宽度:window . innerWidth
获取屏幕高度:window . innerHeight
关于滚动条事件和滚动轮事件
(一)滚动条事件
事件类型:scroll(一般使用高级侦听事件)
滚动条向上滚动的距离:
- scrollTop:针对的是某个盒子里面的内容向上滚动的距离
- window . pageYOffset:针对的是整个页面向上滚动的距离
注意:scrollHeight 是实际内容的高度,内容没有超出盒子,就是盒子的高度
(二)滚动轮事件
鼠标滚动轮每滚动一下(幅度很小),便会触发一次滚动轮事件,常用于全屏滚动,利用 move.js 运动函数,再利用节流阀思想,使得鼠标每滚动一次(无论幅度多大),页面就会下翻一张,以下是兼容谷歌和火狐的判断滚动轮向上还是向下的代码:
//综合谷歌和火狐的兼容写法
document.onmousewheel = gd;
document.addEventListener('DOMMouseScroll', gd);
function gd(e) {
var flag = '';
// e.wheelDelta存在,说明当前是谷歌环境
if (e.wheelDelta) {
flag = e.wheelDelta > 0 ? true : false;
} else { // 是火狐环境
flag = e.detail < 0 ? true : false;
}
console.log(flag) //向上滚动是true,向下滚动是false
}
基于时间的运动算法
优点:可以利用时间戳,规避定时器暂停的bug,代码如下:
//获取一个盒子,用来作动画移动
var div = document.querySelector('div');
//获取一个按钮,点击实现盒子移动的动画效果
var btn = document.querySelector('button');
// 运动的总距离。
var totaladdr = 1000;
// 运动的总时间。
var totaltime = 2000;
btn.onclick = function() {
// 记录当前运动的开始时间
var begintime = new Date();
// 书写一个定时器,在定时器中不断获取当前时间。
var timer = setInterval(function() {
// 获取当前时间
var curtime = new Date();
// 把当前时间减去运动的初始时间,得到的就是目前已经花费的时间
var during = curtime - begintime;
// 当运动所花的时间超过运动所需的总时间时,就应该关闭定时器停止动画
if (during >= totaltime) {
clearInterval(timer);
//并且盒子所在的位置应该是距离左侧1000
div.style.left = 1000 + 'px';
return;
}
// 盒子实时移动的距离=已经话费的时间/总时间*总距离
div.style.left = during / totaltime * totaladdr + 'px';
}, 30);
}
轮播图实现的主要原理
基于 move.js 运动函数,实现轮播图滚动
- 点击小圆圈滚动轮播图:ul 移动的距离等于小圆圈的索引号乘以图片的宽度即可
- 点击左右按钮无缝切换:把第一张图片克隆并添加到末尾,然后作条件判断
- 轮播图自动滚动:其实就是自点击,利用定时器,每隔两秒钟点击一下右按钮,即定时器包裹 右按钮 . click ( );
注意:可使用 swiper 官网插件,快速实现轮播图
节流阀的使用
为了阻止用户点击过快,导致动画连续闪烁的播放,可以在事件外面声明 flag = true,在事件处理函数里加一个判断条件,这个判断条件将所有程序包裹起来,事件触发的第一件事情是先判断 flag 是否为 true,如果是 true,则立马将 flag 设置成 false,然后正常执行里面的程序,此时因为 falg 为 false,再次触发该事件会失效,最后给 move 运动函数增加一个回调函数,给 flag 设置回 true(即动画结束后 flag 才变回 true),该事件触发又可以生效
防抖的原理
- 当事件触发,延迟 n 毫秒后再执行回调函数
- 如果在 n 毫秒内,没有再次触发事件,那么就执行该回调函数
- 如果在 n 毫秒内再次触发事件,那么当前计时取消,重新开始计时
注意:对于短时间内连续触发的事件,防抖的含义就是让某个时间期限内,事件处理函数只执行一次
懒加载(按需加载)原理:
利用滚动条事件,当页面向上滚动的距离 + 屏幕高度 > 图片距离页面顶端的距离,则判断为进入可视区,那么就把图片的临时地址改变为真实地址
错误处理
只要一段代码被进行了错误处理,即便代码有错误,它会输出错误结果,但并不会影响后续代码的执行
try {
if(判断条件){
//人为定义一个指定内容的错误,throw是js关键词,用来抛出指定的错误
throw new Error("这是通过错误对象创建的自定义错误");
}
} catch (error) {
console.log(error)
} finally { 不论执行try还是catch,都会执行finally里面的代码,常用于请求前开启加载状态,请求结束后关闭加载状态 }
尤其注意:try catch(错误处理)只能包裹同步任务,如果需要包裹异步任务,需要将异步任务先转换为同步任务(必须通过 promise,不能是 async/await)
注意:异步转换成同步,必须使用 promise,不能是 async/await,原因是,async/await 语法只能修饰 promise 对象
尤其注意:如果 try catch 包裹的是 axios 请求,由于 axios 本身属于异步任务,而它返回的又是一个 promise 对象,因此直接使用 async/await 语法即可,await 修饰 axios 请求,async 修饰该请求的父级函数,注意 async 一定是给 try 外面的函数修饰,而不是 try,切记混淆
尤其注意:被 async 修饰,就会变成异步任务,如果需要进一步转换成同步任务,再使用一次 async/await 语法即可
深入理解:promise 对象在没有进行 then 操作之前,仍然属于异步任务,这也就是为什么 axios 请求返回的是个 promise 对象,但仍然需要 async/await 语法修饰的原因,await 就相当于替代了 then 的操作,如果需要捕获异常,由于 async/await 语法修饰的 promise 对象没有 catch,所以通常采取 try catch 包裹进行捕获异常
深入理解 try catch 中 catch 捕捉到的错误信息
通过 console.dir (error) 打印错误信息,会以对象形式打印捕捉到的错误,且捕捉到的错误都属于爆红错误,爆红错误在实际开发中通常有两种类型,一种是语法报错(不包含 response),另一种就是接口报错,接口报错会返回一个错误对象,其中包含 response,且 response 中有一个 status(错误状态码),因此我们可以将 error.response.status == xxx 作为判断条件,从而执行某些操作,但这样的写法不严谨,error.response.status == xxx 表达式的写法会默认左边的对象属性名存在,而如果出现语法错误,由于 response 不存在,导致该判断条件无法正常执行,因此需要使用逻辑与,增强代码的健壮性
if (error.response && error.response.status === xxx) { 执行某些操作 }
本地存储技术
(一)localStorage 存储
设置:localStorage . setItem(' 属性名 ',属性值)
获取:localStorage . getItem(‘ 属性名 ’)
删除:localStorage . removeItem(‘ 属性名 ’)
删除所有数据:localStorage . clear();
特点:永久保存数据,如果不是手动删除,永远不会消失,且可以跨页面传递,任何页面都可以访问这个数据
(二)sessionStorage 存储
使用方法同理,但关闭浏览器以后,马上删除数据,且不能跨页面传递
注意:无论是 localStorage 存储还是 sessionStorage 存储,本地存储技术存储的是字符串,由于本地存储不能存储数组或对象,JSON . stringify()方法可以把数组或对象转换成字符串存入本地存储,因为存进去的是字符串,所以取出来也是字符串,可以通过 JSON . parse()方法再次转换成真正的数组或对象
注意:本地存储技术可实现勾选 “ 记住选框 ”,就能记住用户名、密码的功能
关于移动端 click 事件 300ms 延时的解决方案
- 禁用缩放,点击 VS code 设置中的 “ 用户代码片段 ” 选项,复制视口标签文件中的代码到其中,新建 HTML 页面,输入 html + Tab 键即可快速生成
- 使用 fastclick.js 插件解决
移动端触摸事件
注意:移动端无需考虑 js 兼容性问题
事件类型:touchstart手指触摸,touchmove手指移动,touchend手指离开
属于移动端触摸事件的事件对象(e):
- e . touches:获取所有手指的列表(包括没有使用的手指)
- e . targetTouches:获取当前所有触碰了元素的手指列表
- e . changedTouches:获取手指离开的元素列表
深入分析拖拽在PC端、移动端的区别
PC端拖拽:
//获取div盒子
var div = document.querySelector('div');
//给div添加鼠标按下事件
div.onmousedown = function () {
//给整个文档页面添加鼠标移动事件,防止盒子脱离鼠标
document.onmousemove = function (e) {
//盒子的left值和top值和鼠标位置保持一致
//为了使鼠标在盒子的中间,让div的left值往左移动自己宽度的一半,top值往上移动自己高度的一半
div.style.left = e.pageX - div.offsetWidth / 2 + 'px';
div.style.top = e.pageY - div.offsetHeight / 2 + 'px';
}
}
//给div添加鼠标松开事件
div.onmouseup = function () {
//当鼠标松开时,移除鼠标移动事件
document.onmousemove = null;
}
移动端拖拽:
// (1) 触摸元素 touchstart: 获取手指初始坐标,同时获得盒子原来的位置
// (2) 移动手指 touchmove: 计算手指的滑动距离,并且移动盒子
// (3) 离开手指 touchend:
var div = document.querySelector('div');
var startX = 0; //获取手指初始坐标
var startY = 0;
var x = 0; //获得盒子原来的位置
var y = 0;
div.addEventListener('touchstart', function (e) {
//获取手指初始坐标
startX = e.targetTouches[0].pageX;
startY = e.targetTouches[0].pageY;
x = this.offsetLeft;
y = this.offsetTop;
});
div.addEventListener('touchmove', function (e) {
//手指的移动距离 = 手指移动之后的坐标减去手指初始的坐标
var moveX = e.targetTouches[0].pageX - startX;
var moveY = e.targetTouches[0].pageY - startY;
//盒子的位置 = 盒子原来的位置 + 手指移动的距离
this.style.left = x + moveX + 'px';
this.style.top = y + moveY + 'px';
e.preventDefault(); //阻止屏幕滚动的默认行为
});
碰撞检测的核心思路
因为大盒子的固定位置不变,先在外部获取大盒子的上、下、左、右边框分别距离屏幕左端或屏幕顶端的距离,然后搭配拖拽,在鼠标松开事件里面,获取小盒子的上、下、左、右边框分别距离屏幕左端或屏幕顶端的距离,如果小盒子右边框到屏幕左端的距离小于大盒子右边框到屏幕左端的距离,或者小盒子上边框到屏幕顶端的距离大于大盒子下边框到屏幕顶端的距离,或者小盒子左边框到屏幕左端的距离大于大盒子右边框到屏幕左端的距离,或者小盒子下边框到屏幕顶端的距离小于大盒子上边框到屏幕顶端的距离,都表示没有碰到盒子
jQuery 的基本语法规范及内置方法的使用
jQuery 本质上是一个 js 文件,封装了很多 js 原生代码,是一个DOM对象库,使用前必须引入 jQuery.js 文件
注意:移动端通常使用 Zepto (和 jQuery 语法基本一致)
入口函数
$(function(){ 代码程序 })
注意:入口函数等同于原生 API 中的 domcontentloaded 加载事件,会等待页面结构渲染完成以后才会触发,代码程序写在入口函数里面
jQuery 获取元素的方式
- 获取某个或多个元素(即 jQuery对象):$(‘ 选择器 ’)
注意:jQuery 方法获取的元素就是 jQuery 对象
尤其重要:由于 jQuery 是封装了各种函数的简化代码块,所以依据底层封装原理,$ 是 jQuery 的顶级对象,调用任何方法必须以 $ 开头
尤其重要:无论选择器选中的是标签、类名还是id名,返回的都是伪数组,此时需要使用 $(' 选择器 ') . eq(索引号)进行筛选
尤其注意: jQuery 对象不能使用原生 js 的属性和方法,如 reset()表单重置方法,当遇到某些特殊情况,此时需要将jQuery对象转换为DOM对象,即:jQuery对象 [ 索引号 ],此时可以通过索引号筛选选择器选中的元素
- 获取某个已获取的 jQuery对象的兄弟元素:jQuery对象 . siblings()
注意:siblings()括号中如果没有参数,就获取它的所有兄弟元素,如果传了参数,就只获取与该参数匹配的元素
尤其注意:实际开发中常出现兄弟元素中存在其它不同的标签,此时一定要写参数指定它的兄弟,否则会出问题
- 获取某个已获取的 jQuery对象指定的子元素:jQuery对象 . find(’ 选择器 ‘)
- 获取某个已获取的 jQuery对象的父元素:jQuery对象 . parent()
- 获取某个已获取的 jQuery对象的上一个兄弟:jQuery对象 . prev()
- 获取某个已获取的 jQuery对象的下一个兄弟:jQuery对象 . next()
尤其重要:在 jQuery 中,获取元素虽然可以简单直接、一步到位的全部获取,但由于 jQuery 中通常会操作兄弟元素,如果同时需要操控兄弟元素,而获取到的元素(伪数组)中的每一个都是单独被某个标签包含,那么就不能直接获取元素,兄弟操控就没有办法实现,一定是获取它们高一级的父元素,且是并列关系,通过 siblings 方法,找到其它的兄弟父元素,再以父元素为媒介,使用 find 方法寻找到子级元素并操作它们
jQuery 事件的使用
(一)传统事件
jQuery对象 . 事件类型(function(){ })
注意:里面的事件类型不加引号,且不带 on, 不能给同一个元素添加多个同一类型的事件,不具备连续触发并依次执行的功能
尤其注意:jQuery 事件中 this 的使用必须写成 $(this)
(二)高级事件
jQuery对象 . on(‘ 事件类型 ’,function(){ })
注意:该方式是开发中最常用的方式,事件类型必须加引号,具备给同一个元素添加多个同一类型的事件,可以连续触发并依次执行
多个事件连写,以对象的形式添加多个事件类型:jQuery对象 . on({ 事件类型:function(){ },事件类型:function(){ }...... })
(三)事件移除
jQuery对象 . off(‘ 事件类型 ’)
容易混淆:注意与原生 js 区分,原生 js 中使用 dom对象 . 事件类型 = null;
jQuery 专属事件:hover
jQuery 封装了一个只属于自己的事件,事件类型为 hover,语法规范如下:
jQuery对象 . 事件类型(function(){ },function(){ })
注意:两个 function(){ },一个代表移入,一个代表移出
属于 jQuery 自己的方法
-
设置单个 css 样式:jQuery对象 . css(‘ 属性名 ’,‘ 属性值 ’)
-
设置多个 css 样式:jQuery对象 . css({ 属性名:属性值,属性名:属性值 })
注意:属性值为数字不需要加引号,且设置宽高不需要加像素单位 px,属性值为字符串必须要加引号
-
添加类名:jQuery对象 . addClass(’ 类名 ‘)
-
删除类名:jQuery对象 . removeClass(’ 类名 ‘)
-
获取固有属性:jQuery对象 . prop(‘ 属性名 ’)
-
设置或修改固有属性:jQuery对象 . prop(‘ 属性名 ’,‘ 属性值 ’)
注意:prop方法只能操作固有属性,不能操作自定义属性
- 获取自定义属性:jQuery对象 . attr(‘ 属性名 ’)
- 设置或修改自定义属性:jQuery对象 . attr(‘ 属性名 ’,‘ 属性值 ’)
- 反复切换(添加、删除)类名:jQuery对象 . toggleClass(‘ 类名 ’)
尤其注意:反复切换可快速实现 “ 点击高亮,再点击恢复默认 ” 的效果
- 获取当前元素的索引值:$(this). index()
注意:给 jQuery对象添加事件时,由于隐式迭代,此时可使用 $(this). index()获取每个 jQuery对象的索引值,可实现 Tab栏切换
尤其注意:使用 index()方法拿到的是包括自己以及所有的兄弟元素的索引号,如果兄弟中存在其它不同标签,获取的索引号可能不是从0开始,需要手动计算
-
显示该元素:jQuery对象 . show ( )
-
隐藏该元素:jQuery对象 . hide ( )
-
下拉显示:jQuery对象 . slideDown ( )
-
上拉隐藏:jQuery对象 . slideup ( )
-
淡入显示:jQuery对象 . fadeIn ( )
-
淡出隐藏:jQuery对象 . fadeIn ( )
注意:显示,隐藏,下拉显示,上拉隐藏,淡入显示,淡出隐藏,都属于动画方法
注意:由于动画会连续触发,使用 stop()方法,但必须写在动画方法之前(注意是所有的动画前面),这样多个事件同时触发时,每次只执行一个动画
尤其注意:给整个页面设置动画,必须给 $(' body,html ')添加 animate()动画方法
- 获取内容:jQuery对象 . text ( )
- 插入内容:jQuery对象 . html (’ 内容 ‘)
尤其注意:由于jQuery里的 html 方法(同 innerHTML 方法)获取的内容包含空格和标签,一般只用作插入内容,而使用 text 方法获取元素里的内容
- 获取表单的 value 值:jQuery对象 . val ( )
注意:如果 val ( ) 括号中有参数,则是修改表单中 value 的值,如果括号中只有引号,则是清空内部内容
- 删除元素:jQuery对象 . remove ( )
- 清空内容:jQuery对象 . empty ( )
- 克隆元素:jQuery对象 . clone(true)
尤其重要:克隆方法(无论是 jQuery 还是原生 js 写法)的括号里如果有 true,会完整的复制自身以及所有的子元素,尤其注意会将功能也复制下来
- 创建元素:$(‘ 直接写想要创建的标签 ’)
注意:一定要加引号(强调),建议使用模板字符串(反引号 + ${ }),方便拼接,且动态创建的元素一般会搭配插入节点的操作,否则这个动态创建的元素只是存在内存中,看不见,需要进行插入才可生效
尤其注意:原生 js 做插入操作,需要先创建元素节点,然后通过 innerHTML 方法给这个元素节点填入内容,再插入,而 jQuery 中直接可以创建插入
- 将子元素插入到父元素里面,且插入到末尾:父元素 . append(被插入的子元素:可以是某个动态创建的节点元素)
容易混淆:原生 js 中插入节点是 父元素 . appendChild(),如果不严谨的话,只写 append 也可以
在 jQuery中使用排他思想
给自己设置样式,再给自己的兄弟取消样式(通过 jQuery对象 . siblings()获取兄弟元素)
注意:可颠倒顺序
隐式迭代 和 链式调用
隐式迭代:给 jQuery对象添加事件时,jQuery 在底层对获取的 jQuery对象进行了遍历操作,不需要和原生 API 一样对获取到的元素循环添加事件
尤其注意:隐式迭代只针对添加事件有效,从而不需要遍历 jQuery对象便可循环绑定事件,但对其它获取的元素(伪数组)进行批量操作,仍需使用遍历循环,如果只是对获取元素(伪数组)中的指定元素操作,无需遍历,通过 jQuery对象 . eq(索引)操作即可
链式调用:当前 jQuery对象调用完一个方法后,会把这个对象再次返回,返回的对象就可以继续调用下一个方法,所以便可以连写成一行代码
jQuery---遍历元素(jQuery对象)的方式
格式:jQuery对象 . each(function(i,el){ })
尤其注意:i 是下标,自动生成索引号,el 是原生DOM对象,在遍历中使用 el,需转换成 jQuery对象,即 $(el),代指这个 jQuery对象
注意:return false 是终止 jQuery循环的专用写法
jQuery---遍历数组或对象的方式
格式:$ . each(数组或对象,function(i,el){ })
尤其注意:该遍历方式主要针对数组和对象,i 是下标,自动生成索引号,el 可以直接使用,无需转换成 jQuery对象
深入理解何时需要遍历元素,何时不需要?
jQuery:由于获取到的元素是多个元素(伪数组),且 jQuery 中存在隐式迭代,如果对这些元素做相同的事情,则直接可以对获取元素进行设置,无需遍历(例如:统一给所有元素设置背景颜色,设置同样的自定义属性),如果对这些元素分别做不同的事情,则需要进行遍历(例如:分别给这些元素设置不同的索引号,绑定相同的事件但是事件处理函数中做着不同的事情)
原生 js:统统需要循环遍历
事件委托的使用
在 jQuery 高级事件写法的基础上添加一个具体触发的子元素,事件委托给父亲,代码如下:
jQuery对象 . on(‘ 事件类型 ’,‘ 具体触发的子元素 ’,function(){ })
注意:jQuery 高级事件如果写了第二个参数,就变成了事件委托,此时 $(this)指向的是具体触发的子元素
尤其注意:事件类型和具体触发的子元素必须加引号
滚动条事件在 jQuery 中的特殊点
尤其重要:scroll 事件必须要给 window 添加,注意与原生 js 使用滚动条的区别
获取滚动条向上滚动的距离:scrollTop()
注意:设置滚动条向上滚动的距离,括号里面填入想要设置的距离即可(数字,没有单位)
深入分析Tab栏及类似扩展
凡是与 Tab栏类似的,当触发事件时,两个模块需要一 一对应的,都遵循给当前触发的元素设置索引号,并不是给与之对应的元素设置索引号,然后获取当前触发元素的索引号,让与之对应的元素根据这个索引号设置某些样式
Tab栏自动轮播的步骤
前提条件:Tab栏切换已经实现
- 声明一个变量 num = 0,设置定时器,获取所有Tab栏按钮,通过 num 自增变化,使得Tab栏按钮根据索引 num 实现自点击,即 jQuery对象 . click()
- 通过 if 判断语句,使每次Tab栏轮播到最后一个按钮后,num 重置为 -1(注意写在 num 自增语句的前面)
- 当鼠标移入清除定时器,当鼠标离开重启定时器
- 当点击Tab栏某一个按钮后,定时器轮播切换的不是当前点击的下一个位置,因此在Tab栏点击事件中,需要先拿到当前点击按钮的索引号,这里尤其注意,使用 index()方法拿到的是包括自己以及所有的兄弟元素的索引号,如果兄弟中存在其它不同标签,获取的索引号可能不是从0开始,需要手动计算,然后将当前点击按钮的索引号赋值给 num
Javascript 高级
面试题:面向对象的三大特性?答:封装、继承、多态
ES6 之后的面向对象写法(通过 类 实现面向对象编程)
(一)类的声明及实例化对象
- 类的声明
class 类名{
私有(构造函数):constructor(形参){
//里面的 this 为私有属性或方法
//一般用于获取元素,由于constructor函数在实例化对象(即 new)后会立刻调用,因此将 init 初始化函数写在里面,同时调用
this.init();
//涉及到动态改变的元素剪切到updateNode函数里,由于该构造函数会调用初始化函数,而初始化函数包含动态改变的元素的重获方法,所以这里直接剪切就行
}
//初始化函数,专门用于绑定事件
init(){
//一般会在初始化函数里调用updateNode函数,使重新获取元素,并重新绑定事件
this.updateNode();
}
//updateNode重新获取元素的方法
updateNode(){
//此处存放动态改变的元素获取方法
}
//以下抽离出多个普通函数,为公共方法,多为抽离出来的功能模块
公共方法(普通函数):函数名(){
}
公共方法(普通函数):函数名(){
}
}
注意:类名一般规范首字母是大写
注意:constructor 函数里使用 this 定义的方法(或属性)被认为是私有方法(或属性),与 constructor 函数平级的函数被认为是公共方法
尤其注意:类里面的方法,无论是构造函数还是普通函数,不需要写 function,且多个函数之间不能用逗号分隔
特别注意:在面向对象中,constructor 函数常用于获取元素,或者定义一个变量存放一些特定的数据,init 函数常用于绑定事件,涉及到动态改变的元素,都需要放在 updateNode 函数里,专门用来重新获取元素
- 实例化对象
var 实例化的对象名 = new 类名(实参);
尤其重要:实例化对象中的实参会传递到 constructor 中的形参里面
尤其注意:当实例化对象后(即 new 调用类),constructor 函数便会自动调用
注意:this 指向的是实例化的对象名,两者等价
(二)constructor 函数中的属性、方法与公共方法在调用时候的注意点
公共方法与私有方法(或属性)之间互相调用,或者公共方法里面调用其它的公共方法,都必须加通过 this 来调用
尤其重要:如果方法中有事件,则 this 的指向会变成事件源,此时可以在类的外部声明一个变量 that = ‘ ’,然后在 constructor 函数里面将 this 保存在变量 that 中(即 that = this),就可以在事件处理函数中通过 that 调用某个属性或方法
(三)深入分析公共方法和私有方法(或属性)互相调用为什么要加 this
私有方法每生成一个 this,便会创建一个空对象,该对象指向实例化对象,简而言之,this 就是实例化对象,两者等价,因此,通常情况,在外部调用实例化对象的某个属性或方法时,实例化对象 . 函数名()替代了 this . 函数名();而在内部互相调用时,由于调用的其实就是实例化对象的某个属性或方法,同理,this . 函数名()替代了实例化对象 . 函数名(),所以需要加 this
(四)关于继承的注意点
让子类继承父类的方法(通过 extends 关键字):class Son extends Father{ }
注意:子类如果书写了 constructor,虽然可以继承父类,但不能继承父类的私有方法(或属性),此时必须手动在子类的 constructor 中使用 super(),这样便可以完整的继承父类中的私有方法(或属性),super 的本质其实就是代指父类中的 constructor 函数;子类如果没有书写 constructor,默认会自动生成构造函数 constructor,并且在调用中自动执行super完成继承
注意:在继承中,如果子类的的方法或者属性与父类相同,调用该方法时候,会优先调用自己的方法或者属性(就近原则),不论在子类还是父类,如果私有部分和公共部分存在相同的方法或属性,也同样优先调用私有的方法
ES6 之前的面向对象写法(通过 构造函数+原型 实现面向对象编程)
(一)面试题:在构造函数中,什么是实例成员,什么是静态成员?
通过 ‘ this . 属性名 ’ 添加的是实例成员,访问必须通过 ‘ this . 属性名 ’ 或者 ‘ 实例化对象 . 属性名 ’ 来访问
通过 ‘ 构造函数名 . 属性名 ’ 添加的是静态成员,访问必须通过 ‘ 构造函数名 . 属性名 ’ 来访问
(二)关于原型对象的使用
在构造函数里写多个公共方法,当生成多个实例化对象后,构造函数内部的方法会开辟多个空间,造成性能的浪费,因此,可使用原型对象来解决这个问题
注意:简单属性通过 this 定义在构造函数里面,公共方法放到原型对象身上,在构造函数外部定义,简而言之,原型对象就是一个公共区域,用来存放公共方法,原型对象也可用来扩展一些 JavaScript 内置对象(如Array对象)的方法
-
通过原型对象定义方法:构造函数名 . prototype . 方法名 = function(){ }
-
访问原型对象上的方法:实例化对象可以直接访问原型对象上的方法,即 实例化对象 . 方法名
原理:当通过实例化对象来访问某个方法时,首先会查找通过 this 定义的方法,如果没查找到,该实例化对象有个隐藏的 __ proto __ 属性(又称对象原型,简称 __ proto __ 原型),该对象原型指向构造函数的原型对象(即:构造函数名 . prototype),所以便会查找这个原型对象里的方法,如果该构造函数的原型对象里也没有查找到这个方法,由于构造函数的原型对象也是一个对象,里面同样有 __ proto __ 原型,且指向 Object 里的原型对象,因此会顺着 Object 里的原型对象查找,如果还没有,同理,Object 里也有个 __ proto __ 原型,但是它指向的是 null,这个查找过程,被称为原型链查找
注意:由于原型对象函数,可以由实例化对象直接调用,因此,它的 this 指向为实例化对象,同构造函数一致
(三)定义原型对象方法时引起的问题
原型对象(构造函数名 . prototype)里有个 constructor 属性,指向构造函数,如果使用如下方式,一次性定义多个方法:
构造函数名 . prototype = { 方法名1:function(){ } ,方法名2:function(){ } }
则会将该原型对象重新赋值,内容全部被覆盖,包括 constructor ,此时需要在里面手动添加一行代码,即 constructor:构造函数名,重新指回原构造函数
(四)构造函数实现继承
- 继承构造函数的简单属性
ES6之前,没有继承的概念,因此通过 call 方法模拟继承,代码如下:
function Father() {
this.uname = '谢志强';
}
function Son() {
//调用父函数,相当于把父函数里的代码拿了过来,即:this.uname = '谢志强',只不过这里的this变成了Son里面的this,指向Son的实例化对象
Father.call(this);
}
var son = new son();
console.log(son.uname);
注意:函数名 . call(this指向的目标,其它参数是传递给函数的实参),该方法使用后,首先会调用函数,它的第一个参数,可以改变函数的 this 指向
理解:这里的调用,可以理解为,其实就是执行了函数里的所有代码,把代码全部拿了过来,只是相对于不同函数,有的在执行后有返回值,有的没有返回值
尤其重要:通常情况,在子类构造函数里面,直接使用 ” 父类构造函数名 . call(this)“, 即可继承父类构造函数里的属性,由于该 call 方法是写在子类构造函数里面的,因此,call()括号里的 this 其实代表的就是子构造函数里的 this,即把调用过来的父构造函数的 this 指向改变成了子构造函数的 this 指向
- 继承构造函数的原型对象方法
尤其注意:虽然 call 方法能够继承父类构造函数里的属性,但无法继承构造函数的原型对象身上的方法,此时需要利用 “ 实例化对象可以直接访问原型对象方法 ”的特性,让子类构造函数的原型对象指向父类构造函数的实例化对象,而父类构造函数的实例化对象会在 new 之后生成,即:new 父类构造函数名(),就指的是父类构造函数的实例化对象,因此可书写如下代码,从而实现继承原型对象上的方法:
子类函数构造名.prototype = new 父类构造函数名();
子类函数构造名.prototype.constructor = 子类构造函数名;
切记:不能使用原型对象简单赋值的方法实现继承,因为这样做会使两个原型对象指向同一个地址,修改其中任意一个会造成另一个跟着改变
尤其注意:由于对子类构造函数的原型对象进行了重新赋值,因此造成该原型对象原本的内容被全部覆盖,包括 constructor,此时一定要重新指回原构造函数
增强版的数组循环遍历方式
方式一(常用):数组名 . forEach(function(item,index){ })
注意:此处 item 表示迭代(遍历)过程中当前的每一个元素,此处 index 表示当前每一个元素的索引值
尤其注意:不能使用 return 返回值,终止循环不能使用 break,如果想要终止循环,用 try catch 包裹循环体,然后根据条件手动抛出异常即可终止循环
方式二(主要用于筛选数组):var 变量名 = 数组名 . filter(function(item,index){ return 筛选条件 })
注意:此处 item 表示迭代(遍历)过程中当前的每一个元素,此处 index 表示当前每一个元素的索引值
注意:该方法必须写 return 来作为筛选条件,且在遍历数组的同时,会把符合条件的元素放在一个新数组中返回,这个返回的新数组需要用一个变量来接收
方式三(主要用于查找数组中是否有满足条件的元素):var 变量名 = 数组名 . some(function(item,index){ return 指定条件 })
注意:此处 item 表示迭代(遍历)过程中当前的每一个元素,此处 index 表示当前每一个元素的索引值
注意:该方法必须写 return 来作为指定条件,当查找到有符合条件的元素,后面便不会再执行,返回的是布尔值,即 true 或者 false,根据此特性,该方法主要作为 if 语句的判断条件,来实现某些需求,如数组去重等
注意:某些特殊情况,可以手动返回 true(即 return true),强制让 some 循环结束
方式四(主要用于查找数组中所有元素是否全部满足条件):var 变量名 = 数组名 . every(function(item,index){ return 指定条件 })
注意:此处 item 表示迭代(遍历)过程中当前的每一个元素,此处 index 表示当前每一个元素的索引值
注意:该方法必须写 return 来作为指定条件,当查找到有任意一个元素不符合条件,返回 false,当全部满足条件,返回 true
ES6 新增的数组扩展方法
方法一(找到符合条件的那一项,并返回这个成员本身):var 变量名 = 数组名 . find(function(item){ return 指定条件 })
注意:此处 item 表示迭代(遍历)过程中当前的每一个元素
**注意:该方法必须写 return 来作为指定条件,当找到符合条件的那一项,就返回这个成员本身,否则就返回 undefined **
方法二(找到符合条件的那一项,并返回这个成员的索引):var 变量名 = 数组名 . findIndex(function(item){ return 指定条件 })
注意:此处 item 表示迭代(遍历)过程中当前的每一个元素
注意:该方法必须写 return 来作为指定条件,当找到符合条件的那一项,就返回这一项的索引,如果没找到则返回 -1
尤其注意:该方法常用作判断条件,当大于 0 时,就代表找到了该项,即该项存在
尤其注意:findIndex 方法和 indexOf 容易混淆,前者是数组方法,后者是字符串方法(主要针对字符串,但可以对简单数组使用),注意区分
方法三(检测数组中是否包含某个值):var 变量名 = 数组名 . includes(被检测的值)
注意:包含该值返回 true,否则返回 false
方法四(数组累加的方法):数组名 . reduce(function(sum,item,index){ return 返回的值 },初始值)
注意:此处 item 表示迭代(遍历)过程中当前的每一个元素,此处 index 表示当前每一个元素的索引值,大部分方法都有这两个参数,但 reduce 的第一个参数为 sum,它第一次指向的是初始值(一般会给定初始值为0),而 return 返回的值会作为下一次循环的 sum 的初始值
ES6 语法
(一)let 和 const
ES6 标准规定,声明变量时,使用 let 或 const 来代替 var
-
let 相关知识
let 具有块级作用域,即:只能在大括号范围内访问使用 let 声明的变量
let 不会提升变量,也可以防止 for 循环造成的变量污染(变量无法自行销毁)
暂时性死区:花括号中有 let,那么就封闭死整个花括号区域,只能访问花括号内部的变量,哪怕外界有同名变量,也无法访问(不具备作用域链)
-
const 相关知识
const 声明的是常量,同样具有块级作用域,且声明时必须赋初始值,且不能更改,但对于复杂数据类型,数据结构内部的值可以更改,但是本身这个数据结构不能更改
(二)关于何时使用 let,何时使用 const
当定义的变量确定后面不再更改了,这种情况使用 const,其余情况全部使用 let
(三)数组解构和对象解构
-
数组解构
let [ 自定义变量名1,自定义变量名2 ] = 数组或数组名
注意:自定义变量名会与数组里的每一项一 一对应匹配,取值的时候直接使用自定义变量名即可
-
对象解构
let { 属性名:重命名 } = 对象名
尤其注意:对象解构出的属性名,必须和对象名中的属性名保持一致(切记),不能改变,如果需要改变,可以使用冒号进行重命名
尤其注意:如果是函数的某个参数是对象,同样可以使用对象解构作为函数的参数使用
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· 展开说说关于C#中ORM框架的用法!
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?