JS高级
js的线程与任务- 浏览器
- JavaScript执行线程
- UI线程
- JavaScript事件线程
- 前两个线程互斥,所以JavaScript是单线程的
- JavaScript的任务
- 同步任务
- 异步任务
- 主线程直接执行同步任务,异步任务先执行一部分,然后退出主线程执行,等再次准备好之后又交给主线程执行
- js的三个重点难点
- 单线程和异步
- 作用域和闭包
- 原型原型链
JavaScript的执行模式
- 解释
- 词法分析
- 语法分析
- 作用域规则确定
- 执行阶段
- 创建执行上下文
- 执行函数代码
- 垃圾回收
函数执行上下文
函数的执行上下文也叫函数的执行环境或者Execution Context简称EC
EC会在函数调用时创建
- 创建执行上下文:
- 建立作用域链
- 创建变量对象
* 创建arguments对象,初始化参数名称和值,并创建引用复制
* 扫描上下文的函数声明,为每一个函数在变量对象创建一个属性(就是函数的名字),值则指向函数在内存中的引用。如果函数名已经存在,则指针将被重写
* 扫描上下文的变量声明,为每一个函数在变量对象创建一个属性,并初始化为undefined。如果变量名已经存在则不会进行任何操作继续扫描。如果函数与变量同名,函数优先级高。 - 确定this指向
- 执行
- 变量赋值
- 代码执行等等
- 回收销毁
- ** 函数执行上下文不等于函数上下文**
for while 循环内部变量的作用域属于该循环所属的函数
function f1(){
var j = 0;
for (var i = 0; i < 8; i++){
j = j + i;
}
console.log(i);
}
f1(); // 8
// for while 循环内部变量的作用域属于该循环所属的函数.md
变量的提升
特殊情况1:
console.log(b); // function
var b = 9;
console.log(b); // 9
function b() {
}
解析:在创建全局上下文时由于函数名优先级高于变量名,所以在创建上下文的时候 b 为function,然后在执行时b被赋值9,所以第二次打印的b就是9
特殊情况2:
f1();
console.log(b); // 9
console.log(c); // 9
console.log(a); // 程序报错,is not defined,使用未定义变量
function f1(){
var a = b = c = 9; // a 作用域为函数f1内,而b、c则为全局作用域
// var a = 9, b = 9, c = 9; 此时都是函数作用域
console.log(a);
console.log(b);
console.log(c);
}
注意事项:
<html>
<head></head>
<body><body>
<script>
console.log(a1,a2); // undefined undefined
var a1 = function(){
console.log("全局afn")
}
var a2 = "a2";
</script>
</html>
-
var 变量提升注意事项:如var果声明在后,则前面运用的该变量都是undefined值,意思是声明了,但是还没赋值(区别于控制台提示 is not defined,这表示的是变量还没有声明)
-
所以变量的提升是声明的提升,赋值并不会提升
-
因此注意如果是函数变量一定不可以在赋值前就调用,否则会提示错误
let与var的区别
var 定义的是全局域或者函数域
let 定义的是块级域变量
例子:
<html>
<head></head>
<body><body>
<script>
var a = [];
var b = [];
for(var i=0;i<10;i++){
a[i] = function(){
console.log(i);
}
console.log(i); // 0 1 3 4 5 6 7 8 9
}
a[1](); // 10
console.log(i); // 10
for(let j=0;j<10;j++){
b[j] = function(){
console.log(j);
}
console.log(j); // 0 1 3 4 5 6 7 8 9
}
b[1](); // 1
console.log(j); // j is not defined
</script>
</html>
对于let j , for 的每一次循环都会新建一个只属于该次循环且属于循环块内的j,且j的值会继承上一次循坏,所以在for循环块外部的j是 not definded的
函数的几种调用模式
一:构造函数调用以及方法调用
<!DOCTYPE >
<html lang="en">
<head><title></title></head><body></body>
<script>
// 构造调用以及方法调用
function Cat(catName, age){
this.name = catName;
this.age = age;
this.showName = function(){
console.log("The dog name is " + this.name);
}
}
// 当函数用作构造函数来使用时,会默认创建一个新的空this对象,然后如果函数没有返回任的时候,默认返回新对象this,即使有返回值,若只是简单类型或者null,也会被忽略
var myCat = new Cat("JOJO",60); //构造调用
myCat.showName(); // 方法调用 JOJO
</script>
</html>
二、函数调用(直接调用)
function f(a , b){
console.log(a + ' ' + b);
this.a = 19; // this = window
console.log('a' in window); // true
console.log(this); // window,全局对象,但是在严格模式下:undefined
}
f(2,3);
有趣的实例子
function Dog(){
this.age = 19;
console.log(this)
}
Dog(); // window
var d = new Dog(); // 此时函数里面console.log(this)Dog照样运行,此时的this 指向一个Dog对象,并不是指向Window
3.apply/call调用模式
function sum(a,b){
console.log(this);
return a+b;
}
var t = {
name: 'zhozho'
};
var m = sum(1,2);
console.log(m); // sum里面 的this指向window,return 3
var m1 = sum.call(t, 2,3) // call的作用是将对象t传给sum里面的this,此时sum里面的this指向的就是t,然后2,3作为参数传给a,b
console.log(m1); // sum 里面的this指向对象t,return 3
var m2 = sum.apply(t,[4,5]); // 作用跟call()一样,写法不同而已
console.log(m2);
apply()的妙用:
var a = Math.min.apply(null,[3,4,5]);
解析:因为Math.min()里面的参数只能是数字,当我们想取出一个数组,比如[3,4,5]中的最小值时就尅用上面的方法。
PS:
如果call()或者apply()里面的一个参数是一个简单类型:
1:null、undefined,则函数sum里面的this还是指向Window
2:number、string、boolean,则会转成对应的包装类型
案例1 类数组转换成真的数组
var t = {};
t[0] = 1;
t[1] = true;
t[2] = 'zho';
t.length = 3;
var k = Array.prototype.slice.call(t,0);
console.log(k);
// 数组的slice()函数解析
// var m = [1,2,3]; m.slice();
// 如果什么都不传,默认是从0开始截取到数组最后
//第一个参数是:截取开始的位置,startIndex
// 第二个参数是:截取结束的位置+ 1 endIndex
异步与回调函数的探索
function fn(a,b,fn1) {
var j = a + b;
var err = null;
setTimeout(function(){
fn1(err,j);
},0);
}
fn(10,11,function(err,j){
console.log(j);
})
console.log("I am the final");
JS 没有重载
1、重载的定义: 几个行参不同但是名字相同的函数构成重载
2、js中同名的函数后面的会覆盖前面的,所以js中没有重载
若想在js中实现重载可借助arguments,如下
function f(){
if(arguments.length == 0){ // arguments就是存储传入参数的对象
console.log("没有传入参数")
}else{
console.log("有传入参数")
}
}
函数的递归调用
1、定义:
递归调用就是函数自己调用自己
例子、运用递归调用求最大公约数和最小公倍数
<!DOCTYPE >
<html lang="en">
<head><title></title></head><body></body>
<script>
// 求最大公约数
function biggestCommonDivisor(a,b){
return a%b == 0?(b):(biggestCommonDivisor(b,a%b)); // 注意如果a<b,则a%b = a
}
// 求最小公倍数
function smallestCommonmultiple(a,b){
var i = biggestCommonDivisor(a,b);
var j = a * b / i;
return j;
}
var a = biggestCommonDivisor(4,10);
var b = smallestCommonmultiple(4,10);
console.log(a + " " + b);
</script>
</html>
2、arguments.callee()
-
说明 在函数没使用,表示调用函数自己的意思,一般用于递归调用,但是由于在strict模式下会报错,所以一般不用
-
例子
function sumNum(a){
if(a==1){return 1}
return arguments.callee(a-1) + a //相当于sumNum(a-1) + a
}
3、求斐波那数列(斐波那那数列:前两个数是0 ,1,以后的每个数都是前面两个数之和)
函数式编程
1、 函数作为参数传递
- 数组的sort()方法:进行数组排序
var t = [46,2,33,4,55,9,8];
console.log(t.sort()); //sort()将数组里面的元素按照字符串大小的方式进行排序 [2, 33, 4, 46, 55, 8, 9]
console.log(t.sort(function(a,b){ //sort()里面可以传入一个比较函数作为参数
return a - b; // return a-b 在这里的意义是:如果a > b 则返回一个正数,a = b 则返回0,a < b 则返回一个负数
// [2, 4, 8, 9, 33, 46, 55]
}));
- 数组的map()方法:单独对数组的每一个元素进行操作并返回新数组
var t = [4,5,7,10,17];
var m = t.map(function(item,index,array){ // index,array参数可选。map方法IE9+支持,返回一个新数组,对原数组没有影响
return item * 2;
});
console.log(m); // [8,10,14,20,34]
- 数组的forEach()方法:
var t = [2,4,6,7,8];
var a = t.forEach(function(item,index){
item = item + 2;
console.log("index: " + index + " " + item );
});
console.log("++++" + a);
var b = t.map(function(item){
console.log(item); // 2,4,5,7,8 ,所以forEach过后原数组并不会发生改变
});
console.log(b); //[undefined,undefined,undefined,undefined,undefined]
forEach方法跟map()方法类似,都是遍历数组的每一元素执行参数里面的匿名函数,但是map返回一个新数组,而forEach返回undefined
2、函数本身也是一种对象
-
函数对象的构造函数是Function
var t = new Function("return 100");
// t是一个函数,相当于 function t(){return 100}. -
因为是对象,所以可以当做对象来使用
function fn(){
}
fn.age = 100;
console.log(fn.age); // 100
- 函数还有一些固有属性 比如:
fn.length // 函数定义的形参个数 - 注意:
- 函数虽然可以看做一种特殊的对象,但是typeof()后是function
function fn(a){
console.log(a);
};
console.log(typeof(a)); // function
- new Function() 与 new function(){}的区别
var t = new Function("return 100");
var tt = new function(){} // tt是一个对象,相当于 function fn(){}; var tt = new fn();
console.log(typeof(t)); //function
console.log(typeof(tt)); //object
//CON new Funtion() 声明定义一个函数;new function(){}是作为的是构造函数,它定义了一个对象
js的垃圾回收机制
机制
应用
-
数组的清零
arr = [1,2,4,5 ,6];
arr = null; // 不好,因为又新建了一个对象
arr = []; // 不好,因为相当于在内存中新建了一个空数组,再让arr指向那个空数组,而原来的数据还占用内存并等待回收
arr.length = 0; //最好 -
对象复用
var t = {};
for(var i = 0; i < 10; i ++){
//var t = {}; 假如在这里声明的话每一次循环都会创建一个新的空对象,所以最好在循环外声明
t.age = 100;
t.name = 'zho';
console.log(t);
};
t = null; // 如果对象不用了立刻设置成null
闭包
闭包的定义:
- 引用了自由变量的函数
自由变量:不在当前作用域声明的变量,例如
var a = 5;
function f(){
console.log(a);
}
此时f函数中的a就是自由变量
重点
- 被引用的自由变量会与函数一同存在,即使创造它的环境消失了,只要闭包函数还存在就它就不会被回收
function f1(i){
var a = 1;
var b = function(){
console.log(a++);
console.log(i++)
}
return b
}
var c = f1(4);
c(); // 1 4
c(); // 2 5
c(); //3 6
// CON 即使f1()调用结束,a,i本该被回收销毁,但是由于它与函数b存在关联所以会一直存在不被销毁,才导致执行三次c()打印的a和i分别是1,2,3和4,5,6
缺点
- 闭包会导致JavaScript执行效率下降(目前V8引擎已 经对闭包做了很多性能优化,基本不用考虑)
- 闭包导致内存会驻留,如果是大量对象的闭包环境注意 内存消耗。
应用
1、循环注册dom事件中的index
<!DOCTYPE>
<html lang="en">
<head><title></title></head><body>
<ul>
<li>1</li>
<li>2</li>
<li>3</li>
<li>4</li>
<li>5</li>
</ul>
<script>
var lis = document.querySelectorAll('li'); // 从文档结构中获取所有的li
for(var i = 0; i < lis.length; i++)
{
(function(a){
lis[a].onclick = function(e){
console.log(a);
}
})(i); // 加一个立即执行的函数,这样保证了每次点击打印的都是所点击li对应的index
}
</script>
</body>
</html>
2、setTimeOut中的闭包应用
<!DOCTYPE>
<html lang="en">
<head><title></title></head><body>
<script>
for(var i = 0; i < 10; i++){
(function(a){
setTimeout(function(){
console.log(a);
},2000);
})(i); // 0,1,2,3,4,5,6,7,8,9
}
for(var j = 0; j < 10; j++){
setTimeout(function(){
console.log(j);
},2000);
} // 10,10,10,10,10,10,10,10,10,10
</script>
</body>
</html>
对象的创建方式
1 直接单个创建
var a = {}; // 不能重复利用,设置公共属性的代码
2 工厂模式
function createCat(age,name){
var o = new Object();
o.age = age;
o.name = name;
o.run = function(){
console.log(o.name + o.age);
};
return o;
}
var cat1 = createCat(15,'cat1');
var cat2 = createCat(21,'cat2');
优点:
- 批量创建有公共属性的对象
缺点: - 对象的方法不能复用共享,多占用内存
- 不能识别对象的具体原型和构造函数 cat1 instanceof createCat; // false 指向的是Object.optotype 和 new Object(这不是我们想要的)
3 构造函数创建对象模式
function Cat(age,name){
this.age = age;
this.name = name;
this.run = function(){
console.log(this.name + this.age);
}
//注意:如果有返回值,但是返回的是简单类型,new Cat()会直接忽略继续返回this;如果是引用类型则会返回引用对象而不是this
}
var cat1 = new Cat(11,'cat1');
var cat2 = new Cat(22,'cat2');
console.log(cat1 instanceof Cat); // true
优点:
- 创建对象的时候默认初始化一些属性
- 可以通过instanceof追溯对象的原型以及构造函数 cat1 instanceof Cat; // true
缺点: - 对象方法也不能共享
4 原型构建对象: 在构造函数的原型上定义属性和方法
function Cat(){};
Cat.prototype.name = 'black cat';
Cat.prototype.run = function(){
console.log(this.name, this.age)
}
var cat1 = new Cat();
var cat2 = new Cat();
cat1.name = 'white cat';
// 对象属性操作有读取和设置两种模式
// 读取时:如果对象自己没有,就去原型链上找,如果找不到就返回undefined
// 设置是:如果对象自己没有该属性,就直接创建一个
//上面的var cat1.name = 'white cat' 就是属于对对象属性进行设置
console.log(cat1, cat2); // white cat black cat
有点:
- 方法可以共享
缺点: - 没有私有属性
5 组合3 4 创建对象
function Cat(age,name){
this.name = name;
this.age = age;
}
Cat.prototype.run = function(){
console.log(this.name, this.age)
}
var cat1 = new Cat(11, 'cat1');
var cat2 = new Cat(12, 'cat2');
cat1.run();
6 稳妥构造函数模式 ---
- 代码基本跟工厂模式一样
- var cat1 = creatCate(15,'cat1');
var cat1 = new createCate(15, 'cat1'); - 上面加new和不加new得到的结果完全一样
- 所以优缺点也跟工厂模式一样
对象的继承方式
原型继承 将子类构造函数的原型指向父类实例
// 原型继承
function Animal(name,age){
this.name = name;
this.age = age;
this.message = {name:'我是一个对象'};
}
Animal.prototype.run = function(){
console.log(this.age);
}
function Cat(name,age){
this.name = name;
this.age = age;
};
Cat.prototype = new Animal();
Cat.prototype.constructor = Cat;
var cat1 = new Cat('mycat',15);
console.log(cat1.message); // {name: "我是一个对象"}
cat1.run(); // 15
var cat2 = new Cat('maycat2',20);
console.log(cat1.message); // {name: "我是一个对象"}
- 缺点:
- 子类(Cat)的构造函数参数不能传递给父类的构造函数
- 子类的原型的constructor会被改变,需要自己改回来
- 如果父类有引用类型的属性,那么所有的子类会共享这个引用类型
组合继承 核心:在子类构造函数内部调用父类函数.call(this)
// 组合了:借用构造函数继承 + 原型继承
function Animal(name, age) {
this.name = name;
this.age = age;
}
Animal.prototype.run = function() {
console.log(this.age);
}
function Cat(name, age) {
// 借助父类的构造函数,给子类创建实例属性 也就是相当于继承父类
Animal.call(this,"mycat",15);
};
Cat.prototype = new Animal();
Cat.prototype.constructor = Cat;
// 为了继承Animal原型上的方法
var cat1 = new Cat('mycat', 15);
console.log(cat1.name); // mycat
原型式继承模式
本质
- 借用对象来构造另一对象
function object(o){ // o是要模拟的对象
var f = function(){};
f.prototype = o;
return new f();
}
var cat = {name:'zhozho', age : 19, color : 'red'};
var myCat = object(cat);
for(var i in myCat){
console.log('myCat ' + i + ' is ' + myCat[i]);
};
// 以上就是原型式继承的基本步骤
// 下面测试一些具体的细节
myCat.name = 'nozhozho';
console.log(myCat.name); // nozhozho
var myCat2 = object(cat);
console.log(myCat2.name); // zhozho
// CON myCat.name 改变不会影响到 myCat2.name,
//因为执行myCat.name = 'nozhozho'操作时只是在myCat对象上新建了一个name属性,
//并没有影响到原型
cat.name = 'newzhozho';
console.log(myCat2.name); // newzhozho
//CON 可见直接对原型对象进行修改会影响到具体的对象
优点
- 不需要使用new就可以直接构造另外的其它对象
缺点
寄生式继承模式 (原型式继承的扩展 先用原型式object创建一个对象,再对该对象添加一些属性或者方法)
function createCat(o){
var cat = object(o);
o.say = function(){
console.log('hi');
}
}
对象创建的最终模式
寄生组合模式
- 总结
纵观最终方法,它是为了方便一个对象从另一个更大的对象继承而设计的吧
//定义一个原型式继承
function object(o){
var F = function(){};
F.prototype = o;
return new F();
}
//发展成寄生继承
// 创建并返回一个以o为原型,同时constructor属性指向Cat的对象实例
function inheritFrom(o){
var t = object(o);
t.constructor = Cat;
return t;
}
//Animal构造函数
function Animal(name,age){
this.name = name;
this.age = age;
}
Animal.prototype.run = function(){
console.log(name + 'running');
}
//借用Animal构造函数来定义Cat的构造函数 ,使得Cat实例能够继承Animal的属性
function Cat(name,age){
Animal.call(this,name,age);
//call()将Animal中的this指向第一个参数(第一个参数规定是一个对象),
//同时以name,age为参数执行函数Animal
}
//inheritFrom(Animal.prototype)创建并返回了一个以Animal.prototype为原型的对象实例,并且该对象有一个constuctor指向Cat;
//将Cat的prototype再指向该对象
//Cat函数的新prototype确立,该prototype不仅有Animal.prototype的方法,同时还可以创建cat类特有的方法
Cat.prototype = inheritFrom(Animal.prototype);
//创建Cat对象实例子
var c = new Cat('zhoohz',12);
模拟私有变量
- 定义: 对象的某个属性只能通过对象的某个方法访问,而不能通过对象直接访问 (我感觉这个说法有问题,如下,方法一,name并不是Cat实例的一个属性)
我觉得应该这样定义?:就是某一个变量只能由特定的方法访问 - 实现
方法一
function Cat(){
var name = 'catcat';
this.getCat = function(){
return name;
}
}
var myCat = new Cat();
var theName = myCat.getCat;
// name 就是私有变量
方法二
function Cat(){
var name = 'catcat';
return {
getCat : function(){
return name;
}
}
}
var myCat = new Cat();
var theName = myCat.getCat;
// name 就是私有变量
Attention:
function Object(){
var nana = {zho: '周呀'};
return {
use : function(){
return nana;
}
}
}
var object = Object();
var outnana = object.use();
outnana.zho = '不是周'
console.log(outnana); // {zho: "不是周"}
console.log(object.use()); //{zho: "不是周"}
outnana = {zho:'德玛西亚洲'};
console.log(outnana); // {zho: "德玛西亚洲"}
console.log(object.use()); //{zho: "不是周"}
//CON return引用类型也是引用传递
// outnana = {} outnana指向的地址变了,所以也不会影响到nana
模块化演变
全局变量污染
情况一:
function fn(){
var a = b = c = 9;
// 此时a的作用域是函数fn范围内
// b,c都属于全局变量,这样全局变量就在不知不觉被污染了
}
情况二:
//a.js
var m = 0;
//b.js
var m = 'sss'
// 不同文件同名全局变量之间的污染
解决方法
- 命名空间
// a.js
var Shop = {}; // 顶层命名空间
Shop.User = {}; // 电脑的用户模块
Shop.User.UserList = {}; // 用户列表页面模块
Shop.User.UserList.length = 19; // 用户一共有19个
// b.js
Shop.User.UserDetail = {};
Shop.User.UserDetail.length = 20;
//在一起用
console.log(Shop.User.UserList.length);
Shop.User.UserDetail.length = 20;
// CON 命名空间方法就是用不同的对象表示不同的模块,然后将变量定义成不同的对象的属性
// Shortcoming 还是无法避免 var t = 0;这种定义方式对全局变量的污染
// zho.btn.js
(function(w){
//判断zho框架是否存在 作用是组件使用不用顾虑先后顺序,比如可以先引用动画组件也可以
if(!w.zho){
w.zho = {};
}
window.zho.Btn = {
getVal : function(){
console.log('val');
},
setVal : function(str){
console.log('setvale');
}
};
})(window || {});
// zho.animate.js
// 动画组件
(function(w){
if(!w.zho){
w.zho = {};
}
w.zho.animate = {};
})(window || {})
正则表达式
贪婪模式与全局匹配
- 默认是贪婪模式,但是当?字符紧跟在任何一个其他限制符 (*, +, ?, {n}, {n,}, {n,m}) 后面时,匹配模式是非贪婪的。贪婪模式就是尽可能多地匹配字符(注意“?”同时也是一个限制符,等价与{0,1}。
2.全局匹配的意思是匹配到一个字串之后是否继续匹配后面的字串。
情况一:
12 123
全局匹配
/\d+/g ==> 匹配结果:12、123
/\d+?/g ==> 匹配结果:1、2、1、2、3
非全局匹配
/\d+/ ==> 匹配结果:12
/\d+?/ ==> 匹配结果:1
情况二:
<a>aa</a>
非贪婪模式
/<.*>/ ===> <a>aa</a>
/<.*>/ ===> <a>
区分空字符、空白字符、空格符
a. 空字符表示什么字符都没有,长度为0,ascii值为0
b. 空白字符表示看不见的长度不为0的字符,它包含了换页符(\f)、换行符(\n)、回车符(\r)、制表符(\t)、垂直制表符(\v),而且还包含了空格符
c.匹配空字符
7
[a-z]* ====> 有两处匹配
常用的正则表达式
Email地址:
^\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$
分解
-a-+a.a
/\w+([-+.]\w+)*/ ====> -a、+a.a
分析:
a.至少要有一个\w字符
b.开头、结尾不能为-+.
c.-+.不能连续 反思:([-+.]\w+)* 与 [-+.]*\w+的区别
d.例如:不能匹配a--aaaa、-aa,可以匹配a.a,a.aaaaa,aaaa.aaaa,a+a-a
手机号码:
^(13[0-9]|14[5|7]|15[0|1|2|3|4|5|6|7|8|9]|18[0|1|2|3|5|6|7|8|9])\d{8}$
js匹配//
var pattern = ////,
str = '';
console.log(pattern.test(str));
^与$
a
b
^a/nb$ ===> 可以匹配
运算符优先级:
\B(?=(?:\d{3})+(?!\d))
一些难理解的运算符组合
12345678
(\d{3})+ ===> 123456
(\d{3})+(?!\d) ===> 345678
\B(?=(\d{3})+) ===> 将匹配到的\B替换成-得:1-2-3-4-5-678
\B(?=(\d{3})+(?!\d)) ===> 将匹配到的\B替换成-得:12-345-678
[^a] []中括号中^表示 非 的意思,所以[^]似乎可以用来表示所有字符
禁止输入含有的字符:[^\x22]+
{5}|.?$/(5)
[{5}|.?$/(5)]+ ===>{5}|*.?$、/(5),可见除了\外,其它字符在[]都没有特殊的意义,代表的都是一个普通的字符
js中使用正则表达式
- 创建方式
var reg1 = new RegExp('\d',i); // 注意:字符串中用斜线时要用转义字符 这里的正则表达式是 \d
var reg2 = /\d/gi - 说明
i 忽略大小学
g 全局匹配
gi 全局匹配+忽略大小写
m 多行 - 实例
var exp = /\d+/i;
console.dir((exp));
- 正则表达式的一些属性
lastIndex - 下次匹配开始的字符串索引位置,只有设置为g全 局模式的时候才有用,要么设置为0或者匹配下一个的起始位置。 - 测试
//test方法
var exp = /\d+/g;
console.dir((exp));
console.log(exp.test('123b')); // true
console.dir((exp.lastIndex)); // 3
//exec方法
console.log('exec方法');
var exp2 = /\d{2}/;
var arr = exp2.exec(12,34,56); // exec方法返回的是一个数组
console.log('lastIndex' + exp2.lastIndex); // 0
console.log(arr); // ["12", index: 0, input: "12"]
console.log(arr['input']); // 12
console.log(arr[0]); //12 arr[1] 是undefined
console.log(exp2.exec(12,34,56)); // ["12", index: 0, input: "12"]
console.log('lastIndex' + exp2.lastIndex); // 0
//加上参数g的时候
var exp3 = /\d{2}/g;
var str = [12,34,56];
console.log('++++');
console.log(exp3.exec([12,34,56])); // ["12", index: 0, input: "12,34,56"]
console.log(exp3.lastIndex); // 2
console.log(exp3.exec([12,34,56])); // ["34", index: 3, input: "12,34,56"]
console.log(exp3.lastIndex); // 5
console.log(exp3.exec([12,34,56])); // ["56", index: 6, input: "12,34,56"]
console.log(exp3.lastIndex); // 8
console.log(exp3.exec([12,34,56])); // null
console.log(exp3.lastIndex); // 0
// CON g与正则对象的lastIndex属性紧密相连,当没有g时,lastIndex一直未0;有g时则会一直变
console.log('----');
console.log(exp3.exec(str)); // ["12", index: 0, input: "12,34,56"]
console.log(exp3.exec(str)); // ["34", index: 3, input: "12,34,56"]
console.log(exp3.exec(str)); // ["56", index: 6, input: "12,34,56"]
console.log(exp3.exec(str)); // null
console.log('++++');
console.log(exp3.exec([11,22,33])); // ["11", index: 0, input: "11,22,33"]
console.log(exp3.exec([44,55,66])); // ["55", index: 3, input: "44,55,66"]
console.log(exp3.exec([77,88,99])); // ["99", index: 6, input: "77,88,99"]
console.log(exp3.exec([10,12,13])); // null
// 取出str中的所有数字
console.log('取出所有数字');
var temp;
while((temp = exp3.exec(str)) != null){
console.log(temp[0]);
};
// 12 34 56
// 有分组时的情况
console.log('有分组时的情况');
var str2 = '12abc,34,fde,45asf';
var exp4 = /\d{2}(\w)(\w+)/g; // (\w)(\w+)表示分组
console.log(exp4.exec(str2)); // ["12abc", "a", "bc", index: 0, input: "12abc,34,fde,45asf"] ("a", "bc")就是分组的结果
console.log(exp4.exec(str2)); // ["45asf", "a", "sf", index: 13, input: "12abc,34,fde,45asf"]
console.log(exp4.exec(str2)); // null
字符串中的正则应用
str.search(exp) 返回首个匹配的字符串的索引值
str = "34,456,78,777";
var arr = str.search(/\d{3}/g);
console.log(arr); // 3
str.match(exp) 返回一个数组,根据参数是否有g返回的数组方式也不一样
str = "34,456,78";
var arr = str.match(/\d{2}/);
console.log(arr); // ["34", index: 0, input: "34,456,78"]
var arr2 = str.match(/\d{2}/g);
console.log(arr2); // ["34", "45", "78"]
// CON 没有g的时候,返回值跟exp.exec()方法返回的值一样
// 有g的时候则返回匹配到的字符串
str.replace(str/exp,str)
str = "34,456,78";
//将34替换成56
var arr = str.replace('34','56');
console.log(arr); // 56,456,78
var arr2 = str.replace(/\w+/,'z');
console.log(arr2); // z,456,78
var arr2 = str.replace(/\w+/g,'z');
console.log(arr2); // z,z,z
var arr3 = str.replace(/,/g,'-');
console.log(arr3); // 34-456-78
- replace()的特殊参数
var str = "12345";
console.log(str.replace('12','$$')); // $345 插入$
console.log(str.replace('12','$&$&')); // -1212345 $&:表示插入匹配的子串,也就是说这里的$&就代表12
console.log(str.replace('34','$`aa')); // 1212aa5 $`表示插入匹配子串左边的内容
console.log(str.replace('34',"$'aa")); // 125aa5 $'表示插入匹配子串右边的内容
var str1 = "12,34,56";
console.log(str1.replace(/(\d+)(,)/g,'$1--$2'));
//假如第一个参数是 RegExp对象,并且 n 是个小于100的非负整数,那么插入第 n 个括号匹配的字符串。提示:索引是从1开始
正则表达式应用实例
时间提取
// 时间提取
var t = "1392945632000,mss,Date(1392945632000)";
var date = t.replace(/.*Date\((\d+)\)/g,'new Date($1)');
console.log(date); // new Date(1392945632000)
console.log((eval(date))); // Fri Feb 21 2014 09:20:32 GMT+0800 (中国标准时间)
// eval()将字符串当做js代码执行
12,34,56 转成: “12”,”34”,”56”
var str = "12,34,56";
var change = str.replace(/\d+/g,'"$&"');
console.log(change);
12345678 转成 12-345-678
正则表达式写法:\B(?=(\d{3})+(?!\d)) ===> \B的作用是选择非单词间隔
12345678 转成 123-456-78
正则表达式写法:(?<=(?<!\d)(\d{3})+)\B
错误信息对象
throw()
- throw让开发人员抛出一个自定义的异常信息
- throw代码执行后,程序会中断到catch块,如果没有catch则程序终止执行
console.log('错误1'); // 错误1
throw("aaa"); // 错误信息对象.html:6 Uncaught aaa
console.log('错误2'); // 该语句没有被执行
error对象
// 一般浏览器
var mistake = new Error("我是错误信息");
throw mistake; // Uncaught Error: 我是错误信息
//IE and edge
var mistake2 = new Error(50,"我是错误信息")
throw mistake2 ;
var mistake = new Error();
mistake.message = "我是错误信息";
mistake.name = "错误名字";
throw mistake;
ES6 promise对象
# 创建
var promise = new Promise(function(resolve, reject){
if(/*一步操作成功*/){
resolve(value);
}else{
reject(error);
}
})
//resolve和reject是由JavaScript引擎提供的两个函数,不用自己部署
//resolve的作用是将promise的状态由pending变为resolved,并且将value作为参数传递出去
//reject的作用是将promise的状态由pending变为rejected,并且将error作为参数传递出去
promise.then(function(value),function(error))
//当promise状态改为resolved是执行第一个函数,改为reject则执行第二个函数

浙公网安备 33010602011771号