TS基础

  1. TypeScript是微软开发的,基于类的面向对象编程,其文件以 .ts 为后缀名;
  2. TypeScript是JavaScript的超集,完全兼容JavaScript代码;
  3. TypeScript只存活于编译阶段,编译为JavaScript之后,在浏览器/Node环境下才能运行;
  4. TypeScript的安装与编译
    npm i -g typescript
    tsc helloworld.ts
    
    1. 默认情况下,编译生成的js文件输出到当前目录下;
    2. tsc helloworld.ts --outDir ./dist:指定编译后的js文件输出到dist目录。

tsconfig.json

tsc --init  //生成TS的配置文件tsconfig.json

{
    "compileOnSave": true,  //自动编译并保存
    "compilerOptions": {
        "target": "es2017",
        "module": "commonjs",
        "outFile": "./bundle.js",
        "outDir": "./dist",
        "strict": true,
        ......
        "moduleResolution": "node",  //按node编译
        "removeComments": true,  //生成JS代码后,移除注释内容
        "sourceMap": true,  //配合后期调试
        "emitDecoratorMetadata": true,   //支持元数据,装饰器需要使用
        "typeRoots": []  //为TS编译器指定检查类型的依据文件
    },
    "include": [],
    "exclude": []
}
  1. tsconfig.json:用于配置 tsc 的编译配置选项,如js文件的输出目录;
  2. tsc 不指定要编译的ts文件时,编译器会从当前目录开始逐级向上查找tsconfig.json
  3. 当指定了编译的ts文件时,tsconfig.json会被忽略;
  4. --project(-p):指定一个包含 tsconfig.json 的目录来进行编译。

编译选项:compilerOptions

  1. outDir:编译生成的js文件的输出目录,当前目录(./)就是tsconfig.json的所在目录;
  2. outFile:合并输出到一个JS文件中,合并文件的顺序为加载和依赖顺序;
  3. module:编译后的js所使用的模块化系统,none、commonjs、es2015、amd、esnext ...
  4. target:指定编译后的js对应的ECMAScript版本,es3、es5、es6、es2015、es2016、es2017 ...

指定要编译文件

  1. 执行 tsc 命令,但不指定任何ts文件时,默认会编译当前项目中的所有ts文件;
  2. include:指定要编译的ts文件目录
    "include": [ "./src/" ]  //编译src目录下的ts文件
    
    1. 使用glob模式,类似于正则表达式,**/ 递归匹配任意子目录;
    2. * 匹配0或多个字符,? 匹配一个任意字符,但都不包括目录分隔符;
    3. ./src/*:只编译src目录下的ts文件,不包括src的子目录;
    4. ./src/**/*:递归编译src目录下的ts文件,包括子目录;
  3. exclude:指定不编译的ts文件目录,默认已经排除了 node_modulesoutDir 目录

