JSPatch 基础用法

原文: JSPatch 基础用法

JSPatch Platform


索引

  1. Request
  2. 调用 OC 方法
    2.1 调用类方法
    2.2 调用实例方法
    2.3 参数传递
    2.4 Property
    2.5 方法名转换
  3. defineClass
    3.1 API
    3.2 覆盖方法
    3.3 覆盖类方法
    3.4 覆盖 Category 方法
    3.5 Super
    3.6 Property
    3.7 获取/修改 OC 定义的 Property
    3.8 动态新增 Property
    3.9 私有成员变量
    3.10 添加新方法
    3.11 Protocol
  4. 特殊类型
    4.1 Struct
    4.2 Selector
    4.3 nil
  5. NSArray / NSString / NSDictionary
  6. Block
    6.1 Block 传递
    6.2 Block 里使用 Self 变量
    6.3 限制
  7. __weak / __strong
  8. GCD
  9. 传递 id* 参数
  10. 常亮, 枚举, 宏定义, 全局变量
    10.1 常量, 枚举
    10.2 宏定义
    10.3 全局变量
  11. Swift
  12. 加载动态库
  13. 调试

<br />

Require


在使用 Objective-C 类之前需要调用 require('className’):

require('UIView')
var view = UIView.alloc().init()

可以使用逗号,分隔, 一次性导入多个类:

require('UIView, UIColor')
var view = UIView.alloc().init()
var red = UIColor.redColor()

或者直接在使用时才调用 require():

require('UIView').alloc().init()

<br />

调用 OC 方法


调用类方法
var redColor = UIColor.redColor();
调用实例方法
var view = UIView.alloc().init();
view.setNeedsLayout();
参数传递

跟在 OC 一样传递参数:

var view = UIView.alloc().init();
var superView = UIView.alloc().init()
superView.addSubview(view)
Property

获取/修改 Property 等于调用这个 Property 的 getter / setter 方法, 获取时记得加():

view.setBackgroundColor(redColor);
var bgColor = view.backgroundColor();
方法名转换

多参数方法名使用 _ 分隔, 参数使用 , 分隔:

var indexPath = require('NSIndexPath').indexPathForRow_inSection(0, 1);

若原 OC 方法名里包含下划线_, 在 JS 使用双下划线 __ 代替:

// Obj-C: [JPObject _privateMethod];
JPObject.__privateMethod()

<br />

defineClass


API

defineClass(classDeclaration, [properties,] instanceMethods, classMethods)
@param classDeclaration: 字符串,类名/父类名和Protocol
@param properties: 新增property,字符串数组,可省略
@param instanceMethods: 要添加或覆盖的实例方法
@param classMethods: 要添加或覆盖的类方法

覆盖方法

在 defineClass 里面定义 OC 已存在的方法即可覆盖, 方法名规则与调用规则一样,使用 _ 分隔:

// OC
@implementation JPTestObject
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
  // Code Here
}
@end
// JS
defineClass("JPTableViewController", { 
  tableView_didSelectRowAtIndexPath: function(tableView, indexPath) { 
    ... 
  },
})

使用双下划线 __ 代表原 OC 方法名里的下划线 _:

// OC
@implementation JPTableViewController
- (NSArray *) _dataSource {
  // Code Here
}
@end
// JS
defineClass("JPTableViewController", { 
  __dataSource: function() {
      // Code Here
   },
})

在方法名前加 ORIG 即可调用未覆盖之前的 OC 原方法:

// OC
@implementation JPTableViewController
- (void)viewDidLoad {
  // Code Here
}
@end
// JS
defineClass("JPTableViewController", { 
  viewDidLoad: function() { 
    self.ORIGviewDidLoad(); 
  },
})
覆盖类方法

defineClass() 第三个参数就是要添加或覆盖的类方法,规则与上述覆盖实例方法一致:

// OC
@implementation JPTestObject
+ (void)shareInstance {
  // Code Here
}
@end
// JS
defineClass("JPTableViewController", 
{
        //实例方法
}, 
{ 
        //类方法 
      shareInstance: function() {
         ... 
      },
})
覆盖 Category 方法

覆盖 Category 方法与覆盖普通方法一样:

@implementation UIView (custom)
- (void)methodA {
  // Code Here
}

+ (void)clsMethodB {
  // Code Here
}
@end
defineClass('UIView', 
{ 
  methodA: function() { 
    // Code Here
  }
},
 { 
  clsMethodB: function() {
    // Code Here
  }
});
Super

使用 self.super() 接口代表 super 关键字, 调用 super 方法:

