《Javascript语言精髓》读书笔记

终于开始读蝴蝶书,记录一下阅读中的体会

JavaScript语言精髓

第一章 精华

每种语言都有精华和鸡肋,但是标准委员会不会移除语言中鸡肋的部分,因为这样会损害所有依赖鸡肋部分的程序。

但是,我们可以定义自己的子集,基于精华部分去编写更好的程序。

为什么使用JavaScript

它与浏览器结合使它成为世界上最流行的编程语言,但浏览器的API和DOM相当糟糕,导致处理DOM是一件痛苦的事情,因为它的规范制定得很拙劣且实现相互不一致。所以本书只讲Javascript语言本身,并不涉及DOM。

关于DOM可以看《JavaScript DOM编程艺术》

第二章 语法

本章介绍Javascript的精华部分的语法,并简要概述语言结构。

标识符

标识符由一个字母开头,其后可选择性地加上一个或多个字母、数字或下划线。标识符不能使用下面这些保留字:

abstract
boolean break byte
case catch char class const continue
debugger default delete do double
else enum export extends
false final finally float for function
goto
if implements import in instanceof int interface
long
native new null
package private protected public
return
short static super switch synchronized
this throw throws transient true try typeof
var volatile void
while with

JavaScript不允许使用保留字来命名变量或参数,也不允许在对象字面量中,或者在一个属性存取表达式的点号之后,使用保留字作为对象的属性名。

数字

Javascript只有单一的数字类型,它在内部被表示为64位的浮点数。和大多数语言不同,它没有区分整数和浮点数。

  • 指数
    『e之前的部分乘以10的e之后部分的次方』,如1e2 -> 100
  • 负数
    - 前缀构成
  • NaN
    • 是一个数值,表示一个不能产生正常结果的运算结果
    • NaN不等于任何值,包括它自己
    • 可以用 isNaN(number) 检测 NaN
  • Infinity
    表示所有大于 1.79769313486231570e308 的值

字符串

  1. 字符串可以被包围在 '" 中,可能包含0个或多个字符。 \
    是转义符。

  2. JavaScript没有字符类型,要表示一个字符,创建一个包含一个字符的字符串就行。

  3. 字符串可以通过 + 连接其他字符串

    'a'+'b'+'c' === 'abc'   //true
    
  4. 字符串有 length 属性

语句

一个编译单元包含一组可执行的语句,浏览器中每个 <script> 都提供一个被编译且立即执行的编译单元。因为缺少链接器,JavaScript把它们一起抛入一个公共的全局对象(web 中这个全局对象是 window)中。

链接器:一个程序,将一个或多个由编译器或汇编器生成的目标文件外加库链接为一个可执行文件。

简单地说,链接器的工作就是解析未定义的符号引用,将目标文件中的占位符替换为符号的地址。

代码块是包在一对花括号中的一组语句。不像其他语言,JavaScript中的代码块不会创建一个新的作用域。

表达式

最简单的表达式:

  • 字面量值:比如字符串或数字
  • 变量
  • 内置的值:true、false、null、undefined、NaN、Infinity
  • new 为前导的调用表达式
  • delete 前导的属性存取表达式
  • 包在圆括号中的表达式
  • 以一个前缀运算符作为前导的表达式
  • 表达式后面跟着:
    • 一个插入运算符与另一个表达式
    • 三元运算符 ? 后跟着另一个表达式,之后 : 再接着第三个表达式
    • 一个函数调用
    • 一个属性存取表达式

第三章 对象

JavaScript简单类型有数字、字符串、布尔值、null、undefined,其他所有的值都是对象(数组、函数、正则表达式都是对象)。

数字、字符串、布尔值虽然拥有方法(包装对象),但并不是对象。

包装对象:

每当读取一个基本类型值的时候,后台会创建一个对象的基本包装类型的对象,从而能够调用一些方法来操作这些数据。

var s1 = 'abcdefg' ;
var s2 = s1.substring(2) ;

后台自动完成下列处理:

  1. 创建String类型的一个实例
  2. 在实例上调用指定的方法
  3. 销毁这个实例

所以上面代码等同于:

var s1 = new String('abcdefg') ;
var s2 = s1.substring(2) ;
s1 = null ;

对象字面量

var flight = {
    airline: "Oceanic",
    number: 815,
    departure: {
        IATAL: "SYD",
        time: "2004-09-22 14:55",
        city: "Sydney"
    },
    arrival: {
        IATA: "LAX",
        time: "2004-09-23 10:42",
        city: "Los Angeles"
    }
}