数据类型

  1. let 变量: 类型
    let input: HTMLInputElement = document.querySelector('uname');
    let value: number = Number(input.value) + 10;
    let a: string;  a = 1;  //报错
    
  2. TS的类型:数字、字符串、布尔型、null、undefined、数组、元组、枚举、void、any、Never
  3. 基本类型与包装类型
    1. 基本类型:string、number、boolean
    2. 包装类型:String、Number、Boolean
    3. 基本类型可以直接赋值给对应的包装类型,但反之不行。
    let s: String = 'nodejs';
    
  4. 布尔类型
    var flag:boolean = true;
    flag = false;
    
  5. 数字类型
    var n:number = 12;
    
  6. 字符串类型
    var s:string = 'hello ts';
    
  7. 数组类型
    1. 基本语法:var arr:number[] = [1, 2, 3]; //元素类型为number的数组
    2. 泛型方式:var arr:Array<number> = [1, 2, 3];
  8. 元组类型:数组的一种,可以存储多种类型的元素,但 顺序不可以颠倒,长度不可以违规,是固定的;
    let tup:[number, string] = [11, 'hello'];
    tup[0] = 20;
    tup[1] = '元组';
    
    v2.6 之前,超出规定个数的元素称作越界元素,但是只要越界元素的类型是声明类型中的一种即可,属于联合类型,所以没问题。但 v2.6 之后,要求元组赋值的类型和个数都必须保持一致。
  9. 枚举类型
    enum 枚举名{
        标识符[=整型常数],
        标识符[=整型常数],
        标识符[=整型常数],
    }
    
    1. 标识符可以加引号,也可以不加,[] 表示可选参数
    enum Flag {
        success=1,
        error,
        'undefined'=-1
    }
    
    1. 使用枚举
    let f:Flag = Flag.success;
    console.log(f);  // 1
    
    1. 如果标识符都没有赋予整型常数,则默认为从 0 开始的角标。
  10. 任意类型
    var num:any = 123;
    num = false;
    
  11. nullundefined,定义了变量,但并未赋值,默认是undefined
    var num:undefined;
    console.log(num);  // undefined,
    var num:null = null;
    console.log(num);  // null
    
  12. 复合类型
    var num:number | undefined | null;  // num 可以是数字类型,也可以是undefined/null
    console.log(num);  // undefined
    num = 100;
    console.log(num);  // 100
    
  13. void类型:表示没有任何类型,一般用于定义没有返回值的函数;
    function run():void {
        console.log(123)
    }
    
  14. never类型:表示从不会出现的值,即声明为never类型的变量只能被never类型所赋值;
    var num:never;
    num = (()=>{
        throw new Error('some happend');  //抛出异常,不属于任何已知类型
    })();
    
  15. 类型推导
    1. 有时候不一定强制使用类型声明,TS会根据语境进行类型推导;
    2. TS的变量初始化推导
    let a;  a=1;  //变量a 是number类型,不允许再赋予其他类型的值;
    
    1. TS的上下文推导
    btn.onclick = function(e) {}  //e: MouseEvent
    btn.onkeydown = function(e) {}  //e: KeyboardEvent
    
    1. TS会根据当前绑定的事件,推导出回调函数的第一个参数类型MouseEvent/KeyboardEvent

函数

  1. 函数声明
    function fn(x: Type, y: Type): Type {
        ...
    }
    
    function fn(x: number, y: number): number {  //参数为number,返回值为number
        return x+y;
    }
    //匿名函数
    let fn = function(x: number, y: number): number {
        return x+y;
    }
    
  2. 函数表达式
    let fn:(x:Type, y:Type) => Type = function(x:Type, y:Type){
        ...
    }
    
    let fn: (x: number, y: number) => number = function(x: number, y: number) {
        return x+y;
    }
    // 函数体function的参数类型可以省略,ts会进行类型推导
    let fn: (x: number, y: number) => number = function(x, y) {
        return x+y;
    }
    
  3. 形参的对象约束
    1. 如果传入的是匿名对象,其属性必须与约定属性一致;
    function print(label:{name:string}):void {
        console.log('print ', label.name);
    }
    print({name:'Machel'});  //print Machel
    
    1. 如果不是匿名对象,则只需要包含约定的属性即可;
    let obj = {
        name: 'Machel',
        age: 20
    }
    print(obj);  //print Machel
    
  4. 可选参数:与ES6保持一致,必须配置在形参的末尾,且默认值为undefined
    function run(name:string, age?:number):string {
        return 'hello ts';
    }
    run('Jack');
    run('Jack', 20);
    
  5. 默认参数:指定形参的默认值,也必须配置在形参的末尾
    function run(name:string, age:number=20):string {
        return 'hello ts';
    }
    
    1. 如果手动指定了参数的默认值,则不能再声明为可选参数 ?
    2. 对于默认参数,可以利用TS的自动类型推导,从而不声明类型。
  6. 剩余参数:三点运算符
    function sum(name:string, ...rest:number[]):number {
        //name是一个必传参数(字符串类型),其余所有参数(number类型)都被封装在 rest 数组中
        let sum:number = 0;
        for(let i=0; i<rest.length; i++) {
            sum += rest[i];
        }
        return sum;
    }
    sum('Machel', 1, 2, 3);
    sum('Machel', 1, 2, 3, 4, 5);
    
  7. 函数重载:TS需要兼容ES5和ES6,所以TS的函数重载与Java的函数重载略有不同;
    1. 参数个数相同
    function run(name:string):string;
    function run(age:number):string;
    function run(sn:any):any {
        if(typeof sn === 'string') {  // string
            return 'name is ' + sn;
        } else {  // number
            return 'age is ' + sn;
        }
    }
    run('Jackson'); // name is Jackson
    run(12); // age is 12
    run(true); // 编译报错
    
    1. 参数个数不同:借助可选参数
    function run(name:string):string;
    function run(name:string, age:number):string;
    function run(name:any, age?:any):any {
        if(age) {  // age 存在
            return `name: ${name}, age: ${age}`;
        } else {  // age 不存在
            return `name is ${name}`;
        }
    }
    
  8. 函数的this
    1. ts的函数中,this默认指向 any,ts不能对any类型提示任何属性和方法;
    2. tsconfig.json中,取消 this 默认指向 any 的设置:
    "compilerOptions": {
        "noImplicitThis": true
    }
    
    1. 对于某些情况,如DOM事件,回调函数的this默认指向DOM对象,TS自动推导。

