JS DOM相关

盒子模型-DOM操作相关

DOM,文档对象模型,提供一系列的属性和方法让我们能够通过JS操作页面中的元素

获取属性/方法API
获取元素方法
document.getElementById(id) // 根据id获取元素
[context].getElementsByTagName(tagname) // 根据标签名获取元素
[context].getElementsByClassName(classname) // 根据类名获取元素
document.getElementsByName(name)    // 根据name属性获取元素(IE中只对表单元素的name有用)
[context].querySelector(selector) // 通过选择器获取第一个元素
[context].querySelectorAll(selector) // 通过选择器获取所有元素

注:
其中[context]开头的在IE6-8中不兼容

获取元素属性
document    // 整个文档对象
document.documentElement    // document对象的根节点
document.head   // head元素
document.body   // body元素
childNodes  // 所有子节点(不只是元素节点)
children    // 所有元素子节点
// IE6-8中会把注释节点当做元素节点获取
parentNode  // 元素父节点
firstNode/firstElementChild // 第一个子节点/第一个元素子节点
lastChild/lastElementChild // 最后一个子节点/最后一个元素子节点
previousSibling/previousElementSibling // 上一个子节点/上一个元素子节点
nextSibling/nextElementSibling   // 下一个子节点/下一个元素子节点

注:
所有带Element的在IE6-8中不兼容
注:
childNodes属性可以获取所有子节点,但是除了IE6-8以外的版本里,都会把空白区也当成节点,即文本节点,比如:
html中:

<ul id="ul1">
    <li></li>
    <li></li>
    <li></li>
</ul>

js中:

var oUl = document.getElementById("ul1")
alert(oUl.childNodes.length);

会发现弹窗结果为7而不是3,原因就是childNodes会把空白处也当成节点(文本节点),因此三个标签前后都有一个文本节点,合起来就7个。
为了解决这个问题可以使用nodeTypechildNode下的属性),当是文本节点时值为3,元素节点(即标签)时值为1,可以用其来判断是否为标签
还有个children属性,和childNode同级,其识别子节点只识别标签,即childNodenodeType的结合,比如对于上面的html,使用如下js语句结果返回就是3:

var oUl = document.getElementById("ul1")
alert(oUl.children.length);

注:
求子节点数量只包括其下第一层的节点

元素操作
document.createElement(tagname) // 创建一个元素
document.createTextNode(text)   // 创建一个文本节点
[element].innerHTML = xxx       // 添加/修改节点内部HTML代码
[element].innerText = xxx       // 添加/修改节点内部文本信息

[parent].appendChild(newElement)            // 往parent节点里的最后添加子节点
[parent].insertBefore(newElement, element)  // 往parent节点里的指定位置添加节点

[element].cloneNode(true/false) // 拷贝节点(参数代表深/浅拷贝)
[parent].removeChild(element)   // 删除节点

简单举例:

var div = document.createElement('div');
div.innerHTML = "动态创建的div标签";
document.body.appendChild(div)

