前端工程师的第一个Flutter应用
强烈建议想搞Flutter的朋友,读一遍《Flutter实战》,这是Flutter中文网出的一本书,主要以入门、进阶、实例三大部分进行叙述Flutter。是我目前为数不多的成体系的Flutter中文学习指南。
更新
2019/8/28
Flutter厉害在有渲染引擎直接调用底层绘制,但是用Dart写出来的代码难看且没有可读性。布局全靠嵌套,当然这是性能的代价。
前言
人生苦短,少学一样是一样。 ----鲁迅
曾经我把鲁迅的这句名言作为座右铭,时时刻刻铭记于心。
可是没想到上了前端这条贼船之后,我幸福的留下了泪水,从 jQuery 到 AngularJS,到 Vue、React,跨端的 Weex、RN,最近又开始鼓吹 Flutter 浪潮。
公司内部孵化一个创业项目,需要做 Android 和 iOS 端。我有一个绝佳的 idea,就差一个程序员???
在技术选型阶段,从需求复杂度、需求开发周期、成本上考虑我们决定直接由前端组负责这个 App 的开发。接下来就是前端多端框架的选择,综合上手成本、性能、组件库、流行度等因素,最终选择了 uni-app作为我们的多端框架。
多端框架的对比,可以看我转的一篇文章:小程序框架全面测评
这是京东凹凸实验室,做的一份全面的测评,从各方面分析了页面多端框架的现况。
但随着业务的发展,长表单、动画、个性化功能的增加,uni-app 在性能和定制化方面渐渐满足不了产品的需求。我决定调研一下 Flutter。这也是这篇文章的由来,我的第一个 Flutter 应用。
Flutter
what is Flutter?
Flutter 是谷歌的移动 UI 框架,用于在创纪录的时间内在 iOS 和 Android 上制作高质量的原生界面。 Flutter 与现有代码一起使用,由世界各地的开发人员和组织使用,并且是免费和开源的。
why is Flutter?
为什么使用 Flutter?
摸着良心说可能有一部分原因是对 Flutter 比较好奇。但是随着对Flutter的了解,很好奇为什么Weex、RN、uni-app为什么不能像Flutter一样,也搞一套自绘引擎?Flutter算然在性能上有优势,但他的语法、生态跟Web圈子(语法脱离了JS,生态脱离了npm)是脱节的。
这就导致我在使用Flutter的过程中,需要很多新轮子,感觉很浪费时间。
如果Weex、RN、uni-app能有一套自绘引擎,会不会是更好的一个选择呢?
高性能自绘引擎
对我来说,这是我选择 Flutter 最重要的一个理由。同时支持 JIT 和 AOT
Flutter 使用 Dart 语法开发。开发阶段 JIT 模式即时编译,提高开发效率。发布阶段 AOT 模式提前编译,提升应用性能。
开发友好,得益于 JIT
嗯,热重载。这个。。。可能原生开发会比较爽。作为一个页面仔,前端工程基本都是所见即所得。
Dart:强类型语言
支持类型检查,编译前提前发现错误。
介绍
仓库地址:cnode_flutter
环境安装
flutter-io.cn
是 Flutter 官方的中文站点
安装说明:https://flutter-io.cn/docs/get-started/install
flutterchina.club
是 Flutter 中文开发者社区的开源项目。
安装说明:https://book.flutterchina.club/chapter1/install_flutter.html
目录结构
新建完Flutter工程后,有一个默认的计数器Demo,代码在lib/main.dart
文件中。
接下来我们大部分的工作都在lib
目录下完成。
cnode_flutter|-- android|-- build|-- ios|-- lib|-- model|-- model.dart // provider的model|-- pages|-- article.dart // 详情|-- drawer.dart // 抽屉|-- home.dart // 列表|-- services|-- apis.dart // httpPath|-- index.dart // httpAction|-- main.dart|-- test...
入口页面:lib/main.dart
知识点:
package:provider/provider.dart
状态管理package:flutter/material.dart
UI组件应用pub
资源包使用
- 引入资源
- pub是Flutter的资源管理器,类似于node的npm。
// material 组件库
import 'package:flutter/material.dart';
// 列表页部件
import 'package:cnode_flutter/pages/home.dart';
// provider组件
import 'package:provider/provider.dart';
// model
import './model/model.dart';
- 添加应用入口
// 应用入口
void main() => runApp(MyApp());
- 创建Material应用,设置首页
// 应用入口
class MyApp extends StatelessWidget {@overrideWidget build(BuildContext context) {return MultiProvider(// 状态共享 https://book.flutterchina.club/chapter7/provider.htmlproviders: [ChangeNotifierProvider(builder: (_) => Counter()),],// Consumer 消费者 https://book.flutterchina.club/chapter7/provider.html// 这里强行用了一下~ 作为示例而child: Consumer<Counter>(builder: (context, counter, _) {/// [Consumer]可以通过[counter]访问到[Counter]这个model下的状态print(counter);// MaterialApp 是Material库中提供的Flutter APP框架// https://docs.flutter.cn/flutter/material/MaterialApp-class.htmlreturn MaterialApp(// 应用名称title: 'CNode',// 主题theme: ThemeData(// 定义主题色 Colors 是MaterialApp中的颜色部件,里面定义了很多颜色primaryColor: Colors.blue,),// 首页home: Home(),);},),);}
}
列表页面:lib/pages/home.dart
知识点:
- 可滑动列表
ListView
; - 上拉加载新数据;
- 文字过长省略显示;
- 路由跳转;
- 列表中的子项
Card
布局;
- 创建状态组件
// 首页(列表) 继承 StatefulWidget(有状态模型?)
class Home extends StatefulWidget {// Home({Key: key}) :super(Key key);@override_HomeState createState() => _HomeState();
}
class _HomeState extends State<Home> {}
- 创建页面
class _HomeState extends State<Home> {// Scaffold 部件的keystatic GlobalKey<ScaffoldState> _globalKey = new GlobalKey();// List 不免的keystatic GlobalKey<ListState> _listKey = new GlobalKey();@overrideWidget build(BuildContext context) {// 页面脚手架 https://docs.flutter.cn/flutter/material/Scaffold-class.htmlreturn Scaffold(// 部件的key主要用来提升diff算法性能,跟前端概念中的key是类似的// https://my.oschina.net/u/4082889/blog/3031508key: _globalKey,appBar: new AppBar(title: const Text('list'),leading: IconButton(icon: const Icon(Icons.menu),onPressed: () {// Scaffold.of(context).openDrawer();_globalKey.currentState.openDrawer();},tooltip: MaterialLocalizations.of(context).openAppDrawerTooltip,),),// new抽屉实例,并将更新列表的方法传递给drawer页面调用(也可以用eventbus)drawer: new HomeDrawer(getListFn: () {_listKey.currentState.curPage = 1;_listKey.currentState.getListFn(loadMoreBool: false,tab: Provider.of<Counter>(context).tab,page: 1);}),body: new List(key: _listKey),);}
}
- 创建页面中的列表内容
// 产生列表widge
class List extends StatefulWidget {List({Key key}) : super(key: key);@overrideListState createState() => new ListState();
}class ListState extends State<List> {var list = <dynamic>['loading']; // 数据数组var curPage = 1; // 当前页数var loadingBool = false; // 是否正在加载中,避免多次请求阻塞ScrollController _controller = ScrollController(); // list scroll controller/// 通过http请求获取列表数据/// [loadMoreBool]:是否是加载更多 示例:true/// [tab]:话题类型 示例:good/// [page]:第几页 示例:1// ListState() {}@overridevoid initState() {super.initState();curPage = 1;getListFn(loadMoreBool: false, tab: '', page: curPage);}@overridevoid dispose() {//内存泄露,可以调用_controller.dispose,释放// _controller.dispose();super.dispose();}// _ListState({Key:key}):super(Key:key)Widget build(BuildContext context) {// list scroll controller_controller.addListener(() async {// 获取页面长度 和 当前滚动条所在位置var maxScroll = _controller.position.maxScrollExtent;var pixels = _controller.position.pixels;// 滑动到底部加载更多if (!loadingBool && maxScroll == pixels) {/// [loadingBool] 正在加载中状态,避免重复请求loadingBool = true;await getListFn(loadMoreBool: true,tab: Provider.of<Counter>(context).tab,page: curPage);loadingBool = false;}});// 列表// ListView部件说明:https://book.flutterchina.club/chapter6/listview.htmlreturn ListView.builder(/// 总长度,例如为50,第一屏显示五项,那么[itemBuilder]会创建第一屏需要的部件,而不是将列表中的50个部件都创建出来itemCount: list.length,padding: const EdgeInsets.only(top: 0, left: 0, right: 0, bottom: 20),// 按需创建部件itemBuilder: (BuildContext _context, int i) {// 如果这一项为 String,带着这一项是特殊的部件,比如 loading(加载中)、noMore(没有更多)、none(暂无数据)if (list[i] is String) {if (list[i] == 'loading') {// 部件:加载中return Container(padding: const EdgeInsets.all(16.0),alignment: Alignment.center,child: SizedBox(width: 24.0,height: 24.0,child: CircularProgressIndicator(strokeWidth: 2.0)),);} else if (list[i] == 'noMore') {// 部件:没有更多return Container(alignment: Alignment.topCenter,padding: EdgeInsets.all(16.0),child: Text("没有更多了",style: TextStyle(color: Colors.grey),));} else if (list[i] == 'none') {// 部件:暂无数据return Container(alignment: Alignment.topCenter,padding: EdgeInsets.all(16.0),child: Text("暂无数据",style: TextStyle(color: Colors.grey),));}}// 创建item部件,并返回给列表return buildItem(list[i]);},controller: _controller,);}
- 从http接口中获取数据:
// http apis
class Apis {// get /topics 主题首页static const String topicList = '$_domain/topics';
}// http actions
class HttpActions {// 获取话题列表static Future getTopicList({int limit = 20, int page, bool mdrender = false, String tab}) {return Dio().get('${Apis.topicList}?mdrender=$mdrender&limit=$limit&page=$page&tab=$tab');}
}/// 调用http请求获取列表数据/// [loadMoreBool] Bool 加载更多标志/// [tab] String 主题分类。目前有 ask share job good/// [page] Number 页数Future getListFn({bool loadMoreBool, String tab, int page}) {// print('$loadMoreBool,$tab,$page');return HttpActions.getTopicList(page: page, tab: tab).then((res) {var data = res.data['data'];var l = data.length;setState(() {if (loadMoreBool) {// 加载更多逻辑if (l > 0) {// 有数据,向list中添加新数据curPage++;list.insertAll(list.length - 1, data);} else {// 无数据,向list中添加'noMore'标识list[list.length - 1] = 'noMore';}} else {// 第一次获取数据逻辑// 清楚list原有数据list = <dynamic>['loading'];// 滚动列表页到顶部_controller.animateTo(.0,duration: Duration(milliseconds: 300),curve: Curves.easeInOutExpo);if (l > 0) {// 有数据,向list中添加新数据list.insertAll(list.length - 1, data);curPage++;} else {// 无数据,向list中添加'noMore'标识list[list.length - 1] = 'none';}}});});}
抽屉页面:lib/pages/drawer.dart
知识点:
HomeDrawer
抽屉的使用;- 点击抽屉里面 tab标签 切换 列表页面 内容;
- 手势部件的使用
Listener
、GestureDetector
import 'package:cnode_flutter/services/index.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../model/model.dart';class HomeDrawer extends StatefulWidget {final getListFn;HomeDrawer({this.getListFn});_HomeDrawerState createState() => new _HomeDrawerState();
}class _HomeDrawerState extends State<HomeDrawer> {var userInfo = <String, dynamic>{'avatar_url': '','loginname': '北京吴彦祖','score': '0',};// 获取用户信息void getUserInfoFn() async {var res = await HttpActions.getUserInfo();setState(() {userInfo = res.data['data'];});}@override// 生命周期钩子void initState() {super.initState();print('drawer initState');// 获取用户信息getUserInfoFn();}@override// 生命周期钩子void dispose() {print('drawer dispose');super.dispose();}Widget build(BuildContext context) {// Drawer 抽屉部件 https://docs.flutter.cn/flutter/material/Drawer/Drawer.htmlreturn new Drawer(child: Column(children: generateListFn(context)),);}// 生成抽屉列表部件List<Widget> generateListFn(context) {var children = <Widget>[];// 添加用户信息部件children.add(generateUserBoxFn(userInfo, context));// 根据数组信息,生成可以点击的tab分类[{'label': '全部', 'id': '', 'icon': Icons.border_all},{'label': '精华', 'id': 'good', 'icon': Icons.thumb_up},{'label': '分享', 'id': 'share', 'icon': Icons.share},{'label': '问答', 'id': 'ask', 'icon': Icons.question_answer},{'label': '招聘', 'id': 'job', 'icon': Icons.work},].forEach((item) {/// 依次将 按钮部件 推入[children]children.add(ListTile(title: new Text(item['label']),leading: Icon(item['icon']),trailing: Icon(Icons.keyboard_arrow_right),selected: item['id'] == Provider.of<Counter>(context).tab,onTap: () {/// 通过调用[rovider.of<Counter>]的change方法,来改变tab的值Provider.of<Counter>(context).change(item['id']);// 这里没有将 item['id'] 传递下去,是为了强行体现一下 provider 的作用:)widget.getListFn();},),);});return children;}
}// 生成用户信息盒子的方法
Widget generateUserBoxFn(userInfo, context) {return Container(// 内边距padding: EdgeInsets.only(top: 60, right: 20, bottom: 10, left: 20),// Container 部件颜色color: Colors.blue,child: Column(children: <Widget>[// 第一行:头像,夜间模式Row(mainAxisAlignment: MainAxisAlignment.spaceBetween,children: <Widget>[// 头像userInfo['avatar_url'].length > 0? CircleAvatar(backgroundImage: NetworkImage(userInfo['avatar_url']),backgroundColor: Colors.blue,radius: 20,): new Icon(Icons.person,size: 40,color: Colors.white,),// 夜间模式Listener(child: new Icon(Icons.brightness_2),onPointerDown: (PointerDownEvent event) {print(event);// 弹窗 配置如key名称所示,title:标题,titlePadding:标题的内边距,等等等showDialog(context: context,builder: (BuildContext context) => SimpleDialog(title: Text("提示"),titlePadding: EdgeInsets.all(10),backgroundColor: Colors.white,elevation: 5,shape: RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(6))),children: <Widget>[ListTile(title: Center(child: Text("女朋友召唤,来不及写了。"),),),],),).then<void>((value) {// The value passed to Navigator.pop() or null.print(value);});},),],),// 第二行:昵称、注销按钮Padding(padding: EdgeInsets.only(top: 20),child: Row(mainAxisAlignment: MainAxisAlignment.spaceBetween,crossAxisAlignment: CrossAxisAlignment.center,children: <Widget>[Column(crossAxisAlignment: CrossAxisAlignment.start,children: <Widget>[// 昵称、积分Container(height: 20,child: new Text(userInfo['loginname'],style: TextStyle(color: Colors.white,),),),Text.rich(new TextSpan(text: '积分:',children: <InlineSpan>[new TextSpan(text: userInfo['score'].toString())],style: TextStyle(color: Colors.white60,),))],),// 注销按钮,并监听点击事件Listener(child: Text("注销",style: TextStyle(color: Colors.white60,),),onPointerUp: (PointerUpEvent event) {// 弹窗 配置如key名称所示,title:标题,titlePadding:标题的内边距,等等等showDialog(context: context,builder: (BuildContext context) => SimpleDialog(title: Text("提示"),titlePadding: EdgeInsets.all(10),backgroundColor: Colors.white,elevation: 5,shape: RoundedRectangleBorder(borderRadius:BorderRadius.all(Radius.circular(6))),children: <Widget>[ListTile(title: Center(child: Text("女朋友召唤,来不及写了。"),),),],),).then<void>((value) {// The value passed to Navigator.pop() or null.print(value);});}),],),),],));
}
详情页面:lib/pages/article.dart
知识点:
markdown
的使用- 常用部件的使用,头像、文字、等
import 'package:flutter/material.dart';
import 'package:cnode_flutter/services/index.dart';
import 'package:flutter_markdown/flutter_markdown.dart';class ArticleDetail extends StatefulWidget {// 接受列表页传过来的参数final data;ArticleDetail(this.data);_ArticleDetailState createState() => new _ArticleDetailState(data);
}class _ArticleDetailState extends State<ArticleDetail> {var data;// 存放整个页面的widgetsvar listViewChildren = <Widget>[];// 获取文章的内容信息_ArticleDetailState(this.data);@overrideinitState() {super.initState();// avatar_url值为 '//www.baidu.com', //开头flutter的image部件会报错,需要处理一下数据// 这里没有处理的原因是,数据在列表页面已经处理过// data['author']['avatar_url'] = data['author']['avatar_url']// .replaceAllMapped(new RegExp(r'(?<!https:|http:)//'), (hasil) {// return 'https://';// });// 初始化话题详情内容信息initPageWidgetsFn();// 调取详情接口获取文章的详细信息(比如回复)HttpActions.getTopicDetail(id: data['id']).then((res) {print(res);// 添加评论addReplyWidgetsFn(res.data['data']['replies']);});}Widget build(BuildContext context) {// 页面脚手架 https://docs.flutter.cn/flutter/material/Scaffold-class.htmlreturn Scaffold(appBar: new AppBar(title: Text('话题')),body: Padding(padding: EdgeInsets.all(12),child: ListView.builder(itemCount: listViewChildren.length,itemBuilder: (context, index) {return listViewChildren[index];}),));}// 初始化页面内容,话题的标题、内容、作者信息void initPageWidgetsFn() {setState(() {listViewChildren.addAll([// 标题Padding(padding: EdgeInsets.only(bottom: 10),child: Text(data['title'],style: TextStyle(color: Colors.black, fontSize: 17, fontWeight: FontWeight.w500),),),// 作者信息Row(mainAxisAlignment: MainAxisAlignment.spaceBetween,children: <Widget>[Row(children: <Widget>[// 头像CircleAvatar(radius: 20,backgroundImage: NetworkImage(data['author']['avatar_url']),),// 昵称、浏览量Padding(padding: EdgeInsets.only(left: 10),child: Column(crossAxisAlignment: CrossAxisAlignment.start,children: <Widget>[Text(data['author']['loginname']),Text.rich(TextSpan(text: data['visit_count'].toString(),children: [TextSpan(text: '次浏览')]),)],),)],),// 是否已经收藏data['is_collect'] == true? new Icon(Icons.favorite,color: Colors.green,): new Icon(Icons.favorite_border,color: Colors.grey,)],),// 正文Padding(padding: EdgeInsets.only(top: 15),child: new MarkdownBody(// 请注意在下面的示例中使用_raw string_(前缀为`r`的字符串)。 使用原始字符串将字符串中的每个字符视为文字字符。data: data['content'].replaceAllMapped(new RegExp(r'(?<!http:|https:)//'), (hasil) {return 'https://';})),),new Divider(height: 40,)]);});}// 添加评论部件void addReplyWidgetsFn(repliesList) {// 评论部件 生成后一次添加进话题内容,其实刚好的做法是跟话题列表一样,添加上拉加载var widgets = <Widget>[];if (repliesList.length < 1) {// 没有评论的情况widgets.add(Text('no replies'));} else {// 有评论的情况/// 很好奇数组的forEach方法为什么不提供索引[index]repliesList.asMap().forEach((index, item) => widgets.add(Column(crossAxisAlignment: CrossAxisAlignment.start,children: <Widget>[Row(mainAxisAlignment: MainAxisAlignment.spaceBetween,children: <Widget>[Row(// 头像children: <Widget>[CircleAvatar(radius: 16,backgroundImage:NetworkImage(item['author']['avatar_url']),),// 昵称、楼层信息Padding(padding: EdgeInsets.only(left: 10),child: Column(crossAxisAlignment: CrossAxisAlignment.start,children: <Widget>[Text(item['author']['loginname']),Text.rich(TextSpan(text: index.toString(),children: [TextSpan(text: '楼')]),style: TextStyle(color: Colors.green),)],),)],),// 是否已经收藏item['is_collect'] == true? new Icon(Icons.favorite,color: Colors.green,): new Icon(Icons.favorite_border,color: Colors.grey,)],),// 评论Padding(padding: EdgeInsets.symmetric(vertical: 10,),child: Text(item['content'],),),],)));setState(() {listViewChildren.addAll(widgets);});}}
}
总结
强烈建议想搞Flutter的朋友,读一遍《Flutter实战》,这是Flutter中文网出的一本书,主要以入门、进阶、实例三大部分进行叙述Flutter。是我目前为数不多的成体系的Flutter中文学习指南。
环境安装
flutter-io.cn
是 Flutter 官方的中文站点
安装说明:https://flutter-io.cn/docs/get-started/install
flutterchina.club
是 Flutter 中文开发者社区的开源项目。
安装说明:https://book.flutterchina.club/chapter1/install_flutter.html
Flutter开发文档
https://flutter-io.cn/docs
FlutterAPI文档
https://docs.flutter.cn/
Dart 语法
http://dart.goodev.org/guides/language/language-tour
状态管理方案
1.使用状态管理的目的是为了让编写代码变得更简单,任何会增加你的应用复杂度的状态管理,统统都不要用。
2.选择自己能够 hold 住的,BLoC / Rxdart / Redux / Fish-Redux 这些状态管理方式都有一定上手难度,不要选自己无法理解的状态管理方式。
3.在做最终决定之前,敲一敲 demo,真正感受各个状态管理方式给你带来的 好处/坏处 然后再做你的决定。
上面的内容摘自以下链接,该作者就状态管理方案问题,做了详细的解答。
https://juejin.im/post/5d00a84fe51d455a2f22023f
前端工程师的第一个Flutter应用相关推荐
- web前端工资一般多少?在北京前端工程师多少钱一个月?
前端工程师工资多少钱一个月?好程序员告诉大家:平均月薪20K以上,大厂年薪30万起步.web前端工程师在北上广深有巨大的就业需求,所以很多小伙伴选择了学习前端.那么web前端工程师工资一般多少钱呢? ...
- 最受互联网争抢的web前端工程师
说到互联网所包含的各大职业,Web前端企业已经成为市场争抢的香饽饽,据招聘门户网站的招聘数据显示,每个月企业在51job上公布的职位量在1.3万左右,在智联招聘上公布的职位量是2.4万左右,平均月薪1 ...
- 前端工程师是怎样一种职业
作者:吕大豹 文章链接 : 前端工程师是怎样一种职业 本文略有删减,请尊看原文. 前端工程师的英文名为front-end engineer,简称FE,下文将用FE来代称.现在意义上的前端(并非只制作网 ...
- 黄锦诚:前端工程师新手必读
在网站的发展史上,初期的网站建设根本不需要网页重构这个职位,WEB1.0时代的网页,只需要程序员,一堆堆的表格嵌套就完成,或者美工进行配合完成,先由美工负责设计好,再用一些自动化的软件拉伸几下,直接将 ...
- 写给前端工程师的 Flutter 详细教程
本文作者:hicc,腾讯 CSIG 前端开发工程师 最爱折腾的就是前端工程师了,从 jQuery 折腾到 AngularJs,再折腾到 Vue.React.最爱跨屏的也是前端工程师,从 phonega ...
- 学习web前端,合理的学习路线,如何成为一个合格的前端工程师
学习前端,首先应该列举出整个前端的知识图谱,然后制定一个合理的学习线路图,逐个击破,只要保持学习的热情和持之以恒,肯定能成为一位合格的前端工程师.前端算是目前互联网研发岗中门槛相对较低的,只要具备完整 ...
- 一个前端工程师到底需要掌握哪些技能?
作为一名前端想要晋升,需要什么条件? 现在在用 React,要不要也学学 Vue? 有必要学习 Node.js/Flutter/ 函数式吗? 这几个问题看似毫无关联,但是其实它们本质上都是同一个问题, ...
- mssql 计划怎每隔n秒_前端:调你一个接口6秒还配资深工程师?后端:有24部分需要处理!...
有关于做web开发的程序员,不知道你们有没有这样一种感受,那就是前端工程师与后端工程师之间有时也会存在鄙视链的关系,比如前端程序员会认为后端程序员没什么技术含量,不就是写个接口,获取一些数据而已,而前 ...
- 一个合格的Web前端工程师要掌握的知识点汇总
Web前端开发人员使用的技术CSS和HTML.JavaScript,根据设计师设计的雏形来编写代码.布局,框架,浏览器涉及到不同的领域知识广度,把网站界面更好地呈现给用户.那么问题来了,初学Web前端 ...
- [JavaScript] 多数前端工程师都没注意到的一个关于console.log()的坑
[JavaScript] 多数前端工程师都没注意到的一个关于console.log()的坑 请阅读以下代码并猜测结果: function test() {let obj = {}, arr=[]for ...
最新文章
- 《Windows网络与通信程序设计(第3版)》——1.4 网络应用程序设计基础
- 深圳美景品牌策划机构:美景、BOBDOG传媒合作论坛广州举行
- cacti由cmd.php更换成spine后无法绘图
- 2017年2月20日 Adaboost
- redmine plugin
- re正则表达式的使用
- javascript基础入门_javascript基础入门学习第一篇
- 量子计算机如何确定量子状态,量子计算机六个量子位足以确定三个简单分子的基态...
- 电容降压LED驱动电路
- 自定义键盘码_无线+矮轴≤299?ikbc S200 2.4G 机械键盘测评
- python装饰器系列(五)
- 基于JAVA+SpringMVC+Mybatis+MYSQL的新闻发布系统
- 计算机信息的应用安全中心在哪,腾讯游戏安全中心
- POJ 1195 Mobile phones (二维树状数组)
- java并发包aqu_Java并发包之SynchronousQueue
- UR机器人数据包解析(python与C++实现)
- 世界主要货币英文缩写
- Docker - Docker Volume及Volume命令详解
- yylabel html富文本,YYLabel 自动布局 富文本文字点击事件
- EasyCode实现数据库到Swagger全自动化