// JS
defineClass("JPTableViewController", 
{ 
  viewDidLoad: function()
  { 
      self.super().viewDidLoad();
  }
})
Property
获取/修改 OC 定义的 Property

用调用 getter / setter 的方式获取/修改已在 OC 定义的 Property

// OC
@interface JPTableViewController

@property (nonatomic) NSArray *data;

@end

@implementation JPTableViewController

@end
// JSdefineClass("JPTableViewController", 
{
   viewDidLoad: function() {
     var data = self.data(); //get property value 
     self.setData(data.toJS().push("JSPatch"));  //set property value 
  },
})
动态新增 Property

可以在 defineClass() 第二个参数为类新增 property,格式为字符串数组,使用时与 OC property 接口一致:

defineClass("JPTableViewController", ['data', 'totalCount'],
{ 
  init: function() 
  { 
    self = self.super().init() 
    self.setData(["a", "b"]) //添加新的 Property (id data) 
    self.setTotalCount(2) 
    return self 
  },
  viewDidLoad: function() 
  { 
    var data = self.data() //获取 Property 值 
    var totalCount = self.totalCount() 
  },
})
私有成员变量

使用 valueForKey()setValue_forKey() 获取/修改私有成员变量:

// OC
@implementation JPTableViewController
{ 
  NSArray *_data;
}
@end
// JS
defineClass("JPTableViewController",
 { 
  viewDidLoad: function()
   { 
    var data = self.valueForKey("_data") //get member variables 
    self.setValue_forKey(["JSPatch"], "_data") //set member variables
  },
})
添加新方法

可以给一个类随意添加 OC 未定义的方法,但所有的参数类型都是 id :

// OC
@implementation JPTableViewController
- (void)viewDidLoad 
{ 
  NSString* data = [self dataAtIndex:@(1)]; 
  NSLog(@"%@", data); //output: Patch
}
@end
// JS
var data = ["JS", "Patch"]
defineClass("JPTableViewController", 
{ 
  dataAtIndex: function(idx) 
  { 
    return idx < data.length ? data[idx]: "" 
  }
})
Protocol

可以在定义时让一个类实现某些 Protocol 接口,写法跟 OC 一样:

defineClass("JPViewController: UIViewController<UIScrollViewDelegate, UITextViewDelegate>", 
{
  // Code Here
})

这样做的作用是,当添加 Protocol 里定义的方法,而类里没有实现的方法时,参数类型不再全是 id ,而是自动转为 Protocol 里定义的类型:

@protocol UIAlertViewDelegate <NSObject>
...
- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex;
...
@end
defineClass("JPViewController: UIViewController <UIAlertViewDelegate>", 
{
   viewDidAppear: function(animated) 
  { 
    var alertView = require('UIAlertView') 
        .alloc() 
        .initWithTitle_message_delegate_cancelButtonTitle_otherButtonTitles( 
          "Alert", 
          self.dataSource().objectAtIndex(indexPath.row()), 
          self, 
          "OK", 
          null ) 
     alertView.show()
  } 

  alertView_clickedButtonAtIndex: function(alertView, buttonIndex) 
  { 
    console.log('clicked index ' + buttonIndex) 
  }
})

<br />

特殊类型


Struct

JSPatch原生支持 CGRect / CGPoint / CGSize / NSRange 这四个 struct 类型,用 JS 对象表示:

// Obj-C
UIView *view = [[UIView alloc] initWithFrame:CGRectMake(20, 20, 100, 100)];
[view setCenter:CGPointMake(10,10)];
[view sizeThatFits:CGSizeMake(100, 100)];
CGFloat x = view.frame.origin.x;
NSRange range = NSMakeRange(0, 1);
// JS
var view = UIView.alloc().initWithFrame({x:20, y:20, width:100, height:100})
view.setCenter({x: 10, y: 10})
view.sizeThatFits({width: 100, height:100})
var x = view.frame().x
var range = {location: 0, length: 1}

其他 Struct 类型的支持请参照 添加 Struct 类型支持

Selector

在JS使用字符串代表 Selector:

/Obj-C
[self performSelector:@selector(viewWillAppear:) withObject:@(YES)];
//JS
self.performSelector_withObject("viewWillAppear:", 1)
nil

JS 上的 nullundefined 都代表 OC 的 nil ,如果要表示 NSNull , 用 nsnull 代替,如果要表示 NULL , 也用 null 代替:

