005.聊聊 JavaScript 正则表达式 上卷

假设用户需要在HTML 表单中填写姓名、地址、出生日期等。那么在将表单提交到服务器进一步处理前,JavaScript 程序会检查表单以确认用户确实输入了信息并且这些信息是符合要求的。

1.什么是正则表达式?

正则表达式(regular expression)是一个描述字符模式的对象。ECMAScript 的RegExp 类表示正则表达式,而String 和RegExp 都定义了使用正则表达式进行强大的模式匹配和文本检索与替换的函数。
  正则表达式主要用来验证客户端的输入数据。用户填写完表单单击按钮之后,表单就会被发送到服务器,在服务器端通常会用PHP、ASP.NET 等服务器脚本对其进行进一步处理。

因为客户端验证,可以节约大量的服务器端的系统资源,并且提供更好的用户体验

2.正则表达式的语法

1.直接量语法:

 /pattern/attributes;

2.创建RegExp对象的语法

new RegExp(pattern,attributes);

参数:参数pattern是一个字符串,指定了正则表达式的模式;
参数attributes是一个可选的参数,包含属性 g,i,m,分别使用与全局匹配,不区分大小写匹配,多行匹配;
@return 返回值:一个新的RegExp对象,具有指定的模式和标志;

g,i,m说明图

3.支持正则表达式的String对象的方法

(1) search()方法;

该方法用于检索字符串中指定的子字符串或检索与正则表达式相匹配的字符串。

基本语法: stringObject.search(regexp);

  • @param : 参数regexp可以需要在stringObject中检索的字符串,也可以 是需要检索的RegExp对象。
  • @return(返回值) : stringObject中第一个与regexp对象相匹配的子串的起始位置。如果没有找到任何匹配的子串,则返回-1;

注意:search()方法不执行全局匹配,它将忽略标志g,同时它也没有regexp对象的lastIndex的属性,且总是从字符串开始位置进行查找,总是返回的是stringObject匹配的第一个位置

测试代码

var str = "hello world,hello world";
// 返回匹配到的第一个位置(使用的regexp对象检索)
console.log(str.search(/hello/)); // 0
// 没有全局的概念 总是返回匹配到的第一个位置
console.log(str.search(/hello/g)); //0

console.log(str.search(/world/)); // 6

// 也可以是检索字符串中的字符
console.log(str.search("wo")); // 6

// 如果没有检索到的话,则返回-1
console.log(str.search(/longen/)); // -1

// 我们检索的时候 可以忽略大小写来检索
var str2 = "Hello";
console.log(str2.search(/hello/i)); // 0

(2) match()方法

该方法用于在字符串内检索指定的值,或找到一个或者多个正则表达式的匹配。该方法类似于indexOf()或者lastIndexOf(); 但是它返回的是指定的值,而不是字符串的位置;

基本语法:stringObject.match(searchValue) 或者 stringObject.match(regexp)

  • @param(参数)
     searchValue 需要检索字符串的值;
     regexp: 需要匹配模式的RegExp对象;
  • @return(返回值) : 存放匹配成功的数组; 它可以全局匹配模式,全局匹配的话,它返回的是一个数组。如果没有找到任何的一个匹配,那么它将返回的是null;返回的数组内有三个元素,第一个元素的存放的是匹配的文本,还有二个对象属性;index属性表明的是匹配文本的起始字符在stringObject中的位置;input属性声明的是对stringObject对象的引用;

测试代码:

var str = "hello world";
console.log(str.match("hello")); // ["hello", index: 0, input: "hello world"]
console.log(str.match("Hello")); // null
console.log(str.match(/hello/)); // ["hello", index: 0, input: "hello world"]
// 全局匹配
var str2="1 plus 2 equal 3"
console.log(str2.match(/\d+/g)); //["1", "2", "3"]

(3) replace()方法

该方法用于在字符串中使用一些字符替换另一些字符,或者替换一个与正则表达式匹配的子字符串;

基本语法:stringObject.replace(regexp/substr,replacement);

  • @param(参数)
     regexp/substr; 字符串或者需要替换模式的RegExp对象。
     replacement:一个字符串的值,被替换的文本或者生成替换文本的函数。
  • @return(返回值) 返回替换后的新字符串

注意:字符串的stringObject的replace()方法执行的是查找和替换操作,替换的模式有2种,既可以是字符串,也可以是正则匹配模式,如果是正则匹配模式的话,那么它可以加修饰符g,代表全局替换,否则的话,它只替换第一个匹配的字符串;

  • replacement : 既可以是字符串,也可以是函数,如果它是字符串的话,那么匹配的将与字符串替换,replacement中的$有具体的含义

