ECMAScript 5
变量
变量定义
- 命名规则:字母,数字,下划线和$,中文 不允许数字开头
- 命名规范:小驼峰命名法
- 声明变量可以使用
var
关键字,无需指定数据类型,js本身就是动态类型语言 - 也可以不写
var
关键字也是一样可以声明变量,但是建议写上 - 声明变量未赋值时,值是
undefind
- 声明相同的变量名,第二次会覆盖掉第一次的值,但是不会是
undefind
变量提升
js引擎解析代码时,会将所有被声明的变量(包括函数)都提升到当前作用域的头部
console.log(a); //undefined
var a = 1;
//相当于
var a;
console.log(a); //undefind
a = 1;
变量作用域
js中没有块级作用域,只有函数作用域,所以使用var
声明的变量,除拉是当前函数作用域就是全局作用域
{
var a = 1;
}
console.log(a); //1
function fun() {
var a = 1;
}
console.log(a); //a is not defind
var a = 1;
(function(){
console.log(a); //1
})(); //此写法是立即执行函数,可以当作代码块使用,就是写法上有点别扭
数据类型
typeof运算符
- 用来检测变量的数据类型的运算符,返回数据类型的字符串名字
- 对于原始数据类型来说总是返回他的数据类型名
- 对于引用数据类型(合成数据类型)来说,就不一定了,有可能是大的引用数据类型(object),也有可能是具体的什么引用数据类型(合成数据类型)
- 对于两个特殊的数据类型
null
和undefined
来说有特殊的历史原因问题,下面讲- 可以有两种形式普通形式
typeof 变量
和函数形式typeof(变量)
,函数形式的括号只是提升后面表达式优先级的作用,并不是真正意义上的函数
null与undefined
其实本质上这两个特殊数据类型表达的是一个意思,由于历史原因,
null
被留下,一般null
用于指该变量要存放的对象数据类型时赋初值时采用,undefined
用来存放原始数据类型,不必赋初值自动初始化为该值由于
==
是可以发生数据类型自动转换的,所以是true,===
严格相等需要数据类型也一致,这两个数据类型就不一致所以是false这两个数据类型的唯一的不同是:
null
可以自动转换为数字0,而nudefined
只能转换为NaN
console.log(typeof undefined); //undefined
console.log(typeof null); //object
console.log(undefined == null); //true
console.log(undefined === null); //false
console.log(Number(null)); //0
console.log(Number(undefined)); //NaN
布尔值
- 布尔数据类型只有
true
和false
两种状态,true
会转化为1,false
会转化为0
console.log(typeof true); //boolean
console.log(typeof false); //boolean
console.log(Number(true)); //1
console.log(Number(false)); //0
数值
- 整数和浮点数都是数值类型,都是使用64位浮点数来表示的,所以
1==1.0
是true- 整型的科学计数法整数的不同进制表示同其他语言,不过要注意的是八进制若有超过八的数字则会当做十进制来处理,而并不会报错
- NaN:表示非数字,主要出现在字符串无法转换为数字时的特殊值,使用
isNaN()
函数来判断是否是NaN
0/0
的结果也是NaN- NaN不等于、不大于和不小于任何值,包括他本身,换言之NaN做任何逻辑计算都是
false
- NaN与任何数做计算都是NaN,包括自己
- Infinity:表示无穷,得到无穷数值溢出,和非零数值除零的结果,使用
isFinite()
函数判断是否是有限数(包括NaN)
- 有正负之分,正无穷与负无穷是不相等的,符合数学上负负得正的规则(唯一区分正零和负零地方)
- 他的计算符合数学规则,若数学计算错误时返回NaN
console.log(typeof 0); //number
console.log(typeof 0.1); //number
console.log(0.1 == 1); //true
console.log(typeof NaN); //number
console.log(NaN == NaN); //false
console.log(NaN + NaN); //NaN
console.log(NaN + 1); //NaN
console.log(typeof Infinity); //number
console.log(1/0); //Infinity
console.log(Math.pow(2,1024)); //Infinity
console.log(Infinity == -Infinity); //false
console.log(isNaN(NaN)); //true
console.log(isNaN("")); //false
//同等于
console.log(isNaN(Number(""))); //false
console.log(isNaN([])); //false
//同等于
console.log(isNaN(Number([]))); //false
console.log(isFinite(Infinity)); // false
console.log(isFinite(-Infinity) );// false
console.log(isFinite(NaN)); // false
console.log(isFinite(undefined)); // false
//同等于
console.log(isFinite(Number(undefined))); //false
console.log(isFinite(null)); // true
//同等于
console.log(isFinite(Number(null))); //true
console.log(isFinite(0)); // true
字符串
- 使用单引号或双引号进行包裹
- 可以使用字符数组形式对字符串中的字符进行访问
- 也可以使用字符数组形式的length属性可以获得字符串的长度,该属性无法更改,因为他是常量,操作会默默失效
- 无法对字符串内部进行增删改,因为他是常量,这些操作会默默的失效,并不会报错
console.log(typeof ""); //string
var str = "hello";
console.log(str[0]); //h
str.length = 100;
console.log(str.length); //5
str[0] = "H";
str[5] = "!";
delete str[1];
console.log(str); //hello
对象
- 是一种特殊的Map集合
- 键名都是字符串类型,所以即时不加字符串的引号包裹也会自动将其转换为字符串,但是不能使用字符串拼接的形式
- 键值可以存放任意数据类型
- 可以说js对象中只有属性,没有方法,因为函数也是一种特殊的对象数据类型
- 与其他语言一样,对象变量中保存的都是内存地址,所以有对象引用的特性
- 由于创建对象的字面量方式与代码块一样,所以当出现二义性时js引擎在解析的时候会将大括号一律解析成代码块,若想解析成为对象形式则需要手动在大括号外部增加小括号
对象的声明
- 字面量方式:可以动态的位对象的属性进行赋值和修改操作,也可以在初始化时指定属性,使用分号隔开键值,多个键值使用逗号分隔
var obj1 = { //定义有内容的对象
"p":"p",
"p1":1
};
//等价于
var obj1 = { //定义有内容的对象
p:"p",
p1:1
};
//var obj1 = { //定义有内容的对象
// p:"p",
// "p"+"1":1 //报错,不能使用表达式
//};
var obj = {}; //定义空对象
obj.p = "old"; //增加属性操作
obj.p = "new"; //修改属性操作
new
关键字方式:由构造方法创建的实例对象
console.log(typeof new Function()); //function,不难看出function是及其特殊的对象
console.log(typeof new Array()); //object
console.log(typeof new Object()); //object
console.log(typeof new String()); //object
console.log(typeof new Number()); //object
console.log(typeof new Boolean()); //object
console.log(typeof new RegExp()); //object
console.log(typeof /s/); //object
var a = new Number(1) + new Number(1); //这就是基本类型的包装类了
console.log(a); //2
对象的属性操作
- 点运算符方式修改添加操作:只用用于没有二义性的非数字开头的属性
- 方括号运算符方式修改添加操作:键名使用引号括起来的字符串,除非是数值型(包括小数)的键名可以不使用引号,就像数组那样
var obj = {
p1:"p",
1:"number"
};
obj.p1 = "new";
// obj.1 = "new number"; //点有二义性,说不清是小数点还是运算符,所以是非法的
obj["1"] = "new number";
obj["p"+"1"] = "new"; //可以使用表达式
obj[1] = "new number"; //会自动转化为字符串
delete 对象.属性
命令删除对象属性操作:- 要注意的是删除命令只有在该属性无法被删除时才返回
false
- 并不能删除全局变量
- 要注意的是删除命令只有在该属性无法被删除时才返回
var obj = {
p:"p",
p1:1
};
delete obj.p; //删除成功返回true,若这个属性不存在也会返回true,因为删除掉也是不存在了嘛
var a = 1;
console.log(a); //1
delete a;
console.log(a); //1
键名 in 对象
运算符:查看该对象是否有该属性,包括继承的,不包括不可枚举的
var obj = {
p:"p",
1:"number"
};
console.log("p" in obj); //true
console.log("0" in obj); //false
for ... in
循环可遍历对象的属性:基于in
操作符,只能遍历可列举的属性,也会遍历继承的属性
var obj = {
p:"p",
1:"number"
};
for (var key in obj) {
console.log(key + ":" +obj[key]);
}
//1:number
//p:p
//可以看出并不是按照书写的顺序进行输出的,而是内部经过排序之后的顺序输出的
for (const key in obj) {
if (obj.hasOwnProperty(key)) { //使用继承自object的方法判断这个属性是否是继承的,此时只会遍历出非继承的方法了
console.log(key + ":" +obj[key]);
}
}
Object.keys(obj)
方法获取该对象的所有键名组成是数组,不包括继承的
var obj = {
p:"p",
1:"number"
};
var keys = Object.keys(obj); //["p","1"]
函数
和其他编程语言一样,函数是一组可以重复利用的代码块,通过圆括号内写参数进行调用,可以有参数和返回值,不同的是:
- 由于js是弱类型语言所以无需定义参数和返回值的数据类型
- 传递参数时,数量不匹配也没有关系,会用
undefined
来代替 - 当没有返回值时则返回
undefined
,一个函数可以返回不同的数据类型 - js不像c++、Java那样具有方法的重载,若有同名函数则会直接覆盖掉原来的函数
函数是极奇特殊的一种对象,其地位与其他任何一种数据类型都是平等的,所以他可以作为函数的参数和返回值
console.log(typeof function(){}); //function
var fun = function(){};
console.log(typeof fun); //function
console.log(typeof new Function()); //function
console.log(typeof Function()); //function
函数的声明
function
命令方式
function funName(){ //命令方式必须指定函数名
//code
}
- 函数表达式方式
var fun = function(){ //此方法无需指定函数名,由变量名代替,若指定函数名也只能在该函数内部使用,即递归调用
//或者是将此变量名和函数名使用同样的标识符,这样函数调用栈会显示函数名,而不是匿名函数
//code
}; //由于是表达式方式,这是一条语句,建议加一个分号以示区别
Function
构造方式,如果不使用new
关键字来构造直接使用也是可以的,但是不够直观,所以建议加上new
关键字
var fun = new Function("str","console.log(str);return 0"); //最后一个参数是执行体,前面的参数都是该函数的形参
var result = fun("hello"); //hello
console.log(result); //0
//可以看出此方法唯一的好处就是可以将字符串作为构造成一个函数,某些情况下就需要使用他
函数的属性和方法
name
属性:返回当前函数的函数名,是无法动态修改的,他唯一的用途就是获取函数的真实的名字了
function fun(){ //命令方式
}
console.log(fun.name); //fun,返回函数名
var fun = function(){ //表达式方式
};
console.log(fun.name); //fun,返回变量名
var fun = function(){
};
var newFun = fun //指向的是同一块空间
console.log(newFun.name); //fun,返回函数第一次的变量名
var fun = function funInner(){ //表达式方式同时又指定了函数名
};
fun.name = "newFun"; //无法修改,默默失效
console.log(fun.name); //funInner,返回函数名
console.log((new Function).name); //anonymous,含义也是匿名的
console.log(function(){}.name); //匿名函数返回空字符串
console.log(function fun(){}.bind(null).name); //bound fun 会加上bound前缀
length
属性:返回当前函数的定义时的形参个数,是无法动态修改的
function fun(){
}
console.log(fun.length); //0,
/*将下面代码注释掉才会是0,
因为函数也是一种数据类型,函数名也是变量,
所以也存在变量提升,若不注释则会被第二个函数覆盖掉,所以length是1*/
function fun(a){
}
fun.length = 100; //无法修改,默默失效
console.log(fun.length); //1
toString()
方法:返回函数的源码,对于系统函数,则会返回function (){[native code]}
function fun(){
}
console.log(fun.toString());
/*
function fun(){
}
*/
console.log(Object.toString());
//function Object() { [native code] }
参数
与其他语言一样,函数参数传递都是值传递,不同的是,允许出现同名的参数,但是参数同名只会采用靠后的参数,当然一般人也不会这么做
arguments
对象:运行时的所有参数,他有类似数组拥有length
属性,但又不是数组,因为不能使用数组中的一些方法,若想要使用数组方法则需要将其转换为数组
function fun(){
console.log(arguments[0]);
console.log(arguments[1]);
console.log("运行时参数个数"+arguments.length+"、定义时的参数个数"+fun.length); //与函数的length属性不同的是,获取的是运行时的形参个数
}
fun(0);
/*
0
undefined
运行时参数个数1、定义时的参数个数0
*/
fun(0,1);
/*
0
1
运行时参数个数2、定义时的参数个数0
*/
立即执行函数
其实就是定义函数后立即调用该函数,好处是:不必为函数起名,单独的作用域,不会污染全局变量
//function(){}();
//这种方式会报错
//原因是function关键字既可以当表达式又可以当语句,所以当function出现在行首时会解析成语句,只有表达式才能立即执行
(function(){})(); //一般采取这种形式
!function(){}(); //等等等,其他形式不一一列举了
//所以立即执行函数必须将function不放在行首即可,这样就能解析成语句了
//要注意的是语句一定要带有分号结尾,否则多个立即执行函数会报错
// (function(){})()
// (function(){})()
// //TypeError: (intermediate value)(...) is not a function
闭包
js不像其他编程语言,他是可以在函数中定义函数,js是有函数作用域的,然而这些函数的嵌套定义就形成了,内部函数能访问外部函数的变量,而外部函数无法访问内部函数的变量,这就形成了一个作用域链,这就是作用域的闭包
若将内部函数用某种手段暴露给外部时,只要外部存储这个内部函数的变量的指向不变,则这个内部函数依赖的宿主函数会一直存在内存中,所以此时外部调用内部函数就可以使用宿主函数的变量
function fun(i) { //宿主函数
return function () { //返回函数是用了宿主函数作用域中的变量
console.log(i++);
};
}
var f = fun(5);
f() // 5
f() // 6
f() // 7
f = null; //将函数指向丢弃,刚才返回的函数会被垃圾回收机制回收
f = fun(5); //重新调用就是一个新的宿主环境
f() // 5
f() // 6
f() // 7
由于暴露出去的内部函数可以使用宿主函数的变量,就会导致若内部函数肯定比宿主函数后执行,若同时暴露出去多个内部函数,并且这多个内部函数使用同一个宿主函数的变量就会有导致冲突,这就像Java中的多线程同步问题和数据库中的事务类似,只不过这个的顺序是可见的
/*由于宿主函数先执行,内部函数后执行出现的问题*/
var funArr = [];
for (var i = 0; i < 5; i++) {
funArr[i] = function(){console.log(i);};
}
funArr[2](); //5,预期得到2,结果得到5,宿主函数先执行后,内部函数后执行,导致宿主函数执行结束后变量被修改成5
funArr[3](); //5
console.log(i); //5
/*
该问题的解决方案
为每个内层函数都套上一个立即执行函数,在这个立即执行函数中创建宿主函数变量的副本,也就是作为参数传入
这时,内部函数访问外部函数变量采用就近原则,此时的i变量就会从该立即执行函数中获取
这样写虽然简单,但是由于内部函数依赖的宿主函数会一直存在内存中,也就是会有5个立即执行函数一直存在内存中
程序的效率大大降低
*/
var funArr = [];
for (var i = 0; i < 5; i++) {
(function(i){
funArr[i] = function(){console.log(i);};
})(i);
}
funArr[2](); //2
funArr[3](); //3
回调函数与闭包
函数也是一种数据类型,所以函数也可以作为函数的参数进行传递,一个外部函数的参数是函数,,因为要在这个外部函数内部要执行这个参数传递的函数,所以这个参数传递函数就称为回调函数
外部函数调用回调函数时将内部变量以参数形式传递给回调函数,回调函数中就可以直接操作外部函数中定义的变量;此时执行的顺序其实是内部函数先执行结束,外部函数后执行结束,这属于同步的回调
function fun(callback){
let val = 100;
callback(val);
}
fun(function(val){
console.log(val); // 100
})
数组
与其他编程语言不同的是,js中的数组是一种特殊的对象类型,只不过键名是从零开始的正整数,所以数组中可以存放不同类型的数据,js对象的属性操作,在js数组中同样适用,但是要注意一下几点
delete
删除成员只会留下空位,不会影响length
属性for ... in ...
循环会遍历出为数组添加的非下标键,对于数组的遍历不推荐使用此方式
console.log(typeof []); //object
["a",97,true,new Function()]; //都是合法的
var arr = [1,2,3];
console.log(arr[1]); //2
console.log(arr[1.0]); //2
console.log(arr["1"]); //2
console.log(arr["1.0"]); //undefined,会当作字符串1.0的键名来寻找,而不会转换成数字
// console.log(arr.1); //报错,存在点符号二义性
数组的声明和初始化
/*动态初始化*/
var arr = []; //声明一个空数组
arr[0] = 0;
arr[1] = 1;
console.log(arr);
/*静态初始化*/
var arr = [0,1,2];
console.log(arr);
数组的length属性
- 返回数组成员的数量,该属性是一个动态的值,始终等于键名中最大整数加1
- 该属性是可写的,会将超过该属性的下标自动删除,所以清空数组将length属性置为0即可,若设置非正整数会报错
- js数组的length是一个32位的整数,表示范围是2的32次方减1个,直接修改length超过范围会报错,为数组超过这个范围的元素赋值时会将这个元素的键名转自动换为字符串
var arr = [1,2,3];
console.log(arr.length); //3
arr[3] = 4;
console.log(arr.length); //4
arr[6] = 6;
console.log(arr.length); //7
console.log(arr); //[ 1, 2, 3, 4, <2 empty items>, 6 ],导致出现两个空位,浪费空间
console.log(arr[5]) //undefined
//只是证明length属性等于键名中最大整数加1
var arr = [1,2,3];
console.log(arr); //[ 1, 2, 3 ]
arr.length = 0;
console.log(arr); //[]
arr.length = 3;
console.log(arr); //[ <3 empty items> ],自动删除后就算添加回来length也是空位
数组的空位
- 空位中存放的是
undefined
- 空位的表示是
<n empty items>
,n是连序空位的个数 - 空位会占据数组的长度计算位置
in
运算符对空位也会返回false
,如果是显示的存入是undefined
会返回true
- 一些处理数组元素的函数会忽略空位
var arr = [,,1,,,] //人为的创建空位
console.log(arr); //[ <2 empty items>, 100, <2 empty items> ]
console.log(arr[0]); //undefined
console.log(arr.length); //5
伪数组
如果一个对象的所有键名,都是正整数和零,并且有length属性,那么就成为伪数组,若要使用数组中的方法则需要转化成真正的数组,常见的伪数组对象有函数中arguments
、大多数DOM元素集、字符串
数据类型之间的转换
要区分new Number()
和Number()
方法,前者属于构造函数返回object类型,后者是普通函数返回原始类型,其他那几个也是同理
console.log(new Number(1) === new Number(1)); //false,对象地址比较
console.log(Number(1) == Number(1)); //true,返回的值比较
强制转化
函数名 | 原始数据类型 | 复合数据类型 |
---|---|---|
Number() |
忽略前后空格,若是全数字字符串转化为数字(支持科学计数法和正负号),有一个非数字字符转化为NaN ,空字符串转化为0;false 转化为0,true 转化为1;undefined 转化为NaN ;unll 转化为0 |
先调用对象本身valueOf() 方法,也就是拆箱操作,一般返回对象本身,若此方法返回的不是原始数据类型则会再调用对象本身toString() 方法,所以一般对象转化为数字是NaN ,除拉只有一个元素是的数组会被当做一个值对待,空数组是0的情况 |
parseInt() |
字符串转化为整数类型,小数部分会忽略掉;忽略前后空格,参数不是字符串会先自动转化为字符串(不支持科学计数法,支持正负号),逐个字符转化,遇到非数字字符会停止转化并返回已经转化好的部分,所以开头若是非数字字符返回NaN |
先调用String() 方法转化为字符串,所以一般对象被解析为NaN ,除拉的数组 |
parseFloat() |
字符串转化为小数类型,小数部分保留;忽略前后空格,参数不是字符串会先自动转化为字符串(支持科学计数法正负号),逐个字符转化,遇到非数字字符会停止转化并返回已经转化好的部分,所以开头若是非数字字符返回NaN |
先调用String() 方法转化为字符串,所以一般对象被解析为NaN ,除拉的数组会 |
String() |
数值、布尔值、undefined 和null 都会转化成相应的字符串形式 |
先调用对象本身的toString() 方法,一般返回[object Object] 字符串,若此方法返回不是原始数据类型,则会再调用对象本身的valueOf() 方法,所以一般对象转化成字符串是[object Object] 字符串,除拉数组 |
Boolean() |
除拉undefined 、unll 、0 、NaN 、空字符串会转为false ,其他都会转化为true |
一律转化为true |
自动转化
自动转化是在强制转化基础上的,其实就是预期得到什么数据类型,就调用相应的Number()
、String()
和Boolean()
方法,所以转化规则一致
- 不同数据类型互相运算时会自动转化
- 加法中有字符串就会转化为字符串进行拼接,没有字符串会转化成数值类型
- 其他运算一律转化为数值类型
- 对非布尔类型求布尔类型时会自动转化
- 条件判断语句中
- 发生逻辑运算
- 使用一元运算符
+
、-
时会自动转化数值类型
var obj = {};
console.log(obj+1); //[object Object]1
console.log(obj.valueOf()); //{}
console.log(obj.toString()); //[object Object]
var obj = {
valueOf:function(){return 1} //重写valueOf方法,已经返回原始数据类型,所以不会调用toString方法了
};
console.log(obj+1); //2
console.log(obj.valueOf()); //1
console.log(obj.toString()); //[object Object]
var obj = {
toString:function(){return 1} //重写toString方法
};
console.log(obj+1); //2
console.log(obj.valueOf()); //{ toString: [Function: toString] }
console.log(obj.toString()); //[object Object]
/*Date对象是一个特例,与valueOf方法无关,因为valueOf返回的是毫秒数,只会执行toString方法*/
var obj = new Date();
obj.valueOf = function () { return "valueOf" };
obj.toString = function () { return "toString" };
console.log(obj + 1); //toString1
运算符
算数运算
符号 | 含义 |
---|---|
+ | 加 |
- | 减 |
* | 乘 |
/ | 除 |
% | 取余 |
++ | 自增 |
-- | 自减 |
** | 指数(ES6) |
console.log(2 ** 4); //16
console.log(2 ** 3 ** 2) //512,右结合性
赋值运算
符号 | 含义 |
---|---|
= | 赋值 |
+=、-=、*=、/=、%= | 运算赋值 |
关系运算
严格相等不会自动的类型转化,要求数据类型一致,值相等,而非严格相等则是数据类型会自动转化,在进行等值比较,严格不相等和非严格不相等同理
对于原始数据类型的关系运算就是值比较,而对于复合数据类型比较的是地址,要注意的是若两个地址不相等则不管什么关系运算都是返回false
对于null和undefined两个特殊值,他们就想常量一样,自身与自身严格相等,所以两个都位初始化的变量存的都是undefined,所以两个未赋值的变量严格相等
console.log(1 == true) // true
//等同于1 === Number(true)或Boolean(1) === true
console.log([1, 2] == "1,2") // true
// 等同于String([1, 2]) === '1,2'
console.log(1 === 1.0); //true,都是数值类型
var obj1 = {};
var obj2 = {};
console.log(obj1 > obj2); //false
console.log(obj1 < obj2); //false
console.log(obj1 == obj2); //false
console.log(obj1 === obj2); //false
console.log(undefined === undefined); //true
console.log(null === null); //true
console.log(null == undefined); //true
console.log(null === undefined); //false
var v1;
var v2;
console.log(v1 == v2); //true
console.log(v1 === v2); //true
符号 | 含义 |
---|---|
> | 大于 |
>= | 大于等于 |
< | 小于 |
<= | 小于等于 |
!= | 不等于 |
!== | 严格不相等 |
== | 等于 |
=== | 严格相等 |
逻辑运算
符号 | 含义 |
---|---|
! | 非 |
&& | 短路与 |
|| | 短路或 |
位运算
符号 | 含义 |
---|---|
& | 按位与 |
| | 按位或 |
^ | 按位异或 |
~ | 按位取反 |
<< | 按位左移 |
">>" | 按位右移(补符号位) |
">>>" | 无符号按位右移(补0) |
条件运算符
符号 | 含义 |
---|---|
?: | 条件为真返回第一表达式,反正返回第二个 |
操作符
符号 | 含义 |
---|---|
typeof | 返回变量数据类型 |
void | 执行表达式,不返回或说返回undefined |
delete 属性 | 删除对象属性,并返回是否删除成功 |
属性 in 对象 | 判断对象是否具有该属性,包括继承的 |
对象 instanceof 类 | 判定对象是否是类或该类子类产生的 |
运算符的优先级
优先级 | 运算符 | 结合性 |
---|---|---|
1 | ( ) [ ] . | 从左到右 |
2 | ! ~ ++ -- typeof void delete | 从右到左 |
3 | * / % | 从左到右 |
4 | + - | 从左到右 |
5 | << >> >>> | 从左到右 |
6 | < <= > >= in instanceof | 从左到右 |
7 | == != === !== | 从左到右 |
8 | & | 从左到右 |
9 | ^ | 从左到右 |
10 | | | 从左到右 |
11 | && | 从左到右 |
12 | || | 从左到右 |
13 | ?: | 从左到右 |
14 | = += -= *= /= %= &= |= ^= ~= <<= >>= >>>= | 从右到左 |
15 | , | 从右到左 |
单算移关与,异或逻条赋
括号级别最高,逗号级别最低,单目 > 算术 > 位移 > 关系 > 逻辑 > 三目 > 赋值。
面向对象
构造函数
构造函数与普通函数是一样的,只不过通常内部使用this
关键字代表索要生成的对象,在生成对象时需要使用new
关键字进行创建以构造函数为类模板的对象
为了区别构造函数还是普通函数,构造函数首字母通常大写
new命令
创建一个空对象;让构造函数中的this
指向这个创建的对象;将空对象的原型指向构造函数的原型对象;执行构造函数,并返回this
对象
函数内部又return
语句,并且返回的是个对象,则会返回return
语句后指定的对象,否则会忽略return
语句直接返回this
对象,返回return
后的内容
未使用new
关键字则会当成普通函数对待,this
可能会指向全局对象导致污染全局变量的问题,所以为了使构造函数必须使用new关键字来进行调用,可以使用如下几种方式
- 使用严格模式,函数首行加
use strict
,实质是将this为全局对象改为了undefined
,所以未使用new
关键字会报错 this instanceof 构造函数名
,判断this
当前对象是否是当前构造函数的实例,当未使用new
关键字this
就会指向全局对象所以返回false
new.target
属性,若未使用new
关键字调用则值是undefined
,若使用new
关键字则该属性指向当前构造函数
this指向
this
所指代的是运行时环境,即调用者,当运行时环境发生改变时this
的指向也会发生改变,所以要尽可能的避免出现this
指向全局对象的情况
尤其要注意的是:
若有多个对象嵌套时,this是不会越级的,也就是说内层对象中的this指向内层对象,不会指向外层对象,比如
outerObj.innerObj.fun()
的方法指向是outerObj.innerObj
对象而不是outerObj
对象,换言之:总是指向该方法点前面的对象,点前面没有东西则指向全局对象
环境 | this指向 |
---|---|
全局环境中 | 全局对象 |
对象环境中 | 该对象 |
普通函数中 | 全局对象,实质是由全局对象调用的 |
立即执行函数中 | 全局对象,也属于普通函数的一种 |
回调函数中 | 全局对象,也属于普通函数的一种 |
构造函数中 | 创建的实例对象 |
对象方法中 | 调用该方法的对象 |
对象的原型中 | 调用该方法的对象,也属于对象方法中 |
构造函数的原型对象中 | 构造函数的原型对象,也属于对象方法中 |
事件绑定的方法中 | 绑定事件的对象,也属于对象方法中 |
多数情况this
指向不明确都是由于函数执行的环境,所以为了避免这种情况出现,就出现了一种固定或切换this
指向的机制
第一个参数都是this
的指向对象,若是空参、null
、undefined
则默认传入全局对象,若是原始类型则会自动转化成对于的包装对象
函数名 | 描述 |
---|---|
Function.prototype.call(objectThis,...ages) |
以当前指向对象为环境执行函数,参数逐个传入 |
Function.prototype.apply(objectThis,array) |
以当前指向对象为环境执行函数,参数以数组形式传入 |
Function.prototype.bind(objectThis,...ages) |
返回一个以指向对象为环境的函数拷贝,参数逐个传入 |
原型对象
由同一个构造函数生成的实例对象,属性是无法进行共享的,对于相同的属性会在内存中开辟两块内存不同内容相同的空间,从而导致系统资源的浪费,所以js引入了prototype
原型对象,原型对象上的所有属性和方法都能被实例对象共享
每个函数都有一个prototype
属性指向原型对象,该属性对普通函数来说是毫无意义的,对于构造函数来说该属性会自动成为实例对象的原型,即对象原型指向原型对象
在prototype
上的属性无法使用构造函数名直接调用,而构造函数的实例对象可以直接调用,所以称为实例属性;在构造函数上直接添加的属性可以通过构造函数名直接调用,所以称为静态属性
constructor属性
每个prototype
对象都有一个constructor
属性指向构造函数,所以修改原型对象时最好同时修改好原型对象的constructor
属性,更好的方式是在原本原型对象基础上增加方法,而不是直接更改为另一个对象
原型链
js中每个对象默认都有自己的原型指向自己的原型对象,原型对象也是对象也有自己的原型,所以就会形成一个原型链,所有对象最终会指向Object.prototype
,而Object.prototype
对象的原型是null
原型链到此结束
读取对象的某个属性时,先寻找自身属性,若没有找到会到原型中找,再找不到就到原型的上级原型找,直到顶层Object.prototype
为止;若本身和原型中都有该属性则会就近原则选择属性,若想不就近原则调用的话就必须显示的加上是那一层原型的属性
function F(){}
F.prototype.toString = function(){
return "toString";
}
var obj = new F();
console.log(Object.prototype.toString()); //[object Object]
console.log(F.toString()); //toString
获取对象原型的三种方式:
obj.__proto__
:只有浏览器才需要部署,其他环境中不需要部署,node环境中也有,即将废弃,不建议使用obj.constructor.prototype
:由于原型链相当于是obj.__proto__.constructor.prototype
的简写,由上图可知,由于就近原则,无法直接获得上级原型,一直来回指,除非借助__proto__
或Object.getPrototype(obj)
Object.getPrototypeOf(obj)
:最可靠的方式
/*获取整个原型链*/
function F(){}
var obj = new F();
/*第一层原型,是obj对象的原型,为构造函数的原型对象*/
console.log(obj.__proto__ === F.prototype); //true
console.log(obj.constructor.prototype === F.prototype); //true //等同于 console.log(obj.__proto__.constructor.prototype === F.prototype); //true
console.log(Object.getPrototypeOf(obj) === F.prototype); //true
/*第二层原型,是构造函数的原型对象的原型,为Object的原型对象*/
console.log(obj.__proto__.__proto__ === Object.prototype); //true
console.log(Object.getPrototypeOf(obj.constructor.prototype) === Object.prototype); //true
//等同于
console.log(F.prototype.__proto__ === Object.prototype); //true
console.log(Object.getPrototypeOf(F.prototype) === Object.prototype); //true
/*第三层原型是Object原型对象的原型,为null*/
console.log(obj.__proto__.__proto__.__proto__ === null); //true
console.log(Object.getPrototypeOf(Object.getPrototypeOf(obj.constructor.prototype)) === null); //true
//等同于
console.log(Object.prototype.__proto__ === null); //true
console.log(Object.getPrototypeOf(Object.prototype) === null); //true
继承
function Super(){} //父类
Super.prototype.fun = function(){}; //父类的实例方法
function Sub() { //子类
Super.call(this); //调用父类构造函数,相当于将父类的构造函数拷贝过来了
}
Sub.prototype = Object.create(Super.prototype); //修改子类原型对象指向父类对象
//不能使用等号直接赋值,否则修改构造属性时会将父类的一同修改掉
// Sub.prototype = new Super(); //这种方式会带有父类的静态属性
//等同于 Object.setPrototypeOf(Sub,Object.create(Super.prototype));
Sub.prototype.constructor = Sub; //修改构造属性子类构造属性指向子类构造函数
Sub.prototype.fun = function(){}; //子类实例方法覆盖了父类的方法
标准库
Object
js中所有对象都继承自
Object
对象,即js中所有对象都是Object
的实例
函数与构造函数
Object()
在不使用new
关键字的情况下就变成了一个普通函数,功能是将任意原始数据类型的转化为一个对应的包装对象,若是空参、null
和undefined
会返回一个空对象
new Object()
使用时与函数功能和用法是一样的,只不过是语义不同,函数代表转化为一个对象,而构造函数代表新生成一个对象
静态方法
遍历属性
方法名 | 描述 |
---|---|
Object.keys(obj) |
返回obj 对象自身的所有属性名的数组(不包括不可枚举的属性) |
Object.getOwnPropertyNames(obj) |
返回obj 对象自身的所有属性名的数组(包括不可枚举的属性) |
描述对象
注意
通过方法设置的描述对象,对于省略的元属性与直接设置属性方式不同的是
writable
、enumerable
和configurable
默认都是false
方法名 | 描述 |
---|---|
Object.getOwnPropertyDescriptor(obj,propertyName) |
获取obj 的propertyName 属性的描述对象,没有该属性返回undefined |
Object.defineProperty(obj,propertyName,propertyDesc) |
通过描述对象,为obj 对象定义某个属性 |
Object.defineProperties(obj,propertyDesc) |
通过描述对象,为obj 对象定义多个属性 |
每个属性都有自己对应的属性描述对象,保存该属性的一些元信息,即元属性,控制属性的属性
属性名 | 描述 |
---|---|
value |
属性值,默认undefined |
writable |
布尔值,属性值是否可写,默认为true ;若设置为false ,则正常模式下写入默默失效,严格模式下会报错 |
enumerable |
布尔值,属性值是否可枚举,默认为true ; |
configurable |
布尔值,属性值是否可配置,默认为true ;若设置为false 则无法删除该属性、也不得改变该属性的属性描述对象包括configurable ,value 属性除外,但是要注意的是:writable 只有在false 改为true 会报错,true 改为false 是允许的 |
get |
取值函数,默认为undefined |
set |
存值函数,默认为undefined ,必须有一个参数是value |
一旦定义了存取器就必须保证writable
为true
,在进行对属性值存取时就会调用存取器函数,无法绕过存取器,所以设置存取器后就不能同时在设置writable
属性和value
的默认值了,否则会报错
/*设置存取器的第一种方式*/
var obj = Object.defineProperty({}, "p", {
enumerable:true, //此方法中默认为false
configurable:true, //此方法中默认为false
get:function () {
return this.value
},
set: function (value) {
this.value = value
}
});
console.log(Object.getOwnPropertyDescriptor(obj,"p"));
/*设置存取器的第二种方式*/
var obj = { //此方式enumerable、configurable默认都是true
set p(value){
this.value = value;
},
get p(){
return value;
},
}
console.log(Object.getOwnPropertyDescriptor(obj,"p"));
var obj = { //对象描述信息是无法使用这种方式显示设置的,必须使用defineProperty和definePropertya方法
p:{
value:"100",
writable: true,
enumerable:true,
configurable:true,
}
}
console.log(Object.getOwnPropertyDescriptor(obj,"p"));
// { value:
// { value: '100',
// writable: true,
// enumerable: true,
// configurable: true },
// writable: true,
// enumerable: true,
// configurable: true }
对象状态控制
方法名 | 描述 |
---|---|
Object.preventExtensions(obj) |
防止obj 对象扩展,无法添加新属性 |
Object.isExtensible(obj) |
判断obj 对象是否可扩展 |
Object.seal(obj) |
禁止obj 对象配置,无法添加新属性、无法删除旧属性,实质是把属性描述对象的configurable 属性设为false |
Object.isSealed(obj) |
判断一个obj 对象是否可配置 |
Object.freeze(obj) |
冻结一个obj 对象,无法添加新属性、无法删除旧属性、也无法修改属性的值 |
Object.isFrozen(obj) |
判断一个obj 对象是否被冻结 |
原型链相关
方法名 | 描述 |
---|---|
Object.create(obj) |
以obj 为原型创建一个空对象并返回,省略参数或传入空对象父对象就是Object,传入null 则会返回不继承任何对象的空对象 |
Object.getPrototypeOf(obj) |
获取obj 对象的prototype 对象 |
Object.setPrototypeOf(obj,objPrototype) |
设置obj 对象的prototyoe 对象为objPrototype 并返回obj 对象 |
实例方法
方法名 | 描述 |
---|---|
Object.prototype.valueOf() |
返回当前对象对应的值,默认情况下返回对象本身 |
Object.prototype.toString() |
返回当前对象的字符串形式,默认情况下返回类型字符串 |
Object.prototype.toLocaleString() |
返回当前对象的以本地字符串形式,主要用于Date 类,默认情况下与toString() 一样 |
Object.prototype.hasOwnProperty(propertyName) |
判断该对象是否有propertyName 这个属性,不包含继承的 |
Object.prototype.isPrototypeOf(obj) |
判断该对象的原型是否是obj |
Object.prototype.propertyIsEnumerable(propertyName) |
判断该对象的propertyName 属性是否可枚举,对于没有该属性和对象原型中的属性一律返回false |
Array
构造函数
返回一个新生的数组,若不使用new
关键字也是一样的结果,但是建议总是加上new
表示为使用构造函数创建对象
该构造函数的参数不一样时返回结果也是不一样的
参数 | 描述 |
---|---|
无参或0 | 返回空数组 |
一个正整数 | 正整数表示数组的长度,内容都是空位 |
一个非正整数(负数、小数) | 报错 |
一个其他数据类型 | 由此参数构成的单个元素的数组 |
多个参数 | 由此些参数构成数组元素的数组 |
静态方法
方法名 | 描述 |
---|---|
Array.isArray(arr) |
判断arr是否是数组,弥补了typeof 的不准确 |
实例方法
重写的
方法名 | 描述 |
---|---|
Array.prototype.toString() |
重写了Object的方法,返回数组的字符串形式 |
Array.prototype.valueOf() |
重写了Object的方法,返回数组本身 |
会改变原数组的方法
方法名 | 描述 |
---|---|
Array.prototype.push(...element) |
数组尾部添加一个或多个元素,并返回数组长度 |
Array.prototype.pop() |
数组尾部删除一个元素,并返回删除元素 |
Array.prototype.shift() |
数组头部删除一个元素,并返回删除元素 |
Array.prototype.unshift(...element) |
数组头部添加一个或多个元素,并返回数组长度 |
Array.prototype.reverse() |
将数组元素位置翻转 |
Array.prototype.splice(start,count[,...element]) |
从start 位置开始删除count 个元素后,插入element 元素,并以数组形式返回删除的元素;可以只删除不插入;允许起始位置为负数代表倒数第几个; |
Array.prototype.sort([[compareFunction]]) |
对数组按照比较器函数进行排序,要注意的是:最好将比较器返回数值型,而不是布尔型;若不传入比较器函数则会按照字典顺序排序 |
不会改变原数组的方法
方法名 | 描述 |
---|---|
Array.prototype.join(separator) |
返回以separator 连接符连接数组元素的字符串,会将空位、undefined 、null 的元素转化为空字符串 |
Array.prototype.concat(array) |
返回当前数组和array 数组拼接的新数组 |
Array.prototype.slice([start,end]) |
返回的子数组,可以是负数,表示倒数第几个,超过数组长度会转化为数组的长度;范围不合法会返回空数组;若end 参数省略,则会从start 位置开始,一直到到数组最后;若连start 都省略了,则会从数组首部一直到数组的最后,即数组拷贝 |
Array.prototype.lastIndexOf(element) |
返回给定元素在位置,数组中倒着找第一次出现的位置,找不到返回-1 |
Array.prototype.indexOf(element) |
返回给定元素位置,在数组中正着找第一次出现的位置,找不到返回-1 |
函数式编程
- 对于所有的函数式方法都是同步的回调函数,也就是说这些回调函数不会添加到异步队列中
- 对于
map()
、forEach()
、filter()
、some()
和every()
来说参数是一致的callback(currentElement[,index,array])
:表示元素处理回调函数,只有第一个参数是必须的currentElement
:当前正在处理的元素index
:当前正在处理的元素下标array
:被调用的数组
thisTar
:表示函数内部若使用this
是时可以通过此参数改变回调函数中的this
指向,从而避免指向了全局对象
- 对于
reduce()
和reduceRight
来说参数是一致的,只不过处理顺序是相反的callback(accumulator,currentElement[,index ,array])
:表示元素处理回调函数,只有前两个是必须的accumultor
:累计器,每次调用回调函数的返回值;第一次还没调用时还没有返回值,所以第一次可能是数组的第一个元素,当前值会从第二个元素开始;也可能是设置的初始值,当前值会从第一个元素开始currentElement
:当前正在处理的元素index
:当前正在处理的元素下标array
:被调用的数组
initValue
:设置第一次调用回调函数时累计器的值,若省略该值则默认使用数组的第一个元素做累计器的值,并且当前值会是第二个元素
对于函数式编程函数返回值是数组的就可以采用链式编程了,对于以下的回调要求只是建议
方法名 | 描述 |
---|---|
Array.prototype.map(callback[,thisTar]) |
返回每个元素经过回调函数处理的新数组;回调要求有返回值 |
Array.prototype.forEach(callback[,thisTar]) |
无返回值,每个元素都经过回调函数处理;回调要求无返回值 |
Array.prototype.filter(callback[,thisTar]) |
每个元素经过回调函数处理,对于回调函数处理后返回true 的元素组成新数组并返回;回调要求返回布尔值 |
Array.prototype.some(callback[,thisTar]) |
每个元素经过回调函数处理,只要有一个元素经过回调函数处理后返回true 整个函数返回true ,否则返回false ;回调要求返回布尔值 |
Array.prototype.every(callback[,thisTar]) |
每个元素经过回调函数处理,所有元素经过回调函数处理后返回true 整个函数返回true ,否则返回false ;回调要求返回布尔值 |
Array.prototype.reduce(callback[,initValue]) |
从前往后的顺序对每个元素经过回调函数处理,返回最后一次调用回调函数的返回值;回调要求返回有返回值,并且回调中要处理累计器的值 |
Array.prototype.reduceRight(callback[,initValue]) |
从前往后的顺序对每个元素经过回调函数处理,返回最后一次调用回调函数的返回值;回调要求返回有返回值,并且回调中要处理累计器的值 |
包装对象
将原始数据类型放在与之对应包装对象中,使原始数据类型也成为合成数据类型,之后这些包装对象就可以调用包装对象中的方法了,当然也可以为包装对象的原型上添加自定义的属性
要区分
new Number()
和Number()
方法,前者属于构造函数返回对应的包装对象,而后者是普通函数返回原始类型,其他那几个也是同理(除拉Object()
)
构造方法
对于函数方式在数据类型之间的转换,这里是构造方法方式,对于原始类型变为包装对象有一下四种方式:
构造函数名 | 描述 |
---|---|
Number() |
配合new 关键字生成对应的包装对象 |
String() |
配合new 关键字生成对应的包装对象 |
Boolean() |
配合new 关键字生成对应的包装对象 |
Object() |
配合new 关键字生成对应的包装对象 |
公有的实例方法
方法名 | 描述 |
---|---|
valueOf() |
重写了Object中的方法,返回包装对象中的原始类型数据 |
toString() |
重写了Object中的方法,返回包装对象中原始数据类型转化的字符串;若是Number 的实例则还可以指定进制数 |
自动装箱与手动拆装
实际上就是是原始类型到包装对象自动转换,包装对象到原始数据类型手动转化
对于自定义属性是可以在手动装箱的对象中添加的,但是无法在自动装箱的对象中添加,自动装箱的对象是只读的,或者说包装对象方法调用结束后,包装对象实例会自动销毁,下一次调用字符串的属性时,实际是调用一个新生成的对象,而不是上一次调用时生成的那个对象,所以取不到赋值在上一个对象的属性;所以最好的方式是将属性定义在包装对象构造方法的原型对象上
/*自动拆箱*/
console.log(new String("1")); //[String: '1']
//等同于
console.log(new String("1").valueOf()); //1
console.log("1"); //1
console.log(new Number(1)); //[Number: 1]
//等同于
console.log(new Number(1).valueOf()); //1
console.log(1); //1
console.log(new Boolean(true)); //[Boolean: true]
//等同于
console.log(new Boolean(true).valueOf()); //true
console.log(true); //true
/*自动装箱*/
console.log("1".length); //1
//等同于
console.log(new String("1").length); //1
Number.prototype.add1 = function(){return this.valueOf()+1;}; //为包装对象添加自定义方法
console.log((1).add1()); //2 必须带有括号否则会有小数点的二义性,也可以使用方括号形式 1["add1"]();
//等同于
console.log(new Number(1).add1()); //2
Boolean.prototype.not = function(){return !this.valueOf()}; //为包装对象添加自定义方法
console.log(true.not()); //false
//等同于
console.log(new Boolean(true).not());//false
/*无法在自动装箱对象中添加方法*/
var str = new String("1");
str.add = function(){
return this.valueOf()+this.valueOf();
};
console.log(str.add()); //11
var str = "1";
str.add = str.add = function(){
return this.valueOf()+this.valueOf();
};
console.log(str.add()); //TypeError: str.add is not a function
Number
静态属性
属性名 | 属性值 |
---|---|
Number.POSITIVE_INFINITY 和Number.NEGATIVE_INFINITY |
指向Infinity 和-Infinity |
Number.NaN |
指向NaN |
Number.MIN_VALUE |
浮点数中最小的正数5e-324 |
Number.MAX_SAFE_INTEGER |
能精确表示的最大整数为9007199254740991 |
Number.MIN_SAFE_INTEGER |
能精确表示的最小整数为-9007199254740991 |
实例方法
注意调用时小数点的二义性,可以使用括号将数字括起来,也可以使用方括号的形式
方法名 | 描述 |
---|---|
Number.prototype.toFixed(num) |
将数值转化为指定位数的小数字符串(四舍五入不准确) |
Number.prototype.toExponential(num) |
将数值转化为指定位小数位的科学计数法形式 |
Number.prototype.toPrecision() |
将数值转为指定位数的有效数字(小数整数都算上,四舍五入不准确) |
Number.prototype.toLocaleString([zone,options]) |
默认返回本地的数字的字符串形式,也可以根据时区和配置转化为当地的字符串形式 |
String
静态方法
方法名 | 描述 |
---|---|
String.fromCharCode(...Unicode) |
将Unicode 改成编码转化成字符串 |
实例方法
查询方法
方法名 | 描述 |
---|---|
String.prototype.length |
返回字符串的长度 |
String.prototype.charAt(index) |
返回指定下标的字符 |
String.prototype.charCodeAt(index) |
返回指定下标的字符Unicode 编码,若下标不合法返回NaN |
String.prototype.indexOf(substr) |
返回给定子串在位置,字符串中正着找第一次出现的位置,找不到返回-1 |
String.prototype.lastIndexOf(substr) |
返回给定子串在位置,字符串中反着找第一次出现的位置,找不到返回-1 |
截取方法
方法名 | 描述 |
---|---|
String.prototype.slice([start,end]) |
返回的子串,可以是负数,表示倒数第几个,超过字符串长度会转化为字符串的长度;范围不合法会返回空串;若end 参数省略,则会从start 位置开始,一直到到最后;若连start 都省略了,则会从数组首部一直到字符串的最后,即字符串拷贝 |
String.prototype.substring([start,end]) |
返回的子串,若是负数自动转化为0,超过字符串长度会转化为字符串的长度,范围不合法会交换起始结束位置;若end 参数省略,则会从start 位置开始,一直到到最后;若连start 都省略了,则会从数组首部一直到字符串的最后,即字符串拷贝 |
String.prototype.substr([start,count]) |
从start 位置开始,取count 个字符,start 可以是负数,表示倒数第几个,count 为负数会转化为0;若count 参数省略,则会从start 位置开始,一直到到最后;若连start 都省略了,则会从数组首部一直到字符串的最后,即字符串拷贝 |
转化方法
方法名 | 描述 |
---|---|
String.prototype.trim() |
返回一个去除两端空白的字符串 |
String.prototype.toLowerCase() |
返回转小写后的字符串 |
String.prototype.toUpperCase() |
返回转大写后的字符串 |
String.prototype.concat(str) |
返回当前字符串与str 的拼接,与+效果相同 |
分隔方法
方法名 | 描述 |
---|---|
String.prototype.split(regex) |
根据给定规则分隔字符串 |
Boolean
没有自己的方法,但是要注意以下使用方式,对于包装对象会当成对象来处理,所以会转化为true
if (Boolean(false)) { //false转化为false没问题
console.log('true');
} // 无输出
if (new Boolean(false)) { //现在是对象,对象转化为true
console.log('true');
} // true
if (Boolean(null)) { //现在是null转化为false没问题
console.log('true');
} // 无输出
if (new Boolean(null)) { //现在是对象,对象转化为true
console.log('true');
} // true
正则表达式
- 正则属于特殊的字符串,主要用于表达字符串的结构
- 如同字符串一样,有自己的构造函数可以使用
new RegExp()
生成,也可以使用字面量形式/
进行包裹
正则修饰符
字面量形式在/
包裹后修饰,使用构造函数形式则是第二个参数是修饰符
符号 | 含义 |
---|---|
g |
表示全局匹配模式 |
i |
忽略字母大小写模式 |
m |
多行模式,$ 和^ 会识别换行符 |
正则匹配规则
转义字符
在使用new RegExp()
构造函数时,由于正则表达式也是字符串,再匹配正则中已经占用的符号时要进行转移,通常要使用两个反斜线,因为两个反斜线转移成一个反斜线,然后这个被转义后的反斜线再将后面要转义的符号转义
在使用字面量时已使用/
包裹区分字符串了,所以像换行符制表符之类的字符就无需转义了,直接使用即可
符号 | 构造函数方式 | 字面量方式 |
---|---|---|
\\ |
\ |
\\ |
\t |
\\t |
\t |
\n |
\\n |
\n |
特殊符号
[]
中除拉&&、-、^
其他就失去意义,无需转义,其中的关系是或的关系
符号 | 描述 | |
---|---|---|
. |
任意一个字符,除拉\r\n |
|
^ |
以开头,在[] 中是取反 |
|
$ |
以结尾 | |
[] |
单个符号范围 | |
&& |
只作用于[] 中,关系与取交 |
|
- |
只作用于[] 中,表取值范围 |
|
{} |
数量词量词范围 | |
() |
提升优先级和分组 | |
` | ` | 关系或 |
数量词符号
贪婪模式(默认):重复次数多个满足条件情况下,尽可能的多匹配
非贪婪模式(数量词后跟一个
?
):重复次数多个满足情况下,尽可能的少匹配
符号 | 描述 |
---|---|
* |
闭包,零次或多次 |
+ |
正闭包,一次或多次 |
? |
占位,零次或一次 |
{n} |
重复n次 |
{n,} |
重复n次或更多次 |
{n,m} |
重复n到m次 |
简化表达式
预定义类 | 描述 |
---|---|
\d |
[0-9] |
\D |
[^0-9] |
\w |
[0-9a-zA-Z_] |
\W |
[^0-9a-zA-Z_] |
\s |
匹配空格(包括换行,制表空格)[\t\r\n\v\f] |
\S |
[^\t\r\n\v\f] |
实例属性
要注意的是,除拉
lastIndex
属性以外的其他实例属性是只读的
属性名 | 描述 |
---|---|
RegExp.prototype.ignoreCase |
判断该正则是否设置了i 修饰符 |
RegExp.prototype.global |
判断该正则是否设置了g 修饰符 |
RegExp.prototype.multiline |
判断该正则是否设置了m 修饰符 |
RegExp.prototype.flags |
返回所有修饰符,按照字母顺序排序igm |
RegExp.prototype.lastIndex |
返回下一次开始搜索的位置 |
RegExp.prototype.source |
返回正则的字符串形式 |
实例方法
方法名 | 描述 |
---|---|
RegExp.prototype.test(str) |
测试str 是否满足该正则;若是全局匹配模式下每次满足会修改lastIndex 的值,下一次调用会从lastIndex 位置开始进行匹配,也可以手动改lastIndex 的值做到指定位置开始匹配,所以在全局匹配模式下就不要修改str 变量了,因为会导致lastIndex 值不准确 |
RegExp.prototype.exec(str) |
以数组形式返回str 中满足该正则的字串,未有满足的返回null ;若是分组正则(带有括号的正则)则会将每组匹配到的元素都放入数组,最外层未带括号的组号是0,第一个带有括号的组号是1,以此类推;返回的数组会比普通数组多两个属性:input 表示原字符串、index 表示匹配成功的原字符串下标位置,在全局匹配模式下会跟随lastIndex 属性一起变化 |
字符串中的正则
方法名 | 描述 |
---|---|
String.prototype.match(regexp) |
返回一个数组,成员是匹配到的子串,未有满足的返回null ;要注意只有当正则是全局匹配模式下,成员才会是所有匹配的子串,否则会与正则的exec() 方法的非全局匹配模式下返回结果一样,并且此方法与正则的lastIndex 无关 |
String.prototype.search(regexp) |
返回第一个满足正则规则的子串的位置。未有满足返回-1 |
String.prototype.replace(regexp,replacement) |
返回用replacement 替换满足正则的子串;正则全局匹配模式下会替换掉全部满足正则的子串,而非全局匹配模式下只会替换第一个满足的正则的子串 |
String.prototype.split(regexp[,maxCount]) |
按照正则规则将字符串分隔成字符串数组,maxCount 可以限制匹配到字符串的个数,也是数组元素个数,省略则代表全部分隔 |
Math
该对象只是个内置对象,既无法调用,也无法使用new
关键字生成实例
静态属性
属性名 | 描述 |
---|---|
Math.PI |
常数π |
静态方法
方法名 | 描述 |
---|---|
Math.abs(number) |
绝对值 |
Math.ceil(number) |
向上取整 |
Math.floot(number) |
向上取整 |
Math.round(number) |
四舍五入为整数 |
Math.max(...number) |
返回最大值 |
Math.min(...number) |
返回最小值 |
Math.random() |
返回[0,1)之间的随机数 |
Math.sqrt(x) |
平方根,,负数会返回NaN |
Math.pow(x,y) |
幂运算, |
Date
函数
不管有没有参数,总是返回当前时间对象的字符串形式
构造函数
构造函数参数 | 描述 |
---|---|
无参 | 返回当前时间的时间对象 |
单个整型数字 | 表示时间戳,返回当前时间戳的时间对象 |
字符串 | 能被解析的时间字符串,返回字符串描述的时间对象 |
多个整型数字 | 分别表示,年月日-时分秒,毫秒,若只写一个年会当作时间戳处理,所以至少是两个参数;与其他编程语言一样,月份是从0开始的,日期是从1开始的,时间超过范围会自动折算,且允许为负数 |
静态方法
方法名 | 描述 |
---|---|
Date.now() |
返回当前时间的时间对象,是无参构造函数的一个工厂方法 |
Date.parse(str) |
能被解析的时间字符串,返回字符串描述的时间对象,是字符串参数构造函数的一个工厂方法 |
Date.UTC(year,month[,date,hrs,min,sec,ms]) |
与构造函数的参数完全一致,只不过返回的是时间戳,该时间不再是当地时区了,而是世界标准时间 |
实例方法
to
类:从Date
对象返回一个字符串,表示指定的时间
方法名 | 描述 |
---|---|
Date.prototype.toUTCString() |
返回UTC 格式时间 |
Date.prototype.toISOString() |
返回ISO 格式时间 |
Date.prototype.toJSON() |
返回符号JSON格式的时间,也就是ISO 格式时间 |
Date.prototype.toString() |
返回完整的时间 |
Date.prototype.toDateString() |
返回日期 |
Date.prototype.toTimeString() |
返回时间 |
Date.prototype.toLocaleString() |
返回完整的本地时间 |
Date.prototype.toLocaleDateString() |
返回本地日期 |
Date.prototype.toLocaleTimeString([zone,options]) |
默认返回本地时间,也可以根据时区和配置转化为当地的字符串形式 |
get
类:获取Date
对象的日期和时间
也有获取世界标准时间的方法,就是加一个
UTC
的前缀,如:getUTCDate()
方法名 | 描述 |
---|---|
Date.prototype.getTime() |
获取时间戳,与valueOf() 方法相同 |
Date.prototype.getFullYear() |
返回四位数年份 |
Date.prototype.getMonth() |
返回月,0是1月,以此类推 |
Date.prototype.getDate() |
返回日,从1开始 |
Date.prototype.getHours() |
返回小时 |
Date.prototype.getMinutes() |
返回分钟 |
Date.prototype.getSeconds() |
返回秒 |
Date.prototype.getMilliseconds() |
返回毫秒 |
Date.prototype.getDay() |
返回星期,0是星期日,以此类推 |
Date.prototype.getTimezoneOffset() |
返回当前时间与世界标准时间的时差 |
set
类:设置Date
对象的日期和时间
相应的也有设置世界标准时间的方法,也是加个
UTC
前缀,如:setUTCDate()
方法名 | 描述 |
---|---|
Date.prototype.setTime(milliseconds) |
设置时间戳 |
Date.prototype.setFullYear(year[,month,date]) |
设置四位数年份 |
Date.prototype.setMonth(month[,date]) |
设置月 |
Date.prototype.setDate(date) |
设置日 |
Date.prototype.setHours(hour[,min,sec,ms]) |
设置小时 |
Date.prototype.setMinutes(min[,sec,ms]) |
设置分钟 |
Date.prototype.setSeconds(sec[,ms]) |
设置秒 |
Date.prototype.setMilliseconds() |
设置毫秒 |
JSON
JSON格式
- 复合类型的值只能是:数组、普通对象;不能是:函数、正则、日期
- 原始类型值只能是:字符串、十进制数、布尔值和null;不能是:
NaN
、Infinity
、-Infinity
、undefined
- 字符串必须使用双引号,不能使用单引号
- 对象的键名必须使用双引号包裹
- 数组或对象的最后一个成员不能加逗号
静态方法
只有两个静态方法,用来处理JSON格式数据
方法名 | 描述 | ||
---|---|---|---|
JSON.stringify(JSONStr[,callback]) |
将JSONStr 字符串转换成js对象 |
||
`JSON.parse(obj,[callback | array,number | string])` | 将js对象转化成JSON字符串;普通对象:如果属性值undefined 、函数或该属性不可枚举,该属性会被忽略;正则对象:会被转化成空对象;数组:如果数组元素是undefined 、函数,该元素会被转化成null ;原始数据类型:会被转化成对于的字符串格式(只有字符串类型会附带有双引号) |
var obj = {
name:"zhangsan",
age:18
};
var arr = [1,2];
/*第二个参数是数组形式,是配置要转化的属性有哪些*/
console.log(JSON.stringify(obj,["name"])); //{"name":"zhangsan"}
console.log(JSON.stringify(arr,["0"])); //[1,2],对数组下标是无效的
/*
第二给参数是函数,是对要解析对象的属性值进行处理,数组也是可以的
该函数需要有两个参数,一个是key表示对象属性的键,一个是value表示对象属性的值,返回值是该键的属性值
要注意的是该函数会从最外层obj开始递归的像内处理,所以最好加上判断
*/
function fun(key,value){
console.log("["+ key +"]:" + value);
if(typeof value !== "object"){ //如果不是对象
return value+value; //让键值翻倍 若返回undefined就会忽略这个属性
}
return value;
}
console.log(JSON.stringify(obj,fun));
// []:[object Object] //第一个属性是对象本身,没有键名
// [name]:zhangsan //第二个是内层属性
// [age]:18
// {"name":"zhangsanzhangsan","age":36} //经过函数处理后的结果,键值翻倍了
/*
第三个参数是数字,代表每个属性前增加几个空格,数组也是一样
使得JSON缩进,一样是递归的
*/
console.log(JSON.stringify(obj,null,2)); //在属性值前面补充2个空格,更加好看些
// {
// "name": "zhangsan",
// "age": 18
// }
/*
第三个参数是字符串,代表每个属性前增加这个字符串,数组也是一样
但是这样的方式会导致无法解析出来,同样是递归的
*/
console.log(JSON.stringify(arr,null,"--"));
// {
// --"name": "zhangsan",
// --"age": 18
// }
/*同样的parse也有第二个参数,但是只能是函数,与stringify方法的递归方向是相反的*/
var json = JSON.stringify(obj);
console.log(JSON.parse(json,fun));
// [name]:zhangsan
// [age]:18
// []:[object Object]
// { name: 'zhangsanzhangsan', age: 36 }
toJSON方法
如果一个对象中有toJSON()
方法时,则在使用stringify()
方法会使用该方法的返回值做为参数,而不是这个对象了,多数情况下使用此方法返回正则和Date
对象的字符串形式,若没有此方法会转成空对象;Date
中自带了,而正则中没有自带,需要手动处理
var obj = {
name:"zhangsan",
age:18,
toJSON:function() {
return "toJSON";
}
};
console.log(JSON.stringify(obj)); //"toJSON"
var obj = {
name:"zhangsan",
age:18,
toJSON:"toJSON" //该属性不是函数时会失效
};
console.log(JSON.stringify(obj)); //{"name":"zhangsan","age":18,"toJSON":"toJSON"}
异步
js只能在一个线程上运行,同一时间只能执行一个任务,其他任务需要在后面排队等,但是不代表js引擎只有一个线程,实际上js引擎有多个线程,单个脚本只能在一个线程上运行,其他线程都是在后台配合
-
同步任务:指在主线程上排队执行的任务;
-
异步任务:指暂时被js引擎挂起放入任务队列中,当js引擎认为任务队列中的某个任务可以执行了,该任务才会进入主线程执行
-
js引擎执行完所有的同步任务后,先查看微队列中是否有微任务,有就全部执行,微队列中的所有微任务都执行完毕后,查看宏队列中是否有宏任务,有就执行一个宏任务,执行完之后,回过头查看微队列中是否有微任务,有就全部执行;换句话说就是,每次执行宏任务之前都会将所有可执行的微任务都执行完
-
宏任务:存放在宏队列中,包含定时器回调函数、AJAX请求回调函数、DOM事件回调函数
-
微任务:存放在微队列中包含promise回调函数
-
-
事件轮询机制:只要主线程的同步任务执行完毕,就会一遍一遍的检测哪些挂起的在异步队列中的任务是否可以进入主线程执行了
定时器
方法名 | 描述 |
---|---|
setTimeout(fun[,delay,...args]) |
一次性定时器,返回定时器id;省略delay 默认是0,args 是该回调函数的参数列表 |
clearTimeout(id) |
通过定时器id清除一次性定时器 |
setInterval(fun[,delay,...args]) |
循环定时器,返回定时器id;省略delay 默认是0,args 是该回调函数的参数列表 |
clearInterval(id) |
通过定时器id清除循环定时器 |
requestAnimationFrame(fun) |
请求动画帧,返回定时器id;仅执行一次,想执行动画需要在回调中递归的调用;由系统决定回调函数的执行时机,一般是60Hz的刷新频率,解决了定时器的丢帧,卡顿现象 |
cancelAnimationFrame(id) |
通过定时器id清除请求动画帧 |
串行执行多个异步任务
为了保证多个异步任务一个接一个执行,并且上一个异步任务处理的结果再交由下一次异步任务处理,对于这种情况就可能出现多个异步回调函数嵌套,称之为回调地狱(callback hell),难以维护
setTimeout(function () {
console.log(1);
setTimeout(function () {
console.log(2);
setTimeout(function () {
console.log(3);
}, 1000)
}, 1000)
}, 1000)
//1
//2
//3
//一秒输出一个数字,该程序执行3秒结束
为了多个异步任务串行执行,其实就是在一个异步任务中调用另一个异步任务,编写一个异步任务控制流程函数来避免回调地狱的出现
/*
value:第一次函数的input值
async:函数队列,要求形式是async(input, output)
input上一次函数的输出这一次的输入
output是回调函数,用于向下一个函数传递当前函数处理的结果,也就是说将处理结果通过output输出到下一个函数中
功能:依次执行函数队列中的函数,并且上一次函数的输出是这一次函数的输入,多个函数串行执行,包括异步函数
*/
function autoRun(value, ...async) { //es5不支持剩余参数可以使用arguments对象
/*
input:上一次函数的输出,这次函数的输入
功能:执行一个函数队列中的一个函数
*/
function next(input) {
if (typeof async[0] === "function") { //若函数队列的第一个元素是函数才能执行
async[0](input, function (handle) { //该回调用于向下一个函数输入处理后的结果和调用队列中的下一个函数
async.shift(); //函数队列弹出一个执行完毕的函数
next(handle); //调用函数队列中的下一个函数,并将处理后的数据传入到下一个函数的输入参数
})
}
}
next(value); //启动执行函数队列
}
//使用方式
autoRun(1,
function (input, output) {
setTimeout(() => {
console.log(input);
output(++input);
}, 1000);
},
function (input, output) {
setTimeout(() => {
console.log(input);
output(++input);
}, 1000);
},
function (input, output) {
setTimeout(() => {
console.log(input);
}, 1000);
});
//会依次打印 1 2 3
严格模式
启动方式
- 为整个脚本开启严格模式:脚本文件第一行添加
use strict
字符串即可 - 为某个函数开启严格模式:函数体第一行添加
use strict
字符串即可
显式报错
- 对只读属性写操作显式报错
- 对不可配置属性删除操作显式报错
- 对只设置取值器的属性写操作显式报错
- 对禁止扩展对象不可扩展添加属性显式报错
- 函数中有重名参数显式报错
- 使用前缀为0的八进制显式报错
安全措施
- 全局变量必须显式声明,否则显式报错
this
关键字指向全局对象时会被替换成undefined
- 禁止删除变量,否则显式报错
- 非函数代码块中不得声明函数,否则显式报错
错误
所有错误对象都是Error
对象的子对象,可以使用这些构造函数创建一个错误对象,构造函数接收一个字符串参数,表示出错信息
Error.prototype.message
:出错信息Error.prototype.name
:错误名称,也就是构造函数名Error.prototype.stack
:错误堆栈信息
原生错误类型
对象名 | 中文名 | 描述 |
---|---|---|
SyntaxError |
语法错误 | 变量名不合法、关键字使用位置不合法等 |
ReferenceError |
引用错误 | 使用不存在的变量、赋值语句左部不是变量 |
RangeError |
范围错误 | 定义数组长度为负数或超过数组最大的容纳范围 |
TypeError |
类型错误 | new 一个非构造函数、使用未定义的函数 |
URLError |
URL错误 | 对URL 函数传递的参数是非URL 地址时 |
throw 抛出错误
若为捕获的情况下,程序遇到该语句就会停止运行将该关键字后的错误抛出,该关键字不仅仅可以抛出错误对象,还可以抛出任意数据类型
throw new Error("err");
//Error: err
throw 1; //其他不列举了
//1
捕获错误
try...catch
结构:若try中发生错误,生成的错误对象会被catch语句捕获,后面的代码还会执行,要注意的是每个try
只能有一个catch
语句块
try {
throw new Error("err");
} catch (e) {
console.error(e);
}
console.log("后面的代码");
//Error: err
//后面的代码
try...catch...finally
结构:不管有没有发生错误,finally语句块中的代码总会执行,要注意的是finally
不能单独使用,必须配合try
可以没有catch
,每个try
只能有一个finally
语句块
try {
throw new Error("err");
} catch (e) {
console.error(e);
} finally{
console.log("filally中的代码");
}
console.log("后面的代码");
//Error: err
//filally中的代码
//后面的代码
try {
} catch (e) {
console.error(e);
} finally{
console.log("filally中的代码");
}
console.log("后面的代码");
//filally中的代码
//后面的代码
try {
} finally{
console.log("filally中的代码");
}
console.log("后面的代码");
//filally中的代码
//后面的代码
自定义错误
继承Error对象即可
function UserError(message) {
this.message = message;
this.name = UserError.name;
}
UserError.prototype = new Error();
UserError.prototype.constructor = UserError;
console.log(new UserError("自定义的错误"));
console.log(new Error("原生错误"));
console.log(new UserError() instanceof Error ); //true
Comments NOTHING