//Obj-C
@implemention JPTestObject
+ (BOOL)testNull(NSNull *null) 
{ 
  return [null isKindOfClass:[NSNull class]];
}
@end
//JS
require('JPTestObject').testNull(nsnull) //return 1
require('JPTestObject').testNull(null) //return 0

在JS里面判断是否为空要判断false:

var url = "";
var rawData = NSData.dataWithContentsOfURL(NSURL.URLWithString(url));
if (rawData != null) {} //这样判断是错误的

// 应该如下判断:
if (!rawData){}
// 在JSPatch.js源码里_formatOCToJS方法对undefined,null,isNil转换成了false。

<br />

NSArray / NSString / NSDictionary


NSArray / NSString / NSDictionary 不会自动转成对应的JS类型,像普通 NSObject 一样使用它们:

//Obj-C
@implementation JPObject
+ (NSArray *)data
{ 
  return @[[NSMutableString stringWithString:@"JS"]];
}

+ (NSMutableDictionary *)dict
{ 
  return [[NSMutableDictionary alloc] init];
}

@end
// JS
require('JPObject')var 
ocStr = JPObject.data().objectAtIndex(0)
ocStr.appendString("Patch")

var dict = JPObject.dict()
dict.setObject_forKey(ocStr, 'name')
console.log(dict.objectForKey('name')) //output: JSPatch

如果要把 NSArray / NSString / NSDictionary 转为对应的 JS 类型,使用 .toJS() 接口:

// JS
var data = require('JPObject').data().toJS()
//data instanceof Array === true
data.push("Patch")

var dict = JPObject.dict()
dict.setObject_forKey(data.join(''), 'name')
dict = dict.toJS()
console.log(dict['name']) //output: JSPatch

<br />

Block


Block 传递

当要把 JS 函数作为 block 参数给 OC时,需要先使用 block(paramTypes, function) 接口包装:

// Obj-C
@implementation JPObject
+ (void)request:(void(^)(NSString *content, BOOL success))callback
{ 
  callback(@"I'm content", YES);
}
@end
// JS
require('JPObject').request(block("NSString *, BOOL", function(ctn, succ)
{ 
  if (succ) log(ctn) //output: I'm content
}))

这里 block 里的参数类型用字符串表示,写上这个 block 各个参数的类型,用逗号分隔。NSObject 对象如 NSString *, NSArray *等可以用 id 表示,但 block 对象要用 NSBlock* 表示。
<br />
从 OC 返回给 JS 的 block 会自动转为 JS function,直接调用即可:

// Obj-C
@implementation JPObject

typedef void (^JSBlock)(NSDictionary *dict);

+ (JSBlock)genBlock
{ 
  NSString *ctn = @"JSPatch";
   JSBlock block = ^(NSDictionary *dict) { 
    NSLog(@"I'm %@, version: %@", ctn, dict[@"v"]) ;
  }; 
  return block;
}
+ (void)execBlock:(JSBlock)blk
{
  // Code Here
}
@end
// JS
var blk = require('JPObject').genBlock();
blk({v: "0.0.1"}); //output: I'm JSPatch, version: 0.0.1

若要把这个从 OC 传过来的 block 再传回给 OC,同样需要再用 block() 包装,因为这里 blk 已经是一个普通的 JS function,跟我们上面定义的 JS function 没有区别:

// JS
var blk = require('JPObject').genBlock();
blk({v: "0.0.1"}); //output: I'm JSPatch, version: 0.0.1
require('JPObject').execBlock(block("id", blk));

总结:JS 没有 block 类型的变量,OC 的 block 对象传到 JS 会变成 JS function,所有要从 JS 传 block 给 OC 都需要用 block() 接口包装。

Block 里使用 Self 变量

在 block 里无法使用 self 变量,需要在进入 block 之前使用临时变量保存它:

defineClass("JPViewController", 
{ 
  viewDidLoad: function() 
  { 
    var slf = self; 
    require("JPTestObject").callBlock(block(function() 
    { 
      //`self` is not available here, use `slf` instead. 
      slf.doSomething(); 
    }); 
  }
}
限制

从 JS 传 block 到 OC,有两个限制:
A. block 参数个数最多支持6个。(若需要支持更多,可以修改源码)
B. block 参数类型不能是 double
<br />
另外不支持 JS 封装的 block 传到 OC 再传回 JS 去调用 (原因: issue #155)

- (void)callBlock:(void(^)(NSString *str))block
{
  // Code Here
}
defineClass('JPTestObject', 
{ 
  run: function() 
  { 
    self.callBlock(block('NSString*', function(str) 
    { 
      console.log(str); 
    })); 
  }, 

  callBlock: function(blk) 
  { 
    //blk 这个 block 是上面的 run 函数里 JS 传到 OC 再传过来的,无法调用。 
    blk("test block"); 
  }
});

<br />

__weak / __strong


可以在 JS 通过 __weak() 声明一个 weak 变量,主要用于避免循环引用。
例如我们在 OC 里为了避免 block 导致的循环引用,经常这样写:

- (void)test 
{ 
  __weak id weakSelf = self; 
  [self setCompleteBlock:^() { 
    [weakSelf blabla]; 
  }]
}

在 JS 对应的可以这样写:

var weakSelf = __weak(self)
self.setCompleteBlock(block(function() { 
  weakSelf.blabla();
}))

若要在使用 weakSelf 时把它变成 strong 变量,可以用 __strong() 接口:

var weakSelf = __weak(self)
self.setCompleteBlock(block(function() { 
  var strongSelf = __strong(weakSelf) 
  strongSelf.blabla();
}))

<br />

GCD


使用 dispatch_after() dispatch_async_main() dispatch_sync_main() dispatch_async_global_queue() 接口调用GCD方法:

// Obj-C
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ 
  // do something
});

dispatch_async(dispatch_get_main_queue(), ^{ 
  // do something
});
// JS
dispatch_after(1.0, function() { 
  // do something
})

dispatch_async_main(function() { 
  // do something
})

dispatch_sync_main(function() { 
  // do something
})

dispatch_async_global_queue(function() { 
  // do something
})

<br />

传递 id* 参数


如果你需要传递 id* 参数,像 NSURLConnection 里的这个接口里的 NSError ** :

+ (NSData *)sendSynchronousRequest:(NSURLRequest *)request returningResponse:(NSURLResponse **)response error:(NSError **)error;

这里传入的是一个指向 NSObject 对象的指针,在方法里可以修改这个指针指向的对象,调用后外部可以拿到新指向的对象,对于这样的参数,首先需要引入 JPMemory 扩展,然后按以下步骤进行传递和获取:

  1. 使用 malloc(sizeof(id)) 创建一个指针
  2. 把指针作为参数传给方法
  3. 方法调用完, 使用 pval() 拿到指针新指向的对象
  4. 使用完后调用 releaseTmpObj() 释放这个对象
  5. 使用 free() 释放指针

举个例子:

//OC
- (void)testPointer:(NSError **)error { 
  NSError *err = [[NSError alloc]initWithDomain:@"com.jspatch" code:42 userInfo:nil];
  *error = err;
}
//JS
//malloc() pval() free() is provided by JPMemory extension
require('JPEngine').addExtensions(['JPMemory'])

var pError = malloc(sizeof("id"))
self.testPointer(pError)
var error = pval(pError)

if (!error) { 
  console.log("success")
} else { 
  console.log(error)
}

releaseTmpObj(pError)
free(pError)

若反过来你想在 JS 替换上述 -testPointer: 方法,构建 NSError 对象赋给传进来的指针,可以这样写:

defineClass('JPClassName', 
{ 
  testPointer: function(error)
  { 
    var tmp = require('NSError').errorWithDomain_code_userInfo("test", 1, null); 
    var newErrorPointer = getPointer(tmp) 
    memcpy(error, newErrorPointer, sizeof('id')) 
  }
});

<br />

常亮, 枚举, 宏定义, 全局变量


常量, 枚举

Objective-C 里的常量/枚举不能直接在 JS 上使用,可以直接在 JS 上用具体值代替:

//OC
[btn addTarget:self action:@selector(handleBtn) forControlEvents:UIControlEventTouchUpInside];
//UIControlEventTouchUpInside的值是1<<6
btn.addTarget_action_forControlEvents(self, "handleBtn", 1<<6);

或者在 JS 上重新定义同名的全局变量:

//js
var UIControlEventTouchUpInside = 1 << 6;
btn.addTarget_action_forControlEvents(self, "handleBtn", UIControlEventTouchUpInside);

有些常量字符串,需要在 OC 用 NSLog 打出看看它的值是什么:

//OC
[[NSAttributedString alloc].initWithString:@"str" attributes:@{NSForegroundColorAttributeName: [UIColor redColor]];

上面代码中 NSForegroundColorAttributeName 是一个静态字符串常量,源码里看不出它的值,可以先用 NSLog 打出它的值再直接写在 JS 上:

//OC
NSLog(@"%@", NSForegroundColorAttributeName) //output 'NSColor'
NSAttributedString.alloc().initWithString_attributes("无效啊", {'NSColor': UIColor.redColor()});
宏定义

Objective-C 里的宏同样不能直接在 JS 上使用。若定义的宏是一个值,可以在 JS 定义同样的全局变量代替,若定义的宏是程序,可以在JS展开宏:

#define TABBAR_HEIGHT 40
#define SCREEN_WIDTH [[UIScreen mainScreen] bounds].size.height
[view setWidth:SCREEN_WIDTH height:TABBAR_HEIGHT];
//JS
view.setWidth_height(UIScreen.mainScreen().bounds().height, 40);

若宏的值是某些在底层才能获取到的值,例如 CGFLOAT_MIN ,可以通过在某个类或实例方法里将它返回,或者用添加扩展的方式提供支持:

@implementation JPMacroSupport
+ (void)main:(JSContext *)context
{ 
  context[@"CGFLOAT_MIN"] = ^CGFloat() { 
    return CGFLOAT_MIN; 
  }
}
@end
全局变量

在类里定义的 static 全局变量无法在 JS 上获取到,若要在 JS 拿到这个变量,需要在 OC 有类方法或实例方法把它返回:

static NSString *name;
@implementation JPTestObject
+ (NSString *)name
{ 
  return name;
}
@end
var name = JPTestObject.name() //拿到全局变量值

<br />

Swift


使用 defineClass() 覆盖 Swift 类时,类名应为 项目名.原类名 ,例如项目 demo 里用 Swift 定义了 ViewController 类,在 JS 覆盖这个类方法时要这样写:

defineClass('demo.ViewController', {})

对于调用已在 swift 定义好的类,也是一样:

require('demo.ViewController')

需要注意几点:

  1. 只支持调用继承自 NSObject 的 Swift 类
  2. 继承自 NSObject 的 Swift 类,其继承自父类的方法和属性可以在 JS 调用,其他自定义方法和属性同样需要加 dynamic 关键字才行。
  3. 若方法的参数/属性类型为 Swift 特有(如 Character / Tuple),则此方法和属性无法通过 JS 调用。
  4. Swift 项目在 JSPatch 新增类与 OC 无异,可以正常使用。
    详见这篇文章

<br />

加载动态库


对于 iOS 内置的动态库,若原 APP 里没有加载,可以通过以下方式动态加载,以加载 SafariServices.framework 为例:

var bundle = NSBundle.bundleWithPath("/System/Library/Frameworks/SafariServices.framework");
bundle.load();

加载后就可以使用 SafariServices.framework 了。

<br />

调试


可以使用 console.log() 打印一个对象,作用相当于 NSLog() ,会直接在 XCode 控制台打出。console.log() 支持任意参数,但不支持像 NSLog 这样 NSLog(@"num:%f", 1.0) 的拼接:

var view = UIView.alloc().init();
var str = "test";
var num = 1;
console.log(view, str, num)
console.log(str + num);   //直接在JS拼接字符串

也可以通过 Safari 的调试工具对 JS 进行断点调试,详见 JS 断点调试


Lemon龙说:

如果您在文章中看到了错误 或 误导大家的地方, 请您帮我指出, 我会尽快更改

如果您有什么疑问或者不懂的地方, 请留言给我, 我会尽快回复您

如果您觉得本文对您有所帮助, 您的喜欢是对我最大的鼓励

如果您有好的文章, 可以投稿给我, 让更多的 iOS Developer 在简书这个平台能够更快速的成长

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

推荐阅读更多精彩内容

  • JSPatch 1.打印console.log(); 2.Protocol 3.Masonry使用JSPatch如...
    BestJoker阅读 1,533评论 5 9
  • 6.1殷丹种子实践 感恩: 感恩叶武滨老师讲的时间管理。最近逐步的应用在生活和工作中,感觉思路清晰了很多 感恩同事...
    殷丹阅读 191评论 0 0
  • 不要说古诗词离我们有多远,平时用不上,谁说的!这不,中秋佳节又至,与家人亲朋团聚把盏言欢、赏月吃饼之际,不正是吟诵...
    可闻桃杏香阅读 3,011评论 2 6
  • 今天下午去给女儿送饭看见女儿捂着肚子说不舒服(女儿生理期)我认为那动作声音都有点过,再加上看见她外套也不穿就又些控...
    Q3陈翠玲阅读 195评论 0 0
  • 在当下这个和平年代,娃娃们最好的挫折教育,我认为就是体育锻炼! 今天,我要跟大家分享一件我很骄傲的事情,就是我把娃...
    笨妈先飞阅读 1,669评论 0 0