一、JS概述
1.1、什么是JavaScript
-
JavaScript是一种运行于JavaScript解释器/引擎中的解释型脚本语言。
-
JavaScript解释器作为JS脚本的运行环境,有如下两种呈现方式。
- 独立安装的JS解释器–如Nodejs平台(服务端js)
- 嵌入在浏览器中的JS解释器–如Chrome浏览器(客户端的js)
-
JS和Java的区别
- Java编译型语言,代码全都编译完再去执行
- JS是解释型语言,代码无需编译,由js引擎解释执行。
-
编译型语言(JAVA,PHP),运行起来更快,解释性语言需要兼容不同的环境
-
JavaScript当前的应用领域十分广泛,例如
- 编写在网页中,操作网页内容,首先各种用户交互效果
- 编写移动APP应用
- 编写桌面GUI应用
- 编写命令行应用
- 编写企业服务器端应用
- 2D绘图和3D游戏建模
- web VR
- web AR
1.2、JavaScript发展史
-
1992年,Nombas公司为自己cEnvi软件开发了一款脚本语言ScriptEase,可以嵌入在网页中。
-
1995年,Netscape公司为自己的Navigator2.0浏览器开发了另一种客户端脚本语言Livescript,为了赶时髦重命名为JavaScript,但其实和Java没任何关系。
-
1996年,Microsoft公司为了进军浏览器市场,在IE3.0浏览器发布了一个Javascript的克隆版,称为JSscript。
-
1997年,Javascript1.1作为草案提交给了ECMA,各个厂家合力推出了ECMA-262标准,定义了全新的ECMAScript标准脚本语言,各大浏览器厂家开始努力将ECMAScript作为实现的标注和目标。
-
2009年,Commonjs的提出,正式确立了JS向服务器端和桌面端应用发展的方向。
-
CommonJS规范在传统的基于浏览器的JS API基础上,指定了更加丰富的API规范,提供了类似于Java,Python,Ruby等语言一样强大的编程能力,并实现跨硬件平台,跨操作系统,跨解释器的应用体验。
-
目前实现了CommonJS规范的技术有,NodeJS,RequireJS,SealJS等等。
1.3、JavaScript特点
-
代码可使用任何文本编辑工具编写,语法类似于C和Java
-
脚本文件无需编译,由js引擎解释执行。
-
弱类型语言
-
基于对象
-
跨平台性
二、使用JavaScript
2.1、搭建环境
- 安装独立的javascript解释器(v8)
- 使用浏览器内核中嵌入的Javascript解释器
2.2、执行JavaScript
-
使用独立的JavaScript解释器
- 一行一行的输入要执行的脚本并执行(交互型执行)
- 把多行要执行的脚本写在脚本文件中,批量执行(脚本式执行)
-
使用浏览器中内嵌的JavaScript解释器
- 直接在控制台输入脚本并执行(交互型执行)
- 将JS脚本嵌入在HTML页面中执行(脚本执行)
defer和async
连环问 preload prefetch dns-prefetch
- preload 表示资源在当前页面使用,浏览器会优先加载
- prefetch 表示资源可能在未来的页面(如通过链接打开下一个页面)使用,浏览器将在空闲时加载。
<!-- proload -->
<link rel="preload" href="style.css" />
<link rel="preload" href="main,js" />
<!-- prefetch -->
<link rel="prefetch" href="other.js">
<!-- 引用css -->
<link rel="stylesheet" href="style.css">
连环问 dns-prefetch和preconnnect有什么作用?
- 一个http请求,第一步就是dns解析得到ip,然后再进行TCP连接,连接成功过后再发送请求。
- dns-prefetch即DNS预获取,preconnect即预连接。
- 当网页请求第三方资源时,可以提前进行dns查询,tcp连接,以减少请求时的时间。
2.3、JavaScript基础语法规范
- 区分大小写
- 每行代码结尾的分号可加可不加,一般不加
- 分为单行注释//和多行注释/**/
三、变量
3.1、什么是变量
-
在代数中,我们使用字母(比如 x)来保存值(比如 2)
x=2 y=2 sum=x+y
-
通过上面三行语句,能够计算出sum的值为5
-
在JavaScript中,这些字母被称为变量
3.2、变量声明
- 使用关键词var 声明变量,如:
var userName
- 使用"="为变量赋值
var bookPrice=25.5
- 没有初始化的变量自动取值为undefined
var publicData
,console.log(publicData)
3.3、变量命名规范
- 可以包含字母,数字,下划线,美元符号($)
- 不能以数字开头
- 常用于表示函数、变量等的名称
- 名称最好有明确的含义
- 可以使用"匈牙利命名法"、“下划线命名法(user_password)”、"小驼峰命名法(userName)"或"大驼峰命名法"之一,在开发团队内进行统一协调
3.4、命名需要符合标识符语法要求
- 不允许使用语言关键字和保留字作变量名
- 标准关键字
break
case
catch
continue
default
delete
do
if
function
with
throw
undefined
等等 - 预保留的关键字
class
int
float
等等
注意
- 同一变量允许多次赋值,同一var可以声明多个变量 var id=108,title=‘小米’
- 中文也可以作为变量名,不常用
四、常量(const)
不允许重新赋值,不允许不赋值
const pi=3.14
//pi=3.1415
console.log(pi)
五、数据类型
-
ECMAScript数据类型
-
原始类型(不同的JS运行环境支持的原始类型都是一样的)
- number:数字
- string:字符串
- boolean:布尔
- null:空
- undefined:未定义
-
引用类型(不同的JS运行环境提供的应用类型区别很大)
- Object:对象
- Function:函数
- Number:数字
- String:字符串
- Boolean:布尔
- Date:日期
- Error:错误
- Array:数组
-
-
引用型数据和原始数据的区别与释放
- 原始类型数据存储在栈内存中,引用类型数据存储在堆内存中
- 数据如果不需要就会被回收来释放内存,栈内存里的数据用完自动释放,堆内存的数据释放销毁
person1=null
5.1、Number类型
-
数字类型
- 既可以表示32位的整数,也可以表示64位的浮点数
-
整数
- 十进制:逢十进一的整数,如1234567890
- 八进制:逢八进一的整数,如01235670
- 十六进制:逢十六进一的整数,如0x123456789abcdef0
-
浮点数
- 使用小数点记录数据,如95.5,3.1415926
- 使用指数记录数据,如4.3e23,4.3E-23
var num1=13; //13
var num2=010; //8
var num3=0xa; //10
var num4=3.14; //3.14
var num5=3.14e3; //3140
var num6=3.14E-3; //0.00314
console.log(num6,typeof num6) //显示数值类型
5.2、String类型
-
字符串类型
- 表示一系列的文本字符数据,如性别、姓名、住宅等
- 由Unicode字符、数字、标点符号组成的序列
- JavaScript不像Java语言那样严格区分字符和字符串类型
- 首尾由一对单引号或双引号括起
-
所有变量值加引号都变成了字符串类型
-
charCodeAt()
查字符的unicode码 "一"最小
5.3、Boolean类型
-
布尔类型
- 仅有两个值:true和false
- 也代表1和0
- 实际运算中 true1,false0
var b1=true
var b2=3>4
console.log(b1,typeof b1)
5.4、Null 类型
- null的数据类型是一个Object
- 原型链的顶点也是null
var person=null
console.log(person,typeof person);//null Object
5.5、undefined 类型
- 语义:“不存在该数据”
- 声明了变量但从未赋值,或者对象属性不存在
六、数据 隐式转换和强制转换
6.1、隐式转换
-
JavaScript 属于弱类型程序语言
- 变量在声明时不需要指定数据类型
- 变量由赋值操作确定数据类型
- 某个变量可以先后赋值为不同类型的值
-
不同类型数据在计算过程中会自动进行切换
- 数字 + 字符串 = 字符串
- 数字 + 布尔值 = 数字
- 字符串 + 布尔值 = 字符串 (布尔值转换为true或false)
- 布尔值 + 布尔值 = 数字 (布尔值转换为数值1或0)
var n1=2+'hello';
console.log(n1,typeof n1);
//2hello string 加号表示字符串拼接
var n2='tedu'+true;
console.log(n2,typeof n2);
//tedutrue string 加号表示数字的运算
var n3=2+true;
console.log(n3,typeof n3);
//3 number
var n4=false+true;
console.log(n4,typeof n4);
//1 number
var m1=5-'3';
console.log(m1,typeof m1);
//2 number
var m2='8'-'4';
console.log(m2,typeof m2);
//4 number
var m3=3*true;
console.log(m3,typeof m3);
//3 number
var m4='9'/3;
console.log(m4,typeof m4);
//3 number
var m6=1+undefined;
console.log(m6,typeof m6);
//NaN number
var m7='1a'*3;
console.log(m7,typeof m7);
//NaN number
//总结:
//加减乘除里,‘2‘3’只有加法的隐式转换字符串会有拼接,当出现加号外的其他运算符都自动转为数值.
//NaN not a number 不是一个数字
//NaN和任何值运算,都返回NaN,不包括和字符串的相加
6.2、强制转换
使用数据类型转换函数(全局函数)
- toString()
- parseInt()
- parseFloat()
- Number()
七、运算符
表达式: 可以用于计算,可能会产生一个值的式子
- 算数运算
+ - * / % ++ --
- 关系运算
> <>= <= == === != !==
- 逻辑运算
&& || !
- 位运算
& | ~ ^ << >> >>>
- 赋值运算
= += -= *= /= %=
- 字符连接运算
+
- 条件(三目)运算
?:
- 特殊运算符
typeof instanceof void delete
7.1、算数运算符
-
加(+)、减(-)、乘(*)、除(/)、求余(%)
-
可以表示减号,也可以表示负号,如:x=-y+
可以表示加法,也可以用于字符串的连接
var num1=20-5 //15 var num2='20'-'5' //15 var num3='20a'-5 //NaN var num4='20'-'5a' //NaN
-
使用%运算符,表示求余运算,或者称为取模运算
var i = 10 % 3 //i值为1 var i = 10.5 % 3 //i值为1.5 var i = -10 % 3 //i值为-1 var i = 10 % -3 //i值为1 var i = -10 % -3 //i值为-1 console.log(5%3) //2 console.log(3%5) //3 除不尽返回3 //案列:隔行变色 console.log(1%2) //1 console.log(2%2) //0
-
使用
++
和--
运算符-
案例1
i ++ ; //相当于i=i+1; i -- ; //相当于i=i-1; i = 1; j = i++ // i结果是2,j的结果是1 (赋值以后才自增) i = 1; j = ++i // i结果是2,j的结果是2 (自增以后才赋值)
-
案例2
var a=“1”; a++; console.log(a,typeof a); //2 number发生了隐式转换number var i=5; //先让i赋值给j,然后再执行自增 var j=i++; console.log(j);//5 console.log(i);//6 var i=5; //先让i执行自增,然后再赋值 varj=++i; console.log(j);//6 console.log(i);//6
-
案例3
// 判断输出结果 var n=2 var r=n+++++n //2+4 console.log(r) //6 console.log(n) //4
-
7.2、关系运算符 (判断数据大小关系 返回值为布尔值)
-
关系运算用于判断数据之间的大小关系
>
大于<
小于>=
大于等于<=
小于等于==
等于!=
不等于
-
表达式的值为boolean类型(true 或 false)
- 数字字符串和数字作比较,字符串数字转为数值
- 两个都是字符串,比较的是首字符的unicode码
- 数字和字符串比较 ,结果是将字符串转为数值,隐式转换默认调用number,NaN,NaN和任何数比较或运算结果都是NaN即false,除了加号拼接
console.log(3>10) //false
console.log('3'>'10') //true
console.log('3'>10) //false
console.log('3a'>10) //false
console.log('3a'==10) //false
console.log('3a'《10) //false
// 隐式转换默认调用number,NaN
// NaN和任何数比较或运算结果都是NaN,除了加号拼接
-
全等运算符,不会发生’隐式类型转换’,只要两个数据类型或数值之一不相等,即判断为不相等
- 全等:
===
- 类型相同
- 数值相同
- 不全等:
!==
- 全等:
var a1="10"
var a2=10
console.log(a1==a2) //true
console.log(a1===a2) //false
console.log(a1!=a2) //false
console.log(a1!==a2) //true
7.3、逻辑运算符
-
逻辑非(!)、逻辑与(&&)、逻辑或(||)
var age=11; console.log( age>=60 || age<=12 ) //true //工资 5000~7000 varsalary=8000; console.log( salary>=3000 && salary<=8000 );//true console.log(!true ); //false 案列1://声明变量保存用户名,密码,判断用户名是否为root,密码是否为123456,同时满足打印true,否则打印false。--à 常用于登录 var uname='root'; var upwd='888888'; console.log(uname==='root' && upwd==='123456' ); 案列2://声明变量保存用户输入的值,如果值是root,或者是13112345678,或者是root@tedu.cn,如果满足一项打印true,否则打印false var input='root'; console.log(input==='root' || input==='13112345678' || input==='root@tedu.cn' );
-
关于"短路逻辑"的问题
- 对于&&运算
false && ? => false
- 对于||运算
true || ? => true
案列3 var num=3; num>5 && console.log(a); //不执行不报错 num<1|| console.log(b); //报错 b is not defined 案列4 var i=10; var j=10; var k=i-->0 || j++>0; console.log(i,j,k);//9 10 true 案列5 使用短路逻辑 -声明变量保存年龄,如果满18岁打印成年人 var age=17; age>=18&& console.log('成年人'); //成年人
- 对于&&运算
7.4、位运算符 (模拟计算机底层运算过程)
位运算,将数字以二进制形式进行运算,10进制转换为2禁止,再转换为10进制
按位与(&)
上下两位都是1,结果是1,否则是0按位或(|)
上下两位含有1,结果是1,否则是0按位异或(^)
上下两不同为1,相同为0按位右移(>>)
删除二进制的最后一位或者多位,每次缩小到原来的一半或者更多按位左左移(<<)
在二进制的最后添加为0,成倍增加
//按位与 & 1 1 1
console.log(5&7);
/*
101 5
111 7
--------------
101 5
*/
console.log(5&12);
/*
0101 5
1100 12
---------
0100 4
*/
//按位或 | 1 1 1/1 0 1
console.log(5|12);
/*
0101ᅠᅠᅠ5
1100 12
-------
1101 13
*/
console.log(9|17);
/*
01001
10001
---------
11001 25
*/
//按位异或 ^ 不同为1 相同为0
console.log(5^12);
/*
0101
1100
1001 9
*/
//按位右移 移动几位 末尾删几个
console.log(49>>1); //往右移动一位 24
console.log(49>>2); //往右移动二位 12
console.log(49>>3); //往右移动三位 6
console.log(49>>6); //……………. 0
/*
32+16+1
100000+10000+1
110001
11000 24
1100 12
*/*/
//按位左移 移动几位末尾添加几个0
console.log(3<<1); //6
console.log(3<<2); //12
console.log(3<<3);//24
/*
11
110 6
1100 12
11000 24
*/
7.5、运算符的优先级
- 案例
- 判断一个年份是否为闰年,如果是打印闰年;能被4整除,并且不能被100整除,或者能被400整除
&&的优先级大于|| year%4===0&& year%100!==0 || year%400===0&& console.log('闰年');//无显示 (year%4===0 && year%100!==0 || year%400===0)&& console.log('闰年');
7.6、赋值运算
- 使用
=
进行赋值运算
Var score=90;
Var isFullMarks=score==100;
Var x=y=z=10; //这种写法不推荐使用
-
扩展赋值表单式
var a=10 a+=3 //相当于 a=a+3 a-=3 //相当于 a=a-3 a*=3 //相当于 a=a*3 a/=3 //相当于 a=a/3 a%=3 //相当于 a=a%3
-
在绝大多数变成语言种,a+=3由于运行时可以进行优化,执行效率都要优于a=a+3
-
案例
// 赋值运算符的优先级小于&&和|| var price=120 price>=100 && (price*=0.8) console.log(price) //96
7.7、三目运算符
?:
需要对三个表达式进行运算 表达式1?表达式2:表达式3
-
表达式1的值应该是boolean类型,表达的含义
- 若表达式1为true,则整个表达式的值为表达式2的值
- 若表达式1为false,则整个表达式的值为表达式3的值
-
案例
var age = 20
var msg = age >= 18 ? "成年人":"未成年人"
var a = 10
var b = 20
var max =a>b ? a : b
八、流程结构 / 流程控制概述
- 程序 = 数据 + 算法
- 任何复杂的程序算法都可以通过
顺序
,分支
,循环
三种基本的程序逻辑组合实现
8.1、分支结构
- 程序可以在运行过程中,在不同的情况下有选择的执行某些语句
8.1.1、if / if-else
-
当条件满足时运行某些语句,当条件不满足时则不运行这些结构----if结构
语句0 if(逻辑表达式){ 语句1 语句2 } 语句3 // 执行语句0 // 判断逻辑表达式的值 // 若值为true,则执行if语句块中的语句 // 若值为false,则不执行if语句块中的语句 // 执行语句3
//满30减10 var total=31; if(total>=30){ //在原来上减10 total-=10; }; console.log(total); //声明变量保存一个人的年龄,如果满18岁打印成年人 var age=22; if(age>=18){ console.log('满十八岁'); }
- if后的大括号中只有一条语句可以省略大括号。
-
当条件满足时运行某些语句,当条件不满足时运行另外的一些语句----if-else结构
语句0 if(逻辑表达式){ 语句1 }else{ 语句2 } 语句3 // 执行语句0 // 判断逻辑表达式的值 // 若值为true,则执行if语句块中的语句1 // 若值为false,则执行if语句块中的语句2 // 执行语句3
// 判断一个人是否为成年人 var age=13; if(age>=18){ console.log('成年人'); }else{ console.log('未成年人'); } console.log('判断结束'); true和false最后都会执行 // 声明变量保存性别的值(1/0),如果是1打印男,否则打印女 var sex=0; var result; if(sex){ result='男'; }else{ result='女'; } console.log(result); //三目运算符 (条件表达式? 表达式1 : 表达式2) result=sex ? '男' : '女'; console.log(result); //区别:三目只能写一个表达式,ifelse的大括号里可以写多个表达式,所以简单的语句用三目表达式,复杂的用if else表达式。 // 声明变量分别保存用户名和密码,如果用户是root,并且密码是123456,打印登录成功,否则打印登录失败 var inputUname='hello'; var inputUpwd='123456'; if (inputUname==='root' && inputUpwd==='123456') {console.log('登录成功'); }else{ console.log('登录失败'); } //登陆失败 //三目运算符 inputUname==='root' && inputUpwd==='123456' ? console.log('登录成功') : console.log('登录失败') //登录失败
8.1.2、else-if
-
事实上,else if结构就是if else嵌套的简便写法
// if-else的嵌套写法 if(score>=90){ ... ... ... }else{ if(score>=80){ ... ... ... }else{ ... ... ... } } // else-if if(score>=90){ ... ... ... }else if(score>=80){ ... ... ... }else{ ... ... ... }
-
案例
-
声明变量保存订单的状态码 (1,2,3,4,5…)
-
1-等待付款 2-等待发货 3-运输中 4-已签收 5-已取消
-
根据状态码打印对应内容,不存在的状态码打印"非法的状态"
var status=3; if(status===1){ console.log('等待付款'); }else if(status===2){ console.log('等待发货'); }else if(status===3){ console.log('运输中'); }else if(status===4){ console.log('已签收'); }else if(status===5){ console.log('已取消'); }else{ console.log('非法的状态码'); }
-
使用弹出提示框输入商品的价格和数量并计算总价,如果满500打九折,使用变量保存余额,如果足以支付,打印"支付成功",否则打印"支付失败"
var price,num,total,money=800; price=prompt('请输入商品的价格'); num=prompt('请输入商品的数量'); total=price*num;//隐式转换自动转为数值 if (total>=500) { total*=0.9 } console.log(total); if(money>=total){ alert('支付成功'); console.log('当前余额为'+(money-total));//加法隐式字符串的拼接、优先级 }else{ alert('余额不足'); }
-
8.1.3、switch-case语句
-
switch case语句是一种特殊的分支结构,可以根据一个表达式的不同取值,从不同的程序入口开始执行
-
通常case1、case2、…、caseN对应完全不同的操作,可以和break语句配合使用,执行完相应语句后即退出switch块,不继续执行下面的语句。break语句的作用在于跳出switch结构
var num=2; switch(num){ //没有break继续执行下一句语句 case1: console.log('查询余额...'); break; case2: console.log('在线充值...'); break; case3: console.log('转接人工服务...') }
-
switch-case的优势
- switch-case常常和break语句结合使用实现分支的功能
- switch-case在实现分支功能时和if-else的主要区别在于
- if…else… 可以判定相等或不等的情形,适用性更广
- switch…case… 结构的结构更清晰,效率更高,但一般只用于指定变量相等于某个范围内的某个特定的值
-
案例
使用switch-case语句根据一个人的成绩做出对应评价
var score=56; switch ( parseInt(score/10) ) //除10取整数 { case 10: case 9: console.log('A'); break; case 8: console.log('B'); break; case 7: //自动转入下一语句 case 6: console.log('C'); break; default: console.log('D'); }
8.2、break的使用
-
循环次数不确定,一般搭配死循环的使用
-
forEach不支持break的使用
-
案例1
var i=1; while(true){ console.log(i); //当i为10的时候,结束循环 if(i===10){ break; } i++; } //利用死循环
-
案例2
//猜数字的游戏 //声明变量保存一个数字,使用无限循环弹出提示框并输入数字,如果输入的数字大于保存的数字,警示框提示'猜大了',同理,猜小了,猜对了 var num=4999; while (true) { varstr=prompt('请输入要猜的数字'); if(isNaN(str)){ alert('请输入一个数字');//如果输入的值为NaN }elseif(str>num){ alert('猜大了'); }elseif(str<num){ alert('猜小了'); }else{ alert('猜对了'); //结束循环 break; } }
8.3、循环/遍历
- 循环,就是一遍又一遍的重复执行相同或者相似的代码
- 循环结构的两个要素
- 循环体–要执行的相同或相似的语句
- 循环条件–重复执行的次数,或者继续执行循环的条件
8.3.1、while循环
-
while循环语法格式
while(boolean表达式){ 循环体语句 } // 若boolean表达式为true,则执行一遍循环体中的语句,然后再判定一次boolean表达式,若为true,则再次执行一遍循环体中的语句,直到boolean表达式的值为false,则循环结束。 while(isHungury){ eatSomething() } // 需要注意的是,一般情况下,循环操作中会存在使得循环终止的可能性,否则将成为死循环
-
案例1
// 打印1~10 var i=1; //初始值 while(i<=10){//循环条件 //循环体 console.log(i); i++; //增量 } console.log('当循环结束的时候,i的值是'+i)//拼接
-
案例2
// 循环打印70 65 60 55 50 var i=70 while (i>=50) { console.log(i); i-=5; }
-
案例3
//打印1-100之间所有的奇数 var i=1; while(i<=100){ //奇数 if(i%2===1){ console.log(i); } i++;} //计算1-100之间所有整数的和 var i=1; var sum=0;//用于记录和 while (i<=100) { //把每次产生的值加到sum sum+=i //console.log(sum); i++ } //打印sum最终的结果 console.log(sum); //计算1-100之间所有偶数的和 var i=1; var sum=0; while(i<=100){ if(i%2===0){ //加到一起 sum+=i; } i++; } console.log(sum);
8.3.2、do-while循环
-
do-while循环语法格式
do{ 可执行的语句; // 无论循环条件是否满足,循环体至少执行一遍 }while(boolean表达式) // 先执行一次循环体中的语句,然后判定boolean表达式的值,若为true,则继续执行循环体中的语句;然后再继续判定boolean表达式的值...直到boolean表达式的值为false退出 do{ eatSomething() }while(isHungry)
-
案例1
// 打印30-50之间的所有整数 var i=30; do { console.log(i); i++ }while (i<51);
-
案例2
// 打印1-100的所有奇数
var i=1;
do
{ //判断是否为奇数
if(i%2===1){
console.log(i);
}
i++
}while (i<=100);
* 案例3
```javascript
//计算1~100之间所有能被7整除的数字的和
var i=1;
var sum=0;
do
{
if(i%7===0){
sum+=i;
}
i++;
}while (i<=100);
console.log(sum);
//计算1~20之间所有能被3整除的数字的乘积
var i =1;
var s=1;
do
{
if(i%3===0){
s*=i;
}
i++;
}while (i<=20);
console.log(s);
while和do-while语句的区别
- while循环,先判断再执行
- do-while循环,先执行再判断,例如ATM机输密码
- 当初始情况不满足循环条件时,while循环一次都不会执行;do-while循环不管任何情况都至少执行一次。
//ATM机输密码,先输密码再判断对错。
//使用while循环
var upwd=123456;
while(true){
varstr=prompt('请输入密码');
if(str===upwd){
break;
}
}
//使用do-while循环
var upwd=123456;
do
{
varstr=prompt('请输入密码');//输入密码
}
while ( str!=upwd);
8.3.3、for语句的使用
-
for循环的语法格式
// 初始值 循环条件 增量 for(表达式1;表达式2;表达式3){ 循环体语句 }
-
for循环执行过程
-
计算表达式1的值
-
计算表达式2(boolean表达式)的值,如果为true则执行循环体,否则退出循环
-
执行循环体
-
执行表达式3
-
计算表达式2;如果为true则执行循环体,否则退出循环
-
如此循环往复,直到表达式2的值为false
-
案例
//1-50之间所有能被3整除 for (var i=1;i<=50;i++ ) { if(i%3===0){ console.log(i); } } //计算101-200之间所有的偶数和 for (var i=101,sum=0;i<=200;i++) { if(i%2==0){ sum+=i; } } console.log(sum); //打印本世纪所有闰年 for (var i=2000;i<2100;i++ ) { if(i%4===0 && i%100 !==0||i%400===0){ console.log(i); } } //九九乘法表的第五行 //1*5= 5 ,2*5=10 ,3*5=15 ,4*5=20 ,5*5=25 for(var i=1,str='';i<=5;i++){ //str+=i; //1 2 34 5在一行 str+=i+'*5='+i*5+' '; //字符串的拼接 } console.log(str);
-
-
for语句三个表达式特殊用法
-
表达式1位置内容为空
var sum=0; var i=1; for(;i<=100;i++){ sum+=i; } console.log('1-100的和为:',sum) //1-100的和为: 5050
-
表达式3的位置内容为空时
var sum=0; for(var i=1;i<=100;){ sum+=i; i++; } console.log('1-100的和为:',sum) //1-100的和为: 5050
-
表达式1,2,3位置内容均为空时
for(;;){ console.log('我要读书...') //死循环 }
-
-
循环中使用break语句
- break用于循环,可使程序终止循环而执行循环后面的语句,常常与条件语句一起使用
var sum=0 for(var i=0;i<=100;i++){ if(sum>=4000){ break } sum+=i } // 当总和大于等于4000时,退出循环
-
循环中使用continue 语句
- continue关键字只用于循环中
- 其作用为跳过本次循环体中剩余语句而执行下一次循环
//打印1-10之间所有整数,不包含5 for(var i=1;i<=10;i++){ //当i为5,跳过后续语句 if(i===5){ continue; } console.log(i); } //计算1-100之间所有偶数的和,遇到奇数跳过 for (var i=1,sum=0;i<=100;i++){ //如果i为奇数,跳过 if(i%2===1){ continue; } sum+=i; } console.log(sum);
-
循环嵌套(一)
-
需要多次重复执行一个或多个任务的问题考虑使用循环来解决
-
for/while/do-while三种循环在很多情况下是可以互换的;一般情况下,for循环使用最多
-
案例1
//***** for (var i=1,str='';i<=5;i++) { str+='*'; } console.log(str); //***** //***** //***** //***** //***** for (var j=1;j<=5;j++ ) { for(var i=1,str='';i<=5;i++) { str+='*'; } console.log(str); } //外层:循环行 //内层:循环列
-
案例2
//* 1 1 //** 2 2 //*** 3 3 //**** 4 4 //***** 5 5 for (var j=1;j<=5;j++ ) { for(var i=1,str='';i<=j;i++) //str写在内层,表示每循环一层把之前一层清空 str 写在外层,表示每循环一层把之前一层加上 { str+='*'; } console.log(str); } //外层:循环行 //内层:循环列
-
案例3
//打印九九乘法表 (外层是九行 /* 1*1=1 1*2=2 2*2=4 1*3=3 2*3=6 3*3=9 */ //外层循环 for(var i=1;i<=9;i++){ //内层循环 for(var j=1 str='';j<=i;j++){ str+=j+'*'+i+'='+(j*i)+''; } console.log(str); }
-
案例4
//打印本世纪前10个闰年 for (vari=2000,count=0;i<=2100;i++) { if(i%4===0 && i%100!==0 ||i%400===0 ){ console.log(i); //如果i为闰年 count++; //每次有一个闰年产生,判断是否为10 if(count===10){ break; } } } console.log(count);
-
-
循环嵌套(二)
-
案例1
for(var i=0,j=8;i<=5;i++,j--){ console.log(i,j) }
-
案例2
// 多个条件表达式后边的起作用 for(i=1,j=8;j>=1,i<=5;i++,j--){ console.log(i,j) } for(var i=1,j=8;i<=5,j>=1;i++,j--){ console.log(i,j) }
-
案例3
//计算1/20+2/18+.....10/2 for(vari=1,j=20,sum=0;i<=10;i++,j-=2){ sum+=i/j; } console.log(sum);
-
8.3.4、for in
- 用于遍历对象
- 只有对象才会关注原型上的方法,而遍历数组更多的是关注遍历数组里面的值,而不是关注原型。
- for in会把原型上的东西都给遍历出来,只是数组都加了隐藏的属性,所以看不吃来。
8.3.5、for of
- 针对于类数组对象,可迭代对象(Array Map Set string)
8.3.6、forEach()
-
forEach(fun)专门用于对原数组中每个元素执行相同的fun函数对象规定的操作
-
forEach(function(elem,i,arr){})
- elem 数组中的每一个值
- i 每一个下标
- arr 数组本身
【面试题】:forEach能否取代for循环
- 不能,forEach不支持continue和break
- forEach的优势就是,它传入一个回调函数,因此形成一个作用域,它内部所定义的变量不会像for循环一样污染全局变量。
- forEach本身无法跳出循环,必须遍历所有的数据才会结束
8.3.6、every()
-
every(fun) 判断数组中每一个元素是否都满足fun函数定义的条件,只有满足才返回true,否则返回false
-
every(function(elem,i,arr){ return xxx})
- every中自带for循环,自动遍历数组中每个元素
- 每遍历一个元素,就会自动调用回调函数
- 如果回调函数返回true,则有必要继续判断下一个
- 如果回调函数返回false,则没必要继续判断下一个,而是退出函数,直接返回false,表示不是所有元素都符合要求
- 如果程序可以正常执行完循环,说明数组中所有元素都符合要求,才能返回true
- 如果回调函数没有返回值,则every会悄悄得添加一个返回值为false
- every得遍历见到false就停止
- 回调函数得返回值如何设置
- 返回值应该为筛选条件–>看数组中得值是否满足条件
- 不满足条件,直接返回false,遍历停止
- 满足条件,遍历继续,都满足,返回true。
- 返回值应该为筛选条件–>看数组中得值是否满足条件
8.3.7、some(func)
some(function(elem,i,arr){ return xxx})
判断数组中是否包含满足fun函数定义得条件得元素。只要包含返回true,否则返回false- 如果回调函数没有返回值.则some会悄悄得添加一个返回值为false
- some 的遍历见到false就继续,见到true才停止。
- 返回值应该为筛选条件->看数组中的值是否都满足条件
- 满足条件,直接返回true,遍历停止
- 值不满足条件,继续遍历,如都不满足,返回false
8.3.8、filter(func)
filter(function(elem,i,arr){ return xxx})
专门用于筛选出数组中符合fun函数判断条件得新数组
8.3.9、map(func)
-
map(function(elem,i,arr){ return xxx})
专门用于基于原数组创建新数组对象 -
对数组中的每一个值做操作 并把操作后的值汇成一个新数组
var newArr=arr.map((val)=>{ return val+2 }); console.log(newArr);
-
map()和forEach()的区别: 前者不改变原数组的内容,后者改变原数组的内容
8.3.10、reduce(func)
reduce((prev,elem,i,arr)=>{ return prev+elem})
reduce()和reduceRight()方法会迭代数组中的每一个元素,汇总出和一个最终结果值返回
【扩展1】 for和forEach跳出循环的方法
-
for循环跳出循环
- break 跳出循环
- continue 跳出当次循环
- return 终止当前函数的执行
var arr = [1,3,5,7,9]; var id = 5; for (var i = 0; i < arr.length; i++) { if(arr[i]=== 1) continue; //跳过当次循环 console.log(arr[i]) if (arr[i] === id) { break; //满足条件,跳出循环 } if (arr[i] === 6) { return; //满足条件,可以终止当前函数 } }
-
forEach跳出循环
-
通过抛出异常的方式跳出循环,return 跳出当次循环
var arr = [1,3,5,7,9]; var id = 5; try { arr.forEach(function (curItem, i) { if(curItem === 1) return; console.log(curItem) if (curItem === id) { throw Error(); //满足条件,跳出循环 } }) } catch (e) { } arr.forEach(function (curItem, i) { if(curItem === 1) return; console.log(curItem) if (curItem === id) { return; //满足条件,跳出当次循环 // break 语法报错; } })
-
九、函数
9.1、全局函数
常用的全局函数
isNaN()
- 用于判断其参数是否一个"非数字(NaN)"值
- 如果把NaN和任何值(包括其自身)相比得到的结果均是false,所以要判断某个值是否是NaN,不能用或=运算符。这种情况下,只能使用isNaN()函数
- 通常用于检测类型转换函数的运算结果,以判断他们表示是否是合法的数字
- isNaN在调用前先转为数值
- 案例
console.log(isNaN('123.4a')) // true console.log(isNaN(parseInt('123.4a'))) //false console.log(isNaN(parseFloat('123.4a'))) //false console.log(isNaN(Number('123.4a'))) //true
toString()
- 转换成字符串,所有数据类型均可转换为string类型
- 案例
//函数:toString() //只能数值和布尔值转换成字符串 varnum=2; varstr=num.toString(); console.log(str,typeofstr); varislogin=true varstr1=islogin.toString(); console.log(str1,typeofstr1); console.log(‘2’.charCodeAt())查字符串的unicode
parseInt()
-
解析成一个string或number的整数部分
-
如果没有可以转换的部分,则返回NaN(Not a Number)
第一个参数将浮点数转换为字符串,取浮点数的整数部分 第一个参数不是字符串,parseInt会调用这个参数的tostring方法,把参数强制转换成字符串 第二个参数可选,是用来告诉parseInt,字符串用什么进制表示 当第二个参数是undefined,0或未指定,js会假定一下情况 如果输入的string是0x或0X,radix被假定为16,字符串的其余部分被当做十六进制去解析 如果输入的string以0开头,radix被假定为10或者8,具体选择哪一个radix取决于浏览器的实现,现在一般10进制比较多 如果输入的string以其他任何值开头,radix是10(十进制) 当第二个参数为1,parseInt只会返回NaN
-
案例
函数:parseInt(string,radix) console.log( parseInt(3.14) ); //3 console.log( parseInt('6.14') ); //6 console.log( parseInt('a9.18') ); //NaN console.log( parseInt('9.18a') ); //9 console.log(parseInt(true)); //NaN console.log( parseInt(undefined) );//NaN console.log( parseInt(null) ); //NaN //面试题1: const obj={ val:0, tostring(){ this.val++, return this.val } } parseInt(obj,10)//1 parseInt(obj,10)//2 parseInt(obj,10)//3 //面试题2: 例如:[1,2,3].map(parseInt) //[1,NaNNaN] [1,2,3].map(parseInt)等价于 [1,2,3].map((num,index)=>parseInt(num,index))等价于 [parseInt(1,0),parseInt(2,1),parseInt(3,2)] radix为0,parseInt(1,0)等价于parseInt(1,10),string以1开头,默认会转换为10进制的1 1 1进制里面不会存在2 NaN 2禁止里面不会存在3 NaN
parseFloat()
- 解析出一个string的浮点数部分
- 如果没有可以转换的部分,则返回NaN
- 案例:
//函数:ParseFloat() //解析出一个string的浮点数部分 console.log( parfloat('9.18a') ); //9.18
Number()
- 把一个string解析为number
- 如果包含非法字符,则返回NaN
- 案例
//函数:Number() console.log( Number(true) );//1 console.log( Number(null) );//0 console.log( Number(undefined) );//NaN console.log( Number('1a') );//NaN
alert() 弹出警示框
propmt() 弹出提示框
- 需要使用变量来保存输入的值
- 什么都不输入,点"确定",返回空字符串
- 点"取消"返回null
- 案例
两次弹出提示框,输入数字,计算两次输入的和,并使用弹出警示框显示结果var num1=prompt('请输入第一个数字'); var num2=prompt('请输入第二个数字'); num1=Number(num1); num2=Number(num2); alert(num1+num2); // 函数只能在浏览器端运行,在服务端不支持该两个函数。
encodeURI() 编码一个URI
encodeURIComponent() 编码区域更广,使用于将url作为参数时
decodeURI() 对已编码的URI进行解码
isFinite()检测一个值是否为有限值
是返回true,不是返回false,无限值只有infinity 1/0 0做除数返回的是infinity
eval() 执行字符串里的表达式
// 要求弹出提示框输入内容,使用eval来执行输入的内容
var str=prompt('请输入一组算数表达式')
console.log(eval(str))
9.2、自定义函数
-
JavaScript中创建函数得三种方式
- 使用function关键字声明命名函数
- 使用直接量方式声明命名函数
- 使用Function对象构造方法创建方法
9.2.1、声明方式创建函数
-
创建普通函数
function 函数名称(){ 函数体;//所封装的代码 }、 调用函数: 函数名称(); // 函数里存的代码,不调用函数是不会执行的 // 例1 创建函数,封装计算1-100之间所有整数的和,并调用3次 for (var i=1,sum=0;i<=100;i++ ) { sum+=i } console.log(sum); } getsun(); getsun(); getsun();
-
创建带有参数的函数
function 函数名称(参数列表){ // 参数列表:用于接受传递的数据 形参 函数体... } 调用: 函数名称(参数列表)//实际传递的数据 实参 // 例 计算任意两个数字相加的和 function add(a,b){//形参 console.log(a+b); } add(2,3); //实参 add(4,9); add(8,15); // 例 创建函数,计算1-任意数字之间所有数字的和,调用多次 functiongetsum(n){ //计算1-n之间所有整数的和 for (var i=1,sum=0;i<=n;i++) { sum+=i; } console.log(sum); } //getsum(100); //getsum(50); // 例 创建函数,计算任意两个年份之间闰年的个数,调用多次 function getrun(n1,n2){ //计算n1~n2之间闰年的个数 /* for(vari=n1;i<=n2;i++) */ for (var count=0;n1<=n2;n1++ ) { if (n1%4===0 && n1%100!==0 ||n1%400===0) { count++; } } console.log(count);//结果输出到控制台 } getrun(2000,2100);//值只是打印在console里,并没有保存下来。 验证:var str=getrun(2000,2100);//没有存返回值,并不能赋值给str。
-
创建带有返回值的函数
// 有return就可以赋值给变量 // 如果赋值给变量时,函数中没有return或者return后不加任何值,返回undefined function 函数名称(参数列表){//参数列表:用于接受传递的数据 函数体: Return 值 //返回值(函数调用后产出的结果) } // 例 计算任意两个数字相加的和 function add(a,b){ return a+b; } 验证:var res=add(1,6); console.log(res); //7有return赋值成功 // 例 创建函数,比较任意两个数字的大小,返回最大值 function getMax1(a,b){ return a>b ? a : b //三目运算符=if else } var num=getMax1(5,13); console.log(num); // 例 创建函数,比较任意三个数字的大小,返回最大值 function getMax2(a,b,c){ varmax=a>b?a:b; returnmax > c ? max : c; } var res=getMax2(2,7,4); console.log(res); 比较多个数字,用的是数组 // 例 1-等待付款 2-等待发货 3-运输中 4-已签收 5-已取消 其他-无法追踪 function getstatus(n){ switch(n){ case1: return'等待付款';//return 出现后,后面语句不执行,所以break注释; //break; case2: return'等待发货'; //break; case3: return'运输中'; //break; case4: return'已签收'; //break; default: return'无法追踪' } } var res=getstatus(4); console.log(res); // 例 创建函数,传递任意一个年份,如果是闰年返回true,否则返回false //由题可知,肯定是一个参数.参数为年数 function isrun(year){ if(year%4===0&& year%100==0 || year%400===0){ returntrue; } returnfalse; } //var res=isrun(2028); //console.log(res); isrun(2036) ? console.log(366) :console.log(365); // 例 阶乘:!5 = 5*4*3*2*1 //创建函数,传递任意一个数字,返回1~任意数字之间所有整数之间所有整数阶乘的和 !1+!2+!3+!4+!5 //第一步:创建函数,传递任意一个数字,返回1~任意数字之间所有整数的阶乘 function getJc(n){ //1~n for(var i=1,s=1;i<=n;i++){ s*=i; } return s; } //console.log(getJc(5)); //第二步:创建函数,传递任意一个数字,返回1~任意数字之间所有整数的和 function getsun(n){ for(var i=1,sum=0;i<=n;i++){ //1+2+3+4+5 //sum+=i; //!1+!2+!3+...+!5 // getJc(1)+getJc(2)+...+getJc(3) sum+=getJc(i) //sum+=i; } return sum; } console.log(getsun(5)); console.log(i);报错,i变量处于函数内部,只能在函数内部使用,所以i是局部变量。
9.2.2、表达式/直接量方式创建函数
-
使用直接量方式创建函数
var functionName=function(arg1,...argN){ // 函数体... } function add(num1,num2){ var sum=num1+num2 return sum } // 等价于 var add=function(num1,num2){ var sum=num1+num2 return sum }
9.2.3、构造函数方式创建
-
函数名本质上也是一个变量名,是指向某个Function对象得引用
-
在js中函数也是以对象形式存在的,每个函数都是一个function对象实列
function add(){} console.log(typeof add) //function
-
使用Function对象创建函数
var functionName=new Function(arg1,...argN,functionBody) fuacntion add(num1,num2){ var sum=num1+num2 return sum } // 等价于 var add=new Function('num1','num2','var sum=num1+num2;return sum')
-
Function是一个类,所有的方法都是Function类实例化的对象,给Function原型上绑定方法,则所有方法都可以调用这个方法
9.2.4、创建函数三种定义方式的对比
- 仅以声明方式定义的方法,会被声明提前
- 代码动态执行
- 以声明方式和直接量方式定义的方法,方法体固定,无法在运行时动态执行
- 以创建对象方式定义的方法,方法体是字符串,可以在运行时动态创建,修改,并执行字符串格式的方法体
- 效率
- 以创建独对象方式定义的方法,因为方法体是字符串,需要再次解析,所以执行效率低
- 其他两种方法定义方式不存在效率问题。
9.3、匿名函数
- 匿名函数就是函数表达式创建的函数
- 函数名称实际就是一个变量,函数声明创建的函数存在函数提升,函数表达式最多存在变量的提升
var fun=function(){ //变量名称fun就是函数名称
console.log(1)
}
fun() //调用
9.4、匿名函数的自调用
-
函数有名字会污染全局
-
匿名函数自调用不会对全局造成污染,其实就是创建了一个作用域
// 创建函数作用域,防止污染全局 (function(形参){ //作用域 })(实参); var num=3; (function(num){ num++ console.log(num);//4 })(num); console.log(num);//3
9.5、回调函数
- 常用于异步获取数据
function fn(a){
a 就是函数名称
a() 调用传递的匿名函数
}
fn(function(){})
-
案例1
// 创建函数,调用的时候,传递两个匿名函数,每个匿名函数中都是返回一个数字,要计算两个数字相加的和 function add(a,b){ // a和b对应传入的匿名函数 var num1=a() var num2=b() console.log(num1+num2) //3 } add(function(){return 1},function(){return 2})
-
案例2
9.6、函数名称和函数名称()的区别
-
函数名称就是一个变量,不管哪种方式,都保存了函数的结构
-
函数名称()是调用函数,得到函数的返回结果
-
案例
// 使用函数表达式创建函数,计算任意两个数字之间所有整数相加的和,并返回结果 var add=function(a,b){ //循环a~b for(vari=a,sum=0;i<=b;i++){ sum+=i; } return sum; } console.log(add(1,3));// 6
9.7、函数的递归
-
递归是在函数中调用自身的一种算法
function say(){ alert('hahahh'); say(); } say (); //构成死循环 又叫递归 递归的使用: 边界调节 ==break 递归前进 递归结束(return) var i=0; function say(){ i++; alert('hahahh'); //当i为3,递归结束 if(i===3){//边界调节 return;//递归结束 } say();//递归前进 } say (); //构成死循环 又叫递归
-
案例1:
// 使用递归计算1-5之间所有整数的和 function getsum(n){ if(n===1){ ᅠᅠᅠᅠᅠᅠ return1; } //当前的和由n+前n-1的和 return n+getsum(n-1); } console.log(getsum(5))
-
案例2:
// 计算斐波那契数列 // 前两项是1,第三项开始,每项是前两项的和 // 1,1,2,3,5,8,13,21.34 // 分别使用递归和普通的函数的方式计算斐波那契数列的第n项 1.递归方式 (嵌套层次太深,造成计算缓慢,没有充分利用cpu) function fib(n){ //当n为1或者2,返回1 if(n===1||n===2){ return1; } returnfib(n-1)+fib(n-2); } console.log(fib(5)); /*fib(4)+fib(3) fib(3)+fib(2)+fib(2)+fib(1) fib(2)+fib(1)+fib(2)+fib(2)+fib(1) */ fib(5) 2.循环方式 function fib2(n){ var n1=1,n2=1; //循环从第三项开始 for(var i=3;i<=n;i++){ //每循环一次都要挪动一次n1和n2 var tmp=n1; n1=n2; n2=tmp+n2; } //循环后n2就是所求项的值 return n2; } console.log(fib2(50));// 运算速度快
十、作用域
10.1、作用域
-
作用域
- 全局作用域:在函数外使用var声明的变量,可以在任意位置访问到
- 函数(局部)作用域:在函数内使用的var声明的变量,只能在函数内访问到
//全局作用域 var a=1;//全局变量 function fn(){ //函数作用域 varb=2; //局部变量 console.log(a);//全局变量可以调用 } fn();//调用函数 console.log(b); //局部变量不能调用到 //函数里不加var是全局变量 var c=3; function fun(){ c=4; } fun(); console.log(c); //4 function foo(){ //没有加var变成了全局变量 d=5; vare=f=8; //f=8 全局 //var e=f 局部 } foo(); //调用函数 //console.log(d); //5//console.log(e);
10.2、变量作用域和变量提升
10.2.1、变量作用域
- 全局变量拥有全局作用域
- 局部变量(函数内的变量),其作用域是局部性的
- js程序在执行前,会将声明的比那辆提升到所在作用域最前面,赋值还是原来的位置
10.2.2、变量提升
console.log(a)
var a=1
↓
var a
console.log(a) //undefined
a=1
function fn(){
console.log(b)
var b=2
}
fn()
↓
function fn(){
var b
console.log(b) //undefined
b=2
}
fn()
var c=5
function fun(){
console.log(c);
var c=7
}
fun()
↓
var c=5
function fun(){
var c
console.log(c)//undefined
c=7
}
var e=9
function f00(){
console.log(e) //9
e=7
console.log(e) //7
}
foo()
var n=7
function fn2(n){
// 形参就是相当于函数内var声明的变量
// var n=8
n+=2
console.log(n) //10 局部变量 n+=2
}
fn2(8)
console.log(n) //全局变量 n=7 不变 7
10.3、函数作用域和函数提升
10.3.1、函数作用域
-
函数作用域:变量在声明它的函数体以及这个函数体内签套的任何函数体内都是有定义的
-
函数的可访问范围也分为全局函数和局部函数,全局函数可以在任何位置访问,而局部函数只能所有的作用域下访问
//全局作用域函数 function fn(){ console.log(1); } //局部函数 function foo(){ fn();//1 //fun是局部函数 functionfun(){ console.log(2); } fun();//访问到了 } foo();//先调用外面的foo函数 fun();//访问不到
var a=100 function f(){ a=200 console.log(a) //200 } f() console.log(a)//200 var a=100 function f(){ a=200 console.log(a) //200 } console.log(a)//100 f() var a=100 function f(){ var a=200 console.log(a) //200 } console.log(a)//100 f() // 就近原则,全局变量和局部变量互不影响
10.3.2、函数提升
- 只有使用声明方式创建的函数会提升到所在作用域的最前边
bar()
function bar(){
consol.log(3)
}
10.4、作用域链
- 作用域链:是专门保存一个函数可用变量存储位置的对象
- 任何一个函数对象都对应一个作用域链对象
- 作用域链按顺序引用了函数可以使用的变量所在的对象
- 作用域链中对象按从局部到全局的顺序引用
- 定义方法时,作用域链中仅引用了全局对象window
10.4.1、函数从创建(定义)到调用详解
- 当调用一个函数时,js引擎会动态为本次方法调用创建一个活动对象,活动对象中保存了方法可用的局部变量
- 当方法执行时,需要使用一个变量。将从作用域链中按从局部到全局的顺序,查找变量的所在位置
- 只要找到,就不会再继续查找
- 如果找到全局都没有,就报错
- 在任何位置,为一个未声明的变量赋值,都会在全局自动创建该变量,返回undefined
- 函数执行完,作用域链中的活动对象引用和活动对象被释放
- 作为JavaScript的顶层代码,其作用域链中只有一个对象即全局对象,对于浏览器客户端而言就是window
10.4.2、匿名函数和非匿名函数从创建到调用的区别
- 因为非匿名函数在定义时,就已创建函数对象和作用域链对象。所以,即使未调用,也占用内存空间。
- 匿名函数,仅在调用时,才临时创建函数对象和作用域链对象,调用完,立即释放
- 所以,匿名函数比非匿名函数更节省内存空间。
10.5、闭包 closure
10.5.1、什么是闭包
-
词法表示包括不必计算的变量的函数,也就是说,该函数能使用函数外定义的变量
-
MDN对于闭包的解释: 闭包是哪些能够访问自由变量的函数,这里的自由变量就是指作用域外的变量
-
红宝书对于闭包的解释: 有权访问另一个函数作用域的变量的函数
var n; function fn(){ var b="b" n=function(){ return b } }
var a=100 function f(){ var a=200 function g(){ return a } return g } var g=f() console.log(g()) // 1. 函数f被调用时,其局部变量a保存在活动对象中 // 2. 通常在调用结束时,该活动对象被释放 // 3. 但函数f中的内嵌函数g引用到了父级函数活动对象中的属性a,而该函数的引用又作为f的返回值,返回给了外部 // 4. 所以该外层函数的活动对象,由于有了外层引用而在方法调用结束后并没有被回收 // 5. 函数g被调用时,所使用的是声明在方法f中的局部变量a // 6. 该变量存放在调用f方法时所创建的活动对象中,而该对象由于存在外部引用,在方法f调用完成后依然没有被回收。
10.5.2、使用闭包三步
- 用外层函数包裹内存函数和要保护的变量
- 外层函数将内层函数返回到外部,让外部有可能得到内层函数
- 使用者调用外层函数,才能获得返回的内层函数对象和要保护的变量
10.5.3、闭包的三大特性
- 内嵌函数
- 局部变量
- 外部使用
- 参数和变量不会立即被垃圾回收机制回收
10.5.4、闭包的优缺点
- 私有化数据在私有化数据的基础上保持数据
- 私有化数据就是把一些变量私有化到函数里面,然后私有化基础上 保持数据
- 比方说,在节流防抖的时候,当第一次点击的时候let 一个time=接收它的定时器,如果我们不适用闭包去占存它的话,下次点击的时候,他是重新创建了一个函数,也就是重新创建一个定时器,它两个不是互通的,没有引用去做连通的。
- 也就是说,使用闭包不会在内存中消失,所以在下次调用的时候就能够获取到上一次的定时器 时间。
缺点 - 内存泄漏(内部变量不会被回收掉)
- 使函数内部变量存在于内存中,内存消耗大
- 闭包可以在父函数外部改变父函数内部的值
- 所以,我们需要及时的将这些变量进行置空, 防止内存过多,内存爆掉。
10.5.5、闭包的作用
- 提供可共享的局部变量
- 保护共享的局部变量,提供专门的读写变量的函数
- 避免全局污染
// 定义一个变量,只被getValue和setValue两个函数访问
var getValue,getValue
(function(){
var secret=0;
getValue=function(){
return secret
}
setValue=function(value){
secret=value
}
})()
console.log(getValue())//0
setValue(100)
console.log(getValue())//100
secret=0
console.log(getValue())//100
10.5.6、闭包环境
在父级作用域里会生成一个变量var i=0,在子作用域里使用这个变量,这样声明的那个变量i就是自由变量,这种作用域嵌套环境叫做 闭包环境
10.5.7、内存泄漏,影响程序的执行–> 垃圾回收机制
- 在内存中存在和回收站相似的机制,叫做垃圾回收机制,自由变量在函数关闭后会放在回收机制里,下次调用,再重新出来
- 标记清除算法,引用计数算法,新生代老生代
10.5.8、闭包的应用场景
节流防抖函数柯里化 高阶函数 vue的响应式原理 react hooks fab的原理
十一、对象 Object(引用类型数据)
对象是一组属性(property)和方法(method)的集合
例如 一个人:属性有姓名,性别,身高,体重,方法有走路说话
11.1、JS中的对象
- 内置对象(ES): JS提供的
- 宿主对象:根据不同的执行环境划分
- 浏览器宿主对象:dom,bom交互
- 服务端:nodejs中的扩展对象
- 自定义对象:自己创建的对象
11.2、自定义对象
- 对象字面量方式创建
- 内置构造函数创建
- 自定义构造函数创建
- 原型链静态方法创建
11.2.1、使用对象字面量创建对象
-
使用大括号创建空对象
-
属性名和属性值之间用冒号隔开
-
多组属性之间用逗号隔开
-
属性名中的引号可加可不加,如果含有特殊字符必须要加
var person={ name:'tom', sex:'男', userName:'root', 'user@pwd':'1234' }
11.2.2、使用内置构造函数创建对象
-
new Object()
创建一个空对象 -
需要通过访问对象属性来添加每个属性
// 创建完函数对象之后,才能添加属性 var laptop=new Object() laotop.lid=5 laptop.title='小米air' laptop['price']=4999 console.log(laptop)
11.2.3、自定义构造函数创建
class Person{
constructor(name,age,sex){
this.name=name
this.age=age
this.sex=sex
}
interview(){
console.log(`我叫${this.name},今年${this.age}岁`)
}
}
const person=new Person('德玛',17,1)
console.log(person)
11.2.3、原型链静态方法创建
// 创建对象person,然后将create()里的对象放入person对象的原型对象中
const person=Object.create({
name:'tom',
sex:'男',
userName:'root',
'user@pwd':'1234'
})
person.age=17
console.log(person)
// 唯一创建没有原型对象的对象的方法
const person=Object.create(null)
person.age=17
console.log(person)
11.3、访问对象中的属性
-
对象.属性名
-
对象['属性名']
-
如果访问不存在的属性返回undefined
var person={ name:'tom', sex:'男', userName:'root', 'user@pwd':'1234' } //访问属性 console.log(person.age); console.log(person['name']); console.log(person['user@pwd']); //属性名含有引号用中括号的方法 //访问不存在的属性--返回undefined,不会报错 //修改属性 person.name='huwenhao'; console.log(person); //添加不存在属性 person.addr='北京'; console.log(person);
11.4、检测属性是否存在
-
方法一:
对象.属性名===undefined
true不存在 false 存在 -
方法二:
对象.hasOwnProperty(‘属性名’)
true存在 false不存在 -
方法三:
‘属性名’in 对象
true存在 false 不存在 -
案例 1
var person={ name:'tom', age:18, sex:'男' } console.log(person.name===undefined);//false console.log(person.height===undefined);//true console.log(person.hasOwnProperty('height'));//false console.log( 'phone' in person);//false console.log( 'age' in person);//true
-
案例 2
//创建一个商品对象,含有编号,标题属性,如果价格属性不存在,添加价格属性,如果编号属性存在,添加上架时间属性 var laptop={ lid:7, title:'小米Air' } if(laptop.price===undefined){ laotop.price=3000 } if(laptop.hasOwnProperty('lid')){ laptop.shelfTime="2023-10-1" } console.log(laptop)
11.5、添加方法
var person={
name:"richard",
age:23,
say:function(){
console.log(this.name)
//this是当前指代的对象,防止后期对象名修改
},// 匿名函数赋值给say
run:function(){
console.log('running')
}
person.say()
person.run()
//外部添加方法
person.work=function(){ }
}
//创建计算器对象,含有两个数字属性,创建一个方法来实现计算加减乘除
var calc={
num1:8,
num2:15,
jsq:function(t){
if(t==='+'){
console.log(this.num1+this.num2);
}elseif(t==='-'){
console.log(this.num1-this.num2);
//当前对象下的num1和num2
}elseif(t==='*'){
console.log(this.num1*this.num2);
}elseif(t==='/'){
console.log(this.num1/this.num2);
}
}
};
calc.jsq('+');
calc.jsq('/');
// 创建圆对象,包含属性圆周率,半径,添加计算周长和面积的方法
var circle={
r:5,
pi:3.14,
getLength:function(){
return 2*this.pi*this.r
},
getArea:function(){
return Math.pow(r,2)*this.pi
}
}
//Return是返回值 看结果要打印 this表示这个对象, 要想调用属性作运算必须加上。
//Console.log(circle.getLength());
//Console.log(circle.getArea());
11.6、包装对象
【注:202309015】 String Number Boolean 是基础类型数据,但是都可以包装成对象类型(new的方式去创建),并且无论是基础数据类型或者对象类型都可以使用原型对象上的api
- 把原始数据转为引用类型
- 包装对象:目的就是让原始类型的数据像引用类型一样具有属性和方法
- JS提供了3种包装对象 String Number Boolean
11.6.1、String对象
new String()
返回对象String()
返回字符串
var str1= 'dema' //字面量
var str2=new String(2) //构造函数
var str3=String(2) //普通函数
console.log(typeof str2) // object
console.log(typeof str3) // string
console.log(str2+3) // 23
console.log(str23+3) // 23
-
String() 可以转数组和对象为字符串
var obj={name:'tom',age:18} var arr=[1,2,3] console.log(String(obj)) //[object Object] console.log(String(arr)) //1,2,3
-
字符串的api
无论用何种形式创建的字符串都可以使用
-
length
获取字符串的长度 -
字符串[0]
字符串也有下标 -
charAt()
获取下标对应的字符 -
lastIndexOf(str)
查找字符最后出现的下标, 找不到都会返回-1,不支持正则 -
toUpperCase()
输出字符串都大写 -
toLowerCase()
输出字符串都小写 -
slice(start,end)
截取字符串,start开始的下标,end解释的下标,不包括end本身,如果end为空,截取到最后,如果是负数表示倒数,不改变原数组 -
substr(start,count)
截取字符串,start开始的下标,count是截取的长度,count为空截取到最后,start为负数表示倒数,不改变原数组 -
substring(start,end)
截取字符串,start表示开始的下标,end结束的下标,end为空截取到最后,如果是负数,自动转为0,如果start大于end,则位置会自动交换。不改变原数组 -
split(sep/reg,[howmany])
将字符串按照指定的字符切割成数组,sep表示指定的字符,支持正则- 使用一个指定的字符串或正则表达式,对原字符串进行拆分,返回拆得的子串数组
- 若指定了howmany属性,则只能返回拆得得前howmany个字串
var data='How are you' var regexp=/\s+/ var arr=data.split(regexp) console.log(arr);//["How","are","you"]
-
-
匹配模式(敏感词):作用:用于查找、替换字符串、能写正则
-
indexOf(str,start)
start开始查找的下标,如果为空,表示从从第一个开始,找不到都会返回-1,不支持正则 -
search(str/reg)
查找满足条件的第一个,并返回下标,如果找不到返回-1,支持正则 -
match(str/reg)
用于查找满足条件的所有敏感词的内容和位置,返回数组 /china/ig,支持正则, i->ignore 忽略大小写 ,g->global 全局查找var arr=str.match(/正则/g)
- match不加g可以找内容和位置,找到返回
arr:[0:敏感词,index:位置i]
,如果没找到返回null,但是只能找到第一个 - match加g可以找到所有的内容,但是会丢失所有的位置。
- [注] 那么,如何找到所有的敏感词的内容和位置呢–>
do while
+reg.exec()
-
replace(str/reg,replacement)
-
对字符串特定格式的子串进行替换,返回替换后的结果
-
第一个参数既是一个固定的子串,也是一个正则表达式对象
-
第二个参数是一个回调函数,也可以是一个字符串
- Replace每次找到一个敏感词,就会自动调用回调函数
- 每次自动调用回调函数时,都会自动传入本次找到的敏感词为形参
- 回调函数中就可以根据每次接到的敏感词不同 替换不同的新值
- replace会将回调函数每次返回的新值替换到字符串中
-
第二个参数可以给正则里的分组占位
var pid='320121199808310091' var birth=pid.slice(6,14) // 用括号分组,打开懒惰模式 var reg=/(\d{4})(\d{2)(\d{2})/ // ()自动添加组号 // 组1 组2 组3 birth=birth.replace(reg,'$1年$2月$3日') console.log(birth)
-
-
注【20230917】:查找敏感词的api总结
- 用indexOf()不支持正则,所以里面的敏感词写死,只能得到固定敏感词第一次出现的固定位置,得不到返回-1;
- 用search() 支持正则,敏感词不用写死,但是也只能得到敏感词第一次出现的位置,找不到返回-1;
- 用match()返回的是一个数组,可以查找第一个敏感词的内容和位置,或配合g查找所有敏感词的位置,找不到返回null;
- reg.exec(str)可以通过 循环反复调用,查找所有敏感词的位置和内容,返回的是一个数组,找不到返回null。
11.6.2、Number对象
var num1=new Number(true); //转为数值,同时返回对象
var num2= Number(true); //转为数值,返回数值型
var num3=1;//字面量
//console.log(num1,typeof num1);
//console.log(num2,typeof num2);
console.log(num1+2);
console.log(num2+2);//用法相同
- Number的api
toFixed(2)
保留数字小数点后2位; 不改变原数字;用此api转换以后是字符串类型toString(2)
转换成2进制;不改变原数字;用此api转换以后是字符串类型
11.6.2、Boolean对象
- new Boolean() 转为布尔型,返回对象
- Boolean() 转为布尔值,返回布尔值
- !! 转为布尔型
11.7、全局对象
11.7.1、不需要new就能使用的对象,例如 Math
//圆周率 pl
console.log(Math.PI);//3.141592653589793
//取绝对值 absolute abs()
console.log(Math.abs(14-63)) //49
//向上取整 ceil()
console.log(Math.ceil(3.14));//4
//向下取整 floor()
console.log(Math.floor(3.14));//3
//四舍五入取整 round()
console.log(Math.round(7.51));//8
//取一组数字的最大值 max()
console.log(Math.max(23,9,78,45,6));//78
//取一组数字的最小值 min()
console.log(Math.min(23,9,78,45,6));//6
//x的y次方 pow()
console.log(Math.pow(5,2));//25
//随机数random()
console.log(Math.random());//>=0 <1
// 案例
var alp=['a','b','c','d','e','f','g','h','i','g','k','l','i','n','o','p','q','y','s','d','u','v','w','x','y','z'];
var alp2=[];
for(var i=0;i<4;i++){
num=Math.floor(Math.random()*alp.length)
alp2.push(alp[num]);
}
console.log(alp2);
//删除元素 循环完删除就不会重复
Alp.splice(num,1)
11.7.2、需要new使用的对象,例如 Date
用于对日期时间的存储和计算
通常打印出的时间是0时区的时间
var d=new Date('2028/7/20 19:30:45');
console.log(d,typeof d);
2028-07-20T11:30:45.000Zobject
//打印出来的时间早8小时,object
-
创建date对象
-
用字符串形式最简单,用月份还要考虑月份
var d1=new Date('2029/10/23 10:45:30');//2029-10-23T02:45:30.000Z var d1=new Date('2029/10/23');//2029-10-22T16:00:00.000Z var d2=new Date(2029,10,23,10,45,30);//2029-11-23T02:45:30.000Z 月份是0~11 1月份就是0 var d3=new Date();//存储当前系统时间 做秒杀倒计时 var d4=new Date(1000);//距离计算机元年的毫秒数 console.log(d2);
-
-
获取date对象中存储的日期时间
```javascript var d = new Date(); console.log(d.getFullYear());//2021 getYear有bug,不要用,采用截取方式获得后两位。 年 console.log(d.getMonth());//6 月份0~11 console.log(d.getDate());//16 日 console.log(d.getHours());//小时 console.log(d.getMinutes());//分钟 console.log(d.getSeconds());//秒 console.log(d.getMilliseconds());//毫秒 console.log(d.getDay())//星期 星期日-星期六 0—6 console.log(d.getTime());//距离计算机元年的毫秒数 ```
-
例1 计算从2031/05/01到2031/10/01
// 计算两个时间相差的毫秒数
var d1=new Date(‘2023/5/1 18:23:50’)
var d2=new Date(‘2031/10/1 5:45:0’)
var d=d2-d1
// 把单位转化为秒
d=Math.floor(d/1000)
// 把相差的值换算成天
var day=Math.floor(d/(246060))
// 相差的小时首先去除相差的天
var hour=d%(246060)
hour=Math.floor(hour/(60*60))
// 相差的分钟,去除相差的小时,剩余的秒转为分钟
var minute=d%3600
minute=Math.floor(minute/60)
// 相差的秒种,去除相差的分钟
var second=d%60console.log(day,hour,minute,second) //3074 11 21 10
-
-
本地日期时间格式
- toLocaleString
- toLocaleDateString
- toLocaleTimeString
var d=new Date('2028/7/20 19:30:45'); console.log(d.toLocaleString()) //2028/7/20 19:30:45 console.log(d.toLocaleDateString()) //2028/7/20 console.log(d.toLocaleTimeString()) //19:30:45
-
设置日期时间
- setFullYear
- setMonth(0-11)
- setDate
- setHours
- setMinutes
- setSeconds
- setMilliseconds
- setTime
var d=new Date('2028/7/20 19:30:45') d.setFullYear(2038) d.setMonth(9) // 0-11 d.setDate(d.getDate()+3) d.setTime(1000) //设置距离计算机元年毫秒数
var d1=new Date('2028/7/20 19:30:45') var d2=new Date(d1) //以参数形式构建一个新对象 var d1=new Date('1994/8/18 13:20:30') var d2=new Date(d1) var d1=new Date('1995/9/18 13:20:30') console.log(d1) console.log(d2) //d1要以参数形式保存在d2中,d1发生重新赋值,d2的值不会发生改变
十二、数组
12.1、什么是数组
- 多个元素组成的集合–在一个变量名中存储多个值
- JS数组中元素的数据类型可以相同,也可以不同
12.2、创建数组的方式
12.1.1、数组字面量创建
var emp1='tom';
var emp2='king';
var emp3='jerry';
//数组字面量
var emp=['tom','king','jerry'];
console.log(emp);
//数组可存储不同的数据
var emp=['tom','king','jerry',18,19,20,true,false];
console.log(emp);
12.1.2、内置构造函数创建
new Array(元素1,元素2….)
new Array(3) 初始化数组的元素个数为3,可以添加更多的元素
1.var company=new Array('百度','腾讯','阿里');//和对象不同,数组的内置函数创建可以直接添加
//console.log(company);
2.var company2=new Array(3); //创建元素,包含三个元素
company2[0]='百度';
company2[1]='腾讯';
company2[2]='阿里'; //添加元素
console.log(conpany 2);
12.1.3、填充方式创建空对象
// 数组中创建空对象,对象指向同一个堆内存地址
var a=new Array(3).fill({})
consol.log(a) //[{},{},{}]
a[0].name='dema'
console.log(a) //[{name: 'dema'},{name: 'dema'},{name: 'dema'}]
//方式1: 数组中创建空对象,对象指向不同的堆内存地址
var a=[{},{},{}]
a[0].name='dema'
console.log(a) //[{name: 'dema'},{},{}]
//方式2: 数组中创建空对象,对象指向不同的堆内存地址
var a=Array.from({length:3},()=>{return {}})
a[0].name="dema"
console.log(a) //[{name: 'dema'},{},{}]
12.3、访问数组中的某一项
// 通过 下标 数组名[下标从0开始]
var city=['北京','上海', '深圳', '广州'];
console.log(city[0]);//北京
console.log(city[1]);//上海
//访问不存在的数组返回undefined
console.log(city[1],city[5]);//上海 undefined
//通过下标修改或添加数组中的值
12.4、数组的长度属性
数组.length
数组几个元素,数据长度是几- 数组.length 获取数组的个数
-
可以在数组的末尾添加元素
数组[数组.length]=值
var a=[]; a[a.length]=1; a[a.length]=2; a[a.length]=3; console.log(a)//[1,2,3] var day=3; a[day]=4; console.log(a)//[1,2,3,4]
-
遍历数组
-
12.5、数组的分类
-
关联数组:以字符串做下标,只能单独的添加每一项元素
var emp=[] //添加元素 emp['ename']='tom', emp['sex']='男', emp.age=18, console.log(emp) //[ename: 'tom', sex: '男', age: 18]
-
索引数组:以数字为下标
12.6、数组常用的api
api:JS中预定义好的方法
-
toString() 将数组元素转为字符串,字符串用“,”隔开,不改变原数组
-
join(‘-’) 将数组元素按照指定的字符组合为字符串,不改变原数组
-
concat(arr1,arr2,…) 拼接多个数组 其他数组以参数形式,不改变原数组
-
slice(start,end) 截取数组的元素,start表示开始的下标,end是结束的下标,不包括end,如果是负数表示倒数,如果是end为空截取到最后,返回数组,不改变原数组
-
reverse() 翻转数组中的元素 改变原数组
-
sort()分类,排序,默认按照unicode码排列,改变原数组
- 默认按照unicode码排序
- 从小到大排序
arr.sort(function(a,b){return a-b})
- 从大到小排序
arr.sort(function(a,b){return b-a})
-
Splice(start,count,value)
删除数组中的元素,
start:开始的下标,start为负数表示倒数
count:删除的数量,count为0表示不删除,count为空删除到最后
value删除后添加的元素,返回删除后的元素
元素组会发生变化,返回的是一个数组,里面存的是删除的数据 -
push() 在末尾添加一个或多个元素
-
pop() 删除末尾的一个元素
-
unshift() 在开头添加一个或多个元素
-
shift() 删除开头的一个元素
【注】新增返回添加后数组的length,删除返回第一个或最后一个删除的元素
12.7、二维数组
- 数组中的每个元素也是数组
Var arr=[[],[],[]]
- 访问
arr[下标][下标]
十三、正则表达式(RegExp)
13.1、正则表达式概述
-
正则表达式(Regular Expression):由一些普通字符和特殊字符组成的,用以描述一种特定字符规则的表达式。
-
正则表达式常用于在一段文本中搜索,匹配或替换特定形式的文本,如:词语出现频率的统计、验证字符串是否符合邮箱格式、屏蔽一篇帖子中的限制性词语等
13.2、定义正则表达式
-
所有的单个大小写字母、数字都是一个正则表达式,用以匹配单个字符,这个字符与他本身相同,如
var regexp=/ipod/ var data='Apple iPod is No.123 cool??' console.log(regexp.test(data)) //false
-
正则表达式中有些字符有特殊的语法含义,是不能直接使用的,必须使用\进行转义后才能使用
. \ / * ? + [ ( ) ] { } ^ $ |
,对不是元字符的字符进行转义是不会出问题的,但是未对元字符进行转义就会有错误。 -
字符集
:正则表达式使用如下语法匹配一个范围内的字符[abc]
匹配指定集合内的任一个字符/[3458]/
[^abc]
匹配不在指定集合内的任意字符/[^12679]/
[0-9]
匹配任一个数字/[0-9]/
[a-z]
匹配任一个小写字符/[a-z]/
[A-Z]
匹配任一个大写字符/A-Z/
[A-z]
匹配大写A到小写z的所有字符,即A-Z[]^_`a-z/[A-z]/
-
预定义字符集
:正则表达式可以使用如下元字符引用来进行简化\d
匹配一个数字/\d/ 等价于 /[0-9]/
\D
匹配一个非数字/\D/ 等价于 /[^0-9]/
\w
匹配一个数字/字母/下划线/\w/ 等价于 /[0-9a-zA-Z_]/
\W
匹配一个非数字/字母/下划线/\W/ 等价于 /[^0-9a-zA-Z]/
\s
匹配一个空白字符/\s/ 等价于 /[\n\r\t\v\f]/
\S
匹配一个非空白字符/\S/ 等价于 /[^\n\r\t\v\f]/
.
匹配除了回车和换行外的任何单个字符/./等价于/[^\n\r]/
-
数量词
正则表达式可以使用如下特殊字符定义字符的出现频次–量词元字符n?
匹配零次或1次字符n/a?/
n*
匹配零次或多次字符n/a*/
n+
匹配1次或多次字符n/a+/
n{x}
匹配字符n出现x次/a{3}/
n{x,y}
匹配字符n出现x到y次/a{2,4}/
n{x,}
匹配字符n的出现>=x次/a{3,}/
-
选择和分组
正则表达式使用如下语法定义子表达式分组或选择exp1|exp2
使用|进行条件选择/ex|Ex|post|Post/
(exp1)
使用()指定分组—子表达式,每个分组自动获取形如\1、\2、\3…的分组号
-
指定匹配位置
可以使用如下字符进行指定位置的匹配^
匹配字符串的开头/^a/
注意^
的两种用法$
匹配字符串的结尾/a$/
\b
匹配单词的边界/\bhis\b/
\B
匹配单词的非边界/\Bhis\B/
?=x
匹配其后紧接x的字符串/do(?=not)/
?!x
匹配其后没有紧接x的字符串/do(?!not)/
13.3、 转义字符 \
- 转换字符本身的意义
- 用法:在要转义的字符前加
\
\
将特殊字符转为普通字符\n
将普通字符n转义为换行符\t
将普通字符转义为制表符(tab键效果)
// 打印出 c:\xampp\htdoc\js
console.log('c:\\xammp\\htdoc\\js')
13.4、RegExp对象
13.4.1、创建RegExp对象
-
创建正则表达式对象
-
直接量语法
var patt1=/pattern/attributes
var regexp1=/\d{2,3}/ig
-
调用RegExp构造函数
var patt2=new RegExp(pattern,attributes)
var regexp2=new RegExp('\\d{2,3}','ig')
-
13.4.2、RegExp对象的属性
global
(只读)RegExp对象是否具有g修饰符ignoreCase
(只读)RegExp对象是否具有i修饰符multiline
(只读)RegExp对象是否具有m修饰符lastIndex
用法设置/获取下次匹配的起始位置source
(只读) 返回模式匹配所使用的文本$1...$9
(全局属性)指代一次搜索中某个子表达式匹配的文本
13.4.3、RegExp的方法
-
compile()
- 编译/重新编译正则表达式,将pattern转换为内部格式,加快执行速度
- Js是不识别正则表达式语法的,在内部需要编译,也可以用正则对象中的方法compile()进行手动编译,加快执行速度
-
exec()
- 检索字符串中指定的值,返回找到的值,并确定其位置;影响lastIndex属性的值
-
test() 验证
- 检索字符串中指定的值,返回true或false,影响lastIndex属性的值
十四、面向对象(oop)
14.1、ECMAScript5的面向对象设计
面向对象的语言,面向对象适合大量数据的管理和维护,在程序中,在程序中先用对象结构描述现实中一个具体事务的属性和功能,在后续程序开发当中按需使用对象中已经确定的属性和功能。
-
封装
- 把相关的信息(无论数据或方法)存储在对象中
-
继承
- 从其它对象获得属性和方法
-
多态
- 能以多种不同的形式运行函数和方法
14.1.1、使用Function模板创建对象模板
-
可以使用function模板批量的创建某种类型的多个实例,并且这些实例具备相同的基础属性
// 创建对象模板---也称为对象的构造方法 function Emp(){ // 此处的Emp作为对象实例的模板,可以多次调用以构造多个同类型的实例 } // 好处:结构定义一次,就可以调用无数次 // 使用模板创建对象的多个实例 // 此处省略了new关键字会怎样呢,会返回undefined var e1=new Emp() var e2=new Emp() console.log(tyoepf e1) //Object // Emp是否是e1的构造函数 console.log(e1 instanceof Emp) //true
-
利用模板定义对象的属性
- 对象模板中使用
this
关键字声明对象的属性
function Emp(){ this.ename="Tom" this.salary=3500 this.hiredate=new Date() } var e1=new Emp() console.log(e1.ename) console.log(e1['hiredate']) function Emp(ename,salary,hiredate){ this.ename=ename this.salary=salary this.hiredate=hiredate }
- 对象模板中使用
-
利用模板定义对象的方法
-
对象模板中使用
this
关键词声明对象方法function Circle(r){ this.r=r; // 方法声明在构造方法内,则每创建一个实例,都会创建一个函数对象 this.getSize=function(){ return Math.Pl*this.r*this.r } // 方法声明在构造函数外,则所有的实例共用同一个函数 this.getPerimeter=perimeter } function perimeter(){ return 2*Math.Pl*this.r } var c1=new Circle(5) console.log(c1.getSize()) console.log(c1.getPerimeter())
-
-
this关键字
-
JavaScript中,this关键字用在方法内。专门引用正在被调用的方法当前所在的对象
-
函数中,this为当前对象
var hero={} hero.sayName=function(){ return 'hello'+this.ename }
-
构造函数中,this引用新创建的对象
function Emp(){ this.ename="Tom" this.salary=3500 this.hiredate=new Date() }
-
14.1.2、原型对象
- 在JavaScript中函数本身也是一个包含了方法和属性的对象
- 每个函数都有一个prototype属性,该属性引用的就是原型对象
- 原型对象是保存共享属性值和共享方法的对象
- 值得注意的是,js并没有规定一个函数的原型类型,因此原型可以是任何类型
14.1.3、为对象扩展属性
-
扩展单个对象的成员
function Emp(ename,salary,hiredate){ this.ename=ename this.salary=salary } var emp1 = new Emp('Tom',3500) var emp2 = new Emp('Jerry',4500) // 扩展单个对象的成员 emp1.hireDate="2023-09-18" console.log(emp1.toString()+":"+emp1.hireDate) //[object Object]:2023-09-18 console.log(emp2.toString()+":"+emp2.hireDate) //[object Object]:undefined
-
扩展共享值
function Emp(ename,salary,hiredate){ this.ename=ename this.salary=salary } var emp1 = new Emp('Tom',3500) var emp2 = new Emp('Jerry',4500) Emp.prototype.hireDate="2015/05/01" console.log(emp1.toString()+":"+emp1.hireDate) //[object Object]:2015/05/01 console.log(emp2.toString()+":"+emp2.hireDate) //[object Object]:2015/05/01
14.1.4、删除属性
-
可以使用delete关键字删除对象的属性
function Emp(ename,salary,hiredate){ this.ename=ename this.salary=salary } var emp1 = new Emp('Tom',3500) var emp2 = new Emp('Jerry',4500) Emp.prototype.hireDate="2015/05/01" console.log(emp1.toString()+":"+emp1.hireDate) //[object Object]:2015/05/01 console.log(emp2.toString()+":"+emp2.hireDate) //[object Object]:2015/05/01 //删除属性 delete emp1.name; delete Emp.prototype.hireDate console.log(emp1.toString()+":"+emp1.hireDate) //[object Object]:undefined console.log(emp2.toString()+":"+emp2.hireDate) //[object Object]:undefined
14.1.5、自有属性与原型属性
-
自有属性:通过对象的引用提娜佳的属性;其他对象可能无此属性;即使有,也是彼此独立的属性
-
原型属性:从原型对象中继承的属性属性,一旦原型对象中属性值改变,所有继承自该原型的对象属性均改变
-
自有属性重写原型属性
function Hero(){ this.name="jsscript" } Hero.prototype.name="cs" var hero=new Hero() hero.age=18 console.log(hero.name) //jsscript delete hero.name console.log(hero.name) // cs delete Hero.prototype.name console.log(hero.name) //udnefined // 原型链,自由属性没有,就去原型属性找,原型属性没有返回undefined
-
自有属性和原型属性都可以访问获取,区别方法
- 使用hasOwnProperty()检测对象是否具备指定自由属性
console.log(hero.hasOwnProperty('name'))
- 使用in检测对象及其原型链中是否具备指定属性
console.log('name' in hero)
- 使用hasOwnProperty()检测对象是否具备指定自由属性
14.1.6、对象的_proto_
属性
-
自定义对象的__proto__属性
- new 创建的对象使用构造函数的prototype作为原型
- 对象直接量的原型为Object.prototype
-
内置对象的__proto__属性
- Array对象
- Boolean对象
- Date对象
- Number对象
- String对象
14.1.7、获取原型
-
可通过两种方式获取对象的原型,进而设置共享属性
-
使用构造函数prototype属性
- 比如:Emp.prototype
-
使用专门的Object的getPrototypeOf静态方法
- Object.getPrototypeOf(obj)
-
14.1.8、原型链
- 原型对象本身也有原型
- 构造函数的原型对象中也有一个__prototype__属性
- 原型对象中的__proto__属性,默认指向Object类型的原型对象—Object.prototype
- Object.prototype是所有对象的原型。其中包含了所有对象所共有的属性和方法,比如toString方法
- 事实上,所有对象通过__proto__属性的引用关系,都可以直接或间接的引用到Object.prototype对象
- 我们将使用对象的__proto__属性,形成的逐级引用的关系,称为原型链
// 实现Array类型的isArray方法,检查任意类型对象是否是数组类型
// 使用4种方法实现
// typeof方法无法区分数组对象和普通类型的对象
var arr=[1,2,3]
var now=new Date()
var obj={}
// 判断对象的爹如果是Array.prototype,则说明这个对象是数组家的孩子
console.log(
arr.__proto__==Array.prototype
now.__proto__==Array.prototype
obj.__proto__==Array.prototype
)
console.log(
Object.getPrototypeOf(arr)==Array.prototype
Object.getPrototypeOf(now)==Array.prototype
Object.getPrototypeOf(obj)==Array.prototype
)
// 构造函数和元对象是互相引用的关系
// 判断妈妈,判断子对象强行访问父对象的constructor属性,获取构造函数时array
console.log(
arr.constructor==Array
now.constructor==Array
obj.constructor==Array
)
console.log(
arr instanceof Array
now instanceof Array
obj instanceof Array
)
// 自定义
// 向array家的构造函数添加静态方法isArray
//if(Array.isArray===undefined){
Array.isArray=function(obj){
return Object.getPrototypeOf(obj)==Array.prototype
// obj.__proto__=Array.prototype
// obj.constructor=Array
// obj instanceof Array
}
//}
// es5
console.log(
Array.isArray(arr)
Array.isArray(now)
Array.isArray(obj)
)
- 检查对象的原型
isPrototypeOf
该方法用于判定一个prototype对象是否存在于另一个对象的原型链种,如果是返回true,否则返回false
14.1.9、某个对象重写方法
-
默认的toString()方法返回的信息量很少
var emp1={ "ename":"Tom", "salary":3500, "hiredate":new Date(2014,0,15), "toString":function(){ return this.ename+this.salary } } console.log(emp1.ename) //Tom console.log(emp1['hiredate']) //Wed Jan 15 2014 00:00:00 GMT+0800 (中国标准时间) console.log(emp1.toString()) // Tom3500
14.1.10、原型继承
- JavaScript中主要通过原型实现继承
- 通过原型实现继承主要有下面两种方式
- 修改构造函数的原型,为该构造函数创建的对象指定统一的父级对象
- 语法:构造函数.prototype=父级对象
- 单独修改一个对象的原型,而不影响其他对象的原型
-
优点: 对象之间不会纂改各自原型中继承来的属性和方法
function A(){ this.name="a" this.toString=function(){return this.name} } function B(){ this.name="b" } B.prototype=new A() var b=new B() console.log(b.name)//b console.log(b.toString())//b delete b.name //删除b自身的name属性 console.log(b.name)//a console.log(b.toString())//a
-
仅影响当前对象的继承关系,不影响其他的对象
-
语法:Object.setPrototypeOf(子对象,父级对象)
function A(){ this.name="a" this.toString=function(){return this.name} } function B(){ this.name="b" } var a1=new A() var a2=new A() Object.setPrototypeOf(a2,new B()) console.log(a1.toString()) //a console.log(a2.name)//a delete a1.name console.log(a1.name)//undefined delete a2.name // B作为了a2的原型对象,而没有作为a1的原型对象 console.log(a2.name) //b
-
- 修改构造函数的原型,为该构造函数创建的对象指定统一的父级对象
14.1.11、只继承原型
-
出于效率考虑,尽可能地将可重用的属性和方法添加到原型中
- 不要单独为继承关系创建新对象
- 尽量减少运行时的方法搜索,例如toString()方法
function A(){} A.prototype.name="name" A.prototype.toString=function(){return this.name} function B(){} B.prototype=A.prototype B.prototype.name="b" var b=new B() console.log(b.name) //b console.log(b.toString())//b A.prototype.name="C" console.log(b.name) //C console.log(b.toString())//C // B.prototype=A.prototype的方式会造成原型对象的地址指向同一处,造成数据冗余 // =========================================================================================== // 解决方法 function A(){} A.prototype.name="name" A.prototype.toString=function(){return this.name} function B(){} B.prototype.__proto__=A.prototype B.prototype.name="b" var b=new B() console.log(b.name) //b console.log(b.toString())//b A.prototype.name="C" console.log(b.name) //b console.log(b.toString())//b
14.2、ECMAScript6的面向对象设计
14.2.1、对象直面两里的简写
-
增强的对象直面量
- 简化的属性定义
- 简化的方法定义
- 可动态生成的成员名
var id=1001,ename="eric",age=19
var eric={
ename,age,["emp"+id]:id,
intr(){
console.log("l am"+this.ename)
}
}
console.log(eric) //{ename: 'eric', age: 19, emp1001: 1001, intr: ƒ}
14.2.2、class方式创建对象模板
- class方式–es6新特性
- ES6借签了其他编程语言中声明对象的方式,正式启用了class关键字来创建类,再创建类的实例:对象
"use strict" //class关键字必须用于严格模式
class Emp{ //声明一个类
constructor(ename){ //构造函数
this.ename=ename
}
work(){
console.log(`ENAME:${this.ename}`)
}
}
var e3=new Emp('约翰') //创建类的实例
e3.work()
- 如何使用class创建类实例,class就是包裹构造函数和原型对象的方法的一种程序解构
- class的命名,首字母大写
class+{}
来包裹构造函数和原型对象- 构造函数名提升为类名
- 构造函数function更名为constructor,内部无任何变化,原型对象方法只要加在class里,就不用加类名.prototype前缀,同时也不用加function,放在class里的方法,不加static前缀,都会自动放入原型对象中。
// es5创建对象的实例
function Emp(ename){
this.ename=ename
}
Emp.prototype.work=function(){
console.log('l am'+this.ename)
}
var e3=new Emp('约翰')
console.log(e3)
// 旧的语法不符合封装的原理,所以衍生出了es6的class方式
class Emp{
constructor(ename){
this.ename=ename
}
// 方法自动放进原型对象中
work(){
console.log('l am'+this.ename)
}
}
var e3=new Emp('月哈')
console.log(e3)
14.2.3、class的继承
-
ES6中的继承通过使用class配合
extends
关键字来实现class Emp{ constructor(ename){ this.ename=ename } work(){ console.log('l am'+this.ename) } } class Programer extends Emp{ //extends 继承类 constructor(ename,skill){ super(ename) //继承父类的属性 this.skill=skill } work(){ super.work() //继承父类的方法 console.log(`我会:${this.skill}`) } } var p1=new Programer('汤姆','js') p1.work() console.log(p1)
14.2.4、 Class中使用访问器属性
-
ES6的class中也可以使用get/set访问器属性实现虚拟扩展属性
-
案例1
class Emp{ constructor(firstName,lastName){ this.firstName=firstName this.lastName=lastName } get fullName(){ return this.firstName+" "+this.lastName } set fullName(v){ this.firstName=v.split(" ")[0]; this.lastName=v.split(" ")[1] } } var lilei=new Emp("Li","Lei") console.log(lilei.fullName) //Li Lei lilei.fullName="Han Meimei" console.log(lilei.firstName,lilei.lastName) //Han Meimei
-
案例2
class Emp{ constructor(ename,age){ this.ename=ename Object.defineProperty(this,"_age",{ writable:true, enumerable:false, configurable:false }) this.age=age } // prototype // age访问器属性 // age访问器属性放在原型对象是因为访问器属性不存值,他其实本质上是两函数,分开写到原型对象中可以节省内存 get age(){ return this._age } set age(value){ if(value>=18&&value<=65){ return this._age=value }else{ throw Error('年龄必须介于18-65之间') } } // prototype work(){ console.log(`l am ${this.ename}`) } } var e1=new Emp('dema',25) console.log(e1.age) e1.age=11 console.log(e1.age)
14.2.5、 Class中添加静态成员
class Rectangle{
constructor(name,x1,y1,x2,y2){
this.name=name
this.x1=x1
this.y1=y1
this.x2=x2
this.y2=y2
}
static defaultRectangle(){ //加static为该类添加静态成员,报错,c1.defaultRectangle is not a function
return new Rectangle('default',0,0,100,100)
}
}
var r1=Rectangle.defaultRectangle()
console.log(r1) //{name: 'default', x1: 0, x2: 100, y1: 0, y2: 100}
class Circle extends Rectangle{
constructor(name,x1,y1,x2,y2){
super(name)
this.x1=x1
this.y1=y1
this.x2=x2
this.y2=y2
}
}
var c1=new Circle('default',0,0,100,100)
c1.defaultRectangle()
console.log(c1)
十五、ECMAScript5
15.1、ECMAScript5的新特性
- 严格模式
- 新的数组api
- 新的函数api
- 增强的Object
- 新的JSON操作API
15.2、ECMAScript5概述
- 1999年ECMAScript3发布,成为JavaScript的通用标准,得到广泛的支持
- 2007年ECMAScript4发布,但由于修改幅度过于激进,导致各方产生较大分歧。仅保留少量更改,降级为ECMAScript 3.1版本。后改为EcmaScript 5
- 2009年12月ECMAscript5.0发布
- 2011年6月 ECMAScript5.1发布,形成ISO国际标准
- 2015年6月ECMAScript6正式发布,也称为ECMAScript2015
- 目前来说,收到广泛支持的仍然是ECMAScript3版本
15.3、严格模式 “use strict”
- ECMAScript5中定义了一种限制性更强的严格模式
- 严格模式不是正常代码的子集,而是具有完全不同的语义
- 使用严格模式的代码必须经过严格的特性检测
- 严格模式可以与非严格模式并存,建议逐渐将代码纳入严格模式
- 作用
- 消除JavaScript的陷阱:将不合理的行为直接变为错误
- 提高效率,增加运行速度,严格模式修正了一些引擎难以优化的错误
- 为未来新版本JavaScript做铺贴,严格模式禁用了一些可能在未来新版本中使用的语法和关键字
15.3.1、为script
开启严格模式
-
为整个
script
标签开启严格模式,需要在所有语句之前放一句特有语句use strict
- 在严格模式下,先前被接受的拼写错误将被认为是异常
- 好处是无法再擅自创建全局变量
// 严格模式下 "use script" m=10 consol.log(m) //报错,找不到m // 非严格模式下 m=10 console.log(m) // 严格模式下,不允许为未声明的变量赋值;非严格模式下给未声明的变量赋值会自动在全局创建变量
-
注意,盲目拼合严格模式和非严格模式可能会有陷阱,建议用函数包裹要采用严格模式的代码段
15.3.2、严格模式下
-
没有静默失败 (静默失败:不报错也没有效果)
-
禁止给常量赋值
(function(){ //采用非严格模式的函数 const Pl=3.14 pl=1.14 // 静默失败 console.log(Pl) })() (function(){//采用严格模式的函数 "use strict" const Pl=3.14 pl=1.14 // 报错 console.log(Pl) })()
-
禁止删除不允许删除的属性
(function(){ //采用非严格模式的函数 delete Object.prototype //静默失败 console.log(Objecct.prototype) })() (function(){//采用严格模式的函数 "use strict" delete Object.prototype //报错 console.log(Objecct.prototype) })()
-
现在的大多数浏览器严格模式静默失败升级为错误,是在设置对象属性禁止删除之后才会报错。以上例子会不会成功取决于浏览器的版本。
-
严格模式下,多了一个eval作用域,专门保存eval中创建的变量,一旦eval执行结束,变量随eval作用域释放
var x=17; eval("var x=42,console.log(x)"); //42 console.log(x) //42 var x=17 eval("'use strict';var x=42;console.log(x)")//42 console.log(x)//17
-
arguments中的值,不再随参数变量的值改变而改变
(function(a){ //采用非严格模式的函数 a++ ; console.log(arguments[0])//11 })(10) (function(a){//采用严格模式的函数 "use strict" a++ ; console.log(arguments[0])//10 })(a)
-
不允许在函数内调用arguments.callee,这意味着,无法再匿名函数实现递归调用
var f=function(n){ var fn=arguments.callee if(n<3){ return 1 }else{ return fn(n-1)+fn(n-2) } } // 用循环代替递归执行效率更快 function a(){ var f1=1,f2=1,fn for(var i=3;i<=n;i++){ fn=f1+f2 f1=f2 f2=fn } return fn }
15.4、增量的Object对象的属性
- ES中的对象,就是属性的集合
- 属性(property)又分两种
- 命名属性 – 代码中可直接使用,又可分为
- 数据属性
- 访问器属性
- 内部属性–ES内部定义,用于描述对象特定情况下的行为
- 比如
- 继承:proto:应用当前对象继承的原型对象
- 可扩展:Extensible:表示是否能为对象扩展新属性
- class属性:对象类型名。不能直接访问,而是通过强行调用
Object.prototype.toString()
方法打印出来,打印结果的第二部分就是类名- 比如
[object Array]
[object Date]
[object Object]
- 比如
- 命名属性 – 代码中可直接使用,又可分为
15.4.1、数据属性
- 数据属性就是专门存储对象的一个属性值的属性
- 今后只有存储一个属性值,就用数据属性
- 比如:数据属性可以在创建对象时就定义好,也可以在创建完对象后,采用赋值方式增加新数据属性
- ES5之前,程序员都是通过赋值的方式,添加新的属性,而添加的属性随时可以改,可以遍历
- 但是,有时候需要对对象的属性做必要的保护,不允许随意修改对象的属性
15.4.2、保护数据属性的属性特性
每个数据属性都有四个特性(Attribute)来保护数据属性
-
[[Value]]
实际存储属性值的特性 -
[[Writable]]
表示能否修改属性的value值。默认为true,如果设置为false,则该属性只读 -
[[Enumerble]]
表示能否通过for in遍历循环到该属性,默认为true,如果改为false,则使用for in 遍历不到 -
[[Configurable]]
表示能否通过delete删除该属性或能否修改其他属性的特性,默认为true,如果改为false,则属性的其它特性一旦定义,不可修改 -
属性的特性无法直接访问到,只能通过特定的API访问
-
通过特定的API获取属性的特性
Object.getOwnPropertyDescriptor
- 返回值是一个封装了指定属性四大特性的对象
var person={name:"huwenhao",age:20} var descriptor=Object.getOwnPropertyDescriptor(person,"name") console.log(descriptor) //{value: 'huwenhao', writable: true, enumerable: true, configurable: true} // 除value外,其余特性,不写默认为true
-
设置单个属性的特性
Object.defineProperty
Object.defineProperty(obj,"属性名",{ value:值, writable:true/false, .... }) // 在obj对象中添加指定"属性名"的数据属性,同时设置该属性的特性 // 如果指定"属性名"已存在于obj对象中,则直接修改原属性的特性 // Object.defineProperty()一次只能添加或设置一个属性
-
设置多个属性的特性:Object.defineProperties()
Object.defineProperties(obj,{ "属性名":{ value:值, writable:true/false, .... }, "属性名":{ value:值, writable:true/false, .... }, ..... })
15.4.3、数据属性的访问器
-
数据属性的开关只能对自己提供最简单的方法,无法使用自定义的规则来保护自己的属性
-
访问器属性是专门控制对一个数据属性读写操作的特殊属性。它不包括具体属性值。而是包含一对get/set方法
-
今后只要保护一个数据属性的读写操作,就用访问器属性
-
访问器属性不能直接定义
-
一次定义一个数据属性通过
Object.defineProperty
方法var emp={ _id:1001, name:"埃里克", _eage:25 } for(var key in emp){ console.log(`${key} in ${emp[key]}`) } // _id in 1001 // name in 埃里克 // _eage in 25 Object.defineProperty(emp,"_id",{ enumerable:false, configurable:false }) Object.defineProperty(emp,"id",{ //添加id属性 get:function(){ return this._id }, enumerable:true, configurable:false }) Object.defineProperty(emp,"_eage",{ enumerable:false, configurable:false }) Object.defineProperty(emp,"eage",{ get:function(){ return this._eage }, set:function(value){ if(value>=18&&value<=65){ this._eage=value }else{ throw Error('年龄必须介于18-55之间') } }, enumerable:true, configurable:false }) for(var key in emp){ console.log(`${key} in ${emp[key]}`) } // name in 埃里克 // id in 1001 // eage in 25 // 尝试获得emp的id属性 console.log(emp.id) //1001 // 尝试修改emp的id属性 //emp.id=-2 //console.log(emp) // 尝试获取emp的eage属性 console.log(emp.eage) // 尝试修改emp的eage属性 // emp.eage=18 // console.log(emp) // emp.eage=-1 // console.log(emp)
-
一次定义多个数据属性通过
Object.defineProperties
方法// 案例1 var emp={ id:1001, name:"埃里克", eage:25 } Object.defineProperties(emp,{ "_id":{ value:emp.id, enumerable:false, configurable:false }, "id":{ get(){ return this._id }, enumerable:true, configurable:false }, "_eage":{ value:emp.eage, enumerable:false, configurable:false }, "eage":{ get(){ return this._eage }, set(value){ if(value>=18&&value<=65){ this._eage=value }else{ throw Error('年龄必须介于18-55之间') } } } }) //操作 console.log(emp.eage) emp.eage=18 console.log(emp.eage) console.log(emp._eage)
-
-
访问器属性的特性
- 属性访问器没有value和writable特性
- 访问器属性的特性
[[Get]]
读取属性时自动调用的函数,不是必须,如果不提供,表示不可读取受保护的属性值[[Set]]
写入属性值时自动调用的函数,不是必须,如果不提供Set方法,表示受保护的属性值为只读[[Enumerable]]
同数据属性的特性[[Configurable]]
同数据属性的特性
- 以上四个特征,都可以使用defineProperty方法设置
15.5、增强的Object对象
15.5.1、Object内置构造函数里的新方法
-
Object是Object类型得构造函数,所以typeof Object返回得是Function
-
而Object.prototype对象才是所有对象得父对象,
-
Object作为构造函数,有三种情况
Object()
如果传入参数null,undefined,或者不传入,则相当于new Object(),创建有原型的空对象Object(原始类型值)
将原始类型的值转化为Object类型,也等效于new Object(原型类型值)Object(引用类型的对象)
什么都不做,还返回原对象
-
Object类的新方法
Object.getPrototypeOf(obj)
得到obj的原型对象Object.getOwnPropertyDescriptor(obj,"属性名")
返回对象中指定属性的特性Object.getOwnPropertyNames(obj)
获取对象中所有属性名的数据Obejct.keys(obj)
仅返回可被枚举的属性名组成的数组Object.create()
用于创建一个新对象-
新对象继承自指定的父级原型对象
-
同时又扩展出多个自有属性
-
语法
- var newObj=Object.create(proto,{扩展属性对象})
- 其中,{扩展属性对象}和defineProperties方法第二个参数完全一样
- 强调:和defineProperties方法第二个参数一样,除value特性外的其余三个特性,默认都为false
- 何时使用:今后只要基于一个已有的父对象,创建新对象,同时还可能扩展新对象的自由属性时,就要用Object.create方法
-
Object.create方法的特殊用法
-
创建一个原型为null的空对象:var o=Object.create(null)
-
实现子类型构造函数的原型继承父类型构造函数的原型: Sub.prototype=Object.create(Super.prototype)
-
创建普通空对象 等效于var o={}:var o=Object.create(Object.prototype)
-
创建对象同时扩展自有属性
var flyer={name:"A380",speed:1000} var plane=Obejct.create(flyer,{ capacity:{ value:555, writable:true, enumerable:true } })
-
-
案例
function Flyer(fname,speed){ this.fname=fname; this.speed=speed } Flyer.prototype.fly=function(){ console.log(`${this.name}以时速${this.speed}飞行`) } function Plane(fname,speed,capacity){ Flyer.call(this,fname,speed) this.capacity=capacity } Plane.prototype=Object.create(Flyer.protoType) var b747=new Plane('B747',700,300) b747.fly() console.log(b747)
-
15.5.2、防纂改对象
-
访问器,特性是保护对象的数据属性,防纂改是保护对象的结构
-
ES3标准的Javascript中,程序员可以在任何时候,任意位置,无论有意还是无意的修改任何对象的属性
-
这些纂改可能会影响内置对象的内置属性和方法,从而造成内置对象的纂改,导致正常的功能无法正常执行。
-
es5标准为了防止程序员纂改关键对象的属性和方法,提供了三种保护方式
- 防扩展,禁止为对象扩展新属性
- 密封对象:禁止扩展新属性,禁止配置现有属性的特性,仅允许读写属性的值
- 冻结对象:禁止对对象执行任何修改操作
禁止为对象扩展新属性
- 如果希望禁止为对象扩展新属性,就要修改内部属性Extensible为false
- 获取或设置Extensible属性,只能通过专门的API来访问
-
Object.isExtensible(obj)
获取对象是否可扩展 -
Object.preventExtensions(obj)
修改Extensible为false,一旦改为false,就不可能再改为truevar obj={} console.log(Object.isExtensible(obj))//true obj.id=1001 console.log(obj.id) Object.preventExtensions(obj) console.log(Object.isExtensible(obj))//false obj.name="scrott" console.log(obj.name) //undefiend
-
密封对象
- 密封对象:禁止添加新属性,禁止修改现有属性的特性,仅可以修改属性值
- 密封属性就要修改Extensible属性为false,同时修改所有的属性的configurable属性为false
-
Object.isSealed(obj)
判断obj对象是否为密封对象 -
Object.seal(obj)
修改obj对象的内部属性和属性的特性,使其密封var obj={id:1001,name:'dema'} console.log(Object.isSealed(obj)) //false Object.seal(obj) console.log(Object.isSealed(obj))//true console.log(Object.isExtensible(obj))//false console.log(Object.getOwnPropertyDescriptor(obj,"id"))
-
在开发场景中,如果每一个属性都不能删除,直接给该对象设置Object.seal(对象),所以一般情况下,对象保护到密封对象阶段
-
冻结对象
-
不允许对对象的现有属性及其属性值做任何修改,也不允许增加新属性
-
冻结对象
-
Object.isFrozen(obj)
判断obj是否被冻结 -
Obejct.freeze(obj)
冻结obj对象var obj={id:1001,name:'dema'} console.log(Object.isFrozen(obj))//false Object.freeze(obj) console.log(Object.isFrozen(obj))//true obj.age=18 obj.name="Tom" console.log(obj)
-
-
应用环境:多个模块共用的对象,需要冻结
15.6、函数中强行改变this指向
- 强行改变this的指向,需要用到函数类上的3个方法
call
apply
bind
- call和apply改变this的指向是临时的,bind可以永久的指向
15.6.1、FunctionName.call(objName,参数1,参数2…)
-
只改变当前一次,下次再调用该方法,仍然需要改变this的指向
-
让函数中的this指向call的第一个参数,后面的参数为传入原方法的值
-
会自动执行原方法
var obj={ a:10, b:15 } function fn(x,y){ console.log(x+y) console.log(this.a+this.b) } fn.call(obj,2,3);
15.6.2、FunctionName.apply(objName,[参数1,参数2…])
- apply和call的作用大抵一样,唯独在给原方法提供参数时,必须把原方法的参数放入一个数组中,不然会报错。
- 会自动执行原方法
15.6.2、FunctionName.bind(objName,参数1,参数2…)
-
调用函数前的一个常见需求是希望可以预定this的值,或是预先为部分形式参数指定实际值
-
ECMAScript5中定义了一个新的函数bind,可以实现这样的需求
-
bind函数用于基于现有函数创建一个新函数对象,同时为新函数对象提前绑定this值或设置参数值
-
bind函数在调用时,需要指定this的值,同时还可以指定若干个形式参数的实际值
-
bind的作用
- 让调用bind的原方法的this指向参数
- 并且返回改变后的新方法
let newAdd=add.bind(obj)
【注意】
bind不会执行原方法,如果必须要使用bind执行原方法在后面加();但是bind执行原方法是伪代码,因为bind的作用是形成新方法(把this改变后的方法保留下来),所以永远不用bind去改变this指向的同时执行源代码,返回值是this改变后的新方法 所以把其赋值给新方法即可,后续调用原方法就是调用this改变后的方法
【面试题:this的指向有多少种情况】
分为两个方向 第一个方向是function声明的函数,大括号中会形成一个作用域,从而改变this的方向,谁调用这个函数,this指向谁。 第二个方向是ES6的箭头函数,this指向函数声明的位置。
function声明的函数有以下几种情况:
1.对象里的方法 obj.fn() this指向obj
2.构造函数里的this 指向正在new的对象
3.原型对象里的方法因为原型对象上的方法只有通过类实例化才能调用,所以方法中的this会指向这个类实例化的对象。
4.直接调用的fn this会指向window
5.回调函数,匿名函数的自调用都是相当于直接调用这个函数所以this都指向window
6.dom触发的事件中的匿名函数会指向触发这个事件的dom元素
7.vue组件中 this会指向vue对象
箭头函数,有如下几种情况
1.对象中的方法 this指向window 所以在对象中的方法一般不用箭头函数
2.当new的时候 this会强制性的指向这个实例化的对象,所以箭头函数对new没有影响
3.在原型对象中用箭头函数,this会指向window,所以在原型对象中一般也不写箭头函数
4.在函数自调用时,使用箭头函数 输出this就会报错,所以不能使用箭头函数
5.在回调函数中,小括号不会形成作用域,this不会发生改变,但是this所在的作用域和它外部作用域会指向同一个地方。
6.在vue中方法使用箭头函,this指向window
总结
回调函数:
在回调函数中,function形式的函数相当于直接调用,所以this指向window;箭头函数形式的函数的this和它所在作用域的外部作用域指向同一个地方
Function:
不是属于对象的方法,调用就是直接调用,那么它的this指向window,有没有改变,要看function的外层作用域指向谁。
1.不产生新的作用域,this不会改变;
2.箭头函数虽然产生了新作用域,但是this的指向也在创建时就确定了,所以是否会改变要看调用时所在的作用域和方法内部的this是否一致。
十六、ECMAScript6
- ECMAScript6.0(简称6) 是JavaScript语言的下一代标准,于2015年6月正式发布
- 使得JavaScript语言可以用来编写复杂的大型应用程序,成为企业级开发语言
- ES6既是一个历史名词,也是一个泛指,含义是5.1版以后的JavaScript的下一代标准,涵盖了ES2015,ES2016,ES2017等等
16.1、let块级作用域
-
ES6中,变量的作用域分为三种
-
局部作用域:只能在当前函数中使用
-
全局作用域:可以在任意函数内使用–是全局对象的成员
-
块级作用域:只能在当前块内使用—es6特性
"use strict" //块级作用域需要运行于严格模式 for(let i=0;i<10;i++){ console.log(i) // 正常执行 } console.log(i) //执行错误
-
//console.log(a);
//var a=1;
//undefined
//console.log(b);
//let b=2;
//不会提升 直接报错
//letb=2;
//letb=4;
//console.log(b);
//不允许重复声明,但可以重复赋值
//大括号叫做块级作用域,把let封装进去,不会造成全局污染。
{
//var c=3; //可以打印
let c=3;
console.log(c); //console在作用域以内可以打印
}
console.log(c); //console在作用域以外不能打印
// =====================================================
// 使用let可以实现闭包效果
for(let i=1,sum=0;i<=100;i++){
sum+=i
}
console.log(sum);//用let声明的变量都不是全局变量,所以在括号以外访问不到sum,所以sum不能放入循环中。
let sum=0
for(let i=1;i<=100;i++){
sum+=i
}
console.log(sum); //sum写外面就可以打印
16.2、箭头函数
-
箭头函数是匿名函数的另一种写法,不等同于匿名函数
var arr=[10.20.15.17] arr.sort(function(a,b){ return a-b }) ↓ // 箭头函数 var arr=[10,20,15,17] // arr,sort((a,b)=>{ return a-b }) ↓ 简化为 arr.sort((a,b)=>a-b) console.log(arr) var fn=function(){ } ↓ 简化为 var fn = () => { console.log('fn') } fn()
-
箭头函数内外共用统一的this
var arr=[1,2,3] // arr.forEach(function(elem,i,arr){ // arr[i]=elem*2 // }) arr.forEach((elem,i,arr)=>arr[i]=elem*2) console.log(arr) // =================================== <button id="btn1">click me</button> var n=0 var btn1=document.getElementById('btn1') // btn1.οnclick=function(){ // alert(++n) // } btn1.onclick=()=>{ alert(++n) }
16.3、默认参数
-
默认值:es6中,允许参为函数的参数列表末尾的几个参数变量,预先定义默认值
Array.prototype.indexOf=function(val.formi=0){ ... } var arr=[1,2,3,4,3,2,1] arr.indexOf(2)//1 arr.indexOf(2,2)//5
-
可以给参数设置默认值
// es5的写法 function add(a,b,c){ //es5设置参数默认值 c=c||0 return a+b+c } console.log(add(2,4)) console.log(add(2,4,8)) // es6的写法 function add(a,b,c=0){ return a+b+c } add(2,4)
16.4、...
打散数组和剩余参数
-
...
放在形参中是参数形成一个数组(剩余参数),放在实参中是将数组打散为单个值(打散数组) -
剩余参数,es6中,为了代替arguments类数组对象来接收不确定个数的参数值,新增了剩余参数(rest)的特性
function calc(base,...bonus){ return bonus.reduce( (prev,val)=>prev+val,base ) } calc(10000,1000,2000,3000) //16000
-
es6中为了代替apply,实现更灵活的打散数组类型参数的目的,提供了散播(spread)的新特性
function calc(base,b1,b2,b3){ return bonus.reduce( (prev,val)=>prev+val,base ) } var bonus=[1000,2000,3000] calc(10000,...bonus) //16000
16.5、模板字符串
- 单行语句: 使用一对单引号或双引号
- 多行语句: 可以使用一对
(反引号)
来指示多行语句 - 模块字符串支持换行,支持动态生成内容
- 可以使用
${}
在字符串中内嵌JavaScript变量,并使用一对(反引号)
来只是字符串,方便实现字符串的拼接 - 原始字符串:es6中为了避免字符串中的转义符与路径,正则等规则出现歧义,特别引入了原始字符串,用于代替之前手工修改的\
16.6、解构赋值
-
es6中,为了将一个复杂结构中的成员值,分别打散到多个变量中,批量赋值,提供了结构的新特性
-
数组解构:按数组下标匹配每个变量和对应位置的值
var list =[1,2,3] var [a,b,c]=list console.log(a,b,c) [a,c]=[c,a] //交换俩变量的值
-
对象解构:按对象属性米名,匹配每个变量和对象的值
var obj={x:1,y:2,z:3} var {x:a,y:c,z:c}=obj console.log(a,b,c) //1.2.3
-
参数解构:定义函数的参数列表是,也可以用解构语法,可代替apply打散数组或对象中的成员值为单个值,再分别传入
function f([name,val]){ console.log(name,val) } function g({name:n,val:v}){ console.log(n,v) } function h({name,val}){ // {name,val} 等价于 {name:name,val:val} console.log(name,val) } f(['bar',42]) f({name:'foo',val:22}) f({name:'bar',val:42})
16.7、Promise对象
16.7.1、如何理解回调地狱
-
代码耦合性太强,牵一发而动全身,难以维护
-
大量冗余的代码相互嵌套,代码的可读性变差
-
为了解决回调地狱问题,ES6中新增了Promise的概念
//向某位女生发送一则表白短信 //name:女神的姓名 //onFulfilled:成功后的回调 //onRejected:失败后的回调 function sendMessage(name,onFulfilled,onRejected){ //模拟 发送表白短信 console.log(`${name},我喜欢你`) console.log(`等待${name}的回复`) //模拟 女神回复需要一段时间 setTimeout(()=>{ if(Math.random()<=0.1){ //成功 onFulfilled(`${name}:我也喜欢你`) }else{ //失败 onRejected(`${name}:我有喜欢的人了`) } },1000) } sendMessage( '李建国', (reply)=>{ //如果成功,输出回复的消息,结束 console.log(reply) }, (reply)=>{ //如果失败,输出回复的消息,向下一个女神发 console.log(reply) sendMessage( '张茜', (reply)=>{ //如果成功,输出回复的消息,结束 console.log(reply) }, (reply)=>{ //如果失败,输出回复的消息,向下一个女神发 console.log(reply) sendMessage( '张小毛', (reply)=>{ //如果成功,输出回复的消息,结束 console.log(reply) }, (reply)=>{ //如果失败,输出回复的消息,向下一个女神发 console.log(reply) console.log('万年单身狗') } ) } ) } )
16.7.2、什么是Promise
- promsie对象代表一个异步操作,有三种状态
- pending:初始状态,不是成功或者失败的状态
- fulfilled:意味着操作成功完成
- rejected:意味着操作失败
- 优点:
- 将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数,避免回调函数
- promise对象提供了统一的接口,使得控制异步操作更加容易
- 缺点:
- 无法取消Promise,一旦新建它就会立即执行,无法中途取消
- 如果不设置回调函数,Promise内部抛出得错误,无法反映到外部
- 当处于pending状态时,无法得知目前进展到那个阶段(刚刚开始还是即将完成)
16.7.3、Promise规范
这套规范最早诞生于前端社区,规范名称为Promise A+
Promise A+规定:
1、所有的异步场景,都可以看作是一个异步任务,每一个异步任务,在js中应该表现为一个对象,该对象称之为promise对象,也叫做任务对象。
2、每个任务对象都应该有2个阶段(未决阶段 unsettled 、已决阶段settled),三个状态(pending挂起 rejected失败 fulfilled 完成状态)。
- 任务总是从未决到已决阶段,不可逆行
- 任务总是先挂起到完成或失败,无法逆行
- 任务一旦完成或失败,状态就固定下来,永远无法改变
3、挂起到完成,成为resolve;挂起到失败,称之为reject。任务完成后,可能会有一个相关数据。任务失败时,可能会有一个失败的原因。
16.7.4、Promise机制
- 为了避免回调地狱
- promise是一套专门处理异步场景的规范,他能有效的避免回调地狱的产生,使异步代码更加清晰,简洁,统一。
- 可让连续的回调函数调用,看起来更像是连续调用而不是嵌套
- 在方法中的return里 new promise一个对象,对象中的回调函数中有两个参数,resolve 同意,执行下一项任务 ;reject不同意,错误代码处理
primise是一个构造函数
- promsie是一个容器,promsie本身不是异步的,但是容器内部往往存放一个异步操作
- promise构造函数接受一个函数作为参数,该参数分别是resolve和reject。reject和resolve两个形参接收的是函数,接收的函数由javascript引擎提供,不用自己部署
- 通过new promise构造器创建的promise对象是有状态的,状态可能是pending,resolved,rejected中的一种。新创建的promise对象的状态是pending,后续可以变为resolved或rejected,状态一旦变为resolved或rejected就不会再变了
- resolve函数调用后会将promise对象的状态从未完成变为成功,在异步操作成功时调用,并将异步操作的结果,作为参数传递出去
- rejected函数调用后会将promise对象的状态从pending(未完成)变成rejected(失败),在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去。
- new promise()里的是主任务,resolve,reject把任务放进微任务队列中。
- catch方法
catch(onRejected)=.then(null,onRejected)
16.7.5、Promise 链式调用
为什么可以一直then下去?
=》 因为 then的返回值是一个Promise对象
如果回调函数的返回值是Promise对象,则正常then下去
如果不是Promise对象 then方法就会自己创造一个空的Promise对象作为返回值以保证后续的then可以执行空的Promise对象,里面没有人为规定的成功与失败 => 执行第一个回调函数
- then() 是Promise类原型上的方法
- then 的参数有两个,成功的回调和失败的回调 ,回调里的参数【then((xxx)=>{},(yyy)=>{})】就是new Promise对应的方法传过来的数据【resolve(xxx);reject(yyy)】
注: then里面传的是回调函数,也就是方法体 => 不允许在then里直接执行函数
如果需要执行方法体怎么办? => 套在函数里,并且返回出来
load('1.js').then(function(){
return load('2.js')
}).then(function(){
return load('3.js')
}).then(fn)
-
then方法必定会返回一个新的promise,可以理解为后续处理也是一个任务
-
新任务的状态取决于后续处理
-
若没有相关的后续处理, 新任务的状态和前任务一致,数据为前任务的数据
new Promise((resolve,reject)=>{ resolve(1) }) .then((res)=>{ console.log(res)//1 return 2 }) //若没有相关的后续处理,新任务的状态和前任务一致,数据为前任务的数据 .catch((err)=>{ return 3 }) .then((res)=>{ console.log(res) //2 })
-
若前任务还没有完成,新任务挂起
const pro1=new Promise((resolve,reject)=>{ setTimeout(()=>{ resolve(1) },1000) }) const pro2=pro1.then((data)=>{ console.log(data) //1 return data+1 }) const pro3=pro2.then((data)=>{ console.log(data) //2 }) console.log(pro1,pro2,pro3) //看pro2和pro3的状态和值要用到定时器
-
若后续处理了,则根据后续处理的情况确定新任务的状态
// 后续处理无错,新任务的状态为完成,数据为后续处理的返回值 new Promise((resolve,reject)=>{ resolve(1) }) .then((res)=>{ console.log(res)//1 return 2 }) // 后续处理执行有错,新任务的状态为失败,数据为异常对象 new Promise((resolve,reject)=>{ resolve() }) .then((res)=>{ console.log(res.toString()) return 2 }) .catch(err=>{ return 3 }) .then((res)=>{ console.log(res) //3 }) // pro1 fulfilled undefined // pro2 rejected xxx报错 // pro3 fulfilled 3 // pro4 fulfilled 3 // 后续执行后返回的是一个任务对象,新任务的状态和数据与任务对象一致
-
【20230104总结】:首先then必然会返回一个promise,要想任务继续下去,必然要根据上一个promise的状态进行处理。如果没有处理,那数据和状态和上一个任务的promise一样。catch的promise状态只为fulfilled,如果上一个任务是成功,拿不到值,里面代码不执行,如果上一个任务失败,拿到值,代码执行。
16.7.6、Promise 静态方法
promsie是一个构造函数,为我们提供了静态的方法
- Pomise.resolve() 返回完成状态的任务
const pro=Promise.resolve(1);
类似于
const pro1=new Promise((resolve)=>{
resolve(1)
})
- Promise.reject() 返回拒绝状态的任务
const pro3=Promise.reject(1);
类似于
const pro4=new Promise((resolve,reject)=>{
reject(1)
})
- Promise.all(任务数组) 任务数据的每一项都是一个Promise,all方法返回的是一个新任务。任务数组全部成功则成功,任何一个失败则失败。完成后的数据会变成一个数组,失败后返回第一个失败的错误信息
const pro5=Promise.resolve()
const pro6=Promise.reject()
const pro7=Promise.resolve()
const proo=Promise.all([pro5,pro7])//完成后的数据会变成一个数组
proo.then((result)=>{
console.log(result)
})
const proo2=Promise.all([pro5,pro6,pro7])//失败后返回第一个失败的数据
- Promise.any(任务数组) 【20230318批注 node中不支持该方法】 返回一个新任务,任务数组任一成功则成功,返回的数据是第一个成功的数据。任务全部失败则失败,全部任务的原因封装在一个对象中,错误消息error仍然以数组形式返回。
const pro8=Promise.resolve(1)
const pro9=Promise.reject(2)
const pro10=Promise.resolve(3)
const proo3=Promise.any([pro8,pro9,pro10])
setTimeout(()=>{
console.log(proo3)
},1000)
- Promise.allSettled(任务数组) 返回一个新任务,任务数组已抉择成功,该任务不会失败,返回的数组示例如下 (适用于处理汇总结果)
[
{status:'fulfilled',value:1},
{status:'rejected',reason:2},
{status:‘fulfilled’,value:3}
]
- Promise.race(任务数组) 返回一个新任务,返回任务数组中第一个有结果的promise
const pro11=Promise.resolve(1)
const pro12=Promise.reject(2)
const pro13=Promise.resolve(3)
const proo4=Promise.race([pro11,pro12,pro13])
setTimeout(()=>{
console.log(proo4)
},1000)
示例:如何把非promise对象变成promise对象?
var a = 5
Promise.resolve(a).then((res)=>{
console.log(res)
})
var p1 =Promise.resolve(1)
var p2 =Promise.reject(2)
var p3 =Promise.reject(3)
all()
Promise.all([p1,p2,p3]).then((res)=>{
console.log(res)
},(res)=>{
console.log('失败了')
console.log(res)
})
//失败了 3
// all的参数是一个数组,存放一系列Promise对象
// 只有当这些对象都成功时(用resolve传递数据),all才是成功
// 执行后续then里面的第一个回调函数 => 传递的数据是一个数组,
// 存放所有Promise对象用resolve方法传力过来的数据
// 如果有失败的Promise对象(用reject传递数据),all就是失败
// 执行后续then里面的第二个回调函数 => 传递过来的数据只有第一个失败的数据
// 当遇到 必须所有数据都回去成功才执行下一步 的需求时 就用all
var p1 =Promise.reject(1)
var p2 =Promise.reject(2)
var p3 =Promise.reject(3)
race()
Promise.race([p1,p2,p3]).then((res)=>{
console.log(res)
},(res)=>{
console.log('失败了')
console.log(res)
})
// race方法
// 注: resolve/reject 都是传递数据的方法=> 不管传递过来的数据是哪以中方法都会执行后续的then
// race到底是成功还是失败?
// 要看第一个传递过来的数据是resolve 还是 reject
// 如果是resolve 就是成功的 => 执行then里面第一个方法
// 如果是reject 就是失败的 => 执行then里面第二个方法
16.8、async await
async await
- async 是 "异步"得简写,而await得意思是等待,async/await是建立在promise得基础上,像promise一样,也是非阻塞的。
- 根本作用:让异步代码看起来,表现起来更像是同步代码
async
async会将其后的函数的返回值封装成一个promise对象,而await会等待这个promise完成,并将其resolve的结果返回出来。(promsie的特点–无等待),所以在没有await的情况下执行async函数,他会立即执行,返回一个promsie对象,并且,绝不会阻塞后面的语句。
await
-
await等待的是一个表达式,这个表达式的计算结果是promise对象或者其他值,async函数返回一个promise对象,所以await可以用于等待一个async函数的返回值
-
await表达式的运算结果取决于它等的东西
- 如果它等到的不是一个promise对象,那await表达式的运算结果就是它等到的东西
- 如果它等到是一个promise对象,await就忙起来了,他会阻塞后面的代码,等着Promise对象resolve,然后得到resolve的值,作为await表达式的运算结果
**总结:**async会将其后的函数的返回值封装成一个Promise对象,而await会等待这个Promise完成,并将resolve结果返回出来
16.8.1、async await和promise 的区别?
-
区别主要在于按顺序调用多个异步函数时的写法和报错获取
-
promise方式
ajax().then(func1).then(func2).then(func3).then(func4)
-
await/async的方式
async function demo(){ await res=ajax() await res=fun1(res) await res=fun2(res) await res=fun3(res) await res=fun4(res) }
-
报错
1. promise使用.catch抓取错误 2. awit/async 使用try catch的方式抓取错误
-
-
总结
当遇到多个异步函数时,1.promise方法需要很多then,会导致代码不易读,且结构复杂;2. await/async方式让异步代码的格式 与 同步代码一样,更易读。
16.8.2、async await和promise 的执行顺序?
- promise 的执行顺序
promise本身是同步的立即执行函数,当在executor中执行resolve或者reject的时候,此时是异步的操作,会先执行then/catch,当主栈完成后,才会去调用resolve/reject中存放的方法执行。
console.log('script start')
let promise1=new Promise(function(){
console.log('promise1')
resolve()
console.log('promsie1 end')
}).then(function(){
console.log('promise2')
})
setTimeout(function(){
console.log('setTimeout')
})
console.log('script end')
执行顺序
script start-->promsie1-->promise1 end-->script end-->promise2-->setTimeout
- async await的执行顺序
async 函数返回一个Promise对象,当函数执行的时候,一旦遇到await就会先返回,等到触发的同步操作完成,再执行函数体内后面的语句。可以理解为是让出出了线程,跳出了async函数体
async function async1(){
console.log('async1 start')
await async2()
consol.log('async1 end')
}
async fucntion async2(){
console.log('async2')
}
console.log('script start')
async1()
console.log(script end)
输出顺序:script start-->sync1 start-->async2-->script end-->async1 end
- 总结(promise的缺点)
1.因为promise是同步的立即执行函数,因此一旦创建它就会立即执行,中途不能取消
2.当处于pending状态时,无法判断目前进展到哪一个阶段(是刚刚开始还是即将完成)
16.9、Symbol(唯一属性名)
- 基础数据类型 string number boolean null undefined +bigint symbol
- es5之前 对象的属性名都是字符串类型
- es6之后 提供了symbol类型,做属性名使用
- Symbol 专门用来生成唯一的属性名
- Symbol():类似生成了一个 不重复的随机数字
let s=Symbol()
let x=Symbol()
- 利用Symbol做属性名,只能通过[]这种访问器语法来读取
- 不应该声明描述相同的symbol.但是一旦描述信息相同,但是生成的symbol 也是不同的
十七、websocket长连接
17.1、TCP/Ip的长连接和短连接
当网络通信时采用TCP协议时,在真正的读写操作之前。server和client之间必须创建一个连接,当读写操作完成后,双方不再需要这个连接时它们可以释放这个连接,连接的建立是需要三次握手的,而释放则需要4次握手,所以说每个连接的建立都是需要资源消耗和时间消耗的。按照通信方式可以大致分为两类
- 长连接通信
- 短连接通信
17.1.1、TCP短连接
模拟一下短连接的情况,client向server发起连接请求,server接到请求,然后双方建立连接。client向server发送信息,server回应client,然后一次读写就完成了,不过一般都是client先发close操作,为什么呢,一般的server不会回复完client后立即关闭连接 的。
短连接的优点:存在的连接都是有用的连接,提高服务端资源利用率
17.1.2、TCP长连接
在模拟一下长连接的情况,client向server发起连接,server接受client连接,双方建立连接。client和Server完成一次读写之后,他们之间的连接不会主动关闭,后续的读写操作会继续使用这个连接
长连接的特点:实现客户端服务端实时交互,但浪费资源
17.2、Socket和websocket
17.2.1、Socket
- Socket是应用层与TCP/IP协议通信的中间软件抽象层,它是一组接口。在设计模式中,socket其实就是一个门面模式,他把复杂的TCP/IP协议族隐藏在Socket接口后面,对用户来说,一组简单的接口就是全部,让Socket去组织数据,以符合指定的协议。
- 使用Socket提供的api,直接基于TCP/IP与对方进行通信,直接操作api,其他的交给socket底层
17.2.2、WebSocket
-
WebSocket是一种在浏览器端进行Socket通信的一组用户编程接口。WebSocket对象提供了用于创建和管理WebSOcket连接,以及可以通过该连接发送和接收数据的API。可以基于WebSocket实现客户端服务端长连接通信。
-
WebSocket特点有以下三点
- 数据格式比较轻量,通信高效
- 既可以发送文本信息,也可以发送二进制数据
- 协议的前缀是ws(如果加密,则为wss)
17.2.3、Socket和websocket的区别
- Socket是TCP/IP网络的API,是位于应用层和传输控制层之间的一组接口
- WebSocket则是一个典型的应用层协议,是浏览器进行socket通信的用户编程接口
- 因为socket是一个双向的连接,所以前后端都需要相应的技术支持
17.3、Socket.io
-
Socket.io是一个为浏览器与服务器之间提供实时,双向的,基于事件网络通信库。Socket.io实现了WebSocket协议,抹平一些技术细节及兼容性
-
Socket.io包括
- Nodejs服务器版本
- 浏览器的JavaScript客户端版本
17.4、webSocket基础框架
-
一般websocket应用都会嵌在一个webserver应用内部,这个webserver应用除了提供普通的http请求之外,还提供了websocket服务,所以需要基于exoress与socket.io搭建应用基本架构。
-
服务端构建websocket服务(node)
-
安装node
-
安装socket.io
-
新建public目录并设置为静态资源托管目录
```javascript const express=require('express') var app=express() //静态资源托管 app.use(express.static(__dirname+'/public')) app.listen(8080,()=>{ console.log('server is running') }) ```
-
-
建立WebSocket连接需要客户端与服务端基于socket.io提供的API来实现
-
客户端
<script src="scripts/socket.io.js"></script> <script> // 创建连接服务器的websocket </script>
-
服务端
const express=require('express') var app=express() //将express模块注入到http当中,这样,http就能使用express的功能 var http=require('http').createServer(app) var serversocket=require('socket.io')(http) // 与客户端构建websocket长连接 serversocket.on('connection',(client)=>{ console.log(client.id) }) http.listen(1000,()=>{ console.log('server is running ') })
-
-
实现客户端向服务端发消息,包括客户端与服务端两部分
-
客户端广播事件 emit
client.emit('eventName',arguments) //例如: client.emit('TextMessage','hello')
-
服务端侦听事件 on
socket.on('eventName',arguments) //例如 serversocket.on('connection',(client)=>{ client.on('TextMessage',(data)=>{ console.log('TextMessage:'+data) }) })
-
-
实现服务端向客户端发消息,包括服务端与客户端两部分
-
服务端广播事件
serversocket.on('connection',(client)=>{ client.emit('TextMessage','hello') })
-
客户端侦听事件
client=io('ws://127.0.0.1:8080') client.on('TextMessage',(data)=>{ console.log('TexMeg:'+data) })
-
-
实现服务端向所有客户端广播事件
serversocket.emit('hello') // 向特定客户端发送信息:client.emit() // 向所有客户端发送消息:serversocket.emit()
-
十八、WebWorker开启子线程
-
子线程特别浪费cpu资源
-
JavaScript采用单线程模型,也就是说所有的任务只能在一个线程上完成,一次只能做一件事。WebWorker就是为JavaScript创造多线程环境,通过主线程来创建worker线程,可以将繁重的计算工作交由worker线程来实现,此时可以减轻主线程的工作压力
-
想了解WebWorker的存在价值,就需要了解JavaScript浏览器事件循环机制。两者的实现技术不一样。
-
WebWorker的价值就是把耗时的计算操作交给WebWorker在后台异步完成,结束后给主线程(UL)线程发消息(计算结果),这样就不会阻塞主线程完成ui绘制
-
子线程一般用来发送请求或处理耗时的计算操作,js底层不会让子线程更新ui;子线程与主线程并发执行,子线程会把数据封装一下,放入事件队列,由主线程自发的去轮询这个事件队列,并且渲染到页面上。
-
同理,主线程给子线程发消息,也会产生事件队列然后子线程去轮询他并执行。
-
案例
-
创建WebWorker实例,在子线程中执行耗时计算
-
主线程和子线程之间完成数据的传递
// html文件中 <script> // new一个worker对象,括号中写上文件路径 let worker=new Worker('worker.js') // 主线程主动向子线程传递消息 worker.postMessage('Hello!!') // 主线程接收子线程发来的消息 worker.onmessage=(res)=>{ console.log(res) worker.postMessage('Thank you') } </script> // 创建一个js文件`worker.js`作为子线程 console.log('worker...') function fb(n){ return n<3 ? 1 : fb(n-1) + fb(n-2) } res = fb(43) // 子线程接收主线程的信信息 // 主线程发送的数据会被封装成一个messageEvent消息事件,发送的数据封装在data中 this.onmessage=(res)=>{ console.log(res) } // 子线程主动发送消息给主线程 this.postMessage(res)
-
十九、模块化开发
-
原生js中,所有的js脚本文件,都要引入到html文件中,才能运行
-
模块化开发使js文件之间直接可以相互引用,不需要经过第三方
-
引入–
import
引出–export
-
只导出一个类型/接口 默认导出
- export default 类型/接口
- import 自定义类型名 from ‘相对路径’
-
导出多个类型/接口
- 先创建多个类型/接口
- export {…}
- import {…} from ‘相对路径’ 用什么引入什么
-
在创建时就导出
- export default class Demo / import aaa(自定义) from ‘xxx’
- export class Demo / import{Demo} from ’xxx’
-
例如
export const db=12 export function show(){ return 123456 } export calss Demo(){ constructor(){} } /** * 引入时 * import {db,show,Demo} * * * **/ export default db /** * * import xyz from 'demo.js' * * 相当于 let xyz=ab * **/
二十 、DOM和BOM
20.1、DHTML概述
- DHTML:Dynamic HTML,动态HTML,不是一种技术、标准或规范,只是一种将目前已有的网页技术,语言标准整合运用,制作出能在下载后仍然能实时变换页面元素效果的网页设计概念。
- DHTML不是新的语言!DHTML=HTML+CSS+JavaScript。
- DHTML是技术的统称,是一种网页设计的盖帘。
- DHTML的功能
- 动态改变页面的元素
- 与用户进行交互等
- DHTML对象模型包括BOM和DOM
- BOM:Browser Object Model,浏览器对象模型,用来访问和操纵浏览器窗口,使JavaScript有能力与浏览器"对话";通过使用BOM,可移动窗口,更改状态栏文本,执行其他不与页面内容发生直接联系的操作;没有相关标准,但被广泛支持,会有兼容性问题
- DOM:Document Object Model,文档对象模型,用来操作文档;定义了访问和操作HTML文档的标准方法;通过对DOM树的操作,实现对HTML文档内容的操作;w3c组织定义相关的操作标准;DOM是直接操作网页的唯一标准,Jquery vue regular 都是对dom标准的封装。
20.2、DOM概述
- DOM是W3C(万维网联盟)的标准,是中立于平台和语言的接口,它允许程序和脚本动态地访问和更新文档的内容,结构和样式。
- W3C DOM 标准被分为3个不同的部分
核心 DOM
针对任何结构化文档的标准模型XML DOM
针对XML文档的标准模型HTML DOM
针对HTML文档的标准模型
HTML DOM
当网页被加载时,浏览器会创建页面的文档对象模型- 通过DOM,可以访问所有的HTML元素,连同它们所含的文本和属性
- 可以对其中的内容进行修改和删除,同时也可以创建新的元素
- 文档中的所有节点组成了一个文档树(或节点树)
- document对象是一棵文档树的根
- DOM标准发展至今,共三级
- DOM1级规范:98年,最初的DOM规范。定义文档内容的底层结构,所有浏览器100%都兼容
- DOM2级规范:基于DOM1级增加了许多交互模块 比如
- DOM Level2 Core:基于DOM1扩展更多的方法和属性
- DOM Level2 Style:专门操作HTML样式的API
- DOM Level2 Traversal and Range:专门遍历DOM树结构的API
- DOM Level2 Event:标准化的事件API。仅IE8支持,自成一套
- DOM3级规范:进一步扩展了方法和属性,添加了新类型
20.3、DOM树
20.3.1、节点树
- HTML DOM 将HTML文档视作树结构
- 文档中的元素,属性,文本,注释等都看作一个节点,这种被看作一个节点
- 树根为document对象
- 通过document对象,来操作整个节点树
- HTML文档中的每个元素,属性,文本等都代表着树中的每一个节点,HTML文档中的所有内容都被视为文档树中的一个节点
- 整个文档是一个文档节点(document node)
- 每个HTML元素是一个元素节点(element node)
- 元素由开始标签+结束标签+内容 ,例如
<h1>hello</h1>
,元素将会成为dom树上的一个元素对象 - 标签(
<h1>
),一个标签不等于元素,单独一个标签不会成为DOM树上的节点对象,但是,单标签元素,一个标签也可以成为元素对象。
- 元素由开始标签+结束标签+内容 ,例如
- 包含在HTML元素中的文本是文本节点(text node)
- 每一个HTML属性是一个属性节点(attribute node)
- 注释属于注释节点(comment node)
- 浏览器内置的JS解释器会为载入的每个HTML文档创建一个对应的document对象
- 通过使用document对象,可以从脚本中对HTML页面中的所有元素进行访问
20.3.2、document对象
- 浏览器内置的JS解释器会为载入的每个HTML文档创建一个对应的document对象
- 通过使用document对象,可以从脚本中对HTML页面中所有元素进行访问。
20.3.3、节点对象类型 (window.Node)
文档中的元素,属性,文本,注释都会被看作是一个节点
-
DOM树中每种节点都对应一种节点类型
Document
document节点对象的父类型Element
所有元素节点对象的父类型Attr
属性节点对象的类型Text
文本节点对象的类型Comment
注释节点对象的类型
-
节点对象类型与继承关系
-
DOM1级定义了Node类型。Node类型是所有节点类型的父类型。所以,Node类型提用了所有节点对象共有的基本属性和方法。
- nodeType 标明节点类型的数值常量
- nodeName 节点的名称
- nodeValue 节点的内容
-
除此之外,还提供了节点关系的属性以及增加,删除节点的方法
-
Node类型提供的公共属性和方法,多属于核心DOM的范围
-
-
nodeName
节点的名称,String类型属性- nodeName 是只读的
- [节点类型] 元素节点 [nodeName] 标签名
- [节点类型] 属性节点 [nodeName] 属性名
- [节点类型] 文本节点 [nodeName] 始终是#text
- [节点类型] 注释节点 [nodeName] 始终是#comment
- [节点类型] 文本节点 [nodeName] 始终是#document
-
nodeType
节点类型,Number类型属性- [节点类型] 元素节点 [nodeType] 1
- [节点类型] 属性节点 [nodeType] 2
- [节点类型] 文本节点 [nodeType] 3
- [节点类型] 注释节点 [nodeType] 8
- [节点类型] 文档节点 [nodeType] 9
- [节点类型] 文档类型声明节点 [nodeType] 10
-
nodeValue
节点的值,String类型属性- [节点类型] 元素节点 [nodeValue] undefined 或 null
- [节点类型] 属性节点 [nodeValue] 属性值
- [节点类型] 文本节点 [nodeValue] 文本本身
- [节点类型] 注释节点 [nodeValue] 注释文本本身
- [节点类型] 文档节点 [nodeValue] undefined或null
-
案例
console.log(document.body.nodeName)//'BODY' console.log(document.body.nodeType)//1 console.log(document.body.nodeValue)//null
20.3.4、节点间的关系
-
Node类型中定义了节点树中所有节点彼此间的关系属性
- 父节点(parent node)
- 子节点(child node)
- 兄弟节点(sibiling)
-
如果拿到一个元素对象,可以用节点关系进行查找其他
-
节点树中的节点彼此拥有层级关系,Node类型使用如下属性关联节点树上的每个节点对象
parentNode
获取父节点childNodes
获取子节点集合 类数组对象firstChild
获取第一个子节点lastChild
获取最后一个子节点
-
每个节点对象都可使用如下方法访问平行的兄弟节点
previousSibling
获取上一个兄弟节点nextSibling
获取下一个兄弟节点
-
案例:递归方式遍历节点树
// 遍历一个父节点下所有的后代节点 // 1.遍历父节点下的直接子节点 function getChildren(parent){ console.log(parent.nodeName) // 获取当前父节点的直接子节点集合 var childNodes=parent.childNodes; // 遍历直接子节点集合 for(var child of childNodes){ // 2.对每个子节点调用和当前父节点完全相同的遍历操作 getChildren(child) } } //即使看不到的空格回车都是节点对象 getChildren(document.body)
20.4、操作节点
- 通过可编程的对象模型,JavaScript获取了足够的能力来创建动态的HTML
- 查找节点
- 读取节点信息
- 修改节点信息
- 创建新节点
- 删除节点
- 常见DOM属性
- innerHTML
- parentNode
- childNodes
- attributes
- 常见DOM方法
- getElementById()
- getElementsByTagName()
- getElementsByClassName()
- appendChild()
- removeChild()
- replaceChild()
- insertBefore()
- createAttribute()
- createElement()
- createTextNode()
- getAttribute()
- setAttribute()
20.4.1、添加,删除和替换节点
创建元素节点
document.createElement('元素名')
创建文本节点
document.createTextNode('text')
创建注释节点
document.createComment('container begin')
创建文档片段
document.createDocumentFragment()
-
只有当同时添加多个平级子元素时,才会想到用文档片段
-
文档片段中可以包含其他的子节点,待到所有子节点添加完成后,再将整个文档片段添加到DOM树中,从而提高页面渲染效率,防止页面闪屏问题
var fragment=document.createDocumentFragment() var th=docuemnt.createElement('th') fragment.appendChild(th) var tr=document.querySelector('table>thead>tr') tr.appendChild(fragment)
插入节点
- 为一个父元素追加最后一个子节点
parentNode.appendChild(childNode)
- 为父元素中的指定子节点之前添加一个新的子节点
parentNode.insertBefore(newChild,existingChild)
删除节点
parentNode.removeChild(childNode)
【注】子节点被删除前应接触所有绑定的事件,否则无法回收事件绑定所占的内存
替换节点
parentNode.replaceChild(newNode,oldNode)
20.5、操作元素节点
-
DOM操作元素的四步
- 查找触发事件的元素
- 绑定事件处理函数
- 查找要修改的元素
- 修改元素
20.5.1、元素节点
flowchart LR
Node.prototype --> Element.prototype.__proto__ --> HTMLElement.prototype.__proto__ --> HTMLTableElement.prototype.__proto__
- 元素节点对象
- 元素的内容
- 元素的属性
- 元素的样式
元素节点对象
-
文档中的元素都是Element类型的对象
-
Element类型的对象,通过原型继承自Node.prototype
-
所以,Node类型定义的属性和方法,所有Element对象都具备
- 比如 nodeName,nodeType,nodeValue
- 再比如 firstChild,lastChild,childNodes等
-
Element类型,除从Node类型继承了所有节点公共的属性和方法外,还扩展了专门操作元素节点的属性和方法
-
元素节点与普通节点的不同,是元素节点可以有特性(Attribute)和内容(innerHTML)
-
由Element类型提供的所有元素对象共有的属性和方法,也称为核心dom
getAttribute('id')
获取元素对象指定特性的值setAttribute('id','s2')
获取元素对象指定特性的值attributes()
获取元素对象所有特性对象的集合hasAttribute('id')
判断元素对象是否包含指定特性removeAttribute('id')
移除元素对象的指定特性innerHTML
获取元素对象开始标签和结束标签之间的HTML内容原文
-
在HTML文档中,所有HTML元素对象,又都是HTMLElement类型的对象
-
HTMLElement类型是Element类型的子类型,所以Element类型提供的属性和方法,所有HTML元素对象都可以使用
-
HTMLElement类型除了继承Element类的属性和方法外,还重点扩展了简化操作HTML文档中元素对象的标准属性。
- id HTML元素的唯一标识
- title HTML元素的提示信息
- style 对应HTML元素的style属性
- className 对应HTML元素的class属性
- 【注】ECMA标准抢先异步将class定为关键词,所以dom标准不允许使用class属性,所以className对应HTML中的class特性
-
HTML文档中,每种HTML元素对应一个专门的类型
- 例如
<button>
HTMLButtonElement<form>
HTMLFormElement<table>
HTMLTableElement<img>
HTMLImageElement<select>
HTMLSelectElement
-
其实所有HTMLxxxElement类型,都是HTMLElement类型的子类型
-
每种HTMLxxxElement元素,除了具有HTMLElement类型公共的方法外,好扩展了自己对应的HTML元素特有的属性和方法
- HTMLImageElement 扩展了src属性
- HTMLTableElement 扩展了rows,tBodies等属性
- HTMLSelectElement 扩展了options属性等
-
凡是HTMLElement类型及其子类型提供的属性和方法,都称为HTML DOM
-
HTML DOM专门用于简化对HTML元素对象的操作
【总结】
- 核心 DOM类型,可对XML,HTML文档中所有元素节点执行增加,删除,修改,查询操作,但API 相对繁琐
- HTML DOM类型,专门针对HTML文档中的HTML元素,简化操作HTML元素对象的标准特性或子元素。
元素的内容
-
元素节点对象的innerHTML属性读取或设置元素节点中的HTML内容
-
元素节点对象的TextContent属性用于读取或设置元素节点中的文本内容–子HTML标签会被去除
-
nodeValue和TextContent
-
【节点类型】 element node 【nodeValue】null 【textContent】 所包含的所有子节点中的文本内容的组合
-
【节点类型】 text node 【nodeValue】所包含的文本内容 【textContent】 所包含的文本内容
-
案例
<p id="msg">来自<a>新华社</a>的消息</p> var p=document.getElementById('msg') console.log(p.nodeValue) //null console.log(p.TextContent) //来自新华社的消息
-
-
【注意】有争议的innerText
- 标准的DOM操作中,并没有innerText属性
- IE8及之前的IE浏览器不支持标准的textContent属性
- 使用innerText实现类似的功能,但Firefox仍不支持此属性
- innerText与标准的textContent属性并不完全相同
- 案例
<p id="msg">来自<a>新华社</a>的消息</p> var p=document.getElementById('msg') console.log(p.nodeValue) //null console.log(p.innerText) // 来自新华社的消息 / 类似于浏览器解析后的效果 console.log(p.TextContent) //来自新华社的消息 / 类似于源代码的效果
元素的属性
-
元素的属性也是一个节点对象
-
元素节点的attributes属性返回节点的属性集合,即NamesNodeMap—一个类数组对象
-
案例
<a id="bdlink" class="sitelink" href="javascript:void(0) onclick="jump()">百度搜索</a> <script> var a=document.getElementById('bdlink') console.log(a.attributes) //返回类数组对象 NamedNodeMap console.log(a.attributes.length) // 4 console.log(a.attributes[1]) //class="sitelink" 属性节点 console.log(a.attributes['class']) // class="sitelink" 属性节点 </script> // 获取属性节点对象 console.log(a.attributes[3]) console.log(a.attributes["onclick"]) console.log(a.getAttributeNode("onclick")) // 获取属性值 console.log(a.attributes[3].value) console.log(a.attributes["onclick"].value) console.log(a.getAttributeNode("onclick").value) // 创建属性节点对象 var attrNode=document.createAttributes('class') attrNode.nodeValue="hightLight" // 实际开发中用最简方式:h1.classname=”hightlight”(不以属性节点对象方式来操纵属性值。而是可以直接强行设置属性值。而html标准属性都可用.直接修改属性值,这也是实际开发中最推崇的方式) // 修改属性的值 console.log(a.setAttributes(name,value)) //ie8一下版本不支持 console.log(a.setAttributes(attrNode)) // 移除属性 console.log(a.removeAttribute('class')) console.log(a.removeAttributeNode(attrNode)) // 想移除a元素的class属性 // 以节点方式 // var classNode=a.attributes['class'] //获取节点方式1 // var classNode=a.attributes[0] //获取节点方式2 // a.removeAttributeNode(classNode)//移除 // 也可以用属性名来删除 // a.removeAttributes('class') // 是否拥有该属性 a.hasAttributes('属性名') //true 或 false a.hasAttributes() //是否拥有该属性
-
Property and Attributes
-
特性(Attribute):在html元素开始标签中定义的HTML标准属性或自定义属性,用get/setAttribute()属性
-
属性(property):内存中的对象上定义的,用.访问的属性。
-
共同点:操作标准属性时,二者通用
-
差异:操作自定义属性时,不通用
elem.新定义属性=值; //getAttribute()访问不到 elem.setAttribute('自定义属性','值') //elem.自定义属性 访问不到
-
元素的样式
-
HTML中定义样式三种方法
- 内联样式:style=“xxxx:xxx;xxx:xxx”
- 内部样式表:
- 外部样式表:
-
DOM分别提供了针对的API操作各个位置定义的样式
-
- 最常用的就是访问元素的内联样式:
style特性
- HTML元素的style特性,返回一个CSSStyleDeclaration类型的对象。Style特性中的CSS样式属性,都是style对象的属性。可通过.运算符获取和设置样式属性的值
- 仅包含HTML元素中定义的内联样式
- 属性名:style对象中的属性名都是将CSS中的样式属性名去横线,变驼峰后的结果,比如
- CSS中:background-color,list-style-type
- style对象中:backgroundColor,listStyleType
- 获取或设置元素的内联样式
- 获取:
elem.style.属性名
所有属性均返回字符串类型 - 设置:
elem.style.属性名="值"
;值也要是字符串
- 获取:
- 强调:在浏览器标准模式下。带单位的属性设置时,必须加单位。取值时,也会返回带单位的值
- style只代表内联样式 所以element.style.属性名只能获取元素的内联样式
- 最常用的就是访问元素的内联样式:
-
- 获取计算后的样式
getComputedStyle(元素).属性名
var style=getComputedStyle(h1); console.log(style.fontSize) style.fontSize="15px";//报错 只能通过style.属性名的方式,设置内联 样式,利用优先级高的特点,覆盖其他样式表中的样式 // 修改样式一律选择内联样式 元素.style.属性名 // 获取样式一律选择计算后的样式 getComputedStyle(元素).属性名
- 获取计算后的样式
-
- 操作内部和外部的样式表中的样式属性
- 先获取包含属性的样式表对象:CSSStyleSheet
var sheet=document.styleSheets[i]
- 获取样式表中所有规则的集合:
var rules=sheet.cssRules || sheet.rules
- 获取规则集合中包含目标属性的规则:CSSRule
var rule=rules[i]
- 获取或设置规则中的目标属性
rule.style.属性名
(此修改将影响所有使用该规则的元素,所以务必谨慎)
20.5.2、选取元素
- 通过HTML查找
- 通过CSS选取元素
- 其他选取
通过HTML选取元素
-
id 选取元素,私人定制,只能查找某一个元素
document.getElementById('id')
- 可用于在当前DOM树中根据ID选取某一个子元素
-
通过标签名选取元素
-
node.getElementsByTagName('标签名')
-
可以根据标签标签名返回所有具有指定标签名的元素集合
-
案例
var ul=document.getElementById('menuList') var list=ul.getElementsByTagName('li') console.log(typeof list) // 类数组对象 不但找子元素还找所有后代元素 console.log(list.length)
-
-
通过name属性选取元素
-
document.getElementsByName('name属性值')
-
可以返回DOM数中具有指定name属性值的所有子元素集合
-
案例
<form> <input type="checkbox" name="hobby"> <input type="checkbox" name="hobby"> <input type="checkbox" name="hobby"> </form> var list=document.getElementsByName('hobby') console.log(typeof list) console.log(list.length)
-
通过css选取器选取元素
-
按类名选取
node.getElementsByClassName('className')
- HTML5中新添了一个可以根据class名称选取元素的方法
-
按选择器选取
node.querySelector('selector')
// 返回第一个匹配的node.querySelectorAll('selector')
//返回全部匹配的类数组集合
其他选取
-
document.all
是一个类数组对象,是页面中所有元素的一个集合,具体用法- document.all[index]
- document.all[‘id’]
- document.all.id
- document.all[‘name’]
- document.all.tags[‘tagName’]
【注】通过document.all检索一个元素,已经被getElementById(),getElementsByTagName()以及getElementsByName()等方法所取代
document.documentElement
对象是整个HTML文档的根元素(即元素)的引用document.head
对象是HTML文档中元素的引用document.body
对象是HTML文档中元素的引用
20.6、常用HTMLxxxxElement类型的对象
-
HTML Image对象
- Image对象代表嵌入的图像
<img>
标签每出现一次,一个Image对象就会被创建- 也可以使用new Image()创建一个新对象
- 常用属性
- src
- height
- width
-
HTML Table对象
-
Table对象
- 代表一个HTML表格
<table>
标签表示一个Table对象- 常用属性
rows
行var table=document.getElementById('t1') console.log(table.rows.length)
- 常用方法
insertRow(index)
返回TableRow对象deleteRow(index)
-
TableRow对象(行)
- TableRow对象代表一个HTML表格行
<tr>
标签表示一个TableRow对象
- 常用属性
cells
innerHTML
rowIndex
- 常用方法
-
insertCell(index)
返回TableCell对象 -
deleteCell(index)
var table=document.getElementById('t1') console.log(table.rows[0].cells.length)//2 console.log(table.rows[1].rowIndex) //1
-
- TableRow对象代表一个HTML表格行
-
TableCell对象(单元格)
-
TableCell对象代表一个HTML表格单元格
<td>
标签表示一个TableCell对象
-
常用属性
cellIndex
innerHTML
colSpan
rowSpan
var table=document.getElementById('t1') console.log(table.rows[0].cells[0].rowSpan) console.log(table.rows[1].cells[0].innerHTML)
-
-
-
HTML Select对象
-
Select对象
- 代表HTML表单的一个下拉列表
<select>
标签即表示一个Select对象- 常用属性
options
selectedIndex
size
- 常用方法
add(option)
remove(index)
- 事件
-
onchange
<select id="sel1" onchange="showInfo()"> <option value="11">aa</option> <option value="22">bb</option> </select>
-
-
Option对象
-
代表HTML表单中下拉列表中的一个选项
-
<option>
标签表示一个Option对象 -
创建对象
var o=new Option(text,value)
-
常用属性
index
text
value
selected
function showInfo(){ var selObj=document.getElementById('sel1') var i=selObj.selectedIndex console.log(selObj.options[i].value) }
-
-
-
HTML Form对象
- Form对象代表一个HTML表单
- 在HTML文档中每出现一次,Form对象就会被创建
- 常用属性
action
method
enctype
length
表单中的元素数目elements[]
包含表单中所有元素的数组
- 常用方法
reset()
submit()
提交表单
- Form对象代表一个HTML表单
-
HTML Input对象
- 常用表单标签与HTMLDOM对象有如下对应关系
- 【html标签】 【HTML DOM对象】 Text 【常用成员】name/value blur()/focus()/select()
- 【html标签】 【HTML DOM对象】 Password 【常用成员】name/value blur()/focus()/select()
- 【html标签】 【HTML DOM对象】 Checkbox 【常用成员】name/value/checked blur()/focus()/click()
- 【html标签】 【HTML DOM对象】 Radio 【常用成员】name/value/checked blur()/focus()/click()
- 【html标签】 【HTML DOM对象】 Textarea 【常用成员】name/value blur()/focus()/click()
- 【html标签】 【HTML DOM对象】 Submit 【常用成员】name/value/checked blur()/focus()/click()
- 常用表单标签与HTMLDOM对象有如下对应关系
20.7、BOM概述
-
BOM:Browser Object Model,浏览器对象模型,用来访问和操纵浏览器窗口,使JS有能力与浏览器交互。
- 通过使用BOM,可移动窗口,更改状态栏文本,执行其他不与页面内容发生直接联系的操作。
- 没有相关标准,但被广泛支持
-
DOM:Document Object Model,文档对象模型,用来操作当前HTML文档的内容
- 定义了访问和操作HTML文档的标准方法
- 通过对DOM树的操作,实现对HTML文档数据的操作
- W3C制定了相关的标准
-
BOM模型主要包括如下的对象
- 【对象名】window 【含义】 表示浏览器中打开的窗口
- 【对象名】navigator 【含义】 包含有关浏览器的信息
- 【对象名】screen 【含义】 包含有关客户端显示屏幕的信息
- 【对象名】history 【含义】 包含用户(在浏览器窗口中)访问过的URL
- 【对象名】location 【含义】 包含有关当前URL的信息
- 【对象名】document 【含义】 包含当前浏览器加载的文档信息
- 【对象名】event 【含义】 包含当前所触发的事件对象
20.7.1、Window大对象
-
window对象表示浏览器打开的窗口
-
如果文档包含框架(frame 或 iframe框架),浏览器会为HTML文档创建一个window对象,并为每个框架创建一个额外的window对象
-
window对象是BOM的根对象,其他对象其实都是window对象的属性,window对象的属性和方法都可以省略"window.",如window.document可以简写为document,window.alert()可以简写为alert()
-
window对象常见属性
- defaultStatus 设置或返回窗口状态栏中的默认文本
- innerheight 返回窗口文档显示区的高度
- innerwidth 返回窗口文档显示区的宽度
- length 设置或返回窗口中的框架数量
- name 设置或返回窗口的名称
- opener 返回对创建此窗口的窗口的引用
- outerheight 返回窗口的外部高度
- outerwidth 返回窗口的外部宽度
- pageXOffset 设置或返回当前页面相对于窗口显示区左上角的x位置
- pageYOffset 设置或返回当前页面相对于窗口显示区左上角的y位置
- parent 返回父窗口
- self 返回对当前窗口的引用
- status 设置窗口状态栏的文本
- top 返回最顶部
-
窗口的打开和关闭
-
可使用如下方法关闭一个窗口
- close() 关闭当前窗口
- self.close() 关闭当前窗口
- xxWindow.close() 关闭指定窗口
-
可使用如下方法打开一个新窗口
var newWin=window.open(URL,name,features,replace)
- URL 可选,声明了要显示文档的URL
- name 可选,声明了新窗口的名称,这个名称可以用作标记和的属性target的值
- features 可选,声明了新窗口要显示标准浏览器的特征
- replace 可选,true - URL 替换浏览历史中的当前条目;false - URL在浏览历史创建新的条目
-
窗口的特性(兼容性差)
- channelnode=yes|no|1|0 是否使用剧院模式显示窗口
- directories=yes|no|1|0 是否添加目录按钮,默认为yes
- fullscreen=yes|no|1|0 是否使用全屏模式显示浏览器,默认是no。处于全屏模式的窗口必须同时处于剧院模式
- height=pixels 窗口文档显示区的高度。以像素计
- left=pixels 窗口的x坐标,以像素计
- location=yes|no|1|0 是否显示地址字段,默认为yes
- menubar=yes|no|1|0 是否显示菜单栏,默认为yes
- resizable=yes|no|1|0 窗口是否可调节尺寸,默认为yes
- scrollbars=yes|no|1|0 是否显示滚动条,默认为yes
- status=yes|no|1|0 是否添加状态栏,默认为yes
- titlebar=yes|no|1|0 是否显示标题栏,默认是yes
- toolbar=yes|no|1|0 是否显示浏览器的工具栏,默认为yes
- top=pixels 窗口的y坐标
-
浏览器规定每次打开一个新窗口都有一个唯一的name属性值,target使用自定义值,实际是将自定义值赋值给name属性,因为name属性值唯一,所以最多只能再打开一个窗口。
-
Target='_blank’可以开多个,是因为blank是不指定窗口名,浏览器随机分配窗口名
-
window.open(‘url’)
默认是_blank 打开多个新的窗口 -
window.open(‘url’,’_self’)
在自身打开窗口 -
window.open(‘baidu’)
打开一个新的窗口
-
-
窗口的大小和定位
-
窗口的大小
- window.innerHeight/Width 浏览器窗口的可见区域
- window.outerHeight/Width 浏览器窗口的外边框整体区域
- screen.height/width 显示器的完整分辨率
- screen.availHeight/Width 显示器去除任务栏的剩余分辨率
- 调整大小
- resizeTo(newWidth,newHeight)
- resizeBy(changeWidth,changeHeight)
-
窗口的定位(获取窗口位置的坐标)
- 左上角x坐标 window.screenLeft||window.screenX
- 左上角y坐标 window.screenTop||window.screenY
- 事件发生时,鼠标距离显示器左上角的坐标 event.screenX/Y
-
移动窗口位置
- window.moveTo(newX,newY)
- window.moveBy(changeX,changeY)
-
-
对话框(window共提供了三种对话框)
- window.alert(msg) 弹出一个警告框
- window.prompt(msg,[value]) 弹出一个输入提示框
- window.confirm(msg) 弹出一个确认框
-
定时器
- 多用于网页动态时钟,制作倒计时,跑马灯效果等
- 周期性时钟:以一定的间隔执行代码,循环往复
- 一次性时钟:在一个设定的时间间隔之后来执行代码,而不是在函数被调用后立即执行
-
周期性定时器
-
setInterval(exp,time) 周期性触发代码exp
- exp:执行语句,回调函数或字符串
- time:时间周期,单位为毫秒
- 返回已经启动的定时器对象
-
clearInterval(tID) 停止启动的定时器
- tID:启动的定时器对象
-
案例
window.setInterval('alert('hello');',3000) // 或者 window.setInterval(func,3000) function func(){ alert('hello') }
-
-
一次性定时器
-
setTimeout(exp,time) 一次性触发代码exp
- exp:执行语句,回调函数或字符串
- time:间隔时间,单位为毫秒
- 返回已经启动的定时器对象
-
clearInterval(tID) 停止启动的定时器
- tID:启动的定时器对象
-
案例
window.setTimeout('alert('hello');',3000) // 或者 window.setTimeout(func,3000) function func(){ alert('hello') }
-
20.7.2、navigator对象
-
navigator 对象包含有关浏览器的信息
-
常用于获取浏览器和操作系统信息
for(var attr in navigator){ console.log(attr + ":" + navigator[attr]) }
-
-
常用属性和方法
- appCodeName 返回浏览器的代码名 例如:‘Mozilla’
- appMinorVersion 返回浏览器的次级版本
- appName 返回浏览器的名称 例如:‘Netscape’
- appVersion 浏览器的平台和版本的信息 例如:'5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36
- browserLanguage 返回当前浏览器的语言
- cookieEnabled 返回当前浏览器中是否启用cookie的布尔值 例如:true
- cpuClass 返回浏览器系统的CPU等级
- onLine 返回指明系统是否处于脱机模式的布尔值
- platform 返回运行浏览器的操作系统平台 例如:‘Win32’
- systemLanguage 返回OS使用的默认语言
- userAgent 返回由客户机发送服务端的user-agent头部的值 例如 ‘Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36’
- userLanguage 返回OS的自然语言设置
- plugin
20.7.3、location对象
-
location 对象包含有关当前URL的信息
-
常用于获取和改变当前浏览的网址
-
案例
//获取当前显示的页面URL var url=location.href console.log(url) //更改要显示的页面URL--页面跳转 location.href="http://www,baidu.com"
-
-
常用属性和方法
- hash 设置或返回从井号(#)开始的URL(锚)
- host 设置或返回主机名和当前url的端口号
- hostname 设置或返回当前URL的主机名
- href 设置或跳转完整的 URL
- pathname 设置或返回当前URL的路径部分
- port 设置或返回当前URL的端口号
- protocol 设置或返回当前URL的协议
- search 设置或返回从问号(?)开始的URL(查询部分)
- assign() 加载新的文档
- reload() 重新加载当前文档
- replace() 用新的文档替代当前文档
20.7.4、history对象
- history对象包含用户(在浏览器窗口中)访问过的URL的历史记录
- 打开一个新网页location.href(),因为它是以push()方式放入history中,所以history可以后退和前进,用location.replace()会将history中的地址替换成新的地址,所以history不会前进和后退
- 常用属性和方法
-
length 返回浏览器历史列表中URL数量
-
back() 加载history列表中的前一个URL
-
forward() 加载history列表中下一个URL
-
go() 加载history列表中的某个具体页面
<a href="javascript:history.go(-1)">后退一次</a> history.go(1) 前进 history.go(0) 刷新 history.go(-1) 后退
-
20.7.5、screen对象
- Screen 对象包含有关客户端显示屏幕的信息
- 常用于获取屏幕的分辨率和色彩
- JavaScript将利用这些信息来优化它们的输出,以达到需要的显示要求。 例如,一个程序可以根据显示器的尺寸选择使用大图像还是使用小图像,它可以根据显示器的颜色深度选择使用16位色还是使用8位色的图形。另外,JavaScript程序还能根据有关屏幕尺寸的信息将新的浏览器窗口定位在屏幕中间
- 常用的方法和属性
- availHeight 返回显示屏幕的高度(除Windows任务栏40之外)
- availWidth 返回显示屏幕的宽度(除Windows任务栏之外)
- bufferDepth 设置或返回调色板的比特深度
- colorDepth 返回目标设备或缓冲器上的调色板的比特深度
- deviceXDPI 返回显示屏幕的每英寸水平点数
- deviceYDPI 返回显示屏幕的每英寸垂直点数
- fontSmoothingEnabled 返回用户是否在显示控制面板中启用字体平滑
- height 返回显示屏的高度
- logicalXDPI 返回显示屏幕每英寸的水平方向的常规点数
- logicalYDPI 返回显示屏幕每英寸的垂直方向的常规点数
- pixelDepth 返回显示屏幕的颜色分辨率(比特每像素)
- updateInterval 设置或返回屏幕的刷新率
- width 返回显示器屏幕的宽度
20.7.6、Event对象
DHTML对象模型
-
当用户与web页面进行某些交互时,解释器就会创建相应的event对象以描述事件信息,常见的事件有
- 用户点击页面上的某项内容
- 鼠标经过特定的元素
- 用户按下键盘上的某个按键
- 用户滚动窗口或改变窗口大小
- 页面元素加载完成或加载完成
-
1995年IE4浏览器就已经定义了自己的事件模型;而DOM模型2004年才最终确定标准的事件模型,并被其他浏览器所支持。所以事件处理需要注意兼容性的问题
事件处理函数
-
事件处理函数,指用于响应某个事件而调用的函数
-
事件处理函数的本质是对象的一个特殊属性
-
每一个元素对象都可以触发各种事件
-
每个事件均对应一个事件处理函数
-
在程序执行时,将相应的函数或语句绑定给对象的某个事件处理函数,那么一旦该对象的指定事件触发,浏览器便会自动执行该对象的事件处理函数上绑定的操作。
- 常用鼠标事件
onclick
ondbclick
onmousedown
onmouseup
onmouseover
onmouseout
- 常用键盘事件
onkeydown
onkeyup
onkeypress
- 常用状态事件
onload
onunload
onchange
onfocus
onslect
onresize
onsubmit
onreset
onerror
- 常用鼠标事件
事件定义
-
为特定事件定义监听函数有三种方式
-
直接在HTML中定义元素的事件相关属性(此语法违反了"内容与行为相分离"的原则,应尽可能少用)
<button onclick="console.log(123)">按钮</button> <body onload="initData()"></body>
-
在JavaScript中为元素的事件相关属性赋值 onclick
- onclick不能追加方法,因为后续的方法会覆盖原来的方法,不同的事件不起冲突
- 取消事件:d1.οnclick=null
btnObj.onclick=function(){ // .... } document.body.onload=initData function initData(){ //.... }
-
高级事件处理方式,一个事件可以绑定多个监听函数
addEventListener(‘事件名’,callback,true或false)
- 匿名函数方式
d1.addEventListener(‘click’,function(){})
- 具名函数方式
function fn(){} d1.addEventListener(‘click’,fn)
- 如果后续可能会取消某个事件,则在绑定事件的时候,不要绑定匿名事件,不确定是否取消的事件,也不要绑定匿名事件。取消事件:
d1.removeEventListener(‘click’,fn)
【为什么不是fn() 因为绑定的是方法名或者匿名函数,而不是执行方法】
btnObj.attachEvent('onclick',function(){}) //IE btnObj.addEventListener('click',function(){}) //DOM document.body.attachEvent('onload',initData) //IE document.body.addEventListener('load',initData) //DOM function initData(){ // ..... } // 例如 btnShoot.onclick=function(){ // alert('疼') console.log('发送普通子弹') } btnShoot.addEventListener('click',()=>{ alert('发送跟踪导弹') })
-
事件触发产生的event对象以及其属性
- 任何事件触发后将会产生一个event对象
- event对象记录事件发生时的鼠标位置,键盘按键状态和触发对象等信息
- 事件对象
e/event/$event(vue)
的常用属性-
srcElement(IE)/target(FireFox) 事件源对象
-
eventPhase 事件所处的传播阶段
-
clientX/offsetX/pageX/screenX/x 事件发y生的X坐标
-
clientY/offsetY/pageY/screenY/ 事件发生的X坐标
-
which/keyCode/charNdoe 键盘事件中按下的按键
-
button 鼠标哪个按键被按下了
-
cancelBubble 是否取消事件冒泡 e.cancalBubble=true ie浏览器写法
-
e.stopPropagation() 阻止事件向外冒泡 不阻止事件的执行
-
e.preventDefault 阻止默认事件 类似于超链接的跳转等事件
// e 为event对象 document.body.onclick=function(e){ alert(`点在body的x:${e.clientX},y:${e.clientY}位置`) // 或 alert(`点在body的x:${event.clientX},y:${event.clientY}位置`) } // 兼容性写法 a1.onclick=function(e){ e=e||window.event if(typeof e.stopPropagation === "function"){ e.stopPropagation() }else{ e.cancelBubble=true //ie浏览器写法 } alert('d1 到!') }
-
- 对于event对象,经常需要获取事件源—目标对象
- 目标对象:始终保存最初触发事件的节点对象
- this:指代触发事件的当前节点对象,随事件冒泡而改变
- IE:event.srcElement ; Firefox:event.target
<div onclick="func(event)">div text</div>
<!-- IE浏览器 -->
function func(e){
var src=e.srcElement
}
<!-- firefox浏览器 -->
function func(e){
var src=e.target
}
事件坐标
-
event对象记录事件发生时的鼠标位置
- 相对于浏览器显示器坐标位置:clientX/x clientY/y
- 相当于网页左上角坐标位置:pageX pageY IE8不支持
- 没滚动时,以上两组值相等
- 相对于屏幕坐标位置:screenX,screenY
- 相对于目标元素:offsetX,offsetY
事件周期
-
解释器创建一个event对象后,会按如下过程将其在HTML元素进行传播
- 第一阶段:事件捕获,事件对象沿DOM树向下传播
- 第二阶段:目标触发,运行事件监听函数
- 第三阶段:事件冒泡,事件沿着DOM树向上传播
-
高级事件处理方式
addEventListener(‘事件名’,callback,true或false)
第三个参数可以修改事件触发的顺序- 默认为false,触发最内层元素事件时,外层事件的事件也会被触发。顺序由内到外,这个现象叫事件冒泡
- 为true,顺序由外到内,这个现象叫做事件捕获
-
【扩展】如果事件冒泡和事件捕获混在一起是什么顺序?
-
一套结构中的同一个事件分两个过程,先执行事件捕获中的事件(第三个参数是true,顺序由外向内,再执行事件冒泡中的事件(第三个参数是false)
<div id="d1"> <div id="d2"> <!-- 点击d3,d3就是目标事件 --> <div id="d3"> </div> </div> </div> <!-- 事件默认在冒泡阶段触发 ,点击d3, 元素执行顺序 d3 d2 d1 --> <!-- 让事件在捕获阶段触发,点击d3,d1,d2,d3 --> d1.addEventListener( "click", function(){}, <!-- 默认false,只在冒泡阶段,由内向外依次触发,此处为true,所以在捕获阶段就由外向内提前触发 --> true )
-
事件委托
- 事件委托,只在父元素上绑定一次事件处理函数,所有子元素通过冒泡机制来触发父元素上公共的事件处理函数
- 普通事件绑定,this–>当前正在触发事件的目标元素
- 事件委托后,事件处理函数是在父元素上执行的,所以this不再指向实际点击的子元素目标元素,而是指向父元素div。所以,用this就无法获得父元素
-
今后只要用事件委托,必须用e.target代替this,来获取目标元素。
-
必须判断当前元素是否是想要的元素,只有当前元素是想要的元素,才继续触发后续的操作
if(target.nodeName==""){}
event.target
从当前元素的标签结构到最内层元素的标签结构event.target.tagName
获取标签名event.target.innerHTML
获取标签的内容
-
取消事件
-
通常浏览器在事件传递并处理完后可能会执行与该事件关联的默认动作。例如
- 如果表单中input type 属性是"submit",点击后会自动提交表单
-
但事件处理函数过程中发生了错误,会不希望继续执行默认行为时,都可以取消事件或阻止默认行为继续执行
-
可采用下述方法阻止默认行为
if(event.preventDefault){ event.preventDefault() //DOM }else{ event.returnValue=false //ie }
-
如果使用HTML绑定事件处理函数,可使用两个return的方式取消事件
<form onsubmit="return valiAll()"> //------------------------------------------------------- // 取消事件:2个return function vailAll(){ return true/false }
事件的内存与性能
-
页面中绑定的事件处理函数数量,直接影响页面的性能
- 解决:利用冒泡,如果多个子元素绑定了相同的事件处理函数,则仅需要在父元素统一定义一次即可
-
HTML中每个元素上绑定的事件处理函数都是一个从元素到代码位置的连接对象,移除元素,连接对象不释放
- 解决,在使用核心DOM移除或替换元素节点前,先移除元素对象上绑定的事件
-
用户可能频繁刷新页面,重复的在页面间跳来跳去。而页面卸载或刷新时,浏览器窗口中的事件连接对象不会释放,会越积越多
- 解决,在页面的onunload事件处理程序中,先接触页面中所有元素上绑定的事件,在卸载页面
二十一 、前端的优化方案
主要优化方案
-
减少请求次数和请求大小
-
代码优化,优化目标
- 利用SEO
- 利于拓展维护
- 提高性能
-
DNS及HTTP通信方式的优化
详细方案
-
尽量减少闭包的使用
-
进行js和css文件的合并,减少http请求次数,进行可能讲文件压缩,减少请求大小。
- webpack工具会自动实现这种操作
- 移动端开发过程中,代码量不多,则直接合并html css js 到一个文件中书写
-
使用字体图标和svg图标,代替传统的png格式
-
减少DOM操作:主要减少DOM的重绘和重排
-
js避免嵌套循环
-
采用图片懒加载,加快页面启动速度
- 加快页面时先不加载图片,使用一张背景图占位,等页面加载完毕后,再加载图片
-
利用浏览器和服务器的缓存技术(304缓存),把一些不经常变更的资源进行缓存,从而减少带宽,减少延迟,降低网络负荷,缩短网络请求的距离,例如缓存js和css文件等静态资源
-
尽可能用事件委托来处理绑定操作,减少DOM的频繁操作
- 事件委托:为父元素添加事件,利用冒泡机制,让父元素处理所有子元素的事件。
-
减少css表达式的使用
-
较少css标签选择器的使用
-
css 雪碧图技术
-
避免重定向(301:资源永久转移 302:暂时转移)
-
减少cookie的使用
-
页面数据获取方式: 采用异步和延迟分批加载
-
页面出现 音视频 标签,让这些资源懒加载
- 方案:只需设置
preload="none"
页面加载时就会开始加载
- 方案:只需设置
-
数据尽可能使用json格式传递,因为此格式比xml 小
-
进行js封装,尽量复用代码,减少代码冗余
-
css中设置定位后,最好使用z-index 改变层级,让盒子不在一个平面
-
css中尽量减少filter属性滤镜的使用
-
css的导入尽量减少@import 操作,此操作时同步的,而link是异步的
-
避免使用iframe
-
开启服务端的gzip压缩