检索

  • [] : flight['number']
  • . : flight.number

更新

通过赋值语句更新,如果属性名已经存在于对象中,则被替换;如果对象中没有那个属性名,则添加。

stooge['first-name'] = 'Jerome'

引用

对象通过引用来传递,它们永远不会被拷贝。

var a = {
    name: 'a'
}
var b = a
b.name = 'b'
console.log(a.name)     // b

这里牵扯出 JavaScript 深拷贝和浅拷贝的问题
上例是浅拷贝

深拷贝见下:

var deepCopy= function(source) { 
    var result={};
    for (var key in source) {
      result[key] = typeof source[key]===’object’? deepCoyp(source[key]): source[key];
   } 
   return result; 
}

此时 var b = deepCopy(a) 得到的 b 就和 a 没有引用关系,即修改 b 不会影响 a

原型

每个对象都连接到一个原型对象,并且从中继承属性。所有通过对象字面量创建的对象都连接到 Object.prototype 这个JavaScript中标准的对象。

创建一个对象时,可以选择某个对象作为它的原型:

var o = {o1:1,o2:function(){alert(1)}}
function F(){}
F.prototype = o
var f = new F()

反射

使用 hasOwnProperty 检查属性是否是对象独有的,它并不会检查原型链。

flight.hasOwnProperty('number');    //true

枚举

for in 可以遍历对象中所有的属性名(见深拷贝部分)

删除

delete 可以删除对象属性,不会触及原型链中的任何对象

减少全局变量污染

最小化使用全局变量的一个方法是创建唯一一个全局变量:

var App = {}
App.stooge = {
    "first-name": "Joe",
    "last-name": "Howard"
}
App.flight = {
    airline: "Oceanic",
    number: 815
}

减少全局变量污染另一个办法是使用闭包进行信息隐藏

第四章 函数

函数包含一组语句,是Javascript的基础模块单元,用于代码复用、信息隐藏和组合调用。

一般来说,所谓编程就是将一组需求分解成一组函数与数据结构的技能

函数对象

函数就是对象,对象是键值对的集合并且拥有一个连到原型对象的隐藏连接。对象字面量产生的对象连接到 Object.prototype ,函数对象连接到 Function.prototype (该原型对象本身又连接到 Object.prototype)。

每个函数在创建时附有两个附加的隐藏属性:

  • 函数上下文
  • 实现函数行为的代码

每个函数对象在创建时也会带一个 prototype 属性,它的值是一个拥有 constructor 属性且值为该函数的对象。

函数字面量

函数对象可以通过函数字面量来创建:

var add = function(a,b){
    return a + b;
}

函数字面量包含四部分:

  1. 保留字 function
  2. 函数名,可以被省略(匿名函数)
  3. 参数,逗号分隔
  4. 花括号中的语句

函数字面量允许出现在任何允许表达式出现的地方,函数也可以被定义在其他函数中。一个内部函数可以访问自己的参数和变量,同时它也能方便地访问它被嵌套在其中的那个函数的参数和变量。通过函数字面量创建的函数对象包含一个连到外部上下文的连接,被称为闭包

调用

函数在调用的时候有两个附加参数:thisarguments

this 是调用上下文,值取决于函数调用的模式。

1.方法调用模式

一个函数被保存为对象的一个属性时,即为一个方法当一个方法被调用时,this 被绑定到该对象

var dog = {
    name : 'xxx' ,
    leg:{
        sum : 4 ,
        move:function(){
            console.log(this) ; //Object,是leg对象,而不是dog对象,下面证明了
            alert(this.name) ; //underfined
            alert(this.sum) ; //4
        }
    }
}
dog.leg.move();

2.函数调用模式

函数仅仅当做函数来调用时,this 被绑定到全局对象。

var a = 111 ;
function t1(){
    var a = 1
    function t2(){   
        console.log(this.a) //111,这其实很不合理,应该指向t2的。
    }
    t2()
}
t1()

这其实是语言设计上的一个错误,倘若语言设计正确,当内部函数被调用时,this 应该仍然绑定到外部函数的 this 变量。

3.构造器调用模式

如果一个函数前面带上 new 调用,那么将创建一个隐藏连接到该函数的 prototype 成员的新对象,同时 this 将被绑定到那个新对象上。

