从java到javascript的快速入门

javajavascript的快速入门

本文专门为学过C语言的Java程序员,快速理解javascript语言的重要概念,量身定做。笔者对javascript其他实现面向对象的方式,尽可能保持克制,尽可能让javascript的面向对象风格与java对标起来,帮助java程序员快速掌握javascript面向对象。

之所以有必要讲解下javascript,是因为如今的javascript是全能(全栈)的,除了运行于浏览器;还可以借助node运行到服务器;还可以借助electron这类框架,开发桌面程序;还可以React-Native,开发手机程序;还有在类似cassandra服务器里面用javascript做嵌入式脚本语言。类似这样的变化,还在不断发生。

文章用到的代码均托管在 githubdowngoon/java2javascript 上,可通过issue给我反馈 。

1. 从函数开始

要实现一个sumlen函数,输入两个字符串,返回它们的长度之和。java代码(完整代码见C1Sumlen.java)是这样的:

// 函数定义
int sumlen(String x, String y) {
  return x.length() + y.length();
}

// 函数调用
int sl = sumlen("hello", "world");

相比较javascript代码(完整代码见C1Sumlen.js)是这样的:

// 函数定义
function sumlen(x, y) {
  return x.length + y.length;
}

// 函数调用
var sl = sumlen("hello", 'world');

两者的相似与差异正如它们的名字(据说javascript当初叫这个名字也正是想借一下java的势头):

  • 相似处:语法上很像。
  • 差异处:(1)JS是弱类型,Java是强类型;(2)JS函数需要function关键字,Java无需。

弱类型

JS不像Java那样,定义变量x的时候,要指定数据类型是StringBooleanInteger。JS形式上都是var(类似Java的Object是一切类的超类),只不过当它赋值后,就隐含了数据类型(可用系统typeof函数查看)。如下详细代码见C1JsType.js:

var x;  // 类型 undefined
x = 'helloworld';  // 类型 string
x = 3.14;  // 类型 number
x = true;  // 类型 boolean
x = ['helloworld', 3.14, true];   // 属性 数组Object

function关键字

正因为JS是弱类型,于是定义函数的时候,函数签名时的输入参数和输出参数就没必要都带个var了,这样代码(详细见C1JsFunc.js)更简洁:

// 无输入,无输出
function f_in0_ou0() {
    console.log('无输入,无输出');
}

// 无输入,有输出
function f_in0_ou1() {
    return 'helloworld';
}

// 有输入,有输出
function f_in1_ou1(x) {
    return x.length;
}

// 有输入,无输出
function f_in0_ou1() {
    return Math.random();
}

函数指针与调用运算符

弱类型谈到无论是布尔类型,数字类型还是字符串类型都可以赋值给var。在JS中,function也是一种类型,它也可以赋值给var变量,这点非常类似C语言的函数指针或说Java语言的interface。详细代码见C1JsFuncPointer.js

function sumlen(x, y) {
    return x.length + y.length;
};

var f = sumlen; // 函数赋值给变量(类似函数指针)

sumlen('hello', 'world');  //  按函数名称调用
f('hello', 'world');  // 按函数指针调用

f('hello', 'world'); 语法就是在函数指针后面加一个()调用运算符,就表示调用。

优先运算符

函数指针后面的()调用运算符。实际上我们还可以在函数指针前面加(),表示优先运算符。例如:

f('hello', 'world');  // 按函数指针调用
(f)('hello', 'world');  // 函数指针加了一个 优先运算符
((f))('hello', 'world');  // 加2个优先运算符

它就类似算数运算表达式: 1*2+3*4 也可更加清楚的写为 (1*2)+(3*4)

匿名函数

var g = function (x, y) {
    return x.length + y.length;
}
console.log('匿名函数: ' + g('hello', 'world'));

JS的函数,还可以不用起名字,用function声明后,立即赋值给一个变量(如果不赋值给一个变量,以后就没法调用了)。这种不起名字的函数就叫匿名函数