ES5定义类

  1. 构造函数
    function Persion() {
        this.name = 'Machel';
        this.age = 20;
        this.show = function() {
            console.log(this.name, this.age);
        }
    }
    var p = new Persion();
    p.show();  // Machel  20
    
  2. 在原型链上扩展属性和方法
    Persion.prototype.sex = 'male';
    Persion.prototype.work = function() {
        console.log(this.name + 'is work!');
    }
    p.work();
    
  3. 原型链上的属性和方法会被多个实例共享,而构造函数中的属性和方法只是拷贝一份给每个实例;
  4. 静态方法
    Persion.run = function() {
        console.log('run');
    }
    Persion.run();
    
  5. 继承:原型链、对象冒充,以及两种模式的组合
    1. 对象冒充
    function Web() {
        Persion.call(this);  // Web继承Persion
    }
    var w = new Web();
    w.show();  // Machel  20
    w.work();  // 报错:对象冒充不能继承原型链上的属性和方法
    
    1. 原型链继承
    function Web() { }
    Web.prototype = new Persion();
    w.show();  // Machel  20
    //但是这种方式无法给父类传参
    function Persion(name, age) {
        this.name = name;
        this.age = age;
        this.show = function() {
            console.log(this.name, this.age);
        }
    }
    Web.prototype = new Persion();
    var w = new Web('Machel', 22);
    w.show();  //undefined  undefined,接收不到参数!
    
    1. 组合模式
    function Web(name, age) {
        Persion.call(this, name, age);
    }
    Web.prototype = new Persion();  或 Web.prototype = Persion.prototype;
    
    var w = new Web('Machel', 22);
    w.show();  // Machel  20
    w.work();  // Machel is work!
    
  6. 需要百度,详细看看ES5中的类

TS的类

  1. TS的类与ES2015(es6)中的 class 类似,同时新增了一些实用特性;
  2. 类的定义
    class Person {
        name:string;  //属性,默认访问修饰符为public,默认值为undefined
        constructor(name:string) {
            this.name = name;
        }
        getName():string {
            return this.name;
        }
        run():void {
            console.log(this.name + ' 在 Person');
        }
    }
    var p = new Person('Jackon');
    console.log(p.getName());  // Jackon
    p.run();  // Jackon 在 Person
    
  3. 类的继承:extends,单继承
    class Web extends Person {
        constructor(name:string) {
            super(name);  //必须先初始化父类的构造器
        }
        run():void {  //覆写父类的方法
            console.log(this.name + ' 在 Web');
        }
    }
    var w = new Web('Machel');
    console.log(w.getName());  // Machel
    w.run();  // Machel 在 Web
    
  4. 修饰符:public、protected、private、readonly
    1. public:公有,在类内部、子类、类外部(对象)都可以访问,默认修饰符
    2. protected:保护类型,在类内部、子类可以访问,类外部(对象)不能访问;
    3. private:公有,在类内部可以访问,子类、类外部(对象)不能访问;
    4. readonly:只读,对象只能获取,不能重新赋值;
    5. 在构造函数的参数上使用修饰符,表示同时在类中创建该属性,该属性不能在类中预定义;
    constructor(public age: number){  //为Person创建属性age:public age: number;
        this.age = age;
    }
    
  5. 存取器:setter/getter的简写形式
    private _age: number = 10;
    get age(): number {   //访问:p1.age;
        return this._age;
    }
    set age(age: number) {   //访问:p1.age = 20;
        this._age = age;
    }
    
  6. 静态属性、方法:static修饰,静态方法中没有this,所以只能访问静态属性;
    class Person {
        public name:string|undefined;
        static age:number = 20;
        static print() {
            console.log('print ' + Person.age);
        }
    }
    Person.age;  // 20
    Person.print();  //print 20
    
  7. 多态:继承的一种表现,父类引用指向子类对象!
    var p:Person = new Web('Machel');
    p.run();  // Machel 在 Web
    

