一、JavaScript介绍
概念:JavaScript简称JS,是一个运行在【浏览器】的【解释型】【弱类型】【面向对象】的脚本语言 JS由三部分组成:ECMScript(核心语法,版本3/5/6/7/8/9/10/11/12)+DOM(JS操作HTML网页)+BOM(JS操作浏览器)
1、浏览器
JS的运行环境是浏览器
(1)浏览器自带JS解释器,打开浏览器就可以运行
(2)在学习node.js是会安装单独的js解释器
2、编译型/解释型
(1)编译型:在程序执行前会检查程序的语法是否正确,如果不正确,不会运行-严格:eg:Java、C、C++、C#...
(2)解释型:在程序执行前不会检查程序的语法,直接运行,当碰到错误时,就停止运行-自由:eg:JS、php...
3、强类型/弱类型
(1)强类型:变量保存的数据,严格按照数据类型 -比如:eg:Java -严格
(2)弱类型:变量保存的数据是随意的,数据类型由数据来决定 -比如:eg:JavaScript -自由
4、面向对象
对象名.属性名; 对象名.方法名();
5、特点
(1)可以使用一切编辑工具编写js代码
(2)解释型
(3)弱类型
(4)面向对象
(5)可以做一切CSS完成不了的效果(轮播、选项卡、购物车、验证...)
二、如何使用JS
1、两种方式
方式一
在HTML页面上写一个<script></script>标签,中间写上js代码。一般放在HTML页面的中<body></body>中的最后面,html代码的后面
<script>
//js代码
</script>
方式二
单独创建一个xx.js文件,在HTML中使用<script src="../xx.js"></script>引入,放的位置与方式一相同
<script scr="../xx.js">
//引入的方式,一定不能在这里面写代码
</script>
2、输出方式/打桩输出
输出是为了方便定位错误,找出错误,输出有三种方式
(1)在控制台输出。console.log("输出内容")
console.log("输出内容");
console.log(123456);
(2)在页面中输出。document.write("输出内容")。此方法还支持标签的写法,但是搭配点击事件,会把页面中其他内容全部替换掉,不推荐使用
document.write("输出内容");
document.write(123456);
(3)在弹出框中输出内容。alert("输出内容")。有时候会卡住整个页面,导致用户只能看到一个白板
alert("输出内容");
alert(123456);
3、*变量
方式一:var
创建后可以修改
JS何时使用:当一个值会被反复使用时,要提前将它保存在一个变量中,以后使用其变量名,相当于就是在使用变量的值
语法:var 变量名=值;
var test1=123;
var test2="这是一个测试变量";
特殊:
(1)变量名不是随意取的
a、不能以数字开头
b、建议驼峰命名或下划线命名法
c、命名要尽量的见名知意
(2)如果变量名是name,不管你保存的数据类型是什么,都会悄悄的给转成一个字符串
(3)变量名不能是关键字
(4)变量可以只创建,不赋值,默认为undefined,但是undefined什么操作都做不了,后面需要给它赋值
(5)多个变量可以连续创建,简写:var 变量名1=值1,变量名2=值2,变量名3=值3,变量名4=值4...;
var 变量名1=值1,变量名2=值2,变量名3=值3,变量名4=值4...;
方式二:let
let 变量名=值;
作用:
(1)解决了声明提前
(2)带来了块级作用域,一个{}就是一个代码块,此变量只能在那个所在的{}里面使用
(3)如果用let去当下标绑定事件,那么他会记录着你当前元素的下标,不在需要自定义下标了 - 其实forEach的那个形参i就是let创建的
4、常量
常量创建后,不允许修改
生活中的常量:
PI
一个小时60分
一分钟60秒
一天24个小时
一年365/366天
语法:const 常量名=值;
其余特殊点和变量一样
5、数据类型
分为两大类
(1)*原始/基本/值类型:5个
a、*number - 数字:取值有无数个,数字直接写,不需要加引号,在控制台中显示的颜色是蓝色
b、*string - 字符串:取值有无数个,但是必须加上:""或''(颜色是黑色)
c、*boolean - 布尔:取值只有2个,分别叫 true(真|对) 或 false(假|错) - 一般用作条件判断(控制台中显示的颜色是蓝色)
d、null - 空,取值只有1个,就是null,唯一的作用就是用于释放变量释放内存的,节约内存空间,提升网页的性能,以后会有更好的方式(颜色是灰色)
e、undefined - 取值只有1个,就是undefined,什么都做不了(颜色是灰色)
(2)引用/对象类型:有11个。以后学习
6、*运算符
(1)*算术运算符:+ - * / %
特殊:
a、%:取余,俗称模,两个数相除,不取商,而是取除不尽的余数
作用:
i、*任意数%2 - 判断奇偶数
ii、取出某个数字的后n位
1234%10 -> 4
1234%100 -> 34
1234%1000 -> 234
b、带有隐式转换:悄悄地会将数据类型转换,发生变化,默认:都是左右两边转为数字再运算
true -> 1
false -> 0
undefined -> NaN
null -> 0
"1000" -> 1000
"1000px" -> NaN - 需要用强制转换
NaN -> Not A Number:不是一个数字,但是确实是数据类型,不是一个有效数字,有两个缺点:
i、参与任何的算数运算结果都是NaN
i、参与任何的比较运算结果都是false
c、+运算特殊:如果碰上一个字符串,左右两边都会隐式转换为字符串,+运算就不再是+运算,而是拼接操作
(2)*比较运算符:> < >= <= == != === !==
结果:一定是一个布尔值
带有隐式转换:默认左右两边都会悄悄转为数字再进行比较大小
特殊:
a、如果参与比较的左右两边都是一个字符串,则会按位PK每个字符的十六进制的uniccode号(十六进制ASCII码)
数字0-9<大写A-Z<小写a-z<汉字
常识:汉字的第一个字:是"一" unicode号是 4e00 ascii是19968。汉字的最后一个字:是 龥 unicode 号是 9fa5 ascii是40869
b、NaN参与任何的比较运算结果都是false,带来了一个问题:如何判断x是不是NaN,我们不能使用普通的比较运算去判断!isNaN(x)。
结果是一个布尔值:true -> 说明是有效数字 false -> 说明是NaN
c、undefined==null/true
解决:全等:不带隐式转换的等值比较===,要求值相同并且数据类型也要相同
!==:不带隐式转换的不等比较
(3)*赋值运算符:= += -= *= /= %=
a、=:赋值:将=右边的值,保存到=左边的变量名之中
b、后面5个可以理解为是一种升级写法,运算后再保存回变量本身,举例:i=i+1 ==> i+=1;
(4)*逻辑运算符:综合比较 - 用于写条件
&&:与(并且)
全部条件都满足,结果才为true
只要一个不满足,结果就为false
||:或者
全部条件都不满足,结果才为false
只要一个满足,结果就为true
!:颠倒布尔值
(5)*自增自减运算符:
++ --
i++ ===> i+=1 ===> i=i+1
自增:固定的每次只能+1
累加:+=每次加几由程序员自己决定
前++和后++的区别?
前++,返回的是加了过后的新值
后++,返回的是加了之前的旧值
(6)位运算:
左移:m<<n,读作m左移了n位,翻译:m*2的n次方
右移:m>>n,读作m右移了n位,翻译:m/2的n次方
扩展:用户输入框:var 变量=promp("提示文字");
三、*****分支结构
1、程序的三种流程控制语句
(1)顺序结构 - 默认:程序从上往下执行每一句话
(2)分支结构 - 通过条件判断,选择部分代码执行
(3)循环结构 - 通过条件判断,选择要不要重复执行某块代码
2、三种分支结构
(1)if...else...分支
a、一个条件,一件事,满足就做,不满足就不做
if(条件){
//操作;
}
b、一个条件,两件事,满足就做第一件,不满足就做第二件
if(条件){
操作;
}else{
操作;
}
c、多个条件,多件事,满足谁,就做谁
if(条件1){
操作1;
}else if(条件2){
操作2;
}else{
默认操作;
}
(2)switch...case分支
语法:
switch(变量/表达式){
case 值1:
操作1;
break;
case 值2:
操作2;
break;
default:
默认操作;
}
特殊:
a、问题:默认只要一个case满足后,会将后续所有的操作全部做完
解决:break;
建议:每一个case的操作后面都跟上一个break
有的地方也可以不加break;
i、最后一个操作default可以省略break
ii、如果中间多个条件,做的操作是一样的,可以省略中间部分
b、case在做比较的时候是不带有隐式转换的
c、default可以省略不写的,但是不推荐,如果不写,条件都不满足的情况,则什么事儿都不会发生
面试题:if vs switch的区别?
a、switch...case:优点:执行效率较高,速度快(他比较时,case做的不是范围查找,而是等值比较)
缺点:必须要知道最后的结果是什么才可以使用
b、if...else...:优点:可以做范围判断
缺点:执行效率较慢,速度慢(他做的是范围查找)
建议:代码开发完过后,要做代码优化,要尽量的少用if...else...,多用switch...case和三目运算
(3)三目运算符
简化分支的
语法:
条件?操作1:默认操作; === if...else...
条件1?操作1:条件2?操作2:默认操作 === if...else if..else
注意:
a、默认操作不能省略,省略了会报错
b、如果操作复杂,不能使用三目运算:操作只能有一句话,如果操作有多句话还是推荐使用switch或if
四、*****循环结构
1、循环结构
反复执行 相同 或 相似 的操作
循环三要素:
a、循环条件:开始 - 结束,循环的次数
b、循环体:做的操作是什么
c、循环变量:记录着我们当前在哪一次,而且他会不断的变化
2、三种循环
(1)while循环
语法:
var 循环变量=几;
while(循环条件){
循环体;
循环变量变化;
}
执行原理:首先创建出循环变量,判断循环条件,如果条件满足,则做一次循环体操作,并不会退出循环,而会回过头再次判断循环条件满不满足,如果满足,则做一次循环体操作,...直到条件不满足,才会退出循环
宏观上感觉循环好像一瞬间就结束了,但是微观上循环其实是一次一次执行的
特殊:
a、有的时候可能真的需要使用死循环:默认永远不会停下来的循环
何时使用:不确定循环次数的时候
while(true){死循环}
b、死循环其实也会有一天停下来,如何停下来呢?
break; - 退出整个循环,多半都是用来搭配死循环的
continue; - 退出本次循环,下一次依然会执行
(2)*for循环
和while的原理是一样的,但是他比while看上去更加的简洁,更加的舒服 语法:
for(var 循环变量=几;循环条件;循环变量变化){
循环体;
}
特殊
死循环:for(;;){}
面试题:while 和 for的区别?
- while和for在原理上几乎没有区别?
- 一般来说我们不确定循环次数的时候,会使用while循环 - 死循环
- 一般来说我们确定循环次数的时候,会使用for循环 - 大部分情况都是他
(3)do...while循环
一般不使用 语法:
var 循环变量=几;
do{
循环体;
循环变量的变化
}while(循环条件)
面试题:while 和 do...while的区别?
- 区别只看第一次,如果第一次条件都满足,那么两者没有任何区别。
- 但是如果第一次条件不满足,那么while一次都不会执行,do...while至少会执行一次
五、***强制(显示)数据类型转换
******页面上一切数据JS获取到后都是字符串类型
1、转字符串(两种方法)
(1)var str=x.toString();//x不能undefined和null,会报错,undefined和null不能使用.做任何操作,因为他们不是对象
(2)var str=String(x);//万能的,任何人都可以转为字符串,但是绝对不要手动使用,完全等效于隐式转换,还不如+""
不重要:页面上一切数据js获取到后都是字符串类型
2、***转数字:3种方法
(1)*parseInt(str/num); - parse解析 Int整型 - 专门用于将【字符串转为整数】
执行原理:从左向右依次读取转换每个字符,碰到非数字字符就停止转换,如果一来就不认识则为NaN,不认识小数点。
console.log(parseInt(35.5));//35
console.log(parseInt("35.5"));//35
console.log(parseInt("3hello5"));//3
console.log(parseInt("hello35"));//NaN
console.log(parseInt("35px"));//35
console.log(parseInt(".35px"));//NaN
console.log(parseInt(true));//NaN
console.log(parseInt(false));//NaN
console.log(parseInt(undefined));//NaN
console.log(parseInt(null));//NaN
(2)*parseFloat(str); - parse解析 Float浮点型 - 专门用于将【字符串转为小数】
执行原理:几乎和parseInt一致,认识第一个小数点
console.log(parseFloat(35.5));//35.5
console.log(parseFloat("35.5"));//35.5
console.log(parseFloat("3hello5"));//3
console.log(parseFloat("hello35"));//NaN
console.log(parseFloat("35.5px"));//35.5
console.log(parseFloat(".35px"));//0.35
console.log(parseFloat("35.5.5"));//35.5
(3)Number(x);//万能的,任何人都可以转为数字,但是绝对不要手动使用,完全等效于隐式转换,还不如 -0 *1 /1
3、转布尔
-
Boolean(x);//万能的,任何人都可以转为布尔,但是绝对不要手动使用,完全等效于隐式转换,还不如 !!x
-
***只有6个为
false:0,"",undefined,NaN,null,false,其余全部都是true。 -
我们绝对不会手动使用,但是再分支或循环的条件之中,不管以后代老师写什么,他都会悄悄的转为一个布尔值,自带此方法
-
不管条件中写什么,只需要判断不为上面那六个,不为那六个就是true,是那六个就为false
六、函数(Function)
1、概念
Function 也叫函数也叫方法,【先预定义】好,以后可以【反复使用】的【代码段】
2、如何使用函数:(2步)
(1)***定义/创建/声明:
function 函数名(){
函数体/代码段;
}
注意:函数创建后,不会立刻执行,需要我们去调用函数
(2)调用函数:2种
a、在js内部写:函数名(); - 程序员写几次就会调用几次
b、在HTML上面绑定事件:
<elem onclick="函数名()"></elem> - 什么元素都可以绑定事件
3、何时使用函数
(1)不希望打开页面立刻执行
(2)希望用户来触发提升用户的体验感
(3)以后每一个独立的功能(作业)都要封装为一个函数
函数在js里的地位极高,函数是js的第一等公民
4、带有形参的函数
(1)创建带有形参的函数
形参 - 其实就是一个变量,只不过不需要加var,而且不需要赋值,所以称之为叫做形式参数 - 简称形参
function 函数名(形参,...){
函数体/代码段;
}
(2)使用带有形参的函数
必须传入实参 - 实际参数,就是你传递过去的值
函数名(实参,...)
注意:传参的时候顺序是不能乱的,必须和形参的顺序一一对应,数量不多不少
5、**********自定义函数Function
(1)创建方式
a、*【声明方式】创建函数
function 函数名(形参列表){
操作;
return 返回值/结果;
}
b、【直接量方式】创建函数: - 不推荐
var 函数名 = function(){
操作;
return 返回值/结果;
}
函数名其实就是一个变量名
(2)调用
var 接住返回的结果=函数名(实参列表);
- 其实return的本意是退出函数,但是如果return后面跟着一个数据,顺便将数据返回到函数作用域的外部,但是return只负责返回,不负责保存,所以调用函数时要自己拿个变量来接住它
- 就算省略return,默认也有返回值,返回一个undefined
- *具体需不需要得到函数的结果,看程序员自己:如果有一天在全局中希望拿着函数结果去做别的操作,那么就需要return
- 前辈们提供的方法,大部分基本上都加了return
(3)***作用域
a、全局作用域
全局变量 和 全局函数,在页面的任何一个位置都可以使用
b、函数作用域
局部变量 和 局部函数,在【当前函数调用时,内部可用】
变量的使用规则:优先使用局部的,局部没有找全局要,全局也没有那就会报错
特殊点(缺点):
- 千万不要再函数中对着未声明的变量直接赋值 - 全局污染:全局本身没有这个东西,但是被函数作用域给添加了
- 局部可以使全局的,但是全局不能使用局部的 - 解决:函数中使用return
(4)***声明提前: - 只会出现笔试题中
规则:
在程序正式执行之前,将var声明的变量(轻)和function声明的函数(重),都会悄悄的集中定义在当前作用域的顶部,但是赋值留在原地
强调:
- 声明方式创建的函数会完整的提前(第一种)
- 直接量方式创建的函数不会完整提前,只有变量名部分会提前(第二种)
何时使用:
永远不会自己使用,干扰我们判断的 - 只会在笔试题中遇到
开发时只需要遵守以下规则:
- 变量名和函数名尽量不要重复
- 先创建后使用
(5)***重载
相同的函数名,根据传入的实参的不同,自动选择对应的函数去执行,但是JS不支持,函数名如果重复了,后面的肯定会覆盖掉前面的
目的:减轻我们程序员的压力,记住一个方法就可以执行很多的操作
解决:在【函数内部】自带一个arguments的对象(类似数组对象):不需要我们去创建 - 哪怕没有写任何形参,他也可以接受住任何实参,所以默认你看他length长度为0
固定套路:
- 通过下标去获取传入的某一个实参:arguments[i] - i从0开始
- 通过length去获取到底传入了几个实参:arguments.length,通过判断传入的实参的不同,在内部去写判断,从而变相的实现重载
function calc(){
var num=arguments.length;
var result;
switch(num){
case 1:
return result=arguments[0]*arguments[0];
case 2:
return result=arguments[0]+arguments[1];
case 3:
return result=arguments[0]*arguments[1]*arguments[2];
}
}
console.log(calc(3,1,2));
6、ES6的箭头函数
简化一切匿名回调函数
固定公示:
function去掉,()和{}之间添加=>,如果形参只有一个,那么()可以省略,如果函数体只有一句话,那么{}也可以省略,如果函数体只有一句话并且是return,那么return和{}都可以省略。
7、绑定事件(3种方式)
(1)在HTML上书写事件属性
<elem on事件名="函数名(实参)"></elem>
缺点:
a、不符合内容与样式与行为得分离原则
b、无法动态绑定,一次只能绑定一个元素
c、不支持绑定多个函数对象
(2)*在js中使用事件处理函数属性
elem.on事件名=function(){操作}
优点:
a、符合内容与样式与行为得分离原则
b、动态绑定,一次能绑定多个元素
缺点:
a、不支持绑定多个函数对象
(3)*在js中使用事件API:如果不用考虑老IE,他也不错
主流:elem.addEventListener("事件名",callback);
老IE:elem.attachEvent("on事件名",callback);
兼容:
if(elem.addEventListener){
elem.addEventListener("事件名",callback);
}else{
elem.attachEvent("on事件名",callback);
}
优点:
a、复合内容与样式与行为得分离原则
b、动态绑定
c、支持绑定多个函数对象
缺点:
有兼容性问题
8、事件的取消绑定
(1)如果使用elem.onclick=()=>{},那么elem.onclick=null
(2)如果你使用elem.addEventListenter("事件名",回调函数);那么:
elem.removeEventListener("事件名",回调函数); - 事件名和回调函数,必须和添加时的一模一样
七、数组
创建一个变量可以保存【多个数据】
数组都是线性排列,除了第一个元素,每个元素都有唯一的前驱元素;除了最后一个元素,每个元素都有唯一的后继元素
***每个元素都一个自己的位置,叫做下标,下标都是从0开始,到最大长度-1结束
1、创建数组(两种方式)
(1)*直接量方式:var = arr[];//空数组
var arr = [数组1,...];
(2)构造函数方式:var arr = new Array();//空数组
var arr = new Array(数组1,...);
new Array(num);//第二个方法有坑:这句话的意思是:创建一个长度为num的空数组,里面没有任何东西,只有无数的undefined
(3)如何释放一个引用类型:
一定要看清楚有几个变量引用着这个类型,每个变量都要释放后才能释放干净
在JS底层有一个垃圾回收器,只有垃圾回收器的计数器(记录着这个数据有几个人引用着)为0的时候才会删除不要的数据
建议:我们的代码都要封装为一个函数,函数中的一切变量都会自动释放
2、***面试题:按值传递
var a=x; var b=a;修改a,b变不变,或者 修改b,a变不变?
(1)传递的如果是原始类型
其实是复制了一个副本给对方,两者互不影响
(2)传递的如果是应用类型
js中不是原始类型,就是引用类型(函数、数组,都是引用类型)- 引用类型都是地址传递 -- 浅拷贝/深拷贝
因为引用类型很大,比原始类型大得多,不可能保存在变量本地,只是保存了一个地址值而已,其实是赋值了自己的地址值给对方,两者用的是同一个地址值,一个修改另一个也会变化
3、获取数组之中的元素
数组名[i]
4、后续添加/替换元素
数组名[i] = 新数据; 如果下标处没人则为添加,如果下标处有人则为替换
5、数组具有三大不限制
(1)不限制元素的类型
(2)不限制元素的长度
(3)不限制下标越界 - 不是一个好东西
如果获取元素时,下标越界,返回的是一个undefined
如果添加元素时,下标越界,会得到要给稀疏数组,如果我们搭配上我们学过循环去遍历获取每个元素,那么会得到很多很多undefined
6、数组的唯一属性:length
语法:数组名.length 作用:获取到数组的长度,长度是从1开始的
三个固定套路:
- 向末尾添加元素:arr[arr.length]=新数据;
- 获取数组的倒数第n个元素:arr[arr.length - n];
- 缩容:删除倒数n个元素:arr.length-=n;
7、***遍历数组
往往很多情况,我们不会拿出某个元素来使用,而是拿出每个元素来进行 相同 或 相似 的操作 - 搭配上循环
固定公示:
for(var i = 0; i< arr.length;i++){
arr[i];//当前次元素
}
8、***hash数组(关联数组):下标是可以自定义的
(1)如何使用
- 创建空数组:var arr=[];
- 为数组添加自定义下标并且赋值:arr["自定义下标"]=新值;
(2)访问元素:arr["自定义下标"];
(3)强调:hash数组的length失效了,永远为0
遍历hash数组:不能再使用for循环,必须使用for in 循环 - 不需要设置从哪里开始到哪里结束,纯自动化。专门为了遍历hash数组存在的
for(var i in 数组名){
i;//下标
数组名[i];//当前次元素
}
不止能遍历hash数组,也能遍历索引数组
建议:索引数组依然是for,hash数组再使用for in
(4)***hash数组的原理:
hash算法:将字符串,计算出一个尽量不重复的数字(地址值)
字符串内容相同,则计算出来的数字也一定是相同的
添加元素:将自定义下标交给hash算法,得到一个数字(地址值),直接将你要保存的数据放到此地址保存起来
获取元素:将指定的自定义下标交给hash算法,得到一个和当初保存时一样的数字(地址值),通过此地址找到你当初保存的数据,取出来使用
(5)JS里面一切的东西都是对象,万物皆对象,除了undefined和null,【一切对象的底层都是hash数组】
9、*****数组的API
(1)*arr转str
var str=arr.join("自定义连接符");
固定套路:
a、笔试题:将数组里面的内容拼接为一句话/单词 - 无缝拼接,其实就是拼接了一个空字符串
var arr=["h","e","l","l","o"," ","w","o","r","l","d"];
var str=arr.join("");
console.log(str);
b、将数组拼接为DOM页面元素 - 第一次遇到数据渲染页面
//拿数据
var arr=["-请选择-","北京","南京","西京","东京","重庆","北京","南京","西京","东京","重庆","北京","南京","西京","东京","重庆","北京","南京","西京","东京","重庆","北京","南京","西京","东京","重庆","北京","南京","西京","东京","重庆"];
//将数组拼接为页面标签字符串
var str=<开始标签>"+arr.join("</结束标签><开始标签>")+"</结束标签>";
//巧了:innerHTML能够识别标签
sel.innerHTML=str;
(2)*数组拼接:添加元素的新方式
将你传入的实参全部拼接到arr的末尾
var newArr=arr.concat(新值1,arr1...);
特殊:
- 不修改原数组,只会返回一个新数组
- concat支持传入数组参数,悄悄的
将传入的数组打散为单个元素再拼接
(3)*截取子数组:
根据你传入的开始下标截取到结束下标
var subArr=arr.slice(starti,endi+1);
特殊:
不修改原数组,只会返回一个新数组含头不含尾- endi可以省略不写,如果省略,会从starti位置一直截取到末尾
- starti也可以省略,如果两个实参都省略,那么会从头到尾完全的赋值一份:此操作也叫做深拷贝 - 复制了一个副本给对方
- 支持负数参数,-1代表倒数第1个,-2代表倒数第2个...
以上三个 ,join()、.concat(新值1,arr1....)、.slice(starti,endi+1)都不会修改原数组。需要新变量来接收返回结果,会开辟一个新空间,与原数组地址不同,是深拷贝
(4)*删插替
删除:var desl=arr.splice(starti,n);//n代表删除的个数
特殊:
虽然他直接修改原数组,但是也有返回值,返回的是被删除的数据组成的一个新数组,因为前辈们考虑到有可能删除的东西刚好是需要的东西,哪怕没有删除也会返回一个空数组
插入:arr.splice(starti,0,新值,...);
特殊:
- 原starti位置的元素以及后续元素都会向后移动
- 尽量的不要插入一个数组,会导致我们的数组一些是一维,一些是二维,遍历的时候就非常不方便
替换:var dels=arr.splice(starti,n,新值...);
特殊:
删除的个数和插入的个数不必相同
(5)翻转数组:arr.reverse();
(6)数组排序
- 笔试题:冒泡排序:前一个元素和后一个元素进行对比,如果前一个>后一个,两者就交换位置,但是做完一轮发现只有最大的一个数字到了最后,所以再开循环反复使用,固定公式:
var arr=[31,21,54,4376,69,8,8,65,643,52,3,321,5,47,69,87,643,524];
for(var j=1;j<arr.length;j++){
for(var i=0;i<arr.length-j;i++){
if(arr[i]>arr[i+1]){
var m=arr[i];
arr[i]=arr[i+1];
arr[i+1]=m;
}
}
}
console.log(arr);
- 正式开发时:arr.sort();
默认:将数组中的元素转为字符串后,再按位PK每个字符的unicode号(ASCII码)
问题一:希望按照数字升序排列:
arr.sort(function(a,b){//此函数叫做匿名回调函数,回调函数不需要我们程序员调用,由前辈们创建好,我们学习如何使用即可,其实sort方法悄悄的调用了
//console.log(a);//后一个数字
//console.log(b);//前一个数字
return a-b;//如果a-b>0,说明后>前
//如果a-b<0,说明后<前
//如果a-b=0,说明后=前
//而sort方法会根据返回的正数、负数、0,来自动考虑要不要交换位置
})
问题二:希望按照数字降序排列
arr.sort(function(a,b){
return b-a;
})
强调:切记:
- 1、以后只要网页上有功能带有排序,他的底层一定是数组,因为js中只有数组可以排序
- 2、以后只要网页上有随机的功能,那么他的底层一定用到了随机数公式
(7)栈和队列
栈和队列:添加元素和删除元素的新方式:
a、栈:其实就是数组,只不过是一端封闭了,只能从另一端进出
- 何时使用:现实生活中,情况不多
- 如何使用:
开头进:arr.unshift(新值,...);//添加元素的方式,向前添加。缺点:导致其余元素的下标都发生变化
开头出:var first=arr.shift();//删除元素的新方式,向前删除,一次只能删除一个。缺点:导致其余元素的下标都发生变化
结尾进:arr.push(新值,...);//添加元素的新方式,向后添加
结尾出:var last=arr.pop();//删除元素的新方式,向后添加
b、队列:其实就是数组,只不过一端进,从另一端出
开头进:arr.unshift(新值,...);
结尾出:var last=arr.pop();
结尾进:arr.push(新值,...);
开头出:var first=arr.shift();
(8)判断(2个)
a、every()
每一个,要求所有元素都满足条件才会true,只要有一个不满足则为false,非常类似于我们的&&
var bool=arr.every(function(val,i,arr){
//val - 当前的值
//i - 当前的值的下标
//arr - 当前数组本身
return 判断条件;
})
b、some()
有一些,要求只要有一个元素都满足条件会为true,所有元素都不满足则为false,非常类似于||
var bool=arr.some(function(val,i,arr){
return 判断条件;
})
(9)遍历(2个)
拿到数组中的每个元素做相同 或 相似的操作
forEach - 直接修改元素组
arr.forEach(function(val,i,arr){
//操作
})
map - 不修改原数组返回一个新数组
var newArr=arr.map(function(val,i,arr){
return 操作;
})
(10)过滤和汇总
a、过滤
筛选出你需要的部分,但是和现实不一样的是元数组并不会发生变化
var subArr=arr.filter(function(val,i,arr){
return 判断条件;
})
var arr=[1,2,3,4,5,6,7];
var subArr=arr.filter(val=>val%2==0)
console.log(subArr);
console.log(arr);
b、汇总
var result=arr.reduce(function(prev,val,i,arr){
return prev+val;
},基础值)
var arr=[1,2,3,4,5,6,7];
以上every()、some()、forEach()、map()、filter()、reduce()都是在简化for循环操作,以后数组可能就很少写for循环了
(11)类数组转为普通数组
let 变量接住=Array.from(类数组对象);
let 接住=Array.prototype.slice.call/apply(类数组对象)
10、二维数组
数组的元素,又引用着另一个数组
何时使用:在一个数组,希望再次细分每个分类
创建:
var arr=[
["兰大帅",18,900],
["劳伦斯",19,1000],
["Lawrence",20,700]
];
访问:arr[行下标][列下标];
特殊:列下标越界,返回undefined
行下标越界,得到的是一个报错,因为行下标越界已经得到一个undefined,undefined没有资格再加[]做操作
遍历二维数组:必然两层循环,外层循环控制行,内层循环控制列
for(var i=0;i<arr.length;i++){
for(val j=0;j<arr[i].length;j++){
console.log(arr[i][j]);
}
}
八、**********DOM
Document Objuect Model:文档对象模型:专门用于操作HTML文档的,提供了一些方法
在某一个升级后,为了方便各类程序员将DOM分为3方面:
(1)核心DOM:无敌的,既可以操作HTML又可以操作XML,但是语法相对比较繁琐
(2)HTML DOM:只可以操作HTML,不能访问一切自定义的东西,但是语法简单
(3)XML DOM:只可以操作XML,被淘汰了,被JSON数据格式代替了
1、DOM树概念
DOM将我们的HTML看作是一个倒挂的树状结构,但是树根不是html标签,而是document的对象
- document对象:不需要我们程序员创建,由浏览器的js解释器自动创建,一个页面只有一个document树根
- 作用:可以通过树根找到我们想要的任何一个DOM元素/节点/对象(属性和方法)
- DOM会将页面上的每个元素、属性、文本、注释等等都会被视为一个DOM元素/节点/对象
2、查找元素(两大方面)
(1)直接通过HTML的特点去查找元素
a、通过ID查找元素
var elem=document.getElementById("id值");
特殊:
- 返回值,找到了返回当前找到DOM元素,没找到返回的一个null
- 如果出现多个相同id,只会找到第一个
- 记住控制台输出的样子,这个样子才叫做一个DOM元素/节点/对象才可以去做操作
- 忘记此方法,不允许使用,id不好用,一次只能找到一个元素。id留给后端用
- 其实根本不用查找,id直接可用。id名不要和js中的变量名相同,会被覆盖掉,导致id名找不到
b、*通过 标签名 查找元素
var elems=document/已经找到的父元素.getElementsByTagName("标签名");
特殊:
- 返回值,找到了返回一个类数组DOM集合,没找到得到空集合
- js只能直接操作DOM元素,不能直接操作DOM集合,解决:要么下标拿到某一个,要么遍历拿到每一个
- 不一定非要从document开始查找,如果从document去找,会找到所有的元素,可以换成我们已经找到的某个父元素
返回的是一个动态集合
c、*通过class查找元素
var elems=document/已经找到的父元素.getElementsByClassName("class名");
特殊:
- 返回值,找到了返回一个类数组DOM集合,没找到得到空集合
- Js只能直接操作DOM元素,不能直接操作DOM集合,解决:要么下标拿到某一个,要么遍历拿到每一个
- 不一定非要从document开始查找,如果从document去找,会找到所有的元素,可以换成我们已经找到的某个父元素
返回的是一个动态集合
d、可以通过选择器去查找
(a)
var elem=document.querySelector("任意CSS选择器的");//query - 查询selector - 选择器:查询css选择器
缺陷:
只能找到单个元素,如果匹配到了多个,也只会返回第一个,没找到null
一次只能操作一个元素,不舒服
(b)
*** var elems=document.querySelectorAll("任意css选择器的");
优点:
找到了是一个集合,没找到是一个空集合
复杂查找时,非常简单
返回的是一个静态集合NodeList
面试题:document.getXXX和document.queryXXX的区别?
后者更适合复杂查找
动态集合和静态集合的区别?
(a)动态集合:每一个DOM发生变化,它都会悄悄的再次查找,让页面和数据保持一致,但是效率也就低下了 - 不支持forEach
(b)静态集合:每一次DOM发生变化,它不会悄悄的再次查找,让页面和数据没有保持一致,但是效率也就高了 - 支持使用forEach
(2)*通过关系去获取元素
前提条件:必须找到一个元素才可以使用关系
父元素:elem.parentNode; - 单个元素
子元素:elem.children; - 集合
第一个子元素:elem.firstElementChild; - 单个元素
最后一个子元素:elem.lastElementChild; - 单个元素
前一个兄弟:elem.previousElementSibling; - 单个元素
后一个兄弟:elem.nextElementSibling; - 单个元素
当不希望影响到别人,只希望影响到自己的关系网时就可以使用关系去获取元素
3、操作元素
前提:先找到元素,才能操作元素(3个方面)
(1)内容
a、*elem.innerHTML - 获取和设置开始标签到结束标签之间的内容,支持识别标签
获取:elem.innerHTML;
设置:elem.innerHTML="新内容";
b、elem.innerText - 获取和设置开始标签到结束标签之间的纯文本,不识别标签
获取:elem.innerText;
设置:elem.innerText="新内容";
以上两个都是专门为双标签准备,而有一个单标签也是可以写内容,叫做,如何获取?
c、input.value - 专门获取/设置input里的内容
获取:input.value;
设置:input.value="新内容";
(2)属性
获取属性值:elem.getAttribute("属性名");
设置属性值:elem.setAttribute("属性名","属性值");
简化版:
获取属性值:elem.属性名;
设置属性值:elem.属性名="属性值";
简化版的缺点:
a、class必须写为className - ES6(2015年)class变成了一个关键字
b、不能操作自定义属性
(3)样式
内联样式
使用样式的方式(3种):
a、*内联样式
b、内部样式表
c、外部样式表
二阶段使用JS来操作【内联样式】
a、不会牵一发而动全身
c、优先级最高
获取样式:elem.style.css属性名;
设置样式:elem.style.css属性名="css属性值";
特殊点:
a、css属性名,有横线的地方,去掉横线,变为小驼峰命名法
border-radius ---> borderRadius
b、小缺陷:获取时,我们只能获取到内联样式,因为我们目前学的就是内联样式的操作
样式表样式 - 此生不见
//获取你想要操作的样式表
var sheet=document.styleSheets[i];
//获取此样式表中所有的样式规则
var rules=sheet.cssRules;
//数出你想要操作的那个规则的
var rule=rules[i];
//操作
console.log(rule.style.css属性名);
rule.style.css属性名="css属性值";
(4) 绑定事件
elem.onclick = function(){
操作;
\*\*\*\*\*关键字this - 这个
如果单个元素绑定事件,this -> 这个元素
如果多个元素绑定事件,this -> 当前触发事件元素
}
一切的获取,往往都是为了判断
一切的设置,可以说是添加也可以说是修改
4、操作属性
(1)*获取属性值
核心DOM:elem.getAttribute("属性名");
HTML DOM:elem.属性名;
(2)*设置属性值
核心DOM:elem.setAttribute("属性名","属性值");
HTML DOM:elem.属性名="";
(3)删除属性
设置属性值为空字符串,确实某些属性可以算是删除,但是只是删除了属性值,属性名还在,而有的属性哪怕只有一个属性名,也会具有作用(比如:href、disabled、readonly)
*核心DOM:elem.removeAttribute("属性名");
HTML DOM:elem.属性名=""; - 属性节点删不干净
5、判断有没有
垃圾,只能判断有没有,不能判断是什么,推荐用elem.getAttribute("属性名");去获取到值,自己再写比较运算
核心DOM:elem.hasAttribute("属性名");
HTML DOM:elem.属性名!="";
以后使用建议:优先使用HTML DOM,HTML DOM实现不了再用核心DOM补充!
缺陷:
a、class必须写为className
b、自定义的东西操作不了
6、如何创建元素以及上树(3步)
(1)创建空标签:
var elem=document.createElement("标签名");
(2)为其设置必要的属性和事件
elem.属性名="属性值";
elem.on事件名=function(){}
(3)上树(3种)
* 父元素.appendChild(elem);//在父元素末尾处追加一个子元素elem
父元素.insertBefore(elem,已有子元素);//在父元素追加一个子元素elem,但是放在已有子元素的前面
父元素.replaceChild(elem,已有子元素);//在父元素追加一个子元素elem,但是会替换掉已有子元素
7、删除元素
elem.remove();
九、***引用/对象类型(11个)
1、*****String对象
String的概念
什么是字符串:多个字符串组成的【只读】字符【数组】(只读:所有的字符串API都不会修改原字符串,都会返回一个字符串)
和数组的相同点:
(1)字符串中的个数:str.length;
(2)获取字符串中的某个字符:str[i];
(3)遍历字符串
(4)所有数组不修改原数组的API,字符串也可以使用(concat、slice)
和数组的不同点:
(1)所有数组的直接修改原数组的API,字符串都不可以使用,比如排序只有数组可以使用,但是字符串也有很多很多属于自己的API
***引用/对象类型:11个
*String Number Boolean->包装类型
*Array *Function Date(日期) Math(数学) *RegExp(正则:验证)
Error(错误)
*Object(面向对象开发方式)
Global(全局对象) - 只有在浏览器中被window对象代替了,自然保存着全局变量和全局函数,只不过window可以省略不写,后面node.js这个后端语言中全局对象就叫做global
***包装类型
概念:专门用于将原始类型的值封装为一个引用类型的对象
为什么:原始类型的值原本就是没有任何属性和方法,意味着原始类型本身是不支持,去做任何操作的。但是程序员经常用字符串来进行操作,就提供了三个包装类型的属性和方法(String,Number,Boolean)
何时使用:只要试图使用原始类型的变量调用属性或方法的时候,自动包装
何时释放:方法调用完毕后,自动释放包装类型,又变成了原始类型
因为没有给null和undefined提供包装类型,所以它俩不能使用
*****StringAPI
(1)转义字符:\
作用:
- 将字符串中和程序冲突的字符转为原文
"\"" - 包含特殊功能的符号
换行:\n
制表符:\t -> 大空格,和按tap键一样的效果 - *输出unicode编码的字符
\u4e00 - ascii码:19968
\u9fa5 - ascii码:40869
(2)*大小写转换
将字符串中每个字符统一的转为大写 或 小写
何时使用:只要程序不区分大小写,就要【先统一】的转为大写 或 小写,再比较(做验证码)
如何使用:
大写:var upper=str.toUpperCase();
小写:var upper=str.toLowerCase();
(3)获取字符串中指定位置的字符
str.charAt(i) === str[i]
(4)*获取字符串中指定位置的字符的ASCII码
var ascii=str.charCodeAt(i);
*通过ASCII码转回原文:
var 原文=String.fromCharCode(ascii);
(5)***检索字符串
检查索引,检查下标:获取关键字的下标
var i=str/arr.indexOf("关键字",starti);
从starti位置开始,查找右侧第一个关键字的第一个字符的位置
starti可以省略,默认从0位置开始查找
*返回值:找到了,返回第一个关键字的第一个字符的下标位置
没找到,返回-1,其实我们根本不关心下标为多少,我们只关心下标为不为-1
作用:判断有没有
强调:数组也能使用此方法,数组这个方法是后期才添加的,原本此方法只有字符串可用,比如老IE的数组就没此方法
笔试题:默认只能获取到第一个关键字的下标,如何才能获取到所有的关键字的下标?
var str="no zuo no die no can no bibi";
var index=-1;
while((index=str.indexOf("no",index+1))!=-1){
console.log("找到了,下标为:"+index);
}
(6)拼接字符串
var newStr=str.concat("新字符串",...)
不如使用+""运算
(7)*截取字符串(3个)
var subStr=str/arr.slice(starti,endi+1);//用法和数组的用法完全一直
str.substring(starti,endi+1);//用法几乎和slice一致,但是不支持负数参数
str.substr(starti,n);//n代表的是截取的个数,不必考虑含头不含尾
(8)*替换字符串
这个方法很强大,但是需要搭配正则表达式
var newStr=str.replace("固定关键字"/正则表达式,"新内容");
(9)*****切割/分割/分隔字符串
作用:将字符串转为数组
var arr=str.split("自定义切割符");
注意:
(1)切割后,切割符就不存在了
(2)如果你的切割符写的是"",就是切散每一个字符,装入数组
(10)去掉空白字符
str.trim()去掉字符串头和尾部的空字符串
str.trimStart()去掉字符串头部的空字符串
str.trimEnd()去掉字符串尾部的空字符串
扩展:JS如何创建元素并上树(3步)
(1)创建空标签:var elem=document.createElement("标签名");
(2)为其设置必要的属性和事件:
elem.属性名="属性值";
elem.on事件名=function(){操作}
(3)挂载上树/渲染页面:父元素.appendChild(elem);
2、Math对象
专门提供了数学计算的API
强调:不需要创建,直接使用
属性:Math有一些属性,涉及到科学计数法,但是几乎用不到,只有Math.PI有可能用到===3.1415926...这一串数字我们不需要自己创建,浏览器自带
API
(1)取整(3种)
a、上取整
超过一点点,就取下一个整数
var num=Math.ceil(num);//小数位数不能超过15位,否则此方法只能取整,不能上取整
b、下取整
无论超过多少,都会省略小数部分
var num=Math.floor(num);
c、四舍五入取整
var num=Math.round(num);//只看小数位数的第一位
以上三个方法都只能取整
d、取整的方式
以上三个+*parseInt(str 去掉单位)+*num.toFixed(d)
num.toFixed(d)
优点:a、可以四舍五入,并且保留指定小数位数,d其实就是保留的小数位数
b、解决浏览器带来的舍入误差,2-1.6=0.39999999999999这种情况就可以使用toFixed来解决
缺点:结果是一个字符串,建议搭配上parseFloat()使用
笔试题:不允许使用toFixed的情况下,自己封装一个函数,由用户传入数字和保留位数,实现四舍五入操作:
function toFixed(num,d){
num*=(10**d);
num=Math.round(num);
num/=(10**d);
return num;
}
var result=toFixed(Math.PI,2);
console.log(result);
(2)乘方和开方
*乘方:Math.pow(底数,幂); -> 更简化:底数**幂 开放:Math.sqrt(num); - 仅仅只能开平方
(3)*最大值和最小值
var max/min=Math.max/min(a,b,c,d,e,f,g...);//自动在你传入的数字中比较出最大值或最小值
问题:本身不支持数组参数
解决:固定用法:Math.max/min.apply(Math,arr);//具有打散数组的功能
(4)绝对值
把负数转为正数
Math.abs(-1);//1
(5)***随机数
Math.random();//在0-1之间取一个随机的小数
搭配上parseInt,只能取到0,但是不可能取到1,意味着取不到最大值
公式:parseInt(Math.random()*(max-min+1)+min);
强调:只要以后网页中某一块有随机的功能,他的底层一定用到了随机数
3、Date对象
日期对象,提供了操作日期和时间的API
创建(4种)
(1)*创建一个当前日期
var now=new Date();
(2)*创建一个自定义时间
var birth=new Date("yyyy/MM//dd hh:mm:ss");
(3)创建一个自定义时间
var birth=new Date(yyyy,MM,dd,hh,mm,ss);//修改月份,从0-11,0代表1月。这种创建方式计算时间时,结果会有问题
(4)复制一个日期
为什么:日期的所有API都是直接修改原日期的,无法获得修改之前的日期,所以,在执行API之前先进行复制,然后再操作复制后的日期
var end=new Date(start);
使用(2类)
(1)时间差
两个日期对象之间,可以相减(大-小),得到一个毫秒差,换算出自己想要的任何一部分 - 日期的本质其实就是保存了一个毫秒数 - 做到倒计时的关键
创建日期的最后一种方式,绝对没人用:var date=new Date(毫秒数);//计算机元年:1970年1月1日8点整
(2)API
分量:时间的单位
FullYear(年) Month(月) Date(日) Day(星期)
Hours(小时) Minutes(分) Seconds(秒) Milliseconds(毫秒)
每一个分量都有一对儿getXXX/setXXX的方法
其中getXXX负责获取一个分量的值
其中setXXX负责设置一个分量的值
特殊:
a、取值范围:
FullYear - 当前年份的数字
Month - 0-11
Date - 1-31
Day - 0-6:0代表是星期天,外国人的眼里星期天才是一周的第一天
Hours - 0-23
Minutes、Seconds:0-59
日期是一个取值范围,如果设置超过了范围,会自动进制
b、Day,没有set方法
c、如果希望对某个分量进行加减操作
date.setXXX(date.getXXX()+/-n);
d、格式化日期为本地字符串
date.toLocalString(); - 垃圾:具有兼容性问题,我们一般会选择自己创建一个格式化方法来格式日期
用了此方法,会失去一些东西,比如:日期的自动进制,日期的API
但是也会得到一些东西:字符串的API
十、BOM
1、概念
BOM:Browser Object Model - 浏览器对象模型:专门用于操作浏览器的。
但是它使用的不多,远不如ES和DOM,浏览器很多操作都是自带的,而且BOM没有标准,各个浏览器都有自己的定义,但是大部分浏览器的都是一致规范的除了老IE(8及以下)
2、window对象
扮演着两个角色:
(1)全局对象:保存着全局变量和全局函数
(2)指代当前窗口本身
属性:
1、获取浏览器的完整大小:outerWidth/outerHeight
2、*获取浏览器的文档显示区域的大小:innerWidth/innerHeight - 获取每台电脑的浏览器的文档显示区的大小
3、获取屏幕的完整大小:跟window没关系:screen.width/height; - 我们目前学习的都是浏览器应用(网页),并不会去做桌面应用
方法
(1)*打开链接的新方式
a、当前窗口打开,可以后退:
HTML:<a href="url">内容</a>
JS:open("url","_self");
b、当前窗口打开,禁止后退:使用场景:比如电商网站,结账后不允许后退
HTML做不到了,只有JS可以,也不是window能做到的,而是另外一个:
history对象
history:当前【窗口的历史记录】,它其实可以做的事儿就是前进后退
前进:history.go(1);
后退:history.go(-1);
刷新:history.go(0);
***location对象
location:当前【窗口正在打开的url】,有一个API:
location.replace("新url");//叫做替换,不叫跳转,不会产生历史记录,自然也不能后退,但是网址替换了,网页必然会发生变化
***程序员常识:一个url由几部分组成?分别每个部分有什么作用?-以后再学习服务器端和数据库的时候会有很大的帮助
(a)协议:*https(加密)/*http(未加密)/ftp(传输文件)/ws(直播)... 前两个都属于叫做请求-响应模型
(b)主机号|IP地址|域名:域名是需要花钱购买的,主机号|IP地址是免费,127.0.0.1才真的是叫做主机号,只能自己访问自己
(c)端口号:https默认端口为443,http默认端口为80,只有默认端口可以省略不写
(d)***文件的相对路径|路由:百度加密了
(e)***查询字符串|请求消息:前端传输到后端的东西,前端对后端说的话,就是form表单提交带来的东西
属性:
获取url的5个部分的内容,但是不需要记忆,直接输入location对象即可查看
协议:location.protocal;
域名:location.hostname;
端口:location.port;
路由:location.pathname;
请求消息:location.search;
跳转:location="新url" - 替换当前窗口,可以后退
跳转后,禁止后退:location.replace("新url"); - 替换当前窗口,禁止后退
刷新:location.reload();
c、新窗口打开,可以打开多个
HTML:<a href="url" target="_blank">内容</a>
JS:open("url","_blank");
d、新窗口打开,只能打开一个:使用场景:比如电商网站,只允许用户打开一个结账页面
HTML:<a href="url" target="自定义一个name">内容</a>
JS:open("url","自定义一个name");
其实窗口的底层都是有一个名字的,如果打开了一个已经开着的名字的窗口的,他会把他关掉,再次打开
总结:
a、以后的跳转,任何标签都可以
b、提升用户的体验感
c、a标签的其他用途
i、跳转
ii、锚点
iii、下载按钮:<a href="xx.exe/rar/zip/7z">下载</a>
iiii、打开图片和txt文档:<a href="xx.png/jpg/jpeg/gif/txt">打开图片和txt</a>
iiiii、直接书写js - 不需要绑定点击事件:<a href="javascript:js代码;">打开图片txt</a>
(2)打开新窗口/新链接
newW=open("url","target","width=?,height=?,left=?,top=?");
t特殊:a、如果没有加第三个参数,那么窗口会和浏览器融为一体
b、如果你加入了第三个参数,那么窗口会脱离浏览器独立存在
(3)关闭窗口
window/newW.close();
(4)改变新窗口的大小
newW.resizeTo(新宽,新高);
(5)改变新窗口的位置
newW.moveTo(新X,新Y);
(6)*window提供了三个框
警告框:alert("提示文字");
输入框:var user=prompt("提示文字");
确认框:var bool=confirm("提示文字")
(7)*****定时器也是window的
(8)事件
a、window.onload事件 - load - 加载:等待其他所有的资源加载完毕后才会执行的代码,放在里面的代码其实要最后才执行
b、*window.onresize事件 - 窗口如果大小发生了变化,就会触发,搭配上判断innerWidth可以理解为是js版本的css媒体查询
c、***window.onscroll事件 - 滚动事件,一旦滚动就会触发
i、获取滚动条当前的位置:window.scrollY
ii、获取元素距离页面顶部有多远:elem.offsetTop/offsetLeft
(9)*****本地/客户端存储技术
cookie:淘汰了,存储的大小只有2kb,而且操作极其麻烦,尤其要到处切割只能最多保存30天
webStorage:H5带了一个新特性,存储的大小有8mb,永久保存,而且非常简单
分类(2种)
sessionStorage - 会话级,只要浏览器一旦关闭,数据就会死亡了
localStorage - 本地级,只要你不清空,就会永久存在
两者的用法是一模一样的,不用创建,直接可用
操作:
1、添加:xxxStorage.属性名="属性值";
2、读取:xxxStorage.属性名;
3、删除:xxxStorage.removeItem("属性名");
4、清空:xxxStorage.clear();
3、*****定时器
(1)周期性定时器
每过一段时间就会执行一次,先等后做
开启:timer=serInterval(callback,间隔毫秒数);
停止:clearInterval(timer);
(2)一次性定时器:等待一段时间,只会做一次就结束了
开启:timer=setTimeout(callback,间隔毫秒数);
停止:clearTimeout(timer);
同步技术:代码必须一行一行的执行,前面没做完,后面就等着。定时器就是目前见到第一个异步技术:无论这一块代码多么的耗时,也不会卡住后续代码。Ajax也是异步技术
4、event(事件对象)
(1)事件周期:从事件发生,到所有事件处理函数执行完毕的全过程
3个阶段
a、捕获阶段:由外向内,记录要发生的事件有哪些
b、目标优先触发:目标元素->当前点击的实际发生事件的元素
c、冒泡触发:由内向外,依次执行我们之前记录着的要发生的事件
(2)*****获取事件对象event:
主流:会自动作为事件处理函数的第一个形参传入
老IE:event; - 老IE全局有这个变量
兼容:event;//第1次见到小三上位,不光老IE可用,主流也可用
evet事件可做的操作
(1)获取鼠标的坐标
获取鼠标相对于屏幕的坐标:e.screenX/e.screenY
获取鼠标相对于窗口/客户端/文档显示区域的坐标:e.clientX/e.clientY
*获取鼠标相对于网页的坐标:e.pageX/e.pageY
(2)阻止事件冒泡:- 笔试中
主流:e.stopPropagation();
老IE:e.cancelBubble=true;
兼容:e.cancelBubble=true;//第2次见到小三上位,不光老IE可用,主流也可用
(3)*****利用冒泡 - 事件委托
开发中常用,提升网页性能,有了它我们的事件函数也可以换为箭头函数了
优化:如果多个子元素定义了相同 或 相似的事件操作,最好只给父元素定义一次
为什么:每一次绑定一个事件函数,其实都是创建了一个事件对象,创建的事件对象越多,网站性能就越差
淘汰了this,表示当前元素
认识一个目标元素target:你点击的是哪一个,他永远就是那一个,不会变化
主流:e.target
老IE:e.srcElement;
兼容:e.srcElement;//第3次见到小三上位,不光老IE可用,主流也可用
(4)阻止浏览器的默认行为
比如:a标签默认就可以跳转,提交按钮可以提交表单,右键自带一个弹出框,F12自带一个控制台,F11自带全屏功能,F5自带刷新功能
主流:e.preventDefault();
老IE:e.returnValue=false;
兼容:e.returnValue=false;//第4次见到小三上位,不光老IE可用,主流也可用
新事件:
(1)右键事件 - window.oncontextmenu
(2)键盘事件:一般来说用于游戏开发较多+都要搭配上键盘的键码
window.onkeydown - 按住和按下,任何键盘按键都可以触发
window.onkeypress - 按住和按下,只有字母、数字、回车、空格可以触发,其他按键不行
window.onkeyup - 松开,任何键盘按键都可以触发(比手速的游戏)
(5)获取键盘的键码
e.keyCode; - 可以获取到你按了哪个键,每个键都有自己对应的键码,但是不需要记忆,要么输出看,要么百度搜个表
event可以说BOM之中最最重要的一个点,其他点就算你不用,你要知道,笔试面试只要考BOM多半都是event
老IE:不支持HTML5和ES5+
十一、递归
简单来说就是再函数之中再一次调用了函数自己,迟早有一天会停下来
何时使用:专门用于【遍历层级不明确的情况】 - DOM树和数据(children只能找到儿子层,找不到孙子层)
如何使用:(2步)
function 函数名(root){
//1、第一层要做什么直接做
//2、判断有没有下一层,如果有下一层则再次调用此方法,只不过传入的实参是自己的下一层
}
调用:函数名(实际的根);
算法:深度优先!优先遍历当前节点的子节点,子节点遍历完毕才会跳到兄弟节点
缺陷:不要过多使用,性能相对较差,同时开启大量的函数调用,良妃内存,我们只在一个情况下使用:【层级不明确】
递归VS纯循环
递归:优点:简单易用
缺点:性能低
纯循环:优点:几乎不占用性能
缺点:难得一批
十二、*****this
1、this的指向
单个元素绑定事件
这个元素
多个元素绑定事件
当前元素
箭头函数中的this
外部对象
***函数中的this
当前正在调用函数的对象
定时器的this
window
2、******ES5强制改变this的指向
call/apply
call/apply:临时的替换了函数的this - 借用
语法:函数名.call(借用的对象,实参...); - 单独传入每个实参
函数名.apply(借用的对象,arr); - 只能传入一个实参要求是一个数组,apply其实会悄悄的打散数组
强调:call/apply:相当于立刻调用函数,立即执行
bind
bind:永久替换了函数中的this - 买
3件事:
(1)创建了一个和原函数功能完全相同的新函数
(2)将新函数的this永久绑定为了指定对象,别人都借不走
(3)将新函数的部分参数永久固定
语法:var 新函数=函数名.bind(永久对象,永久实参,...); - 不是立刻执行,需要自己调用
强调:bind绑定的新函数没办法被call/apply再次借走
推荐使用call/apply
三个固定套路:
(1)Math.max/min.apply(Math,arr) - 也能支持数组参数
(2)Object.prototype.toString.call/apply(x)=="[object Array]";//笔试题:判断xx是不是一个数组
(3)类数组转为普通数组:
1、接住=Array.prototype.slice.call/apply(类数组对象)
2、接住=Array.from(类数组对象)
十三、ES6
1、学过了let、const关键字、箭头函数
2、模板字符串
可以直接识别变量,不再需要+运算去拼接了,而且实现了一个简单的js环境,甚至支持再里面书写API
`我的名字${name}`,需要加这两个模板符号``
3、*****解构赋值
顾名思义:解析结构再进行赋值 - 赋值的新方式,并且得到了增强
如果赋值符号,左右两边的结构一样的,就会悄悄的解开/脱掉结构再一一的进行赋值
语法:
(1)类似数组的解构赋值
let [a,b,c]=[1,2,3];
console.log(a);
console.log(b);
console.log(c);
(2)类似对象的解构赋值
let {a,b=默认值,c}={c:3,a:1,b:2};
console.log(a);
console.log(b);
console.log(c);
(3)调用函数时,传递实参的顺序无所谓了
unction zwjs({name,age,hobby="女"}){
return `我的名字叫${name},今年${age}岁,喜欢${hobby}`;
}
console.log(zwjs({hobby:"学习",age:18,name:"袍哥"}));
(4)函数的返回的结果,可以有多个
function f1(){
var a=1;
var b=2;
return [a,b];
}
var [a,b]=f1();
console.log(a,b);
只要以后见到:方法名({里面放着键值对就是使用了ES6的解构赋值})
4、Set和Map新的数据类型
(1)*Set
类似于数组的一种数据格式 - 【去重数组,然后再转回数组】
var s=new Set(arr);
...S - 三个点扩展运算符,可以脱掉数组的外套
一句话完成:[...new Set(arr)] - 不用记忆任何API
(2)Map:类似于对象的一种数据格式
var m=new Map();
添加:m.set("键","值");
获取:m.get("键");
清空:m.clear();
删除:m.delete("键");
5、新的循环 - 垃圾
for(var v of arr){
v;
}
缺陷:
(1)没有提供过下标,意味着不能修改原数组
(2)只能遍历索引数组,不能遍历hash数组,意味着也不能遍历对象
十四、*****正则表达式
概念:定义字符串中字符出现规则的表达式
何时使用:切割 替换 【验证】
如何使用:语法:/正则表达式/
1、正则表达式基础语法
(1)最简单的正则
最简单的正则就是关键字原文 "no" -> /no/后缀
后缀:
g :找全部 i:忽略大小写
(2)备选字符集
格式:/^[备选字符集]$/
强调:a、一个中括号,只管一位字符
b、问题:正则表达式默认只要满足就不管后续了,而我们做验证的人,希望的使用户从头到尾按照我们的要求来,希望从头到尾完全匹配:
解决:前加^,后加$,两者同时使用,代表要求从头到尾完全匹配/^[备选字符集]$/ - 只要做验证必加
特殊:如果备选字符集中的ascii码是连续的,那么可用-省略掉中间部分
比如:
a、一位数字:[0-9];
b、一位字母:[A-Za-z];
c、一位数字、字母、下划线:[0-9A-za-z_];
d、一位汉字:[\u4e00-\u9fa5];
e、除了字符集之外的:[^0-9] - 表示除了0-9的数字以外的其他字符。很少使用,范围太广了
(3)预定义字符集
前辈们提前定义了一些字符集,方便我们程序员 - 简化了备选字符集
- 一位数字:\d ===> [0-9]
- 一位数字、字母、下划线:\w ===> [0-9A-Za-z]
- 一位空白字符:\s
- 一位除了换行外的任意字符: . - 很少使用,范围太广了
建议:优先使用预定义字符集,预定义满足不了我们再用备选字符集补充
问题:不管是备选字符集,还是预定义字符集,一个都只管一位
(4)量词
规定一个字符集出现的次数
- 有明确数量:
- 字符集{n,m}:前边相邻的字符集,至少n个,最多m个
- 字符集{n,}:前边相邻的字符集,至少n个,多了不限
- 字符集{n}:前边相邻的字符集,必须n个
- 无明确数量:
- 字符集? : 前边相邻的字符集,可有可无,最多1个
- 字符集* : 前边相邻的字符集,可有可无,多了不限
- 字符集+ : 前边相邻的字符集,至少一个,多了不限
(5)选择和分组
选择:在多个规则中选一个
规则1 | 规则2
分组:将多个字符集临时组成一组子规则
(规则1 | 规则2)
(6)指定匹配位置
^:开头
$结尾
特殊:两者同时使用,前加^,后加$,表示从头到尾要求完全匹配 - 只要做【验证】
(7)密码强度验证
2-4位,可以输入数字、字母,但是必须出现一位大写和一位数字的组合:/^[0-9A-Za-z]{2,4}/;
预判公式:
(?![0-9]+$) ->不能全由数字组成,可能有大写、小写、汉字、日文、特殊符号...
(?![a-z]+$) ->不能全由小写组成,可能有数字、大写、汉字、日文、特殊符号...
(?![0-9z-a]+$) ->不能全由数字组成、也不能全由小写组成、也不能全由数字和小写的组合组成
比如:
/(?![0-9a-z]+$)(?![A-Za-z]+$)[0-9A-Za-z]{2,4}///2-4位,可以输入数字、字母。但是必须出现一位大写和一位数字的组合
/(?![0-9z-a]+$)(?![A-Za-z]+$)(?![A-Z0-9]+$)[0-9A-Za-z]{2,4}///必须三者都有
/(?![0-9A-Za-z]+$)[0-9A-Za-z_]{2,4}///至少要有下划线
2、*****支持正则表达式的字符串API
(1)切割
var arr=str.split("固定切割符"/RegExp);
(2)*****替换(笔试题中可能出现)
a、基本替换法
str=str.replace(/正则表达式/后缀,"新内容");
replace支持正则,并且搭配上后缀g就可以找到全部
缺陷:替换得新内容是一个固定得
b、高级替换法
str=str.replace(/正则表达式/后缀,function(a,b,c){
console.log(a);//正则匹配到得关键字
console.log(b);//正则匹配到得关键字的下标
console.log(c);//原字符串
return 判断a关键字的长度,而返回不同的星号数量
});
c、格式化:身份证
var id="513111129602226826";
var reg=/(\d{6})(\d{4})(\d{2})(\d{2})(\d{4})/;
id.replace(reg,function(a,b,c,d,e,f,g,h){
//再replace的时候,正则出现了分组,我们会得到更多的形参
//再形参a的后面就会出现n个形参,就看你有多少个分组
//第1个分组获得的内容会保存在第2个形参之中
//第2个分组获得的内容会保存在第3个形参之中
//...
//倒数第二个一定是下标
//倒数第一个一定是原文本身
})
d、格式化:手机号
var phone="12345678912";
var reg=/(\d{4})\d{4}(\d{3})/;
phone=phone.replace(reg,(a,b,c)=>{
return b+"****"+c;
})
console.log(phone);
总结:
何时前加^后加$,何时又该添加后缀g?
a、前加^后加$ - 验证
b、替换,你希望替换所有 - 必须加g
(3)正则对象
创建:
a、直接量:var reg=/正则表达式/后缀;
b、构造函数:var reg=new RegExp("正则表达式","后缀");
API:
验证:var bool=reg.test(用户输入的);
十五、animaite.css动画库
1、如何使用
- 百度搜索:animate.style/
- 下载
- 引入下载的文件
- 挑选需要的动画,把class放到元素上
- 并且设置设置上animation-duration:2s;执行时长
- 根据不同的动画,设置不同的初始效果,才会更好看
2、淡入淡出轮播
所有图片通过定位重叠在一起,通过设置opacity为0或1来显示图片,淡入淡出
3、滚动轮播
所有图片横向排列在一个div中,溢出的都隐藏,通过移动div的位置来显示图片
4、无缝轮播
页面上只加载两张图片,点击右按钮或者右边小圆点,就在右边生成一张图片,两张图片同时往左移动,移动完毕后删除左边的img标签。反之。显示的都是下标为0的,生成的都是下标为1的
5、swiper插件
一个专门的轮播插件,提供了HTML/CSS/JS,只需要复制
网站链接:www.swiper.com.cn/
十六、面向对象
面向对象的三大特点:封装、继承、多态
Array/String/RegExp/Date...对象具有属性和方法,都是预定义好的,现在可以学习自定义对象 - js是基于原型的面向对象语言
1、*****Object
(1)***开发方式
面向过程:过程 - 开始 ->结束,其实我们一直的开发方式都是面向过程:先干什么再干什么最后干什么
面向对象:对象(属性和方法),js有一句话万物皆对象,假设一个人是一个对象的话:
属性:身高、体重、姓名、性别、爱好、智商、情商...
方法:吃饭、睡觉、跑步、打字、上课...
何时使用面向对象:以后任何操作都要封装在一个对象之重 - 但是新手并不推荐,难度较大
为什么要面向对象:现实生活中所有的数据都必须包含在一个事务之中才有意义
(2)*****封装/创建/定义:封装自定义对象(3种)
a、*直接量方式
var obj={
"属性名":属性值,
...,
"方法名":function(){操作},//可以简化为箭头函数
...
}
强调:
- 其实属性名和方法名的""可以不加 - 暂时建议加上,以后学习一个数据格式JSON,他必须在键上加上""
- 访问对象的属性和方法
- obj.属性名;===obj["属性名"];
- obj.方法名();===obj"方法名";
- 建议使用 . 去访问对象的属性和方法,更简单
- ***js中一切都是对象,除了undefined和null,一切对象的底层都是hash数组
- 访问到不存在的属性,返回undefined
- 可以随时随地的添加新属性和新方法
- obj.属性名=新值;
- obj.方法名=function(){};
- 如果希望遍历出对象所有的东西,必须使用for in,obj[i]才能拿到。不要使用 . 会出问题
- ***如果希望在对象的方法里,使用对象自己的属性,写为this.属性名
- *****难点:this的指向:
- 单个元素绑定事件,this->这个元素
- 多个元素绑定事件,this->当前元素
- 定时器中的this->window
- 箭头函数this->外部对象
- 函数中this->谁在调用此方法,this就是谁
- 构造函数之中this->当前正在创建的对象
- *****难点:this的指向:
b、预定义构造函数方式
var obj=new Object();//空对象
//需要自己后续慢慢添加属性和方法
obj.属性名=新值;
obj.方法名=function(){};
以上两个都有一个缺陷:一次只能创建一个对象,适合创建单个元素的时候(第一种方法),第二种方法完全是垃圾,如果你要批量创建多个对象,那么我推荐第三种方法
(3)自定义构造函数方式(2步)
a、创建自定义构造函数
function 类名(name,age,hobby){
this.name=name;
this.age=age;
this.hobby=hobby;
}
千万不要在里面创建方法,每个对象都会创建出一个相同的方法,浪费内存 - 学习继承后可以解决
b、调用构造函数创建对象
var obj=new 类名(实参,...);
面向对象
优点:
(1)所有的属性和方法都保存在一个对象之中 - 更符合现实更有意义
(2)每个功能特地分开写 - 便于以后维护
(3)铁锁练舟 - 一个方法触发多个方法联动
缺点:
对新手不友好,尤其是this的指向问题
2、*****继承
父对象的成员(属性和方法),子对象可以直接使用
为什么要继承:代码重用!提高代码的复用性,节约了内存空间!提升了网站的性能!
何时继承:只要多个子对象共用的属性和【方法】,都要集中定义在父对象之中
(1)***如何找到原型对象(父对象)
保存了一类子对象共有属性和共有方法
- 对象名.proto;//必须先有一个对象
- 构造函数名.prototype;//构造函数名几乎人人都有,除了Math和Window,new构造函数名();//Array、String、Date、RegExp...
(2)*面试题
a、两链一包
作用域链和[原型链]和闭包
每个对象都有一个属性:proto,可以一层一层的找到每个人的父亲,形成的链式结构,就称之为原型链
可以找到父对象的成员(属性和方法),作用:找共有属性和共有方法
最顶层的是Object的原型,上面放着一个我们眼熟的方法toString,所以人人都可以使用toString
JS万物皆对象
b、*****判断是自有还是共有的属性或方法
判断自有
obj.hasOwnProperty("属性名");
如果结果为true,说明是自有属性,如果结果为false,有两种可能,说明可能是共有,也可能是没有
判断共有
if(obj.hasOwnProperty("属性名")==false&&"属性名" in obj){//in 关键字,会自动查找整条原型链上的属性,找到了结果为true,找不到结果为false
共有
}else{
没有
}
完整公式:
if(obj.hasOwnProperty("属性名")){
自有
}else{
if("属性名" in obj){
自有
}else{
if("属性名" in obj){
共有
}else{
没有
}
}
c、修改和删除自有和共有属性或方法
自有
修改:obj.属性名=新属性值;
删除:delete obj.属性名;
共有
修改:原型对象.属性名=新属性值;//千万不要觉得,自己能拿到,就能直接修改这样很危险,并没有修改原型的东西,而是在本地添加了一个同名属性
删除:delete 原型对象.属性名;//如果对着本地直接删除,那么此操作直接无效
d、*如何为老IE的数组添加indexOf方法(如果为一类人创建某个方法)
if(Array.prototype.indexOf===undefined){//判断是否为老IE
Array.prototype.indexOf=function(key,starti){
starti===undefined&&(starti=0);
for(var i=starti;i<this.length;i++){
if(this[i]===key){
return i;
}
}
return -1;
}
}
let arr1=[1,2,3,4,5];
let arr2=[2,4,6,8,10];
arr1.indexOf(3);//找到值为3的下标,从头开始找
arr1.indexOf(8,1);//找到值为8的下标,从下标为1出开始找
e、*如何判断x是不是一个数组(4种方法)
千万别用typeof(),只能检查原始类型,不能检查引用类型,如果检查引用类型得到的结果都是一个object
let arr=[],
obj={},
date=new Date(),
reg=/\s/;
(i)判断x是否继承自Array.prototype
Array.prototype.isPrototypeOf(x);
结果为true,说明是数组,结果为false,说明不是数组
console.log(Array.prototype.isPrototypeOf(arr));
console.log(Array.prototype.isPrototypeOf(obj));
console.log(Array.prototype.isPrototypeOf(date));
console.log(Array.prototype.isPrototypeOf(reg));
(ii)判断x是不是由Array这个构造函数创建的
x instanceof Array;
结果为true,说明是数组,结果为false,说明不是数组
console.log(arr instanceOf Array);
console.log(obj instanceOf Object);
console.log(date instanceof Date);
console.log(reg instanceof RegExp);
(iii)Array.isArray(x)
- ES5新增的方法,只有数组可以这么使用
结果为true,说明是数组,结果为false,说明不是数组
console.log(Array.isArray(obj));
(iiii)*输出【对象的字符串】形式
- 在Object的原型上保存着最原始的toString方法
- 原始的toString输出形式:[object 构造函数名]
- ***多态:子对象觉得父对象的成员不好用,就在本地定义了同名函数,覆盖了父对象的成员,不严格定义:同一个方法,不同的人使用,效果不同,有多种形态
固定套路:
Object.prototype.toString.call(x)==="[object Array]"
console.log(Object.prototype.toString.call(arr));
console.log(Object.prototype.toString.call(obj));
console.log(Object.prototype.toString.call(date));
console.log(Object.prototype.toString.call(reg));
f、实现自定义继承
(i)两个对象之间设置继承
子对象.__proto__=父对象;
(ii)多个对象之间设置继承
构造函数名.prototype=父对象;
时机:应该在开始创建对象之前就设置好继承关系
(3)有了原型对象,可以设置共有属性和共有方法
原型对象.属性名=属性值; 原型对象.方法名=function(){}
3、class关键字
简化面向对象(封装、继承、多态)
class 类名 extends 老类{
constructor(name,age,hobby,...){//放在constructor里面的都是自有属性
super(name,age);
this.hobby=hobby;
}//放在constructor外面的都是共有方法
//还会继承到老类所有的API,也可以添加新的
}
4、*****Function:闭包
(1)作用域(2种)
- 全局:随处可用,可以反复使用,缺点:容易被污染
- 函数:只能在函数调用时内部可用,不会被污染,缺点:一次性的,是会自动释放的
(2)***函数的执行原理
a、程序加载时
- 创建执行环境栈(ESC):保存函数调用顺序的数组
- 首先压入全局执行环境(全局EC)
- 全局EC引用着全局对象window
- window中保存着我们全局变量
b、定义函数时
- 创建函数对象:封装代码段
- 在函数对象之中有一个scope(作用域)属性:记录着函数来自己的作用域是哪里
- 全局函数的scopt都是window
c、调用前
在执行环境栈(ESC)压入新的EC(函数的EC) 创建出活动对象(AO):保存着本次函数调用时用到的局部变量 在函数的EC中有一个scope chain(作用域链)属性引用着AO AO有一个parent属性是函数的scope引用着的对象
d、调用时
正是因为有前面三步,才带来变量的使用规则:优先使用自己的,自己没有找全局,全局没有就报错
e、调用完
函数的EC会出栈,没人引用AO,AO自动释放,局部变量也就释放了
(3)*****闭包
希望保护一个可以【反复使用的局部变量】的一种词法结构,其实还是一个函数,只是写法比较特殊
何时使用:希望保护一个可以【反复使用的局部变量】的时候
如何使用:
两个函数进行嵌套
外层函数创建出受保护的变量
外层函数return出内层函数
内层函数再操作受保护的变量
强调:
a、判断是不是闭包:有没有两个函数嵌套,返回了内层函数,内层函数再操作受保护的变量
b、外层函数调用了几次,就创建了几个闭包,受保护的变量就有了几个副本
c、同一次外层函数调用,返回的内层函数,都是再操作同一个受保护的变量
缺点:
受保护的变量,永远都不会被释放,使用过多,会导致内存泄漏 - 不可多用
问题:
应该在哪里去使用?
三个事件需要防抖节流 - 共同点:触发的速度飞快
elem.onmousemove - 鼠标移动事件
input.oninput - 每次输入/改变都会触发
onresize - 每次窗口改变大小都会触发
防抖节流的公式:
function fdjl(){
var timer=null;
return function(){
if(timer!==null){
clearTimeout(timer);
timer=null;
}
timer=setTimeout(()=>{
操作;
},500)
}
}
let inner=fdjl();
elem.on事件名 =function(){
inner();
}
总结
两链一包:
作用域链:以函数的EC的scope chain属性为起点,经过AO,逐级引用,形成的一条链式结构,我们就称之为叫做作用域链
作用:查找变量,带来了变量的使用规则:优先使用自己的,自己没有找全局,全局没有就报错
原型链:每个对象都有一个属性叫做.proto,可以一层一层的找到每个对象的原型对象,最顶层的就是Object的原型,形成的一条链式结构,我们就称之为叫做原型链
作用:查找属性和方法,哪怕自己没有也会顺着原型链向上找,怪不得人人都能用toString(),因为他在最顶层
闭包:希望保护一个可以【反复使用的局部变量】的一种词法结构,其实还是一个函数,只是写法比较特殊
作用:专门用于防抖节流
5、保护对象
保护对象的属性和方法
(1)四大特性
{
value:1001,//实际保存值的地方
writable:true,//开关:控制着是否可以被修改 - 默认值:都为true
enumerable:true,//开关:控制着是否可以被for in 循环遍历到 - 默认值:都为true
configurable:true,//开关:控制着是否可以被删除 - 默认值:都为true,总开关:一旦设置为false,其他特性不可以在修改,而且它本身也是一旦设置为false,不可逆
}
修改四大特性:
a
Object.defineProperty(对象名,"属性名",{
修改四大特性
})
调用一次方法只能保护一个属性的四大特性
b
Object.defineProperty(对象名,{
"属性名":{修改四大特性},
...
})
至少方法只调用了一次
四大特性甚至不能防止添加
(2)三个级别
a、防扩展
防止添加
Object.preventExtension(obj);
b、密封
防止添加和删除
Object.seal(obj);
c、冻结
防止添加和删除和修改
Object.freeze(obj);
保护对象不重要
- 如果不用面向对象开发,那也没有对象可保护
- 前辈们都没保护,我们也不用
- 别人一般来说不可能知道你取的对象名叫什么
***四大特性,其实应该叫六大特性 - 可以帮助我们做出动态数据
Object.defineProperty(obj,"name",{
get:()=>{
console.log("获取数据会进行拦截");
},
set:v=>{
console.log("设置数据会进行拦截");
v;//拦截到的新数据
d1.innerHTML=v;
}
})
理解到这一块即可,以后三阶段有一个框架叫做vue,vue中的所有数据都是动态数据,意味着他的底层就是做了拦截操作
6、*对象的深浅拷贝
a、浅拷贝
var obj1={"name":"obj1"};
var obj2=obj1;
b、深拷贝
var obj1={"name":"obj1"};
var obj2={...obj1}
c、以后如何脱掉后端传来的数据
后端穿衣服:var jsonText=JSON.stringify(obj1); - Node.js就是这句话,Node.js也是"js",历史上第一次一门语言可以通吃前后端
前端脱衣服:var jsonObj=JSON.parse(jsonText);
此方法也能实现深拷贝
7、*Error对象
错误对象
(1)以后工作/学习的目的
- 快速找到错误
- 记得要放用户
(2)***浏览器自带4种类型:可以快速找到自己的错误
- 语法错误:SyntaxError - 一定是你的符号写错了
- 引用错误:ReferenceError - 没有创建就去使用了
- 类型错误:TypeError - 不是你的方法,你却去使用了
- 范围错误:RangeError - 只有一个API会碰到:num.toFixed(d);//d取值范围:0~100之间
(3)只要发生错误,就会报错,会导致后续代码终止(闪退)
错误处理:就算发生错误,我们也不希望报错,而是给出一个错误提示,让后续代码可以继续执行
语法:
try{
只放入你可能出错的代码
}catch(err){
发生错误后才会执行的代码
alert(err);//err就是我们的错误提示:只不过是英文的
alert("中文的错误提示,来提示用户");
}
try...catch...的性能非常差,几乎是所有代码里最差的,放在try中的代码效率会被降到最低
*可以用一个技术代替他:分支结构
*开发经验:记住一切的客户端输入/用户输入都是坏人 - 但是你不必担心,只要你做好该做的防护就不会出错(!isNaN、正则)
(4)抛出自定义错误,只要是错误,后续代码都不会执行
throw new Error("自定义错误消息");
(5)ES5:严格模式
开启:"use strict"; - 写在任何一个作用域的顶部都可以
作用:
a、禁止了全局污染,使用变量之前必须先创建变量
b、将静默失败升级为了错误
8、柯里化函数
function add(a){
return function(b){
return function(c){
console.log(a+b+c);
}
}
}
add(3)(5)(7);
9、百度/高德地图
(1)定位技术有哪些?(不重要,吹牛用)
- GPS - 美国(卫星定位)
- 北斗 - 中国自主:最初并不是民用,而是军用,使用的是惯性定位(定位并不精准)
- 后期发展为了民用,添加了卫星定位了(更精确了)
- 物联网、车联网...
- 基站定位 - 信号的范围发射,可以检测到你现在的信号属于哪个范围
- IP定位 - 在网不要乱说话,乱造谣,要坐牢
(2)网站定位技术都是使用GPS
不用学习如何使用GPS,只需学习如何使用百度/高德地图
(3)开发者如何使用百度/高德地图
- 打开百度:搜索百度/高德地图开放平台
- 注册、登录百度账号
- 拉到最下面、点击立即注册成为开发者
- 进入控制台
- 应用管理->我的应用->创建应用->实名认证->获得密钥(AK)
- 鼠标移动到导航条->放到开发文档上->web开发->JavaScript API->示例DEMO
- 挑选出你喜欢的地图,然后复制全部代码(HTML/CSS/JS),到你需要的位置
- 查询经纬度:api.map.baidu.com/lbsapi/getp…
- 百度地图你看上的每一个都可以混搭在一起,但是一定要注意版本:普通版(老) 和 webGL(新),是不可以混搭的
10、匿名函数
没有名字的函数,有两种用法
(1)自调
只能执行一次,好处:函数中的没用的变量是会自动释放的,他可以用于代替全局代码写法,两者很相似:都只会执行一次,但是自调会释放
(function(){
console.log(1);
})();
(2)回调
匿名函数不是自调,就是回调
elem.on事件名=function(){}
arr.sort(function(){})
var obj={
"方法名":function(){}
}
一切的回调函数,都可以简化为箭头函数
11、设计模式
不仅仅局限于前端,它是一种编程思想,越来越复杂,对于我们前端人员要求也越来越高了
有21种设计模式
(1)单例模式
也称之为单体模式,保证一个类仅有一个实例对象,并且提供一个访问它的全局访问点,为三阶段的vue,new Vue();
举例:一个班级只有一个班主任,只有一个太阳,一个国家只有一个主席,"唯一" "便于访问(全局访问的)",行为对象变成单例
如何实现:
最简单的单例模式:利用ES6的let不允许重复声明的特性,刚好就符合了单例的特点
let obj={
"name":"袍哥1",
"getName":()=>this.name,
}
不推荐这种写法:
1、污染命名空间(容易变量名冲突)
2、维护时不容易管控(搞不好就直接覆盖了)
推荐写法:
let h52301=(function(){
let state=null;
return function(name,age){
this.name=name;
this.age=age;
if(state){
return state;
}
return state=this;
}
})();
h52301.prototype.sayHello=function(){
console.log(this.name);
}
var llc=new h52301("兰淋川",18);
var yxw=new h52301("尹星文",19);
console.log(llc);
console.log(yxw);
xonsole.log(yxw==llc);
llc.sayHello();
yxw.sayHello();
何时使用:我们的页面只有一个弹出框
(2)发布订阅模式
为三阶段vue的bus总线用到的底层原理就是我们的发布订阅模式
let obj={};
//创建订阅者
function on(id,fn){
if(!obj[id]){//判断有没有此id(有没有这个人),没有我就创建了一个空数组
obj[id]=[];
}
obj[id].push(fn);
}
on("老袍",(msg)=>{console.log("小兰来了",msg)});
on("老袍",(msg)=>{console.log("小尹来了",msg)});
on("老袍",(msg)=>{console.log("小张来了",msg)});
on("老袍",(msg)=>{console.log("小李来了",msg)});
on("老袍",(msg)=>{console.log("小赵来了",msg)});
console.log(obj);//{"老炮":[fn,fn,fn,fn,fn]}
//发布者的操作
function emit(id,msg){
obj[id].forEach(fn=>fn(msg));//obj[""老炮].forEach
}
btn.onclick=()=>{
emit("老炮","一支穿云箭");
}
12、事件轮询
js其实是单线程引用,代码必然是从上向下,一步一步的执行,如果某一块代码非常耗时,可能会导致整个页面卡住,尤其是把js放在head之中,会看到页面是一个白板
(1)宏任务
不会再卡住我们的单线程应用,可以让后续代码先走,我们慢慢跟着来,但是问题在于,多个宏任务同时存在,到底谁先执行谁后执行,分不清
a、定时器:setInterval和setTimeout
b、Ajax
(2)微任务
ES6提供的Promise对象 - 可以控制异步代码,依然是异步代码,但是可以控制执行的顺序了
function ajax1(resolve){
setTimeout(()=>{
console.log(1);
resolve();
},Math.random()*5000);
}
function ajax2(){
return new Promise(resolve=>{
setTimeout(()=>{
console.log(2);
resolve();
},Math.random()*5000);
})
}
function ajax3(){
return new Promise(resolve=>{
setTimeout(()=>{
console.log(3);
resolve();
},Math.random()*5000);
})
}
new Promise(ajax1).then(ajax2).then(ajax3);//promise的回调函数提供了一个形参函数,可以用于放行
console.log("后续代码");