function Dog(name){
    this.name = name ;
}
Dog.prototype.cry = function(){
    alert(this.name)
}
var dog1 = new Dog('xxx');
dog1.cry(); // 'xxx'

4.Apply/Call调用模式

apply 接受两个参数,第一个是将被绑定给this的值,第二个就是一个参数数组。

call 与 apply 相同,不过第二个参数不是数组。

var dog = {
    leg : 4 ,
    color:'yellow'
}
var color = 'red' ;
function t(){
    alert(this.color) ;
}
t(); // red , 因为指向this在函数中调用指向window
t.call(dog); //yellow , 把t()的作用域指向了dog

再来说说 arguments,它是一个类数组对象(拥有length属性,但缺少所有数组方法)。通过它可以访问函数调用时传递给函数的参数列表。

返回

一个函数调用时,将暂停当前函数的执行,传递控制权和参数给新函数。它从第一个语句开始执行,并在遇到关闭函数体的 } 时结束。使得函数把控制权交还给调用该函数的程序部分。

return 语句可用来使函数提前返回,当 return 执行时,函数立即返回而不再执行余下的语句。一个函数总是会返回一个值,如果没有指定返回值,则返回 undefined

如果函数前加上 new 来调用,且返回值不是一个对象,则返回this(该新对象)。

异常

抛出异常

function add(a,b){
    if(typeof a!=='number' || typeof b!=='number'){
        throw {
            name: 'TypeError',
            message: 'add needs numbers'
        }
    }
    return a + b;
}

throw 语句中断函数的执行,抛出一个 exception 对象,该对象包含可识别异常类型的 name 属性和一个描述性的 message 属性。

exception 对象将被传递到一个 try 语句的 catch 从句:

try{
    add('seven')
}catch(e){
    console.log(e.name)
    console.log(e.message)
}

如果在 try 代码块中抛出异常,控制权就跳转到其 catch 从句。

给类型增加方法

Number.prototype.integer = function(){
    return Math[this < 0 ? 'ceiling' : 'floor'](this) //this指向实例
}

var num = 10/3
console.log(num.integer())  ;   // 3

作用域

作用域空间那个值变量和参数的可见性和生命周期,对程序员来说很重要,因为它减少了命名冲突,并且提供了自动内存管理。

JavaScript没有块级作用域,却有函数作用域:定义在函数中的参数和变量在函数外部是不可见的,而且在一个函数中的任何位置定义的变量在该函数中任何地方都可见。

下面这个例子与以对象字面量初始化对象不同,通过调用一个函数形式去初始化对象,返回一个对象字面量。此函数定义了一个 val 变量,该变量对 addValgetVal 总是可用的,但函数的作用域使得其对其他程序来说是不可见的。

// 从设计模式的角度来说这是模块模式
var o = (function(){
    var val = 0;
    return {
        addVal: function(){
            val += 1
        },
        getVal: function(){
            console.log(val)
        }
    }
})()

联想到之前我做的一个小游戏,是20秒内完成任务,使用 restTime 做倒计时变量。后来同事把restTime修改了,成绩贼高。最后我就是用这种办法把 restTimeval 一样隐藏了起来。

闭包

作用域的好处是内部函数可以访问定义它们的外部函数的参数和变量(除了this和arguments)。

var o = function(){
    var val = 0;
    return {
        addVal: function(){
            val += 1
        },
        getVal: function(){
            console.log(val)
        }
    }
}
var oo = o()
oo.addVal()
oo.addVal()
oo.getVal() // 2

当调用 o 时,返回一个包含addValgetVal的新对象,该对象的引用保存在 oo 中。虽然 o 返回了,但是 ooaddValgetVal有访问 val 的特权,它们可以访问被创建时所处的上下文,这就是闭包

模块

可以使用函数和闭包来构造模块。模块是一个提供接口却隐藏状态与实现的函数或对象。

具体见『作用域』部分-模块模式

第五章 继承

当一个函数对象被创建时,Function 构造器产生的函数对象会运行类似这样的代码:

this.prototype = {constructor: this}

每个函数都会得到一个 prototype 对象,其值是包含一个 constructor 属性且属性值为该新函数对象。该 prototype 对象是存放继承特征的地方。

第六章 数组

区分数组和对象

一个常见的错误是在须使用数组时使用了对象,在须使用对象时使用了数组。其实规则很简单:当属性名是小而连续的整数时,用数组,否则就用对象。

JavaScript中数组 typeof 返回是 object,这并不能区分数组和对象。