var node = document.getElementById("node");
var div = document.createElement('div');
odiv.insertBefore(div, node;
// 将新创建的div节点插入到node节点前

注:
这里appendChild添加的是一个新建的节点,所以会认为该方法的功能是追加节点,但其实际上的功能是把原来的节点删了,再添加到新的地方上去,比如用appendChild操作一个本身存在的节点,会发现原来的节点没了,而出现在了新的位置上,所以准确地说该方法是移动节点

属性操作
[element].attributes    // 元素所有属性
[element].xxx = xxx     // 修改节点属性
delete [element].xxx    // 删除节点属性

[element].setAttribute("xxx", xxx)  // 添加/修改节点属性
[element].getAttribute("xxx")   // 获取节点属性值
[element].removeAttribute("xxx")    // 删除节点属性
其他属性
tBodies/rows/cells

表格特有的属性,分别对应表格<table><tbody>/<tr>/<td>,比如平常要获取表格的某一格内容:

document.getElementsByTagName('tbody')[i].getElementsByTagName('tr')[i].getElementsByTagName('td')[i]

现在就可以改成:

document.getElementsByTagName("table").tBodies[i].rows[i].cells[i]

因为一个表格可以有好几个<tbody>、<tr>和<td>,所以上面那三个都是数组,还有像tHeadtFoot也可以用,而这两个因为一个表格只有一个,所以其是元素而不是一个数组

class操作
[element].classList // 获取节点类,以list形式
[element].className // 获取节点类,以字符串形式
[element].classList.add("xxx")  // 给节点添加类
[element].classList.contains("xxx")  // 查看节点是否有某个类
[element].classList.remove("xxx")  // 删除节点的某个类
样式操作
[element].style.xxx // 获取当前元素的行内样式(只能获取行内的,如:CSS文件中定义的无法获取)

[element].style.xxx = xxx;  // 修改和设置行内样式(只能修改行内的,如:CSS文件中定义的无法修改)
[element].className = xxx;  // 设置样式类

[element].style.xxx = "";   // 删除样式

window.getComputedStyle(element, 伪类)  // 获取元素计算后的最终样式(没有设置的则会按默认来)
[element].currentStyle  // IE6-8没有getComputedStyle方法,此时需要使用该属性获取所有样式

盒子模型-js属性

基于一些属性和方法,获取当前元素的样式信息

属性
  • client
clientWidth/clientHeight
clientTop/clientLeft
  • offset
offsetWidth/offsetHeight
offsetTop/offsetLeft
offsetParent
  • scroll
scrollWidth/scrollHeight
scrollTop/scrollLeft
client相关
clientWidth/clientHeight

获取盒子可视区域宽高(content+padding)

  • 内容溢出不影响该属性值大小
  • 获取的结果没有单位(其他盒子模型属性也是)
  • 获取的结果是四舍五入的整数(其他盒子模型属性也是)

举例:

let box = document.getElementById("xxx");
box.offsetWidth

获取当前屏幕可视区域宽高:

let w = document.documentElement.clientWidth || document.body.clientWidth;
let h = document.documentElement.clientHeight || document.body.clientHeight;
clientLeft/clientTop

获取盒子左边框和上边框的大小

scroll相关
scrollWidth/scrollHeight
  • 内容没溢出时,结果和client相关的一样
  • 内容溢出时,结果约等于真实的宽高(padding + content)
  • 不同浏览器获取的结果不一定相同
  • 设置overflow属性对最后结果会产生影响

举例:

let box = document.getElementById("xxx");
box.scrollWidth

document.documentElement.scrollHeight || document.body.scrollHeight
// 整个页面真实高度
scrollLeft/scrollTop

滚动条卷去的宽高
边界值:

min = 0
max = 整个高度scrollHeight - 屏幕高度clientHeight

注:
13个盒子模型属性中,只有这两个是可读写的属性,其他的都是只读属性,例如下面的代码是合法的:

box.scrollTop = 0;
offset相关
offsetWidth/offsetHeight

获取盒子本身的宽高(content+padding+border)

offsetParent

获取父参照物(未必是父元素)

  • 同一平面中,最外层元素是所有后代元素的父参照物,但脱离文档流的元素,父参照物就未必是最外层元素了
  • body的父参照物为null,举例:
document.body.offsetParent = null;
offsetTop/offsetLeft

距离其父参照物的上/左偏移(当前元素的外边框到父参照物的内边框)

获取元素相对body偏移
  • 获取父参照物的偏移和边框
  • 获取父参照物的父参照物的偏移和边框
  • ...
  • 直到父参照物为空为止
  • 将全部加到一起
function offset(ele) {
    let parent = ele.offsetParent;
    let left = ele.offsetLeft;
    let top = ele.offsetTop;
    while (parent && parent.tagName !== "BODY") {
        if(!/MSIE 8\.0/.test(navigator.userAgent)) {
            left += parent.clientLeft;
            top += parent.clientTop;
        }
        left += parent.offsetLeft;
        top += parent.offsetTop;
        parent = parent.offsetParent;
    }
    return { top, left }
}

获取CSS样式相关

获取内联样式
document.xxx.style
获取CSS标签样式
document.styleSheets[0].rules[0].style
获取动态样式
window.getComputedStyle(xxx)

能够获取当前元素所有经过浏览器计算过的样式

  • 页面中呈现的元素的所有样式都是经过浏览器计算的
  • 即使没有设置过的样式也都会自动计算
  • 在IE6-8中不兼容该方法,需要通过currentStyle属性获取
// 传入两个参数,第一个是元素,第二个是其伪类:`:after`/`:before`
// 获取的是`CSSStyleDeclaration`类的对象,包含了当前元素所有的样式信息
let style = window.getComputedStyle(element, null);
style.display   // 获取对应属性结果
style["backgroundColor"]    // 属性名中有"-"用驼峰代替

元素相关

https://developer.mozilla.org/zh-CN/docs/Web/API/Node

常见属性
nodeType

元素节点是1,文本节点是3

更多参考:https://developer.mozilla.org/zh-CN/docs/Web/API/Node/nodeType

title

鼠标悬浮提示

value

如表单里的值

textContent/innerText

元素文本内容

区别参考:https://blog.csdn.net/qq_39207948/article/details/86099905

innerHTML

内部文档

style

样式属性

activeElement

当前页面的焦点元素

事件相关

所有的事件都是以on开头命名的,在事件调用函数时可以调用多个,以分号隔开,举例:

<body onload="fun1();fun2()" onunload="fun3()">
常见事件
onload

页面资源加载完成事件,当js代码绑定操作时,不管是在html中还是通过js文件引入,必须得在标签后面出现,因为如果执行js,会因为找不到对应绑定的标签而报错。当然还有另一种办法,那就是在前面先用window.onload()来解决,其意思就是这段代码在页面加载完成时才执行,举例:

window.onload = function() {
  var element = document.getElementById("b");
  element.onclick = function() {
      alert('xyz');
    }
}
onunload

页面卸载事件,即关闭页面

onmousedown

当按下鼠标时触发

onmouseup

鼠标点下后松开时触发

onmousemove

鼠标移动时触发

onmouseover

鼠标进入时触发,鼠标移入时全选文本框举例:

<input type="text" onmouseover="this.select()">
onmouseout

和上面相反,鼠标移出时触发

onclick

鼠标单击时触发

ondbcllick

鼠标双击时触发

onfocus

当焦点在这个元素内时触发

onblur

当焦点离开这个元素时触发

onkeydown

键盘按下时触发,可以通过事件的keyCode来得知是哪个键(ASCII码),举例:

document.onkeydown = function(ev) {
    oEvent = ev || event;
    alert(oEvent.keyCode);
}

再比如用键盘上下左右控制标签移动:

var odiv = document.getElementById('a');
    document.onkeydown = function(ev) {
        oEvent = ev || event;
        switch (oEvent.keyCode) {
            case 37:
                odiv.style.left = odiv.offsetLeft - 10 + 'px';
                break;
            case 39:
                odiv.style.left = odiv.offsetLeft + 10 + 'px';
                break;
            case 38:
                odiv.style.top = odiv.offsetTop - 10 + 'px';
                break;
            case 40:
                odiv.style.top = odiv.offsetTop + 10 + 'px';
                break;    
        }
    }

然后还有一些如ctrl,其给了特殊的字符ctrlKey来代替,当然在keyCode中数值为17,还有shiftKeyaltKey,然后现在实现按ctrl+回车把输入框的东西弄到消息框里,此时要注意的是因为按回车的时候焦点是在输入框,所以事件要写在文本框里:

var odiv = document.getElementById('a');
    var otext = document.getElementById('c');
    document.onkeydown = function(ev) {
        oEvent = ev || event;
        if (oEvent.keyCode == 13 && oEvent.ctrlKey) {   //ctrl+回车
            odiv.textContent += '\n' + otext.value;
            otext.value = '';
        }
    }
onkeyup

键盘松开时触发

onsubmit/onreset

表单里点击提交和重置时触发事件

oncontextmenu

鼠标右键的上下文菜单,我们可以修改这个方法从而改变右键后的内容:

document.oncontextmenu = function(){
    alert('别点右键');
    return false;
};

所以现在可以根据这个方法写个自定义菜单:
html中:

#a {
    background: blue;
    width: 100px;
    height: 150px;
    position: absolute;
    display: none;
}
<div id="a">
    <div>选项1</div>
    <div>选项2</div>
    <div>选项3</div>
    <div>选项4</div>