抽象类

abstract:修饰抽象类和抽象方法

  1. 抽象类不能直接实例化,抽象方法不包含具体实现;
  2. 抽象类中可以同时包含抽象方法和普通方法,但抽象方法只能放在抽象类中;
  3. 抽象类的子类如果不是抽象类,则必须实现父类的抽象方法!
    abstract class Animal {
        public name:string;
        constructor(name:string) {
            this.name = name;
        }
        abstract eat():any;
    }
    class Dog extends Animal {
        constructor(name:string) {
            super(name);
        }
        eat() {
            console.log(this.name + ' is eat!')
        }
    }
    var d:Animal  = new Dog('dog');
    d.eat();  // dog is eat!
    

接口:interface

  1. 属性接口,约束函数的对象形参
    interface Options {
        width: number, height: number
    }
    function print(opts: Options){
        console.log(opts.width, opts.number);
    }
    print({width:100, height:50})
    
    1. TS类型检测器只会检查接口所定义的规则属性是否存在,并不会检查属性的顺序;
        print({height:50, width:100})
    
    1. 当以匿名对象的方式传入时,必须严格遵守接口规则,不能有多余属性;
    print({
        firstName: 'Jack',
        secondName: 'Machal',
        age: 1  //编译报错:匿名对象的形式传入时,只能包含接口中约束的属性
    });
    
    1. 如果传入的对象不是一个匿名对象,那么只需要包含接口规则的属性即可;
    var obj = {
        firstName: 'Jack',
        secondName: 'Machal',
        age: 1
    }
    print(obj);  //编译通过:Jack Machal
    
    1. as 断言可以绕开TS检测
    print({ width: 100 } as Options); //编译通过
    
  2. 可选属性的接口
    interface FullName {
        firstName:string;
        secondName?:string;  //可选属性
    }
    print({
        firstName: 'Jack'  //不传secondName属性
    })
    
  3. 函数接口
    interface Func {
        (key:string, value:string):string;
    }
    var fn:Func = function(key:string, value:string):string {
        return key + value;
    }
    fn('Jackson', '123456');  //Jackson123456
    
  4. 可索引接口:数组、对象的约束,也就是一组 key-value 的数据,数量是不确定的,其中的key具有某种特性;
    1. key的特性在于:只能是 stringnumber
    2. 数组约束
    interface UserArr {
        [index:number]:string  //索引为number类型,值为string类型
    }
    var arr:UserArr = ['aaa', 'bbb'];
    
    1. 对象约束
    interface UserObj {
        [index:string]:string  //属性名和属性值都是string类型
    }
    var obj:UserObj = {name:'Joker', age:'20'}
    
    1. TS类是不允许对象自己直接扩展属性和方法的,但可以通过接口去扩展!
    class Person {
        name = 'Mack'
    }
    let p = new Person();
    p.run = function(){ }  //编译报错!
    
    interface Person {
        [attr: string]: any
    }
    p.fly = function() {  //编译通过
        console.log('fly: ', this.name);
    }
    p.fly();  // fly: Mack
    
  5. 类类型接口:对类的约束,有点类似于抽象类
    interface Animal {
        name:string;
        eat(foot:string):void;
    }
    class Dog implements Animal {
        name:string;
        constructor(name:string) {
            this.name = name;
        }
        eat() {  //可以只实现接口要求的方法,忽略要求的参数
            console.log('eat...');
        }
    }
    
  6. 接口的继承
    interface Animal {
        eat():void;
    }
    interface Person extends Animal {
        work:void;
    }
    class Web implements Person {
        public name:string;
        constructor(name:string) {
            this.name = name;
        }
        eat() {
            console.log('eat...');
        }
        work() {
            console.log('work...');
        }
    }
    
  7. 一个类可以同时实现多个接口,但只能继承一个类
    class A extends B implements C,D {
    
    }
    

