前端知识体系总结

数据结构与算法


栈和队列的区别

网络基础


HTTP 无状态怎么理解

可以从REST的角度来理解这个问题。我们知道REST风格是无状态的。而REST是基于HTTP协议的,所以REST的无状态基本就可以解释HTTP的无状态。

TCP三次握手与四次挥手

三次握手
为了准确无误地将数据送到目标处,TCP采用三次握手策略,过程中使用了TCP的标志:SYN和ACK.
三次握手
Client --> 置SYN标志 序列号 = J,确认号 = 0 ----> Server
Client <-- 置SYN标志 置ACK标志 序列号 = K, 确认号 = J + 1 <-- Server
Clinet --> 置ACK标志 序列号 = J + 1,确认号 = K + 1 --> Server
四次挥手
Client -> 发送FIN 序列号 = J,确认号 = 0 --> Server
Server -> 发送ACK 确认号 = J + 1
Server -> 发送FIN 序列号 = K, 确认号 = 0 -> Client
Client -> 发送ACK 确认号 = K+1

HTTPS

HTTPS是在HTTP与TCP之间添加一个安全协议层(SSL或TSL).
网络请求中往往中间需要很多服务器或者路由器的转发,中间的节点都可能篡改信息,而如果使用HTTPS,密钥在请求客户端和终点站才有,所以相对于HTTP会更安全,就是因为HTTPs利用SSL/TSL协议传输,它包含证书等安全信息,保证了传输过程的安全性。

前端其他问题


前端工程价值

  • 解放前后端互相在开发进度上的依赖问题,前后端可以同时进行
  • 为简化用户使用,提高交互体验/用户体验
  • 解决浏览器兼容问题
  • 提高浏览速度(性能)
  • 跨平台应用的支持
  • 展现数据,数据处理
  • 降低后端压力

浏览器缓存技术

** Etag **

当发送一个服务器的请求时,浏览器会首先进行缓存过期的判断,浏览器根据缓存过期的时间判断缓存文件是否过期。

  • 若没有过期,则不向服务器发送请求,直接使用缓存中的结果

Session Cookie LocalStorage

全局环境与局部环境

JS对象 BOM DOM

http://www.runoob.com/jsref/dom-obj-event.html

浏览器的基本组成与页面渲染原理

为什么不能频繁操作DOM

重排与重绘

前端模块化


https://segmentfault.com/p/1210000007731421?from=singlemessage&isappinstalled=1
CommonJs规范 - NodeJs实现 同步
分支 异步
AMD RequireJs
CMD SeaJs
UMD 前后端整合
ES6 - I import export

前端优化


前端兼容性


渐进增强和优雅降级

** 渐进增强 ** :针对低版本浏览器进行构建页面,保证最基本的功能,然后再针对高级浏览器进行效果、交互等改进和追加功能达到更好的用户体验。
** 优雅降级 ** :一开始就构建完整的功能,然后再针对低版本浏览器进行兼容。

前端安全性


常见的几种安全攻击

SQL 注入
XSS
CSRF

前端工具


模块打包工具

** Webpack **

  1. 模块打包工具
  1. 管理依赖模块间依赖,生成优化并且合并后的静态资源文件
  2. 编译输出静态文件,将代码分割成不同的chunk,实现按需加载,降低初始化时间
  3. 所有文件都是模块, html, js, css, 图片等
  4. 模块加载器, 支持串联操作
  5. 以commonJs的形式书写,但对AMD/CMD的支持也比较全面,方便旧项目进行代码迁移

HTML5


Webworker

Js中的多线程实现

Websocket

SVG

Canvas

CSS


position的值, relative和absolute分别是相对于谁进行定位的?

盒模型

IE盒模型与其他浏览器的盒模型

CSS选择器的优先级

JavaScript


ES6

参考:http://es6.ruanyifeng.com/

Js运行机制

