2019前端基础面试题

1、css实现图片自适应宽高

主要使用padding-bottom,该属性是基于父元素宽度百分比的;

<div style="width:100px;height:100px;">
 <img src="" style="width:100%;padding:100%" />
</div>

2、什么是BFC?

BFC 全称为 块级格式化上下文 (Block Formatting Context)

BFC 特性(功能)

  1. 使 BFC 内部浮动元素不会到处乱跑;
  2. 和浮动元素产生边界。

一个块格式化上下文由以下之一创建:

  • 根元素或其它包含它的元素
  • 浮动元素 (元素的 float 不是 none)
  • 绝对定位元素 (元素具有 position 为 absolute 或 fixed)
  • 内联块 (元素具有 display: inline-block)
  • 表格单元格 (元素具有 display: table-cell,HTML表格单元格默认属性)
  • 表格标题 (元素具有 display: table-caption, HTML表格标题默认属性)
  • 具有overflow 且值不是 visible 的块元素,
  • display: flow-root
  • column-span: all 应当总是会创建一个新的格式化上下文,即便具有 column-span: all 的元素并不被包裹在一个多列容器中。
  • 一个块格式化上下文包括创建它的元素内部所有内容,除了被包含于创建新的块级格式化上下文的后代元素内的元素。

https://www.jianshu.com/p/0d713b32cd0d

3、项目里面的前端鉴权是怎么实现的?

1、session-cookie

将登录的用户名和密码发送给后端,后端生成session,然后保存session生成唯一标识字符串,在响应头种下这个唯一标识字符串,返回给前端,前端将唯一标识字符串保存到cookie中,下次再发送http请求时带上该域名下的cookie信息,服务端解析请求头cookie中的唯一标识,然后根据唯一标识查找保存该客户端的session,并判断是否合法。

2、token

客户端使用用户名和密码登录,服务端接受请求,验证用户名和密码,验证成功后,签发一个token,发送给客户端,客户端收到token后保存起来,以后每次请求都带上签发的token

3、OAuth(开放授权)

支付宝、微信、QQ登录

4、vue里面的虚拟dom是怎么回事?

因为javascript的dom操作是很影响性能的,所以要尽量减少js和dom的交互,虚拟dom就是为了解决这种交互而设计的,虚拟dom通过树的形式保存dom信息,当检测到数据更新,需要更新dom,先将js中需要修改的节点全部修改完成,最终将生成的虚拟dom更新到视图中去。代价是需要在内存中保存一份可供维护的数据信息。

5、vue双向绑定讲一讲

vue实现双向数据绑定主要采用数据劫持和发布订阅结合的方式。

数据劫持就是利用Object.defineProperty()这个方法重新定义了对象获取属性值(get)和设置属性值(set)的操作实现的。

发布订阅需要Observer监听器(用来监听属性的变化通知订阅者)、Watcher订阅者(收到属性变化,然后更新视图)、Compile解析器(解析指令、初始化模板、绑定订阅者)

代码演示:

var obj = {}
var name;
//第一个参数:定义属性的对象。//第二个参数:要定义或修改的属性的名称。//第三个参数:将被定义或修改的属性描述符。
Object.defineProperty(obj, "data", {
 //获取值
 get: function () {
 return name;
 },
 //设置值
 set: function (val) {
 name = val;
 console.log(val)
 }
})
6、手写函数防抖和函数节流
函数防抖的应用场景

连续的事件,只需触发一次回调的场景有:

  • 搜索框搜索输入。只需用户最后一次输入完,再发送请求
  • 手机号、邮箱验证输入检测
  • 窗口大小Resize。只需窗口调整完成后,计算窗口大小。防止重复渲染
函数节流的应用场景

间隔一段时间执行一次回调的场景有:

  • 滚动加载,加载更多或滚到底部监听
  • 谷歌搜索框,搜索联想功能
  • 高频点击提交,表单重复提交
// 函数防抖
const _.debouce = (func, wait) => {
 let timer;
 return () => {
     cleatTimeout(timer)
      timer = setTimeout(func,wait)
   }
}
​
// 函数节流
const _.throttle = (func, wait) => {
   let timer;
   return () => {
     if(timer){
       return;
     }

 timer = setTimeout(() => {
   func();
   timer = null;
  },wait)
 }
}
7、图片懒加载实现原理

1、设置图片src为同一张默认托地图,同时自定义一个data-src属性来存储图片的真实地址