</div>

js中:

window.onload = function() {
    var odiv = document.getElementById('a');
    document.oncontextmenu = function(ev) {
        oEvent = ev || event;
        odiv.style.display = 'block'; //显示菜单
        odiv.style.background = 'blue';
        odiv.style.left = oEvent.clientX + 'px'; //菜单出现在鼠标处
        odiv.style.top = oEvent.clientY + 'px';

        return false;
    };
    document.onclick = function(ev) {
        oEvent = ev || event;
        if (oEvent.clientX > parseInt(odiv.style.left) && oEvent.clientX <= parseInt(odiv.style.left) + 100 && oEvent.clientY > parseInt(odiv.style.top) && oEvent.clientY <= parseInt(odiv.style.top) + 150) {
            odiv.style.background = 'red'; //当在菜单内点击背景变色
        } else {
            odiv.style.display = 'none'; //菜单外点击菜单消失
        }

    }
}
onhashchange

地址栏地址改变

DOMSubtreeModified

dom子元素节点修改事件,举例:

document.addEventListener("DOMSubtreeModified", (e) => {console.log(e)})
DOMNodeInserted

dom子元素节点添加事件

DOMNodeRemoved

dom子元素节点删除事件
注:
上面几个的原理是通过MutationEvent监听实现的,现在的标准是使用MutationObserver来进行监听,举例:

<!DOCTYPE html>
<html>
  <head>
    <title></title>
    <style>
      .aaa {
        height: 500px;
        width: 500px;
        background: black;
      }
      .bbb {
        height: 300px;
        width: 300px;
        background: gainsboro;
      }
      .hide {
        display: none;
      }
    </style>
  </head>
  <body>
    <div class="aaa">
      <button onclick="togglebbb()">点击隐藏/显示</button>
      <div class="bbb"></div>
      <div class="ccc"></div>
    </div>
  </body>
  <script type="text/javascript">
    // 修改bbb、ccc节点属性
    function togglebbb() {
      document.querySelector(".ccc").setAttribute("test", "1");
      let node = document.querySelector(".bbb");
      if (node.classList.contains("hide")) return node.classList.remove("hide");
      node.classList.add("hide");
    }
    let node = document.querySelector(".aaa");
    // 监听aaa节点及子节点的属性变化
    let config = { attributes: true, subtree: true };
    let callback = (mutationsList, observer) => {
      Array.from(mutationsList).map(mutation => {
        console.log("节点:", mutation.target, "改变属性:", mutation.attributeName);
      });
      // 只监听一次以后关闭监听
      observer.disconnect();
    };
    // 浏览器兼容
    let MutationObserver =
      window.MutationObserver || window.WebKitMutationObserver || window.MozMutationObserver;
    // 设置回调
    let observer = new MutationObserver(callback);
    // 启动观察
    observer.observe(node, config);
  </script>
</html>

参考:
https://segmentfault.com/a/1190000019599439

动态绑定事件

一般一个标签同一个事件只能绑定一次,假如要给一个标签绑定两个onclick事件,那么结果很可能就是后一个事件把前一个给覆盖了,举例:

var obtn = document.getElementById('a');
obtn.onclick = function(){
    alert('a');
}
obtn.onclick = function(){
    alert('b');
}

结果只会提示b,所以如果想要实现函数不会被覆盖,可以一个事件有多个函数的话,那么就要用到动态绑定事件

addEventListener
element.addEventListener("click", fun, false)
// 三个参数依次为事件类型、回调函数、相关配置

举例:

element.addEventListener("事件", 函数, false)   //这里事件去掉on,比如click

上面的参数,前两个很好理解,第三个参数如果不传则按默认的来,如果传入相关配置,则需要是对象类型,部分配置参考:

once    事件只调用一次,调用完则移除事件
passive 是否为被动调用,如果设置为true,将无法使用`preventDefault()`方法,大部分事件默认都是false,但是浏览器为了提升性能,在一些事件上设置为了true,如网页缩放(ctrl+滚轮)

举例-自定义区域缩放事件,并且阻止浏览器默认缩放事件:

<style>
  .container {
    height: 500px;
    width: 500px;
    background: gainsboro;
    overflow: auto;
  }
  .content {
    height: 300px;
    width: 300px;
    color: white;
    background: black;
  }
</style>
<body>
  <div class="container">
    <div class="content">鼠标在黑色区域内按住ctrl+滚轮滑动,实现放大缩小</div>
  </div>
</body>
<script>
  const func = e => {
      if (!e.target.classList.contains("content")) return e.preventDefault();
      let content = e.target;
      let width = parseInt(content.style.width || "100%");
      let height = Math.max(parseInt(content.style.width || "100%"), 30);
      // 浏览器兼容
      if ((e.deltaY || e.detail) < 0) {
        content.style.width = `${width * 1.1}%`;
        content.style.height = `${height * 1.1}%`;
      } else {
        content.style.width = `${width / 1.1}%`;
        content.style.height = `${height / 1.1}%`;
      }
      e.preventDefault();
    }
  // mousewheel事件浏览器默认设置passive为true,为了能够阻止默认事件,需要我们手动设置为true
  document.addEventListener("DOMMouseScroll", func , {passive:false});
  document.addEventListener("mousewheel", func , {passive:false});
