Change And Its Detection In JavaScript Frameworks(JavaScript 框架中的变更及其检测)

翻译说明: 意译, 中英对照, 重新分段排版以便于阅读。
原文: Change And Its Detection In JavaScript Frameworks

Change And Its Detection In JavaScript Frameworks

In 2015 there is no shortage of options when it comes to JavaScript frameworks.
2015年时, 谈论到 JavaScript 框架, 并不缺乏可选项。

Between Angular, Ember, React, Backbone, and their numerous competitors, there's plenty to choose from.
有很多可以选择的, Angular, Ember, React, Backbone 以及 它们的各种竞争者。

One can compare these frameworks in various ways, but I think one of the most interesting differences between them is the way they manage state. In particular, it is useful to think about what these frameworks do when state changes over time. What tools do they give you to reflect that change in your user interface?
你可以用很多方式比较这些框架, 但是我认为它们之间最有意思的不同之一是它们管理 状态 的方式。特别是,考虑一下, 当状态随时间改变, 这些框架做了什么, 非常有用。 它们提供给你什么工具来在用户界面反映变更?

Managing the synchronization of app state and the user interface has long been a major source of complexity in UI development, and by now we have several different approaches to dealing with it.
在 UI 的开发中, 管理 应用 状态 和用户界面之间的同步 是复杂度的一个主要来源, 现在, 我们有几种不同的方式来处理。

This article explores a few of them: Ember's data binding, Angular's dirty checking, React's virtual DOM, and its relationship to immutable data structures.
本文探索了其中几种: Ember 的 数据绑定, Angualr 的脏检查, React 的 虚拟DOM 及其和不可变数据结构的关系。

Projecting Data 投影数据

The basic task we're talking about is taking the internal state of the program and mapping it to something visible on the screen. You take an assortment of objects, arrays, strings, and numbers and end up with a tree of text paragraphs, forms, links, buttons, and images. On the web, the former is usually represented as JavaScript data structures, and the latter as the Document Object Model
我们讨论的基本任务是获取程序的内部状态并将其映射到屏幕上可见的内容。你获取各种对象、数组、字符串和数字,最后得到一个包含文本段落、表单、链接、按钮和图像的树。在web上,前者通常表示为JavaScript 数据结构,后者表示为文档对象模型。

We often call this process rendering, and you can think of it as a projection of your data model to a visible user interface. When you render a template with your data, you get a DOM (or HTML) representation of that data.
我们常称这个过程为渲染, 你可以将它看作是 你的数据模型到可见的用户界面的投影。 当你用数据渲染模板时, 你会获取到该数据的 DOM(或者是HTML) 表示。

This by itself is simple enough: While the mapping from data model to UI may not always be trivial, it's basically still just a function with straightforward inputs and outputs.
这本身很简单: 虽然从数据模型到UI的映射并不总是那么简单,但它基本上仍然只是一个具有简单输入和输出的函数。

Where things start to get more challenging is when we start talking about data changing over time. This can happen when the user interacts with the UI, or when something else happens in the world that updates the data. The UI needs to reflect this change. Furthermore, because rebuilding DOM trees is expensive, we'd like to do as little work as possible to get that updated data on the screen.
当谈及数据 随时间变化 时,开始变得更具挑战性。当用户与UI交互,或者在更新数据时发生其他事情,就会发生这种情况。UI需要反映这种变化。此外,由于重新构建DOM树的成本很高,我们希望尽可能少地在屏幕上获取更新的数据。

This is a much more difficult problem than merely rendering a UI once, since it involves state changes. This is also where the solutions out there begin to show their differences.
这比只渲染一次 UI 困难得多,因为涉及到 状态改变。这也是解决方案开始显示它们的不同之处。

Server-Side Rendering: Reset The Universe 服务端渲染: 重置宇宙

"There is no change. The universe is immutable."
并没有改变。 宇宙是不可变的。