2、页面初始化显示的时候或者浏览器发生滚动的时候判断图片是否在视野中

3、当图片在视野中,通过js自动改变该区域图片的src属性为真实图片地址

8、跨域有哪些处理方式

1、通过jsonp跨域

2、跨域资源共享(CORS)

3、nodejs中间件代理跨域

4、ngnix反向代理中设置proxy_cookie_domain

9、说说对闭包的认识

一句话解释:能够读取其他函数内部变量的函数。函数作为参数传递和函数作为返回值而已形成闭包。

在js中变量的作用域属于函数作用域,在函数执行完成后。作用域就会被清理,内存也会随之被回收,但是由于闭包函数是建立在函数内部的子函数,由于其可以访问上级作用域,即使上级函数执行完,作用域也不会随之销毁,这时的子函数便拥有了访问上级函数作用域变量的权限,即使上级函数执行完后作用域内的值也不会被销毁。

10、数组常用方法有哪些
改变数组方法

1、splice() 添加/删除数组元素

let a = [1, 2, 3, 4, 5, 6, 7]; 
let item = a.splice(0, 3); // [1,2,3]
console.log(a); // [4,5,6,7]
// 从数组下标0开始,删除3个元素
let item1 = a.splice(0,3,'添加'); // [4,5,6]
console.log(a); // ['添加',7]
// 从数组下标0开始,删除3个元素,并添加元素'添加'

2、sort() 数组排序

  var array = [10, 1, 3, 4,20,4,25,8]
    // 升序 a-b < 0 a将排到b的前面,按照a的大小来排序
    array,sort(function(a, b){
     return a-b
    })
    console.log(array) // [1,3,4,4,8,10,20,25];
    // 降序
    array.sort(function(a,b){
     return b-a
    })
    console.log(array) // [25,20,10,8,4,4,3,1];

3、pop() 删除数组中最后一个元素

4、push() 向数组的末尾添加一个元素

5、shift() 删除数组的第一个元素

6、unshift() 向数组开头添加元素

7、reverse() 翻转数组

8、ES6: copyWithin() 指定位置的成员复制到其他位置

9、ES6

 // 数组求和
    let sum = [0, 1, 2, 3].reduce(
    function (a, b) { 
      return a + b; 
    }, 0);
 // 6
// 将二维数组转化为一维 将数组元素展开
let flattened = [[0, 1], [2, 3], [4, 5]].reduce( (a, b) => a.concat(b), [] ); 
// [0, 1, 2, 3, 4, 5]

10、fill() 填充数组

不改变数组的方法

1、join() 数组转字符串

2、concat() 合并两个或多个数组

3、ES6扩展运算符…合并数组

4、indexOf() 查找数组是否存在某个元素,返回下标

5、ES7 includes() 查找数组是否包含某个元素 返回布尔

6、slice() 浅拷贝数组

let a = [{name: 'zhangsan'}, {name: 'lisi'}]
let b = a.slice(0,1);
console.log(b, a);
// [{name: 'zhangsan'}] [{name: 'zhangsan'}, {name: 'lisi'}]
a[0].name = '改变数组';
console.log(b, a)
// [{"name":"改变原数组"}] [{"name":"改变原数组"}, {name: 'lisi'}]

遍历方法

1、forEach

2、every 检测数组所有元素是否符合判断条件,如果有一个元素不满足在,则整个表达式返回false,且元素不会再进行检测

3、some 检测数组中是否有满足条件的判断,如有有一个元素满足条件,则表达式返回true,剩余元素不会再执行检测

4、filter 过滤原始数组,返回新数组

5、reduce 为数组提供累加器,合并为一个值

  // 数组求和
    let sum = [0, 1, 2, 3].reduce(function (a, b) { return a + b; }, 0); // 6
    // 将二维数组转化为一维 将数组元素展开
    let flattened = [[0, 1], [2, 3], [4, 5]].reduce( (a, b) => a.concat(b), [] ); 
// [0, 1, 2, 3, 4, 5]

6、ES6 find() & findIndex() 根据条件找到数组成员 这两钟方法都可以识别NaN,弥补了indexOf的不足

 [1, 4, -5, 10,NaN].find((n) => Object.is(NaN, n));
// 返回元素NaN
 [1, 4, -5, 10].findIndex((n) => n < 0);
 // 返回索引2

7、ES6 keys() & values() & entries() 遍历键名、遍历键值、遍历键名+键值