匿名函数的立即调用

前面我们看到,函数可以赋值给一个变量(函数指针),然后通过函数指针可以调用函数:

var f = sumlen;  // 函数赋值给变量
f('hello', 'world');  // 按函数指针调用

也提到如果是匿名函数定义,必须赋值给一个变量,不然以后没有调用句柄。但是我们有没有想过,如果定义的时候,就立即调用呢?这样不就不需要调用句柄么?答案:的确可以。

(function (x, y) {
    console.log('匿名函数的立即调用:' + x + ',' + y);
    return x.length + y.length;
})('hello', 'world');

这个匿名函数是带两个输入参数的,也可以不带输入参数的:

(function() {
    console.log('不带参数的匿名函数的立即调用');
})();

上述结构是这样的:

  • 匿名函数:function() {}
  • 匿名函数调用:先用了一个优先运算符,把函数优先起来,(function(){});接着使用调用运算符(function(){})()

2. 插播JS对象

对象定义

前面谈了JS的函数,还没完,但我们先放一放。插播下JS对象。对Java程序员来说,JS对象不会陌生,它非常类似JSON数据(因为JSON就是从JS来的),具体代码详见C2JsObj.js

var x = {'name': 'zhangsan', 'age': 25, 'male': true};

当然,为了方便,JS还允许它写成如下格式(属性名可以不用引号):

var x = {name: 'zhangsan', age: 25, male: true};

属性读取

如果要引用它的属性呢?下面两种方式都可以:

x.name   // 很像Java的public成员,也像C的结构体成员
x['name']   // 居然还可以类似Java的Map

恐怕让Java程序员吃惊的是对象的属性还可以x['name']引用。其实Java也可以的,只不过是要用反射机制。
自此JS的变量,可以被赋值以下各种类型:

var x;
x = 3.14;
x = `helloworld`;
x = true;
x = function() {};
x = {'name': 'zhangsan', 'age': 25, 'male': true};

属性追加

JS对象还有个特点,它可以随时追加属性。比如上面的人除了姓名、年龄和性别外,我们还可以加一个学历信息:

x.edu = 'master';
console.log(x); // 它输出: { name: 'zhangsan', age: 25, male: true, edu: 'master' }

对Java程序员看来,JS对象,有点像Class类new出来的对象,又有点像Map这种字典类或者是基于反射的类,可以随时加属性。

属性遍历

如果要遍历对象x下的所有属性,可以类似Java的Map遍历一样:

for (var f in x) {
    console.log(f);
}

3. 对象与函数纠缠

有了函数对象的基本知识,我们接着看看它们之间的纠缠组合。详细代码见C3FuncObj.js

对象的属性是一个函数

刚才的var x = {name: 'zhangsan', age: 25, male: true};,对象的属性都是普通类型的数据,而对象属性本质上就是一个变量,这个变量既可指向普通数据,也可指向另一个对象,还可以指向一个函数。

var x = {
    name: 'zhangsan',
    age: 25,
    male: true,
    selfIntro: function() {
        console.log('我叫'+this.name+',今年'+this.age+'岁,性别'+(this.male?'男':'女'));
    },
    sayHi: function() {
        console.log("Hello, World");
    },
    parents: [
        {'name': 'wangyi', 'age': 42},
        {name: 'maliu', 'age': 40}
    ]
};

console.log(x.selfIntro); // 打印出函数
x.selfIntro();
x.sayHi();

这里的sayHi属性和selfIntro属性都指向一个匿名函数;而parents属性指向一个对象数组。

注意

selfIntro方法中,为了访问name等属性,还使用到了this关键字,表示本对象的引用。

返回对象的函数

截止目前,我们之前讲的函数,要么没有返回值,要么返回的只是普通数据。这里我们要在一个函数调用后,返回一个对象:

function Person() { // 函数名称的首字母故意大写
    return {'name': 'zhangsan', 'age': 25, 'male': true};
};

var p1 = Person(); // 函数调用第一次,返回一个对象
var p2 = Person(); // 函数调用第二次,返回第二个对象
console.log(p1==p2); // 打印false,表示的确是两个不同对象

代码var p1 = Person();,完全是函数的一次调用,只不过这个函数的首字母大写了,就会马上让Java程序员联想到Person p1 = new Person();。我们还可以引入3个输入变量,把Person函数改写下:

function Person(name, age, male) {
    return {'name': name, 'age': age, 'male': male};
};

返回函数的函数

前面的章节,大家感受到在JS里函数也是个对象,可以赋值给var x。那么我们有没有想过,如果一个函数的返回值是另外一个函数呢?

function Person(n, a, m) { // 函数的输入参数
    var name = n;
    var age = a;  // 定义局部变量(函数内的)
    var male = m;

    return function() { // 函数的返回值是一个匿名函数
        return '我叫'+name+',今年'+age+'岁,性别'+(male?'男':'女');
    };
};

var pSelfIntro = Person('chenliu', 36, true);
console.log(pSelfIntro());

这段代码是一个名叫Person的函数,它有点特别的是函数调用后的返回值不是普通数据,而是一个匿名函数(当然这个匿名函数的返回值是一个组合字符串)。语句var pSelfIntro = Person('chenliu', 36, true);是把函数调用的结果(一个匿名函数)赋值给了一个pSelfIntro变量,这个变量后面接一个()调用运算符,实现调用,并把调用结果用console输出。

函数嵌套另一个函数

刚才的例子,返回值是一个函数的时候,其实也意味着在JS中,一个函数的内部还可以定义另外一个函数。也就是函数嵌套
上述返回函数的函数改写下,以便更清晰地展现函数嵌套的概念:

function PersonOuter(n, a, m) {
    var name = n;
    var age = a;  // 定义局部变量(函数内的)
    var male = m;

    function PersonInner() { // 函数嵌套
        return '我叫'+name+',今年'+age+'岁,性别'+(male?'男':'女');
    };

    return PersonInner; // 函数的返回值是一个带名函数
};

var innerFunc = PersonOuter('chenliu', 36, true);
innerFunc(); // 函数调用

我们可以看到,在内部函数可以访问外部函数的变量。再来C3FuncObj-nestfun.js

var outer_var = 1;
function outer_fun() { // 外层函数定义
    var midd_var = 2;
    function inner_fun() { // 内层函数定义
        var inner_var = 3;
        console.log(outer_var + midd_var + inner_var);
        function core_fun() { // 核层函数定义
            var core_var = 4;
            console.log(outer_var + midd_var + inner_var + core_var);
        };
        core_fun(); // 核层函数调用
    };
    inner_fun(); // 内层函数调用
};
outer_fun(); // 外层函数调用

这个例子简单来说,就是说明了JS函数可以嵌套定义,并且内层可以访问外层的变量(包括父节点和祖先节点)。

4. 闭包

封装

有了前面这么多铺垫后,我们可以讲解JS的一个比较费解的概念闭包
假设我们要实现一个SDK,它用来登陆一个网站,它需要给各个子系统共享,以便减少大家的重复开发。但是需要考虑避免全局变量等的冲突。我们期望的使用代码是这样的:

var passed = PassportSDK.login('guest', 'guest');
console.log('登陆结果:' + passed); // 输出 false

passed = PassportSDK.login('admin', '123456');
console.log('登陆结果:' + passed); // 输出 true
console.log('失败次数:' + PassportSDK.getFailCount()); // 输出 1 (因为曾今失败过一次)
console.log('已经登录?:' + PassportSDK.isLogined()); // 输出 true (当前是登陆状态)
console.log(PassportSDK.dbPwd); // 报错,试图访问私有成员

那么实现代码是:

var PassportSDK = (function() { // 匿名函数

    // 扮演私有成员:函数的内部变量
    var dbUser = 'admin'; // 数据库账号
    var dbPwd = '123456'; // 数据库密码

    var failCnt = 0; // 存储失败次数
    var status = false; // 记录登陆状态

    // 扮演公开方法:函数的返回值
    // 返回值是一个对象,对象的有些属性是一个函数
    return {

        login: function(user, password) {
            var isPassed = (dbUser == user && dbPwd === password);
            if (! isPassed) {
                failCnt ++;
            } else {
                status = true;
            }
            return isPassed;
        },

        getFailCount: function() {
            return failCnt;
        },

        isLogined: function() {
            return status;
        }

    };

})(); // 匿名函数的立即调用

怎么解释下这段代码呢?有了前面的铺垫,我们可以这么解释:

  • 闭包是匿名函数的立即调用;
  • 调用的返回值是一个对象;
  • 对象的某些属性是一个函数。

如果您是javascript新手,请反复琢磨这句话,并用这句话解读PassportSDK代码,您一定会有所悟。

闭包有什么好处呢?

  • 名字空间:它好比Java的package机制。比如PassportSDK.login方法不会跟其他的login引发名字冲突。
  • 私有变量:函数嵌套中的内部变量,按道理内部函数执行完后,就会释放掉;但是因为函数把内部变量植入到另一个对象,当做返回值返出去了,导致一方面不能直接访问(发挥私有成员的效果了),另一方面变量还没有释放(因为被包裹着作为返回值了)。

继承

刚才的闭包实现了名字空间,还实现了私有成员。但是Java的面向对象还有继承,假如我们要在开闭原则下,拓展PassportSDK,加入一个logout方法呢?期望使用:

PassportSDKExt.login('admin', '123456'); // 复用 PassportSDK 的实现
PassportSDKExt.logout(); // 增加新的功能

实现代码如下:

var PassportSDKExt = (function(parent) {

    parent.logout = function() { // 对象可以动态追加属性
        parent.status = false; // 访问不了私有成员,没有Java的Protected概念
    };

    return parent;

})(PassportSDK); // PassportSDK 是之前的变量

上述代码就是直接利用了对象可以动态可以追加属性的机制。当然同样是用了匿名函数的立即调用,只不过这个匿名函数有一个输入参数,所以立即调用时,需要把PassportSDK当做输入参数传递进去。

但是这个代码实际上无法运行,因为parent.status是访问不了的,它没有Java的protected成员的概念(子类可以访问)。

5. 面向对象

模板与实例

上面的方式我们都采用了 匿名函数的立即调用 这种形式。但这种形式有个问题,只能在定义的时候调用一次,后面再想调用就调用不了。有点类似Java设计模式里面的单例模式。但面向对象,应该有这种模板机制,每次调用构造方法都能构建一个新的对象。为了能多次调用,我们可以把匿名函数改成命名函数。本实例完整代码见 C5Session.js

在改成命名函数前,为了更加符合RESTful风格,我们把上面PassportSDK的名字重构一下,loginlogout分别重构为session.createsession.remove,因为从资源与资源操作的角度看,登陆的本质就是在服务端分配了一个session资源,登出就是回收session资源。

  • 名称重构

重构后的 匿名函数的立即调用 形式:

var session = (function () { // 匿名函数的立即调用
    var status = false;  // 私有成员
    return {
        create: function (name, pwd) { // 公有方法
            status = ('admin' == name && '123456' == pwd);
            return status;
        },
        remove: function () { // 公有方法
            status = false;
        },
        isActive: function () { // 公有方法
            return status;
        }
    };
})();

session.create('guest', 'guest'); // 使用
  • 改成命名函数
