JS高级

js的线程与任务- 浏览器

  • JavaScript执行线程
  • UI线程
  • JavaScript事件线程
  • 前两个线程互斥,所以JavaScript是单线程的
  • JavaScript的任务
    • 同步任务
    • 异步任务
    • 主线程直接执行同步任务,异步任务先执行一部分,然后退出主线程执行,等再次准备好之后又交给主线程执行
      - js的三个重点难点
  • 单线程和异步
  • 作用域和闭包
  • 原型原型链

JavaScript的执行模式

  1. 解释
  • 词法分析
  • 语法分析
  • 作用域规则确定
  1. 执行阶段
  • 创建执行上下文
  • 执行函数代码
  • 垃圾回收

函数执行上下文

函数的执行上下文也叫函数的执行环境或者Execution Context简称EC
EC会在函数调用时创建

  1. 创建执行上下文:
  • 建立作用域链
  • 创建变量对象
    * 创建arguments对象,初始化参数名称和值,并创建引用复制
    * 扫描上下文的函数声明,为每一个函数在变量对象创建一个属性(就是函数的名字),值则指向函数在内存中的引用。如果函数名已经存在,则指针将被重写
    * 扫描上下文的变量声明,为每一个函数在变量对象创建一个属性,并初始化为undefined。如果变量名已经存在则不会进行任何操作继续扫描。如果函数与变量同名,函数优先级高。
  • 确定this指向
  1. 执行
  • 变量赋值
  • 代码执行等等
  1. 回收销毁
  2. ** 函数执行上下文不等于函数上下文**

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>
  1. var 变量提升注意事项:如var果声明在后,则前面运用的该变量都是undefined值,意思是声明了,但是还没赋值(区别于控制台提示 is not defined,这表示的是变量还没有声明)

  2. 所以变量的提升是声明的提升,赋值并不会提升

  3. 因此注意如果是函数变量一定不可以在赋值前就调用,否则会提示错误

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,以后的每个数都是前面两个数之和)

4、 递归函数效率低,能不用就不用

函数式编程

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: "我是一个对象"}
  • 缺点:
  1. 子类(Cat)的构造函数参数不能传递给父类的构造函数
  2. 子类的原型的constructor会被改变,需要自己改回来
  3. 如果父类有引用类型的属性,那么所有的子类会共享这个引用类型

组合继承 核心:在子类构造函数内部调用父类函数.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;这种定义方式对全局变量的污染 
  • 自执行函数
    //a.js
    (function(){
    var a = 9;
    })();
    //b.js
    (function(){
    var a = 'sss';
    })();
  • 封装一个整体的框架
// 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 || {})

正则表达式

贪婪模式与全局匹配

  1. 默认是贪婪模式,但是当?字符紧跟在任何一个其他限制符 (*, +, ?, {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则执行第二个函数    
posted @ 2020-04-14 20:58  Mrzhozho  阅读(85)  评论(0)    收藏  举报