for (let index of ['a', 'b'].keys()) { console.log(index); } // 0
 // 1
for (let elem of ['a', 'b'].values()) { console.log(elem); } // 'a'
// 'b'
for (let [index, elem] of ['a', 'b'].entries()) { console.log(index, elem); } // 0 "a"
// 1 "b"

11、怎么判断一个Object是数组
  • 使用Object.prototype.toString 来判断是否是数组
  • 使用原型链来完成判断
    function isArray(obj){
    return obj.proto === Array.prototype;
    }
  • ES6 Array.isArray()
12、继承有哪些方式
  • ES6得class继承
  • 原型继承
  • 构造继承
  • 寄生组合式继承
  • 实例继承
13、call、apply、bind之间的关系
apply和call
  • 二者都是Function对象上的方法,每个函数都能调用
  • 二者的第一个参数都是你要指定执行的上下文
  • 最终都会转换成一个一个参数的执行,故call比apply运行效率高
  • 区别是:call方法接受的是若干个参数列表,而apply接受的是一个包含多个参数的数组
var a = {
     name: 'cherry',
     fn: function (a, b) {
       console.log(a + b) 
     }
}
var b = a.fn
b.apply(a, [1, 2]) // 3
b.call(a, 4, 5, 6) // 15
b.bind(a, 1, 2)() // 3

bind 与apply、call区别

bind()是强绑定,只要传进去了context,则bind中return出来的的函数this便一直指向context,除非context是个变量;因为bind会return一个新函数,故此需要二次执行。

apply、call是显示绑定,立即执行

14、Promise

所谓promise,简单说就是一个容器,里面保存着某个未来才会结束的事件的结果。从语法上说,promise是一个对象,从它可以获取异步操作的消息。Promise提供统一的Api,各种异步操作都可以用同样的方法处理,让开发者不用再关注时序和底层的结果。Promise的状态具有不受外界影响和不可逆两个特点。

promise解决了什么问题?

Promise解决了回调地狱的问题,提高代码可读性以及解决信任度问题

promise在事件循环中的执行过程是怎样的
var promise = new Promise((resolve, reject) => {
 console.log('我是promise任务')
 resolve('resolved')
})
promise.then(res => {
 console.log(res)
})
console.log('我是同步任务')
setTimeout(() => {
 console.log('我是延时任务')
})
// 上面代码的执行顺序是: 我是promise任务、我是同步任务、resolved、我是延时任务。

Promise新建后立即执行,立即resolve的Promise对象,是在本轮”事件循环”(event loop)的结束时,而不是在下一轮”事件循环”的开始时;setTimeout在下一轮”事件循环”开始时执行。

15、Vue-router的原理

vue-router是通过hash和History interface两种方式实现前端路由,更新视图但不重新请求页面,在vue-router中,mode参数决定采用哪一种方式来实现路由跳转。

mode参数:

  • 默认hash
  • history(注:如果浏览器不支持history新特性,则采用hash方式)
  • 如果不在浏览器环境中则使用abstract(node环境下)

当你选择了mode类型之后,程序会根据你选择的mode类型创建不同的history对象(HashHistory或HTML5History或AbstractHistory)

HashHistory替换路由有两种方式:HashHistory.push() 和 HashHistory.replace()

HashHistory.push() 将新路由添加到浏览器访问历史的栈项

  1. $router.push() // 调用方法
  2. HashHistory.push() // 根据Hash模式调用,设置hash并添加到浏览器历史记录(添加到栈项)(window.location.hash = xxx)
  3. History.transitonTo() // 监测更新,更新则调用History.updateRoute()
  4. History.updateRoute() // 更新路由
  5. {app._route = route} // 替换当前app路由
  6. vm.render() // 更新视图

HashHistory.replace

replace() 方法与push()方法不同之处在于,它并不是将新路由添加到浏览器访问历史的栈项,而是替换当前路由

HTML5History替换路由两种方式:pushState()和replaceState()

window.history.pushState(stateObject, title, URL)

window.history.replaceState(stateObject, title, URL)

异同:

  • pushState设置新的URL可以是与当前URL同源的任意URL;而hash只能修改#后面的部分,故只可以设置与当前同文档的URL
  • pushState通过stateObject可以添加任意类型的数据到记录中,而hash只可添加短字符串
  • pushState可以额外设置title属性供后续使用
  • history模式会将url修改的和正常请求后端的url一样,如果后端没有配置对应的(/user/id)路由处理,则会返回404错误