http://www.ruanyifeng.com/blog/2014/10/event-loop.html

  • Javascript 是单线程
    Javascript 的单线程与它的用途有关
  • 任务队列
    任务可分为两种,一种是同步任务,一种是异步任务。
    ** 同步任务 :在主线程上排队执行的任务,只有前一个任务执行完毕后,才能执行后一个任务;
    ** 异步任务 :不进入主线程,而进入任务队列(task queue)的任务,只有任务队列通知主线程,某个异步任务要以执行了,该任务才会进入主线程执行。
    ** 异步任务运行机制如下:

    Paste_Image.png

    1)所有同步任务都在主线程上执行,形成一个执行栈(execution context stack)。
    2)主线程之外,还存在一个"任务队列"(task queue)。只要异步任务有了运行结果,就在"任务队列"之中放置一个事件。
    3)一旦"执行栈"中的所有同步任务执行完毕,系统就会读取"任务队列",看看里面有哪些事件。那些对应的异步任务,于是结束等待状态,进入执行栈,开始执行。
    4)主线程不断重复上面的第三步。
    ** 只要主线程空了,就会去读取"任务队列",这就是JavaScript的运行机制。这个过程会不断重复。
  • 事件和任务队列
    ** 任务队列 **
  1. 是一个事件的队列,也可以理解成是消息队列。主线程读取任务队列就是读取里面有哪些事件。
  2. 任务队列里面不止有IO设备的事件,也有用户操作产生的事件。只要指定过回调函数,这些事件发生时就会进入任务队列中,等待主线程读取。主线程执行异步任务,就是执行对应的回调函数。
  3. 主线程会首先检查执行时间,某些事件只有到了规定的时间,才能返回主线程。如setTimeout().
    ** Event Loop **
    主线程从事件队列中读取事件的过程是不断循环的,所以整个的运行机制就是一个Event Loop,也叫事件循环。


    Paste_Image.png
  4. 定时器
    除了放置异步任务的事件,"任务队列"还可以放置定时事件,即指定某些代码在多少时间之后执行。这叫做"定时器"(timer)功能,也就是定时执行的代码。
    setTimeout()
    setInterval()
    HTML5标准规定了setTimeout()的第二个参数的最小值(最短间隔),不得低于4毫秒,如果低于这个值,就会自动增加。在此之前,老版本的浏览器都将最短间隔设为10毫秒。另外,对于那些DOM的变动(尤其是涉及页面重新渲染的部分),通常不会立即执行,而是每16毫秒执行一次。这时使用requestAnimationFrame()的效果要好于setTimeout()。
    需要注意的是,setTimeout()只是将事件插入了"任务队列",必须等到当前代码(执行栈)执行完,主线程才会去执行它指定的回调函数。要是当前代码耗时很长,有可能要等很久,所以并没有办法保证,回调函数一定会在setTimeout()指定的时间执行。

Js事件模型

参考:https://segmentfault.com/a/1190000006934031?from=singlemessage&isappinstalled=1
DOM事件探秘

  • ** 发布订阅模式(观察者模式)**
    Javascript的事件模型基于发布请阅模式,可以让多个观察者对象同时监听某一个主题对象,这个主题对象的状态变化会通知所有的订阅者,使得它们能够做出反应。
    以下是用js实现的一个发布订阅模式
var events = (function () {
    var topics = {};
    return {
        publish: function (topic, info) {
            console.log("publish a topic:" + topic);
            if (topics.hasOwnProperty(topic)) {
                topics[topic].forEach(function(handler) {
                    handler(info ? info : {});
                });
            }
        },
        subscribe: function(topic, handler) {
            console.log("subscribe an topic" + topic);
            if (!topics.hasOwnProperty(topic)) {
                topics[topic] = [];
            }
            topics[topic].push(handler);
        },
        remove: function(topic, handler) {
            if (!topics.hasOwnProperty(topics)) {
                return;
            }
            var handlerIndex = -1;
            topics[topic].forEach(function (element, index) {
                if (element === handler) {
                    handlerIndex = index;
                }
            });
            if (handlerIndex >= 0) {
                topics[topic].splice(handlerIndex, 1);
            }
        }
    };
})();
var handler = function (info) {
    console.log(info);
}
events.subscribe("hello", handler);
events.publish("hello", "hello world");
  • ** 事件与事件流 **
    事件是与浏览器或文档交互的瞬间,如点击按钮,填写表格等,它是JS与HTML之间交互的桥梁。DOM是树形结构,如果同时给父子节点都绑定事件时,当触发子节点的时候,这两个事件的发生顺序如何决定?这就涉及到事件流的概念,它描述的是页面中接受事件的顺序。
    事件流有两种:
    ** 事件冒泡(Event Capturing): ** 是一种从下往上的传播方式。事件最开始由最具体的元素(文档中嵌套层次最深的那个节点接受, 也就是DOM最低层的子节点), 然后逐渐向上传播到最不具体的那个节点,也就是DOM中最高层的父节点。
    ** 事件捕获(Event Bubbling): **与事件冒泡相反。事件最开始由不太具体的节点最早接受事件, 而最具体的节点最后接受事件。
  • ** 事件模型 **
    ** DOM 0级模型 **
    HTML代码中直接绑定:
<input type="button" onclick="fun()">

通过JS代码指定属性值:

var btn = document.getElementById('.btn');btn.onclick = fun;

移除监听函数:

btn.onclick = null;

这种方式所有浏览器都兼容,但是逻辑与显示并没有分离。
** IE事件模型 **
IE事件模型共有两个过程:
事件处理阶段(target phase)。事件到达目标元素, 触发目标元素的监听函数。
事件冒泡阶段(bubbling phase)。事件从目标元素冒泡到document, 依次检查经过的节点是否绑定了事件监听函数,如果有则执行。
事件绑定监听函数的方式如下:

attachEvent(eventType, handler)

事件移除监听函数的方式如下:

detachEvent(eventType, handler)

参数说明:
eventType指定事件类型(注意加on)
handler是事件处理函数
Example:

var btn = document.getElementById('.btn');
btn.attachEvent(‘onclick’, showMessage);
btn.detachEvent(‘onclick’, showMessage);

** DOM 2级模型 **
属于W3C标准模型,现代浏览器(除IE6-8之外的浏览器)都支持该模型。在该事件模型中,一次事件共有三个过程:
事件捕获阶段(capturing phase)。事件从document一直向下传播到目标元素, 依次检查经过的节点是否绑定了事件监听函数,如果有则执行。
事件处理阶段(target phase)。事件到达目标元素, 触发目标元素的监听函数。
事件冒泡阶段(bubbling phase)。事件从目标元素冒泡到document, 依次检查经过的节点是否绑定了事件监听函数,如果有则执行。
事件绑定监听函数的方式如下:

addEventListener(eventType, handler, useCapture)

事件移除监听函数的方式如下:

removeEventListener(eventType, handler, useCapture)

Example:

var btn = document.getElementById('.btn');
btn.addEventListener(‘click’, showMessage, false);
btn.removeEventListener(‘click’, showMessage, false);

参数说明:
eventType指定事件类型(不要加on)
handler是事件处理函数
useCapture是一个boolean用于指定是否在捕获阶段进行处理,一般设置为false与IE浏览器保持一致。
DOM事件模型中的事件对象常用属性:

  • type用于获取事件类型
  • target获取事件目标
  • stopPropagation()阻止事件冒泡
  • preventDefault()阻止事件默认行为
    IE事件模型中的事件对象常用属性:
  • type用于获取事件类型
  • srcElement获取事件目标
  • cancelBubble阻止事件冒泡
  • returnValue阻止事件默认行为

Javascript垃圾回收机制

** 标记清除 ** -> 最常见的垃圾回收方式
这是JavaScript最常见的垃圾回收方式,当变量进入执行环境的时候,比如函数中声明一个变量,垃圾回收器将其标记为“进入环境”,当变量离开环境的时候(函数执行结束)将其标记为“离开环境”。垃圾回收器会在运行的时候给存储在内存中的所有变量加上标记,然后去掉环境中的变量以及被环境中变量所引用的变量(闭包),在这些完成之后仍存在标记的就是要删除的变量了
** 引用计数 ** -> 会出现因为循环引用而出现的无法回收而导致的内存泄漏
参考: http://www.jianshu.com/p/80ed3805edc3

创建对象的几种方式

  • ** 使用{}
var bar = {
  color: "blue"
};
  • ** 使用new关键字 **
var bar = new Object();
bar.color = "blue";
  • ** 使用构造函数 **
var Fun = function (color) {
  this.color = color;
}
var bar = new Fun();

面向对象与继承的几种方式

** 原型编程泛型基本规则 **

  • 所有的数据都是对象
  • 要得到一个对象,不是通过 实例化类,而是找到一个对象作为原型并克隆它
  • 对象会记住它的原型
  • 如果对象本身无法响应某个请求,它会把这个请求委托给它自己的原型
    ** 实现方式 **
    http://www.jb51.net/article/81766.htm

解决跨域问题

Jsonp
Cors
Document.domain
Document.name
HTML5 postMessage

AJAX

创建过程
(1)创建XMLHttpRequest对象,也就是创建一个异步调用对象.
(2)创建一个新的HTTP请求,并指定该HTTP请求的方法、URL及验证信息.
(3)设置响应HTTP请求状态变化的函数.
(4)发送HTTP请求.
(5)获取异步调用返回的数据.(6)使用JavaScript和DOM实现局部刷新.
参考:https://segmentfault.com/a/1190000004322487