Before the era of Big JavaScript, every interaction you had with a web application used to trigger a server roundtrip. Each click and each form submission meant that the webpage unloaded, a request was sent to the server, the server responded with a new page that the browser then rendered.
在大JavaScript时代之前,你和 web应用的每次交互都会触发服务器往返行程。每次点击和表单提交意味着页面被卸载,请求被发送到服务器,服务器用 页面进行响应, 然后浏览器渲染此页面。

With this approach, there wasn't really any state to manage in the front-end. Each time something happened, that was the end of the universe, as far as the browser was concerned. Whatever state there was, it was a backend concern. The frontend was just some generated HTML and CSS, with perhaps a bit of JavaScript sprinkled on top.
使用这种方法,前端实际上没有任何状态可以管理。每次发生什么事情,对浏览器来说都是世界末日。无论处于什么状态,它都是后端问题。前端只是一些生成的HTML和CSS,上面可能撒了一点JavaScript。

While this was a very simple approach from a front-end perspective, it was also very slow. Not only did each interaction mean a full re-rendering of the UI, it was also a remote re-rendering, with a full roundtrip to a faraway data center.
从前端的角度来看,这是一种非常简单的方法,但也非常缓慢。每次交互不仅意味着UI的完全重新渲染,还是一个 远程 重渲染,与一个遥远的数据中心的完整往返。

Most of us don't do this in our apps anymore. We may render the initial state of our app server-side but then switch to managing things in the front-end (which is what isomorphic JavaScript is largely about). There are people that are still succeeding with more sophisticated versions of this pattern though.
我们大多数人都不再在应用程序中这样做了。我们可以服务端渲染应用的 初始 状态,然后切换到前端管理(这是同构JavaScript的主要内容)。有些人仍然在[此模式的更复杂的版本]中取得成功。

First-gen JS: Manual Re-rendering 第一代: 手动重渲染

"I have no idea what I should re-render. You figure it out."
"我不知道我应当重渲染什么,由你确定"

The first generations of JavaScript frameworks, like Backbone.js, Ext JS, and Dojo, introduced for the first time an actual data model in the browser, instead of just having some light-weight scripting on top of the DOM. That also meant that for the first time you had changing state in the browser. The contents of the data model could change, and then you had to get those changes reflected in the user interface.
JavaScript 框架的第一代, 例如 Backbone.js, Ext JS, Dojo, 首次在浏览器中引入了 数据模型, 而不是基于 DOM的一些轻量脚本。 这也意味着你首次在浏览器中有 改变状态 。 数据模型的内容可能会改变, 你必须将这些改变反映到用户界面中。

While these frameworks gave you the architecture for separating your UI code from your models, they still left the synchronization between the two up to you. You can get some sort of events when changes occur, but it is your responsibility to figure out what to re-render and how to go about it.
虽然这些框架为您提供了将 UI 代码与模型分离的体系结构,但它们仍然将二者之间的同步留给您自己。当发生变更时,你可以得到一些事件,但是由你来确定要重渲染什么以及如何执行它。

The performance considerations of this model are also left largely to you as an application developer. Since you control what gets updated and when, you can tweak it pretty much as you'd like. It often becomes a balancing act between the simplicity of re-rendering large sections of the page, and the performance of re-rendering just the bits that need updating.
作为应用开发人员,模型的性能考虑也主要留给您。因为你可以控制更新的内容和时间,你可以随心所欲地调整它。在重新渲染页面大部分内容的简易性和只重新渲染需要更新的位元的性能之间,常常会成为一种平衡行为。

Ember.js: Data Binding 数据绑定

"I know exactly what changed and what should be re-rendered because I control your models and views."
我知道什么改变了,什么应该被重新渲染,因为我控制着你的模型和视图。

Having to manually figure out re-rendering when app state changes is one of the major sources of incidental complexity in first-gen JavaScript apps. A number of frameworks aim to eliminate that particular problem. Ember.js is one of them.
当应用状态改变时,必须手动重新渲染,这是第一代 JavaScript 应用中复杂性的主要来源之一。许多框架旨在消除这个特定的问题,Ember.js 就是其中之一。