var Session = function () { // 命名函数
    var status = false;  // 私有成员
    return {
        create: function (name, pwd) { // 公有方法
            status = ('admin' == name && '123456' == pwd);
            return status;
        },
        remove: function () { // 公有方法
            status = false;
        },
        isActive: function () { // 公有方法
            return status;
        }
    };
};

var session = Session(); // 命名函数的调用
session.create('guest', 'guest'); // 使用

改成命名函数后,如果想“构造一个对象”,可以执行var session = Session();,但这种风格总跟Java有点不习惯,Java需要一个new关键字,比如:Session session = new Session();。但是Java领域,Factory模式都不太主张在使用方直接用new了,因为那样是强耦合,设计模式里倡导的风格是Session session = sessionFactory.newInstance();,而且最近几年开始流行Vertx vertx = Vertx.vertx()这种风格(Java的一个叫vertx.io的框架中有很多这种风格)。Vertx.vertx() 这种风格,如果换到javascript里面,也是非常容易实现的,它就是Vertx对象,有个叫vertx的属性,这个属性是一个函数。于是我们再改写一下:

  • Vertx.vertx()风格
var Session = {
  session: ${上面的命名函数}
};

var session = Session.session();
session.create('guest', 'guest');

完整的代码:

var Session = { // Vertx.vertx() 风格
  session: function () { // ${上面的命名函数}
      var status = false;  // 私有成员
      return {
          create: function (name, pwd) { // 公有方法
              status = ('admin' == name && '123456' == pwd);
              return status;
          },
          remove: function () { // 公有方法
              status = false;
          },
          isActive: function () { // 公有方法
              return status;
          }
      };
  };
};

var session = Session.session();
session.create('guest', 'guest');

到这,模板就是Session,构造实例就是Session.session(),跟Java就非常吻合了。利用闭包,也有了类似status属性的私有成员;通过返回对象,对象的属性是一个函数的形式,有了公有方法。

静态成员与静态方法

如果我们把上面的Session比作是Java的类,那么Session.session()就好比是静态方法。如果我们需要一个静态成员,来记录实例的个数呢?

var Session = {
  instanceCount: 0, // 类的静态成员 (无法私有化)
  session: function () { // 类的静态方法
      Session.instanceCount ++; // 调用Session.session() 后,实例数+1
      var status = false;  // 实例的私有成员
      return {
          create: function (name, pwd) { // 实例的公有方法
              status = ('admin' == name && '123456' == pwd);
              return status;
          }
      };
  },
  getInstanceCount: function() { // 类的静态方法
    return Session.instanceCount; // 只是对标Java风格,实际instanceCount并非私有
  }
};

var session = Session.session();
Session.getInstanceCount();

类的继承

类的继承依然可以用前面讲过的方式:

var SessionSub = {
    sessionSub: (function(parent) { // 这段代码前面讲过
        parent.currentUser = function() {
          return '{}';
        }
        return parent;
    })(Session); // 把超类作为参数
};

SessionSub sessionSub = SessionSub.sessionSub();
sessionSub.currentUser();

6. 包命名空间

前面我们讲闭包的时候,提到了它类似Java的package机制。现在我们把前面的闭包和这里的面向对象(实际上这里面也是有闭包,只是我们前面自己把闭包的定义弄成了以匿名函数的立即调用为前提,主要是为了初级接触时,能记住和理解,对概念咬文嚼字,笔者认为没有太必要,毕竟使用javascript是一种技能,并非一种理论)组合起来运用,详细代码见C6PackageSession.js

以下我们定义一个package,名字叫io_example_passport,它下面有两个类:SessionUser