var is_array = function(value){
    return value && 
        typeof value === 'object' &&
        typeof value.length === 'number' &&
        typeof value.splice === 'function' &&
        !(value.propertyIsEnumerable('length'))
}
  • 首先,我们判断这个值是否为真,我们不接受 null 和其他为假的值。
  • 其次,我们判断这个值的 typeof 运算结果是否是 object 。对于对象、数组和null来说,将得到 true
  • 第三,我们判断这个值是否有一个值为数字的 length 属性,对于数组是 true ,对于对象则为 false
  • 第四,判断这个值是否包含一个 splice 方法。对于数组,返回 true
  • 最后,我们判断 length 属性是否是可枚举的

这真的很复杂,实际上,我一直是这样用的,什么类型都能检测,堪称万能:

var toString = Object.prototype.toString
function isObject(obj) {
  return toString.call(obj) === "[object Object]"
}
function isString(obj) {
  return toString.call(obj) === "[object String]"
}
function isArray(obj) {
  return toString.call(obj) === "[object Array]"
}
function isFunction(obj) {
  return toString.call(obj) === "[object Function]"
}

具体见《Sea.js 源码解析(三)》

第七章 正则表达式

没说什么,都是基本语法,略过

第八章 方法

Array

concat(item...)

返回一个新数组,并不会修改原数组

var a1 = [2,3]
var a2 = [332,12]
console.log( a1.concat(a2) ); //[ 2, 3, 332, 12 ]

join(separator)

把一个 array 构造成一个字符串,并用 separator 作为分隔符把它们连接在一起。

pop(item...)

移除数组中最后一个元素并返回该元素

push(item...)

将一个或多个元素添加到数组尾部,会修改原数组

reverse()

反转数组中元素的顺序,会修改原数组,返回当前数组

shift()

移除数组中第一个元素

slice(start,end)

从start开始,到end为止(不包括end,可选,默认值是length)复制数组

sort(comparefn)

对数组中的内容排序,并不能给数字排序,因为默认比较函数是假定要被排序的元素都是字符串。

比较函数接受两个参数,并且如果两个参数相等返回0,如果第一个参数应该排在前面,则返回一个负数,如果第二个参数应该排在前面,则返回一个正数。

// a 比 b小时返回 -1,而且根据上述规则 a 会排在前面
// 所以这是从小到大的排序

var arr = [1,123,341,34,123]
arr.sort(function(a,b){
    if(a==b){
        return 0
    }else{
        return a < b ? -1 : 1 
    }
})

splice(start,deleteCount,item...)

splice 从数组中移除一个或多个元素,并用新的item代替他们。

start是从数组中移除元素的开始位置,deleteCount是要移除的个数。

会修改原数组,返回一个包含被移除元素的数组。

var arr = [23,3,23,2]
var b = arr.splice(0,1)

console.log(arr)     ;  //[ 3, 23, 2 ]
console.log(b) ;    //[ 23 ]

deleteCount 为0时,则为添加新元素:

var arr = [23,3,23,2]
var b = arr.splice(1,0,'aa')

console.log(arr)        // [ 23, 'aa', 3, 23, 2 ]
console.log(b)      // []

deleteCount 与 item的个数相等时,则为替换:

var arr = [23,3,23,2]
var b = arr.splice(1,1,'aa')

console.log(arr)        //[ 23, 'aa', 23, 2 ]
console.log(b)      //[ 3 ]

unshift(item...)

将item从数组头部插入数组

Function

apply(thisArg,argArray)

见 『Apply/Call调用模式』

Number

toFixed(fractionDigits)

把这个 number 转换成一个十进制形式的字符串。可选参数 fractionDigits 控制其小数点后的数字位数。

Math.PI.toFixed(); //3
Math.PI.toFixed(2); //3.14
Math.PI.toFixed(4); //3.1415

toPrecision(precision)

toFixed ,参数控制有效数字的位数

toString()

将number转换成字符串

Object

hasOwnProperty(name)

只检查此对象中的属性,原型链中得同名属性不会被检查。如果存在此属性则返回 true

String

charAt(pos)

返回在字符串中pos处的字符

charCodeAt(pos)

返回不是一个字符串,而是以整数形式表示的字符码位

concat(string...)

与其他字符串连接起来构造一个新字符串,不常用,因为 + 也能满足需求

indexOf(searchString,pos)

在字符串内查找另一个字符串 searchString,如果被找到,则返回第一个匹配字符的位置,否则返回 -1 。