Ember, just like Backbone, sends out events from the data model when changes occur. The difference is that with Ember, there's also something the framework provides for the receiving end of the event. You can bind the UI to the data model, which means that there is a listener for update events attached to the UI. That listener knows what updates to make when it receives an event.
和 Backbone 一样,在发生变更时 Ember 从数据模型发送事件。不同的是,Ember 框架还为事件的接收端提供了一些东西。你可以将 UI 绑定到数据模型,这意味着有一个侦听器用于更新附加到UI的事件。该侦听器知道在接收到事件时要进行哪些更新。

This makes for a pretty efficient update mechanism: Though setting all the bindings up takes a bit of work initially, after that the effort needed to keep things in sync is minimal. When something changes, only the parts of the app that actually need to do something will activate.
这使得更新机制非常高效: 尽管设置所有绑定最初需要做一些工作,但在此之后,保持同步所需的工作是最少的。当某些东西发生变化时,只有应用程序中真正需要做一些事情的部分才会被激活。

The big tradeoff of this approach is that Ember must always be aware of changes that occur in the data model. That means you need to have your data in special objects that inherit from Ember's APIs, and that you need to change your data using special setter methods. You can't say foo.x = 42. You have to say foo.set('x', 42), and so on.
这种方法的最大缺点是,Ember 必须始终了解数据模型中发生的更改。这意味着您需要将数据放在继承自Ember api的特殊对象中,并且需要使用特殊的setter方法更改数据。你不能 foo.x = 42。你必须 foo.set('x', 42)等等。

In the future this may be helped somewhat by the arrival of Proxies in ECMAScript 6. It lets Ember decorate regular objects with its binding code, so that all the code that interacts with those objects doesn't necessarily need to adhere to the setter conventions.
将来,这可能会在某种程度上受到ECMAScript 6 中 Proxies 的影响。它允许Ember使用它的绑定代码来装饰常规对象,这样所有与这些对象交互的代码都不必遵循 setter 约定。

AngularJS: Dirty Checking 脏检查

"I have no idea what changed, so I'll just check everything that may need updating."
我不知道改变了什么, 所以我只要检查所有可能需要更新的东西。

Like Ember, Angular also aims to solve the problem of having to manually re-render when things have changed. However, it approaches the problem from a different angle.
像 Ember,Angular 也旨在解决当发生变更时必须手动重新渲染的问题。但是,它从不同的角度来处理这个问题。

When you refer to your data in an Angular template, for example in an expression like {{foo.x}}, Angular not only renders that data but also creates a watcher for that particular value. After that, whenever anything happens in your app, Angular checks if the value in that watcher has changed from the last time. If it has, it re-renders the value in the UI. The process of running these watchers is called dirty checking.
当你在 Angular 模板中引用数据时,例如在表达式 {{foo.x}},Angular 不仅渲染数据,而且还为该值创建一个 watcher。此后,无论你的应用中发生什么事情,Angular 都会检查 watcher 的值是否与上次相比是否发生了变化。如果有,它会在UI中重新渲染值。运行这些 watcher 的过程称为“脏检查”。

The great benefit of this style of change detection is that now you can use anything in your data models. Angular puts no restrictions on that - it doesn't care. There are no base objects to extend and no APIs to implement.
这种类型的变更检测最大好处是现在你可以在数据模型中使用任何东西。Angular 对此没有限制-它不在乎。没有要扩展的基对象,也没有要实现的api。

The downside is that as the data model now doesn't have any built-in probes that would tell the framework about changes, the framework doesn't have any insight into if and where it may have occurred. That means the model needs to be inspected for changes externally, and that is exactly what Angular does: All watchers are run every time anything happens. Click handlers, HTTP response processors, and timeouts all trigger a digest, which is the process responsible for running watchers.
缺点是,由于数据模型没有任何内置的探针告诉框架发生的变化,Angular 不知道是否发生改变以及哪里可能发生改变。这意味着需要外部对模型进行检查,而这正是Angular 所做的: 只要有事情发生,就会运行所有的 watcher。单击处理程序、HTTP响应处理器和超时都会触发一个 digest,该过程负责运行监视程序。