1,1,2,3....3....99 含义是:与regexp中的第1到第99个子表达式相匹配的文本

$& 的含义是:与RegExp相匹配的子字符串。
var str = "hello world";
// 替换字符串
var s1 = str.replace("hello","a");
console.log(s1);// a world
// 使用正则替换字符串
var s2 = str.replace(/hello/,"b");
console.log(s2); // b world

// 使用正则全局替换 字符串
var s3 = str.replace(/l/g,'');
console.log(s3); // heo word

// $1,$2 代表的是第一个和第二个子表达式相匹配的文本
// 子表达式需要使用小括号括起来,代表的含义是分组
var name = "longen,yunxi";
var s4 = name.replace(/(\w+)\s*,\s*(\w+)/,"$2 $1");
console.log(s4); // "yunxi,longen"

// $& 是与RegExp相匹配的子字符串
var name = "hello I am a chinese people";
var regexp = /am/g;
if(regexp.test(name)) {
    //返回正则表达式匹配项的字符串
    console.log(RegExp['$&']);  // am

    //返回被搜索的字符串中从最后一个匹配位置开始到字符串结尾之间的字符。
    console.log(RegExp["$'"]); // a chinese people

    //返回被查找的字符串从字符串开始的位置到最后匹配之前的位置之间的字符。
    console.log(RegExp['$`']);  // hello I 
    
    // 返回任何正则表达式查找过程中最后括号的子匹配。
    console.log(RegExp['$+']); // 空字符串

    //返回任何正则表达式搜索过程中的最后匹配的字符。
    console.log(RegExp['$_']);  // hello I am a chinese people
}

// replace 第二个参数也可以是一个function 函数
var name2 = "123sdasadsr44565dffghg987gff33234";
name2.replace(/\d+/g,function(v){
    console.log(v); 
    /*
     * 第一次打印123
     * 第二次打印44565
     * 第三次打印987
     * 第四次打印 33234
     */
});
/*
 * 如下函数,回调函数参数一共有四个
 * 第一个参数的含义是 匹配的字符串
 * 第二个参数的含义是 正则表达式分组内容,没有分组的话,就没有该参数,
 * 如果没有该参数的话那么第四个参数就是undefined
 * 第三个参数的含义是 匹配项在字符串中的索引index
 * 第四个参数的含义是 原字符串
 */
 name2.replace(/(\d+)/g,function(a,b,c,d){
    console.log(a);
    console.log(b);
    console.log(c);
    console.log(d);
    /*
     * 如上会执行四次,值分别如下(正则使用小括号,代表分组):
     * 第一次: 123,123,0,123sdasadsr44565dffghg987gff33234
     * 第二次: 44565,44565,11,123sdasadsr44565dffghg987gff33234
     * 第三次: 987,987,22,123sdasadsr44565dffghg987gff33234
     * 第四次: 33234,33234,28,123sdasadsr44565dffghg987gff33234
     */
 });

(4) split()方法

该方法把一个字符串分割成字符串数组。

基本语法如:stringObject.split(separator,howmany);

  • @param(参数)
     1. separator[必填项],字符串或正则表达式,该参数指定的地方分割stringObject;
     2. howmany[可选] 该参数指定返回的数组的最大长度,如果设置了该参数,返回的子字符串不会多于这个参数指定的数组。如果没有设置该参数的话,整个字符串都会被分割,不考虑他的长度。
  • @return(返回值) 一个字符串数组。该数组通过在separator指定的边界处将字符串stringObject分割成子字符串。

测试代码

var str = "what are you doing?";
// 以" "分割字符串
console.log(str.split(" "));
// 打印 ["what", "are", "you", "doing?"]

// 以 "" 分割字符串
console.log(str.split(""));
/*
 * 打印:["w", "h", "a", "t", " ", "a", "r", "e", " ", "y", "o", "u", " ", "d", "o", "i", "n", 
 * "g", "?"]
 */
// 指定返回数组的最大长度为3
console.log(str.split("",3));
// 打印 ["w", "h", "a"]

4,RegExp对象方法

(1) test()方法

该方法用于检测一个字符串是否匹配某个模式;

基本语法:RegExpObject.test(str);

  • @param(参数) : str是需要检测的字符串;
  • @return (返回值) : 如果字符串str中含有与RegExpObject匹配的文本的话,返回true,否则返回false;