16、左边定宽,右边自适应方案:float + margin , float + calc
// 方案一
.left{
 width: 120px;
 float: left;
}
.right{
 margin-left: 120px;
}
// 方案二
.left{
 width: 120px;
 float: left;
}
.right{
 width: calc(100% - 120px);
 float: left;
}
17、左右两边定宽,中间自适应:float,float + calc,flex,圣杯布局(设置BFC,margin负值法)
.wrap{
 width: 100%;
 height:200px;
}
.wrap > div{
 height: 100%;
}
// 方案1
.left{
 width: 120px;
 float: left;
}
.right{
 width: 120px;
 float: right;
}
.center{
 margin: 0 120px;
}
// 方案2
.left{
 width: 120px;
 float: left;
}
.right{
 float: right;
 width: 120px;
}
.center{
 width: calc(100% -240px);
 margin-left: 120px;
}
// 方案3
.wrap{
 display: flex;
}
.left{
 width: 120px;
}
.right{
 width: 120px;
}
.center{
 flex: 1;
}
18、Vue和React的区别
  • 相同点:都支持ssr,都有Vdom,组件化开发,实现webComponents规范,数据驱动等
  • 不同点:Vue是双向数据流,react是单项数据流。vue的vdom是追踪每个组件的依赖关系,不会渲染整个组件树,react每当状态改变时,全部子组件都会重新render
19、XSS和CSRF
  • XSS:跨站脚本攻击,代码注入的一种,常见方式是将恶意代码注入合法代码里隐藏起来,再诱发恶意代码。防范:所有的用户输入做过滤和转译
  • CSRF:跨站请求伪造,是在用户已登录的web应用程序上执行非本意的操作攻击。防范:验证码,额外验证机制(token)
20、computed和methods的区别
  1. computed是属性调用,methods是函数调用
  2. computed带有缓存功能,methods没有
21、$nextTick

定义:在下次DOM更新结束之后执行延迟回调。在修改数据之后立即使用这个方法,获取更新之后的DOM

应用场景:需要在视图更新之后,基于新的视图进行操作。(点击按钮使输入框展示出来,同时获取焦点)

22、如何实现左边两栏一定比例,左栏高度随右栏高度自适应?

关键点:margin-bottom: -10000px; padding-bottom: 10000px;

.container {
 overflow: hidden;
 width: 400px;
}
.container .left,
.container .right {
 float: left;
 margin-bottom: -10000px;
 padding-bottom: 10000px;
}
.container .left {
 width: 20%;
 background: pink;
}
.container .right {
 width: 80%;
 background: deeppink;
}
23、什么是深拷贝和浅拷贝

对于基本数据类型来说,直接创建一个变量的副本,即为深拷贝

对引用类型来说,浅拷贝指向某个对象的指针,而不复制对象,新旧对象共用一块内存;

深拷贝是复制一个一模一样的对象,不共享内存,修改新对象,旧对象保持不变;

深拷贝实现方式:

  • JSON.Stringify和JSON.parse
  • 递归实现
  • Jquery的extend()

深拷贝的两种实现方式:

// 方式一
let o1 = {
 a:{
 b: 1
 }
}
let o2 = JSON.parse(JSON.stringfy(o1))
​
// 方式二
function deepCopy(s){
 const d = Array.isArray(s) ? [] : {}
 for(let key in s){
 if(typeof s[key] == 'Object'){
 d[key] = deepCopy(s[key])
 }else{
 d[key] = s[key]
 }
 }
 return d

}
24、js中数据类型的判断

1、typeof

数字 Number、布尔值Boolean、字符串String、函数Function、对象Object、Undefined可以被解释,但数组和null判断的不准确

2、instanceof

引用数据类型Array、Function、Object可以被准确判断,Number、Boolean、String不能准确判断

3、constructor

本可以判断,然而一旦将函数的prototype重新赋值,则会轻易更改constructor

function Fn(){};

Fn.prototype=new Array();

var f=new Fn();

console.log(f.constructor===Fn);    // false
console.log(f.constructor===Array); // true 

4、Object.prototype.toString.call()

25、数组去重
  1. ES5方式
function unique(arr){
     const temp = []
     arr.forEach(e => {
     if(temp.indexOf(e) == -1){
     temp.push(e)
     }
     })
     return temp
    }
  1. ES6方式