Running all watchers all the time sounds like a performance nightmare, but it can be surprisingly fast. This is mostly because there's usually no DOM access happening until a change is actually detected, and pure JavaScript reference equality checks are cheap. But when you get into very large UIs, or need to render very often, additional performance optimization trickery may be required.
一直运行所有的观察者听起来像是一场性能噩梦,但它的速度却是惊人的快。这主要是因为在实际检测到变更之前通常不存在DOM访问,而且纯JavaScript引用相等检查成本很低。但是当您遇到非常大的ui,或者需要经常渲染时,可能需要额外的性能优化技巧。

Like Ember, Angular will also benefit from upcoming standards: In particular, the Object.observe feature in ECMAScript 7 will be a good fit for Angular, as it gives you a native API for watching an object's properties for changes. It will not cover all the cases that Angular needs to support though, as Angular's watchers can do much more than just observe simple object attributes.
和 Ember 一样,Angular 也将从即将到来的标准中获益: 特别是,ECMAScript 7中的 object.observer 特性很适合 Angular,因为它为您提供了一个原生的 API来监视对象属性的更改。它不会涵盖所有需要 Angular 支持的情况,因为Angular 的 watcher 可以做的不仅仅是观察简单的对象属性。

The upcoming Angular 2 will also bring some interesting updates on the change detection front, as has been described recently in an article by Victor Savkin. Update 7.3.2015: Also see Victor's ng-conf talk about the subject.
即将发布的 Angular 2 也将带来一些关于变化检测前沿的有趣更新,正如Victor Savkin最近的一篇文章所描述的。

React: Virtual DOM 虚拟 DOM

"I have no idea what changed so I'll just re-render everything and see what's different now."
我不知道改变了什么, 所以我重新渲染一切, 然后看看有什么不同。

React has many interesting features, but the most interesting of them in terms of our discussion is the virtual DOM.
React 有许多有趣的特性,就我们的讨论而言, 最有趣的是虚拟DOM。

React, like Angular, does not enforce a data model API on you and lets you use whatever objects and data structures you see fit. How does it, then, solve the problem of keeping the UI up to date in the presence of change?
和 Angular 一样,React 并不强制使用数据模型API,而是允许您使用任何您认为合适的对象和数据结构。那么,它如何解决在变更发生时保持UI最新的问题呢?

What React does is effectively take us back to the good old server-side rendering days, when we simply didn't care about state changes: It renders the whole UI from scratch every time there may have been a change somewhere in it. This can simplify UI code significantly. You (mostly) don't care about maintaining state in React components. Just like with server-side rendering, you render once and you're done. When a changed component is needed, it's just rendered again. There's no difference between the initial rendering of a component and updating it for changed data.
React 所做的就是把我们带回到以前的服务器端渲染时代,那时我们根本不关心状态的变化: 每次在某个地方发生变化时,它都会从头渲染整个UI。这可以大大简化UI代码。你(大部分)不关心在 React 组件中维护状态。就像服务器端渲染一样,你只渲染一次就完成了。当需要更改的组件时,它将再次渲染。组件的首次渲染和由于数据变更而更新之间没有区别。

This sounds immensely inefficient, and it would be if that was the end of the story. However, the re-rendering React does is of a special kind.
这听起来效率非常低,如果这就是故事的结局的话。然而,React 的重新渲染是另类。

When a React UI is rendered, it is first rendered into a virtual DOM, which is not an actual DOM object graph, but a light-weight, pure JavaScript data structure of plain objects and arrays that represents a real DOM object graph. A separate process then takes that virtual DOM structure and creates the corresponding real DOM elements that are shown on the screen.
当一个 React UI 被渲染时,它首先被渲染到一个virtual DOM中,这不是一个实际的DOM对象图,而是用一个由普通对象和数组组成的轻量级的纯JavaScript的数据结构,表示一个真实的DOM对象图。然后,另一个进程使用虚拟DOM结构并在屏幕上显示创建的对应的真实DOM元素。