说说你对作用域链的理解

作用域链的作用是保证执行环境里有权访问的变量和函数是有序的,作用域链的变量只能向上访问,变量访问到window对象即被终止,作用域链向下访问变量是不被允许的。

闭包

实现延迟打印1-5

for (var i = 0; i < 5; i++) {
  (function (i) {
    setTimeout(function () {
      console.log(i)
    }, i * 1000)
  })(i);
}
for (var i = 0; i < 5; i++) {
  setTimeout(function (i) {
    return function() { console.log(i); };
  }(i), i * 1000)
}
[1,2,3,4,5,6].forEach(i => {
    setTimeout(function () {
      console.log(i)
    }, i * 1000);
});

严格模式 (use strict)

  • 非严格模式下有些错误会在运行时被悄悄地忽略掉,而在严格模式下,这些错误会被抛出来,从而方便debug,通常来说,这是一种很好的实践。
  • 防止给未声明的变量赋值
  • 非严格模式下如果引用的this是null或者undefined的话会自动将this指向global对象,这种情况会造成一些比较难发现且头疼的bug。而在严格模式下,则会抛出错误。
  • 防止在一个对象内重复定义属性。
  • 安全使用eval()
  • delete操作符

如何判读一个变量是个整数 (实现 isInteger(x))

在ES6中,Number.isInteger() 可以用来判断是否为整数。
在ES6之前的版本中这个问题则比较复杂,因为numberic的值通常是作为一个浮点数来存储的,只能用比较取巧的方式来实现这样的判断逻辑。