var io_example_passport = (function () { // 依然是匿名函数的立即调用
    return {
        Session: { // 类似Java的类
            instanceCount: 0, // 类的静态成员 (无法私有化)
            session: function () { // 类的静态方法
                Session.instanceCount++; // 调用Session.session() 后,实例数+1
                var status = false;  // 实例的私有成员
                return {
                    create: function (name, pwd) { // 实例的公有方法
                        status = ('admin' == name && '123456' == pwd);
                        return status;
                    },
                    isActive: function() {
                        return status;
                    }
                };

            },

            getInstanceCount: function () { // 类的静态方法
                return Session.instanceCount; // 只是对标Java风格,实际instanceCount并非私有
            }
        },

        User: { // 类似Java的类
            user: function(session) { // 带参数的构造函数
                var priSession = session; // 私有成员
                var user = {};
                user.getUser = function () {
                    if (priSession.isActive) {
                        return "{'name': 'zhangsan', 'uid': 514233}";
                    } else {
                        throw 'Please login first';
                    }
                };
                user.getSession = function () {
                    return priSession;
                };
                return user;
            }
        }
    }

})();

如何使用上面的代码呢?跟Java还是比较相似的:

// Session类
var Session = io_example_passport.Session;
var session1 = Session.session();
var session2 = Session.session();
console.log('实例个数:' + Session.getInstanceCount());
var s1Passed = session1.create('guest', 'guest');
console.log('s1 登陆结果:' + s1Passed);
var s2Passed = session2.create('admin', '123456');
console.log('s2 登陆结果:' + s2Passed);
console.log('s2 登陆状态:' + session2.isActive());


// User 类
var User = io_example_passport.User;
var user = User.user(session2);
var currentUser = user.getUser();
console.log('current user on session2: ' + currentUser);
console.log('当前用户依然保持登陆吗?' + user.getSession().isActive());

运行结果:

$ node C6PackageSession.js
实例个数:2
s1 登陆结果:false
s2 登陆结果:true
s2 登陆状态:true
current user on session2: {'name': 'zhangsan', 'uid': 514233}
当前用户依然保持登陆吗?true

虽然这里是通过node C6PackageSession.js来运行的,其实您也可以通过浏览器运行,用浏览器直接打开C6PackageSession.html即可。

C6PackageSession.html

7. 回归总结

本文重点讲解了javascript的函数与对象的特点,并由它们的相互纠缠(即:返回对象的函数,返回函数的函数,对象属性是一个函数)引出了闭包的概念。我们把闭包不严谨的定义为:匿名函数的立即调用;调用返回的是一个对象;对象的某些属性是一个函数。,然后再由匿名函数的一次调用的缺陷,重构成命名函数,Vertx.vertx()风格,从而实现了类似Java的面向对象。最后,把闭包和面向对象再组合了一次,引入了package命名空间机制。

当然javascript是发展的,关于模块化与面向对象的机制,还有prototype机制,还有ES6直接从语言层面引入了类机制。另外一个是,借助webpack这种工具,可以更好的模块化,然后“编译”生成一个单一的js文件。您完全没有听错,当项目规模大的时候,为了更好的模块化,即便是javascript动态脚本语言,都需要类似Java语言一样去“编译”(可能的工作包括把多个JS文件合并成一个JS文件;也可以用类JS语言写,比如CoffeJS,然后翻译成纯JS;还可以压缩最小化等)。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念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

推荐阅读更多精彩内容

  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 31,560评论 18 399
  • 从三月份找实习到现在,面了一些公司,挂了不少,但最终还是拿到小米、百度、阿里、京东、新浪、CVTE、乐视家的研发岗...
    时芥蓝阅读 42,169评论 11 349
  • 余老师领读《大学》,第一讲原文解释,第二讲理解基础上提出问题。我做了如下思考: 1.小李在单位跟同事相处不好,该怎...
    一说就笑阅读 243评论 1 1
  • 不知何时起,怀旧之风似乎日渐盛行。怀念儿时风物,追忆悠游时光.照片里的故事,泛黄的老照片,回忆曾经的往昔。朋友圈...
    yanqiong阅读 398评论 0 0
  • 条件编译 1、#if, #elif, #else, #endif#if条件1代码段1#elif 条件2代码段2.....
    秋灯锁忆阅读 99评论 0 0