可选参数 pos 设置从字符串的某个指定位置开始查找。

lastIndexOf(searchString,pos)

与indexOf类似,不同从末尾开始查找

slice(start,end)

复制字符串的一部分构造一个新的字符串

split(separator,limit)

把字符串分割成片段创建数组,limit可限制被分割的片段数量。

一个有意思的技巧:
new Array(10).join('0').split('') 生成10个元素为0的数组

toLowerCase()

将字符串中所有字母转化为小写

toUpperCase()

将字符串中所有字母转化为大写

第九章 代码风格

其他都略去,作者提到『用一个单独的全局变量去包含一个脚本应用或工具库,每个工具库都有它自己的命名空间,所以我很容易使用对象去管理代码』。这个思想在YAHOO的YUI库中得到彻底的贯彻,在YUI中仅用到两个全局变量:YAHOOYAHOO_config

第十章 优美的特性

精简的JS里都是好东西:

  • 函数是头等对象
  • 基于原型继承的动态对象:对象是无类别的,我们可以通过普通的赋值给任何对象增加一个新成员元素,一个对象可以从另一个对象继承成员元素
  • 对象字面量和数组字面量:这对创建新对象的数组来说是十分方便的方法。JavaScript字面量是数据交换格式JSON的灵感之源。

附录 糟粕

这一部分用来吐槽JS这门语言设计上不周到的地方

全局变量

共三种方法定义全局变量:

  1. 脱离任何函数var语句 var foo = value
  2. 直接添加一个属性到全局对象中,全局对象是所有全局变量的容器。在web中,全局对象是 windowwindow.foo = value
  3. 使用未声明的变量,这被称为隐式的全局变量:foo = value

之前说过,可以通过 创建一个全局变量闭包 减少全局变量污染(注意,只是减少,没办法避免,总要有暴露出来的变量,不要钻牛角尖)。

作用域

没有块级作用域,只有函数作用域

自动插入分号

JavaScript 有一个机制,会试图通过自动插入分号来修正有缺损的程序。它有可能会掩盖更为严重的错误。

return
{
    status: true
}

看起来是返回一个对象,但是自动插入分号让它返回了undefined,这样可以避免:

return {
    status: true
}

typeof

typeof并不能正确地检测数据类型:

typeof null ; //object

所以使用 Object.prototype.toString.call(null) 这个办法就好,万能的!

附录 JSLint

JSLint,一个JavaScript语法检查器和校验器,它取得源文本并进行扫描。如果发现问题,会返回一个消息。

地址

有人认为JSLint过于严格,就有了jshint

我的总结

关于作者

道格拉斯 克罗克福德

这本书的作者是Douglas Crockford,是 JSONJSLint的发明者,ECMA JavaScript2.0标准化委员会委员,曾任Yahoo!资深JavaScript架构师,现任PayPal高级JavaScript架构师。

关于这本书

这本书被称为『蝴蝶书』,是JS的进阶书籍。我在很早之前看过一次,这两天再看,体会又不同了。因为我记性不是很好,所以赶紧记录一下读书笔记。如果以后再读,到时候也可以把这篇文章拿出来对比一下。这篇文章记录了我不是很熟东西,一些我认为不是很重要的没有记录,还用注释的方式加入了一些我的笔记。

这本书在知乎上的讨论

原文地址

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

推荐阅读更多精彩内容

  • 第5章 引用类型(返回首页) 本章内容 使用对象 创建并操作数组 理解基本的JavaScript类型 使用基本类型...
    大学一百阅读 3,211评论 0 4
  • 我笑了。 那是一个宁静的早晨,我也如平常一样,走进那平凡的教室。突然一个女孩的身影以掩耳之势闪到我的面前。...
    321e622aa58d阅读 154评论 0 0
  • 坐在开往家乡的列车上,望着窗外的风景飞快后退我心里还是有几分着急。往常火车经常会晚点,但今天不行,终点有人在等我。...
    是青鸟呀阅读 326评论 0 0
  • 【一】 爷爷得了胃癌。 在父母的遮遮掩掩下,我还是得知了这个消息。 我不知道此时家里是不是已经乱成一团糟,还是东家...
    魔女刘阅读 334评论 0 1
  • (一) 昨夜小楼雨, 游子不知寒。 阁中看玉花, 肠断忆往昔。 (二) 朝别凌烟阁, 暮落天涯月。 写意寄山水, ...
    紫雯阁阅读 294评论 0 2