泛型

  1. 泛型变量
    function getData<T>(value:T):T {
        return value;
    }
    getData<number>(123);  //number类型
    getData<string>('Machel');  //string类型
    
    function getData3<T>(value:T):void {
        console.log(value)
    }
    getData<number>(123);  // 123
    
    1. 泛型也可以有多个
    function fn<T, S>(a:T, b:S): [T, S] {
    
    }
    
    1. 还可以是数组形式
    function fn<T>(a: T[]): T[] {
    
    }
    function fn<T>(a: Array<T>): Array<T> {
    
    }
    
  2. 泛型类
    class Min<T> {
        public list:T[] = [];
        add(value:T):void {
            this.list.push(value);
        }
        min():T {
            var m = this.list[0];
            ...
            return m;
        }
    }
    var n = new Min<number>();
    n.add(2);
    
  3. 泛型作为一种类型
    let fn: <T>(x: T, y: T) => number = function(x, y) {
        return Number(x) + Number(y);
    }
    
  4. 泛型接口
    1. 方式一
    interface IFn {
        <T>(x: T): T;
    }
    var fn:IFn = function<T>(x:T):T {
        return x;
    }
    fn<string>('Joker');  //Joker
    
    interface IFn<T> {
        (x: T, y: T): number
    }
    let fn: IFn<string> = function(x, y) {
        return Number(x) + Number(y);
    }
    
    1. 方式二
    interface IFn<T> {
        (x:T):T;
    }
    function fn<T>(x:T):T {
        return x;
    }
    var myGet:IFn<string> = fn;
    myGet('Joker');
    
  5. 泛型约束:extends,约束泛型的类型范围
    1. 约束泛型为HTML节点对象
    function fn<T extends HTMLElement> (ele: T) {
        
    }
    
    1. 配合接口使用
    interface Len {
        length: number
    }
    function fn<T extends Len> (e: T) {
        
    }
    fn(1);   //编译报错:number类型没有实现 Len 接口,也不具备 length 属性
    fn('2');  //string类型实现了 Len 接口
    
  6. 类类型
    1. 如何让一个外部函数成为创建对象的工厂
    function getArray(constructor: Array) {
        return new constructor();
    }
    let arr = getArray(Array);   //编译报错
    
    1. 形参constructor表示Array类型的对象,而不是一个Array的构造函数,所以无法创建对象;
    2. {new()}:表示构造函数类型
    function getInstance(constructor: {new()}) {
        return new constructor();
    }
    let arr = getInstance(Array);   //通过TS检查,创建一个数组对象
    
    1. 限制构造函数的类型:
    function getInstance(ct: {new(): Array<string>}) {
        return new ct();    //只能创建Array<string>类型的对象
    }
    function getInstance<T>(ct: {new(): T}) {  //泛型
        return new ct();
    }
    
  7. 应用:数据库操作的封装
    class MySqlDB<T> { //
        add(info:T):boolean {
            //...... 把info中的属性插入到对应的表中
            return true;
        }
    }
    class User {  //数据库表的映射对象
        username:string;
        password:string;
        constructor(params:{ username:string, password:string }) {
            this.username = params.username;
            this.password = params.password;
        }
    }
    var u = new User({username:'Joker', password:'123456'});
    var DB = new MySqlDB<User>();
    DB.add(u);
    

模块

  1. 内部模块称为命名空间,外部模块称为模块;
  2. 模块在自身作用域中执行,而不影响全局作用域,即模块中的变量、函数、类等等对外部是不可见的;
  3. 外部要使用模块内的数据,必须先让模块通过 export 暴露出里面的数据,外部再通过 import 引入这些数据即可;

(外部)模块

  1. 创建目录modules,用于存放项目的模块,创建一个模块db.ts
    var url = 'xxxxxx';
    
    export function getData():any[] {
        return [
            { title: '111' },
            { title: '222' },
        ]
    }
    export function save() {
        console.log('save...')
    }
    
  2. 在其他模块中引入 db.ts 模块中的方法
    import { getData } from './modules/db';
    
  3. 引入时起别名
    import { getData as get } from './modules/db';
    
  4. 但是,export、import 会被编译为 exports、require(),浏览器默认不支持,需要使用 node 命令执行,或者借助 webpack 这样的工具编译为浏览器能执行的代码;
  5. 暴露方式二
    export { url, getData, save }
    
  6. 暴露方式三:export default
    1. export 可以导出多次,而 export default 只能导出一次
    export default getData;
    
    1. export default 导出的是什么数据,import 导入的就是什么数据
    import getData from './modules/db';
    

命名空间

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

推荐阅读更多精彩内容