function unique(arr){
     return Array.from(new Set(arr))
}
26、获取地址栏参数信息
function getQueryString(name){
 let reg = new RegExp('(^|&)' + name + ='([^&]*)(&|$)','i')
 let r = window.location.search.substr(1).macth(reg)
 if(r !== null){
 return unescape(r[2])
 }
 return ''
}
27、不同情况调用,this分别指向如何
  1. 由new调用则绑定到新创建的对象
  2. 由call、apply或bind调用,绑定到指定的对象
  3. 由上下文调用,绑定到上下文对象
  4. 不符合上述规则,指向全局对象,浏览器环境下是window,严格模式下是undefiend
  5. 如果是ES6的箭头函数,this指向它被创建的上下文
28、http1.1跟http1.0比新特性

1.1版本最大的变化就是引入了持久化连接,即TCP连接默认不关闭,可以被多个请求复用,不用声明Connection:keep-alive

1.1版本还引入了管道机制,即在同一个TCP连接里面,客户端可以同时发送多个请求。

还增加了许多动词方法:putpacthoptionsdelete

另外还增加了host字段,用来指定服务器域名

http协议入门(阮一峰)

29、http状态码

1、2xx 成功

204:请求发送成功,但无任何响应内容

2、3xx 重定向

301:永久性重定向 所请求的资源/网页已经被分配了新的URL,搜索引擎在抓取新内容的同时也将旧网址交换为重定向后的新网址

302:临时重定向 临时从旧地址A跳转到B,搜索引擎会抓取新的内容而保存旧的地址

3、4xx 客户端错误

400:请求报文中存在语法错误

401:未授权,需要进行身份验证,如果是需要登录的网页,服务端可能返回此响应

403:服务器拒绝请求

404:服务器上无法找到该资源

4、5xx 服务端错误

500:服务器内部错误

501:服务器无法识别请求方法

502:网关错误

503:服务不可用(超载或停机维护)

504:网关超时

30、浏览器进程下5大线程

GUI渲染线程

js引擎线程

事件触发线程

定时器触发线程

异步http请求线程

31、捕获js运行时报错和跨域资源加载报错

js运行时报错

1、try catch 捕获错误

2、window.onerror

资源加载报错

1、Object.onerror

var  img  =  document.getElementById('img')  
img.onerror  =  function(){  
// 捕获错误 
}

2、window.performance.getEntries()

浏览器获取网页时,会对网页中每一个对象(脚本文件、样式表、图片文件等)发出一个Http请求,而通过window.performance.getEntries方法,则可以以数组形式,返回这些请求的时间统计信息。

function  ()  {  
// 浏览器不支持,就算了!  
if  (!window.performance  &&  !window.performance.getEntries)  { 
 return  false;  
}  
var  result  =  [];  // 获取当前页面所有请求对应的PerformanceResourceTiming对象进行分析  window.performance.getEntries().forEach((item)  =>  { 
 result.push({ 
    'url':  item.name,  
    'entryType':  item.entryType, 
    'type':  item.initiatorType,  
    'duration(ms)':  item.duration 
 });  
});  
// 控制台输出统计结果  
console.table(result);  
})();  

3、捕获Error事件

window.addEventListener("error",function(ev){ 
console.log('捕获',ev)// 捕获错误 
},true);
跨域资源加载报错

跨域js文件获取是有限制的,如果想获取其他域下的js错误需要在script标签里添加crossorigin属性,然后服务器端要设置header(‘Access-Control-Allow-Origin: *’),或者 指定域名。

<script  type="text/javascript"  src="http://localhost:3000/test/script.js"  crossorigin="anonymous"></script>>
32、target和currentTarget区别
  • e.target指向触发事件监听的对象

  • e.currentTarget指向添加监听事件的对象

33、flex: 1 集成的是哪三个属性

flex-grow:定义了元素放大比例,默认值0

flex-shrink:定义了元素缩放比例,默认值1

flex-basis:在分配多余空间之前,项目占据主轴的空间。浏览器根据这个属性,计算主轴是否有多余空间,默认值是auto

34、js获取dom元素的八种方法
  • getElementById

  • getElementByName

  • getElementByTagName

  • getElementByClassName

  • 获取html的方法 document.documentElement

  • 获取body的方法 document.body

  • 通过选择器获取一个元素 querySelector

  • 通过选择器获取一组元素 querySelectorAll

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

推荐阅读更多精彩内容