JavaScript基础
前端验证,更多功能 -ECMAJavascript -DOM -BOM
JS的特点: -解释型语言 -类似C与java -动态语言 -基于原型的面向对象
Chapter1基本语法
1.1基本规则
1.JS 中严格区分大小写 2.JS中每一条语句以分号结尾, 如果不写分号,浏览器可能会自动添加,可能还会出错 3.JS会忽略多个空格和换行
1.2字面量和变量
字面量(常量):不可改变的值,可以直接使用,但一般不会直接使用 变量:用于保存字面量的值,可以通过变量对字面量进行描述 声明变量: 在js中使用var关键字声明变量 var a; a = 123; 标识符:在JS中所有可自主命名的都称为标识符 如变量名,函数名,属性名 命名一个标识符时要遵循以下规则: 1.标识符可以含字母、数字、_ 、$ 2.不能以数字开头 3.不能是ES中的关键字或保留字 4.标识符一般采用驼峰命名法:首字母小写,每个单词开头字母大写 JS底层保存标识符时,实际上采用的是Unicode编码。
1.3数据类型
1.3.1 String字符串
需要用引号引起来 eg: var str="hello"; 使用单引号或双引号都可以,但不能混用 不能嵌套使用,但可以单引号套双引号,双引号套单引号 在字符串中可使用\作为转义字符表示特殊字符 \n——换行 \t——tab \——\ '——' "——"
1.3.2 Number数值
包括整数和浮点数 可以使用运算符typeof来检查变量的类型 语法:typeof 变量(检查infinity和NaN也会返回number) Number.MAX_VALUE表示最大值 Number.MIN_VALUE表示最小正值 Infinity表示正无穷 NaN 表示Not a Number 整数的运算基本可保证准确,但如果进行浮点数运算可能得到一个不精确的结果 因此不要使用JS进行精确度要求高的运算
1.3.3 Boolean布尔值
布尔值只有两个:true false 使用typeof检查时返回 boolean
1.3.4 Null空值
Null类型的值只有一个: null 专门用于表示一个为空的对象 使用typeof检查时返回object
1.3.5 Undefined未定义值
Undefined类型的值只有一个:undefined 当声明一个变量但没有赋值时,它的值就是undefined 使用typeof检查时返回undefined
1.3.6强制类型转换
将一个数据类型转换成其他数据类型, 主要是转换成String Number Boolean
1.转换成String
方式一:调用被转换数据类型的toString()方法
eg: var b = a.toString();
该方法不会影响到原变量,即a仍是原数据类型,b才是字符串类型
Attention:null和undefined没有toString()方法
方式二:调用String()函数,并将被转换的数据作为参数传递
eg:a = String(a);
使用String()函数做强制类型转换时
对于Number和Boolean实际上就是调用tostring()方法
对于null和undifined会直接将null转换为"null",将undifined转换为"undifined"
2.转换为Number
方式一: 使用Number() 函数
eg:a = Number(a);
字符串-->数字:1.如果是纯数字,直接转换为数字
2.如果字符串中有非数字,转换为NaN
3.如果字符串是空串或全为空格,转换为0
布尔型-->数字:true转换为1;false转换为0
null-->数字:0
undifined-->数字:NaN
方式二:使用parseInt()函数,将a转换为Number
eg:a = 1213px; a = parseInt(a);
可以将一个字符串中有效的整数内容取出来,然后转换为Number
parseFloat()函数与parseint()函数相同,但可以取出浮点数
如果对非String使用会对其先转换为String然后再操作
其他进制的数字:
16进制:var a = 0x123;以0x开头
8进制:var a = 070;以0开头
2进制:var a = 0b10;以0b开头(兼容性不好)
a = parseInt(a,10);指定转换为10进制
3.转换为Boolean
方式一:使用Boolean()函数
number-->boolean:除了0和NaN,其余都是true
string-->boolean:除了空串,其余都是true
null,undifined-->boolean:false
object-->boolean:true
方式二:a = !!a;
1.3.7 Object对象
除上述五种基本类型外,都是对象
1.4运算符
- typeof 获得值的类型,以字符串类型返回
1.4.1算术运算符
+-*/ 对非Number运算会先转换为Number再运算
任何值与NaN运算都得NaN
String + String是将两个字符串连接起来
任何值与String相加都会先转换为String
可以通过加一个空串将类型转换为String(原理与String()函数相同)
可以通过减0(乘除1)将类型转换为Number(原理于Number()函数相同)
%取模运算(取余数) 9%4=1
1.4.2一元运算符
+(正号) -(负号)
对于非Number值会先转换为Number值再取负值
可以对其他类型使用+转换为Number
eg:a=+a;
++(自增) --(自减)
eg: var a = 1; a++; a立刻变为2
后自增(a++),前自增(++a)
a++的值是1,++a的值是2,但a的值都是2
1.4.3逻辑运算符
! 非 : 对布尔值进行取反,如果对非布尔值进行运算会先转换为布尔值
&& 与 : 对符号两侧逻辑与运算,(短路式)如果第一个值为false则不再检查后面的值
|| 或 : 对符号两侧逻辑或运算,(短路式)如果第一个值为true则不再检查后面的值
对于非布尔值与或运算时会先将其转换为布尔值,然后再运算,并返回原值
与运算:如果第一个值为true,则直接返回第二个值;如果第一个值为false,则直接返回第一个值
或运算:如果第一个值为true,则直接返回第一个值;如果第一个值为false,则直接返回第二个值
1.4.4赋值运算符
= 将右侧值赋给左侧
+= -= *= /= %=
1.4.5关系运算符
>大于号,<小于号,>=大于等于号,<=小于等于号 关系成立返回true,否则返回false
对于非数值进行比较时,会转换为Number
任何值和Null比较结果都是false
如果符合两侧都是字符串时,不会将其转换为Number,而是比较其Unicode编码,一位一位依次比较
比较中文没有意义
在字符串中可以使用转义字符输入Unicode编码
\u四位编码
在网页中使用Unicode编码
&#编码(需要使用10进制编码)
== 相等运算符 相等返回true,不等返回false
若类型不同时,会自动进行类型转换(大部分情况都是转换为Number)
undifined衍生自null,因此它们相等判断为true
NaN不与任何值相等,包括它本身
可以通过isNaN()函数来判断一个值是否为NaN,如果是NaN返回true
!= 不等于
也会对类型自动转换
=== 全等 用于判断两个值是否全相等,不会做类型转换
!== 不全等 用于判断两个值是否不全等,与!=相似,但不会做类型转换
1.4.6条件运算符(三元运算符)
条件表达式?语句1:语句2;
条件表达式为true时执行语句1并返回执行结果,否则执行语句2并返回执行结果
eg: var max = a > b ? a: b;
eg: var max = a>b?(a>c?a:c):(b>c?b:c); 获取a b c中最大值(不推荐)
如果条件表达式结果为非布尔值,会转换为布尔值
1.4.7逗号运算符
, 可以使用逗号同时声明多个变量(并赋值)
eg: var a,b,c;
eg: var a=1,b=2,c=3;
1.4.8运算符的优先级
- 在运算符优先级表中越靠上,优先级越高,同一优先级从左往右运算
- 可以通过()使优先级变高
1.5代码块
- 在JS中可以使用{}来为语句进行分组,一个{}中的语句我们也成为一个代码块
- 代码块只具有分组的作用,没有其他的用途
- 一个代码块中的语句,要么全部执行,要么全不执行
- JS的程序是从上到下一行一行执行的,通过流程控制语句可以控制程序执行流程
1.5.1条件判断(if语句)
语法一:if(条件表达式){
语句
}
语法二:if(条件表达式){
语句1
}else{
语句2
}
语法三:if(条件表达式){
语句1
}else if(条件表达式){
语句2
}else{
语句3
}
1.5.2条件分支语句(switch语句)
语法:switch(条件表达式){
case 表达式 :语句1;break;
case 表达式 :语句2;break;
......
default :语句n;
}
条件表达式与case后表达式进行全等比较
1.5.3循环语句
1.5.3.1while循环
语法一:
while(条件表达式){
语句...
}
当表达式为true时执行循环体,直至表达式为false
语法二:
do{
语句...
}while(条件表达式)
先执行循环体,再判断条件表达式,若为true重复循环体
1.5.3.2for循环
语法:
for(初始化表达式;条件表达式;更新表达式){
语句...
}
三部分均可省略,也都可以写在外部
1.5.3.3break和continue
break:用于退出switch或循环语句(不能用于退出if语句)
label:循环语句
使用break语句时,可以在break后跟着一个label,这样break会结束指定的循环而非最近的循环
continue:用于跳过当前次循环,同样默认只会对最近的循环起作用
同样可以用label指定跳出的循环
Chapter2对象
2.1对象的介绍
2.1.1对象的基本操作
对象属于一种复合的数据类型,在对象中可以保存多个不同数据类型的属性 对象的分类: 1.内建对象 - 由ES标准中定义的对象,在任何ES的实现中都可以使用 - 如Math,String,Number,Boolean,Function,Object... 2.宿主对象 - 由JS的运行环境提供的对象,目前来讲主要时指由浏览器提供的对象 - 如BOM DOM 3.自定义对象 - 由开发人员自己创建的对象
创建对象 使用new关键字(调用构造函数constructor--专门用来创建对象的函数) eg : var obj = new Object(); 使用typeof检查时会返回object 在对象中保存的值称为属性
-
向对象添加属性
对象.属性名 = 属性值; eg: obj.name = "孙悟空";
-
读取对象中的属性 语法: 对象.属性名 如果读取对象中没有的属性,不会报错而是返回undefined
-
删除对象的属性 语法: delete 对象.属性名
- 属性名:对象的属性名不强制要求遵守标识符的规范,但仍建议按照规范命名 如果要使用特殊的属性名,不能采用.的方式来操作 需要采用 对象["属性名"] = 属性值; 使用[]去操作属性值更加灵活,在[]中可以直接传递一个变量,变量值是多少就会读取哪个属性 eg: var n = '123'; obj[n] = 'hello' . new [] 的优先级最高
- 属性值:可以是任意的数据类型,甚至可以是一个对象
- in运算符:检查一个对象中是否含有指定的属性,如果有返回true,如果没有返回false 语法: "属性名" in 对象
2.1.2基本数据类型和引用数据类型
基本数据类型: String Number Boolean Null Undefined 引用数据类型: Object
JS 中的变量都是保存在栈内存中的
- 基本数据类型的值直接在栈内存存储, 值与值之间独立存在,修改一个变量不会影响其他的变量
- 对象是保存到堆内存中的,每创建一个新的对象,就会在堆内存中开辟一个新的空间,而变量保存的是对象的内存地址(对象的引用),如果两个变量保存的是同一个对象引用,当一个提供变量修改属性时,另一个也会受到影响
- 当一个变量的值修改为null,其他指向同一对象的变量不会受到影响
- 两个对象属性全部相同,但他们也仍是两个对象,不会相等(双胞胎是两个人) 比较两个引用数据时,比较的是对象的内存地址
2.1.3对象字面量
使用对象字面量可以直接在创建对象时指定对象中的属性 语法: {属性名:属性值,属性名:属性值,...}
var obj={
name:"猪八戒",
age:28,
gender:"男"
};
- 属性名可以加引号也可以不加,但如果使用特殊的属性名就需要加引号
- 属性名和属性值是一组一组名值对结构,属性名与属性值之间用:连接,多个名值对之间用,隔开,最后一个名值对后不写逗号
2.2函数
2.2.1函数的简介
- 函数也是一个对象(万物皆对象)
- 函数可以封装一些功能(代码),在需要时可以执行。
创建一个函数对象 var fun = new Function();
- 可以将要封装的代码以字符串的形式传递给构造函数
- 封装到函数中的代码不会立即执行,会在调用时执行
调用函数语法: 函数对象() 当调用时,函数中封装的代码会按照顺序执行
使用函数声明来创建一个函数 语法: function 函数名([形参1,形参2,...]){语句...} 使用函数表达式来创建一个函数 语法: var fun3=function([]){语句...}
2.2.2函数的参数与返回值
function sum(a,b){
console.log(a+b);
var d = a + b;
return d;
}
sum(1,2);
var result = sum(7,8);
实参会赋值给形参
-
调用函数时,解析器不会检查实参的类型
-
调用函数时,解析器不会检查实参的数量
- 多余的实参不会被赋值
- 如果形参的数量多余形参,没有对应实参的形参将是undefined
-
函数中return以后的语句不会执行
-
如果return后不跟任何值或函数中没有return语句,则会返回undefined
-
实参可以是任何数据类型,也可以是对象,也可以是函数
-
fun() 调用函数,相当于使用函数的返回值
-
fun 函数对象,相当于直接使用函数对象
2.2.3返回值的类型
- 使用return可以结束整个函数(相比于break和continue)
- 返回值可以是任意的数据类型,也可以是一个对象,也可是是一个函数
匿名函数 function(){语句...} 立即执行函数 (function(){语句...})(); 往往只会执行一次
2.2.4 方法
- 对象的属性值可是是任何数据类型,也可以是函数
- 如果一个函数作为一个对象的属性保存,那我称这个函数是这个对象的方法
- 调用这个函数就叫做调用对象的方法
- 但是这只是名称上的区别,没有本质的区别
枚举对象中的属性 语法:for (var 变量 in 对象){语句...} for ... in 语句:对象中有几个属性,循环体就会执行几次 每次执行时,会将一个属性名赋值给变量
for( var n in obj){
console.log(n);
}
2.2.5作用域
-
作用域:变量作用的范围
-
在JS中一共有两种作用域
-
全局作用域:
-
直接编写在script 标签中的JS代码都在全局作用域
-
全局作用域在页面打开时创建,在页面关闭时销毁
-
全局作用域中有一个全局对象window,代表一个浏览器的窗口,由浏览器创建,我们可以直接使用
-
在全局作用域中:
- 创建的变量都会作为window对象的属性保存
- 创建的函数都会作为window对象的方法保存(因此方法与对象没有区别)
-
-
函数作用域:
- 调用函数时创建函数作用域,函数执行完毕以后,函数作用域销毁
- 每调用一次函数就会创建一个新的函数作用域,它们之间互相独立
- 在函数作用域中可以访问到全局变量 而在全局作用域中无法访问到函数作用域的变量 -当在函数作用域中操作一个变量时,它会先在自身作用域中寻找,如果有就直接使用;如果没有则向上一级作用域中寻找 -在函数中要访问全局变量可以使用window对象
- 在函数作用域中也有声明提前:使用var关键字声明的会最先执行
- 在函数中不使用var声明的变量,都会成为全局变量
- 定义形参就相当于在函数中声明了一个变量
-
-
变量声明提前:
- 使用var关键字声明的变量,会在所有代码执行前被声明(但不会赋值)
- 如果不使用var关键字,则变量不会被声明提前
-
函数声明提前:
- 使用函数声明创建的函数,可以在创建前使用(在所有代码创建前被创建)
- 使用函数表达式创建的函数不会被声明提前,因此不能在声明前使用
2.2.6this
-
解析器在调用函数时每次都会向函数内部传递进一个隐含的参数,这个参数就是this
-
this指向的是一个对象,这个对象我们称为函数执行的上下文对象
-
根据函数调用方式的不同,this会指向不同的对象
- 以方法调用时,this是调用方法的那个对象
- 以函数形式调用时,this永远都是window
2.2.7使用工厂方法创建对象
//创建一个可以创建对象的函数
function createPerson(name,age,gender){
//创建一个新的对象
var obj = new Object();
//向对象中添加属性
obj.name = name;
obj.age = age;
obj.gender = gender;
obj.sayName = function(){
alert(this.name);
}
//将新建的对象返回
return obj;
}
//调用createPerson()函数创建对象
var obj1=createPerson("白骨精",28,"女");
2.2.8构造函数
-
构造函数就是一个普通的函数,创建方式与普通函数没有什么区别
-
构造函数函数名首字母一般大写
-
构造函数与普通函数的区别在于调用方式不同:
- 普通函数直接调用,构造函数需要使用new关键字调用
-
构造函数执行流程: 1.立刻创建一个新建对象 2.将新建的对象设置为函数中的this,在构造函数中可以使用this来引用新建的对象 3.逐行执行函数中的代码 4.将新的对象作为返回值返回
-
使用同一个构造函数创建的对象,称为一类对象,因此也将一个构造函数称为一个类
-
通过一个构造函数创建的对象,称为该类的实例(per是Person类的实例)
function Person(name,age,gender){
this.name = name;
this.age = age;
this.gender = gender;
this.sayName = function(){
alert(this.name);
}
}
var per = new Person("玉兔",16,"女");
- 使用instanceof可以检查一个对象是否是一个类的实例 语法: 对象 instanceof 构造函数 如果是,则返回true,否则返回false eg: console.log(per instanceof Person) //true
- 所有对象都是Object的后代,所以任何对象在instanceof检查时都会返回true
- 以构造函数的形式调用时,this就是新创建的对象
构造函数优化
function Person(name,age,gender){
this.name = name;
this.age = age;
this.gender = gender;
this.sayName = fun;
}
}
//将sayName函数在全局中定义,大大节省了空间
//但将函数定义在全局作用域,污染了全局作用域的命名空间,而且也不安全
function fun(){
alert(this.name);
}
var per = new Person("玉兔",16,"女");
2.2.9原型
-
我们创建的每一个函数,解析器都会向函数中添加一个属性prototype(显示原型)
-
这个属性对应着一个对象,就是原型对象(默认是空对象)
-
如果函数作为普通函数调用,prototype没有任何作用
-
如果函数作为构造函数调用时,它所创建的对象中都会有一个隐含的属性指向该构造函数的原型对象
- 每个实例对象都有一个*proto(隐式原型)*属性
- 对象的隐式原型即为其构造函数的显示原型即per.proto = = = Person.prototype
-
原型对象就相当于一个公共区域,同一个类的所有实例都可以访问到这个原型对象
- 可以将对象中共有的内容同一设置到原型对象中
- 当我们访问对象一个属性或方法时,会先在对象自身寻找,如果有则使用,没有则去原型中寻找
-
原型对象中有一个constructor属性指向函数对象
function Person(name,age,gender){
this.name = name;
this.age = age;
this.gender = gender;
}
Person.prototype.sayName = function(){
alert(this.name);
}
var per = new Person("玉兔",16,"女");
因此创建构造函数时,可以将对象共有的属性和方法统一添加到构造函数的原型对象中,这样就不用分别为每个对象添加,也不会影响到全局作用域,就可以使每个对象都具有这些属性和方法
- 使用 in 检查对象中是否含有某个属性时,如果对象中没有但原型中有,也会返回true
- 使用per.hasOwnProperty()检查对象自身中是否含有该属性
- 原型对象也是对象,它也有原型
- 自身中没有去原型中找,原型中没有去原型的原型找,直到Object的原型(也就是Object的原型没有原型)
- 在Object的原型中仍没找到,则返回undefined
- 直接在页面中打印一个对象时,实际上是输出对象的toString()方法的返回值
- 如果不希望输出对象时输出[object Object],可以为对象添加一个toSting()方法
function Person(name,age){
this.name = name;
this.age = age;
}
var per = new Person("Bob",40);
Person.prototype.tostring = function(){
return "Person[name="+this.name+",age="+this.age]";
}
console.log(per);
垃圾回收(GC)
- 处理程序运行过程中产生的垃圾
- 当一个对象没有任何变量或属性对它进行引用,此时我们无法操作它,它就是一个垃圾
- 垃圾过多会占用大量内存,导致程序运行过慢
- 在JS中拥有自动垃圾回收机制,会自动从内存中销毁垃圾
- 我们只需要将不再使用的对象设置为null
2.3数组Array
2.3..1数组简介
- 数组也是一个对象,和普通对象功能类似
- 不同的是普通对象是使用字符串作为属性名,而数组使用数字作为索引(index)
- 数组的存储性能更好
- 使用typeof检查时会返回object
- 数组中的元素可以是任意数据类型包括对象
//创建数组对象
var arr = new array();
//向数组中添加元素
arr[0] = 10;
//读取数组中的元素(如果读取不存在的索引,会返回undefined)
console.log(arr[0]);
//获取数组长度
console.log(arr.length);
//修改length
//如果修改的length大于原长度,多余部分空出来;如果小于原长度,多余元素会被删除
arr.length=10;
console.log(arr.length);
//向数组的最后一个位置添加元素
arr[arr.length]=70;
2.3.2数组字面量
// 使用字面量创建数组
var arr = [1,2,3,4,5];
console.log(arr[0]);
//使用构造函数创建数组时,也可以同时添加元素
var arr2 = new Array(10,20,30);
//创建一个只有元素10的数组
arr=[10];
//创建一个长度为10的数组
arr2 = new Array(10);
2.3.3数组的方法
push()
- 向数组末尾添加一个或多个元素,并返回数组的新长度
var arr=['alice','Bob','cindy'];
var result = arr.push('Jack','lucy');
console.log(arr); //'alice','bob','cindy','jack','lucy'
console.log(result);//5
pop()
- 删除数组的最后一个元素,并返回删除的元素
var arr=['alice','Bob','cindy'];
var result = arr.pop();
console.log(arr); //'alice','bob'
console.log(result);//'cindy'
unshift()
- 向数组开头添加一个或多个元素,并返回新的数组长度
var arr=['alice','Bob','cindy'];
var result = arr.unshift('Jack','lucy');
console.log(arr); //'jack','lucy','alice','bob','cindy'
console.log(result);//5
shift()
- 删除数组的第一个元素,并将被删除的元素返回
var arr=['alice','Bob','cindy'];
var result = arr.shift();
console.log(arr); //'bob','cindy'
console.log(result);//'alice'
2.3.4数组的遍历
var arr=['alice','Bob','cindy'];
for(var i=0;i<arr.length;i++){
console.log(arr[i]);
}
var arr=['alice','Bob','cindy'];
var result = arr.pop();
console.log(arr); //'alice','bob'
console.log(result);//'cindy'
function Person(name,age,gender){
this.name = name;
this.age = age;
this.sayName = function(){
alert(this.name);
}
}
var per1 = new Person("玉兔",16);
var per2 = new Person("嫦娥",26);
var per3 = new Person("二郎神",36);
var per4 = new Person("猪八戒",36);
var per5 = new Person("红孩儿",6);
var perArr = [per1,per2,per3,per4,per5];
回调函数:由我们创建不由我们调用的函数
- forEach()方法需要一个函数作为参数
- 数组中有几个元素就会执行几次,每次执行时浏览器会将遍历到的元素以实参形式传递进来,可以定义形参来读取这些内容
- 浏览器在回调时会传递三个参数:第一个参数是当前正在遍历的元素;第二个参数是当前正在遍历的元素的索引;第三个参数就是正在遍历的数组
arr.forEach(function(){
console.log("hello");
});
2.3.5数组的其他方法
slice()
- 从数据中提取指定元素
- 该方法不会影响原数组,而是将截取的元素封装到一个新的数组中返回
- 参数: 1.截取开始的位置索引,包含开始索引 2.截取结束的位置索引,不包含结束索引 第二个参数可以不写,表示从开始位置截取到最后 索引也可以传负值 -1是倒数第一个 -2是倒数第二个
var arr=['alice','Bob','cindy'];
var result = arr.slice(1,2);
console.log(arr); //'alice','bob','cindy'
console.log(result);//'bob,cindy'
splice()
- 用于删除数组中的指定元素
- 会影响原数组,返回被删除的元素
- 参数: 1.表示开始位置的索引 2.表示删除的数量 3.第三个参数及以后可以传递一些新的元素插入到开始索引的前面
var arr=['alice','Bob','cindy'];
var result = arr.splice(1,1);
console.log(arr); //'alice,cindy'
console.log(result);//'bob'
concat()
- 将两个或多个数组连接并将新的数组返回(不会对原数组产生影响)
join()
- 将数组转换为一个字符串并返回(不会对原数组产生影响)
- 可以指定一个字符串作为参数以指定元素间的连接符(默认为逗号)
reverse()
- 反转数组(会直接对原数组产生影响)
sort()
- 对数组进行排序(会直接对原数组产生影响)
- 默认按照Unicode编码排序(小的在前,大的在后)
- 但是对于纯数字数组也会按照Unicode编码排序
- 可以通过回调函数指定排序规则,浏览器会根据回调函数返回值来决定元素位置 如果返回大于0的值,则交换两元素位置;否则,元素位置不变
arr = [5,4,3];
arr.sort(function(a,b){
// 升序排列
return a-b;
//降序排列
return b-a;
})
filter()
- 创建一个新数组, 其包含通过所提供函数实现的测试的所有元素
const words = ['spray', 'limit', 'elite', 'exuberant', 'destruction', 'present'];
const result = words.filter(word => word.length > 6);
console.log(result);
2.3.6call()和apply()
- 这两个都是函数对象的方法,需要通过函数对象来调用
- 当对函数调用call()和apply()可以将一个对象指定为第一个参数,此时这个对象将会成为函数执行时的this
- call()方法可以将实参在对象之后传递
- apply()方法需要将实参封装到数组中统一传递
2.3.7arguments
在调用函数时,浏览器每次都会传递进两个隐含的参数: 1.函数的上下文对象this 2.封装实参的对象arguments - arguments是一个类数组对象,它也可以通过索引来操作数据,也可以获取长度 - 在调用函数时,我们传递的实参都会在arguments中保存 - arguments.length可以用来获取实参的长度 - 即使不定义形参也可以通过arguments来使用实参 - arguments[0]表示第一个实参... - 它里面有一个属性叫做callee,对应一个函数对象,就是当前正在指向函数的对象
2.4其他对象
2.4.1Date对象
- 在JS中使用Date对象来表示一个时间
//如果直接使用构造函数创建一个Date对象,则会封装为当前代码执行的时间
var d = new Date();
//创建一个指定的时间对象,需要在构造函数中传递一个表示时间的字符串作为参数
//日期的格式: 月/日/年 时:分:秒
var d2 = new Date("03/13/2022 17:52:52");
//获取当前日期对象是几号
var date = d2.getDate();
//获取当前日期对象是周几(周日返回0)
var day = d2.getDay();
//获取当前月份(会返回0-11的值,11表示12月)
var month = d2.getMonth() + 1 ;
//获取当前年份
var year = d2.getFullYear();
//获取当前日期的时间戳(1970年1月1日0时0分0秒到现在的时间差,单位毫秒)
var time = d2.getTime();
//获取当前的时间戳
time = Date.now();
console.log(d);
2.4.2Math对象
- Math和其他对象不同,它不是一个构造函数
- Math属于一个工具类,它封装了数学运算相关的属性和方法
//输出圆周率PI
console.log(Math.PI);
//绝对值Math.abs();
console.log(Math.abs(-1));//1
//向上取整Math.ceil();
console.log(Math.ceil(1.111));//2
//向下取整Math.floor();
// 四舍五入取整Math.round()
//生成一个0-1 的随机数(不包含0和1)
var a = Math.random();
// 生成一个x-y之间的随机整数
var b = Math.round(Math.random()*(y-x)+x);
//max(); min();获得多个数中最大/最小值
//Math.pow(x,y);获得x^y
//Math.sqrt();开方
2.4.3包装类
-
在JS中为我们提供了3个包装类,提供这三个包装类可以将基本数据类型的数据转换为对象
-
String()
- 将基本数据类型转换为String对象
-
Number()
- 将基本数据类型转换为Number对象
-
Boolean()
- 将基本数据类型转换为 Boolean对象
但实际上开发中并不会使用基本数据对象 当我们对基本数据类型调用属性和方法时,浏览器会临时使用包装将其转换为对象,然后再调用
2.4.4String的方法
- 在底层,字符串是以字符数组的形式保存的
length
- 获取字符串的长度
charAt()
- 返回字符串中指定位置的字符,arr.charAt(6)与arr[6]相同
charCodeAt()
- 获取指定位置字符的字符编码(Unicode编码)
fromCharAt()
- 根据字符编码获取字符
result = String.fromCharAt(20045);// 乍
concat()
- 连接两个或多个字符串,作用和+一样
result = str.concat("hi","bye");
indexof()
- 检索一个字符串中是否含有指定内容,如果有则返回其第一次出现的索引;没有则返回-1,可以指定第二个参数为开始查找的位置
result = str.indexof("p");
result = str.indexof("p",1);// 从str[1]开始检索
lastIndexof()
- 与indexof()用法相同,优先返回最后出现的索引
slice()
- 与数组用法完全相同
substring()
- 与slice()基本相同,但参数不能传递负值(如果传负值默认为0),还会自动调整参数位置使小数在前大数在后
split()
- 将字符串拆分为数组
- 需要一个字符串作为参数,根据参数去拆分数组
- 如果参数为空串,则会将字符串中每个字符作为一个元素
str='abc,bcd,efgh';
result = str.split(",");
console.log(result[0]);//'abc'
toUpperCase()
- 将字符串全部转换为大写并返回(不会影响原字符串)
toLowerCase()
- 将字符串全部转换为小写并返回(不会影响原字符串)
2.4.5正则表达式
- 用于定义一些字符串的规则
- 计算机可以根据正则表达式来检查一个字符串是否符合规则或者将符合规则的字符串提取出来
- 使用typeof检查正则对象,会返回object
创建正则表达式的对象 语法: var 变量 = new RegExp("正则表达式","匹配模式"); 正则表达式的方法: test() 如果符合返回true,否则返回false 匹配模式: - i:忽略大小写 - g:全局匹配模式
//检查字符串是否包含某字符串(严格区分大小写)
var reg = new RegExp('a');
console.log(reg.test('abc'));//true
使用字面量创建正则表达式:var 变量 = /正则表达式/匹配模式;
- 使用字面量创建更加简单,使用构造函数创建更加灵活
//检查字符串是否包含a或b
var reg = /a|b/i;
console.log(reg.test('abc'));//true
//检查字符串是否包含字母
//[]里也是或的关系 [a-z]表示任意小写字母;[A-Z]表示任意大写字母
reg = /[A-z]/;
//检查字符串是否包含abc或adc或aec
reg = /a[bde]c/;
//[^ab] 检查字符串是否包含除了ab以外的其他字符
2.4.6String和正则表达式相关的方法
split()
- 可以传递一个正则表达式作为参数,这样会根据正则表达式去拆分字符串
- 默认全局匹配
str='abc,bcd,efgh';
result = str.split(/[A-z]/);
console.log(result[0]);//','
search()
- 搜索字符串中是否含有指定内容
- 如果搜索到指定内容会返回第一次出现的索引,否则返回-1
- 可以接受一个正则表达式作为参数
- 不能设置全局匹配,只会查找第一个
str='abc,bcd,efgh';
result = str.search(/a[bde]c/);
console.log(result[0]);//0
match()
- 可以根据正则表达式从一个字符串中将符合条件的内容提取出来
- 默认情况下只会找到第一个符合要求的内容,找到以后就停止检索,但可以设置为全局匹配模式,就会匹配到所有内容
- 可以为一个正则表达式设置多个匹配模式,且顺序无所谓
- match()会将匹配到的内容封装到一个数组中返回,即使只查询到一个结果
str='1a2vg34jkhjw';
//找到字符串中的字母
result = str.match(/[a-z]/gi);
console.log(result);//'a,v,g,j,k,h,j,w'
replace()
- 可以将字符串中指定内容替换为新的内容
- 参数: 1.被替换的内容,可以接受一个正则表达式 2.新的内容
- 默认只替换第一个,可设置全局匹配模式
str='1a2vg34jkhjw';
//找到字符串中的字母
result = str.replace(/[a-z]/gi,'*');
console.log(result);//'1*2**34*****'
2.4.7正则表达式的语法
- 通过量词可以设置一个内容出现的次数
var reg = /a{n}/; //连续n个a
reg = /(ab)(3) /; //ababab
reg = / ab{3}c/; //abbbc
reg = /ab{1,3}c/;// abc|abbc|abbbc {m,n} m-n次 {m,}至少m次
reg = /ab+c/; //b+相当于b{1,}
reg = /ab*c/; //b* 有没有b 都行,相当于b{0,}
reg = /ab?c/; //b?相当于b{0,1}
//检查一个字符串是否以a开头
reg = /^a/; //^表示开头,匹配开头a;$表示结尾
reg = /a$/; // 表示以a结尾 /^a$/只能是一个a
reg = /^a|a$/; //表示以a开头或结尾
//检查是否为手机号码
var str = "19171172391";
var reg = /^1 [3-9] [0-9]{9}$/;
console.log(reg.test(str));
- · 表示任意字符,因此要表示单纯的 · 需要使用转义字符\
//检查是否含有·
var str = "abc.c";
var reg = /./;
console.log(reg.test(str));//true
reg = /\/; // 需要使用\表达反斜杠时要用\
attention:用构造函数创建正则表达式时由于正则表达式是字符串,需要用\作为转义字符
- \w:任意字母数字和下划线 [A-z0-9_]
- \W:除\w
- \d:任意数字
- \D:除了\d
- \s:空格
- \S:除了\s
- \b:单词边界,如\b child\b检测是否含有独立单词child
- \B:除了\b
- /^\s |\s&/g 匹配开头或结尾的空格
邮件的正则表达式
任意字母数字下划线 .任意字母数字下划线(可有可无) @ 任意字母数字 . 任意字母(2-5位).任意字母(2-5位) /^\w{3,} (.\w+)* @ [A-z0-9]+(.[A-z]{2,5}){1,2}$/;
Chapter3DOM
3.1DOM的简介
-
Document Object Model文档对象模型
-
使我们可以通过js操作网页
-
文档:HTML网页文档
-
对象:将网页中每一个部分转换为一个对象
-
模型:使用模型表示对象之间的关系,方便我们获取对象
-
节点Node:构成网页的最基组成部分,每一个部分都是一个节点
- 文档节点:整个html文档
- 元素节点:html文档中的html标签
- 属性节点:元素的属性
- 文本节点:html标签中的文本内容
| 节点的属性 | nodeName | nodeType | nodeValue |
|---|---|---|---|
| 文档节点 | #document | 9 | null |
| 元素节点 | 标签名 | 1 | null |
| 属性节点 | 属性名 | 2 | 属性值 |
| 文本节点 | #text | 3 | ※文本内容 |
- 浏览器已经为我们提供了文档节点对象,这个对象是window属性 可以在页面中直接使用,文档节点代表的是整个网页
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<button id="btn"></button>
<script type="text/javascript">
var btn = document.getElementById("btn");
</script>
</body>
</html>
3.2事件
- 事件就是用户和浏览器之间的交互行为,如点击按钮,鼠标移动,关闭窗口等
- 我们可以在时间的属性中设置一些js代码,当时间执行时代码就会执行
- 可以为按钮的对应事件绑定处理函数来响应事件
var btn = document.getElementById("btn");
btn.onclick = function(){
alert("别点我!");
};
- 浏览器在加载时自上向下加载,读取一行就运行一行
- onload事件会在整个页面加载之后执行
- 因此要将js代码统一写到window.onload = function(){}内
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script type="text/javascript">
window.onload = function(){
var btn = document.getElementById("btn");
btn.onclick = function(){
alert("别点我!");
};
};
</script>
</head>
<body>
<button id="btn"></button>
</body>
</html>
3.3DOM查询
3.3.1获取元素节点
- 通过document对象调用 1.getElementById() : 通过id属性获取一个元素节点对象
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script type="text/javascript">
window.onload = function(){
var btn = document.getElementById("btn");
btn.onclick = function ()
{
var bj = document.getElementById("bj");
// innerHTML 通过这个属性可以获取到元素内部的html代码
//innerHTML 不能获取自结束标签的内部(空)
alert(bj.innerHTML);
};
};
</script>
</head>
<body>
<button id="btn">按钮</button>
</body>
</html>
2.getElementsByTagName() : 通过标签名获取一组元素节点对象
- 以数组形式返回
3.getElementsByName() : 通过name属性获取一组元素节点对象
- 以数组形式返回
- 如果需要读取元素节点属性,直接使用元素.属性名读取,但class属性需要使用*.className*
3.3.2获取元素节点的子节点
- 通过具体的元素节点调用 1.getElementByTagName() 2.childNodes : 获取包括文本节点在内的所有节点(包含空白) 3.children : 获取当前元素的子元素 4.fisrtChild : 获取当前节点的第一个子节点(包含空白) 5.firstElementChild : 获取当前元素的第一个子元素 6.lastChild 7.parentNode : 获取当前元素的父节点 8.previousSibling : 当前元素的前一个兄弟节点(可能是空白) 9.previousElementSibling : 当前元素的前一个兄弟元素 10.nextSibling
3.3.3绑定单击响应函数
- 定义一个函数,专门用于为指定元素绑定单击响应函数
- 参数:idStr 要绑定的对象的id
myClick(idStr,function(){});
- innerText : 元素内部的文本内容,与innerHTML类似,但它会自动将html标签去除
3.3.4DOM查询的其他方法
-
在document中有一个属性body,保存的就是body的引用
-
document.documentElement保存的是html根标签
-
document.all保存的是页面里所有元素
-
根据元素class属性值查询一组元素节点对象
-
document.querySelector() 以一个选择器字符串为参数,查询符合选择器的元素
- 使用此方法只会返回一个元素,如果满足条件的元素有多个,只会返回第一个
-
document.querySelectorAll() 可以返回满足条件的所有元素(封装到数组中返回)
var body = document.body;
var heml = document.documentElement;
var all = document.all;
var all1 = document.getElementsByName("*");
//获取class为box1的元素组
var box1 = document.getElementsByClassName("box1");
//获取box1中的所有div元素
var div = document.querySelector(".box1 div");
3.3.5dom的增删改
appendChild()
- 向一个父节点中添加一个子节点,用法: 父节点.appendChild(子节点);
removeChild()
- 删除子节点, 用法: 父节点.removeChild(子节点);
replaceChild()
- 替换子节点, 用法: 父节点.replaceChild(新节点,旧节点);
insertBefore()
- 在指定子节点之前插入新的子节点, 用法: 父节点 .insertBefore(新节点,旧节点);
createElement()
- 创建元素节点对象,需要标签名作为参数,将会根据标签名创建对象,并将创建好的对象返回
createTextNode()
- 创建文本节点对象,需要文本内容作为参数,将会根据文本内容创建对象并将建好的节点返回
var li = document.createElement("li");
var gzText = document.createTextNode("广州");
li.appendChild(gzText);
var city = document.getElementbyId("city");
city.appendChild(li);
//子节点.parentNode.removeChild(子节点);
bj.parentNode.removeChilde(bj);
//可以用修改innerHTML的内容实现增删改(**两种方式结合使用**)
city.innerHTML += <li>广州</li> ;
3.3.6操作内联样式
-
语法: 元素.style.样式名 = 样式值 (样式值要为字符串)
-
样式名如果含 - ,修改为驼峰式命名(如background-color修改为backgroundColor)
-
通过style属性设置的样式都是内联样式,内联样式的优先级很高但不如 ! importent
-
读取样式
- 语法: 元素.style.样式名
- 通过style属性读取的也是内联样式,而非样式表中样式
3.3.7获取元素的样式
- 语法: getComputerStyle(box1,null);
- window的对象,可以直接使用
- 需要两个参数: 1.要获取样式的元素 2.传递一个伪元素,一般为null
- 返回一个对象,对象封装了当前元素对应的样式
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
#box1 {
width: 100px;
height: 100px;
background-color: aqua;
}
</style>
<script>
window.onload = function(){
var box1 = document.getElementById("box1");
var btn1 = document.getElementById("btn1");
btn1.onclick = function () {
var obj = getComputedStyle(box1, null);
alert(obj.backgroundColor); //rgb(0,255,255)
alert(obj.width); //100px
};
};
</script>
</head>
<body>
<div id="box1"></div>
<button id="btn1">click</button>
</body>
</html>
3.3.8其他样式的相关属性
client
-
clientWidth
-
clientHeight
- 获取元素的可见宽度或高度
- 获取的属性值不带px,返回的是数值,可以直接进行计算
- 宽度和高度包括内容区和内边距
- 属性只可读,不可修改
window.onload = function () {
var box1 = document.getElementById("box1");
var btn1 = document.getElementById("btn1");
btn1.onclick = function () {
var cw = box1.clientWidth;
var ch = box1.clientHeight;
alert(cw); //100
alert(ch); //200
};
};
offset
-
offsetWidth
-
offsetHeight
- 获取元素的整个宽度或高度,包括内容区,内边距和边框
-
offsetParent
- 获取当前元素的定位父元素
- 获取离他最近的开启定位的祖先元素,若所有祖先元素都未开启,则返回body
-
offsetLeft
- 当前元素相对于其定位父元素的水平偏移量
-
offsetTop
- 当前元素相对于其定位父元素的垂直偏移量
scroll
-
scrollHeight
-
scrollWidth
- 获取整个滚动区域的高度宽度
-
scrollLeft
-
scrollTop
- 当前滚动条距上端距离 当滚动到底时,满足scrollHeight - scrollTop == clientHeight
-
onscroll
- 该事件会在元素的滚动条滚动时触发
3.4事件对象
- 当事件的响应函数被触发时,浏览器每次都会将一个事件对象作为实参传递进响应函数
- 在事件对象中封装了当前事件相关的一切信息,比如:鼠标的坐标,键盘哪个值被按下,鼠标滚轮滚动的方向等
- onmousemove : 在鼠标移动时触发
- clientX 可以获取鼠标指针的水平坐标(在当前可见窗口的坐标)
- clientY 可以获取鼠标指针的垂直坐标(在当前可见窗口的坐标)
- pageX 可以获取鼠标相对于当前页面的坐标
- pageY
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
#areaDiv{
width: 400px;
height: 300px;
border: 1px black solid;
}
</style>
<script>
window.onload = function(){
var area = document.getElementById("areaDiv");
area.onmousemove = function (event) {
var x = event.clientX;
var y = event.clientY;
show.innerHTML = "x=" + x + ",y=" + y;
};
};
</script>
</head>
<body>
<div id="areaDiv"></div>
<div id="show"></div>
</body>
</html>
3.5事件的冒泡(Bubble)
- 当后代元素事件被触发时,祖先元素的相同事件也会被触发(事件的向上传导)
- 在开发中大部分冒泡是有益的,如果不希望事件发生冒泡,可以通过事件对象来取消冒泡
- 语法 :event.cancelBubble = true;
3.6事件的委派
- 将事件的响应函数统一绑定给共同的祖先元素,当后代元素的事件触发时,会一直冒泡到祖先元素,从而通过祖先的响应函数处理事件
- 事件的委派利用了冒泡,通过委派可以减少事件绑定次数,提高程序性能
- 响应函数中的this是响应函数的绑定对象
- 响应函数事件对象的target属性是触发事件的对象(谁触发,target就是谁)
3.7事件的绑定
- 对象.事件 = function(){} 只能同时为一个对象的一个事件绑定一个响应函数
- 若要为事件同时绑定多个响应函数,则需使用addEventListener()
- 当使用addEventListener()绑定多个函数时,会按照绑定顺序依次触发
- addEventListener()中的this是绑定事件的对象
- 参数: 1.事件的字符串(不要on) 2.回调函数,当事件触发时该函数会被调用 3.是否在捕获阶段触发事件,(布尔值),一般传false
window.onload = function(){
var btn =document.getElementById("btn1");
btn.addEventListener("click",function(){
alert(1);
},false);
btn.addEventListener("click",function(){
alert(2);
},false);
btn.addEventListener("click",function(){
alert(3);
},false);
};
3.8事件的传播
1.捕获阶段 2.目标阶段 3.冒泡阶段
- 如果希望在捕获阶段执行响应函数,可以将addEventListener()的第三个参数设为true
3.9鼠标滚轮事件
window.onload = function () {
var box1 = document.getElementById("box1");
box1.onmousewheel = function (event) {
if (event.wheelDelta > 0) {
box1.style.height = box1.clientHeight - 10 + "px";
} //获取滚轮方向,不看大小只看正负
else {
box1.style.height = box1.clientHeight + 10 + "px";
}
return false;
//当滚轮滚动时,如果浏览器有滚动条,也会随之滚动,如果不希望发生,可以取消
};
};
3.10键盘事件
window.onload = function () {
//键盘事件一般都会绑给可以获取焦点的对象或是document
//键盘事件: onkeydown按键按下 , 如果按着按键不松手会一直触发
//onkeyup 按键松起
document.onkeydown = function (event) {
console.log("down");
//可以通过keyCode获得按键的编码
if (event.keyCode == 89) {
console.log("y");
}
};
//altKey alt是否被按下,若按下返回true
//ctrlKey
//shiftKey
document.onkeyup = function () {
console.log("up");
};
var input = document.getElementsByTagName("input")[0];
input.onkeydown = function (event) {
console.log("down");
// return false;
//取消默认行为,输入内容不会在文本框中显示
//不能键入数字
if(event.keyCode >=48 &&event.keyCode<=57){
return false;
}
};
};
Chapter4BOM
4.1BOM的简介
-
Browser Object Model浏览器对象模型
-
BOM可以使我们通过JS操作浏览器
-
在BOM中为我们提供了一组对象,用于完成对浏览器的操作
-
BOM对象
-
Window
- 代表整个浏览器的窗口,同时window也是网页中的全局对象
-
Navigator
- 代表当前浏览器的信息,通过该对象可以识别不同的浏览器
-
Location
- 代表当前浏览器的地址栏信息,通过该对象可以获取地址栏信息,或操作浏览器跳转页面
-
History
- 代表浏览器的历史记录,可以通过该对象操作浏览器的历史记录
- 由于隐私原因,该对象不能获取到具体的历史记录,只能操作浏览器向前或向后翻页
- 该操作只在当此访问有效
-
Screen
- 代表用户屏幕的信息,可以获取到显示器相关信息
-
这些对象在浏览器中都是作为window对象的属性保存的,可以通过window对象使用,也可以直接使用
-
4.2Navigator
-
代表当前浏览器的信息,通过该对象可以识别不同的浏览器
-
由于历史原因,Navigator对象中大部分属性已不能帮助我们识别浏览器了
-
appName : 返回浏览器的名称(已失效)
-
一般只会使用ueserAgent来判断浏览器信息
- ueserAgent是一个字符串,这个字符串中包含有用来描述浏览器信息的内容
- 不同浏览器会有不同的userAgent
-
如果通过userAgent仍不能判断,可以通过一些浏览器特有的对象来判断
4.3History
- 代表浏览器的历史记录,可以通过该对象操作浏览器的历史记录
- 由于隐私原因,该对象不能获取到具体的历史记录,只能操作浏览器向前或向后翻页
- 该操作只在当前访问有效
- length属性 : 获取到当前页面的链接数量
- back() : 回退到上一个页面(与浏览器的回退按钮作用相同)
- forward() : 跳转到下一个页面(作用与浏览器的前进按钮相同)
- go() : 跳转到指定页面,需要一个整数作为参数 如:history.go(-1); 与back()相同
4.4Location
-
代表当前浏览器的地址栏信息,通过该对象可以获取地址栏信息,或操作浏览器跳转页面
-
如果直接打印location可获取地址栏的信息(当前页面的完整路径)
-
如果直接将location修改为一个完整的路径(或相对路径),则页面会跳转至该路径,并会生成相应历史记录
-
assign() : 用于跳转到其他页面,作用和直接修改location相同
-
reload() : 重新加载当前页面(与刷新按钮功能相同)
- 如果传递true作为参数,刷新时会强制清空缓存
-
replace() : 可以使用一个新的页面替换当前页面,调用完毕也会跳转,但不会生成历史记录
4.5定时器
-
JS程序执行速度非常快
-
如果希望一段程序每间隔一段时间执行一次,可以使用定时调用
-
setInterval()
- 定时调用
- 可以将一个函数每隔一段时间调用一次
- 参数: 1.回调函数,该函数会每隔一段时间被调用一次 2.每次调用间隔的时间,单位是毫秒
- 返回值:返回一个Number类型的数据,这个数字作为定时器的唯一标识
-
clearInterval()
- 关闭定时器
- 需要一个定时器的标识作为参数
- 可以接受任意参数,如果参数是一个有效的标识则会停止对应函数;否则会什么也不做
- 开启每个定时器之前都要关闭上一次的定时器,以避免多次点击开启多个定时器(妙蛙)
-
setTimeout()
- 延时调用,延时调用一个函数不马上执行,而是隔一段时间以后再执行,只执行一次
-
clearTimeout()
- 关闭延时调用
4.6类的操作
- 通过修改元素的class属性来间接修改样式
- 这样可以只修改一次就能同时修改多个样式,浏览器只需要渲染一次性能更好
- 使表现和行为进一步分离
//box.className += " box2";
function hasClass(obj,cn){
var reg = new RegExp("\b"+cn+"\b");
return reg.test(obj.className);
}
//构造一个类添加函数
function addClass(obj,cn){
if(!hasClass(obj,cn)){
obj.className += " "+cn;
}
}
addClass(box,"box2");
//构造删除函数
function removeClass(obj,cn){
var reg = new RegExp("\b"+cn+"\b");
obj.className = obj.className.replace(reg,"");
}
//toggleClass函数:如果有则删除,如果无则添加
function toggleClass(obj,cn){
if(hasClass(obj,cn)){
removeClass(obj,cn);
}else{
addClass(obj,cn);
}
}
4.7 JSON
-
JS中的对象,只有JS认识,其他语言都不认识
-
JSON就是一个特殊格式字符串,这个字符串可以被任何语言所识别并可以转换为任意语言中的对象
-
JSON在开发中主要用于数据的交互
-
JavaScript Object Notation JS对象表示法
-
JSON和JS对象格式一样,只不过JSON字符串中属性名必须加双引号,其他和JS语法一致
-
JSON的分类: 1.对象{} 2.数组[]
-
JSON中允许的值:字符串,数组,布尔值,null,对象,数组
-
在JS中为我们提供了一个工具类,就叫JSON
-
这个对象可以帮助我们将一个JSON转换为JS对象,也可以将一个JS对象转换为JSON
- JSON --> JS对象: JSON.parse() 需要JSON字符串为参数,返回JS对象
- JS对象 --> JSON: JSON.stringify() 需要JS对象作为参数,返回JSON字符串
JS进阶
Chapter1 基础总结深入
1.1数据类型
-
基本(值)类型
- String
- Number
- Boolean
- Null 定义并赋值,只是值为null;初始赋值为null表明将要赋值为对象;最后赋值为null释放内存(让对象成为垃圾对象被回收)
- Undefined 定义未赋值
-
对象(引用)类型
- Object : 任意对象
- Function : 一种特别的对象(可以执行)
- Array : 一种特别的对象(数值下标)
-
typeof 用于判断数据类型,返回字符串 不能判断null与object array与object
-
instanceof 判断对象是否是某类的实例,返回布尔值
-
=== 可用于判断null和undefined类型
-
变量类型和数据类型:变量类型是指变量保存的值的数据类型,变量类型常说引用类型,数据类型常说对象类型
1.2数据,内存和变量
-
数据:存储在内存中代表特点信息的东西,本质上是010101...
-
内存:内存条通电后产生的可存储数据的空间(临时的)
-
一块内存产生两个数据:地址和存储的数据
-
内存分类:
- 栈:全局变量/局部变量
- 堆:对象
-
-
变量:可变化的量,由变量名和变量值组成;每个变量对应一块内存,变量名用于查找对应的内存;变量值就是对应内存中保存的数据
三者之间的关系
内存是用来存储数据的空间,变量是内存的标识
- 将变量的内容赋值给另一个变量,会将该变量在栈中的内容赋值给新变量
- 两个变量指向同一个对象,修改其中一个的内容,另一个也随之改变
- 两个变量指向同一个对象,一个指向新的对象,另一个不受影响
- 函数以对象为参数时,可以理解为将对象的地址传递过去
var obj1={name:"tom",age:12}
var obj2 = obj1
obj2.age = 12
console.log(obj1.age) //12
function fn1(obj){
obj.name = "Alice"
}
fn1(obj1)
console.log(obj2.name) //A
function fn2(obj){
obj = {age:18} //obj指向了新的对象
}
fn2(obj1) //形参obj指向了新的对象,但实参obj1并没有
console.log(obj1.age) //12
-
需要使用['属性名']的情况:
- 当对象的属性名包含特殊字符(空格或分隔线)时
- 属性名不确定时
var p ={}
p['content-ypr'] = 'text/json' //属性名包含特殊字符
var proName = 'myAge' //属性名不确定
p[proName] = 18
1.3函数
1.什么是函数?
实现特点功能的语句的封装体
2.为什么要用函数?
提高代码复用,便于阅读交流
3.如何定义函数?
函数声明 表达式
4.如何调用函数?
直接调用 通过对象调用 new调用 call/apply()调用 fn.call(obj) 可以让一个函数为指定对象调用(临时让fn函数成为 obj的方法)
1.4回调函数
1.什么是回调函数?
1)你定义的 2) 你未调用 3) 最终执行了
2.常见的回调函数?
dom事件回调函数,定时器回调函数,ajax请求回调函数,生命周期回调函数
1.5IIFE
- Immediately-Invoked Function Expreesion立即执行函数表达式
- 作用: 1.隐藏实现 2.不会污染外部(全局)命名空间
(function(){ //匿名函数自调用
var a;
console.log(a);
})();
1.6this
1.this是什么?
所有函数内部都有一个变量this,它的值是调用函数的当前对象
2.如何确定this的值?
test() : window p.test() : p new test() : 新建的对象 p.test().call(obj) : obj
1.7分号问题
1.可以不加分号 2.以下情况下不加分号会出问题:小括号开头的前一条语句,中方括号开头的前一条语句 3.解决方法:在行首加分号
Chapter2 函数高级
2.1原型与原型链
2.1.1原型
-
我们创建的每一个函数,解析器都会向函数中添加一个属性prototype(显示原型)
-
这个属性对应着一个对象,就是原型对象(默认是空Object对象)
-
原型对象中有一个constructor属性指向函数对象
-
如果函数作为普通函数调用,prototype没有任何作用
-
如果函数作为构造函数调用时,它所创建的对象中都会有一个隐含的属性指向该构造函数的原型对象
- 每个实例对象都有一个*proto(隐式原型)*属性
- 对象的隐式原型即为其构造函数的显示原型即per.proto = = = Person.prototype
function Person(name,age,gender){
this.name = name;
this.age = age;
this.gender = gender;
}
Person.prototype.sayName = function(){
alert(this.name);
}
var per = new Person("玉兔",16,"女");
2.1.2※原型链
-
原型对象就相当于一个公共区域,同一个类的所有实例都可以访问到这个原型对象
- 可以将对象中共有的内容统一设置到原型对象中
-
自身中没有去原型中找,原型中没有沿着 proto 找,直到Object的原型对象(Object的原型对象的_proto_为null)
-
所有函数都有两个原型属性,一个隐式原型一个显式原型
-
函数的显式原型指向它的原型对象,原型对象一般默认是空的Object实例对象
- 但Object自己的原型对象不是Object的实例,也不是空的
- 所有函数都是Fuction的实例,包括它本身
- Object的原型对象是原型链尽头
-
所有函数的原型对象都是Object的实例(Object除外)
-
Function()函数是由自己创建的,因此它的隐式原型与显式原型指向同一个原型对象
-
所有函数的隐式原型都指向Function()的原型对象
-
原型对象也是对象,它也有原型
-
在Object的原型中仍没找到,则返回undefined
1.读取对象的属性值时,会沿着原型链查找
2.设置对象的属性值时,不会查找原型链,如果当前对象中没有此属性,直接添加此属性并且设置其值
3.方法一般定义在原型中,属性一般通过构造函数定义在对象本身
2.1.3instanceof
1.instanceof是如何判断的?
A instanceof B 如果B函数的显式原型对象在A对象的原型链上,就说明A是B的实例,则返回true,否则返回false Object instanceof Function //True Object instanceof Object //True Fuction instanceof Function //True Function instanceof Object //True
- 使用 in 检查对象中是否含有某个属性时,如果对象中没有但原型中有,也会返回true
- 使用per.hasOwnProperty()检查对象自身中是否含有该属性
2.2执行上下文与执行上下文栈
2.2.1变量提升与函数提升
1.变量提升:
- 使用var关键字声明的变量,会在所有代码执行前被声明(但不会赋值)
- 如果不使用var关键字,则变量不会被声明提前
2.函数提升:
- 使用函数声明创建的函数,可以在创建前使用(在所有代码创建前被创建)
- 使用函数表达式创建的函数会被变量提升而非函数提升,因此不能在声明前使用
2.2.2执行上下文
-
代码可分为全局代码和局部代码(即函数代码)
-
全局执行上下文
-
在执行全局代码前将window确定为全局执行上下文
-
对全局数据进行预处理
- var 定义的全局变量 ==>undefined,添加为window的属性
- function定义的全局函数 ==>赋值(fun),添加为window的方法
- this ==>赋值(window)
-
开始执行全局代码
-
-
函数执行上下文
-
在调用函数,准备执行函数体之前,创建对应的函数执行上下文对象
-
对局部数据进行预处理
- 形参变量*>赋值(实参)*>添加为执行上下文的属性
- arguments==>赋值 (实参列表),添加为执行上下文的属性
- var 定义的局部变量==>undefined,添加为执行上下文的属性
- function声明的函数==>赋值(fun),添加为执行上下文的方法
- this ==>赋值(调用函数的对象)
-
开始执行函数体代码
-
2.2.3执行上下文栈
1.在全局代码执行前,JS引擎会创建一个栈来存储管理所有的执行上下文对象 2.在执行全局上下文window确定后,将其添加到栈中(压栈) 3.在函数执行上下文创建后,将其添加到栈中(压栈) 4.在当前函数执行完后,将栈顶的对象移除(出栈) 5.当所有的代码执行完后,栈中只剩下window
console.log("global begin:"+i)
var i = 1
foo(1)
function foo(i){
if(i == 4){
return;
}
console.log('foo() begin:'+ i);
foo(i+1); //递归调用
console.log('foo() end:'+ i);
}
console.log('global end:'+ i);
- 依次输出什么?
gb:undefined fb:1 fb:2 fb:3 fe:3 fe:2 fe:1 ge:1
2.整个过程产生了几次执行上下文?
5次,共调用4次函数,一次window
function a(){}
var a
console.log(typeof a) //'function'
先执行变量提升,再执行函数提升
if(!(b in window)){
var b = 1
}
console.log(b) //undefined
变量提升,b在window里面
var c = 1
function c(c){
console.log(c)
}
c(2) //报错
先变量提升再函数提升,因此最后将c赋值为1,c不是函数无法调用
2.3作用域与作用域链
2.3.1作用域
-
作用域:(n+1)
- 变量作用的范围(一个代码块所在的区域)
- 静态的(相对于上下文执行对象),在编写代码时就确定了
-
分类
- 全局作用域
- 函数作用域(定义几个函数就有几个函数作用域)
-
作用:隔离变量,不同作用域下同名变量不会有冲突
-
在函数中要访问全局变量可以使用window对象
-
在函数中不使用var声明的变量,都会成为全局变量
-
定义形参就相当于在函数中声明了一个变量
2.3.2作用域与执行上下文
1.区别:
- 全局作用域之外,每个函数都会创建自己的作用域,作用域在函数定义时就已经确定了,而非函数调用时
- 全局执行上下文环境时全局作用域确定之后,js代码马上执行之前创建
- 函数执行上下文是在调用函数时,函数体代码执行之前创建
- 作用域是静态的,只要函数定义好了就一直存在且不会变化
- 执行上下文环境是动态的,调用函数时创建,函数调用结束时执行上下文环境就会被释放
2.联系:
- 执行上下文环境是从属于所在的作用域
- 全局执行上下文环境 ==>全局作用域
- 函数执行上下文环境 ==>对应的函数作用域
2.3.3作用域链
- 多个上下级关系的作用域形成的链,方向是从下向上(从内到外)
- 查找变量时沿着作用域链查找
- 先在当前作用域查找,若找不到则去上一级作用域,直到全局作用域
var x = 10
function fn(){
console.log(x)
}
function show(f){
var x = 20
f()
}
show(fn) //10
在fn的作用域中找x,找不到则去全局作用域中找,而不会去show的作用域中找
var fn = function(){
console.log(fn)
}
fn() //f{}
var obj = {
fn2:function(){
console.log(fn2)
}
}
obj.fn2() //报错
在作用域中无法找到fn2,在全局里也无法找到,因此报错,若输出this.fn2才能找到
2.4闭包Closure
2.4.1理解闭包
1.如何产生闭包?
当一个嵌套的内部函数引用了嵌套的外部函数的变量时,就产生了闭包
2.闭包到底是什么?
使用chrome调试查看 理解一:闭包是嵌套的内部函数 理解二:闭包是被引用变量的对象
3.产生闭包的条件?
1.函数嵌套 2.内部函数引用了外部的数据 3.外部函数被执行(执行内部函数定义,不用调用内部函数)
2.4.2常见的闭包
1.将函数作为另一个函数的返回值
function fn1(){
var a = 2
function fn2 (){
a++
console.log(a)
}
return fn2
}
var f = fn2
f() //3
f() //4
整个过程只产生了一个闭包(只调用一次fn1,产生一次fn2的定义)
2.将函数作为实参传递给另一个函数调用
function showDelay(msg,time){
setTimeout(function(){
alert(msg) //内部函数引用了外部的变量msg
},time)
}
showDelay('atjlu',2000) //调用外部函数,因此产生了闭包
2.4.3闭包的作用
function fn1(){
var a = 2
function fn2 (){
a++
console.log(a)
}
return fn2
}
var f = fn2
f() //3
f() //4
1.使用函数内部的变量在函数执行完后,仍然存活在内存中(延长了局部变量的生命周期) 2.让函数外部可以操作(读写)到函数内部的数据(变量/函数)
1.函数执行完毕后,函数内部声明的局部变量是否还存在?
内部函数本身不存在了,但闭包里的变量还存在
2.在函数外部能否直接访问函数内部的局部变量?
不能,但是可以通过闭包让外部操作它
2.4.4闭包的生命周期
产生:在嵌套内部函数定义执行完时就产生了(而非调用时) 死亡:在嵌套内部函数成为垃圾对象时
function fn1(){
//此时闭包就已产生(函数提升)
var a = 2
function fn2 (){
a++
console.log(a)
}
return fn2
}
var f = fn2
f() //3
f() //4
f = null //闭包死亡(包含闭包的函数对象成为垃圾对象
2.4.5闭包的应用:自定义JS模块
JS模块:
- 具有特定功能的js文件
- 将所有数据和功能封装到一个函数内部
- 只向外暴露一个包含n个方法的对象或函数
- 模块的使用者只需要通过模块暴露的对象调用方法来实现对应功能
function myModule(){
var msg = 'jlu' //私有数据
//操作数据的函数
function doSth(){
console.log('doSth()'+ msg.toUppercase())
}
function doOtherthing(){
console.log('doOtherthing()'+msg.toLowerCase())
}
//向外暴露对象
return {
doSth:doSth,
doOtherthing:doOtherthing
}
}
var module = myModule()
module.doSth()
module.doOtherthing()
(function (){
var msg = 'jlu' //私有数据
//操作数据的函数
function doSth(){
console.log('doSth()'+ msg.toUppercase())
}
function doOtherthing(){
console.log('doOtherthing()'+msg.toLowerCase())
}
//向外暴露对象(给外部使用的方法)
window.myModule2 = {
doSth:doSth,
doOtherthing:doOtherthing
}
})()
myModule2.doSth()
myModule2.doOtherthing()
2.4.6闭包的缺点及解决
缺点: 1.函数执行完后,函数内的局部变量没有释放,占用内存时间会变长 2.容易造成内存泄漏 解决: 1.能不用闭包就不用闭包 2.及时释放
内存溢出:
- 一种程序运行出现的错误
- 当程序运行需要的内存超过了剩余的内存时,就出现内存溢出的错误
内存泄漏:
-
占用的内存没有及时释放
-
内存泄漏积累多了就容易导致内存溢出
-
常见的内存泄露:
- 意外的全局变量
- 没有及时清理的计时器或回调函数
- 闭包
var name = 'The Window'
var object = {
name : 'My Object',
getNameFunc : function(){
return function(){
return this.name
}
}
}
alert(object.getNameFunc()()) // The window (即输出this.name)
object.getNameFunc( )是一个函数(最内层的函数),这里是window直接调用它,因此this是window
var name = 'The Window'
var object = {
name : 'My Object',
getNameFunc : function(){
var that = this
return function(){
return that.name
}
}
}
alert(object.getNameFunc()()) // My Object (that处的this是什么?)
that保存的是调用外层函数时的this, 而外层函数是由object调用的,因此此处的this是object
Chapter3 面向对象高级
3.1面向对象编程介绍
- 面向过程编程POP(Process-oriented progranmming): 分析出解决问题所需的步骤,然后按照步骤一步一步实现
- 面向对象编程OOP(Object Oriented Programming) : 把事务分解成一个个对象,然后由对象之间分工与合作
- 面向对象的特性: 1.封装性 2.继承性 3.多态性
3.2对象创建模式
方式一:Object构造函数模式
- 适用于一开始不确定对象属性的时候
- 缺点:语句太多
var p = new Object()
p.name = 'Tom'
p.age = 12
方式二:对象字面量模式
- 使用{}创建对象,同时指定属性和方法
- 适用于起始时内部数据确定
- 缺点:创建多个对象时代码有重复
var p = {
name : 'Tom',
age : 12,
setName : function(name){
this.name = name
}
}
方式三:工厂模式(基础2.2.7)
- 通过工厂函数动态创建对象并返回
- 适用于需要创建多个对象的场景
- 缺点:不同类型无法得以体现
function createPerson(name,age){
var obj = {
name : name,
age : age,
setName : function (name){
this.name = name
}
}
return obj
}
var p1 = createPerson('Tom',12)
方式四:自定义构造函数模式(基础2.2.8)
- 自定义构造函数,通过new创建对象
- 适用于需要创建多个类型确定的对象
- 缺点:每个对象都有相同的数据,浪费内存
function Person(name,age){
this.name = name
this.age = age
this.setName = function(name){
this.name = name
}
}
var per = new Person('Jack',16)
方式五:构造函数+原型的组合模式
function Person(name,age){ //在构造函数中只初始化属性
this.name = name
this.age = age
}
Person.prototype.setName = function(name){ //类的方法保存在原型里
this.name = name
}
var per = new Person('Jack',16)
3.3继承模式
3.3.1原型链继承
- 定义父类型构造函数
- 给父类型的原型添加方法
- 定义子类型的构造函数
- 创建父类型的对象赋值给子类型的原型
- 将子类型原型的构造属性设置为子类型
- 给子类型原型添加方法
- 创建子类型的对象:可以调用父类型的方法
- 子类型的原型为父类型的一个实例对象
//父类型
function Supper(){ //Step1
this.supProp = 'Supper property'
}
Supper.prototype.showSupperProp = function(){ //Step2
console.log(this.supProp)
}
//子类型
function Sub(){ //Step3
this.subProp = 'Sub property'
}
Sub.prototype = new Supper() //Step4子类型的原型为父类型的一个实例对象
Sub.prototype.constructor = Sub //Step5,若没有这一步,对象sub的构造函数为Supper()
Sub.prototype.showSubProp = function(){ //Step6
console.log(this.subProp)
}
var sub = new Sub() //Step7
sub.showSupperProp() //Supper property
console.log(sub.constructor) //Sub()
3.3.2借用构造函数继承(假)
- 定义父类型构造函数
- 定义子类型构造函数
- 在子类型构造函数中调用父类型构造函数
- 在子类型构造函数中通过call()调用父类型构造函数
- 用于继承父类型的属性
function Person(name,age){
this.name = name
this .age = age
}
function Student(name,age,grade){
Person.call(this,name,age)//相当于this.Person(name,age)
this.grade = grade
}
var s = new Student('Tom',20,9)
console.log(s.name,s.age,s.grade) //Tom 20 9
3.3.3组合继承
原型链+借用构造函数的组合继承
- 利用原型链实现对父类型对象的方法继承
- 利用call()借用父类型构造函数初始化相同属性
Chapter4 线程机制与事件机制
4.1进程与线程
进程process
程序的一次执行,它占有一片独有的内存空间,可以通过windows任务管理器查看进程
线程thread
是进程内一个独立执行单元,是程序执行的一个完整流程,是CPU最小的调度单元
- 应用程序必须运行在某个进程的某个线程上
- 一个进程中至少有一个运行的线程:主线程,进程启动后自动创建
- 一个进程中也可以同时运行多个线程,我们会说程序是多线程运行的
- 一个进程内的数据可以提供其中的多个线程直接共享
- 多个进程之间数据不能直接共享
- 线程池(thread pool):保存多个线程对象的容器,实现线程对象的反复利用
何为多进程和多线程?
多进程运行:一应用程序可以同时启动多个实例运行 多线程:在一个进程内,同时有多个线程运行
比较单线程与多线程?
多线程:优点:能有效提高CPU利用率;缺点:创建多线程开销,线程间切换开销,死锁与状态同步问题 单线程:优点:顺序编程简单易懂;缺点:效率低
JS是单线程还是多线程?
js是单线程运行的,但使用H5中的Web Workers可以多线程运行
浏览器运行是单线程还是多线程?
都是多线程运行的
浏览器运行是单进程还是多进程?
有单进程的也有多进程的(chrome)
4.2浏览器内核
-
支撑浏览器运行的最核心的程序
-
不同浏览器可能不太一样:webkit(Chrome,Safari), Gecko(firefox), Trident, Trident+webkit
-
内核由很多模块组成: 主线程
- js引擎模块:负责js程序的编译与运行
- html,css文档解析模块:负责页面文本的解析
- DOM/CSS模块:负责dom/css在内存中的相关处理
- 布局和渲染模块:负责页面的布局和效果的绘制(内存中的对象)
- ....... 分线程
- 定时器模块:负责定时器的管理
- 事件响应模块:负责事件的管理
- 网络请求模块:负责ajax请求
4.3定时器引发的思考
- 定时器真的是定时执行的吗?
定时器并不能保证真正定时执行 一般会延迟一点点(可接受),也有可能延迟很久(不可接受)
- 定时器回调函数是在分线程执行的吗?
在主线程执行的,(JS是单线程的)
- 定时器如何实现?
事件循环模型
4.4JS是单线程执行的
- alert会暂停当前主线程的执行,同时也会暂停定时器的计时,直到点击确定后才会恢复程序执行和计时(2022不暂停计时了)
代码分类:初始化代码,回调代码 js引擎执行代码的基本流程:先执行初始化代码,设置监听器,绑定事件监听,发生 ajax请求,后面才会执行回调代码
4.5浏览器的事件循环模型
- 所有代码分类
- 初始化执行代码(同步代码):包含绑定dom事件监听,设置定时器,发送ajax请求的代码
- 回调执行代码(异步代码):处理回调逻辑
- js引擎执行代码的基本流程
- 初始化代码===> 回调代码
- 模型的2个重要组成部分
- 事件管理模块
- 回调队列
- 模型的运转流程
- 执行初始化代码,将事件回调函数交给对应模块管理
- 当事件发生时,管理模块会将回调函数及其数据添加到回调列队中
- 只有当初始化代码执行完毕后(可能要一定时间),开会遍历读取回调队列中的回调函数执行
4.6 H5 Web Woekers(多线程)
- H5规范提供了js分线程的实现,取名Web Workers
- 相关API :
- Worker : 构造函数,加载分线程执行的js文件
- Woker.prototype.onmessage : 用于接收另一个线程的回调函数
- Woker.prototype.postMessage : 向另一个线程发送消息
- 不足;
- worker内代码不能操作DOM(更新UI)
- 不能跨域加载JS
- 不是每个浏览器都支持这个新特性
var number = new Worker('worker.js')
worker.onmessage = function(event){
console.log('主线程接收分线程返回的数据:'+event.data)
alert(event.data)
}
worker.postMessage(number)
console.log('主线程向分线程发送数据:'+number)
var onmessage = function(event){
var number = event.data
console.log('分线程接收到主线程的数据:'+number)
var result = fibonacci(number)
postMessage(result)
console.log('分线程向主线程返回数据:'+result)
}
ES6+
ECAM(European Computer Manafacturers Association)欧洲计算机制造商协会(ECMA国际) ES(ECMAScript):ECMA国际制作的脚本程序设计语言
Chapter1ES6
1.1 let关键字
let a
let b,c
let star
let star //报错
{
let girl = 'Alice'
}
console.log(girl) //报错
console.log(song) //报错
let song = 'faded'
{
let school
function(){
console.log(school)
}
}
对比var 关键字
- let声明变量不能重复声明
- let具有块级作用域
- 不存在变量提升
- 不影响作用域链
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<style>
.item {
height: 100px;
width: 200px;
border: 2px skyblue solid;
float: left;
margin: 20px;
}
</style>
</head>
<body>
<h2>点击切换颜色</h2>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<script>
let items = document.getElementsByClassName("item")
for(var i = 0;i<items.length;i++){
items[i].onclick = function(){
this.style.background = 'pink'
}
}
</script>
</body>
</html>
- 如果for循环中使用var关键字声明i,代码会先执行整个循环,因此点击函数响应时i已经为3;而使用let关键字每一次循环的i互不影响,因此每次i都是当前值
1.2 const关键字
//声明常量
const SCHOOL = 'jlu'
{
const A = 100
}
console.log(A) //报错
const TEAM = ['uzi','mlxg','lapi']
TEAM.push('lapi') //不会报错
TEAM = 100 //会报错
- 一定要赋初值
- 一般常量用大写
- 常量的值不能修改
- 也是块级作用域
- 对于数组和对象的元素修改,不算做对常量做修改,不会报错
1.3变量的解构赋值
ES6允许按照一定模式从数组和对象中提取值,对变量进行赋值,这被称为结构赋值
- 数组的解构
const F4=['拉皮','辣皮','啦皮','辣啤']
let [a,b,c,d] = F4
console.log(a) //拉皮
console.log(d) //辣啤
- 对象的解构
const zhao = {
name: '赵本山',
age: 60,
xiaopin: function () {
console.log("我可以演小品")
}
}
let { name, age, xiaopin } = zhao
console.log(name)
console.log(xiaopin)
xiaopin()
1.4模板字符串
ES6引入了新的声明字符串方式 ' ' " "
//声明
let str = `我也是一个字符串哦!`
console.log(str,typeof str) //我也是一个字符串哦! string
//内容中可以直接出现换行符,而'',""均不可出现换行符
let str1 = `<ul>
<li>沈腾
</li>
</ul>`
//变量拼接
let lovest = '贾玲'
let out =`${lovest}是我最喜欢的喜剧演员`
console.log(out) //贾玲是我最喜欢的喜剧演员
1.5对象的简化写法
ES6允许在大括号里,直接写入变量和函数作为对象的属性和方法
let name ='lapi'
let change = function(){
conbsole.log('我们可以改变')
}
const school = {
name,
change,
improve(){
console.log('我们可以提高')
}
}
console.log(school)
1.6箭头函数
let fn = function(a,b){
return a+b
}
let fn1 = (a,b) => {
return a+b
}
//1. this 是静态的,始终指向函数声明时所在作用域下的this的值
function getName(){
console.log(this.name)
}
let getName2 = () =>{
console.log(this.name)
}
//设置window对象的name属性
window.name = 'sgg'
const SCHOOL = {
name:'jlu'
}
//直接调用
getName() //sgg
getname2() //sgg
//call方法调用
getName.call(SCHOOL)//jlu
getName2.call(SCHOOL)//sgg
//2.不能作为构造函数实例化对象
let Person = (name,age) =>{
this.name = name
this.age = age
}
let me = Person('lapi',20) //报错
//3. 不能使用arguments变量
let fn2 = () =>{
console.log(arguments)
}
fn2(1,2,3) //报错
//4. 箭头函数的简写
//(1)当形参有且仅有一个时可以省略小括号
//(2)当代码体只有一条语句时省略大括号,此时return必须省略,语句执行的结果就是返回值
let add = n =>{
return n+n
}
let pow = n => n*n
console.log(pow(8)) //64
箭头函数实践
- this是静态的
let ad = document.getElementById("box")
ad.onclick = function(){
//let self = this
setTimeout(() => {
//self.style.background='pink'
this.style.background='pink'
},2000)
}
const arr = [1,6,9,20,15]
const result = arr.filter(item => item%2===0)
console.log(result)
- 箭头函数适合与this无关的回调,定时器,数组的方法回调
- 箭头函数不适合与this 有关的回调,事件回调,对象的方法
1.7函数参数的默认值设置
- ES6允许给函数参数赋值初始值
- 形参初始值(具有默认值的参数一般位置要靠后)
- 与解构赋值结合
function add(a,b,c=10){
return a+b+c
}
let result = add(1,2)
console.log(result)
function connect({host='127.0.0.1',username,password,post}){
console.log(host)
console.log(username)
console.log(password)
console.log(post)
}
connect({
username:'root',
password:'root',
post:3306
})
1.8rest参数
ES6引入 rest参数,用于获取函数的实参,用来代替arguments
//ES5获取实参的方式
function date(){
concole.log(arguments) //返回对象
}
date('海绵宝宝','派大星','痞老板')
//rest
function date(...args) {
console.log(args) //返回数组
}
date('海绵宝宝', '派大星', '痞老板')
//rest参数必须要放到参数最后
function fn(a,b,...args){
console.log(a)
console.log(b)
console.log(args)
}
fn(1,2,3,4,5,6)
1.9扩展运算符及应用
... 扩展运算符能将数组转换为逗号分隔的参数序列
const hs = ['Dh','Durid','Huter']
function battle(){
console.log(arguments)
}
battle(...hs)
//1. 数组的合并
const kz = ['wtl','xy']
const fh = ['lh','zy']
// const two = kz.concat(fh)
const two = [...kz,...fh]
console.log(two)
//2. 数组的克隆
const she = ['s','h','e']
const she2 = [...she]
console.log(she2)
//3. 将伪数组转换为真正的数组
const divs = document.querySelectorAll('div')
const divArr = [...div]
console.log(divArr)
1.10 Symbol
ES6引入了一种新的原始数据类型Symbol,表示独一无二的值。是一种类似于字符串的数据类型
- Symbol值唯一,用于解决命名冲突的问题
- Symbol值不能与其他数据进行运算
- Symbol定义的对象属性不能使用for...in循环遍历,但是可以使用Reflect.ownKeys来获取对象的所有键名
//创建Symbol
let s = Symbol()
console.log(s, typeof s)
let s2 = Symbol('lapi')//传入字符串是描述字符串
let s3 = Symbol('lapi')
console.log(s2 === s3)
//Symbol.for创建
let s4 = Symbol.for('lapi')
console.log(s4, typeof s4)
let s5 = Symbol.for('lapi')
console.log(s4 === s5)
Symbol.hasInstanceof
当使用instanceof时会自动调用函数,并将o的值传递给 pp
class Person{
static [Symbol.hasInstance](pp){
console.log(pp)
console.log('检测类型ing')
}
}
let o ={}
console.log(o instanceof Person) //false
Symbol.isConcatSpreadable控制对象是否可展开,是一个布尔值
const arr1 = [1,2,3]
const arr2 = [4,5,6]
arr2[Symbol.isConcatSpreadable] = false //设置为不可展开
console.log(arr1.concat(arr2)) //1,2,3,Array(3)
1.11迭代器Iterator
迭代器是一种接口,为各种不同的数据结构提供统一的访问机制 任何数据只要部署Iterator接口,就可以完成遍历操作
- ES6创造了一种新的遍历命令for...of循环,Iterator接口主要供for...of消费
- 原生具备Iterator接口的数据(可用for...of遍历)
- Array
- Arguments
- Set
- Map
- String
- TypeArray
- NodeList
const xy = ['唐僧','孙悟空','猪八戒','沙和尚']
for(let v of xy){
console.log(v) //将数组内的元素遍历,若是for...in,v则是0123
}
工作原理:
- 创建一个指针对象,指向当前数据结构的起始位置
- 第一次调用对象的next方法,指针自动指向数据结构的第一个成员
- 接下来不断调用next方法,指针一直往后移动,直到指向最后一个成员
- 每调用next方法返回一个包含value和done属性的对象
const banji = {
name:'终极一班',
stus:['xm','xn','xt','knight'],
[Symbol.iterator](){
let index = 0
return {
next:() => {
if(index<this.stus.length){
const result = { value: this.stus[index], done: false }
index++
return result
}else{
return {value:undefined,done:true}
}
}
}
}
}
for(let v of banji){
console.log(v)
}
生成器函数
function* gen(args) {
console.log(args)
console.log(yield 111)
yield 222
yield 333
}
let iterator = gen('AAA')
console.log(iterator.next())
//next方法可以传入实参,参数将作为上一次yield语句整体的返回结果
console.log(iterator.next('BBB'))
// setTimeout(() => {
// console.log(111)
// setTimeout(() => {
// console.log(222)
// setTimeout(() => {
// console.log(333)
// }, 3000)
// }, 2000)
// }, 1000)
function one(){
setTimeout(()=>{
console.log(111)
iterator.next()
},1000)
}
function two() {
setTimeout(() => {
console.log(222)
iterator.next()
}, 2000)
}
function three() {
setTimeout(() => {
console.log(333)
iterator.next()
}, 3000)
}
function * gen(){
yield one()
yield two()
yield three()
}
let iterator = gen()
iterator.next()
1.12 Promise
概述
Promise是ES6引入的异步编程的新解决方案,语法上是一个构造函数,用来封装异步操作并可以获取其成功或失败的结果
- Promise 构造函数: Promise (excutor) {};
const p = new Promise(function(resolve,reject){
setTimeout(function(){
// let data = '数据'
// resolve(data)
let err = '失败'
reject(err)
},1000)
})
//调用promise的then方法
p.then(function(value){
console.log(value)
},function(reason){
console.error(reason)
})
Promise封装读取文件
// 1、引入 fs 模块
const fs = require("fs");
// 2、调用方法,读取文件
// fs.readFile("resources/text.txt",(err,data)=>{
// // 如果失败则抛出错误
// if(err) throw err;
// // 如果没有出错,则输出内容
// console.log(data.toString());
// });
// 3、使用Promise封装
const p = new Promise(function(resolve,data){
fs.readFile("resources/text.txt",(err,data)=>{
// 判断如果失败
if(err) reject(err);
// 如果成功
resolve(data);
});
});
p.then(function(value){
console.log(value.toString());
},function(reason){
console.log(reason); // 读取失败
})
- Promise.prototype.then 方法; 如果promise执行成功,执行第一个回调函数;如果失败执行第二个回调函数 then方法地返回结果是promise对象,对象状态由回调函数地执行结果决定 如果回调函数中返回的是非promise类型的属性,状态为成功,返回值为对象的成功的值 如果回调函数中返回的是promise对象,内部promise成功外部则为成功,内部失败外部则失败,返回值与内部返回值相同
const p = new Promise((resolve,reject)=>{
setTimeout(()=>{
resolve('用户数据')
reject('出错啦')
},1000)
})
const result = p.then(value=>{
console.log(value)
return 'bjxshb'
},reason=>{
console.warn(reason)
})
console.log(result)
链式调用
p.then(value=>{}).then(value=>{})
- Promise.prototype.catch 方法; 用于指定失败回调函数
const p = new Promise((resovle,reject)=>{
setTimeout(()=>{
//设置p对象的状态为失败,并设置失败的值
reject('出错啦')
},1000)
})
//p.then(function(value){},function(reason){
//console.error(reason)
//}) //出错啦
p.catch(function(reason){
console.warn(reason)
})//出错啦
1.13 Set集合
- ES6提供了新的数据结构Set(集合)
- 它类似于数组,但成员的值都是唯一的
- 集合实现了iterator接口,所以可以使用扩展运算符和for...of... 进行遍历
- 向 Set 加入值的时候,不会发生类型转换,所以5和"5"是两个不同的值
- Set 内部判断两个值是否不同,使用的算法叫做“Same-value-zero equality”,它类似于精确相等运算符(===),主要的区别是向 Set 加入值时认为NaN等于自身,而精确相等运算符认为NaN不等于自身
- 两个对象总是不相等的
集合的声明与初始化:
new Set() new Set([数组]) Set函数可以接受一个数组(或者具有 iterable 接口的其他数据结构)作为参数,用来初始化
集合的属性和方法:
- size :返回集合的元素个数
- add(value) : 增加元素
- delete(value) : 删除元素
- has(value) : 判断集合是否包含某元素
- clear() : 清除集合
- 使用for...of...遍历
- Array.from方法可以将 Set 结构转为数组。
let s = new Set([1,2,3,4,4])
console.log(s) //1,2,3,4
console.log(s.size) //4
s.add(5) //增加元素
s.delete(1) //删除元素
console.log(s.has(6)) //false
for (let v of s) {
console.log(v)
}
const items = new Set([1, 2, 3, 4, 5]);
const array = Array.from(items);
集合的遍历
- Set.prototype.keys():返回键名的遍历器
- Set.prototype.values():返回键值的遍历器
- Set.prototype.entries():返回键值对的遍历器
- Set.prototype.forEach():使用回调函数遍历每个成员
由于 Set 结构没有键名,只有键值(或者说键名和键值是同一个值),所以keys方法和values方法的行为完全一致。 可以省略values方法,直接用for...of循环遍历 Set。
let set = new Set(['red', 'green', 'blue']);
for (let item of set.keys()) {
console.log(item);
}
// red
// green
// blue
for (let item of set.values()) {
console.log(item);
}
// red
// green
// blue
for (let item of set.entries()) {
console.log(item);
}
// ["red", "red"]
// ["green", "green"]
// ["blue", "blue"]
for (let x of set) {
console.log(x);
}
// red
// green
// blue
forEach()
Set 结构的实例与数组一样,也拥有forEach方法,用于对每个成员执行某种操作,没有返回值。
let set = new Set([1, 4, 9]);
set.forEach((value, key) => console.log(key + ' : ' + value))
// 1 : 1
// 4 : 4
// 9 : 9
上面代码说明,forEach方法的参数就是一个处理函数。该函数的参数与数组的forEach一致,依次为键值、键名、集合本身(上例省略了该参数)。这里需要注意,Set 结构的键名就是键值(两者是同一个值),因此第一个参数与第二个参数的值永远都是一样的。
另外,forEach方法还可以有第二个参数,表示绑定处理函数内部的this对象。
集合遍历的应用:
//数组去重
let arr = [1,2,3,3,2,5,6]
let result = [...new Set(arr)] //创建一个内容为arr的数组,然后扩展并转换为数组
let a = new Set([1, 2, 3]);
let b = new Set([4, 3, 2]);
// 并集
let union = new Set([...a, ...b]);
// Set {1, 2, 3, 4}
// 交集
let intersect = new Set([...a].filter(x => b.has(x)));
// set {2, 3}
// (a 相对于 b 的)差集
let difference = new Set([...a].filter(x => !b.has(x)));
// Set {1}
1.14 Map
- ES6提供了Map数据结构
- 它类似于对象,也是键值对的集合
- Map键的类型不局限于字符串,可以是各种类型
- 实现了 iterator接口,所以可以使用扩展运算符和for...of... 进行遍历
Map的属性和方法
const map = new Map();
map.set('foo', true);
map.set('bar', false);
console.log(map.size) // 2
const m = new Map();
const o = {p: 'Hello World'};
//set()方法,添加元素
m.set('name','lapi') //键(name)值(lapi)对
m.set(o, 'content') //键(对象o)值(content)对
//get()方法,获取元素
m.get(o) // "content"
//has()方法,判断是否包含某元素
m.has(o) // true
//delete()方法,删除元素
m.delete(o) // true
m.has(o) // false
console.log(m.get('name')) //lapi
//clear方法清除所有成员,没有返回值
m.clear()
- set方法返回的是当前的Map对象,因此可以采用链式写法
let map = new Map().set(1, 'a').set(2, 'b').set(3, 'c')
console.log(map)//Map(3) {size:3,1=>a,2=>b,3=>c}
- 作为构造函数,Map 也可以接受一个数组作为参数。该数组的成员是一个个表示键值对的数组
const map = new Map([
['name', '张三'],
['title', 'Author']
]);
map.size // 2
map.has('name') // true
map.get('name') // "张三"
map.has('title') // true
map.get('title') // "Author"
上面代码在新建 Map 实例时,就指定了两个键name和title
Set和Map都可以用来生成新的 Map。
const set = new Set([
['foo', 1],
['bar', 2]
]);
const m1 = new Map(set);
m1.get('foo') // 1
const m2 = new Map([['baz', 3]]);
const m3 = new Map(m2);
m3.get('baz') // 3
上面代码中,我们分别使用 Set 对象和 Map 对象,当作Map构造函数的参数,结果都生成了新的 Map 对象
- 如果对同一个键多次赋值,后面的值将覆盖前面的值
只有对同一个对象的引用,Map 结构才将其视为同一个键
const map = new Map();
map.set(['a'], 555);
map.get(['a']) // undefined
- 上面代码的set和get方法,表面是针对同一个键,但实际上这是两个不同的数组实例,内存地址是不一样的,因此get方法无法读取该键,返回undefined
- 因此,同样的值的两个实例,在 Map 结构中被视为两个键
- 如果 Map 的键是一个简单类型的值(数字、字符串、布尔值),则只要两个值严格相等,Map 将其视为一个键,比如0和-0就是一个键,布尔值true和字符串true则是两个不同的键
- 另外,undefined和null也是两个不同的键
- 虽然NaN不严格相等于自身,但 Map 将其视为同一个键
Map的遍历
Map 结构原生提供三个遍历器生成函数和一个遍历方法
- Map.prototype.keys():返回键名的遍历器
- Map.prototype.values():返回键值的遍历器
- Map.prototype.entries():返回所有成员的遍历器
- Map.prototype.forEach():遍历 Map 的所有成员 需要特别注意的是,Map 的遍历顺序就是插入顺序
const map = new Map([
['F', 'no'],
['T', 'yes'],
]);
for (let key of map.keys()) {
console.log(key);
}
// "F"
// "T"
for (let value of map.values()) {
console.log(value);
}
// "no"
// "yes"
for (let item of map.entries()) {
console.log(item[0], item[1]);
}
// "F" "no"
// "T" "yes"
// 或者
for (let [key, value] of map.entries()) {
console.log(key, value);
}
// "F" "no"
// "T" "yes"
// 等同于使用map.entries()
for (let [key, value] of map) {
console.log(key, value);
}
// "F" "no"
// "T" "yes"
- Map 还有一个forEach方法,与数组的forEach方法类似,也可以实现遍历
map.forEach(function(value, key, map) {
console.log("Key: %s, Value: %s", key, value);
});
//forEach方法还可以接受第二个参数,用来绑定this。
const reporter = {
report: function(key, value) {
console.log("Key: %s, Value: %s", key, value);
}
};
map.forEach(function(value, key, map) {
this.report(key, value);
}, reporter);
//上面代码中,forEach方法的回调函数的this,就指向reporter
类型互换
- Map 转为数组 Map 转为数组最方便的方法,就是使用扩展运算符(...)
const myMap = new Map()
.set(true, 7)
.set({foo: 3}, ['abc']);
[...myMap]
// [ [ true, 7 ], [ { foo: 3 }, [ 'abc' ] ] ]
- 数组 转为 Map 将数组传入 Map 构造函数,就可以转为 Map
new Map([
[true, 7],
[{foo: 3}, ['abc']]
])
// Map {
// true => 7,
// Object {foo: 3} => ['abc']
// }
- Map 转为对象 如果所有 Map 的键都是字符串,它可以无损地转为对象
function strMapToObj(strMap) {
let obj = Object.create(null);
for (let [k,v] of strMap) {
obj[k] = v;
}
return obj;
}
const myMap = new Map()
.set('yes', true)
.set('no', false);
strMapToObj(myMap)
// { yes: true, no: false }
如果有非字符串的键名,那么这个键名会被转成字符串,再作为对象的键名
- 对象转为 Map 对象转为 Map 可以通过Object.entries()
let obj = {"a":1, "b":2};
let map = new Map(Object.entries(obj));
//也可以自己实现一个转换函数。
function objToStrMap(obj) {
let strMap = new Map();
for (let k of Object.keys(obj)) {
strMap.set(k, obj[k]);
}
return strMap;
}
objToStrMap({yes: true, no: false})
// Map {"yes" => true, "no" => false}
- Map 转为 JSON Map 转为 JSON 要区分两种情况
一种情况是,Map 的键名都是字符串,这时可以选择转为对象 JSON
function strMapToJson(strMap) {
return JSON.stringify(strMapToObj(strMap));
}
let myMap = new Map().set('yes', true).set('no', false);
strMapToJson(myMap)
// '{"yes":true,"no":false}'
另一种情况是,Map 的键名有非字符串,这时可以选择转为数组 JSON
function mapToArrayJson(map) {
return JSON.stringify([...map]);
}
let myMap = new Map().set(true, 7).set({foo: 3}, ['abc']);
mapToArrayJson(myMap)
// '[[true,7],[{"foo":3},["abc"]]]'
- JSON 转为 Map JSON 转为 Map,正常情况下,所有键名都是字符串
function jsonToStrMap(jsonStr) {
return objToStrMap(JSON.parse(jsonStr));
}
jsonToStrMap('{"yes": true, "no": false}')
// Map {'yes' => true, 'no' => false}
//但是,有一种特殊情况,整个 JSON 就是一个数组,且每个数组成员本身,又是一个有两个成员的数组。这时,它可以一一对应地转为 Map。这往往是 Map 转为数组 JSON 的逆操作。
function jsonToMap(jsonStr) {
return new Map(JSON.parse(jsonStr));
}
jsonToMap('[[true,7],[{"foo":3},["abc"]]]')
// Map {true => 7, Object {foo: 3} => ['abc']}
1.15 Class类
ES6 提供了更接近传统语言的写法,引入了 Class(类)这个概念,作为对象的模板 通过class关键字,可以定义类
基本上,ES6 的class可以看作只是一个语法糖,它的绝大部分功能,ES5 都可以做到,新的class写法只是让对象原型的写法更加清晰、更像面向对象编程的语法而已
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
toString() {
return '(' + this.x + ', ' + this.y + ')';
}
}
- 上面代码定义了一个“类”,可以看到里面有一个constructor()方法,这就是构造方法,而this关键字则代表实例对象
- Point类除了构造方法,还定义了一个toString()方法
- 定义toString()方法的时候,前面不需要加上function这个关键字,直接把函数定义放进去了就可以了
- 方法与方法之间不需要逗号分隔,加了会报错
- ES6 的类,完全可以看作构造函数的另一种写法
class Point {
// ...
}
typeof Point // "function"
Point === Point.prototype.constructor // true
上面代码表明,类的数据类型就是函数,类本身就指向构造函数
使用的时候,也是直接对类使用new命令,跟构造函数的用法完全一致。
class Bar {
doStuff() {
console.log('stuff');
}
}
const b = new Bar();
b.doStuff() // "stuff"
- 类的所有方法都定义在类的prototype属性上面
- 因此,在类的实例上面调用方法,其实就是调用原型上的方法
class B {}
const b = new B();
b.constructor === B.prototype.constructor // true
上面代码中,b是B类的实例,它的constructor()方法就是B类原型的constructor()方法
由于类的方法都定义在prototype对象上面,所以类的新方法可以添加在prototype对象上面
- Object.assign()方法可以很方便地一次向类添加多个方法
class Point {
constructor(){
// ...
}
}
Object.assign(Point.prototype, {
toString(){},
toValue(){}
});
prototype对象的constructor()属性,直接指向“类”的本身,这与 ES5 的行为是一致的
Point.prototype.constructor === Point // true
另外,类的内部所有定义的方法,都是不可枚举的(non-enumerable)
静态方法
- 类相当于实例的原型,所有在类中定义的方法,都会被实例继承
- 如果在一个方法前,加上static关键字,就表示该方法不会被实例继承,而是直接通过类来调用,这就称为“静态方法”
class Foo {
static classMethod() {
return 'hello';
}
}
Foo.classMethod() // 'hello'
var foo = new Foo();
foo.classMethod()
// TypeError: foo.classMethod is not a function
如果静态方法包含this关键字,这个this指的是类,而不是实例
Class的继承
Class 可以通过extends关键字实现继承,让子类继承父类的属性和方法
class Point { /* ... */ }
class ColorPoint extends Point {
constructor(x, y, color) {
super(x, y); // 调用父类的constructor(x, y)
this.color = color;
}
toString() {
return this.color + ' ' + super.toString(); // 调用父类toString()
}
}
上面示例中,constructor()方法和toString()方法内部,都出现了super关键字
- super在这里表示父类的构造函数,用来新建一个父类的实例对象
- ES6 规定,子类必须在constructor()方法中调用super(),否则就会报错
- 因为子类自己的this对象,必须先通过父类的构造函数完成塑造,得到与父类同样的实例属性和方法,然后再对其进行加工,添加子类自己的实例属性和方法
- 如果不调用super()方法,子类就得不到自己的this对象
- 在子类的构造函数中,只有调用super()之后,才可以使用this关键字,否则会报错(这是因为子类实例的构建,必须先完成父类的继承,只有super()方法才能让子类实例继承父类)
get()和set()
class Phone{
get price(){
console.log('价格属性被读取了')
return 'here'
}
set price(newVal){
console.log('价格属性被修改了')
}
}
let s = new Phone()
console.log(s.price) //价格属性被读取了 here
s.price = 'free' //价格属性被修改了
- 利用get()方法创建类里的某属性,当读取实例的该属性时会自动执行get()方法,get()方法的返回值就是该属性的值
- 利用set()方法可以设置属性修改时的执行代码,当属性值被设置时会执行set()方法,注意set()方法需要有形参
1.16数值扩展
1.Number.EPSION
Number.EPSION是JS表示的最小精度
function equal(a,b){
if(Math.abs(a-b)<Number.EPSILON){
return true
}else{
return false
}
}
console.log(equal(0.1+0.2,0.3)) //true
console.log(0.1+0.2 === 0.3) //false
2.二进制和八进制
ES6 提供了二进制和八进制数值的新的写法,分别用前缀0b(或0B)和0o(或0O)表示
0b111110111 === 503 // true
0o767 === 503 // true
从 ES5 开始,在严格模式之中,八进制就不再允许使用前缀0表示,ES6 进一步明确,要使用前缀0o表示
// 非严格模式
(function(){
console.log(0o11 === 011);
})() // true
// 严格模式
(function(){
'use strict';
console.log(0o11 === 011);
})() // Uncaught SyntaxError: Octal literals are not allowed in strict mode.
如果要将0b和0o前缀的字符串数值转为十进制,要使用Number方法
Number('0b111') // 7
Number('0o10') // 8
3.Number,isFinite
检测一个数是否为有限数
console.log(Number.isFinite(100)) //true
console.log(Number.isFinite(100/0)) //false
console.log(Number.isFinite(Infinity)) //false
4.Number.isNaN
检测一个数是否为NaN
Number.isNaN(NaN) // true
Number.isNaN(15) // false
Number.isNaN('15') // false
Number.isNaN(true) // false
Number.isNaN(9/NaN) // true
Number.isNaN('true' / 0) // true
Number.isNaN('true' / 'true') // true
Number.isFinite()对于非数值一律返回false Number.isNaN()只有对于NaN才返回true,非NaN一律返回false
5.Number.parseInt Number.parseFloat
字符串转整数/字符串转浮点数
console.log(Number.parseInt('523143asg')) //523143
console.log(Number.parseFloat('3.1415926535后面还有')) //3.1415926535
6.Number.isInteger
判断一个数是否为整数
console.log(Number.isInteger(5.13)) //false
7.Math.trunc
将小数部分抹掉
console.log(Math.trunc(5.13)) //5
8.Math.sign
判断一个数为正数负数还是0
console.log(Math.sign(100)) //1
console.log(Math.sign(0)) //0
console.log(Math.sign(-100)) //-1
1.17对象方法扩展
1.Object.is()
ES6 提出“Same-value equality”(同值相等)算法,用来解决这个问题 Object.is就是部署这个算法的新方法 它用来比较两个值是否严格相等,与严格比较运算符(===)的行为基本一致 不同之处只有两个:一是+0不等于-0,二是NaN等于自身
Object.is('foo', 'foo') // true
Object.is({}, {}) // false
object.is(NaN,NaN) //true
console,log(NaN===NaN) //false
Object.is(+0,-0) //false
console,log(+0===-0) //true
2.Object.assign()
基本用法 Object.assign()方法用于对象的合并,将源对象(source)的所有可枚举属性,复制到目标对象(target)
const target = { a: 1 };
const source1 = { b: 2 };
const source2 = { c: 3 };
Object.assign(target, source1, source2);
target // {a:1, b:2, c:3}
Object.assign()方法的第一个参数是目标对象,后面的参数都是源对象 如果目标对象与源对象有同名属性,或多个源对象有同名属性,则后面的属性会覆盖前面的属性
const target = { a: 1, b: 1 };
const source1 = { b: 2, c: 2 };
const source2 = { c: 3 };
Object.assign(target, source1, source2);
target // {a:1, b:2, c:3}
如果只有一个参数,Object.assign()会直接返回该参数。
const obj = {a: 1};
Object.assign(obj) === obj // true
如果该参数不是对象,则会先转成对象,然后返回 由于undefined和null无法转成对象,所以如果它们作为参数,就会报错
typeof Object.assign(2) // "object"
Object.assign(undefined) // 报错
Object.assign(null) // 报错
如果非对象参数出现在源对象的位置(即非首参数),那么处理规则有所不同。首先,这些参数都会转成对象,如果无法转成对象,就会跳过。这意味着,如果undefined和null不在首参数,就不会报错
Object.assign()拷贝的属性是有限制的,只拷贝源对象的自身属性(不拷贝继承属性),也不拷贝不可枚举的属性(enumerable: false)
Object.assign({b: 'c'},
Object.defineProperty({}, 'invisible', {
enumerable: false,
value: 'hello'
})
)
// { b: 'c' }
上面代码中,Object.assign()要拷贝的对象只有一个不可枚举属性invisible,这个属性并没有被拷贝进去
属性名为 Symbol 值的属性,也会被Object.assign()拷贝
Object.assign({ a: 'b' }, { [Symbol('c')]: 'd' })
// { a: 'b', Symbol(c): 'd' }
3.proto属性
JavaScript 语言的对象继承是通过原型链实现的 ES6 提供了更多原型对象的操作方法
proto属性 proto属性(前后各两个下划线),用来读取或设置当前对象的原型对象(prototype) 实现上,proto调用的是Object.prototype.proto,具体实现如下 如果一个对象本身部署了proto属性,该属性的值就是对象的原型
Object.setPrototypeOf()
用来设置一个对象的原型对象(prototype),返回参数对象本身( ES6 正式推荐的设置原型对象的方法)
// 格式
Object.setPrototypeOf(object, prototype)
// 用法
const o = Object.setPrototypeOf({}, null);
该方法等同于下面的函数
function setPrototypeOf(obj, proto) {
obj.__proto__ = proto;
return obj;
}
下面是一个例子
let proto = {};
let obj = { x: 10 };
Object.setPrototypeOf(obj, proto);
proto.y = 20;
proto.z = 40;
obj.x // 10
obj.y // 20
obj.z // 40
上面代码将proto对象设为obj对象的原型,所以从obj对象可以读取proto对象的属性
如果第一个参数不是对象,会自动转为对象,但是由于返回的还是第一个参数,所以这个操作不会产生任何效果
Object.setPrototypeOf(1, {}) === 1 // true
Object.setPrototypeOf('foo', {}) === 'foo' // true
Object.setPrototypeOf(true, {}) === true // true
由于undefined和null无法转为对象,所以如果第一个参数是undefined或null,就会报错
Object.getPrototypeOf()
该方法与Object.setPrototypeOf方法配套,用于读取一个对象的原型对象 Object.getPrototypeOf(obj); 下面是一个例子
function Rectangle() {
// ...
}
const rec = new Rectangle();
Object.getPrototypeOf(rec) === Rectangle.prototype
// true
Object.setPrototypeOf(rec, Object.prototype);
Object.getPrototypeOf(rec) === Rectangle.prototype
// false
如果参数不是对象,会被自动转为对象 如果参数是undefined或null,它们无法转为对象,所以会报错
1.18模块化
将一个大的程序文件拆分成许多小的文件,然后将小文件组合起来
模块化的好处
- 防止命名冲突
- 代码复用
- 高维护性
模块化规范产品
- CommondJS => NodeJS、Browserify
- AMD => requireJS
- CMD =>seaJS
ES6模块化语法
模块功能主要由两个命令组成:export和import
- export命令用于规定模块的对外接口
- import命令用于输入其他模块提供的功能
export
一个模块就是一个独立的文件 该文件内部的所有变量,外部无法获取 如果希望外部能够读取模块内部的某个变量,就必须使用export关键字输出该变量 下面是一个 JS 文件,里面使用export命令输出变量。
// profile.js
export var firstName = 'Michael';
export var lastName = 'Jackson';
export var year = 1958;
上面代码是profile.js文件,保存了用户信息。ES6 将其视为一个模块,里面用export命令对外部输出了三个变量。
export的写法,除了像上面这样,还有另外一种。
// profile.js
var firstName = 'Michael';
var lastName = 'Jackson';
var year = 1958;
export { firstName, lastName, year };
上面代码在export命令后面,使用大括号指定所要输出的一组变量 它与前一种写法是等价的,但是应该优先考虑使用这种写法 因为这样就可以在脚本尾部,一眼看清楚输出了哪些变量
export命令除了输出变量,还可以输出函数或类(class)
export function multiply(x, y) {
return x * y;
};
上面代码对外输出一个函数multiply
通常情况下,export输出的变量就是本来的名字,但是可以使用as关键字重命名
function v1() { ... }
function v2() { ... }
export {
v1 as streamV1,
v2 as streamV2,
v2 as streamLatestVersion
};
上面代码使用as关键字,重命名了函数v1和v2的对外接口 重命名后,v2可以用不同的名字输出两次
export命令规定的是对外的接口,必须与模块内部的变量建立一一对应关系
// 报错
export 1;
// 报错
var m = 1;
export m;
上面两种写法都会报错,因为没有提供对外的接口 第一种写法直接输出 1,第二种写法通过变量m,还是直接输出 1 1只是一个值,不是接口 正确的写法是下面这样:
// 写法一
export var m = 1;
// 写法二
var m = 1;
export {m};
// 写法三
var n = 1;
export {n as m};
上面三种写法都是正确的,规定了对外的接口m 其他脚本可以通过这个接口,取到值1 它们的实质是,在接口名与模块内部变量之间,建立了一一对应的关系
同样的,function和class的输出,也必须遵守这样的写法
// 报错
function f() {}
export f;
// 正确
export function f() {};
// 正确
function f() {}
export {f};
另外,export语句输出的接口,与其对应的值是动态绑定关系,即通过该接口,可以取到模块内部实时的值
export var foo = 'bar';
setTimeout(() => foo = 'baz', 500);
上面代码输出变量foo,值为bar,500 毫秒之后变成baz export命令可以出现在模块的任何位置,只要处于模块顶层就可以 如果处于块级作用域内,就会报错,import命令也是如此 这是因为处于条件代码块之中,就没法做静态优化了,违背了 ES6 模块的设计初衷
function foo() {
export default 'bar' // SyntaxError
}
foo()
上面代码中,export语句放在函数之中,结果报错
import 命令
使用export命令定义了模块的对外接口以后,其他 JS 文件就可以通过import命令加载这个模块
// main.js
import { firstName, lastName, year } from './profile.js';
function setName(element) {
element.textContent = firstName + ' ' + lastName;
}
上面代码的import命令,用于加载profile.js文件,并从中输入变量 import命令接受一对大括号,里面指定要从其他模块导入的变量名 大括号里面的变量名,必须与被导入模块(profile.js)对外接口的名称相同
如果想为输入的变量重新取一个名字,import命令要使用as关键字,将输入的变量重命名
import { lastName as surname } from './profile.js';
import命令输入的变量都是只读的,因为它的本质是输入接口 也就是说,不允许在加载模块的脚本里面,改写接口
import {a} from './xxx.js'
a = {}; // Syntax Error : 'a' is read-only;
上面代码中,脚本加载了变量a,对其重新赋值就会报错,因为a是一个只读的接口 但是,如果a是一个对象,改写a的属性是允许的
import {a} from './xxx.js'
a.foo = 'hello'; // 合法操作
上面代码中,a的属性可以成功改写,并且其他模块也可以读到改写后的值 不过,这种写法很难查错,建议凡是输入的变量,都当作完全只读,不要轻易改变它的属性
import后面的from指定模块文件的位置,可以是相对路径,也可以是绝对路径 如果不带有路径,只是一个模块名,那么必须有配置文件,告诉 JavaScript 引擎该模块的位置
import { myMethod } from 'util';
上面代码中,util是模块文件名,由于不带有路径,必须通过配置,告诉引擎怎么取到这个模块
注意,import命令具有提升效果,会提升到整个模块的头部,首先执行
foo();
import { foo } from 'my_module';
上面的代码不会报错,因为import的执行早于foo的调用 这种行为的本质是,import命令是编译阶段执行的,在代码运行之前
由于import是静态执行,所以不能使用表达式和变量,这些只有在运行时才能得到结果的语法结构
// 报错
import { 'f' + 'oo' } from 'my_module';
// 报错
let module = 'my_module';
import { foo } from module;
// 报错
if (x === 1) {
import { foo } from 'module1';
} else {
import { foo } from 'module2';
}
上面三种写法都会报错,因为它们用到了表达式、变量和if结构 在静态分析阶段,这些语法都是没法得到值的
最后,import语句会执行所加载的模块,因此可以有下面的写法
import 'lodash';
上面代码仅仅执行lodash模块,但是不输入任何值
如果多次重复执行同一句import语句,那么只会执行一次,而不会执行多次
import 'lodash';
import 'lodash';
上面代码加载了两次lodash,但是只会执行一次
import { foo } from 'my_module';
import { bar } from 'my_module';
// 等同于
import { foo, bar } from 'my_module';
上面代码中,虽然foo和bar在两个语句中加载,但是它们对应的是同一个my_module模块
模块的整体加载
除了指定加载某个输出值,还可以使用整体加载,即用星号(*)指定一个对象,所有输出值都加载在这个对象上面
下面是一个circle.js文件,它输出两个方法area和circumference
// circle.js
export function area(radius) {
return Math.PI * radius * radius;
}
export function circumference(radius) {
return 2 * Math.PI * radius;
}
现在,加载这个模块
// main.js
import { area, circumference } from './circle';
console.log('圆面积:' + area(4));
console.log('圆周长:' + circumference(14));
上面写法是逐一指定要加载的方法,整体加载的写法如下
import * as circle from './circle';
console.log('圆面积:' + circle.area(4));
console.log('圆周长:' + circle.circumference(14));
注意,模块整体加载所在的那个对象(上例是circle),应该是可以静态分析的,所以不允许运行时改变 下面的写法都是不允许的
import * as circle from './circle';
// 下面两行都是不允许的
circle.foo = 'hello';
circle.area = function () {};
1.19 Babel
Chapter2 ES7
2.1Array.prototypr.includes
includes方法用于检测数组中是否包含某个元素,返回布尔值
const arr = [1,2,3,4]
console.log(arr.includes(1))//true
console.log(arr.includes(5))//false
2.2指数操作符
指数运算符**,用于实现幂运算,功能与Math.pow相同
console.log(2**10)//1024
Chapter3 ES8
3.1 async与await
async函数
ES8标准引入了 async 函数,使得异步操作变得更加方便 async 函数是什么?
一句话,它就是 Generator 函数的语法糖
- async函数的返回值是promise对象
- promise对象的结果由async函数的返回值决定
//async函数
async function fn(){
//只要返回的不是一个Promise对象,则函数返回结果为成功的Promise
//return 'lapi'
//return
//抛出错误
//throw new Error('出错啦') //返回一个失败的Promise
//返回的是一个Promise对象,返回的结果与Promise的结果相同
return new Promise((resovle,reject)=>{
//resovle('成功的数据')
reject("失败")
})
}
const result = fn()
console.log(result)
await函数
- await必须写在async函数中
- await右侧的表达式一般为promise对象
- await返回的值是promise成功的值
- await 的promise失败了,就会抛出异常,需要通过try...catch捕获处理
//创建一个promise对象
const p = new Promise((resolve,reject)=>{
//resolve('won!')
reject("No!")
})
//await
async function main(){
try{
let result = await p
console.log(result)
} catch (e){
console.log(e)
}
}
main()
3.2 对象方法扩展
Object.values()和Object.entries()
- Object.values()方法返回一个给定对象的所有可枚举属性值的数组
- Object.entries()方法返回一个给定对象自身可遍历属性[key,value]的数组 返回的是数组,数组的每一个元素也是一个数组,内容是键值对
const school = {
name: 'jlu',
cities: ['北京', '上海', '长春'],
course: ['微店', '电工', '电信', '电科']
}
let a = Object.keys(school)//获取对象所有的键 'name','cities','course'
let b = Object.values(school)//获取对象所有的值 'jlu',Array(3),Array(4)
let c = Object.entries(school)//获取对象所有的键值对 Array(2),Array(2),Array(2) 每个数组内容均是键值对
console.log(c)
const m = new Map(c)
console.log(m)
let city = m.get('cities')
console.log(city) //'北京','上海','长春'
Object.getOwnPropertyDescriptors
该方法返回指定对象所有自身属性的描述对象
Chapter4 ES9
4.1扩展运算符和rest参数
4.2正则扩展
4.2.1命名捕获分组
let str = '<a href="vxsgu">你好</a>'
let reg = /<a href="(?<url>.*)">(?<text>.*)</a>/
const result = reg.exec(str)
console.log(result) //groups{url:vxsgu,text:你好}
4.2.2反向断言
let str = 'JS5211314uknow555lalala'
//正向断言
// const reg = /\d+(?=l)/
// const result = reg.exec(str)
//反向断言
const reg = /(?<=w)\d+/
const result = reg.exec(str)
console.log(result);
4.2.3dotAll模式
let str = `
<ul>
<li>
<a>肖申克的救赎</a>
<p>上映日期:1994-09-10</p>
</li>
<li>
<a>阿甘正传</a>
<p>上映日期:1994-07-06</p>
</li>
</ul>`
// const reg = /<li>\s+<a>(.*?)</a>\s+<p>(.*?)</p>/
const reg = /<li>.*?<a>(.*?)</a>.*?<p>(.*?)</p>/s
const result = reg.exec(str)
console.log(result);
Chapter5 ES10
5.1对象扩展方法Object.fromEntries()
将二维数组或Map转换为对象
//二维数组
const result = Object.fromEntries([
['name','lapi'],
['age','20']
])
//Map
const m = new Map()
m.set('name','lapi')
const result2 = Object.fromEntries(m)
console.log(result);
console.log(result2);
5.2字符串扩展方法trimStart与trimEnd
//trim用于清除字符串两侧空白
let str = ' lapi '
console.log(str); // lapi .
console.log(str.trimStart()); //lapi .
console.log(str.trimEnd()); // lapi.
5.3数组方法扩展flat与flatMap
//flat将多维数组转化为低维数组
const arr = [[1,2],[3,4],[5,6]]
console.log(arr.flat()); //[1,2,3,4,5,6]
const arr1 = [[1, 2,[11,22]], [3, 4], [5, 6]]
console.log(arr1.flat()); //[1,2,Array(2),3,4,5,6]
//可以传递参数表示降低的深度,默认为1
console.log(arr1.flat(2)) //[1,2,11,22,3,4,5,6]
flatMap()方法对原数组的每个成员执行一个函数(相当于执行Array.prototype.map()),然后对返回值组成的数组执行flat()方法 该方法返回一个新数组,不改变原数组
// 相当于 [[2, 4], [3, 6], [4, 8]].flat()
[2, 3, 4].flatMap((x) => [x, x * 2])
// [2, 4, 3, 6, 4, 8]
flatMap()只能展开一层数组。
5.4Symbol.prototype.description
用于获取Symbol的字符串描述
let s = Symbol('lapi')
console.log(s.description) //lapi
Chapter6 ES11
6.1私有属性
私有属性只能在类的内部进行操作,不能在外部操作
class Person{
//公有属性
name
//私有属性
#age
#weight
constructor(name,age,weight){
this.name = name
this.#age = age
this.#weight = weight
}
}
const one = new Person('lapi',18,'45kg')
console.log(one.name);
console.log(one.#age); //报错
6.2Promise.allSettled方法
const p1 = new Promise((resolve,reject)=>{
setTimeout(()=>{
resolve('number - 1')
},1000)
})
const p2 = new Promise((resovle,reject)=>{
setTimeout(()=>{
reject('No!')
},1000)
})
//调用allSettled方法
const result = Promise.allSettled([p1,p2])
//console.log(result)
//返回的值始终是成功的,成功的值是每一个Promise对象的结果
const res = Promise.all([p1,p2])
console.log(res)
//只有每一个promise都成功返回值才是成功,若失败失败的值就是里面失败的promise的值,若成功值与上相同
6.3String.prototypr.macthAll方法
let str = `
<ul>
<li>
<a>肖申克的救赎</a>
<p>上映日期:1994-09-10</p>
</li>
<li>
<a>阿甘正传</a>
<p>上映日期:1994-07-06</p>
</li>
</ul>`
//声明正则
const reg = /<li>.*?<a>(.*?)</a>.*?<p>(.*?)</p>/sg
//调用方法
const result = str.matchAll(reg)
console.log(result)
// for(let v of result){
// console.log(v)
// }
const arr = [...result]
console.log(arr)
6.4可选链操作符
6.5动态import
//按需加载
const btn = document.getElementById('btn')
btn.onclick = function(){
import('./hello.js').then(module=>{
moudle.hello()
})
}
export function hello(){
alert('hello')
}
6.6BigInt类型
//大整形
let n = 521n
console.log(n,typeof n)
//函数
let nn = 123
console.log(BigInt(nn))
//大数值运算
let max = Number.MAX_SAFE_INTEGER
console.log(max) //9007199254740991
console.log(max+1) //9007199254740992
console.log(max+2) //9007199254740992
console.log(BigInt(max)+BigInt(3)) //9007199254740994n
6.7绝对全局对象globalThis
始终指向全局对象