测试代码

var str = "longen and yunxi";
console.log(/longen/.test(str)); // true
console.log(/longlong/.test(str)); //false

// 或者创建RegExp对象模式
var regexp = new RegExp("longen");
console.log(regexp.test(str)); // true

(2) exec()方法

该方法用于检索字符串中的正则表达式的匹配。

基本语法:RegExpObject.exec(string)

  • @param(参数):string【必填项】要检索的字符串。
  • @return(返回值):返回一个数组,存放匹配的结果,如果未找到匹配,则返回值为null;

注意:该返回的数组的第一个元素是与正则表达式相匹配的文本,该方法还返回2个属性,index属性声明的是匹配文本的第一个字符的位置;input属性则存放的是被检索的字符串string;该方法如果不是全局的话,返回的数组与match()方法返回的数组是相同的。

测试代码

var str = "longen and yunxi";
console.log(/longen/.exec(str)); 
// 打印 ["longen", index: 0, input: "longen and yunxi"]

// 假如没有找到的话,则返回null
console.log(/wo/.exec(str)); // null

5.正则中的普通字符

字母,数字,汉字,下划线及一些没有特殊定义的标点符号,都属于普通字符,正则中的普通字符,在匹配字符串的时候,匹配与之相同的字符即可

代码说明

var str = "abcde";
console.log(str.match(/a/)); // ["a", index: 0, input: "abcde"]
如上代码,字符串abcde匹配a的时候,匹配成功,索引位置从0开始;

6.正则中的方括号[]的含义

方括号包含一系列字符,能够匹配其中任意一个字符

(red|blue|green); 查找小括号中的任意一项,小括号中的 | 是或者的意思;
列举1:表达式[bcd][bcd] 匹配 "abcde"时候,匹配成功,内容是bc,匹配到的位置开始于1,结束与3;
测试代码

var str = "abcde";
console.log(str.match(/[bcd][bcd]/)); // ["bc", index: 1, input: "abcde"]

7.理解javascript中的元字符

字符类:单个字符和数字

(1) 元字符 . : 用于匹配任何单个字符(除了换行符以外);
基本语法:new RegExp(“regexp.”) 或者 直接量语法 /regexp./
测试代码:

var str = "abcde";
console.log(str.match(/a.c/)); // ["abc", index: 0, input: "abcde"]

(2) \w ; 查找任意一个字母或数字或下划线,等价于[A-Za-z0-9_]
基本语法:new RegExp(“\w”); 或 直接量语法:/\w/
代码测试:

var str = "abcde";

// 匹配单个字符,找到一个直接返回
console.log(str.match(/\w/)); // ["a", index: 0, input: "abcde"]

// 匹配所有字符
console.log(str.match(/\w+/)); //["abcde", index: 0, input: "abcde"]

(3) \W ; 查找非单词的字符,等价于[^A-Za-z0-9_]
基本语法:new RegExp(“\W”) 或直接量 /\W/
代码测试:

var str = "abcde";

// 匹配单个字符,没有找到返回null
console.log(str.match(/\W/)); // null

(4) \d : 匹配与一个数字字符,等价于 [0-9];
基本语法:new RegExp(“\d”); 或 直接量语法:/\d/
代码测试:

var str = "abcde111";
console.log(/\d/g.exec(str)); // ["1", index: 5, input: "abcde111"]

(5) \D : 匹配一个非数字字符,等价于 [^0-9]
基本语法:new RegExp(“\D”) 或直接量 /\D/
代码测试:

var str = "abcde111";

console.log(/\D+/g.exec(str)); // ["abcde", index: 0, input: "abcde111"]
空白字符
锚字符

(6) \s : 匹配任何空白字符,包括空格,制表符,换行符等等。等价于 [\f\n\r\t\v]
基本语法:new RegExp(“\s”) 或直接量 /\s/
代码测试:

var str="Is this all there is?";

console.log(/\s/g.exec(str)); // [" ", index: 2, input: "Is this all there is?"]

(7) \S : 匹配任何非空白字符,等价于 [^\f\n\r\t\v]
基本语法:new RegExp(“\S”) 或直接量 /\S/
代码测试:

var str="Is this all there is?";

console.log(/\S+/g.exec(str)); // ["Is", index: 0, input: "Is this all there is?"]

(8) \b : 匹配一个单词边界,也就是指单词和空格间的位置,比如’er\b’可以匹配”never”中的”er”,但是不能匹配”verb”中的”er”
基本语法:new RegExp(“\bregexp”) 或直接量 /\bregexp/
代码测试:

var str="Is this all there is?";

console.log(/\bthis\b/g.exec(str)); // ["this", index: 3, input: "Is this all there is?"]

(9) \B : 匹配非单词边界,’er\B’ 能匹配 ’verb’ 中的 ’er’,但不能匹配’never’中的’er’
基本语法:new RegExp(“\Bregexp”) 或直接量 /\Bregexp/
代码测试:

var str="Is this all there is?";

console.log(/\Bhi/g.exec(str)); // ["hi", index: 4, input: "Is this all there is?"]

(10) \n : 匹配一个换行符;返回换行符被找到的位置。如果未找到匹配,则返回 -1。
基本语法:new RegExp(“\n”) 或直接量 /\n/
代码测试:

var str="Is this all \nthere is?";

console.log(/\n/g.exec(str)); // ["换行符", index: 12, input: "Is this all ↵there is?"]

(11) \xxx : 查找一个以八进制数xxx规定的字符,如果未找到匹配,则返回 null。
基本语法:new RegExp(“\xxx”) 或直接量 /\xxx/
代码测试:

var str="Visit W3School. Hello World!";

console.log(/\127/g.exec(str)); // ["W", index: 6, input: "Visit W3School. Hello World!"]

如上代码分析:127的八进制转换为10进制的值等于 18的二次方 + 28的一次方 + 7*8的0次方 = 64 + 16 + 7 = 87 而W的ASCLL编码转换为10进制也是87,因此打印W

(12) \xdd : 查找以16进制数dd规定的字符。如果未找到匹配,则返回 null。
基本语法:new RegExp(“\xdd”) 或直接量 /\xdd/
代码测试:

var str="Visit W3School. Hello World!";

console.log(/\x57/g.exec(str)); // ["W", index: 6, input: "Visit W3School. Hello World!"]

W的16进制数等于57;

(13) \uxxxx : 查找以16进制数的xxxx规定的Unicode字符。
基本语法:new RegExp(“\uxxx”) 或直接量 /\uxxx/
代码测试:

var str="Visit W3School. Hello World!";

console.log(/\u0057/g.exec(str)); // ["W", index: 6, input: "Visit W3School. Hello World!"]

