简介
ECMAScript是JavaScript的标准,JavaScript实现了ECMAScript,ECMAScript规范了JavaScript。
JavaScript体系
- 语言核心
- DOM
- BOM
JavaScript书写位置
body里面写script标签,例如:
<body>
<script>
alert('hello, world');
</script>
</body>
或者
<script src=" .js" type="text/javascript" charset="utf-8"></script>
认识输出、输入语句
alert( ) 语句 弹窗
console.log( ) 语句 控制台输出
比如:
alert('haha');
alert( ) 是内置函数,函数就是功能的封装,调用函数需要使用小括号。
'haha' 是函数的参数,因为是字符串,所以要用引号包裹。
console.log('test');
console是js的内置对象,通过 打点 可以调用它内置的 log 方法。
所谓方法就是对象能够调用的函数。
var input = prompt('请输入内容');
输入语句 prompt( ) 函数,弹出框输入,输入的内容会转为字符串类型。
学会处理报错
常见两个错误:
Uncaught SyntaxError: Invalid or unexpected token
未捕获的语法错误:不合法或错误的符号。
Uncaught ReferenceError: a is not defined
未捕获的引用错误:a没有被定义。
函数名拼写错误也会引发 ReferenceError。
控制台
控制台也是一个REPL环境,可以使用它临时测试。
REPL:read 读 eval 执行 print 打印 loop 循环
变量
变量能储存计算结果 或 表示一个值;
变量不是数值本身,只是储存值的容器。
定义变量
var 关键字, 定义变量后,就可以使用了,可以用等号赋值。如果没有负值,默认值为undefined。可以同时声明多个变量,用逗号隔开。
var a;
console.log(a); //此时打印undefined
a = 7;
var b,
c,
d;
变量的合法命名
变量命名可以用多种风格,例如:
- 小驼峰命名法:首字母小写,后面每个单词头字母大写。
- C风格命名法:每个单词用下划线分隔。
变量命名符合JS标识符命名规则,变量、函数、类名、对象的属性等都要遵守。
标识符命名规则
- 只能由字母、数字、下划线、美元符号$组成,不能以数字开头。
- 不能是关键字或保留字,例如float等。
- 变量名大小写敏感,例如a和A不同。
全局变量声明提升
全局预编译
- 生成AO对象。
- 找变量声明,将变量名作为GO的属性名,值为undefined。
- 在函数体里找函数声明。
数据类型
- 基本数据类型:number、string、boolean、undefined、null
- 复杂数据类型:Array、function、object 等...
typeof 运算符
- 使用 typeof 运算符可以检测值或变量的类型,检测复杂类型可能会有出入。
- typeof 返回值是字符串类型的值。
- typeof 检测5种基本类型的的结果,除了null会返回object外,其他同名。
- typeof 可接未定义的变量不会报错。
基本数据类型
number 数字类型
所有数字不分大小、不分整数小数、不分正负,都是数字类型。
零点几的0可以省略。
科学计数法:3e7 => 3*10⁷。
不同进制的数字
二进制数值以 0b 开头:0b10 => 2;
八进制数值以 0 开头:017 => 15;
十六进制数值以 0x 开头。
一个特殊的数字类型值NaN
- not a number ,不是一个数,但它是一个数值类型的值。
- 0除以0的结果是 NaN ;运算结果不是数字的数学运算,返回值都是NaN。任何涉及NaN的运算都返回NaN。
- NaN 不自等,即 NaN == NaN; 返回 false。
- 用isNaN( ) 函数可以用来判断变量值是否为NaN,返回值是boolean布尔值,函数里的参数也会隐式转换为数字类型,所以判断不了undefined。
一个特殊的数字类型值Infinity
- Infinity 也是数值类型的的值,正数除以0得Infinite,负数除以0得-Infinity。
string 字符串类型
- 字符串要用引号包裹,双引号或者单引号均可。
- 加号可以用来拼接多个字符串(拼串儿)。
- 直接书写闭合的引号可得到空串。
字符串的length属性:表示字符串的长度。
字符串的常用方法:
方法名 | 功能 |
---|---|
charAt( ) | 得到指定位置字符 |
substring( ) | 提取子串 |
substr | 提取子串 |
slice | 提取子串 |
toUpperCase | 将字符串变为大写 |
toLowerCase | 将字符串变为小写 |
indexOf | 检索字符串 |
charAt( )方法
- 可以得到指定位置的字符,比如:
'abcdef'.charAt(0)
返回a。 - 得到指定位置字符串还可以用方括号方法,例如:
'abcdef'[3]
返回d。就是直接读取可遍历对象方括号里写索引。 - charCodeAt() 返回相应索引号的字符ASCII值
substring( )方法
sub 子,string字符串。
substring(a,b) 得到从 a 开始到 b 结束(不包括 b 处)的子串。
如果省略第二个参数,返回的子串会一直到字符串的结尾。
参数会自动从小排到大,比如 (5,3) 会自动变为 (3,5)。(慎用,可读性差)
substr( )方法
substr(a,b) 得到从a开始的长度为b的子串。
a可以是负数,表示倒数位置。b不能是负数,否则会返回空串。
slice( )方法
slice(a,b) 得到从a开始到b结束(不包括b处)的子串。
a 可以是负数,表示倒数,倒数的下标从-1开始。
参数 a 必须小于参数 b。
toUpperCase( )转为大写,toLowerCase( )转为小写
indexOf( )方法
返回某个指定的字符串值再字符串中首次出现的位置(索引值)。
如果要检索的字符串没有出现,则返回-1。
传入的参数是字符串,例如'abcdef'.indexOf('de')
返回3。
此方法还可以传入第二个参数,代表开始检索、搜索的索引位置。
boolean 布尔类型
小知识:布尔(Boole.George)英国19世纪数学家及逻辑学家。
布尔类型 值只有两个:true 和 false ,分别表示真假。
undefined 类型
undefinded 又是值,又是一种类型。
undefined 是全局对象上的一个属性,window.undefined
不可写 writable: false,比如window.undefined = 1
无效。
不可配置 configurable: false,比如delete window.undefined
无效。
不可枚举 enumerable: flase ,比如
for(var k in window){
if(k === undefined){
console.log(k);
}
}
不打印内容
不可重新定义 cannot redefine property: undefined;
系统会给一个未赋值的变量自动赋值为undefined,类型也是undefined 。
函数内部不写返回值的时候,返回的也是一个undefined。
在函数内部可以把undefined当作一个变量名来用,但尽量别用。例如:
function test(){
var undefined = 1;
console.log(undefined);
}
test();
会打印1
void(0) 会返回undefined
null
null是基本数据类型的值,typeof返回类型是object。
代表空、空对象。
可用于对象销毁,数组销毁,事件清空。
显式类型转换
其他类型 转 数字
- 使用 Number( ) 函数
字符串 → 数字:纯数字字符串能变为数字,不是纯数字的转为NaN,空串转为0.
布尔值 → 数字:true转为1,false转为0.
undefined → 数字:undefined转为NaN。
null → 数字:null转为0。 - 使用 parseInt( ) 函数。
用在字符串上时:将自动截断第一个非数字字符之后的所有字符,点也算,不会四舍五入。 - 使用 parseFloat( ) 函数。
将字符串转为浮点数,只会识别到第一个点。
其他类型 转 字符串
- 使用 String( ) 函数,变为长得相同的字符串。
- 使用 toString( ) 方法,数字使用 toString 要加括号,例如
(7).toString();
其他类型 转 布尔值
使用 Boolean( ) 函数。
数字→布尔:除了0和NaN是false,其他true。
字符串→布尔:空窗false,其他true。
undefined和null:转成布尔值都是false。
表达式 和 运算符 和 隐式类型转换
表达式
表达式,由运算元和运算符构成,并产生运算结果的语法结构。
表达式的种类有:算术、关系、逻辑、赋值、综合。
算术运算符
符号 | 功能 |
---|---|
+ | 加法、正数、连字符 |
- | 减法、负数 |
* | 乘法 |
/ | 除法 |
% | 取余 |
取余,也叫求模运算,用百分号 % 表示,小数模大数得小数。
乘除余运算优先级高于加减运算。
算术运算符的隐式类型转换
如果参与数学运算的值不是数值型,会自动转换为数值型然后运算,加号例外因为有拼串功能。其本质是内部调用 Number( ) 函数,例如:
true + true;
//Number(true) + Number(true) 然后返回一个2
+'1' //会返回一个数字1
3*'2天' //会返回NaN
算术运算符中浮点运算问题IEEE754
在JavaScript中,有些浮点运算不是很精准,因为采用的是IEEE754二进制浮点数算术标准,会产生“精度丢失”问题。
解决方法:可以用数字的 toFixed() 方法保留指定的小数位数,类型会变成字符串。例如:
(3.141592654).toFixed(2)
其他运算的Math方法
-
幂和开根:
幂和开根没有运算符,需调用Math对象的方法实现。
幂:Math.pow(底数,次方数);
开方:Math.sqrt(开根数);
-
向上取整和向下取整:(相对数轴而言)
向上取整:Math.ceil(取整的数);
向下取整:Math.floor(取整的数);
-
随机数:
Math.random( );
得到0到1的随机小数。(大于等于0小于1)
想得到[a,b]区间的整数,公式为:
parseInt(Math.random()*(b-a+1))+a;
封装成函数就是
function getRandom(min,max){
return parseInt(Math.random()*(max-min+1))+min;
}
-
绝对值:
Math.abs();
关系运算符
符号 | 意义 |
---|---|
> | 大于 |
< | 小于 |
>= | 大于或等于 |
<= | 小于或等于 |
== | 等于 |
!= | 不等于 |
=== | 全等于 |
!== | 不全等于 |
进行关系运算符运算候的结果返回值是boolean布尔值。
除了全等和不全等以外,其他关系运算符都会隐式转换两边值为数字类型。
例如:
1 == '1'; //返回true
undefined == null; //这是个例外,undefined转数字是NaN的,但返回true
undefined === null; //返回false
NaN == NaN; //返回false,NaN不自等,所以要用isNaN( ) 函数判断
没有数学书写上的连比3<=16<=15
,要连比只能用逻辑运算符
逻辑运算符
符号 | 意义 |
---|---|
! | 非 |
&& | 与 |
|| | 或 |
! 非
- 也可以称为“置反运算”。
- 是一个“单目操作符”,只需要一个操作数。
- 置反运算的结果一定是布尔值,要置放的值会先隐式转换为布尔值。所以用双非
!!
可以用来隐式转换一个值为布尔值。
&& 与运算
找假返回假,如果没有假则返回最后一个真。(都真就真)
|| 或运算
找真返回真,如果没有真则返回最后一个假。(有真就真)
逻辑运算符优先级:非>与>或
赋值表达式
赋值运算符
符号 | 意义 |
---|---|
= | 赋值 |
+= | 快速赋值 |
-= | 快速赋值 |
*= | 快速赋值 |
/= | 快速赋值 |
%= | 快速赋值 |
++ | 自增运算 |
-- | 自减运算 |
赋值运算也会产生值,例如:
var a;
console.log(a=7);
//a=7会返回7,所以会打印7而不是a=7。
所以可以连续使用赋值运算(实际开发禁止使用),例如:
var a,b,c;
a=b=c=7;
//先c=7返回7,再b=c=77,再a=b=c=77
快速赋值:+= 、-= 、*= 、 /= 、%=
在原数值基础上进一步运算。
自增自减:++、--
自身基础上加一或减一。
a++和++a区别:a++先用再加,++a先加再用。例如:
var n1=1,n2=2;
var n3 = (n1++)+n2
//此时n3等于3
综合表达式
综合运算顺序:非运算>数学运算>关系运算>逻辑运算 (非数关逻)
流程控制语句
条件语句:if、if elseif else、switch、三元运算符。
循环语句:for、while、break和continue、do while。
条件语句
if语句
if(条件){
语句块1,当条件为真时执行
}else{
语句块2,当条件为假时执行
}
if语句执行流程图
graph TD
A[程序执行] -->| | B(执行)
B --> C{测试条件}
C -->|条件1| D[语句块1]
C -->|条件2| E[语句块2]
- if语句中的else可以省略
- 如果if语句体中只有一行语句,可以省略大括号和换行,且结尾加分号
else if 否则如果
else if( ) 条件分支“暗含”不符合之前的所有条件。
if( 测试表达式1){
当测试表达式1为true时执行
}else if(测试表达式2){
当测试表达式2为true时执行
}else{
所有表达式为false时执行
}
if 语句算法题
第一题: 判断水仙花数。
如何拆位?
- 数学方法:百位是原数值除以100取整。十位是原数字除以10取整,再与10求模。个位是原数字与10求模
- 字符串方法:直接将原数字变为字符串,然后使用charAt( )方法。
第二题: 游乐园门票计算。
年龄大于等于10 | 年龄小于10岁 | |
---|---|---|
平日 | 300 | 140 |
周末 | 500 | 210 |
switch 语句
用途:当一个变量被分类讨论的情形。
- switch( ) 括号内一般是一个变量名,这个变量被分类讨论。
- case表示情况,后面直接跟一个值,程序会将这个值依次与switch括号内的变量匹配,如果全等,则执行case后的语句,多条case可以共用一个语句体。
- default 表示默认情况。
- break 跳出switch语句。
三元运算符
条件表达式 ? 表达式1 : 表达式2
当条件为真执行1,为假执行2。
循环语句
for 循环语句
for的小括号有三个表达式:
表达式1:for循环内可用的变量,循环变量一般定为i,意为iterator迭代器。
表达式2:只要这条返回true,就会继续执行后面的语句块。
表达式3:一般用来更新循环变量。
for循环执行机理:
for(var i=1①; i<=10②; i++④){
console.log(i)③;
}
//①→②→③→④
也就是说,for循环小括号内的第三条语句,是先执行中括号内的语句块,再执行的。
for循环算法题
- 累加器
- 1到100哪个数除3余1,除4余2,除5余3.(穷举法)
while 循环语句
是一种“不定范围”循环。先不指定循环开始和结束范围。只要满足测试条件,就一直执行循环体。
while(测试条件){
循环执行的语句块;
}
循环注意: 当循环条件终止值与更新的循环体内的值不是同一个时,当要求的值是循环值时,注意“出一错误”
循环语句中的break和continue
执行 break; 会立即终止循环。
执行 continue; 会跳过循环中的一个迭代,跳过其中一次循环,这一次循环体内的continue后面的其他语句将跳过执行一次。
do while 循环
先执行一次循环体,再判断测试条件是否满足。
do{
循环执行的语句块(会先执行一次);
}while(循环执行条件)
while循环算法题 猜数字
流程语句经典算法题
可以先用伪代码写下解决思路,再翻译成js语法。
累加器与累乘器
- 计算 3/2 + 4/3 + 5/4 + 6/5 + ...... + (n+1)/n 的结果
- 由用户输入数字n,计算n的阶乘。
for(var i=n; i>=1; i--){result*=i;}
result要从1开始- 圆周率可以由莱布尼茨级数公式计算,用户输入n,计算圆周率Π。
Π/2= 1 + 1/3 + 1*2/3*5 + 1*2*3/3*5*7 + 1*2*...n/3*5*...(2n-1)
穷举法
- 100以内,能整除3也能整除5的数。
- 输入一个数,在控制台显示这个数的全部约数。
- 寻找全部水仙花数。(每个数位的立方和等于它本身)。
综合算法题
- 寻找100以内的质数。(用到给外层for循环套label,然后用continue label)
- 鸡兔同笼,35头,94足。
数组
Array ,存储一组值。
如何定义数组
- 使用方括号:
var arr = ['A','B','C'];
- 使用 new Array 构造函数:
var arr = new Array('A','B','C');
当new Array函数只传入一个数字时,则定义一个长度为n的数组。
数组的每一项都有下标,下标从0开始。
使用 数组变量 接方括号中写下标 的形式,可访问数组项,例如:a[0];
当下标越界,会返回undefined而不会报错。
数组的length属性表示长度,数组的最后一项的下标时length-1
数组是可读可写的,如果更改的项下标越界,则会创造这项。
数组的遍历:
for (var i=0;i<arr.length; i++){
arr[i];
}
数组的不停写入,可用while循环(数组自增):
var arr = [];
while( ){
arr[arr.length]=
}
判断是不是数组
数组用typeof 检测结果是object,可用 Array.isArray( );
方法检测。
数组相关遍历算法题
- 求数组中每一项的总和、平均数。
- 求数组项的最大值和最小值。
数组的头尾操作方法
方法 | 功能 |
---|---|
push( ) | 在尾部插入新项 |
pop( ) | 在尾部删除 |
unshift( ) | 在头部插入 |
shift( ) | 在头部删除 |
push( ) 方法:推入新项,如果要推入多项,用逗号隔开。
pop( ) 方法:删除数组中的最后一项,该方法会返回被删除的项,可用一个变量接收。
unshift与shift特性与push、pop相似。以上方法均会改变原数组
数组的其他方法
会改变数组的:
方法(会改变原数组) | 功能 |
---|---|
reverse | 逆转排序 |
splice | 切割 |
sort | 改序 |
reverse( ) 方法
用于将数组中的全部项顺序置反。
splice( ) 方法
用于替换数组中的指定项。用法:
arr.splice(从下标为n的项开始 , 替换多少项 , 替换的新项可多项);
当第二个参数为0时,就会变为插入功能。
当第三个参数不写时,就会变为删除功能。
该方法会返回被删除的项组成的数组。
sort( ) 方法
arr.sort( );
sort开放了接口,可传入一个函数 arr.sort(function(){ });
- 必须写两形参!
arr.sort(function(a,b){ });
- 看返回值
1)返回值为正时,后面的数放前边。
2)返回值为负时,前面的数放前边。
3)返回值为0时,不做操作。
自己总结就是,正数交换位置,负数不换(正交负不换),所以:
arr.sort(function(a,b){return a-b;}) //升序
arr.sort(function(a,b){return b-a;}) //降序
arr.sort(function(a,b){return Math.random()-0.5;}) //乱序
不改变数组的方法:
方法(不改变原数组) | 作用 |
---|---|
slice( ) | 截取数组 |
concat( ) | 连接多个数组 |
数组的slice方法:
不会改变原数组,用于得到子数组。
slice(a,b) 截取从下标为a开始,到下标为b(不包括b)结束。
如果不传第二个参数。就会截取从a开始的后面所有项。参数允许为负数。
数组的concat方法: 合并连接多个数组,不改变原数组。
数组与字符串间转换的方法,以及更多通性
数组转字符串: join( )方法,将数组改变为字符串类型并以参数作为连接符输出,不传参数默认逗号。传递的参数要用引号括起来。
字符串转数组: split( )方法,以什么字符拆分字符串,参数为空时将拆为一个字符串。
更多相关性:
- 可以使用方括号写下标的形式访问某个字符,等价于charAt( )方法。
- 字符串的一些算法问题可以改为数组解决。
indexOf( ) 和 includes( )
indexOf( ) 方法:搜索数组中的元素并返回下标数,如果不存在返回-1。
includes( ) 方法:判断数组是否包含一个元素,返回布尔值。
数组经典算法题
冒泡排序:
for(var i=1; i<=arr.length-1;i++){
for(var j=0; j<arr.length-i; j++){
// j多功能担任遍历数组的下标
if (arr[j]>arr[j+1]) {
//因为j是访问不到最后一位的,j<arr.length-i,j最大只能到每一趟的倒数第二位。但是因为在if里面比较的是[j+1]位,所以第一轮刚好能访问到最后一位
var temp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = temp;
}
}
数组去重:
思路:准备一个空数组,遍历原数组,如果遍历到的项新数组里没有(用indexOf判断或includes判断,后者是ES6),则push进去。
function unique(arr){
var newArr = [];
for(var i=0;i<arr.length;i++){
if(newArr.indexOf(arr[i])===-1){
newArr.push(arr[i]);
}
}
return newArr;
}
随机样本:
思路:准备一个空数组,遍历原数组,随机选择一项,推入新数组,并删除这项。
二维数组
- 数组里的项是数组时,就形成了二维数组
- 二维数组可以看作是矩阵。书写上最好写成矩阵。
- 遍历二维数组需要2层循环,n维数组则需要n层。
数组高阶函数map() reduce() filter()
map方法接收一个函数作为参数,这个函数会接收三个参数:参数一是这个数组里的每一项,参数二是每一项的下标索引值,参数三是调用map方法的这个数组本身,return的是指定的值进一个新的数组。最后map方法返回这个新的数组
var arr = [{name:'ming',age:18},{name:'he77',age:20},{name:'qi',age:24}]
var newArr = arr.map(function(item,index,arrSelf){
console.log(item,index,arrSelf)
return item.age
})
console.log(newArr) //[18,20,24]
reduce方法接收一个函数作为参数以及一个“开始计算的初始值”,这个函数会接收两个参数:参数一是上一次return的结果,参数二是这一次参与计算的数据。不断的计算最后return一个值。最后reduce方法返回这个值
var arr = [{name:'ming',age:18},{name:'he77',age:20},{name:'qi',age:24}]
var newValue = arr.reduce(function(previous,joining){
console.log(previous,joining)
return previous+joining.age
},0)
console.log(newValue) //62
filter方法接收一个函数作为参数,这个函数的参数会接受一个参数,参数一是这个数组的每一项,return符合判断为true的数据进一个新数组,最后filter方法返回这个新数组。
var arr = [{name:'ming',age:18},{name:'he77',age:20},{name:'qi',age:24}]
var newArr = arr.filter((item)=>{return item.name.includes('i')})
console.log(newArr) //[{name:'ming',age:18},{name:'qi',age:24}]
引用类型和基本类型
基本类型:number、string、boolean、undefined、null
引用类型:array、object、function、regexp...
相等判断:
- 基本类型进行相等判断,会比较 值 是否相等。
- 引用类型进行相等判断,会比较 址 是否相等。
所以浅克隆中如果克隆到数组,并不完全克隆,这个时候就需要深克隆。
函数
函数就是语句的封装,方便复用,具有“一次定义,多次调用”的优点。
函数的定义
使用function关键字,function是功能的意思。例如:
function fun( ){
}
function表示定义函数。
fun 是函数名,命名规则见js标识符命名规则。
圆括号()是形参列表,即使没有形参,也必须书写圆括号。
大括号{ }是函数执行的语句体
第二种定义函数方法是使用函数表达式:
var fun = function(){ }
调用函数
在函数名字后面书写圆括号即可,也可以称为函数执行符。。
函数声明提升
只有函数声明能提升,函数表达式不提升。提升相关详情见GO、AO预编译。
例题
fun();
var fun = function(){alert('a');}
function fun(){alert('b');}
fun();
//执行这段程序,会先弹出b,然后弹出a。
//所以函数只是声明提升,预编译后不再参与变量赋值。
函数的参数
函数声明时圆括号内定义形参(形式参数)
调用函数时传入实参(实际参数)
- 形参数量 = 实参数量 时:一一对应。
- 形参数量 < 实参数量 时:多出的实参不传入函数,但arguments仍可读到。
- 形参数量 > 实参数量 时:多出的形参值为undefined。
arguments
函数内的 arguments 表示它接收到的实参列表,他是一个类数组对象。
类数组对象:
是对象,可以当数组用。
和数组类似可以用方括号访问对象的属性值,但不能调用数组的方法,可以自己写入模拟数组方法。构成要素:
①属性要为索引(数字)属性
②必须要有length属性
return 函数的返回值
使用return关键字。
调用一个有返回值的函数,可以当一个普通值来用。
遇见return语句会立即退出函数。(结合if语句,往往不需要写else分支了)
return后不接内容则函数返回undefined,接内容则函数返回内容。
函数算法题
寻找喇叭花数(喇叭花数:三位数,每一位数字阶乘的和等于他本身)
函数的递归
函数内部语句调用这个函数自身,从而发起函数新一轮迭代,在新的迭代中又执行调用函数自身的语句,一直迭代下去,知道函数执行到某一次时不在迭代,然后一层一层返回,则形成函数递归。
递归的要素
- 边界条件:确定递归何时终止,也称递归出口。
①要么找到当参数等于某个值时候函数有结果
②要么函数执行次数有限,当循环到某一次的时候不再调用函数自身。 - 递归模式:大问题分小问题,递归体。
递归常见算法题
斐波那契数列:每项等于前两项的和。
function fibonacci(n){
if(n==0) return 0;
else if(n==1) return 1;
return fibonacci(n-1)+fibonacci(n-2);
}
实现深度克隆数组:
function arrClone(arr){
var newArr=[];
for(var i=0;i<arr.length;i++){
if(!Array.isArray(arr[i])) newArr[newArr.length]=arr[i];
else newArr[newArr.length] = arrClone(arr[i]);
}
return newArr;
}
变量作用域
JavaScript是函数及作用域编程语言:变量只在其定义时所在的 function 内部有效,function 内的变量成为 function 的局部变量。
不见变量定义在任何函数里面,这个变量就是全局变量,或者 function 内不声明变量直接赋值也是全局变量。
遮蔽效应: 如果函数中也定义了与全局变量同名的变量,则函数内的变量会将全局变量“遮蔽”。
js运行三部曲
- 语法解析
- 预编译
- 解释执行
会发生以下几种情况:
函数声明整体提升
变量声明提升
imply global 暗示全局变量,window就是全局
预编译发生在函数执行前一刻。
全局预编译创建GO对象。
局部预编译先创建AO对象,再逐步执行(部分执行)。
全局预编译三部曲
- 生成GO对象。
- 找变量声明,将变量名作为GO属性名,值为undefined。
- 在函数体里面找函数声明。
局部预编译四部曲
- 生成AO对象(activation object)
AO{ } - 找形参和变量声明,将变量和形参名作为AO属性名,值为undefined。
AO{
a: undefined;
b: undefined
}
- 将实参值与形参统一。
AO{
a: 1;
b: undefined
}
- 再函数体里找函数声明。
AO{
a: function a(){ };
b: undefined;
d: function d(){ }
}
闭包
JavaScript的函数会产生闭包(closure)。闭包是函数本身和该函数声明时所处的环境状态的组合。
函数能够“记忆住”其定义时所处的环境,即使函数不在其定义的环境被调用,也能访问定义时所处环境的变量。
在Javascript中,每次创建函数时都会创建闭包。
但是,闭包特性往往需要将函数“换一个地方”执行,才能被观察出来。
闭包非常实用,它允许我们将数据与操作该数据的函数关联起来。
用途:
- 记忆性:当闭包产生时,函数所处环境的状态会始终保持在内存中,不会在外层函数调用后自动清除。
- 模拟私有变量。
坏处:
滥用闭包会造成内存泄漏,引发性能问题。
立即执行函数 IIFE
IIFE全称:Immediately Invoked Function Expression,立即调用函数表达式,一被定义就立即被调用。
IIFE形成方法
只有表达式才能被执行符号(圆括号)执行。
表达式:由运算元和运算符构成,并产生运算结果的语句结构
能被执行符号执行的表达式会忽略这个函数的名字,所以立即执行函数写函数名会被忽略。
IIFE作用
- 为变量赋值,例如一个值需要经过一段流程控制语句后才产生,那么就可以把这堆流程控制语句封装成函数并立即执行然后返回值。
- 将全局变量变为局部变量(让IIFE里面的函数立即形成一个新的闭包)。
DOM
DOM是 Document Object Model 文档对下个模型,是Javascript操作HTML文档的接口。
DOM 节点树
节点的四个属性
属性名 | 作用 |
---|---|
nodeName | 元素的标签名,以大写形式表示,只读 |
nodeValue | Text节点或Comment节点的文本内容,可读可写 |
nodeType | 该节点类型,见下表 |
attributes | Element节点的属性集合 |
nodeType 常用属性值
值 | 属性 |
---|---|
1 | 元素节点,例如<p>和<div> |
3 | 文字节点 |
8 | 注释节点 |
9 | document节点 |
10 | DTD节点 |
延迟运行
- 在测试DOM代码时,通常JS代码要写在HTML节点后面,否则JS无法找到相应HTML节点。
- 可以使用
window.onload = function(){ }
或者document.addEventListener( 'DOMContentloaded',function(){ },false);
使页面加载完毕后,在执行指定的代码,推荐第二种
访问元素节点
寻找/遍历/访问主要依靠document对象。
几乎所有DOM的功能都封装在 document对象中。
document 对象也表示整个HTML文档,是DOM节点树的根
常用方法有:
方法 | 功能 |
---|---|
document.getElementById( ) | 通过ID得到元素 |
document.getElementsByTagName( ) | 通过标签名得到元素数组 |
document.getElementsByClassName( ) | 通过类名得到元素数组 |
document.getElementsByName( ) | 只有部分标签name可生效 |
document.querySelector( ) | 通过选择器得到元素 |
document.querySelectorAll( ) | 通过选择器得到元素数组 |
document.getElementById( )
当html元素有id属性的时候,可以用id获取该元素节点
注意事项:
- 如果页面上有相同id的元素,则只能得到第一个。
- 不管元素藏得多深,都能通过id找到
getElementsByTagName( )
通过标签名得到节点数组。
注意事项:
- 数组方便遍历,从而可以批量操控元素节点。
- 即使页面上只有一个指定标签名的节点,也将得到长度为1的数组。
- 任何一个节点元素可以调用getElementsByTagName方法,比如
box1.getElementsByTagName( 'p');
, 从而得到一个元素内部的子元素节点。
getElementsByClassName( )
通过类名得到节点数组。
注意事项:
- 从IE9开始兼容。
- 其他同上Tag特性
querySelector( )
通过选择器得到元素。例如:
var the_p = document.querySelector('#box1 .p')
注意事项:
- querySelector( )方法只能得到页面上一个元素,如果有多个元素符合条件,只能得到第一个元素。
- querySelector( ) 方法从IE8开始兼容,但从IE9开始支持CSS3的选择器,比如 :nth-child() 、:[src^='seven']等选择器形式支持良好。(伪类不行)
querySelectorAll( )
通过选择器得到元素数组。计师页面上只有一个符合选择器的节点,也将得到长度为1的数组
querySelector和querySelectorAll注意事项: 都不是实时的,例如节点是后期加上的会获取不到。
节点关系,节点之间的关系
关系 | 考虑所有节点 | 只考虑元素节点 |
---|---|---|
子节点 | childNodes | children |
父节点 | parentNode | parentElement |
第一个子节点 | firstChild | firstElementChild |
最后一个子节点 | lastChild | lastElementChild |
前一个兄弟节点 | previousSibling | previousElementSibling |
后一个兄弟节点 | nextSibling | nextElementSibling |
- DOM中,文本节点也属于节点,在使用节点关系时一定要注意。
- 在W3C标准规范中,空白文本节点也属于节点,IE8之前不把空文本当作节点。
- 可以用
父节点.hasChildNodes( );
判断有没有子节点
封装节点关系函数
//兼容IE8以下的获取所有子元素节点函数,类似children属性
function getChildren(node){
var children = [];
for(var i=0; i<node.childNodes.length; i++){
if(node.childNodes[i].nodeType == 1){
children.push(node.childNodes[i]);
}
}
return children;
}
//兼容IE8以下获取前一个兄弟元素节点
function getPreviousElementSibling(node){
while(node.previousSibling != null){
if (node.previousSibling.nodeType == 1) {
return node.previousSibling;
}
node = node.previousSibling;
}
}
//兼容IE8以下获取后一个兄弟元素节点
function getNextElementSibling(node){
while(node.nextSibling != null){
if (node.nextSibling.nodeType == 1) {
return node.nextSibling;
}
node=node.nextSibling;
}
}
//获取所有兄弟元素节点
function getAllElementSibling(node){
var o =node;
var preES = [];
var nexES = [];
while(o.previousSibling != null){
if (o.previousSibling.nodeType == 1) {
preES.unshift(o.previousSibling);
}
o = o.previousSibling;
}
o = node;
while(o.nextSibling != null){
if (o.nextSibling.nodeType == 1) {
nexES.push(o.nextSibling);
}
o = o.nextSibling;
}
return preES.concat(nexES);
}
节点操作
改变元素节点中的内容可以使用两个相关属性:
- innerHTML :能以HTML语法设置节点中的内容结构。
- innerText : 只能以纯文本的形式设置节点中的内容。写进去的标签也会变成字符串。
改变元素节点的CSS样式
用 元素.style.要修改的属性 = ***;的形式,例如:
div1.style.backgroundColor = 'blue';
div1.style.backgroundImage = 'url(images/1.jpg)';
div1.style.fontSize = '32px';
注意:
- js语句不能用横杠-书写标识符,所以用小驼峰形式书写css属性名。
- css属性值要设置成完整形式。
- 注意写单位,注意写单位,注意写单位。
- 是通过CSS行内属性设置上的。
- 写入值必须是字符串,必须是字符串,必须是字符串。
脚本化CSS
以上操作称为脚本化CSS,读写行间样式,如果是js保留字,前面应该加css,例如:cssFloat
如何查询计算样式,可以使用:
window.getComputedStyle(元素名,null);
//第一个值传的是需要获取计算样式的元素,第二个值传入是否获取的是伪元素。
//IE8以下用ELE.currentStyle,或者封装计算样式方法
function getStyle(obj, name) {
if(window.getComputedStyle) {
//正常浏览器的方式,具有getComputedStyle()方法
return getComputedStyle(obj, null)[name];
} else {
//IE8的方式,没有getComputedStyle()方法
return obj.currentStyle[name];
}
}
改变元素的css还可以用改变类名的写法
使用 DOMCUMENT.className += 需要样式的类型
或者使用dom的 classList 属性,classList是一个类数组,可以通过下标方式取得元素的class组,该属性有多种方法
方法名 | 功能 |
---|---|
DOMCUMENT.classList.add(class1,class2) | 在元素中添加一个或多个类名。如果指定的类名已存在,则不会添加 |
DOMCUMENT.classList.remove(class1, class2, ...) | 移除元素中一个或多个类名。注意: 移除不存在的类名,不会报错。 |
DOMCUMENT.classList.toggle(class,布尔值) | 在元素中切换类名。第一个参数为要在元素中移除的类名,并返回 false。 如果该类名不存在则会在元素中添加类名,并返回 true。 第二个是可选参数,是个布尔值用于设置元素是否强制添加或移除类,不管该类名是否存在。 |
DOMCUMENT.classList.item(index) | 返回元素中索引值对应的类名。索引值从 0 开始。如果索引值在区间范围外则返回 null |
DOMCUMENT.classList.contains(class) | 返回布尔值,判断指定的类名是否存在。可能值:true - 元素包已经包含了该类名false - 元素中不存在该类名 |
改变元素节点的引用地址
比如图片的引用地址和a标签的跳转地址用 元素.src 和 元素.href
更改元素节点的HTML属性 setAttribute( ) 和 getAttribute( )
不符合W3C标准的属性,例如自定义属性名的属性,要使用 setAttribute( ) 和 getAttribute( ) 来设置,读取。格式是:
元素.setAttribute(属性名,属性值);
元素.getAttribute(属性名); //返回属性名对应的值
例如:
div1.setAttribute('data-n',10);
div1.getAttribute('data-n');
setAttribute() 修改元素的类名用的是 class 而不是 className
removeAttribute() 删除元素节点的HTML属性
removeAttribute('属性名')
更改元素节点的自定义属性data
JavaScript的dataset属性。
元素节点.dataset.data后面的字符 = 'data-*的值';
如果后面的字符还是多个减号连接,那么data后面的字符用小驼峰式命名法。
节点的创建、插入、移动、移除、克隆
方法名 | 功能 |
---|---|
document.createElement( ) | 创建一个指定tag name的HTML元素 |
父节点.appendChild(孤儿节点) | 将新创节点挂载成最后一个子节点 |
父节点.insertBefore(孤儿节点,标杆节点 ) | 将新创节点挂载成标杆节点前一个子节点 |
父节点.removeChild( ) | 从DOM父节点中删除一个子节点 |
子节点.remove( ) | 从DOM中删除一个子节点 |
老节点.cloneNode( ) | 克隆出一个新的孤儿节点 |
节点.replaceChild(新节点,旧节点) | 替换节点 |
节点创建
document.createElement( )
方法用于创建一个指定标签名的HTML元素,例如:
var div1 = document.createElement('div');
此时新创建的节点是“孤儿节点”,只生成在内存中,需要挂在到DOM树上才能看见他,也就是要“孤儿上树”,此时就需要使用孤儿上树的两个插入方法。
其他节点创建方法
方法名 | 作用 |
---|---|
document.createTextNode( ) | 创建一个文本节点 |
document.createComment( ) | 创建一个注释节点 |
document.createDocumentFragment( ) | |
创建一个虚拟的节点对象 |
节点插入
孤儿就是孤儿,要挂数只能找父亲,然后让一个野生父亲使用 appendChild( )
和 insertBefore( )
方法即可。
appendChild( )
任何已经在DOM树上的节点,都可以调用appendChild( ) 方法,它可以将孤儿节点挂载到它的内部,使孤儿节点成为最后一个子节点。
父节点.appendChild(孤儿节点);
insertBefore( )
任何已经在DOM树上的节点,都可以调用insertBefore( ) 方法,它可以将孤儿节点挂载到它的内部,成为它的“标杆子节点”之前的节点。
父节点.insertBefore(孤儿节点,标杆节点);
创建和插入练习:九九乘法表
<body>
<table></table>
<script type="text/javascript">
var table = document.getElementsByTagName('table')[0];
for (var i = 1;i<=9;i++) {
var tbr = document.createElement('tr');
table.appendChild(tbr);
for (var j=1;j<=i;j++) {
var trd = document.createElement('td');
trd.innerText = i+'*'+j+'='+i*j;
tbr.appendChild(trd);
}
}
</script>
</body>
元素的移动
appendChild( ) 和 insertBefore( ) 还可以用作元素的移动,因为 一个节点不能同时位于DOM树的两个位置
如果将已经挂载到DOM树上的节点成为appendChild( ) 和insertBefore( ) 的参数,这个节点将会被移动,使用方法:
新父节点.appendChild(已经有父亲的节点);
新父节点.insertBefore(已经有父亲的节点,标杆子节点);
删除节点
父节点.removeChild(要删除的子节点);
子节点.remove(要删除的节点);
removeChild( ) 方法从DOM中删除一个子节点。
意味着节点不能主动删除自己,必须由父节点删除它。
克隆节点
var 孤儿节点 = 老节点.cloneNode( );
var 孤儿节点 = 老节点.cloneNode(true);
cloneNode( ) 方法可以克隆节点,克隆出的节点是孤儿节点。
参数可以传入一个布尔值,默认false,false意味着只克隆元素本身不管子节点。
如果是true则采用深度克隆,该节点的所有后代节点也都会被克隆。
替换节点
替换父节点中的子节点。用法:
父节点.replaceChild(新的子节点,旧的子节点)
此方法如果用在已经上树的节点上,会拉下来重新上树,还是那个道理,一个dom节点不能位于树上两个位置
事件
事件是实现交互的核心。用户与界面做出的交互动作都可以称为事件。例如“当用户点击元素时,当鼠标移动到元素上时,当文本框内容被改变时,当网页已加载完毕时。”
事件的三种模型
DOM0级:标签上直接绑定的onXXX事件和js用onXXX绑定的事件
IE:IE特有模型,解决了某三个DOM0级的问题。
DOM2事件模型:addEventListener方式添加事件,可以监听事件冒泡和捕获阶段阶段
常见事件
常见的鼠标事件
事件名 | 事件描述 |
---|---|
click | 鼠标单击 |
dblclick | 鼠标双击 |
mousedown | 鼠标按键按下 |
mouseup | 鼠标按键松开 |
mousemove | 鼠标按键移动 |
mouseenter、mouseover | 鼠标进入 |
mouseleave、mouseout | 鼠标离开 |
contextmenu | 右击鼠标时触发并打开上下文菜单 |
mousewheel | 鼠标滚轮 |
只有mouseup和mousedown区分鼠标键,button值0、1、2
如何解决mousedown和click冲突(例如a标签拖拽后会出发click事件):
利用时间戳和开关关闭click事件
mouseenter不冒泡,mouseover冒泡
常见的键盘事件
事件名 | 事件描述 |
---|---|
keydown | 键盘的键被按下(可响应所有按键,先于press执行) |
keypress | 键盘的键被按下,只能识别字符按键,例如方向键和功能键无法识别 |
keyup | 当某个按键被松开 |
keypress返回的ASCII码(例charCode)可转字符
常见的表单事件
事件名 | 事件描述 |
---|---|
input | 当用户改变域的内容 |
change | 当用户改变了域的内容后 |
focus | 当某元素获得焦点(比如tab键跳转到或鼠标点击) |
blur | 当某元素失去焦点 |
submit | 当表单被提交 |
reset | 当表单被重置 |
常见的页面(窗体)事件监听
事件名 | 事件描述 |
---|---|
load | 当页面或图像完全加载 |
unload | 当用户退出页面 |
scroll | 滚动滚动条 |
resize | 窗体大小改变时 |
事件监听
DOM允许我们书写JavaScript代码以让HTML元素对事件做出反应。
“监听”就是让计算机随时能够发现这个事件发生了,从而执行一些预先编写的语句。主要有两种方法。
- 元素.onXXX = function(){}
- 元素.addEventListener('',function(){},BOOLEAN);
两种监听区别
- onXXX只能监听冒泡阶段,addEventListener可以开启捕获阶段。
- onXXX只能绑定一个函数,后写的会覆盖。addEventListener可多次复写。
事件监听解除
- 元素.onXXX = null;
- 元素.removeEventListener('事件类型',函数,false);
- IE独有:元素.detachEvent('onXXX',函数);
事件传播
当元素形成嵌套结构时,事件就会发生传播,传播分为:
- 事件捕获:结构上的父子元素,开启捕获后,事件执行从父元素捕获到子元素。
- 事件冒泡:结构上的父子元素关系,时间会从子元素冒泡到父元素。
- onXXX监听方法只能监听到冒泡阶段
- addEventListener第三个参数为true时会开启捕获监听。
- 嵌套结构的最内部元素不再区分捕获和冒泡,冒泡写在前面先执行冒泡,捕获写在前面先执行捕获。
- 冒泡阶段的onXXX和addEventListener谁先写先执行哪个函数。
阻止事件传播: event.stopPropagation( ); 方法,或者ie678的event.cancelBubble=true;
事件对象
事件处理函数提供一个形式参数,他是一个对象,封装了本次事件的细节。这个形参通常用字母e或单词event表示:例如
div.onclick = function(e){ }
div.addEventListener('click',function(event){},false);
谁是实参呢,实参由浏览器提供,当事件触发时,浏览器会打包所有事件对象传入事件处理函数形参中。
实际上并不用形参实参的说法,实测有一个event对象就是事件对象。
鼠标位置属性
属性名 | 属性描述 |
---|---|
offsetX、offsetY | 鼠标指针相对于事件源元素的X轴、Y轴坐标 |
clientX、clientY | 鼠标指针相对于浏览器视口的X轴、Y轴坐标 |
pageX、pageY | 鼠标指针相对于整张网页的X轴、Y轴坐标 |
screenX、screenY | 鼠标指针相对于电脑屏幕的X轴、Y轴坐标 |
键盘charCode属性和keyCode属性
e.charCode属性通常用于onkeypress事件中,表示用户输入的字符的ASCII码。
e.keyCode属性通常用于onkeydown事件和onkeyup中,表示用户按下的按键的键码。
charcode字符码
字符 | 字符码 |
---|---|
数字0~数字9 | 48~57 |
大写字母A~Z | 65~90 |
小写字母a~z | 97~122 |
keycode键码
按键 | 键码 |
---|---|
数字0~数字9 | 48~57 |
字母不分大小a~z | 65~90 |
四个方向左上右下 | 37、38、39、40 |
回车键 | 13 |
空格键 | 32 |
元素offset系列属性
- 该系列属性只读,不可写,且读出来的数值是一个Number类型数值
- 可以得到元素的偏移位置,返回不带单位的数值。这个数值以开启定位的父元素为准。
ELE.offsetTop
ELE.offsetLeft - 可以得到元素的宽度和高度,包含padding和border,即元素盒子的大小。
ELE.offsetWidth
ELE.offsetHeight - 返回带有定位的父亲,否则返回body
元素的client系列属性
- clientWidth 和 clientHeight 得到元素盒子除了border以外的尺寸大小。
- clientLeft 和 clientTop 不常用,相当于左边框和上边框的大小
元素的scroll系列属性
- scroll系列属性要出现滚动条才更好理解更好用
- scrollWidth 和 scrollHeight 得到元素盒子除了border外的所有内容的尺寸大小。
- scrollTop 和 scrollLeft 得到元素盒子被卷去的尺寸长度。该属性可用于“滑到底部才可以点击确定”
获取元素在页面上的属性
- 可以用
window.getComputedStyle(元素名,null);
//第一个值传的是需要获取计算样式的元素,第二个值传入是否获取的是伪元素 - 可以用
ELE.getBoundingClientRect()
,例如ELE.getBoundingClientRect().left
事件的方法
preventDefault() 方法 阻止默认事件
e.preventDefault( ) 方法用来阻止事件产生的“默认动作”。
例如鼠标滚动的动作,表单提交,a标签跳转,右键菜单等。
一些特殊的业务需求,需要阻止事件的“默认动作”。
阻止默认事件还可以用return false;
stopPropagation( ) 方法 阻止事件传播
e.stopPropagation( ) 方法用来阻止事件继续传播
在一些场合,非常有必要切断事件继续传播,否则会造成页面特效显示出bug
事件委托
把事件绑定到父元素上,用 event.target 定位到子元素(利用了事件冒泡)
event.target 触发此事件的最早元素,即“事件源元素”
event.currentTarget 事件处理程序附加到的元素
事件委托的使用场景:
- 当有大量类似元素需要批量添加事件监听时,使用事件委托可以减少内存开销。
- 当由动态元素节点上树时,使用事件委托可以让新上树的元素具有事件监听
注意事项:
- 不能委托不冒泡的事件给祖先元素
- 最内层元素不能再有额外的内层元素了
移动端事件
- 触摸事件:touch事件、pointer事件。
- 手势事件:gesture
- 传感器事件:sensor
移动端使用 tansform:translate3d();
会开启GPU加速,提高性能。
touch 事件:
touchstart、touchmove、touchend、touchcancel(比如操作时突然来电话)
touchstart 必须要在绑定事件的元素触发,触发start后,touchmove 和 touchend 手指移除元素依然可触发。
touch 事件的 TouchEvent 对象
- event.touches
- event.targetTouches
- event.changedTouches
这些对象都是一个 Touchlist,单指操作只需提取一个即可,比如 event.changedTouches[0]
- touches是正在触摸屏幕的所有手指的列表。
- targetTouches是正在触摸当前DOM元素的手指列表。
- 如果是绑在dom元素的事件以上两个是一样的
- changedTouches是手指状态发生了改变的列表,从无到有 或者 从有到无
touches 和 targetTouches 很多情况下在 touchend 事件里没有值,所以推荐使用 changedTouches
其他触摸事件
touch 只是基础事件,还有其他例如单击,长按,双指等事件。
可以自行封装这些高级事件,也可使用例如 hammer.js
等别人封装好的事件库。
定时器和延时器
定时器
setInterval( ) 函数可以重复调用一个函数,在每次调用之间具有固定的时间间隔,写法:
setInterval(function(){},1000)
第一个参数是函数,第二个参数是间隔时间,函数里的语句块将会在每过间隔时间后执行一次。
setInterval函数可以接受第3、4、5...个参数,它们将按顺序传入函数,例如。
setInterval(function(a,b){
},1000,1,2)
//第三个参数开始,表示传入函数内的实参
清除定时器
clearInterval( ) 函数,圆括号内传入要清除的定时器
延时器
setTimeout( ) 函数可以设置一个延时器,当指定时间到达后,会执行函数一次,不再重复执行,写法:
setTimeout(function(){
},1000)
清除延时器
clearTimeout( ) 函数可以清除延时器
初识异步语句
定时器和延时器是两个异步语句。
异步(asynchronous):不会阻塞CPU继续执行其他语句,当异步完成时,会执行“回调函数”(callback)
//例子:
setInterval(function(){
console.log('a');
},1000)
console.log('b');
//会先输出b,再输出aaaaaa
异步语句不会阻塞程序的正常执行。
(这里记个记号,异步这部分是我学前端的第一个大坎,原因是我看的好多教程都没有讲解异步底层原理,直到去看了pink老师的讲解才理解了异步,因为这点导致我后面的promise和ajax学得一知半解的)
函数节流
延时器可以用作函数节流,即加个开关,定时打开或关闭开关,需足够时间才能执行下一次函数,具体方法:
var lock = true;
function 需要节流的函数名(){
if(!lock) return;
lock = false;
setTimeout(function(){
lock =true;
},节流时间);
}
BOM
Browser Object Model 浏览器对象模型,是JS与浏览器窗口交互的接口。
一些与浏览器改变尺寸、滚动滚动条相关的,都要借助BOM技术。
window对象
window对象是当前js脚本运行所处的窗口,而这个窗口中包含DOM结构,window.document属性就是document对象。
在有标签页功能的浏览器中,每个标签拥有自己的window对象,不共用。
全局变量是window的属性
var a = 10;
//控制台输入window.a返回10
这意味着多个js文件之间是共享全局作用域的。
内置函数普遍是window的方法
如setInterva( ) 、alert( ) 等内置函数,普遍是window的方法
window属性
浏览器窗口尺寸相关属性
属性 | 意义 |
---|---|
innerHeight | 浏览器窗口的内容区高度,包含滚动条(如果有) |
innerWidth | 浏览器窗口的内容区高度,包含滚动条(如果有) |
outerHeight | 浏览器窗口的外部高度 |
outerWidth | 浏览器窗口的外部宽度 |
浏览器的滚动条是有宽度的,如果想要获得不包含滚动条的窗口宽度,要用
document.documentElement.clientWidth
窗口改变事件resize见window事件
浏览器窗口滚动条相关属性
属性 | 意义 |
---|---|
window.pageYOffset | 获取当前滚动条Y轴已滚动像素值 |
window.pageXOffset | 获取当前滚动条X轴已滚动像素值 |
window.scrollY | |
window.scrollX | |
document.documentElement.scrollTop | |
document.documentElement.scrollLeft |
document.documentElement.scroll可读可写,可用作返回顶部。
滚动条相关事件scroll见window事件
浏览器窗口滚动条相关方法
方法名 | 作用 |
---|---|
window.scoll( ) | |
window.scollTo( ) | |
window.scrollBy( ) |
传入(x,y)值滚动到输入的位置。
scroll和scrollTo一样,scrollBy是在现基础上累加
window方法
事件名 | 事件描述 |
---|---|
load | 当页面或图像完全加载 |
unload | 当用户退出页面 |
scroll | 滚动滚动条时 |
resize | 窗体大小改变时 |
navigator对象
属性 | 意义 |
---|---|
appName | 浏览器官方名称 |
appVersion | 浏览器版本 |
userAgent | 浏览器的用户代理(含有内核信息和封装壳信息) |
platform | 用户操作系统 |
history对象
history.back(); //等同于浏览器回退按钮
history.go(-1); //同上
history.go(1); //前进一个页面
history,forward(); //同上
a标签实现回退功能
<a href="javascript:history.back();">回退</a>
location 对象
location.href 返回当前页面URL
location.reload(true) 传入true强制从服务器重新渲染页面
location.search 当前浏览器的get请求查询参数
面向对象
认识对象
Object 是键值对的集合。
写法:键和值之间用冒号分割,每组键值之间用逗号分隔。
如果对象的属性键名不符合js标识符命名规则,这个键名必须用引号包裹。
//方法一:
var 变量 = {
键1 : 值1,
键2 : 值2
}
//方法二:
var 变量 = new Object();
属性的访问
用 点语法 访问对象中指定的值,
如果属性名不符合js标识符命名规则,则必须用方括号里加引号里写属性名。
如果属性名以变量形式存储,也必须用方括号。
可用 hasOwnProperty( )
方法判断属性是否是自己的,
或者使用 in
判断属性是否是自己和父级的。
属性的更改
直接用赋值运算符重新对属性赋值即可。
属性的创建
如果对象本身没有属性,直接用 点语法 写属性名,并用赋值运算符赋值即可,全局变量原理也是这样。
属性的删除
用delete操作符,例如:
delete 对象.属性;
对象的方法
如果某个属性值是函数,着它也被称为对象的方法。
方法的调用
使用点语法即可。
方法和函数
方法是对象的函数属性,需要用对象打点调用,函数也可以用window打点,所以二者在js里无差别。
对象的遍历
用 for(var 循环变量 in 对象){} 循环,例:
for(var k in obj){
console.log(obj[k]);
}
循环变量可随意命名,一般为k或prop,循环变量会一次变为对象的每一个键。在for in 遍历里,一定要写成 对象[] ,的形式,因为循环变量是变量,中括号形式才能没问题循环所有形式的变量。
对象的深浅克隆
慕课网方法:
function deepClone(o){
if (Array.isArray(o)) {
var result = [];
for (var i = 0;i < o.length;i++) {
result.push(deepClone(o[i]));
}
}else if(typeof o == 'object'){
var result = {};
for (var k in o) {
result[k] = deepClone(o[k]);
}
}else{
var result = o;
}
return result;
}
渡一方法
function deepclone(origin,target){
var target = target||{};
for(var prop in origin){
if(origin.hasOwnProperty(prop)){
if(typeof(origin[prop]) == 'object'){
if (Object.prototype.toString.call(origin[prop])=='[object object]') {
target[prop] = {};
} else{
target[prop] = [];
}
deepclone(origin[prop],target[prop]);
}else{
target[prop] = origin[prop];
}
}
}
return target;
}
认识函数的上下文
函数中可以用 this 关键字,他表示函数的上下文。
this 指向必须通过调用函数时的“前言后语”来判断。
函数的上下文由调用方式决定,函数只有被调用,才会产生上下文。
上下文规则
- 对象打点调用他的方法函数,则函数的上下文是这个打点的对象。
- 圆括号直接调用函数,则函数上下文是 window 对象。
- 数组(或类数组对象)枚举出函数进行调用,则上下文是这个数组(或类数组对象)。
- 在IIFE函数中,上下文是window对象。
- 定时器、延时器调用函数,上下文是 window 对象。
- 事件处理函数的上下文是绑定事件的DOM元素,所以通常需要备份上下文。
call 和 apply
能指定函数的上下文,例:
函数.call(上下文)
函数.apply(上下文)
区别: 当函数需要参数时,call 要用逗号罗列参数; apply 需把参数写在数组中。
bind
bind 和 call 差不多,不过它只改变this指向,不会调用函数,执行完bind之后会返回一个已经修改好this指向的函数,可以用一个变量接收
function fn(){}
var ojj = {}
var bii = fn.bind(o)
用new调用函数
用new接函数调用,会从函数内部返回一个对象。
JS规定,使用new操作符调用函数会进行“四步走”用new调用函数的四步走:
- 函数体内会自动创建出一个空白对象。
- 函数的上下文(this)会指向这个空白对象。
- 函数体内的语句会执行。
- 函数会自动返回上下文对象,即使函数没有return语句。
上下文规则总结表格
情况 | this指向 |
---|---|
对象.函数() | 对象 |
函数直接调用 | window |
数组[下标] () | 数组 |
IIFE函数 | window |
定时器、延时器 | window |
DOM事件处理函数 | 绑定DOM元素 |
call和apply | 任意指定 |
用new调用函数 | 秘密创建出的对象 |
构造函数
function 函数名(参数1,参数2){
this.参数1同名 = 参数1;
this.参数2同名 = 参数2;
}
然后用 new 调用函数执行:
构造函数开发者约定函数命名时首字母要大写,不用new调用不能正常工作。
构造函数用来“构造新对象”,它内部语句会为新对象添加属性和方法,完成对象初始化。
“类”与实例
JS中是没有类这个概念的,因为JS的对象与真正的面向对象语言有差别,叫“基于对象”。
构造函数可以看为是“类”,构造函数new出来的对象可以看作为“实例”。
判断这个实力是否是一个“类”构造出来的,可以用
A instanceof B
prototype 原型
任何函数都有prototype属性。
prototype属性是个对象,它默认拥有constructor属性指回函数。
普通函数的prototype属性没有任何用处,而构造函数的prototype非常有用,构造函数的prototype是它实例的原型。
原型链
[图片上传失败...(image-be2be0-1649463907747)]
hasOwnProperty 可以检查对象是否真正“自己拥有”某属性或方法。
in 可以检查对象是否能访问到这个属性或方法。
无法直接访问的意思是,构造函数自身无法直接调用或访问原型上的方法或属性,而实例可以,构造函数自身想访问原型上的方法必须先找原型再调用
在prototype上添加方法
将方法添加到构造函数上时,生成的每一个新对象就会多一个新占用内存的方法,会造成内存浪费。
解决方法就是将方法写道prototype上,只生成一个堆内存就够了。
重写constructor
prototype里面自带有一个constructor方法用于指回是哪个构造函数构造出来的,但通常一个构造函数的原型方法里需要塞下多个方法的时候我们通常需要用对象的形式,例如
function FFnn(){
}
FFnn.prototype = {
say(){}
sing(){}
}
通常会这样子通过对象的形式往里面添加多个方法,那么这个时候自带的construtor就没有了,所以这个时候如果你还想要有一个constructor方法指回构造函数,记得重新自己把constructor写上
FFnn.prototype = {
constructor:FFnn
say:function(){}
sing:function(){}
}
继承
两个无关的构造函数,但一个构造函数需要的属性在另一个函数上也有,就可以利用原型链原理达成继承。
方法:使子函数的原型等于另一个函数的实例:
Fun2.prototype = new Fun1();
继承——圣杯模式
function inherit(Target,Origin){
function F(){};
F.prototype = Origin.prototype
Target.prototype = new F();
Targer.prototype.constructor = Targer;
Targer.prototype.uber = Origin.prototype;
}
包装类
包装类的目的就是为了让基本类型值可以从他们的构造函数的prototype上获得方法。
包装类构造出来的值是一个对象,type of 返回 object ,可以参与运算。
Math 数学对象
除了之前介绍的那些 Math 方法以外,我们还有更多常用的Math方法。
四舍五入 Math.round()
实现四舍五入到小数点后某位:
数字 * 10的n次方 然后四舍五入 再 / 10的n次方。
得到参数列表最大最小值
Math.max() 得到参数列表最大值。
Math.min() 得到参数列表最小值。
如何求数组最大最小值:Math.max() 和 Math.min() 结合apply,例如:
var arr = [3,2,9];
var max = Math.max.apply(null,arr);
//ES6之后还可以
Math.max(...arr);
Date 日期对象
使用 new Date()
即可得到当前时间的日期对象,他是object类型值。如果什么参数都不加就是此时此刻的时间。
使用new Date(2021,6,12)
即可得到指定日期的日期对象,注意第二个参数是从0开始算的,6表示7月份。
也可以new Date('2021-7-12')
这样写,算时区。
常见日期方法
方法 | 功能 |
---|---|
getDate() | 得到日期1~31 |
getDay() | 得到星期0~6 |
getMonth() | 得到月份0~11 |
getFullYear() | 得到年份 |
getHours() | 得到小时数0~23 |
getMinutes() | 得到分钟数0~59 |
getSeconds() | 得到秒数0~59 |
时间戳
表示从1970年1月1日零点整距离某时刻的毫秒数。
可以通过 getTime()
方法或 Date.parse()
函数可将日期对象改为时间戳。
或者直接在date对象前面加个加号 + 即可,例如 var chuo = + new Date()
通过 new Date(时间戳)
可将时间戳改为日期对象
正则表达式
regular expression 描述了字符串的“构成模式”,经常被用于检查字符串是否符合预定的格式要求。
正则表达式“按位”描述规则,是指它是一位一位的描述子字符串的构成形式。
正则表达式的创建
使用 /内容/ 的语法形式,可以快速创建正则表达式
也可以使用 new RegExp('内容') 这种方法使用一些字符可能要用转义。
type of 检查正则表达式返回object。
元字符
元字符 | 功能 |
---|---|
\d | 匹配一个数字 |
\D | 匹配一个非数字字符 |
\w | 匹配一个单字字符 |
\W | 匹配一个非单字字符 |
\s | 匹配一个空白字符,包含空格制表符换行符 |
\S | 匹配一个非空白字符 |
. | 任意字符 |
^ | 匹配开头 |
# | 匹配结尾 |
量词
量词 | 意义 |
---|---|
* | 匹配前一个表达式0次或多次,等价于{0,} |
+ | 匹配前一个表达式1次或多次,等价于{1,} |
? | 匹配前一个表达式0次或1次 |
{n} | n是一个正整数,匹配了前面一个字符刚好出现了n次 |
{n,} | n是一个正整数,匹配前一个字符至少出现了n次 |
{n,m} | n和m都是正整数,匹配前面的字符至少出现n次,最多m次 |
方括号表示法
- 使用方括号,比如[xyz]可以创建一个字符集合,表示匹配方括号中的任意字符,一个方括号代表一位。
- 可以用短横 - 来指定一个字符范围,^ 表示否定
例如[0-9] [a-z] [A-Z] [0-9a-zA-Z_] [0-68-9]
修饰符
i | 不区分大小写搜索 |
g | 全局搜索 |
正则表达式的方法
test() 测试某字符串是否匹配正则表达式,返回布尔值
exec() 根据正则表达式,在字符串中进行查找,返回结果数组或者null。exec方法开启全局搜索后可逐条遍历(ES6迭代器概念)。
字符串的相关正则方法
方法 | 简介 |
---|---|
search() | 在字符串中根据正则表达式及逆行查找匹配,返回首次匹配到的位置索引,测试不到则返回 -1 |
match() | 在在字符串中根据正则表达式进行查找匹配,返回一个数组,找不到则返回null |
replace() | 使用替换字符串替换掉匹配到的子字符串,可以使用正则表达式 |
split() | 分割字符串为数组,可以使用正则表达式 |
本地存储
- 本地存储不会发送到服务器端,且二者大小有所区别,sessionStorage能存储5m左右,localstorage可以储存10m左右。(单个域名下)
- 二者都可以在浏览器F12中,Application-Storage 中找到,分为 key 和 value。
- 使用方法为
sessionStorage.setItem(key,value)
和localStorage.getItem(key,value)
- 二者存储的键和值只能是字符串类型,如果不是字符串类型,也会先转成字符串再存进去。或者存入JSON类型值
- 不同域名不能共用 sessionStorage 和 localStorage
- IE7及以下不支持
sessionStorage
sessionStorage 是一种浏览器本地存储数据的方式,只存储在本地,不发送到服务器端。当会话结束(例如关闭浏览器时),sessionStorage 中的数据会清空。基本用法:
- 增加数据setItem方法:
sessionStorage.setItem(key,value);
- 查询数据getItem方法:
sessionStorage.getItem(key);
- 删除数据removeItem方法:
sessionStorage.removeItem(key);
全部清空clear方法:sessionStorage.clear();
- 改更数据setItem方法:同key属性覆盖
其他属性:
- length 属性:
sessionStroage.length()
获取长度 - key方法,参数时数据,代表key的索引,根据索引获取key值。
sessionStorage.key(0)
localStorage
localStorage 时持久化的本地存储,除非手动清楚或者满了自动清除,否则数据永远不会过期。
- 增加数据setItem方法:
localStorage.setItem(key,value);
- 查询数据getItem方法:
localStorage.getItem(key);
- 删除数据removeItem方法:
localStorage.removeItem(key);
全部清空clear方法:localStorage.clear();
- 改更数据setItem方法:同key属性覆盖
- length 属性:
localStroage.length()
获取长度 - key方法,参数时数据,代表key的索引,根据索引获取key值。
loacalStorage.key(0)
ES6简介
ECMAScript 第六代标准
let 和 const
用于声明变量或声明常量
变量和常量
var 和 let 声明的就是变量,变量初始化后还可以重新赋值。
const 声明的是常量,常量一旦初始化后就不能重新赋值,否则报错。常量可以防止不经意间改变了不能改变的量。如果常量值的类型是引用类型,例如对象,可以修改对象属性的值。
const 声明常量一旦声明,就必须完成初始化,否则报错
let、const 与 var 的区别
- 重复声明:var 支持重复声明,let 与 const 不支持。
- 变量提升:let 与 const 不存在变量提升。
- 暂时性死区:只要作用域内存在 let 和 const ,他们声明的量就会绑定作用域,该量不再受外部影响。
- window对象的属性和方法:let 和 const 声明的量不会变成 window 对象的属性或方法。
- 块级作用域: let 与 const 存在块级作用域。
模板字符串
一般字符串:单引号和双引号。
模板字符串:反引号。
特征:
- 模板字符串中所有空格、换行、缩进都被保留在输出中。
- 同样可以使用转义字符输出反引号、反斜杠等特殊字符。
- 可以直接在模板字符串中使用
${}
注入内容(变量,属性值,函数值,计算值等)
标签模板
是函数调用的一种特殊形式。模板字符串可以紧跟在一个函数后面,该函数将被调用来处理这个模板字符串,这被成为“标签模板”功能,例如:
alert('hello')
//等同于
alert`hello`
"标签"指的就是函数,紧跟在后面的模板字符串就是他的参数。
如果模板字符串中有变量,就不是简单的调用了,而是会将模板字符串先处理成多个参数,再调用函数。
let a = 5;
let b = 10;
function tag(tpldata,...values){
console.log(tpldata);
console.log(values);
console.log(arguments);
}
tag`hello ${a+b} world ${a*b}`
//等同于
tag(['hello',' world ',''],15,50)
“标签模板”的一个重要应用,就是过滤HTML字符串,防止用户注入代码
let message =
SaferHTML`<p>${sender} has sent you a message.</p>`;
function SaferHTML(templateData) {
let s = templateData[0];
for (let i = 1; i < arguments.length; i++) {
let arg = String(arguments[i]);
// Escape special characters in the substitution.
s += arg.replace(/&/g, "&")
.replace(/</g, "<")
.replace(/>/g, ">");
// Don't escape special characters in the template.
s += templateData[i];
}
return s;}
箭头函数
写法: ()=>{}
小括号内参数,等号大于号,中括号内函数体。
通常需要用一个变量或常量接收。
注意事项
- 单个参数可省略小括号,0个或多个不可省略。
- 单行函数体可同时省略大括号和 return ,必须同时。
- 单行对象如果要省略大括号,需要在对象外加小括号:例如
const add = (x+y)=>({value:x+y})
箭头函数没有自己的this
非箭头函数执行一定会产生属于这个函数的this,箭头函数没有自己的this,只会从自己的作用域链的上一层继承 this.
箭头函数使用注意
- 不能作为构造函数。
- 需要 this 指向调用对象的时候箭头函数不适用。
- 需要 arguments 的时候,箭头函数没有 arguments 。可以用剩余参数替代。
Iterator 迭代器/遍历器
找到 iterator
先创建一个数组 const arr = [1,2]
用控制台查看,深挖其属性会有个 Symbol.iterator ,执行该方法 arr[Symbol.iterator]()
执行后会返回一个 Array Iterator{}
可以用一个量接收。
使用Iterator
iterator调用 next() 方法,会返回 {value: xxx , done: xxx}
可迭代对象
只要有 Symbol.iterator 方法,且这个方法调用后会产生一个 iterator 。
例如:数组、字符串、arguments、NodeList、Set、Map、剩余参数 等。
一般对象不是可迭代对象,可以给一般对象强行添加 Symbol.iterator 方法。
obj[Symbol.iterator] = ()=>{
let index = ;
return(
next(){
index++;
……
}
)
}
//类数组对象可以直接偷数组原型上的
LEISHUZU[Symbol.iterator] = Array[Symbol.iterator]
for of
想要遍历一些东西的时候,五花八门,对象用 for(...in...) ,数组用 for 循环、forEach 方法。而 iterator 只有一种遍历方式,就是“下一个”,一般用系统封装好的 for(...of...)
for(let item of arr){
}
- for(...of...) 循环只会遍历出那些 done 为false时对应的 value 值。(如果改写了对象的Symbol.iterator()之后比如把done改为false则会疯狂输出)
- for(...of...) 可以和 break 、 continue 一起使用。
可迭代对象的方法
forEach()
forEach() 方法可遍历 可迭代对象 。
forEach() 传入的第一个参数 是一个回调函数。回调函数可以使用三个参数,按顺序分别是 (1)可遍历对象键值对的值 (2)可遍历对线键值对的键 (3)可遍历对像本身。
forEach() 传入的第二个参数 是 this 指向(非箭头函数时才可用)。例如:
let arr = [11,22,33];
arr.forEach((value,key,self)=>{
console.log(value,key,self)
},arr)
keys()
一些可迭代对象可以执行 keys() 方法,遍历此方法得到的对象,可以取得键名(例如数组的索引值,可迭代对象的键名)。例如:
let arr = [1,2,3]
for(let k of arr.keys()){
console.log(k)
}
values()
可迭代对象的 values() 方法,遍历此方法返回的对象可以取得键值对的值(低版本浏览器不支持)
let arr = [1,2,3]
for(let k of arr.values()){
console.log(k)
}
entries()
可迭代对象的 entries() 方法,遍历此方法返回的对象可以取得键值对(索引+值组成的数组)
let arr = [1,2,3]
for(let k of arr.entries()){
console.log(k)
}
生成器函数
生成器函数,在旧函数声明方法 function fc(){}
中,在function关键字和函数名中间插入一个*号
function* (){}
普通函数执行时没有停止功能 Generator函数可以暂停
Generator 生成器 返回的是iterator接口
CO库就是不停的调用next方法将最后结果返回
Generator 中 yield 后面只能跟 Thunk 函数或 Promise 对象
function * gen(){
console.log(arg);
let one = yield 111;
console.log(one)
}
let iterator = gen('AAA')
console.log(iterator.next());
console.log(iterator.next('BBB'))
next方法里面可以传入实参,传入的实参将会作为上一个yield整体返回的结果
展开运算符
... 可用于 可迭代对象 的展开。
例如,可以用于数组展开,把数组拆成一个一个数字。例如:Math.min 和Math.max 只能计算一串参数的最大最小值不能传入数组,这时就能传入数组同时用展开运算符展开。
let arr = [1,2,3];
//直接Math.min(arr)则不计算,可以
Math.min(...arr);
展开运算符可用于数组浅复制(浅克隆),例如
let arr1 = [1,2,3];
let arr2 = [...arr1]
所以展开 可迭代对象 外直接套一个中括号可以快速转变为一个数组。
展开运算符可以用于 对象 展开
但不能直接展开,只能在 {} 大括号中展开。
所以还可以用于对象复制,也可以用于合并对象,用于合并对象时,后展开的与前展开的对象同名的属性会覆盖。
展开空对象没有效果
let obj1 = {name:'mingzi',age:18}
let obj2 = {...obj1}
展开运算符只能“浅展开”
即只能展开一层
展开运算符特殊用法:剩余参数
当函数执行传入 比形参数量更多的 实参时,可以用剩余参数接受,写法 ...xxx ,本质是个数组,即使没有值,也是个控股数组,读取时不用 ... 直接读 xxx 即可。例如
function func(a,b,...sycs){console.log(sycs)}
func(1,2,3,4,5,6,7,8)
//返回数组[3,4,5,6,7,8]
箭头函数中的剩余参数
箭头函数中,因为箭头函数只有一个参数的时候可以省略小括号,但即使只有一个剩余参数,也不能省略小括号。
箭头函数没有arguments,所以可以用剩余参数替代,且剩余参数可以用数组的方法。
剩余参数必须、只能是最后一个参数,之后不能有其他参数,否则报错
剩余参数可以与解构赋值结合使用。
剩余参数与对象解构赋值结合使用会成为剩余元素。
解构赋值
- 将 可迭代对象 的值 赋给一个 可迭代对象 书写形式的结构,例如:
const [a,b] = [1,2]
- 对象也可以使用解构赋值,但必须满足 1.模式(结构)匹配 2.属性名相同的完成匹配。
- 其他数值类型也可以使用解构赋值,例如number和boolean,会先将等号右边的值先转为对象。但无法解构其内容,只能提取继承的属性。
- undefined 和 null 无法进行解构赋值。
可迭代对象全部都可以使用数组形式完成结构赋值,字符串还可以使用对象的结构完成解构赋值,例如: let {0:a} = 'bilibili'
,此时 a 为下标为 0 的字母b。
解构赋值的默认值
可以给解构赋值的常量变量赋一个默认值,例如 let[a=1,b=2]=[]
注意:只有右边与左边解构赋值严格等于 undefined 时,默认值才会生效。默认值拥有惰性求值的特性,即用得到才执行求值式求值,用不到则不执行。
解构赋值的应用用途
解构赋值可以用于快捷交换变量值,例如
let x=1,y=2;
[x,y]=[y,x]
//此时x和y就快速交换了值。
Set
Set是一些列无序、没有重复值的数据集合。
无序: Set没有下标去标识每一个值,所以set是无序的,不能像数组一样通过下标去访问 set 的成员,但遍历 set 的时候,是根据成员添加进 set 的顺序进行遍历的。
没有重复值: Set会自动去重,Set重复值的判断遵循全等原则,但NaN除外,在Set中NaN也不可重复。
创建一个 Set
使用 Set 构造函数 const se = new Set()
即可创建一个set,创建 Set 时可以传入一个可迭代对象作为参数,构造出来的Set就会拥有对象里的成员,例如:
let arr = [1,2,3]
const se = new Set(arr)
Set 的属性
size:可以访问Set里面的成员个数。new Set().size
会返回一个0.
Set 的常用方法
方法 | 作用 |
---|---|
add() | 添加成员,可连续使用 |
has() | 查看是否有某个成员,返回布尔值 |
delete() | 删除某个成员 |
clear() | 清除所有成员 |
forEach() | 遍历 |
add
可以连续添加,例如:
const se = new Set()
se.add(1).add(2).add(3)
delete
如果删除不存在的成员,则什么都不会发生,也不会报错
forEach
Set 的 forEach 是按照成员添加进Set的顺序遍历的
Set 用途
Set 可以用于 可迭代对象 去重,例如数组去重,字符串去重。或者需要使用Set方法的地方也可以转换成Set
Map
Map 和对象都是键值对的集合。
Map 和对象的区别:
对象一般用字符串当作键,Map 可以用所有数据类型的值当键,包括引用类型。
创建一个 Map
使用 Map 构造函数 const ma = new Map()
即可创建一个 Map ,创建 Map 时可以传入一个 entry object (例如二维数组,二维set对象,map对象),例如:
let arr2d = [
[1,2],
[3,4],
[5,6]
]
const ma = new Map(arr2d);
Map 属性
size:获取有几个键值对,上面的代码然后 ma.size
会返回3.
Map 的方法
方法 | 作用 |
---|---|
set() | 添加新成员 |
get() | 获取成员 |
has() | 判断是否有这个成员返回布尔值 |
delete() | 删除成员,如果没有则无事发生也不报错 |
clear() | 清除所有成员 |
forEach() | 遍历成员 |
set
可连续使用,例如 new Map().set(9,8).set(7,6)
,当后添加的成员与前添加的键名一样时,后添加的会覆盖前添加的。
get
获取成员,当获取不存在的成员时返回 undefined
Map 其他注意事项
Map 重复键名的判断方式遵循全等原则,NaN例外。
Map 用途
当你只需要键值对结构时,或者需要Map的方法时,或者使用字符串以外的值做键时。
ES6其他新增技巧
对象字面量增强
- 属性的简洁表示法:已定义的变量或常量,可以直接写进对象里当其属性,例如:
let name = 'mingzi';
let obj = {name}; //等同于let obj = {name:name}
obj.name //直接返回 'mingzi'
- 方法的简洁表示法:对象的方法可以省略冒号和 function 关键字,例如:
//const obj = { func : function(){} }
const obj = { func(){} }
- 方括号语法可以写在对象字面量里,方括号可以注入变量值,运算值等,例如:
let bianliang = 'x'
const obj = {
[bianliang] : 18;
}
obj.x //返回18
函数参数的默认值
可以给函数的参数设置一个默认值,例如:
function xjiay(x=1,y=2){
return x+y
}
xjiay()
//直接执行不传参数则使用默认值
- 生效条件:不传参或者传的是undefined的时候,默认值才会生效。
- 同样拥有惰性求值特性。
ES6 其他新增方法
ES6字符串新增方法
includes() 方法
用于判断字符串是否有某些字符。
用法:第一个参数传入的是一个值,返回布尔值判断字符串内是否有这个值。第二个参数表示开始搜索的位置。
startsWith() 和 endsWith
用于判断参数是否在字符串的头部和尾部,返回布尔值。
例如 abcd.startsWith('ab') 返回true,abcd.endsWith('cd')返回true
repeat()
repeat() 方法返回一个新的字符串,表示将源字符串重复n次。例如:
'rero'.repeat(4);
//输出rerorerorerorero
参数如果不是整数会自动向下取整,为0次则不输出内容,为负数则报错
padStart() 和 padEnd()
pad是填充的意思,用于补全字符串长度,无法改变原字符。
用法:
- 第一个参数表示“需要补到的长度”是多少,通常是一个整数。
- 第二个参数表示“补入的值”,如果不传参数2,默认使用空格代替。
- 如果第一个参数小于或等于原字符串长度,则补全不生效,返回原字符串。
- 如果第二个参数补入的值长度,补入后总长度不满足第一个参数,则循环再补,补到满足长度。
- 如果第二个参数补入的值长度,补入后总长度超过第一个参数,则只补需要补的长度,后面的截去。
padStart() 是从头部开始补, padEnd() 是从尾部开始补。例如:
'x'.padStart(5,'ab') //返回 'ababx'
'y'.padEnd(5,'6688') //返回 'y6688'
trimStart() 和 trimEnd()
trim是去掉不必要部分的意思,用于清除字符串的首或尾的空格,中间的空格不会清除。
trimStart() 等同于 trimLeft() ,trimEnd() 等同于 trimRight()。
同时去除头尾空格的方法为 trim()。
matchAll()
matchAll() 方法返回一个正则表达式在当前字符串的所有匹配
返回的是⼀个遍历器(Iterator),⽽不是数组。
ES6数组新增方法
includes()
数组的 includes() 方法,可以判断数组中是否含有某个成员,判断基于全等原则,NaN除外。第一个参数传入的是一个值,返回布尔值判断数组内是否有这个值。第二个参数表示开始搜索的位置。
Array.from()
Array.from() 可以将 可迭代对象 和 类数组对象 转换成数组。
- 第一个参数表示的是要转换的对象。本来是什么类型的值,如果没发生显式或隐式类型转换,转换后数组的每个成员还是原来的类型。
- 第二个参数是一个回调函数,用于将转换后的数组内的每一个成员进行处理然后返回新数组。例如
Array.from('01',(count)=>{return Boolean(count)})
将成员转成布尔类型。例如Array.from('12',(count)=>{return count*2})
将成员每个乘以2。 - 第三个参数指定/修改this指向
Array.of()
Array.of()方法用于将一组值转化为数组,即新建数组,而不考虑参数的数量或类型。
//使⽤Array.of()创建数组
console.log(Array.of()); //[] 创建⼀个空数组
console.log(Array.of(8)); //[8] 创建只有⼀个元素值为8的数组 console.log(Array.of(1, 2, 3)); //[1,2,3] 创建⼀个值为1,2,3的数组
//以前直接使⽤Array创建数组
console.log(Array()); //[] 创建⼀个空数组
console.log(Array(4)); // [ , , , ] 创建拥有4个元素空值的数组
console.log(Array(1, 2, 3)); //[1,2,3] 创建⼀个值为1,2,3的数组
find() 和 findIndex()
find() 方法找到满足条件的第一个立即返回这个值。
findIndex() 方法找到满足条件的第一个立即返回其索引。
- 第一个参数是一个回调函数,回调函数的参数分别为 (1)可遍历对象键值对的值 (2)可遍历对像键值对的键 (3)可遍历对像本身。可以在这个函数里指定规则。
- 第二个参数是回调函数里面的 this 指向
some() 和 every()
ARR.every() 和 ARR.every() 目的:确定数组的所有成员是否满足指定的测试
- some() 方法:只要其中一个为true 就会返回true。
- every() 方法:必须所有都返回true才会返回true,哪怕有一个false,就会返回false.
const data =[
{name:'zhangsan',age:22,sex:'man'},
{name:'lisi',age:25,sex:'woman'},
{name:'wangwu',age:23,sex:'man'},
];
//使⽤some判断data中是否含有⼀条name以"wang"开头的
let s1 = data.some(v => v['name'].startsWith("wang"));
console.log(s1); //true
//使⽤every判断data信息中是否都是age⼤于20的信息。
let s2 = data.every(v => v['age']>20);
console.log(s2); //true
若有⼀个不符合则返回false
fill()
ARR.fill() 函数,使用指定的元素替换原数组内容,会改变原来的数组。
参数一:要改变成的内容;参数二:要改变的开始位置;参数三:要改变的结束位置。
//空数组则没有替换
console.log([].fill(6)); //[]
//将数组中的值都替换成指定值6
console.log([1,2,3].fill(6));//(3) [6, 6, 6]
//从数组索引位置2开始替换成指定值6,替换到数组结束位置。
console.log([1,2,3,4,5].fill(6,2)); //(5) [1, 2, 6, 6, 6]
//从数组索引位置2开始替换到索引位置4前结束。
console.log([1,2,3,4,5].fill(6,2,4)); //(5) [1, 2, 6, 6, 5]
ES6对象新增方法
Object.assign()
Object.assign() 作用是合并对象
传入的参数为多个对象。本质是将后面参数的对象合并到第一个参数中,会改变第一个参数自身。如果不想改变任何参数,可以在第一个参数写一个空对象,例如: Object.assign({},obj1,obj2)
注意事项:基本数据类型作为源对象时,会先转换成对象,再合并。合并时如果出现同名属性,后面的会把前面的覆盖。
Object.keys() 、Object.values() 、 Object.entries()
这三个方法都是Object构造函数上的, 传入的参数是一个对象,执行后分别返回键名组成的数组、值组成的数组、键值对组成的数组。
为什么返回的不是 可迭代对象 ?因为 keys 是ES5的方法,ES5 中没有 Iterator 的概念,所以为了统一,Object 的这三个方法返回的都是数组。
Object.fromEntries()
是 Object.entries() 的逆操作,将可迭代对象作参数传入Object.fromEntries()中,会返回一个对象。
Object.is()
- ES5 ⽐较两个值是否相等,只有两个运算符:相等运算符(==)和严格相等运算符(===)。 它们都有缺点,前者会⾃动转换数据类型,后者的NaN不等于⾃身,以及+0等于-0。 JavaScript 缺乏⼀种运算,在所有环境中,只要两个值是⼀样的,它们就应该相等。
- ES6 提出“Same-value equality”(同值相等)算法,⽤来解决这个问题。 Object.is就是部署这个算法的新⽅法。它⽤来⽐较两个值是否严格相等,与严格⽐较运算符 (===)的⾏为基本⼀致。
Object.is('foo', 'foo') // true
Object.is({}, {}) //false
+0 === -0 //true
NaN === NaN // false
Object.is(+0, -0) // false
Object.is(NaN, NaN) // true
Object.getOwnPropertyDescriptors()
ES5 的Object.getOwnPropertyDescriptor()⽅法会返回某个对象属性的描述对象 (descriptor)。 ES2017 引⼊了Object.getOwnPropertyDescriptors()⽅法,返回指定对象所有⾃身属性(⾮继承 属性)的描述对象
const obj = { foo: 123, get bar() { return 'abc' } };
Object.getOwnPropertyDescriptors(obj);
// { foo:
// { value: 123,
// writable: true,
// enumerable: true,
// configurable: true },
// bar:
// { get: [Function: get bar],
// set: undefined,
// enumerable: true,
// configurable: true } }
、proto属性,Object.setPrototypeOf()和Object.getPrototypeOf()
JavaScript 语⾔的对象继承是通过原型链实现的。ES6 提供了更多原型对象的操作⽅法。 具体详⻅⽂档
Object.defineProperty
参数一:给谁添加属性
参数二:添加属性的属性名
参数三:配置项,是一个对象,其中包括value。
此方法定义的属性默认是不可枚举/遍历的,可在配置项里修改enumerable:true
开启遍历。
定于的属性默认是不可以被修改的,可在配置项修改 writable:true
开启修改
定义的属性默认是不可以被删除的,可在配置项修改 configurable:true
开启删除
类与对象
class (类)
- Javascript 的类,写法为
class 类名{ }
注意没有圆括号和分号。
- 类也可以是匿名的,所以可以用表达式形式定义类
const 类名 =class{ }
-
type of 类
返回的也是一个 function ,类里的方法本质上也是相当于写在这个 function 上的 prototype 上,所以 class 是构造函数的语法糖。
所以衍生出了“立即执行类”,例如:
//加new因为constructor必须要有new才能执行。
(new class {
constructor(){
执行语句;
}
})
constructor
class里的构造方法 constructor 用法,如下:
class 类名{
constructor(参数1,参数2...参数n){
this.属性名1 = 参数1;
this.属性名2 = 参数2
}
}
- 构造方法 constructor 里面的 this 直接指向实例化出来的对象。
- 构造方法 constructor 里面一般不定义方法,只定义属性,因为 constructor 里的方法会使每个实例都新生成一个自己的方法,占用新内存。
- 所以共享的方法直接写在类里即可,不用写在 constructor 里面,注意写法,方法与方法之间没有逗号,直接写
方法名(){ }
即可,不用加function关键字,例如:
class 类名{
constructor(参数1,参数2...参数n){
this.属性名1 = 参数1;
this.属性名2 = 参数2
}
方法1 (){ }
方法2 (){ }
}
类的方法(静态方法)
使用关键字 static ,写在声明里,是只有类可以访问的,实例无法访问,例如:
//static 静态方法名(){}
class Lei{
static func(){
console.log('静态方法')
}
}
Lei.func()
类的属性(静态属性)
有三种方法:
- 类名.属性名 = 值
- 声明里写 static 静态方法(){return 值;}
- 声明里直接写 static 静态属性 = 值
方法3存在兼容性问题,低版本浏览器不支持。
实例对象
如何创建一个实例:
new 类名();
即可生成一个实例对象,这个实例对象会拥有 class 里面的 constructor 的属性。例如
class Person{
constructor(a,b){
this.name = a;
this.age = b;
}
}
const student = new Person('ming',24);
这个 student 就会拥有 name 和 age 属性,属性值是什么取决于你写的是定值还是参数。
也可以给属性值添加默认值(截至2021年7月默认值已失效)
JS 类与对象 的私有属性和方法
想要实现私有,可以使用闭包实现,或者利用模块化。
class 继承
使用关键字 extends 和关键字 super ,例如:
class Person{
constructor(a,b){
this.name = a;
this.age = b;
}
func(){console.log(1)}
}
class Student extends Person{
}
此时 Student 这个类就会拥有 Person 的所有构造方法和共享方法。
子类如果想要拥有自己的构造方法,则必须配合 super 关键字使用,否则会报错;
super 前面不能放任何this操作,否则报错。例如
class Person{
constructor(a,b){
this.name = a;
this.age = b;
}
func(){console.log(this.name)}
}
class Student extends Person{
constructor(x,y,c){
super(x,y)
//这里super接收的参数会扔到父类的constructor中执行,this指向子类实例
this.newProp = c
}
}
子类想要使用父类的方法,子类调用的父类方法里的this指向的是父类,所以要在之类的构造方法里使用super
class 中的 super 关键字
- 在构造方法和一般方法中使用,super 代表的都是父类的原型 SUPERCLASS.prototype
- 在静态方法 static FUNCTION(){} 中使用,代表的是父类自身,this 指向子类。
- super 要么接点要么接括号,否则浏览器会报错。
class中的get和set
当实例调用读取父类方法时会触发get方法,get方法里面返回的值就是触发后返回的值
当实例修改父类方法时会触发set方法,set方法必须带有一个形参
class Phone{
get price(){
console.log('价格属性被读取了')
return 'iloveyou'
}
set price(newVal){
console.log('价格被修改了')
}
}
let s = new Phone()
console.log(s.price); //这里会触发get price并得到里面的返回值
s.price = 'free'; //这里会触发set
Module 模块系统
模块系统解决的主要问题:
- 模块化的问题。
- 消除全局变量。
- 管理加载顺序。
import、export、export default 和 script type='module'
- 只要你会用到 import 或 export ,在使用 script 标签加载的时候,就要使用
<script type='module'></script>
- 一个模块如果没有导出,也可以直接将其导入,被导入的代码都会执行一遍,多次导入同一个模块也只执行一遍,但无法使用模块的东西。
export default
export default 可以直接接模块里的定变量名,类或类名,方法或方法名等。
- 注意一个模块只能有一个 export default
- 注意 export default 导出的东西是按语句顺序执行的,没有提升。
export default 的 import 直接写导出的东西然后命名即可,名字可以随便取。
export
export 导出可以有两种形式。
- 接声明或语句,例如
export let a = 'cosin'
- 接大括号,例如
export {导出1,导出2}
export 的 import 只能用大括号接收,例如import{导出1,导出2}
如果导出或导入的东西想改名称,可以使用 as
关键字,例如:
export {name as newName} //此时import 接收的是 newName
import{newName as newnew}
import
import三种形式导入
- 通用形式导入,用as后接一个名字当作接受的对象
import * as m1 from './js/m1.js'
console.log(m1.name)
m1.fn()
- 解构赋值形式导入
import {name,fn} from './js/m1.js'
import {name as mingzi,fn as fangfa} from './js/m2/js'
import {default as mod} from './js/m3.js'
- 简便形式 仅针对 export deafault 默认暴露
import m3 from './js/m3.js'
同时导入
因为 export 和 export default 的导入方式不同,所以想一次导入这两种导出,可以使用:
import default导出的,{非default导出} from '路径'
- 采用整体导入,使用通配符 * 星号然后接 as 然后接一个名字,就代表导入所有导出的,包括 export 和 export default 的,例如:
import * as obj from './module.js'
导入的内如是一个 Module 对象,导出的东西都会变成这个 Module 对象的属性,default 的内容会变成 default 属性
Module 注意事项
- 导入的变量是只读的,导入的对象的属性值可以修改。
- 一个模块可以直接写export,即导入后直接导出,此时这个模块不可以使用导入的任何东西,相当于一个中转站。
- import 和 export 命令具有提升效果,会提升到整个模块的头部并率先执行。所以 import 和 export 别写在代码块中,想要按条件导入,可以使用 import() 函数。
- 模块顶层的 this 指向 undefined ,可以利用这点提示使用模块加载,例如:
if(type of this !== 'undefined'){
throw new Error('请使用模块加载')
}
ES6 数值拓展
Number.EPSILON
Number.EPSILON是 Javascript表示的最小进度,可以用来做浮点运算判断 、
fuction equal(a,b){
if(Math.abs(a-b)<Number.EPSILON){
return true;
}else{
return false;
}
}
console.log(equal(0.1+0.2,0.3))
Number.isFinite 检测一个数值是否为有限数
console.log(Number.isFinite(100))
Number.isNaN
isNaN旧方法扔进Number中
Math.trunc()
将数字(参数)的小数部分抹掉
Math.sign()
判断参数是正数、负数、还是0
直接书写N进制数字
let b = 0b1010
let o = 0o777
ES7 新特性
includes
可以检测数组是否拥有该组员
两个星号(乘号)
两个星号后面接的数字代表几次方,2 ** 10 类似于 Math.pow(2,10)
ES8
async 函数
在函数前面加上 async 关键字
async function fn(){
}
async函数返回结果是一个promise,返回的promise状态由内容决定
- 如果返回的东西是一个Promise对象,则由该promise决定成功还是失败
- 如果返回结果是抛出一个错误,则状态为失败reject,例如 throw new Error('err')
- 其他返回结果大多为成功resolve/fulfilled
await
- await 要放在 async 函数中
- await 右侧的表达式一般为 promise 对象
- await 返回的是 promise 成功的值
- await 的 promise 失败了,就会抛出异常,需要使用 try{}catch{} 捕获
ES9
正则捕获分组
?<>
方向断言
?<=
dotAll 拓展
结尾加上s
ES10
数组拓展 flat
将多维数组转化为低维数组
ARR.flat()
参数是数字,意义为展开深度为几层,例如三维转一维,深度就是3-1=2
数组拓展 flatMap
如果数组使用map之后返回一个多元数组,flatMap则是把多元数组变回一维数组,
ARR.flatMap() 就是数组的map方法和flat方法结合
Symbol.prototype.description
let sb = Symbol('HQM')
sb.description
ES11
对象的私有属性
在属性前面加上井号
class Person{
name;
#age;
#weight;
constructor(name,age,weght){
this.name = name;
this.#age = age;
this.#weight = weight;
}
}
const girl = new Person('hong',18,'45kg')
console.log(girl.#age) //此时会报错,私有属性不能在外部直接访问,可以在类定义一个内部方法通过内部方法访问
Promise.allSettled
- Promise.all 是有一个失败则直接返回失败。
- 而 Promise.allSettled 是无论成功还是失败都会把所有参数里的Promise全部执行完再返回结果。结果都是成功
- Promise.allSettled() 和 Promise.all() 的参数都是一个可迭代对象(通常为数组)
可选链操作符
?.
当一个函数的参数 是一个对象的时候,如果要找传入的对象是否有这个值,一般需要层层判断,有了可选链操作符,直接使用 ?. 即可,不会报错
const fn = (config) => {
console.log(config.db.a1);
}
fn({
db: {
a1: '111',
a2: '222'
}
})
//如果有这个参数则不会报错
const fn = (config) => {
console.log(config.db.a1);
}
fn()
//如果没有这个参数则报错,只能使用 config&&config.db&&config.db.a1
有了可选链操作符,只需要这样即可
console.log(config?.db?.a1)
动态import
主要用来提升性能,动态import就是不在文件头部使用import导入模块,而是使用import()这个函数在使用的时候才导入,函数的参数就是导入这个模块的路径。
导入执行之后返回的是一个Promise,用then后的回调函数的参数接收此模块
BigInt() 大整数
在整数后面加一个字母n,就是大整型数据类型
let bi = 521n
console.log(bi,typeof bi)
使用 BigInt() 方法,往里面穿一个整型数字,就会转变为大整型数字
let norm = 123
console.log(BigInt(norm))
运用场景是大数值运算,BigInt不能和普通Int做运算
let max = Number.MAX_SAFE_INTEGER
console.log(max)
console.log(max+1)
console.log(max+2)
//此时就会计算丢失精度
console.log(BigInt(max))
console.log(BigInt(max)+BigInt(1))
console.log(BigInt(max)+BigInt(2))
//使用BigInt之后就没有问题
网友评论