| flutter sdk 1.17.4 | 切回应用正常 |
| flutter sdk 1.20.1 | 切回应用无响应 |
| flutter sdk 1.20.2 | 切回应用无响应 |
| flutter sdk 1.20.3 | 切回应用正常 |
现在至少可以确定在 >1.17.4 <1.20.3之间的版本是可能会有兼容性问题的。也可能是Flutter团队在最新版本修复了这个不为人知的bug。
获取屏幕的宽高尺寸
double width = MediaQuery.of(context).size.width;
double height = MediaQuery.of(context).size.height;
有时遇到一些边界问题,不得不指定一个固定的宽度高度,这时可以参考屏幕的宽高,尽量按屏幕尺寸的百分比去设置,而不是写死魔法值。
判断当前是哪个平台
import ‘package:flutter/foundation.dart’;
…
//TargetPlatform目前支持6种平台
if (defaultTargetPlatform == TargetPlatform.android) {
// 是安卓系统,do something
}
// 我们可以通过显式指定debugDefaultTargetPlatformOverride全局变量的值来指定应用平台。比如:
debugDefaultTargetPlatformOverride = TargetPlatform.iOS;
print(defaultTargetPlatform); // 会输出TargetPlatform.iOS
//上面代码即在Android中运行后,Flutter APP就会认为是当前系统是iOS,Material组件库中所有组件交互方式都会和iOS平台对齐,defaultTargetPlatform的值也会变为TargetPlatform.iOS。
RefreshIndicator有时无法下拉刷新
RefreshIndicator组件的子组件listview或gridview的item数量较少时,无法触发下拉刷新操作,解决方法:滚动组件的physice
属性设置为AlwaysScrollableScrollPhysics()
,总是保持可滚动状态。
release模式下运行app无法连接网络
首先release模式运行的命令:
flutter run --release
运行之后发现请求接口的列表都出不来了,原来是AndroidManifest
文件中默认没有添加INTERNET
权限,手动添加上即可。奇怪的是,在debug模式下运行,就算不添加默认也是能请求网络的,害我找了好久原因。。
AS中运行除了main.dart以外的dart文件到手机上
Android Studio中运行除了main.dart以外的dart文件(必须包含main函数),Terminal执行命令:
flutter run lib/animated_list.dart
热加载直接在Terminal键入:r 即可
pubspec.yaml中批量添加图片资源
一开始我都是按照规矩这样添加的:
flutter:
uses-material-design: true
To add assets to your application, add an assets section, like this:
assets:
-
images/ic_test.png
-
images/ic_timg.jpg
-
images/timg4.jpg
-
images/avatar.png
后来看了官网的demo,原来这样也可以:
flutter:
uses-material-design: true
assets: [images/]
这样如果我有很多图片就不用一个一个的去添加依赖了,省了很多力气,哈哈
目前主要学到的几点:
-
尽量使用无状态的类组件来代替函数式的组件
-
尽量保持
build
方法的纯净,减少跟UI无关的逻辑处理 -
尽量使用
const
final
等修饰符来避免重复创建新的组件
主要理解两个问题
第一个问题:为啥要使用类组件来代替函数组件?
Class Widget VS Function Widget 的口水之战:
- [Why should I ever use stateless widgets instead of functional widgets?
]( )(为什么我应该使用无状态组件而不是函数组件?) ——来自Flutter的Github源码的一个issue,国外各路神仙们就此展开了激烈辩论。。
- What is the difference between functions and classes to create reusable widgets?(使用类和函数构建可复用组件的不同点是什么?) ——来自stackoverflow的issue
两个链接都是出自同一大神Rousselet的回答,他首先指出的是这两者之间的一个显著的不同点,就是:Flutter的framework对函数是无感知的,但是对类是有感知的。
也就是说通过类创建的组件最终会作为一个独立的element被挂载在element树上面去,而函数构建方式仅仅是纯粹的插入到了调用的地方,函数内的组件只是作为调用者的组件的一部分而已。使用函数构建组件会比较容易产生bug,这并不是说你使用函数构建组件就一定会有bug, 只不过如果你使用类构建组件的话,则一定能够保证不会面临用函数构建组件时可能会产生的bug。
既然使用函数实际也是可以的,也不一定就会产生bug, 而且使用函数会比使用类少写很多代码,那么为啥还一定要推荐类组件呢,有啥更特别的地方吗?
——因为使用类组件你会发现有如下显著优势:
-
允许性能优化(使用
const
构造函数,做到更精细的重建) -
确保在两种不同布局之间的切换时能正确地释放资源(函数可能会重用一些先前的状态)
-
能确保热重载正常工作(函数则可能会中断热重载)
-
类组件会被显示到devTool的
widget inspector
检查器当中,便于调试 -
类组件可以拥有
key
, 而函数则不行 -
类组件可以使用
context
API, 而函数则不行 -
类组件可以通过重写运算符
==
, 减少重建次数, 而函数则不行
Github上的那个issue评论了两年多终于被关闭了。。不得不佩服外国人的钻研精神。。但是直到这个issue被关闭的时候,仍然有很多人还是不是很理解,为啥要优先用类,其实官方的说法也只是强烈推荐,但并不是必须的强制规则。也有很多人提出了如下代码:
class Toggler extends StatelessWidget {
final VoidCallback onToggle;
const Toggler({Key key, this.onToggle}) : super(key: key);
@override
Widget build(BuildContext context) {
return Row(
children: [
FlatButton(onPressed: onToggle, child: Text(‘On’)),
FlatButton(onPressed: onToggle, child: Text(‘Off’)),
],
);
}
}
上面方法中有两个FlatButton
是一样的使用方式,那么你可能会考虑这样去重构:
//…
Widget _toggleButton(String text) =>
FlatButton(onPressed: onToggle, child: Text(text));
@override
Widget build(BuildContext context) {
return Row(
children: [
_toggleButton(‘On’),
_toggleButton(‘Off’),
],
);
}
按照我的理解,其实这样做是完全可以的,因为这两种方式最终是等价的,包括我们经常会写的构建列表组件会使用map方法:
final List fruits;
//…
@override
Widget build(BuildContext context) {
return Column(
children: _buildChildren(),
);
}
List _buildChildren() {
return fruits.map((f) => _FruitInfo(fruit: f)).toList();
}
这样应该也是完全OK的,也就是说使用箭头函数和inline组件的方式完全没有任何区别。更多的可能还是要考虑实际业务场景,如下面的代码,flutter就很难理解,某些情况下可能会造成不可预期的结果,这时应该去函数化来得到更加安全的保障。
bool condition;
Widget _foo();
Widget _bar();
Widget build(BuildContext context) {
return condition
-
? _foo()
- _bar();
}
为了更好理解再看两组代码
以下两组代码等价:
Widget functionA() => Container()
@override
Widget build() {
return functionA()
}
@override
Widget build() {
return Container();
}
以下两组代码等价:
class ClassA extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Container();
}
}
@override
Widget build() {
return ClassA()
}
@override
Widget build() {
return KeyedSubtree(
key: ObjectKey(ClassA),
child: Container(),
);
}
可以看出两种方式本质上的不同,类的优势明显优于函数。当然如果你还是比较习惯写函数式的组件,Rousselet大神写了一个库functional_widget可以让你以写函数组件的方式来写类组件:
@swidget
Widget foo(BuildContext context, int value) {
return Text(‘$value’);
}
比如,当你写上面的代码时,使用他这个库会生成下面的代码:
class Foo extends StatelessWidget {
const Foo(this.value, {Key key}) : super(key: key);
final int value;
@override
Widget build(BuildContext context) {
return foo(context, value);
}
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
properties.add(IntProperty(‘value’, value));
}
}
第二个问题:如何避免没必要的重复构建?
How to deal with unwanted widget build? (如何处理多余的组件构建?)
这个链接同样也是Rousselet的回答,他的意思,简而言之就是build方法是Flutter中刷新界面时会高频调用的方法,所以尽量保持它的纯净。这对提高刷新性能尤为重要,我们需要把跟UI构建不直接相关的处理逻辑全部移出build方法。例如他提到的这部分优化代码就是将Future对象的构造从build中移了出来:
class Example extends StatefulWidget {
@override
_ExampleState createState() => _ExampleState();
}
class _ExampleState extends State {
Future future;
@override
void initState() {
future = Future.value(42);
super.initState();
}
@override
Widget build(BuildContext context) {
return FutureBuilder(
future: future,
builder: (context, snapshot) {
// create some layout here
},
);
}
}
Flutter中实例相同的组件构建时不会被重新构建,利用这一点我们可以想办法缓存部分组件,如:
@override
Widget build(BuildContext context) {
return const DecoratedBox(
decoration: BoxDecoration(),
child: Text(“Hello World”),
);
}
其中const
修饰的组件不会每次都被重新创建,使用final
关键字也可以做到:
@override
Widget build(BuildContext context) {
final subtree = MyWidget(
child: Text(“Hello World”)
);
return StreamBuilder(
stream: stream,
initialData: “Foo”,
builder: (context, snapshot) {
return Column(
children: [
Text(snapshot.data),
subtree,
],
);
},
);
}
这里final
修饰的组件也不会每次都被重新创建,这种模式在动画中被大量使用如AnimatedBuilder。另外,也提到其他解决方法如尽可能拆分到更小的独立Statefull
组件中,或者使用Provider
库来解决。
build
方法在下面的场景下会被调用:
-
调用
initState
方法之后 -
调用
didUpdateWidget
方法之后 -
调用
setState()
方法时 -
键盘被打开时
-
屏幕方向发生旋转时
-
父组件被构建子组件也会重新构建
自定义组件的一个重要点就是在didUpdateWidget
中根据新旧组件的状态值比较决定是否要重新构建UI:
@override
void didUpdateWidget(MyRichText oldWidget) {
if (widget.text != oldWidget.text) {
_textSpan = parseText(widget.text);
}
super.didUpdateWidget(oldWidget);
}
又如:
@override
void didUpdateWidget(CounterWidget oldWidget) {
//通过新旧widget的一些属性来判断是否变化
// 检查新旧child是否发生变化(key和类型同时相等则返回true,认为没变化)
if (Widget.canUpdate(widget.child, oldWidget.child)) {
// child没变化,…
} else {
super.didUpdateWidget(oldWidget);
}
}
开头介绍过Flutter的不好的地方,第一个就是嵌套层级过多的问题,
第一种方法,将复杂的widget抽取变量,或者抽取类组件
详细的戳这篇文章:Flutter避免代码嵌套,写好build方法
这篇文章的主要思想是不断的复用抽取的控件变量来减少嵌套层级,包括利用控件变量结合if
条件渲染, 以及将组件抽取到类当中(Stateful或Stateless)。注意这里提到的是抽取变量,而不是抽取函数,详细前面优化部分已经介绍过了。
其实简单来说,这种方法做的事情就是两个字:重构!不借助任何外力或者依赖多余的库,重新设计和组织你的代码,以得到更加清晰的代码架构和逻辑,并且能够减少bug。我觉得这个已经不算是什么新鲜的姿势了,而是作为一只优秀程序猿的基本素养。
总的来说这种方法是比较原生态的,我个人也比较推荐这种。
第二种方法,通过Dart 2.7的新属性扩展函数来解决
主要看这篇文章:https://blog.csdn.net/c6E5UlI1N/article/details/104057737
虽然这种方式也能解决嵌套问题,但是最终使用起来你会发现它的思想都是反着去添加的,跟日常使用习惯部分,有点反人类。。所以我个人不推荐这种方式。
第三种方法,利用建造者模式来解决
这个可以看这里:flutter解决布局嵌套问题
设计模式是用来优化代码和重构的终极法宝,很多时候你想不到的答案可能先人已经总结了方法。按照build模式我们可以将常用的布局组件都封装一下使用,不限于文中介绍的。只是提供了一种思路。
第四种方法,将组件属性封装为方法来解决
这种其实是由上面第三种建造者模式得到的启发,但是这里我不用建造者模式,因为建造者模式到最后一步build才会生成对象实例,而我想先生成对象实例,然后再调用实例的方法来为其添加属性。最终想要的效果如下:
import ‘package:flutter/material.dart’;
import ‘column_row_wrap.dart’;
class UnNestLayoutTestPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text(“去除嵌套示例”),),
body: ColumnWidget()
.addChild(Text(“呵呵”))
.addChild(Text(“怎样”))
.addChildByPadding(Text(“可以”), EdgeInsets.all(10.0))
.addChild(CenterWidget(Text(“测试”)))
.addChild(RowWidget()
.addChild(Text(“你好”))
.addChildByMargin(Text(“很好”), EdgeInsets.all(20.0))
.addChild(Text(“大家好”)))
.addChild(ContainerWidget(Text(“中国”))
.addPadding(EdgeInsets.all(10.0))
.addDecoration(BoxDecoration(color: Colors.red)))
.addChild(RaisedButtonWrap(“提交”)
.textColor(Colors.blue)
.onClick(() => { print(“点击了提交”)}))
);
}
}
可以看到,在build方法中我用了20行不到的方法添加了很多控件,并且可以轻松的设置颜色、padding、margin、点击事件等,如果要用Flutter原生的写法实现上面的代码远远不止20行,因为每一行要扩展出来好几行,代码量要暴增好几倍。
很显然,这样的代码,肯定是更加清晰且易于维护的。那怎么实现的呢,没有很神秘的东西,直接上代码:
import ‘package:flutter/material.dart’;
class ColumnWidget extends StatelessWidget {
List _children = [];
ColumnWidget addChild(Widget widget) {
_children.add(widget);
return this;
}
ColumnWidget addChildByPadding(Widget widget, EdgeInsetsGeometry padding) {
_children.add(Padding(
child: widget,
padding: padding,
));
return this;
}
@override
Widget build(BuildContext context) {
return Column(
children: _children,
);
}
}
class RowWidget extends StatelessWidget {
List _children = [];
RowWidget addChild(Widget widget) {
_children.add(widget);
return this;
}
RowWidget addChildByMargin(Widget widget, EdgeInsetsGeometry margin) {
_children.add(Container(
child: widget,
margin: margin,
));
return this;
}
@override
Widget build(BuildContext context) {
return Row(
children: _children,
);
}
}
class ContainerWidget extends StatelessWidget {
final Widget children;
ContainerWidget(this.children);
EdgeInsetsGeometry _padding;
EdgeInsetsGeometry _margin;
Decoration _decoration;
ContainerWidget addPadding(EdgeInsetsGeometry padding) {
_padding = padding;
return this;
}
ContainerWidget addMargin(EdgeInsetsGeometry margin) {
_margin = margin;
return this;
}
ContainerWidget addDecoration(Decoration decoration) {
_decoration = decoration;
return this;
}
@override
Widget build(BuildContext context) {
return Container(
child: children,
padding: _padding,
margin: _margin,
decoration: _decoration,
);
}
}
class RaisedButtonWrap extends StatelessWidget {
String title;
RaisedButtonWrap(this.title);
Color _textColor;
Color _bgColor;
VoidCallback _clickListener;
RaisedButtonWrap onClick(VoidCallback clickListener) {
_clickListener = clickListener;
return this;
}
RaisedButtonWrap textColor(Color textColor) {
_textColor = textColor;
return this;
}
RaisedButtonWrap bgColor(Color bgColor) {
_bgColor = bgColor;
return this;
}
@override
Widget build(BuildContext context) {
return RaisedButton(
child: Text(title),
textColor: _textColor,
color: _bgColor,
onPressed: _clickListener,
);
}
}
class CenterWidget extends StatelessWidget {
final Widget child;
CenterWidget(this.child);
@override
Widget build(BuildContext context) {
return Center(
child: child,
);
}
}
这里我只是最简单的封装实现,实际你可以做的更精细将方法封装的更全面一些。当然Flutter当中有300多个组件,我们并不需要将所有的组件都封装一遍,你可以只将常用或者高频使用的组件进行封装,不常用的可以暂时不用去管直接按原来的方式使用即可。
当然并不一定是全部只用这种写法写的,也可以混合在嵌套的布局中使用:
class UnNestLayoutTestPage2 extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text(“去除嵌套示例”),),
body: ColumnWidget()
.addChild(Text(“测试”))
.addChild(Container(
child: RowWidget()
.addChild(Text(“你好”))
.addChildByMargin(Text(“很好”), EdgeInsets.all(20.0))
.addChild(Text(“大家好”)),
))
.addChild(Container(
child:Center(
child: RaisedButtonWrap(“提交”)
.textColor(Colors.blue)
.onClick(() => { print(“点击了提交”)}),
),
))
);
}
}
但是很明显,一旦我们开始了嵌套,代码行数就开始多了起来,看起来就没有那么清爽了。
之所以会有这种感觉,本质上是因为Flutter当中所有的组件属性都是通过构造函数的命名参数传递的,并且没有提供任何的属性操作方法,这就使得你不得不去层层嵌套的使用,这跟android中的java类组件完全不一样,android中的所有类都是提供方法的。很难想象如果我定义了一个类和N多个属性,但是不提供操作方法来使用,而是只提供构造函数来传递这些属性参数。
普通的断点调试跟纯android的没有太大区别,比如当我点击按钮执行某个方法时可以断点分步执行等待。
Dart DevTools
这里只要记录一下Dart DevTools这个工具:
https://flutter.cn/docs/development/tools/devtools/inspector
在AS中点击debug运行或者运行Flutter Attach:
运行起来之后在控制台的会有一个dart的蓝色图标按钮,点击这个按钮就会在浏览器中打开Dart调试面板:
当我们选中Flex类型的布局(如Column和Row)时, 我们可以点击右侧的Layout Explorer查看布局的详细信息,不过这个目前支持flex类型的容器才行:
这里可以改变主轴和交叉轴的对齐方向,或者可以点击设置子组件的flex属性,可以在设备上实时预览改变的结果。这对于理解布局和解决一些UI溢出问题比较有帮助。
默认打开左侧是列出的首页界面的代码,如果想查看指定的组件,需要点击Select Widget Mode模式,然后在手机上点击想要查看的控件,浏览器就会刷新到对应的界面。
但是这个工具感觉目前还不成熟,点几下经常会报错或者失去反应,有时没有指定 textDirection: TextDirection.ltr
也可能导致Layout Explorer无法查看。
另外这个工具可以用来分析性能时间、内存、网络等,但是需要以--profile
模式运行,这里可以先了解一下Flutter的四种运行模式:Debug、Release、Profile和test
我们现在terminal中运行 flutter run --profile
,运行成功最终会看到如下界面 :
点击上面红框中的地址打开浏览器:
复制上面红框中的地址然后打开Tools-->Flutter-->Open Dart DevTools
,并粘贴到打开页面的输入框当中(以http开头):
点击Connect就可以进入到性能相关的页面:
偶尔会失去反应,需要刷新一下页面。。
Dart Pad在线编辑器
这里有一个在线的Dart编辑器网站:https://dartpad.cn
可以在上面进行dart语言练习,甚至还可以在线运行flutter界面:
好像还可以在线分享你写的代码,可以自行点击它的右上角了解。
JsonToDart工具
推荐一个JsonToDart工具:https://juejin.im/post/6844904138032037895
在《Flutter实战》中有介绍json序列化相关的工具,但是这个工具跟教程中介绍的不一样,它是一个桌面版的独立工具应用,不是IDE中的一个插件库,使用方式类似Android Studio中的GsonFormat
插件类似,不过他是生成的dart语言的实体类代码而已。相比flutter的json_to_model
库有一定的方便之处,但并不是所有情况,比如不同的json嵌套子对象是一个已有的相同json对象时,这个工具并不能做到复用已有的对象实体类,这时还是用json_to_model
的方式比较方便一些。
Flutter目前主打手机端,对PC和Web端的支持比较弱,相关的资料也比较少,这里推荐两篇博文,需要的可自行了解:
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)
![](https://i-blog.csdnimg.cn/blog_migrate/33c92ff7c14286b60eebbdc093391179.jpeg)
最后为了帮助大家深刻理解Android相关知识点的原理以及面试相关知识,这里放上我搜集整理的2019-2021BATJ 面试真题解析,我把大厂面试中常被问到的技术点整理成了PDF,包知识脉络 + 诸多细节。
节省大家在网上搜索资料的时间来学习,也可以分享给身边好友一起学习。
《960全网最全Android开发笔记》
《379页Android开发面试宝典》
历时半年,我们整理了这份市面上最全面的安卓面试题解析大全
包含了腾讯、百度、小米、阿里、乐视、美团、58、猎豹、360、新浪、搜狐等一线互联网公司面试被问到的题目。熟悉本文中列出的知识点会大大增加通过前两轮技术面试的几率。
如何使用它?
1.可以通过目录索引直接翻看需要的知识点,查漏补缺。
2.五角星数表示面试问到的频率,代表重要推荐指数
《507页Android开发相关源码解析》
只要是程序员,不管是Java还是Android,如果不去阅读源码,只看API文档,那就只是停留于皮毛,这对我们知识体系的建立和完备以及实战技术的提升都是不利的。
真正最能锻炼能力的便是直接去阅读源码,不仅限于阅读各大系统源码,还包括各种优秀的开源库。
腾讯、字节跳动、阿里、百度等BAT大厂 2019-2021面试真题解析
资料太多,全部展示会影响篇幅,暂时就先列举这些部分截图
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!
-ZeNhMnuG-1712758002733)]
[外链图片转存中…(img-x2SBsbPu-1712758002734)]
[外链图片转存中…(img-LIV1rF2Q-1712758002734)]
[外链图片转存中…(img-jSgCX6P1-1712758002735)]
[外链图片转存中…(img-x6LIC64u-1712758002735)]
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)
![](https://i-blog.csdnimg.cn/blog_migrate/33c92ff7c14286b60eebbdc093391179.jpeg)
最后为了帮助大家深刻理解Android相关知识点的原理以及面试相关知识,这里放上我搜集整理的2019-2021BATJ 面试真题解析,我把大厂面试中常被问到的技术点整理成了PDF,包知识脉络 + 诸多细节。
节省大家在网上搜索资料的时间来学习,也可以分享给身边好友一起学习。
《960全网最全Android开发笔记》
[外链图片转存中…(img-WNsXn6tt-1712758002735)]
《379页Android开发面试宝典》
历时半年,我们整理了这份市面上最全面的安卓面试题解析大全
包含了腾讯、百度、小米、阿里、乐视、美团、58、猎豹、360、新浪、搜狐等一线互联网公司面试被问到的题目。熟悉本文中列出的知识点会大大增加通过前两轮技术面试的几率。
如何使用它?
1.可以通过目录索引直接翻看需要的知识点,查漏补缺。
2.五角星数表示面试问到的频率,代表重要推荐指数
[外链图片转存中…(img-Op5ht2Sh-1712758002735)]
《507页Android开发相关源码解析》
只要是程序员,不管是Java还是Android,如果不去阅读源码,只看API文档,那就只是停留于皮毛,这对我们知识体系的建立和完备以及实战技术的提升都是不利的。
真正最能锻炼能力的便是直接去阅读源码,不仅限于阅读各大系统源码,还包括各种优秀的开源库。
[外链图片转存中…(img-ANCiUxnP-1712758002736)]
腾讯、字节跳动、阿里、百度等BAT大厂 2019-2021面试真题解析
[外链图片转存中…(img-7CX6PvLq-1712758002736)]
资料太多,全部展示会影响篇幅,暂时就先列举这些部分截图
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!