慧慧结束了,长达几个月的划水生活,开始学一些东西了(希望期末这个不要挂)
这个是学长给的http://huziketang.mangojuice.top/books/react/(然后……真的是懵,框架是什么)
这篇文章主要会介绍ECMAScript 2015的基本语法(我学东西比较慢,然后最近还一堆课和考试,但是这个绝对不会坑的)
好了,先解决我的一些问题:
什么是ECMAScript 2015?
es6 是js语言的下一代标准,15年6月发布,也就是ECMAScript(2015)
-------------------历史引用自--------------
http://www.css88.com/archives/6200
首先回顾一下JavaScript的历史,不清楚历史的人,很难理解JavaScript为什么会这样发展。下面就是一个简单的JavaScript发展时间轴:
1、1995:JavaScript诞生,它的初始名叫LiveScript。
2、1997:ECMAScript标准确立。
3、1999:ES3出现,与此同时IE5风靡一时。
4、2000–2005: XMLHttpRequest又名AJAX, 在Outlook Web Access (2000)、Oddpost (2002),Gmail (2004)和Google Maps (2005)大受重用。
5、2009: ES5出现,(就是我们大多数人现在使用的)例如foreach,Object.keys,Object.create和JSON标准。
6、2015:ES6/ECMAScript2015出现。
----------------------------------------------------
下面进入正题
本文主要依据为书(如下图)以及各种各样的网站
大家也可以自行参观电子书,网址如下:
http://es6.ruanyifeng.com/#README
那么,var又是什么呢?
简而言之,就是声明一个变量。
在js中不声明而直接给一个变量赋值也是可以的,但这样的变量默认是全局的
#感觉这就像C++里在main函数外面声明函数和在main函数里面声明一样,然后也神似Python里的global关键字。
console.log()是什么用处呢?
http://www.ok12.net/js/about-js-console-log.html
console.log()在控制台中打印,打印任意字符或者js的变量信息;console.log()可以接受任何字符串、变量、数字、对象;
简而言之——就是为了好看
函数相关http://www.runoob.com/js/js-function-definition.html
和C++以及Python看起来差距不大,格式也比较类似都是一个函数的声明,参数,返回值。不再赘述。
下面还有些内容,不过等到需要用的时候吧
总之,和其他的const没什么区别。
当然还有其他声明变量的方法,比如global……
侵删。
es6 是js语言的下一代标准,15年6月发布,也就是ECMAScript(2015)
ECMAScript就是JavaScript的国际标准,js是es的实现
1996年11月,网景公司把js语言提交给国际标准组织ECMA,希望能成为国际标准推广,1997年,ECMA发布ECMAScript 1.0
兼容的不是很好,但是写起来方便也多了很多新的好玩的东西
编译的话咱们就暂时先用咱们react使引入的哪个browser.js + type=‘text/bable’
不用的话也可能能用,但是有的地方就体现不出来了,还是编译的好
为什么要有es6呢?自己想一下去吧..
编译es6的方式:
1.浏览器编译
2.gulp
用途及区别:ES6新增了let命令,用来声明变量,类似于var ,但是声明的变量只在let所在的代码块的内部有效
let不存在变量提升,这个要注意哟
只要块级作用域里存在let命令,它所声明的变量就绑定这个区域,不在受外部的影响
内部的数据有了let声明过之后不允许重复声明
为什么需要块级作用域:
es5只有全局作用域和函数作用域,这样会造成一些不合理的地方
1>.内部数据覆盖外部数据的情况
2>.用来计数的循环变量泄露成全局变量
虽然没有专业的定义词语来创建块级作用域,但是只要内部用了let,就说明一个块级作用域存在了,也就是说let命令为es6增加了块级作用域
在以往我们为了让变量执行完成后就销毁不要浪费内存,会把大段的代码放到立即执行函数里,而现在,我们只需要一个块级作用域就好了
声明只读常量 一旦声明就不能进行更改,使用起来和var,let定义的的变量一样,也是属于块级的定义方式,和let相似,也是不可重复声明的
但是对象呀,数组呀都是可以改这个对象或者数组的东西的,因为真正保存的是地址,数组多个元素少个元素又不会改变地址
const person=Object.freeze({});
var cat=Object.freeze({});
赋值的时候可以用Object.freeze来冻结一个对象,使其内部的属性和方法不能更改
全局对象是最顶层的对象,在浏览器环境指的是window对象,在node。js指的是global对象,在js语言里,所有变量都是全局对象的属性,
es6规定,var 和function声明的全局变量不属于全局对象的属性,let、const、class声明的全局变量不属于全局对象的属性
destructuring es6允许按照一定模式,从数组和对象里提取值,对变量进行赋值,这被成为解构赋值
var [a,b,c]=[1,2,3];
指定默认值 注意:es6内部使用严格相等运算符(===)判断一个位置上是否有值,所以如果一个数组成员不严格等于undefined,默认值是不会生效的
注意数组对应的如果不是数组的话就会报错
解构不仅可以用于数组,还可以用于对象 对象的属性没有次序,变量必须与属性同名,才能取到正确的值
默认值生效的条件是,对象的属性值严格等于undefined
在以前的时候,当用户传入数组或者对象的时候,需要在函数内部对这个数组或者对象进行解构并且赋值、设置默认值 es6可以直接在函数的形参部分进行解构赋值和设置默认值
var food='蛋糕';
var drink='可乐'; var str=`今天的早餐是${food}和${drink}`;
利用kitchen函数来操作拼接字符串的过程,注意的是这个函数的返回值就是最终的字符串 它接受两个参数,第一个是一个数组,前几位元素都是没有拼接变量时的每一个小的部分,并且这个数组有个属性叫raw,里面也会存放所有的元素 第二个参数可以用...的方式来接受所有的要拼接的变量
```
let food='蛋糕';
let drink='可乐';
let str=kitchen`今天的早餐是${food}和${drink}`;
function kitchen (strings,...values) { console.log(strings.raw) console.log(values) return ... } console.log(str) ```
let str='今天是hige好日子,心想的事儿都能成'
//1.判断是否是以...开头
console.log(str.startsWith('今天'))//true //2.判断是否是以...结尾 console.log(str.endsWith('能成'))//true //3.判断是否包含... console.log(str.includes('心'))//true
四:函数相关
1.设置函数参数的默认值
function fn (x=1,y=2) {
return [x,y] } console.log(fn())
2.es6里面的... 叫法有两个
1.展开操作符 spread 可以在一个数组或者一个对象里快速添加另一个数组的元素或者另一个对象的属性方法,同名属性会覆盖
var fruit=['apple','banana']; var food=['meat',...fruit]; console.log(fruit); console.log(...fruit) console.log(food) var obj={ name:'xiu', age:13 } console.log(obj) console.log({x:1,...obj}) 2.剩余操作符 rest 多在函数里设置不确定数量的参数时使用 function lunch (meat,drink,...food) { console.log(meat) console.log(drink) console.log(food) console.log(...food) } lunch('beaf','cola','bread','baozi') 3.name属性 es6为函数增加了一个name属性,需要注意的是,匿名函数的name是引用的变量的名字,又赋值又有名字的函数name是函数自己的函数名 function myFn (argument) { } console.log(myFn.name)//myFn var yourFn=function(){ } console.log(yourFn.name)//yourFn var hisFn=function herFn(){ } console.log(hisFn.name)//herFn
4.es6新增了箭头函数这样的定义函数的方式,箭头前面的是函数的参数,多个参数可以用()包裹,没有参数也要写(),箭头后面的是函数的返回值,如果写成{}包裹的话,就是函数体,需要返回值的时候要return
var fn=a=>a+1; // function fn (a) { // return a+1; // } console.log(fn(5))//6 var fn2=(a,b)=>{ return a+b; } // function fn2(a,b){ // return a+b; // } console.log(fn2(1,2))//3
es6 觉得如果一个对象想拥有和一个变量的名字一样的键名,并且值为这个变量的值的属性的话,可以直接在键值对里直接写一个变量名就好, 如果想设置一个方法,可以直接写方法名(){}
let name='allen',
age=13;
let person={ name, age } console.log(person) var laugh=()=>{alert('hahaha')} let xiu={ laugh, sayHello(name='美女'){alert(`${name},你好`)}, ...person } xiu.laugh(); xiu.sayHello(); xiu.sayHello('baobao');
let person={};
person.name='xiuxiu';
person.firstName='zhao';
person['second name']='wang'; let myname='third name' person[myname]='gao'; console.log(person)
console.log(+0==-0)//true console.log(+0===-0)//true console.log(NaN==NaN)//false console.log(NaN===NaN)//false console.log(Object.is(+0,-0))//false console.log(Object.is(NaN,NaN))//true
var person={};
Object.assign(person,
{name:'zhaoxiu'},
{name:'wangxiu'},
{age:18}
)
console.log(person)//{name:wangxiu,age:18}
var a= Object.create(b)//把b作为原型来创建一个对象a
Object.getPrototypeOf(a)//获取a的原型 Object.setPrototypeOf(a,c);把a的原型设置为c var wang={ getName(){ return '王' } } var li={ getName(){ return '李' } } var xiu=Object.create(wang); console.log(xiu.getName())//王 console.log(Object.getPrototypeOf(xiu)==wang)//true Object.setPrototypeOf(xiu,li); console.log(xiu.getName())//李 console.log(Object.getPrototypeOf(xiu)==li)//true
通过{}.prototype可以来设置原型,但是输出出来的却是__proto__,es6觉得,那就直接设置__proto__吧
var wang={
getName(){
return '王'
}
}
var li={ getName(){ return '李' } } var xiu={ __proto__:wang } console.log(xiu.getName())//王 console.log(Object.getPrototypeOf(xiu)==wang)//true xiu.__proto__=li console.log(xiu.getName())//李 console.log(Object.getPrototypeOf(xiu)==li)//true
在对象的方法里可以使用它原型的方法和属性,这个时候在方法里用super来代表自己的原型
var wang={
getName(){
return '王'
}
}
var xiu={ __proto__:wang, getFname(){ console.log(super.getName()) } } console.log(xiu) xiu.getFname()
es6里引入了类的概念,可以通过class关键字来创建一个类,里面的contructor方法可以用来接受实例化时传入的参数,还可以在class里写一些自定义的方法,不需要加,哦
class person {
constructor(name){
this.name=name
}
say(){ console.log(this.name) } } var xiu=new person('xiu') console.log(xiu) //..... person name:"xiu" __proto__:Object constructor:person(name) say:say() __proto__:Object //............ xiu.say()///xiu
es6觉得获取属性和设置属性的这些操作可以专门用set和get关键字来定义
class person {
constructor(name){
this.name=name; this.hobby=[]; } get gethobby(){ return this.hobby; } set sethobby(hobby){ return this.hobby.push(hobby); } } var xiu=new person('xiu') console.log(xiu.hobby) //[] console.log(xiu.gethobby) //[] console.log(xiu.sethobby='sing') //sing console.log(xiu.hobby) // ['sing'] console.log(xiu.gethobby)// ['sing']
static 可以设置一个这个类自己的静态方法,只有类可以调用,实例不可以调用
class person {
constructor(name){ this.name=name; } say(word){ console.log(word) } static superSay(word){ console.log(word) } } var xiu=new person('xiu'); xiu.say('hello') //person.say('hello')//error //xiu.superSay('hello')//error person.superSay('hello')
es6的class里,可以用extentds来进行继承class Student extends Person{ },但是如果父类通过constructor接受了参数,并且之类需要接受更多的参数,那么需要在子类的constructor里用super来给父类contructor传递参数,剩下的再自己使用
class Person {
constructor(name,age){ this.name=name; this.age=age; } say(){ console.log(`${this.name},${this.age}`) } } new Person('xiu',18).say()//xiu,18 class Student extends Person{ constructor(name,age,sex){ super(name,age) this.sex=sex } } console.log(new Student('liu',13,'male'))
1.es6新增了Set这个集合类型,类似与数组,但是还是有不少区别,比如,Set里不能有重复元素,重复元素不报错但是也不会添加进去
通过new Set()来创建。初始参数是字符串 其他的要报错,会把字符串拆开了作为元素
var _set=new Set('123')
console.log(_set)//Set {"1", "2", "3"}
Set里不能有重复元素,重复元素不报错但是也不会添加进去
var _set2=new Set('1223')
console.log(_set2)//Set {"1", "2", "3"}
通过add方法来添加新元素 并且新添加是什么类型就是什么类型
var _set3=new Set('123')
_set3.add(4) console.log(_set3)//Set {"1", "2", "3",4}
通过size来获取元素个数
var _set4=new Set('123') console.log(_set4.size)//3 通过delete来删除元素 var _set5=new Set('123') _set5.delete('3') console.log(_set5)//Set {"1", "2"} 通过has来判断有没有这个元素 var _set6=new Set('123') console.log(_set6.has('3'))//true 可以通过forEach来遍历 var _set7=new Set('123') _set7.forEach((x,i)=>{ console.log(`${x},${i}`) }) // 1,1 // 2,2 // 3,3 通过clear来清除元素 var _set8=new Set('123') _set8.clear(); console.log(_set8)//Set {}
2.map,es6新增了map这样的类型,和{}的区别就是可以用任何数据类型作为键值
let map=new Map(); //获取一个map
let [obj,fn,str]=[{},function () {},'hello']; map.set(obj,'obj') //设置map的键值对 map.set(fn,'fn') map.set(str,'str') console.log(map) console.log(map.size)//获取map的键值对个数 console.log(map.get(obj)) //获取map的键对应的值 console.log(map.delete(fn)); //删除某一个键值对 console.log(map.has(fn)) //检测是否包含某个键名 console.log(map.has(obj)) map.forEach((value,key)=>{ //可以用forEach来遍历map console.log(value) console.log(key) }) map.clear(); //清空map的键值对 console.log(map)
10.模块系统
在es6里引入了模块的概念
export function square(x) {
return x * x;
}
import square from 'lib';
square(2)//4
export function square(x) {
return x * x;
}
export function diag(x, y) { return sqrt(square(x) + square(y)); }
import { square, diag } from 'lib';
console.log(square(11)); // 121 console.log(diag(4, 3));
import * as lib from 'lib'; square = lib.square;
re.match 尝试从字符串的起始位置匹配一个模式,如果不是起始位置匹配成功的话,match()就返回none。
函数语法:
re.match(pattern, string, flags=0)
函数参数说明:
参数 | 描述 |
---|---|
pattern | 匹配的正则表达式 |
string | 要匹配的字符串。 |
flags | 标志位,用于控制正则表达式的匹配方式,如:是否区分大小写,多行匹配等等。参见:正则表达式修饰符 - 可选标志 |
匹配成功re.match方法返回一个匹配的对象,否则返回None。
我们可以使用group(num) 或 groups() 匹配对象函数来获取匹配表达式。
匹配对象方法 | 描述 |
---|---|
group(num=0) | 匹配的整个表达式的字符串,group() 可以一次输入多个组号,在这种情况下它将返回一个包含那些组所对应值的元组。 |
groups() | 返回一个包含所有小组字符串的元组,从 1 到 所含的小组号。 |
re.search 扫描整个字符串并返回第一个成功的匹配。
函数语法:
re.search(pattern, string, flags=0)
函数参数说明:
参数 | 描述 |
---|---|
pattern | 匹配的正则表达式 |
string | 要匹配的字符串。 |
flags | 标志位,用于控制正则表达式的匹配方式,如:是否区分大小写,多行匹配等等。 |
匹配成功re.search方法返回一个匹配的对象,否则返回None。
我们可以使用group(num) 或 groups() 匹配对象函数来获取匹配表达式。
匹配对象方法 | 描述 |
---|---|
group(num=0) | 匹配的整个表达式的字符串,group() 可以一次输入多个组号,在这种情况下它将返回一个包含那些组所对应值的元组。 |
groups() | 返回一个包含所有小组字符串的元组,从 1 到 所含的小组号。 |
re.match只匹配字符串的开始,如果字符串开始不符合正则表达式,则匹配失败,函数返回None;而re.search匹配整个字符串,直到找到一个匹配。
Python 的 re 模块提供了re.sub用于替换字符串中的匹配项。
语法:
re.sub(pattern, repl, string, count=0, flags=0)
参数:
compile 函数用于编译正则表达式,生成一个正则表达式( Pattern )对象,供 match() 和 search() 这两个函数使用。
语法格式为:
re.compile(pattern[, flags])
参数:
pattern : 一个字符串形式的正则表达式
flags : 可选,表示匹配模式,比如忽略大小写,多行模式等,具体参数为:
在字符串中找到正则表达式所匹配的所有子串,并返回一个列表,如果没有找到匹配的,则返回空列表。
注意: match 和 search 是匹配一次 findall 匹配所有。
语法格式为:
findall(string[, pos[, endpos]])
参数:
和 findall 类似,在字符串中找到正则表达式所匹配的所有子串,并把它们作为一个迭代器返回。
re.finditer(pattern, string, flags=0)
参数:
参数 | 描述 |
---|---|
pattern | 匹配的正则表达式 |
string | 要匹配的字符串。 |
flags | 标志位,用于控制正则表达式的匹配方式,如:是否区分大小写,多行匹配等等。参见:正则表达式修饰符 - 可选标志 |
split 方法按照能够匹配的子串将字符串分割后返回列表,它的使用形式如下:
re.split(pattern, string[, maxsplit=0, flags=0])
参数:
参数 | 描述 |
---|---|
pattern | 匹配的正则表达式 |
string | 要匹配的字符串。 |
maxsplit | 分隔次数,maxsplit=1 分隔一次,默认为 0,不限制次数。 |
flags | 标志位,用于控制正则表达式的匹配方式,如:是否区分大小写,多行匹配等等。参见:正则表达式修饰符 - 可选标志 |
re.compile() 返回 RegexObject 对象。
group() 返回被 RE 匹配的字符串。
正则表达式可以包含一些可选标志修饰符来控制匹配的模式。修饰符被指定为一个可选的标志。多个标志可以通过按位 OR(|) 它们来指定。如 re.I | re.M 被设置成 I 和 M 标志:
修饰符 | 描述 |
---|---|
re.I | 使匹配对大小写不敏感 |
re.L | 做本地化识别(locale-aware)匹配 |
re.M | 多行匹配,影响 ^ 和 $ |
re.S | 使 . 匹配包括换行在内的所有字符 |
re.U | 根据Unicode字符集解析字符。这个标志影响 \w, \W, \b, \B. |
re.X | 该标志通过给予你更灵活的格式以便你将正则表达式写得更易于理解。 |
模式字符串使用特殊的语法来表示一个正则表达式:
字母和数字表示他们自身。一个正则表达式模式中的字母和数字匹配同样的字符串。
多数字母和数字前加一个反斜杠时会拥有不同的含义。
标点符号只有被转义时才匹配自身,否则它们表示特殊的含义。
反斜杠本身需要使用反斜杠转义。
由于正则表达式通常都包含反斜杠,所以你最好使用原始字符串来表示它们。模式元素(如 r'\t',等价于 '\\t')匹配相应的特殊字符。
下表列出了正则表达式模式语法中的特殊元素。如果你使用模式的同时提供了可选的标志参数,某些模式元素的含义会改变。
模式 | 描述 |
---|---|
^ | 匹配字符串的开头 |
$ | 匹配字符串的末尾。 |
. | 匹配任意字符,除了换行符,当re.DOTALL标记被指定时,则可以匹配包括换行符的任意字符。 |
[...] | 用来表示一组字符,单独列出:[amk] 匹配 'a','m'或'k' |
[^...] | 不在[]中的字符:[^abc] 匹配除了a,b,c之外的字符。 |
re* | 匹配0个或多个的表达式。 |
re+ | 匹配1个或多个的表达式。 |
re? | 匹配0个或1个由前面的正则表达式定义的片段,非贪婪方式 |
re{ n} | 精确匹配 n 个前面表达式。例如, o{2} 不能匹配 "Bob" 中的 "o",但是能匹配 "food" 中的两个 o。 |
re{ n,} | 匹配 n 个前面表达式。例如, o{2,} 不能匹配"Bob"中的"o",但能匹配 "foooood"中的所有 o。"o{1,}" 等价于 "o+"。"o{0,}" 则等价于 "o*"。 |
re{ n, m} | 匹配 n 到 m 次由前面的正则表达式定义的片段,贪婪方式 |
a| b | 匹配a或b |
(re) | 匹配括号内的表达式,也表示一个组 |
(?imx) | 正则表达式包含三种可选标志:i, m, 或 x 。只影响括号中的区域。 |
(?-imx) | 正则表达式关闭 i, m, 或 x 可选标志。只影响括号中的区域。 |
(?: re) | 类似 (...), 但是不表示一个组 |
(?imx: re) | 在括号中使用i, m, 或 x 可选标志 |
(?-imx: re) | 在括号中不使用i, m, 或 x 可选标志 |
(?#...) | 注释. |
(?= re) | 前向肯定界定符。如果所含正则表达式,以 ... 表示,在当前位置成功匹配时成功,否则失败。但一旦所含表达式已经尝试,匹配引擎根本没有提高;模式的剩余部分还要尝试界定符的右边。 |
(?! re) | 前向否定界定符。与肯定界定符相反;当所含表达式不能在字符串当前位置匹配时成功 |
(?> re) | 匹配的独立模式,省去回溯。 |
\w | 匹配字母数字及下划线 |
\W | 匹配非字母数字及下划线 |
\s | 匹配任意空白字符,等价于 [\t\n\r\f]. |
\S | 匹配任意非空字符 |
\d | 匹配任意数字,等价于 [0-9]. |
\D | 匹配任意非数字 |
\A | 匹配字符串开始 |
\Z | 匹配字符串结束,如果是存在换行,只匹配到换行前的结束字符串。 |
\z | 匹配字符串结束 |
\G | 匹配最后匹配完成的位置。 |
\b | 匹配一个单词边界,也就是指单词和空格间的位置。例如, 'er\b' 可以匹配"never" 中的 'er',但不能匹配 "verb" 中的 'er'。 |
\B | 匹配非单词边界。'er\B' 能匹配 "verb" 中的 'er',但不能匹配 "never" 中的 'er'。 |
\n, \t, 等. | 匹配一个换行符。匹配一个制表符。等 |
\1...\9 | 匹配第n个分组的内容。 |
\10 | 匹配第n个分组的内容,如果它经匹配。否则指的是八进制字符码的表达式。 |
实例 | 描述 |
---|---|
python | 匹配 "python". |
实例 | 描述 |
---|---|
[Pp]ython | 匹配 "Python" 或 "python" |
rub[ye] | 匹配 "ruby" 或 "rube" |
[aeiou] | 匹配中括号内的任意一个字母 |
[0-9] | 匹配任何数字。类似于 [0123456789] |
[a-z] | 匹配任何小写字母 |
[A-Z] | 匹配任何大写字母 |
[a-zA-Z0-9] | 匹配任何字母及数字 |
[^aeiou] | 除了aeiou字母以外的所有字符 |
[^0-9] | 匹配除了数字外的字符 |
实例 | 描述 |
---|---|
. | 匹配除 "\n" 之外的任何单个字符。要匹配包括 '\n' 在内的任何字符,请使用象 '[.\n]' 的模式。 |
\d | 匹配一个数字字符。等价于 [0-9]。 |
\D | 匹配一个非数字字符。等价于 [^0-9]。 |
\s | 匹配任何空白字符,包括空格、制表符、换页符等等。等价于 [ \f\n\r\t\v]。 |
\S | 匹配任何非空白字符。等价于 [^ \f\n\r\t\v]。 |
\w | 匹配包括下划线的任何单词字符。等价于'[A-Za-z0-9_]'。 |
\W | 匹配任何非单词字符。等价于 '[^A-Za-z0-9_]'。 |
字符串对象共有 4 个方法,可以使用正则表达式:match()
、replace()
、search()
和split()
。
ES6 将这 4 个方法,在语言内部全部调用RegExp
的实例方法,从而做到所有与正则相关的方法,全都定义在RegExp
对象上。
String.prototype.match
调用 RegExp.prototype[Symbol.match]
String.prototype.replace
调用 RegExp.prototype[Symbol.replace]
String.prototype.search
调用 RegExp.prototype[Symbol.search]
String.prototype.split
调用 RegExp.prototype[Symbol.split]
JavaScript 语言的正则表达式,只支持先行断言(lookahead)和先行否定断言(negative lookahead),不支持后行断言(lookbehind)和后行否定断言(negative lookbehind)。ES2018 引入后行断言,V8 引擎 4.9 版(Chrome 62)已经支持。
”先行断言“指的是,x
只有在y
前面才匹配,必须写成/x(?=y)/
。比如,只匹配百分号之前的数字,要写成/\d+(?=%)/
。”先行否定断言“指的是,x
只有不在y
前面才匹配,必须写成/x(?!y)/
。比如,只匹配不在百分号之前的数字,要写成/\d+(?!%)/
。
/\d+(?=%)/.exec('100% of US presidents have been male') // ["100"]
/\d+(?!%)/.exec('that’s all 44 of them') // ["44"]
上面两个字符串,如果互换正则表达式,就不会得到相同结果。另外,还可以看到,”先行断言“括号之中的部分((?=%)
),是不计入返回结果的。
“后行断言”正好与“先行断言”相反,x
只有在y
后面才匹配,必须写成/(?<=y)x/
。比如,只匹配美元符号之后的数字,要写成/(?<=\$)\d+/
。”后行否定断言“则与”先行否定断言“相反,x
只有不在y
后面才匹配,必须写成/(?。比如,只匹配不在美元符号后面的数字,要写成
/(?。
/(?<=\$)\d+/.exec('Benjamin Franklin is on the $100 bill') // ["100"]
/(?.exec('it’s is worth about €90') // ["90"]
上面的例子中,“后行断言”的括号之中的部分((?<=\$)
),也是不计入返回结果。
下面的例子是使用后行断言进行字符串替换。
const RE_DOLLAR_PREFIX = /(?<=\$)foo/g;
'$foo %foo foo'.replace(RE_DOLLAR_PREFIX, 'bar');
// '$bar %foo foo'
上面代码中,只有在美元符号后面的foo
才会被替换。
“后行断言”的实现,需要先匹配/(?<=y)x/
的x
,然后再回到左边,匹配y
的部分。这种“先右后左”的执行顺序,与所有其他正则操作相反,导致了一些不符合预期的行为。
首先,后行断言的组匹配,与正常情况下结果是不一样的。
/(?<=(\d+)(\d+))$/.exec('1053') // ["", "1", "053"]
/^(\d+)(\d+)$/.exec('1053') // ["1053", "105", "3"]
上面代码中,需要捕捉两个组匹配。没有“后行断言”时,第一个括号是贪婪模式,第二个括号只能捕获一个字符,所以结果是105
和3
。而“后行断言”时,由于执行顺序是从右到左,第二个括号是贪婪模式,第一个括号只能捕获一个字符,所以结果是1
和053
。
其次,“后行断言”的反斜杠引用,也与通常的顺序相反,必须放在对应的那个括号之前。
/(?<=(o)d\1)r/.exec('hodor') // null
/(?<=\1d(o))r/.exec('hodor') // ["r", "o"]
上面代码中,如果后行断言的反斜杠引用(\1
)放在括号的后面,就不会得到匹配结果,必须放在前面才可以。因为后行断言是先从左到右扫描,发现匹配以后再回过头,从右到左完成反斜杠引用。