Then, when a change occurs, a new virtual DOM is created from scratch. That new virtual DOM will reflect the new state of the data model. React now has twovirtual DOM data structures at hand: The new one and the old one. It then runs a diffing algorithm on the two virtual DOMs, to get the set of changes between them. Those changes, and only those changes, are applied to the real DOM: This element was added, this attribute's value changed, etc.
然后,当发生变更时,将从头创建一个新的虚拟 DOM。新的虚拟 DOM 将反映数据模型的新状态。React 现在有 2个 虚拟 DOM 数据结构: 新的和旧的。然后在两个虚拟 dom 上运行 diffing 算法,得到它们之间的changes 集合。这些变更,以及只有那些更改,被应用到实际的DOM中: 添加了这个元素,这个属性的值更改了,等等。

So the big benefit of React, or at least one of them, is that you don't need to track change. You just re-render the whole UI every time and whatever changed will be in the new result. The virtual DOM diffing approach lets you do that while still minimizing the amount of expensive DOM operations.
所以 React 的最大好处,或者至少是其中之一,就是你不需要追踪变化。你只需每次重新渲染整个UI,任何改变都会在新的结果中出现。虚拟DOM diffing 方法允许您这样做,同时仍然可以最小化昂贵的DOM操作的数量。

Om: Immutable Data Structures ( Om: 不可变的数据结构 )

"I know exactly what didn't change."
我知道什么没有改变。

While React's virtual DOM approach is pretty fast, it can still become a bottleneck when your UI is big or when you want to render very often (say, up to 60 times per second).
虽然 React 的虚拟DOM 非常快, 当你的 UI 很大或者 你经常渲染(比如, 每秒 60 次) 时, 它仍然会成为瓶颈。

The problem is that there's really no way around rendering the whole (virtual) DOM each time, unless you reintroduce some control into the way you let changes happen in the data model, like Ember does.
问题是,实际上没有办法每次都渲染整个(虚拟)DOM,除非你重新引入一些控件,让变更在数据模型中发生,就像 Ember 所做的。

One approach to controlling changes is to favor immutable, persistent data structures. They seem to be a particularly good fit with React's virtual DOM approach, as has been shown by David Nolen's work on the Om library, built on React and ClojureScript.
控制变更的一种方法是支持不可变的、持久的数据结构。它们似乎特别适合与React的虚拟DOM方法相匹配,正如David Nolen 在基于React 和 ClojureScript 构建的库 Om中中所展示的。

The thing about immutable data structures is that, as the name implies, you can never mutate one, but only produce new versions of it. If you want to change an object's attribute, you'll need to make a new object with the new attribute, since you can't change the existing one. Because of the way persistent data structures work, this is actually much more efficient than it sounds.
关于不可变数据结构的问题是,正如它的名称所暗示的那样,您永远不能改变一个结构,而只能生成它的新版本。如果您想要更改一个对象的属性,您将需要使用新的属性创建一个新的对象,因为您不能更改现有的对象。由于持久数据结构的工作方式,这实际上比听起来要高效得多。

What this means in terms of change detection is that when a React component's state consists of immutable data only, there's an escape hatch: When you're re-rendering a component, and the component's state still points to the same data structure as the last time you rendered it, you can skip re-rendering. You can just use the previous virtual DOM for that component and the whole component tree stemming from it. There's no need to dig in further, since nothing could possibly have changed in the state.
就变更检测而言, 这意味着一个 React 组件的状态只由不可变数据组成, 有一个逃生出口: 当你重新渲染一个组件, 该组件的状态仍然指向和你上次渲染相同的数据结构时, 你可以跳过重新渲染。你可以为该组件使用以前的虚拟DOM,并使用它产生的整个组件树。没有必要再深究下去了,因为这状态不可能有什么变化。