function isInteger(x) { return (x^0) === x; }
function isInteger(x) { return Math.round(x) === x; }
function isInteger(x) { 
  return  (typeof x === "number") && (x % 1 === 0;;
}

此外也可以使用parseInt来实现

function isInteger(x) { return parseInt(x, 10) === x;}

数组操作 (split, reverse, push, slice, concat)

What will the code below output to the console and why?

var arr1 = "john".split('');
var arr2 = arr1.reverse();
var arr3 = "jones".split('');
arr2.push(arr3);
console.log("array 1: length=" + arr1.length + " last=" + arr1.slice(-1));
console.log("array 2: length=" + arr2.length + " last=" + arr2.slice(-1));

JavaScript 设计模式


Angular


双向绑定如何实现

脏检查机制

React


虚拟DOM 实现机制

JQuery


Jquery 绑定事件的方法

target.on("click");
target.click(function() {
});
target.live("click", function () {
});
target.bind("click", function () {
});

#### Jquery的链式操作如何实现

# 正则表达式
****
#### 语法
> https://msdn.microsoft.com/zh-cn/library/ae5bf541(VS.80).aspx

#### 验证身份证
> ```
var reg=/^[1-9]{1}[0-9]{14}$|^[1-9]{1}[0-9]{16}([0-9]|[xX])$/;

面试题


更多面试题: https://www.toptal.com/javascript/interview-questions

鼠标点击页面中的任意标签,alert标签名称 (兼容性)

<script type="text/javascript" >
document.onClick() = function(event) {
    var e = event || window.event;
    var src = event.target || event.srcElement;
    alert(src.tagName.toLowercase());
}
</script>

异步加载js方案,不少于两种

** 同步加载 **

就是我们平时使用的最多的方式,在页面中使用script标签
这种模式也叫阻塞模式,会阻止浏览器的后续处理,停止后续的解析,只有当加载完成,才能进行下一步的操作,所以默认同步执行才是比较安全的。但是这种方式会造成页面的阻塞,所以一般建议把<script>标签放在<body>结尾,这样尽可能减少页面阻塞。

** 异步加载 **

异步加载 又叫非阻塞加载,浏览器在下载执行js的同时,还会继续进行后续页面的处理。主要有以下几中方式:

  • ** Script Dom Element **
(function() {
    var scriptEle = document.createElement("script");
    scriptEle.type = "text/javascript";
    scriptEle.async = true;
    scriptEle.src="http://xxx/jquery.js";
    var x = document.getElementByTagName("head")[0];
    x.insertBefore(scriptEle, x.firstChild);
})();

google的使用方式

(function(){;
    var ga = document.createElement('script'); 
    ga.type = 'text/javascript'; 
    ga.async = true; 
    ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js'; 
    var s = document.getElementsByTagName('script')[0]; 
    s.parentNode.insertBefore(ga, s); 
})();

缺点: 执行完成之前会阻止onLoad事件的触发,而现在很多页面的代码都会在onLoad的时候做一些额外的渲染动作,所以还是会阻塞部分页面的初始化的处理。

  • ** onload时的异步加载 **
;(function () {
  if (window.attachEvent) {
    window.attachEvent('load', asyncLoad)
  } else {
    window.addEventListener('load', asyncLoad)
  }
  var asyncLoad = function () {
    var scriptEle = document.createElement('script')
    scriptEle.type = 'text/javascript'
    scriptEle.async = true
    scriptEle.src = 'http://xxx/jquery.js'
    var x = document.getElementByTagName('head')[0]
    x.insertBefore(scriptEle, x.firstChild)
  }
})()

注:DOMContentLoaded与load区别
前者是在document 已经解析完成,页面中的dom元素可用,但是页面中的图片,视频,音频等资源还未加载完,作用同jquery的ready. 后者的区别在于页面中所有资源包括js都加载完成。

  • ** 其他方法**
    ** XHR Injection **
    通过XMLHttpRequest来获取javascript,然后创建一个script元素插入到DOM结构中。ajax请求成功后设置script.text为请求成功后返回的responseText
var getXmlHttp = function () {
  var obj
  if (window.XMLHttpRequest)
    obj = new XMLHttpRequest()
  else
    obj = new ActiveXObject('Microsoft.XMLHTTP')
  return obj
}
var xhr = getXmlHttp()
xhr.open('GET', 'http://xxx.com/jquery.js', true)
xhr.send()
xhr.onReadyStateChange = function () {
  if (xhr.readyState == 4 && xhr.status == 200) {
    var script = document.createElement('script')
    script.text = xhr.response.text
    document.getElementsByTagName('head')[0].appendChild(script)
  }
}

** XHR Eval **
与XHR Injection对responseText的执行方式不同,直接把responseText放在eval()函数里面执行。
** Script In IFrame **
在父窗口插入一个iframe元素,然后再iframe中执行加载JS的操作。

var insertJS = function(){alert(2)};
    var iframe = document.createElement("iframe");
    document.body.appendChild(iframe);
    var doc = iframe.contentWindow.document;//获取iframe中的window要用contentWindow属性。
    doc.open();
    doc.write("<script>var insertJS = function(){};<\/script><body onload='insertJS()'></body>");
    doc.close();
  • ** HTML5新属性:async和defer属性 **
    ** defer属性 **:IE4.0就出现。defer属声明脚本中将不会有document.write和dom修改。浏览器会并行下载其他有defer属性的script。而不会阻塞页面后续处理。注:所有的defer脚本必须保证按顺序执行的。
<script type="text/javascript" defer></script>

** async属性 **:HTML5新属性。脚本将在下载后尽快执行,作用同defer,但是不能保证脚本按顺序执行。他们将在onload事件之前完成。

<script type="text/javascript" defer></script>

Firefox 3.6、Opera 10.5、IE 9和最新的Chrome和Safari都支持async属性。可以同时使用async和defer,这样IE 4之后的所有IE都支持异步加载。
没有async属性,script将立即获取(下载)并执行,期间阻塞了浏览器的后续处理。如果有async属性,那么script将被异步下载并执行,同时浏览器继续后续的处理。


** 总结 **: 对于支持HTML5的浏览器,实现JS的异步加载只需要在script元素中加上async属性,为了兼容老版本的IE还需加上defer属性;对于不支持HTML5的浏览器(IE可以用defer实现),可以采用以上几种方法实现。原理基本上都是向DOM中写入script或者通过eval函数执行JS代码,你可以把它放在匿名函数中执行,也可以在onload中执行,也可以通过XHR注入实现,也可以创建一个iframe元素,然后在iframe中执行插入JS代码。

设计一种方案,确保页面中所有js加载完全

function loadScript (url, callback) {
var script = document.createElement('script')
script.type = 'text/javascript'
if (script.readyState) {
script.onreadystatechange = function () {
if (script.readyState == 'loaded' || script.readyState == 'complete') {
script.onreadystatechange = null
callback()
}
}
} else {
script.onload = function () {
callback()
}
}
script.src = url
document.getElementsByName('head')[0].appendChild(script)
}

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

推荐阅读更多精彩内容