8.RegExp特殊字符中的需要转义字符

  • 需要转义的特殊字符前面加 \
  • 匹配输入字符串的结尾位置,如果需要匹配匹配输入字符串的结尾位置,如果需要匹配本身的话,使用$
  • ^ 匹配输入字符串的开始位置,匹配^本身的话,使用 \^
  • * 匹配前面的子表达式的零次或者多次,匹配*本身的话,使用 \*
  • +匹配子表达式的1次或者多次,匹配+本身的话,使用 \+
  • . 匹配除换行符之外的任何一个字符,匹配.本身的话,使用 \.
  • [ 匹配一个中括号开始,匹配本身的,使用 \[
  • ? 匹配前面的子表达式的零次或者1次,或指明一个非贪婪限定符,要匹配本身的话,使用 \?
  • \ 匹配本身的话,请使用\\\\
  • { 标记限定符开始的地方,要匹配{ ,请使用 \{
  • | 指明多项中的一个选择,可以理解含义为或的意思,匹配本身的话,使用 \|

9.了解量词

了解量词

(1) n+ 匹配至少包含一个或者多个n的字符串。
基本语法:new RegExp(“n+”) 或直接量 /n+/
测试代码:

var str = "hello longen";

// 匹配至少一个或者多个l的字符串

console.log(str.match(/l+/g)); //["ll", "l"]

// 匹配至少一个或者多个字母数字或者下划线

console.log(str.match(/\w+/g)); //["hello", "longen"]

(2) n 匹配零个或者多个n的字符串。
基本语法:new RegExp(“n*”) 或直接量 /n*/
测试代码:

var str = "hello longen hello";

// 匹配至少零个或者多个l的字符串 

// 可以匹配多个l或者不匹配l 全局匹配

console.log(str.match(/el*/g)); //["ell", "e", "ell"]

// 可以匹配多个u或者不匹配u 全局匹配

console.log(str.match(/hu*/g)); //["h", "h"]

(3) n? 匹配零个或者1个n的字符串,可以匹配n字符串,也可以只匹配一个n;先尽量匹配,如没有匹配到,就回溯,再进行不匹配;
基本语法:new RegExp(“n?”) 或直接量 /n?/
测试代码:

var str = "hello longen hello";

// 匹配至少零个或者1个l的字符串 

console.log(str.match(/el?/g)); //["el", "e", "el"]

// 可以匹配1个u或者不匹配u 全局匹配

console.log(str.match(/hu?/g)); //["h", "h"]

(4) n{x} 匹配包含x个的n的序列字符串。X必须是数字。
基本语法:new RegExp(“n{x}”) 或直接量 /n{x}/
测试代码:

var str="100, 1000 or 10000?";

// 匹配4个数字的 匹配到1000和10000

console.log(str.match(/\d{4}/g)); //["1000", "1000"]

(5) n{x,y} 匹配包含至少x个的n字符串,最多y个n字符串。
基本语法:new RegExp(“n{x,y}”) 或直接量 /n{x,y}/
测试代码:

var str="100, 1000 or 10000?";

// 匹配最小3个数字,最多四个数字的 匹配到100,1000和10000

console.log(str.match(/\d{3,4}/g)); //["100", "1000", "1000"]

(6) n{x,} 匹配至少包含x个n序列的字符串;
基本语法:new RegExp(“n{x,}”) 或直接量 /n{x,}/
测试代码:

var str="100, 1000 or 10000?";

// 匹配最小3个数字 匹配到100,1000和10000

console.log(str.match(/\d{3,}/g)); //["100", "1000", "1000"]

(7) n$ 匹配任何以n结尾的字符串
基本语法:new RegExp(“n$”) 或直接量 /n$/
测试代码:

var str="my name is longen";

// 匹配以en结尾的字符串

console.log(str.match(/en$/g)); //["en"]

(8) ^n 匹配任何以n开头的字符串;
基本语法:new RegExp(“^n”) 或直接量 /^n/
测试代码:

var str="my name is longen";

// 匹配以my开头的字符串

console.log(str.match(/^my/g)); //["my"]

// 匹配以na开头的字符串,没匹配到,返回null

console.log(str.match(/^na/g)); //null

(9) ?=n 匹配任何其后紧接指定字符串n的字符串;
基本语法:new RegExp(“regexp(?=n)”) 或直接量 /regexp(?=n)/
测试代码:

var str="my name is longen";

// 匹配以na其后紧接m的字符串

// ?= 只是匹配位置,不会返回值

console.log(str.match(/na(?=m)/g)); //["na"]

(10) ?!n 匹配任何其后不紧接n的字符串 基本语法:new RegExp(“regexp(?!n)”)或直接量/regexp(?!n)/`
测试代码:

var str="my name is longen";

// 匹配以na其后不紧接ma的字符串

// ?! 只是匹配位置,不会返回值

console.log(str.match(/na(?!ma)/g)); //["na"]

console.log(str.match(/na(?!m)/g)); // null

(11) ^ 以字符串开始的地方匹配,不匹配任何字符;
比如:表达式^aaa 在匹配字符串 “longen aaa bbb”的时候,匹配的结果是失败的;因为^的含义是以某某字符串开始进行匹配;只有当aaa在字符串起始位置才能匹配成功;比如”aaa longen bbb” 才匹配成功;

(12) $ 以字符串结束的地方匹配,不匹配任何字符;
比如:表达式aaa在匹配字符串“longenaaabbb”的时候,匹配的结果是失败的;因为在匹配字符串“longenaaabbb”的时候,匹配的结果是失败的;因为的含义是以某某字符串结束进行匹配;只有当aaa在字符串结束位置才能匹配成功;比如”longen bbb aaa” 才匹配成功;

(13) \b 匹配一个单词边界,也就是单词与空格之间的位置,不匹配任何字符;
测试代码:

var str="my name is longen";

// 匹配单词边界的字符

console.log(str.match(/\bname\b/g)); //["name"]

// 如果不是单词边界的地方,就匹配失败

console.log(str.match(/\blong\b/g)); // null

(14) | 左右两边表达式之间 “或” 关系,匹配左边或者右边。
测试代码:

var str = "hello world";

// 使用|的含义是 或者 匹配成功 结果为["hello "] 

//如果再次匹配的话 就是world

console.log(str.match(/(hello | world)/g)); // ["hello "]

(15) ()的含义
在被修饰匹配次数的时候,括号中的表达式可以作为整体被修饰。取匹配结果的时候,括号中的表达式匹配到的内容可以被单独得到。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,126评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,254评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,445评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,185评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,178评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,970评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,276评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,927评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,400评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,883评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,997评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,646评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,213评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,204评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,423评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,423评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,722评论 2 345

推荐阅读更多精彩内容