</script>

参考:
https://developer.mozilla.org/zh-CN/docs/Web/API/EventTarget/addEventListener
https://blog.csdn.net/csdnXiaoZou/article/details/87276026

绑定事件在IE下比较特殊,需要使用到下面的方法绑定:

obj.attachEvent('事件', 函数)   //这里事件写完整名,比如onclick
解决IE兼容性
var obtn = document.getElementById('a');
if (obtn.attachEvent) {     //IE下
    obtn.attachEvent('onclick', abc);
} else {    //FireFox/chrome下
    obtn.addEventListener('click', abc, false);
}

function abc() {
    alert('a');
}
obtn.onclick = function() {
    alert('b');
}
解除绑定

IE下:detachEvent('事件', 函数),DOM方式:removeEventListener('事件', 函数, false)

事件的绑定和解绑参考:https://blog.csdn.net/tswc_byy/article/details/82824798

事件对象相关API

监听事件时,默认会传递事件对象本身

target

事件对象的DOM元素

clientX/clientY

横纵坐标位置,举例点击时显示鼠标位置:

document.onclick = function(event) {
    alert(event.clientX + ',' + event.clientY);
}
cancelBubble

在HTML页面中默认允许事件冒泡,比如给document加个onclick事件,然后再在页面里一个标签加个onclick事件,当点击标签时,会发现标签和document事件都触发了,如下代码:

document.onclick = function() {
    alert("document's event")
}
var odiv = document.getElementById('a');
odiv.onclick = function() {
    alert("div's event")
}

结果会发现两个alert都执行了,可以发现事件冒泡就是标签执行完事件后会继续执行父节点的事件,一直往上层执行,所以这样有时候可能就会造成一些意外的结果。为了避免事件冒泡,可以给对应的标签事件里设置:

ev.cancelBubble = true;
stopPropagation

阻止事件传播

preventDefault

阻止默认行为

其他

滚动条事件监听
<!DOCTYPE html>
<html lang="en">
<head>
    <style>
        .box {
            width: 100%;
            height: 1000px;
            background: repeating-linear-gradient(45deg, blue 0px, blue 30px, white 30px, white 60px);
        }
    </style>
</head>
<body>
    <div class="box"></div>
</body>
<script>
    document.addEventListener("scroll", function(e) {
        console.log("scroll:", window.scrollY);
    });
</script>
</html>
表单上传相同数据不触发change事件解决

思路:在每次提交后清空表单即可,代码示例:

<body>
  <form class="upload">
    <input type="file" multiple onchange="change(this)" />
    <input type="reset" id="reset" style="display: none;" />
  </form>
</body>
<script>
  function change(_this) {
    for(let file of _this.files) console.log(file.name);
    document.querySelector("#reset").click();
    // 每次上传完成清空表单
  }
</script>
纯前台下载功能实现
// 方式一:通过base64下载
function downloadByBase64() {
  // 下载数据
  let data = ["1", "2", "3", "4", "5"];
  let str = data.join(",");
  str += "\n";
  // encodeURIComponent解决中文乱码
  let uri = "data:text/csv;charset=utf-8,\ufeff" + encodeURIComponent(str);
  // 创建a标签
  let link = document.createElement("a");
  link.style.display = "none";
  link.href = uri;
  // 默认文件名
  link.download = "test.csv";
  document.body.appendChild(link);
  // 触发点击
  link.click();
  document.body.removeChild(link);
}

// 方式二:通过二进制流下载
function downloadByBin() {
  let data = ["1", "2", "3", "4", "5"];
  let str = data.join(",");
  str += "\n";
  let link = document.createElement("a");
  link.download = "test.csv";
  link.style.display = "none";
  let blob = new Blob([str]);
  link.href = URL.createObjectURL(blob);
  document.body.appendChild(link);
  link.click();
  document.body.removeChild(link);
}
浏览器下匿名函数绑定事件解绑(基于chrome提供的api)

1.通过getEventListeners获取对应的dom节点上绑定的所有事件
2.获取事件上对应的listener
3.利用和绑定对应的方式并传入listener进行解绑
举例(chrome命令行下,放在js文件中会报错):

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