Just like Ember, libraries like Om do not let you use any old JavaScript object graphs in your data. You have to build your model from immutable data structures from the ground up to reap the benefits. I would argue that the difference is this time you don't do it just to make the framework happy. You do it because it's simply a nicer approach to managing application state. The main benefit of using immutable data structures is not to gain rendering performance, but to simplify your application architecture.
就像 Ember 一样,像 Om 这样的库不允许在数据中使用任何旧的JavaScript对象图。您必须从头开始从不可变的数据结构构建模型,以获得好处。我认为,不同之处在于,这一次你做这些并不是为了让框架满意。您这样做是因为它只是管理应用状态的一种更好的方法。使用不可变数据结构的主要好处不是获得渲染性能,而是简化应用程序体系结构。

While Om and ClojureScript have been instrumental in combining React and immutable data structures, they're not the only game in town. It is perfectly possible to just use plain React and a library like Facebook's Immutable-js. Lee Byron, the author of that library, gave a wonderful introduction to the topic in the recent React.js Conf:
虽然 Om 和 ClojureScript 在将 React 和不变的数据结构结合起来方面发挥了重要作用,但它们并不是唯一的。完全有可能使用普通 React和Facebook的Immutable-js库。该库的作者 Lee Byron 在最近的 React.js Conf 中对这个话题做了精彩的介绍。

I would also recommend watching Rich Hickey's Persistent Data Structures And Managed References for an introduction to this approach for state management.
我也建议观看 Rich Hickey 的 Persistent Data Structures And Managed References , 介绍了这种状态管理的方式。

I myself have been waxing poetic about immutable data for a while now, though I definitely didn't foresee its arrival in frontend UI architectures. It seems to be happening in full force though, and people in the Angular team are also working on adding support for them.
我自己也对不可变数据进行了一段时间的研究,尽管我肯定没有预见到它会出现在前端UI体系结构中。尽管如此,这似乎正在全力进行,而且 Angular 的团队中的人也在致力于为他们增加支持。

Summary 总结

Change detection is a central problem in UI development, and JavaScript frameworks attempt to solve it in various ways.
在 UI 开发中, 变更检测是个核心问题, JavaScript 框架尝试用各种方式解决这个问题。

EmberJS detects changes when they occur, because it controls your data model API and can emit events when you call it.
EmberJS 会检测到变更当变更发生时, 因为它它控制了你的数据模型API 并且当时调用时可以发射事件。

Angular.js detects changes after the fact, by re-running all the data bindings you've registered in your UI to see if their values have changed.
Angular.js 通过重新运行在UI中注册的所有数据绑定以查看它们的值是否发生了变化来会检测事实之后的变更。

Plain React detects changes by re-rendering your whole UI into a virtual DOM and then comparing it to the old version. Whatever changed, gets patched to the real DOM.
普通的 React 通过将整个UI重新渲染为虚拟DOM,然后将其与旧版本进行比较,从而检测变更。无论发生什么变化,都会被修补到真正的DOM中。

React with immutable data structures enhances plain React by allowing a component tree to be quickly marked as unchanged, because mutations within component state are not allowed. This isn't done primarily for performance reasons, however, but because of the positive impact it has on your app architecture in general.
通过允许组件树被快速标记为不变的,使用不可变数据结构的 React 增强了 普通的 React, 因为组件状态内的改变是不允许的。 不过,这并不是主要出于性能原因,而是因为它总体上对应用架构的正面影响。

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

推荐阅读更多精彩内容

  • rljs by sennchi Timeline of History Part One The Cognitiv...
    sennchi阅读 7,283评论 0 10
  • 1 我是赵括,因为父亲赵奢是赵国的马服君,人们便叫马服子。父亲在世的时候,我经常和他讨论兵法,各种兵书阵法,我都烂...
    墨白先森阅读 643评论 0 0
  • 这段时间许多朋友问我该如何优化拼多多主图。这次方法就不写了,因为之前写了很多全面的教程了,很多朋友都说太难优化了又...
    qiukang9299阅读 405评论 0 0
  • 刚才走在路上,看着满天繁星,突然间意识到没有多久2016年即将结束,离2017年考研也只有仅仅41天。25岁已经过...
    木子蛋阅读 375评论 0 0
  • 人间几番新恨, 旧梦零如雨。 满纸诗书浑是泪, 雪泥清踏何清明。
    古风长歌阅读 107评论 0 2