一. 自整理面试题:

1.装饰器(修饰符)是什么?

修饰符:被装饰的变量或者是对象,具备某种特性和属性的意义。

1.state:定义响应式数据,本组件内的响应式数据。-必须给初始值。

2.entry:修饰自定义组件是个页面。

3.compoment:修饰当前的struct结构体是个组件。

4.builder:轻量级UI复用的修饰符,可以实现ArkTS的ui结构。

5.builderParam:接收传入的轻量复用UI。-可以给可以不给,不给调用时需要判断

6.Prop:接收父组件的响应式数据。-可以给可以不给初始值

7.Styles:修饰样式复用集合,不允许传参。

8.Extend:修饰某个特定组件的样式复用集合,允许传参。

9.previe:可预览组件。

10.link:实现父子的双向绑定。-必须不给初始值

11.CustomDialog:修饰自定义弹层

12.Require:可以约束Prop和BuilderParam的参数为必须传递。

13.Track:用来标记一个对象中的哪些字段可以被更新,如果一个track都没有那无所谓。

14.ObjectLink 和 Observed:他可以实现局部的数据更新,使用场景是子组件想实现局部组件更新。

15.Provide和consume:如果组件层级特别多,实现跨组件传递状态数据来实现双向同步。

16.LocalStorageProp:单向读取LocalStorage共享的属性。-必须给初始值

17.LocalStorageLink:双向读取LocalStorage共享的属性。-必须给初始值

18.Watch:关注某个状态变量的值是否改变,可以用watch为状态变量设置回调函数。

19.StorageLink:直接修改-自动同步到全局状态。

20.StorageProp:可以改,但只会在当前组件生效,只是改的全局状态的副本,不会对全局状态产生影响。

1.1.UIAbility组件是什么,ArkTS是什么?

UIAbility组件是一种包含UI的应用组件,UIAbility组件是系统应用中调度的基本单元(最小单元),为应用提供绘制界面的窗口,主要用于和用户交互。一个应用可以包含一个或多个UIAbility组件。 就是一个任务窗口

ARKTS不是TS,只是借鉴了TS的语法,做了动态的扩展,是一个缝合怪!

借鉴了TS的类型,约束等,集成了一些JS的方法。

借鉴了Flutter的链式调用

借鉴了安卓里的compose语法,

借鉴了swift里的UI声明

借鉴了JAVA的面向对象。

尽管ArkTS是基于TS设计的,但出于性能考虑,TS的一些特性被限制了,比如:不支持any。

1.2.ArtTS提供了好几种状态用来帮助我们管理我们的全局数据?

LocalStorage:UIAbility状态(内存- 注意:和前端的区分开,它非持久化,非全应用)

AppStorage:应用内状态-多UIAbility共享(内存-非持久化-退出应用同样消失)

PersistentStorage:全局持久化状态(写入磁盘-持久化状态-退出应用 数据同样存在)

2.builder和builderParam的区别?

他们都是修饰符,

1.builder是将一个函数修饰为轻量Ui复用的函数,在builder修饰的函数中可以实现ArkUI的应用,但是呢builder用法比较多,有全局builder,还有局部builder,全局builder不适合做状态更新。全局builder在鸿蒙4.0中不许允许被导出使用,支持在next版本中导入导出。

builder的传值有基础类型传值和引用类型传值,引用类型传值才能具备响应式的特点。

2.builderParam类似于前端领域中的VUE中的插槽,可以传入UI的结构,支持自定义组件的传入结构,首先在子组件中定义builderParam,在父组件传入BuilderParam对应的函数,该函数可以没有Builder修饰,但是必须调用一个Builder修饰的函数。

builderParam有一种尾随闭包的写法,就是组件()后面的大括号可以传入内容,有两个前提:1.必须只有一个builderParam。2.builderParam没有接收参数的需求。

3.组件通信的方式有哪些?

父子通信:Prop,link,provide consume,

不用修饰符也可以实现夫传子,只有一次生效,除非组件会被销毁。

子给父传:link,consume,还可以通过函数(子组件定义一个函数,父组件传递,子组件调用传给父组件),builderParam可以实现UI结构的传参

storage:

路由传参:

事件进程传参:

ability传参:

4.弹窗UI是怎么在页面UI中使用的?

有两种方式,

1.CustomDialogController:它比较灵活,可以像使用组件一样。前提:1.必须要有一个用CustomDialog修饰的struct结构体,这个结构体中必须有一个类型为CustomDialogController的变量。2.使用的时候,需要在父组件声明弹层的变量,可以实现传值和方法的传递,弹层一共就两个方法,一个open,一个close,open会创建元素,close会销毁元素。

2.采用通用的属性bindSheet:类似于IOS系统中的模态框,bindSheet($$this.showxxx,CustomBuilder,属性对象设置),坑点:builder中最外层不可以直接放置自定义组件。

5.如何使用Prop修饰符,实现父子双相同步?

可以通过函数的调用,通过子组件调用父组件的方法,将值传递给父组件,赋值之后再次同步到子组件。

6.类型中的“!”和“?”

“!”表示类型中的非空断言:人为主观上认为该变量不是空的,只是为了规避IDE的空指针检查,不影响任何运行时的实际效果。

“?”可选链操作符,相当于一个if判断,xxx?.name相当于 if(xxx){xxx.name}

~代码逻辑上不可能为空,用!,不确定是否为空,用?

7.鸿蒙的刷新机制是什么样的?多层嵌套时,是从build开始刷新吗?

一个组件的渲染:aboutToAppear > build(构建UI视图)

当响应式数据发生变化 > build的重新执行。

所有的鸿蒙的响应式监测机制都只能检测到第一层。

多层嵌套时,执行的顺序是:洋葱圈模式-先最里面的组件渲染完毕,最外层的组件最后许渲染。

关于更新机制的问题:比较机制-列表比较-foreach-有三个参数,第三个参数需要返回唯一的key,如果不写,系统会自动帮我们生成一个函数。

8.说一下全局存储状态有哪些方式?

LocalStorage:内存化存储,局部可用

AppStorage:内存化存储,全局可用

PersistentStorage:写入磁盘(沙箱),全局可用

首选项:写入磁盘,全局可用

关系型数据库:写入磁盘

9.什么是进程?什么是线程?多线程如何实现

进程的范围更大 颗粒度更大

进程中包含线程

进程是操作系统进行资源分配的最小单位。

线程是CPU调度的最小单位,程序执行的最小单元。线程不能独立存在,必须依赖于进程。

进程:

应用中(同一Bundle名称)的所有UIAbility、ServiceExtensionAbility和DataShareExtensionAbility均是运行在同一个独立进程(主进程)中。

应用中(同一Bundle名称)的所有同一类型ExtensionAbility(除ServiceExtensionAbility和DataShareExtensionAbility外)均是运行在一个独立进程中。

WebView拥有独立的渲染进程。

基于当前的进程模型,针对应用间和应用内存在多个进程的情况,系统提供了如下进程间通信机制:

公共事件机制:多用于一对多的通信场景,公共事件发布者可能存在多个订阅者同时接收事件。

可以通过commentEventManager进行进程间通信- 公共事件发布 + 自定义事件发布。

线程

Stage模型下的线程主要有如下三类:

  • 主线程
    • 执行UI绘制。
    • 管理主线程的ArkTS引擎实例,使多个UIAbility组件能够运行在其之上。
    • 管理其他线程的ArkTS引擎实例,例如使用TaskPool(任务池)创建任务或取消任务、启动和终止Worker线程。
    • 分发交互事件。
    • 处理应用代码的回调,包括事件处理和生命周期管理。
    • 接收TaskPool以及Worker线程发送的消息。
      • 用于执行耗时操作,支持设置调度优先级、负载均衡等功能,推荐使用。
      • 没有线程数量限制-由系统优先级调度

TaskPool(任务池)和Worker的作用是为应用程序提供一个多线程的运行环境,用于处理耗时的计算任务或其他密集型任务。可以有效地避免这些任务阻塞主线程,从而最大化系统的利用率,降低整体资源消耗,并提高系统的整体性能。

taskPool和Worker的区别:

1.TaskPool和Worker均支持多线程并发能力。由于TaskPool的工作线程支持配置任务优先级,而Worker不支持设置调度优先级。

2.TaskPool自行管理线程数量,其生命周期由TaskPool统一管理。Worker线程最多创建8个,其生命周期由开发者自行维护。

3.TaskPool无需关注线程的执行时长,超长任务(大于3分钟)会被系统自动回收;而Worker支持长时间占据线程执行,需要主动管理线程生命周期。

性能方面使用TaskPool会优于Worker,因此大多数场景推荐使用TaskPool。

线程和进程的通信:

  • 同一线程内通信:同一线程中存在多个组件,例如UIAbility组件和UI组件都存在于主线程中。在Stage模型中目前主要使用EventHub进行数据通信。
  • 线程间通信:emitter .
  • 进程间通信:commentEventManager

10.首选项和APPStorage的区别是什么?

首选项存储的数据只能是字符串,APPStorage的持久化不需要再进行字符串的转化。

大量的数据更推荐用首选项。

11.音视频录制和播放解决方案?

鸿蒙提供了两种方案:

1.最简单的是AvRecorder API方案:适合不懂音频录制的人。

AvPlayer,和AvRecorder配套的

这里面录制的声音都属于非PCM的声音,只能播放mp3 wav这种文件。

2.专业工程师开发的音视频API-AudioCapturer,录制PCM音频数据。

AudioRenderer用来播放PCM音频数据。

录音AudioCapyurer:原理是监听话筒,通过音频流拿到buffer,不间断地写入一个音频文件,采用pcm格式。

播音AudioRenderer:原理是从一个文件中,不间断地拿出一段buffer交给AudioRenderer。

12.怎么用相机拍照片和视频发送?

用CameraPicker这个API:把一个文件拷贝到沙箱,然后把沙箱路径存一下,这个地址存入我们的首选项,然后就可以渲染来预览和播放。

还有一种复杂方法:大概20步。

13.原生地图的使用?

Windows模拟器无法使用地图,地图必须使用真实签名,p12,csr,p7b,cer文件,要在华为网站上生成。

定位需要申请两个权限,location和approval location这两个权限获取经纬度。

14.语音识别?

asr:语音转文本,通过buffer的获取,进行分段式的解析,目前来讲只能支持16000赫兹,只支持通道1。

ttr:文本输出成语音,创建引擎将文本输出就行了。

15.什么是单例模式?

全局只有一个实例

16.你的软件三个tabs,他们三个组件是同时渲染的还是渲染的当前看的?

API 11 12:只渲染了当前看的tab组件,钩子函数从切换当前页签才开始执行,这个组件切出去之后就存入。

4.0版本:是同时全部渲染tabcontent,初步的钩子都执行。

17.尾随闭包就是一种写代码的形式

大括号里面传入结构

hmCard(){

hmCardItem({leftTitle:'车辆信息',rightText:""})

hmCardItem({leftTitle:'车辆信息',rightText:""})

hmCardItem({leftTitle:'车辆信息',rightText:""})

它会把这三个子组件包装成一个回调函数,然后调一下

18.ArkUI的页面渲染过程?

页面和组件的生命周期:

aboutToAppear

build构建

onPageShow

组件构建

例子:父组件A-entry,子组件B。

A页面执行aboutToAppear → A页面的build开始 → B组件的aboutToAppear → B组件的build执行并完成 → A页面的build完成 → A页面的onPageShow

总的来说就是,先完成最内部的组件构建,然后再完成外部组件的构建。

19.页面的刷新机制是什么?

什么情况下会造成页面的刷新?

响应式修饰符state,prop,link,provide,consume,objectLink,storageProp,storageLink,LocalStorageProp,LocalStorageLink,条件渲染。用这些修饰符 修饰的,如果可以检测到数据的变化,页面就会进行更新,就是先销毁再创建。

ForEach会根据key的变化,进行比较,如果key一直不变,不更新,否则是更新。

ObjectLink采用局部更新的方式,头像不闪。

State更新也是局部更新的。

响应式状态更新只能监测到第一层数据的变化。

20.如何性能优化?

资源-图片不要全用原图

  • 请求-尽可能延后-最好不要把所有请求都放在入口处
  • 组件-尽可能采用拆分组件-延展组件- 低开门-高入户
  • 设计-多层架构-高内聚低耦合-公用har包共(每个都引一份) 公用hsp包
  • 多任务处理-多线程-耗时任务(-IO操作-文件压缩-解压缩-裁剪-位移)
  • 主线程-子线程处理耗时任务(诸多限制

21.页面和自定义组件的生命周期是什么?

自定义组件和页面是什么:

  • 自定义组件:@Component装饰的UI单元,它可以组合多个系统组件实现UI的复用,可以调用组件的生命周期。
  • 页面:即应用的UI页面。可以由一个或者多个自定义组件组成,@Entry装饰的自定义组件为页面的入口组件,即页面的根节点,一个页面有且仅能有一个@Entry。只有被@Entry装饰的组件才可以调用页面的生命周期

页面生命周期,即被@Entry装饰的组件生命周期,提供3个生命周期接口:

  • onPageShow:页面每次显示时触发一次,包括路由过程、应用进入前台等场景。
  • onPageHide:页面每次隐藏时触发一次,包括路由过程、应用进入后台等场景。
  • onBackPress:当用户点击返回按钮时触发。

组件生命周期,即一般用@Component装饰的自定义组件的生命周期,提供2个生命周期接口:

  • aboutToAppear:组件即将出现时回调该接口,具体时机为在创建自定义组件的新实例后,在执行其build()函数之前执行。
  • aboutToDisappear:aboutToDisappear函数在自定义组件析构销毁之前执行。不允许在aboutToDisappear函数中改变状态变量,特别是@Link变量的修改可能会导致应用程序行为不稳定。

生命周期流程如下图所示,下图展示的是被@Entry装饰的组件(页面)生命周期。

根据上面的流程图,我们从自定义组件的初始创建、重新渲染和删除来详细解释。

自定义组件的创建和渲染流程

  1. 自定义组件的创建:自定义组件的实例由ArkUI框架创建。
  2. 初始化自定义组件的成员变量:通过本地默认值或者构造方法传递参数来初始化自定义组件的成员变量,初始化顺序为成员变量的定义顺序。
  3. 如果开发者定义了aboutToAppear, 则执行aboutToAppear方法
  4. 在首次渲染的时候,执行build方法渲染系统组件,如果子组件为自定义组件,则创建自定义组件的实例。在首次渲染的过程中,框架会记录状态变量和组件的映射关系,当状态变量改变时,驱动其相关的组件刷新。

当应用在后台启动时,此时应用进程并没有销毁,所以仅需要执行onPageShow。

自定义组件重新渲染

事件句柄被触发(比如设置了点击事件,即触发点击事件)改变了状态变量时,或者LocalStorage / AppStorage中的属性更改,并导致绑定的状态变量更改其值时:

  1. 框架观察到了变化,将启动重新渲染。
  2. 根据框架持有的两个map(自定义组件的创建和渲染流程中第4步),框架可以知道该状态变量管理了哪些UI组件,以及这些UI组件对应的更新函数。执行这些UI组件的更新函数,实现最小化更新。

自定义组件的删除

如果if组件的分支改变,或者ForEach循环渲染中数组的个数改变,组件将被删除:

  1. 在删除组件之前,将调用其aboutToDisappear生命周期函数,标记着该节点将要被销毁。ArkUI的节点删除机制是:后端节点直接从组件树上摘下,后端节点被销毁,对前端节点解引用,前端节点已经没有引用时,将被JS虚拟机垃圾回收。
  2. 自定义组件和它的变量将被
  3. 删除,如果其有同步的变量,比如@Link@Prop@StorageLink,将从同步源上取消注册。

不建议在生命周期aboutToDisappear内使用async await,如果在生命周期的aboutToDisappear使用异步操作(Promise或者回调方法),自定义组件将被保留在Promise的闭包中,直到回调方法被执行完,这个行为阻止了自定义组件的垃圾回收。

22.前端的同步和异步 promise/then/catch

📎2.同步和异步.mp4

先执行同步代码 - 微任务(promise) - 宏任务(set TimeOut)

23.你是什么时候开始学习鸿蒙的,怎么学习的?

首先2023年的华为的遥遥领先发布,个人开发者发现华为即将退出Next版本,不再支持安卓,单框架,您知道吗,华为和我们大学有合作,我们其实从去年就开始研究鸿蒙了,然后版本是从2.0就开始了,直到现在4.0更新,而且目前国内有很多企业都在对Next跃跃欲试,所以我们课题组搞了一个项目,所有的内容都是我们老师教的,我们老师挺厉害的,基本上把鸿蒙4.0全吃透了,带领我们做了一个项目,包括对于Next版本的一次提前预判,这个老师把4.0能够遇到的问题和坑点都给我们讲了,而且我们学校准备用这个课题去参加一个华为鸿蒙的大赛。

因为安卓和ios已经固化了 甚至逐渐消亡,所以看到国产的操作系统还是非常兴奋,就义无反顾的来学习和开发应用了,如果您问我关于前端的知识点,我可能答的会不让您特别满意,但是我能利用了将三个月的时间把鸿蒙的开发基本都摸了一遍,我觉得我们完全的去聊鸿蒙,这里所有的坑点和一些注意点其实我都玩过一遍啦。

24.简单介绍下鸿蒙的状态管理?

鸿蒙的状态和前端的WVVM差不多,都是数据驱动视图,数据变化,会驱动视图变化,但是它又不像Vue,我们需要手动的实现双向绑定,next版本里面基本上很多表单组件都支持双向绑定,那么如果开发一个正常的业务,就需要用到状态台管理的修饰符。

如果是父组件,用state

如果是父子组件通信,用Prop和Link

如果是跨级组件,用Provide和Consume

如果是跨应用,用StorageLink / StorageProp

25.如何更新深层次的嵌套数据?

首先呢。State是驱动UI更新的修饰符,被State修饰的数据如果发生了变化,注意,这里只能监听到数据的第一层的变化,因为文档中说的非常清楚,state只能监测到Object.keys(变量)列出来的属性,它的执行会驱动build函数的重新执行,我不知道您是否想问鸿蒙里面是不是会有虚拟dom,这个我可以明确的说没有,因为方舟编译器是直接将arkTS映射底层字节码,所以我们的应用在4.0和next应该少用或者禁用动态类型,如果说我们想要更新深层次的嵌套数据,首先直接给第二层的属性进行赋值,它不是不更新吗,没问题,我直接把它第一层的对象重新new一下重新赋值不就可以了吗,或者说我还有一种方案,我更新第二层的数据之前,捎带手更新一下第一层的数据是不是也可以。

别着急,还有Prop和Link呢?您还想听吗

在4.0里面Prop有个硬伤, 类型是受限的,我们一般只能传个文本或者布尔类型,我觉得在next版本里面就可以大展拳脚了,为什么呢?我之前的项目里面有个上传功能,如果prop类型可以传对象或者数组,我就不用使用ObjectLink了, 所以说期待Next吧。

Link就是双向拉,这个用法就是非常简单,直接传过去,接过来想改就改,想取就取,但是有个硬伤,必须是$开头。这就导致我循环的时候,我没办法传link,所以我又得用ObjectLink拉。

26.鸿蒙组件是如何通信的?

最简单的-不用任何修饰符,可以传参,但是只能第一次生效-父传子

  • 可以通过Prop修饰符传基本的 字符串/数字/布尔 父传子
  • 子传父- 父给子传一个函数,子调用父的函数(注意this的问题)
  • link 子更新Link,父watch这个state
  • 父更新state, 子watch link
  • provide/consume
  • 事件总线- emitter/context.eventhhub.on/emit

其实我这个项目中还有还有ability通过want通信,就是类似于我拉起一个应用,给他传参数的通信。

27.你有没有封装过什么鸿蒙的组件?简单说一下过程

我之前的项目我封装了十三个组件,这里面有 列表, 加载进度,上传组件,卡片,卡片选项,弹出确认按钮,预览组件,选项组件组件

过程: 确定我们的业务需求,一定要做一件事,就是组件和业务的解耦,拿HmList举例, 支持下拉刷新,支持上拉加载,并且重新封装了下拉刷新的动画,这个组件还有lazyForEach优化了性能,还用avplayer播放了短音频,还支持各种的文本定制,其实就是借鉴前端里面的组件库 Vant/Antd 里面的List, 让我们用组件的人只传入几个参数就可以了。

28.LazyForEach数据懒加载是什么?

LazyForEach从提供的数据源中按需迭代数据,并在每次迭代过程中创建相应的组件。当在滚动容器中使用了LazyForEach,框架会根据滚动容器可视区域按需创建组件,当组件滑出可视区域外时,框架会进行组件销毁回收以降低内存占用。

本质上-LazyForEach和ForEach的用法基本一致,但是ForEach属于全量渲染,而LazyForEach属于只渲染可见区域。

● LazyForEach必须在容器组件内使用,仅有List、Grid、Swiper以及WaterFlow组件支持数据懒加载(即只加载可视部分以及其前后少量数据用于缓冲),其他组件仍然是一次性加载所有的数据。

29.如何建立一个子线程worker实现多线程来处理图片压缩?

  1. 建立多线程 - 子线程,worker.ThreadWorker( )
  2. 将主线程拿到的图片地址给到子线程,postMessage( )
  3. 子线程通过onMessage( )接收到图片数据,将原图压缩成一个新的图片,新的图片要存到一个位置,worker无法使用沙箱,所以要通过主线程传来一个沙箱目录路径来存储压缩后的图片。
  4. 压缩完成 将完成的图片信息通过postMessage( )回传给主线程
  5. 子线程自动关闭 销毁
  6. 主线程继续后续上传操作

30.开发鸿蒙项目的流程是什么?

  • 创建项目
  • 导入一些资源/色值
  • 创建一些公共目录-组件库-数据模型-工具类-请求-常量
  • 读写首选项工具类
  • 封装请求工具类
  • 使用泛行工具统一处理响应数据
  • 封装组件库-列表-加载-上传-日历-弹出时间-卡片。。。
  • 分包
  • 发布上线

31.你这个项目中有什么你觉得比较难的点吗?

  • 封装请求工具- 统一处理返回类型,导入类型相对来讲比较费劲,因为要处理泛行-泛型传了好几层
  • 封装HmList组件- 这里只需要传入几个参数- onLoad-onRefresh-dataSource-finished- lazyForEach/播放音乐
  • 封装上传组件-唤出选择图片(4.0没有相机)- 上传(沙箱上传)-拷贝到沙箱-上传成功没有地址-uuid循环调用获取地址接口- 才把数据搞定(Next版本还是这个揍性)
  • 上报位置-4.0可以获取定位,但是没有地图组件-没有sdk集成-混合开发
  • 混合开发-两种通信方式-第一种-postMessage( 原生要创建两个端口,传一个端口给网页,网页拿到端口再用端口和原生通信 )- 第二种方式(注入sdk的模式)- 网页可以调用原生方法(硬伤=this的问题-异步的问题-)-异步的问题-采用( html调我,给我一个方法,我拿到值之后反调回去,再把值传入就可以了 )
  • HmSelectCard组件也很复杂,因为这个组件基础组件和业务组件解耦的一个应用,又要保证基础组件独立的,又要保证能够实现,所以难度比较大。

33.HarmonyOS和Open Harmony的关系?

harmonyOS :华为的操作系统。

open Harmony:捐给了开放原子开源基金会的一个开源系统。

34.你觉得开发鸿蒙有什么需要注意的地方吗?

兄弟多了去了,注意点的特别多,您想问哪方面的?

● Next版本的类型是个最大的问题!!!!!

● 调试问题-模拟器的问题- 只能用debugger,日志系统是个废的

● 真机没有办法debugger

● 模拟器不行的地方 真机不一定不行

● 调试不好调试,因为没有热更新,下一代Next也不行。

● 弹出或者直接显示文本到页面是最直观最快的

● 文档的问题-不好找-用法和代码验证起来需要大量的时间

35.假设现在给你一个项目重构成鸿蒙,你会如何开展你的工作

  • 按照神领的基础搭建来

36.鸿蒙的系统能力你用过哪些?

  • 弹窗
  • 路由跳转
  • 声音播放-av-player(Video)
  • 震动
  • 打电话
  • 定位(需要用户授权)
  • 发布通知
  • ability通信-传参
  • 网络权限
  • 首选项
  • 文件选择
  • 后台订阅闹铃

37.分享一下你做鸿蒙开发过程中的一些坑点吧

38.你项目中的权限是如何管理的

● 进入应用-判断token(坑点-只能用首选项获取token)- 跳转到主页/登录页

● 销毁应用

● 进入登录页- 获取token(存储到持久化/首选项)-跳到主页

● token超时- 销毁token(销毁持久化/首选项)-跳回登录页

39.简历技能?

鸿蒙技能:

精通/熟练掌握HarmonyOSNEXT版本中的ArkTS语法及ArkUI组件库的应用,熟练通过4.0提供的单向状态机模式完成UI的绘制,数据交互,页面跳转及系统开发,熟练掌握Ability能力集的应用及开发,如ability通信,want传参,通知发布,声音播放,文件上传,数据请求,动画控制,UI组件库封装,基础工具封装,混合开发sdk封装,及混合开发模式下的sdk通信,状态更新及坑点,持久化数据管理及本地用户首选项数据管理,app架构设计,har包单独拆分,及最终生成p12, csr,cer,p7b发布签名证书上架到AGC。

40.项目描述?

个人独立完成一款 鸿蒙4.0app的设计,开发,及上架(因资质问题,待最终提交)。

交通/物流/餐饮/音乐/刷题/送货/超市/电商 app是一个基于鸿蒙4.0系统开发的原生鸿蒙应用。个人完成从0到1的系统设计及项目搭建。

● 独立设计基于该系统的组件库,完成十几个组件的从0到1的封装,并拆解到har包供其他模块使用

● 从0到1搭建项目架构体系,包含组件库层-UI适配层-数据管理层-工具使用层-请求工具层-页面设计层

● 重点攻关4.0设备中基于项目实际业务需要的技术特性-如上传图片(从0到1封装上传sdk, 独立解决4.0无法拿到响应地址的问题-通过设计避坑)

● 重点攻关4.0混合开发中无法进行有效通信的问题(提供两种优选解决方案-均可被项目组使用1.消息机制2.sdk机制)

● 从0到1封装基于性能及使用体验于一身的列表组件HwList, 重新定义下拉效果,全手写实现下拉刷新,上拉加载-播放声音(av_player单例模式),用法及其精简

● 设计及实现一套基于动态广告页模式的架构-基于首选项缓存及数据动态处理完成广告页可替换-可移除0可定制-可管理的业务

● 从0到1 实现底层请求工具封装的请求工具类,使得请求api随用随走,所用皆所得

● 系统能力集成-如震动-播放声音-授权申请再定位权限应用-web通信导航-打电话系统能力-发布通知-声音提醒-通知取消-ability能力通信,want传参等众多系统能力应用

● 发布-签名-证书-profile-上架AGC全流程实现

41.har包和hsp包的区别,动态包和静态包的区别,使用场景是啥?

1)作用:用于实现代码和资源的共享。同一个Library类型的Module可以被其他的Module多次引用,合理地使用该类型的Module,能够降低开发和维护成本。Library类型的Module分为Static和Shared两种类型,编译后会生成共享包。

  • Static Library:静态共享库。编译后会生成一个以.har为后缀的文件,即静态共享包HAR(Harmony Archive)。
  • Shared Library:动态共享库。编译后会生成一个以.hsp为后缀的文件,即动态共享包HSP(Harmony Shared Package)。

2)HAR与HSP两种共享包的主要区别体现在:

3)使用场景:

har包适用于不同项目下代码和资源共享,可以上传至openharmnony第三方共享仓开源,也可以在公司内搭建私有仓库进内共享。hsp适用于单个项目内容三层架构按功能模块分包,有利于降低打包体积,目前hsp还不能项目间共享。

性能优先用HAR包。

体积优先用HSP包。

43.app启动速度如何优化?

44.有哪些锁?

45.性能优化有哪些,例如list中item高度不同等等,请求列表加载慢数据如何缓存。

46.一共封装13个组件,想让5个组件被一个模块依赖,另外8个组件被其他模块依赖。

47.你会断点续传吗?如何实现。

断点续传本质的上的逻辑和前端的断点续传无疑,如果需要详细了解,可以参考这个文档面试题讲解 · 语雀

本质上-就是将一个大的文件分片拆分成若干片段,然后按照顺序一片一片的传递,也就是使用File的slice方法切割成Blob对象,当遇到网络堵塞或者异常的时候,此时服务器可以保存对应的已上传分片的数量和标记,再次请求时,可以通过唯一的md5的标识符判断是否已经传过分片,传过几片,得到第几片的标记,然后再从这个标记直接开始传即可。 大家可以参考上述链接中的详细实现。

48.下载大文件的时候,如何保证下载文件的完整性?

49.鸿蒙内存泄漏有哪些?怎么排查内存泄漏出现在哪里?

50.一个Ability启动的过程是什么?

51.组件化的认识?UI组件化,功能组件化,模块化,各个模块是用什么通信的?一个模块中,各个组件是怎么通信的?A,B,C三个功能组件库,A依赖B,C依赖B,那么A,C怎么通信?

52.app打包流程,资源打包在哪个过程?

53.ability有没有合法校验?怎么合法校验?

54.怎么做屏幕适配的?比如设备屏幕不合适,怎么适配?

55.鸿蒙的一次开发,多端部署的原理是什么?

56.鸿蒙云服务和微信小程序的区别是什么?

本质上很像,因为他们都遵循即用即走的模式,都可以免安装,轻量级的使用。

但是微信小程序本质上是在微信的安卓架构/ios架构上做了一层代码框架的机制,也就是说本质上- 微信使用安卓/ios的开发语言完成,小程序是微信自创的一套小程序的开发语言-类似于Vue的MVVM的框架,然后微信进行了统一的代码编译和代码解释,最终实现如今的小程序的效果

鸿蒙的元服务实现了代码大一统的效果,也就是开发一个鸿蒙应用和鸿蒙元服务本质上都直接使用鸿蒙的arkTS和arkUI来进行,调用通用能力和api方式一致,并且把应用变成元服务变得非常简单,只需要加个参数即可。

57.设备和设备之间怎么传数据?

58.页面持久化数据库是啥?

59.共享库之间怎么进行页面跳转?

如果是hsp的共享库,也是可以通过router进行跳转,

url规则为: @bundle:包名/module名称/ets/pages/页面名称

例如: `@bundle:com.itcast.zhongzhou_family/family/ets/pages/FamilyDetailPage`

60.你在项目中用过线程通信吗,线程是怎么进行通信的?

61.你在项目中使用首选项主要用来做什么?

62.buffer是什么与16进制和数组有什么关系吗?

63.对于一些公共的样式你是怎么做的?有没有什么优化的方式?

64.说一下全局存储状态用哪些方式?

LocalStorage- 内存化存储- 局部可用

AppStorage- 内存化存储- 全局可用

PersitentStoreage- 写入磁盘(沙箱) 全局可用

首选项- 写入磁盘- 全局可用

关系型数据库- 写入磁盘

65.请简述鸿蒙OS与Android OS的主要区别是什么?

设备兼容性:鸿蒙OS是一款面向各种设备的分布式操作系统,支持手机、平板电脑、智能手表、智能家居、汽车等多种设备类型,并能在这些设备之间实现无缝切换和共享数据。而Android系统则主要用于移动设备,如手机和平板电脑。
系统架构:鸿蒙OS采用分布式技术架构,通过分布式技术实现多设备间的协作和数据共享,更加灵活、安全、高效。而Android则采用单一设备架构,其多设备协作能力较弱,数据共享相对不便。
应用生态:Android系统已经建立了非常完善的应用生态系统,拥有数百万的应用程序,涵盖了各种应用场景。相比之下,鸿蒙OS的应用程序数量较少,生态系统相对不成熟。不过,鸿蒙OS的应用程序数量正在不断增长,未来可能会吸引更多的开发者和应用。
安全性:鸿蒙OS采用了多层安全防护措施,包括安全隔离、安全通信、安全识别等,相比Android更加安全。此外,鸿蒙OS还采用了一种名为“微内核”的操作系统内核架构,该架构的安全性和稳定性都非常高。
性能和效率:鸿蒙OS在性能和效率方面进行了优化,采用了分布式架构,可以根据设备的资源情况进行智能调度和管理,旨在提供更流畅的用户体验。而Android系统在某些低端设备上可能存在卡顿和性能瓶颈的问题。

66..api11 和 api12 的区别是什么?

参考话术: 之前开发项目的时候还没有到 api12,所以没有深入了解,但是api12 出来之后我去看了一下文档:

  1. 新增的功能可以参考,版本说明看看最新的功能,目前还没去翻阅,用到了对应的功能再去找哈 文档中心 ()
  2. 装饰器多了不少新的,不过文档里面说 都是在逐步开发中,准备等稳定了再去测试文档中心
  3. 咱们现在的项目是 api 几呢,如果是 api11,最新的开发工具可以一键迁移呢,如果是新开的 api12 的项目的话,我准备做到了对应的功能再去翻文档哈,因为华为这边的 api 变化的还是比较勤快的,我一般都是随用随查哈,看文档,看示例,codelabs,还有论坛什么的。

二. ArkUI相关:

65.什么是线性布局?

线性布局(LinearLayout)是开发中最常用的布局,通过线性容器 Row 和 Column 构建。Column容器内子元素按照垂直方向排列,Row容器内子元素按照水平方向排列。

66.线性布局对齐方式?

主轴: 属性: justifyContent()

交叉轴: 属性: alignItems()

67.什么是弹性布局?

● 弹性布局分为单行布局和多行布局。默认情况下,Flex 容器中的子元素都排在一条线(又称“轴线”)上。子元素尺寸总和大于 Flex 容器尺寸时,子元素尺寸会自动挤压。
● wrap 属性控制当子元素主轴尺寸之和大于容器主轴尺寸时,Flex 是单行布局还是多行布局。在多行布局时,通过交叉轴方向,确认新行排列方向。

68.绝对定位和相对定位的区别?

绝对定位:position,相对父组件左上角进行偏移, 不占位置

相对定位:offset,相对自身左上角进行偏移, 占位置

69.什么是层叠布局(堆叠)?

层叠布局通过 Stack 容器组件实现位置的固定定位与层叠,容器中的子元素依次入栈,后一个子元素覆盖前一个子元素,子元素可以叠加,也可以设置位置.

通过 alignContent 来设置子组件的位置

70. Swiper容器组件?

Swiper组件提供滑动轮播显示的能力。Swiper本身是一个容器组件,当设置了多个子组件后,可以对这些子组件进行轮播显示。

71.@Extend、@Styles、@Builder 区别?

72.网格布局 Grid/GridItem?

网格容器,由“行”和“列”分割的单元格所组成,通过指定“项目”所在的单元格做出各种各样的布局。

73.容器组件 Scroll?

可滚动的容器组件,当子组件的布局尺寸超过Scroll的尺寸时,内容可以滚动。只支持一个子组件。

74.容器组件Tabs?

通过页签进行内容视图切换的容器组件,每个页签对应一个内容视图。里面要放一个TabContent( )

75. Badge组件?

以附加在单个组件上用于信息标记(气泡)的容器组件

76.页面布局上的性能和内存上的注意事项

1、使用row/column+layoutweight代替flex容器使用

2、scroll嵌套list/grid容器时,要设置容器的宽高,数组数据渲染尽量使用lazyforeach渲染item

3、组件的显隐设置,要使用if语句来判断,避免使用visibility

4、list/grid容器要根据具体场景来使用cachecount,避免卡顿

77.ArkUI的两大开发范式是什么,区别是什么?

ArkUI推荐使用声明式开发范式 , 其他的框架有参考类Web开发范式。

类Web开发范式:采用经典的HTML、CSS、JavaScript三段式开发方式,即使用HML标签文件搭建布局、使用CSS文件描述样式、使用JavaScript文件处理逻辑。该范式更符合于Web前端开发者的使用习惯,便于快速将已有的Web应用改造成方舟UI框架应用。

声明式开发范式:采用基于TypeScript声明式UI语法扩展而来的ArkTS语言,从组件、动画和状态管理三个维度提供UI绘制能力。

三. ArkTS语法相关:

78.ArkTS常见的数据类型有哪些?

1为什么要有数据类型
●为了程序员更好的操作数据,对数据进行了分类
2分类
●基本数据类型
●复合数据类型
●对象类型
●函数类型
●高级类型
3详细分类
●基本数据类型:
number: 表示数字,包括整数和浮点数。
string: 表示文本字符串。
boolean: 表示布尔值,即true或false。
null、undefined: 分别表示null和undefined。
symbol: 表示唯一的、不可变的值。
●复合数据类型
array: 表示数组,可以使用number[]或Array<number>来声明其中元素的类型。
tuple: 表示元组,用于表示固定数量和类型的数组。
enum: 表示枚举类型,用于定义具名常量集合。
●对象类型
object: 表示非原始类型,即除number、string、boolean、symbol、null或undefined之外的类型。
interface: 用于描述对象的结构,并且可以重复使用。
●函数类型
function: 表示函数类型。
void: 表示函数没有返回值。
any: 表示任意类型。
●高级类型
union types: 表示一个值可以是几种类型之一。
intersection types: 表示一个值同时拥有多种类型的特性。

79.typeof 返回的数据类型?

1是什么
●typeof用于检测数据的类型, 一般用于检测简单数据类型
2返回结果
●'number' 'string' 'boolean' 'undefined' 'object' 'function'
3特殊情况

3.1. typeof null 返回 'object'

3.2. typeof array 返回 'object'

80.转布尔值返回false的情况有哪些?

常见情况: undefined 0 "" null false NaN 不成立的表达式

81.any 类型的作用是什么?

any类型的作用是允许我们在编写代码时不指定具体的类型,从而可以接受任何类型的值。使用any类型相当于放弃了对该值的静态类型检查,使得代码在编译阶段不会对这些值进行类型检查。
主要情况下,any类型的使用包括以下几点:
●当我们不确定一个变量或表达式的具体类型时,可以使用any类型来暂时绕过类型检查。
●在需要与动态类型的JavaScript代码交互时,可以使用any类型来处理这些动态类型的值。
●有时候某些操作难以明确地定义其类型,或者需要较复杂的类型推导时,也可以使用any类型。
滥用的后果:尽管any类型提供了灵活性,但由于它会放弃TypeScript的静态类型检查,因此滥用any类型可能会降低代码的健壮性和可维护性。当滥用any类型时,可能会导致以下后果:

滥用any类型会导致代码失去了TypeScript强大的类型检查功能,带来了如下问题:
●可能引入未知的运行时行为和错误。
●降低了代码的可维护性和可读性,因为难以理解某些变量或参数的具体类型。
因此,在实际开发中,应尽量避免过度使用any类型。可以通过合适的类型声明、接口定义和联合类型等方式,提高代码的健壮性和可维护性。

82.== 和 === 的区别?

1是什么?
●都是我们的比较运算符, 用来判断数据相等还是全等
2区别
●== 表示是相等,比较值是否相等
●=== 表示是全等,不仅比较值,也比较类型是否相等

83.null 和 undefined 的区别?

1是什么?
●都是都属于数据类型中的基本数据类型
2区别?
●null 表示空值 typeof null 返回"object"
● 返回null的情况: 没有回去到元素, 没有获取到本地存储数据
●undefined 表示未定义 typeof undefined 返回"undefined"
●返回undefined的情况: 声明变量没有赋值

84.let、const和@State的区别?

1是什么?

都是用来声明变量的关键字

2区别 ?

let和const 的特点:

2.1 不能在定义前使用

2.2 指定的类型和存储的类型要一致

2.3 不能重复定义

const和@State的特点:

2.4 定义必须要赋值

2.5 let/const 值改变不能, 在UI中更新

2.5 @State值改变, 可以在UI中更新

85.值类型和引用类型的区别?

1是什么
●ArkTS中数据分为值类型(基本数据类型)和引用类型(复杂数据类型)
2区别
2.1存储位置不一样值类型的会保存在栈内存中 引用类型的变量名(地址)会存在栈内存中,但是值会存储在堆内存中
2.2赋值方式不一样值类型的直接赋值,赋的值本身 引用类型赋值,赋的是地址(影响=>修改值会相互影响=>解决:浅拷贝或深拷贝)

86.0.1加0.2是否等于0.3? 原因和解决方法

1是什么?
●浮点数精度丢失问题, 0.1+0.2 不等于 0.3
2原因?
●0.1和0.2在二进制中却是一个表现不出来的无限不循环数,所以只能取一个近似数。而计算机精度有限,所能表现的值而非真正的0.1,0.2,所以自然相加时有偏差。
3解决
●我们可以先将其转化为整数,运算之后,然后再将其转化为小数
●方法 filter加includes

87.数组的方法和作用?

1是什么?
●数组是有序的数据的集合
2方法?
●定义数组: let 数组名:类型[] = [数据1,数据2,...]
●数组取值: 数组名[下标]
●数组添加内容:
- 数组.push(数据1,...) 向数组最后添加
- 数组.unshift(数据1,....) 向数组最前添加
●数组删除内容:
- 数组.pop() 删除数组的最后一个
- 数组.shift() 删除数组的第一个
●指定删除数组内容
- 数组.splice(下标, 个数)

88.函数相关内容

1是什么?
●用于封装可以重复执行的代码块, 帮助我们实现代码的复用和封装
2使用方式?
●可以通过funciton 关键字、箭头函数等形式去定义

●当我们没有提供函数实现的情况下,有两种声明函数类型的方式

3函数参数
●函数调用传递的参数是-实参
●函数定义写的参数是-形参
4可选参数
当函数的参数可能是不存在的,只需要在参数后面加上 ? 代表参数可能不存在。

5剩余类型
剩余参数与JavaScript的语法类似,需要用...来表示剩余参数
如果剩余参数 rest 是一个由number类型组成的数组。

89.说一说数组常用的API

数组都是 Array 的实例化对象,它提供了很多的 方法来帮助我们快速处理数据。
●push() - 在数组末尾添加一个或多个元素,并返回新的长度。
●pop()- 删除数组的最后一个元素,并返回那个元素。
●shift()- 删除数组的第一个元素,并返回那个元素。
●unshift()- 在数组的开始添加一个或多个元素,并返回新的长度。
●slice()- 返回数组的一个浅拷贝。
●splice()- 通过删除现有元素和/或添加新元素来更改一个数组的内容。
●concat()- 连接两个或更多数组,并返回一个新数组。
●join()- 将数组中的所有元素转换为一个字符串。
●reverse()- 颠倒数组中元素的顺序。
●sort()- 对数组的元素进行排序。
●forEach()- 遍历数组中的每个元素并执行回调函数。
●map()- 创建一个新数组,其结果是该数组中的每个元素是调用一次提供的函数后的返回值。
●filter()- 创建一个新数组, 其包含通过所提供函数实现的测试的所有元素。
●reduce()- 对数组中的每个元素执行一个由您提供的reducer函数(升序执行),将其结果汇总为单个返回值。
●reduceRight() - 对数组中的每个元素执行一个由您提供的reducer函数(降序执行),将其结果汇总为单个返回值。

90.new操作符做了什么?

1.创建一个新对象
2.函数内部的this指向这个对象
3.执行构造函数代码
4.返回新对象

91.说说你对泛型的理解?

1是什么
●泛型程序设计是程序设计语言的一种风格或范式 , 定义函数,接口或者类的时候,不预先定义好具体的类型,而在使用的时候在指定类型的一种特性.
2使用方式
●泛型通过<>的形式进行表述,可以声明:
- 函数

- 接口

- 类

92.说一下interface和type的区别

相同点:

1. 都可以描述 '对象' 或者 '函数' 。

2. 都允许拓展(extends):interface 和 type 都可以拓展,并且两者并不是相互独立的,也就是说 interface 可以 extends type, type 也可以 extends interface 。 虽然效果差不多,但是两者语法不同。

不同点:
1.type 可以而 interface 不行
type 可以声明基本类型,联合类型,元组

type 可以使用 typeof 获取实例的类型进行赋值

2.interface 可以而 type 不行
interface 能够声明合并

一般来说,如果不清楚什么时候用interface/type,能用 interface 实现,就用 interface , 如果不能就用 type 。接口和类型别名的区别:
●接口定义了一个契约,描述了对象的形状(属性和方法),以便在多个地方共享。它可以被类、对象和函数实现。
●类型别名给一个类型起了一个新名字,便于在多处使用。它可以用于原始值、联合类型、交叉类型等。与接口不同,类型别名可以用于原始类型、联合类型、交叉类型等,而且还可以为任意类型指定名字。

93.枚举(enum)是什么,它的优势,应用案例。枚举和常量枚举的区别?

枚举是一种对数字值集合进行命名的方式。它们可以增加代码的可读性,并提供一种便捷的方式来使用一组有意义的常量。

枚举和常量枚举的区别:
●枚举可以包含计算得出的值,而常量枚举则在编译阶段被删除,并且不能包含计算得出的值,它只能包含常量成员。
●常量枚举在编译后会被删除,而普通枚举会生成真实的对象。

94.什么是联合类型和交叉类型?

联合类型表示一个值可以是多种类型中的一种,而交叉类型表示一个新类型,它同时具备了多个类型的特性。

95.什么是类型断言(Type Assertion)

类型断言允许程序员手动指定一个值的类型。这在需要明确告诉编译器某个值的类型时非常有用。

96.const和readonly的区别?

当在ArkTS中使用const和readonly时,它们的行为有一些显著的区别:
●const:
const用于声明常量值。一旦被赋值后,其值将不能被重新赋值或修改。
常量必须在声明时就被赋值,并且该值不可改变。
常量通常用于存储不会发生变化的值,例如数学常数或固定的配置值。

●readonly:
readonly关键字用于标记类的属性,表明该属性只能在类的构造函数或声明时被赋值,并且不能再次被修改。
readonly属性可以在声明时或构造函数中被赋值,但之后不能再被修改。
readonly属性通常用于表示对象的某些属性是只读的,防止外部代码修改这些属性的值。

总结来说,const主要用于声明常量值,而readonly则用于标记类的属性使其只读。

97.interface可以给Function/Array/Class做声明吗?

interface可以被用来声明函数、数组和具有索引签名的类,从而帮助我们定义和限定这些数据结构的形式和行为。

98.说一说静态类型和动态类型有什么区别?

●静态类型是在 编译期间 进行类型检查,可以在编辑器或 IDE 中发现大部分类型错误。
●动态类型是在 运行时 才确定变量的类型,通常与动态语言相关联。
静态类型(Static Typing)
定义:静态类型是指在编译阶段进行类型检查的类型系统,通过类型注解或推断来确定变量、参数和返回值的类型。
特点:静态类型能够在编码阶段就发现大部分类型错误,提供了更好的代码健壮性和可维护性。
优势:可以在编辑器或 IDE 中实现代码提示、自动补全和类型检查,帮助开发者减少错误并提高代码质量。
动态类型(Dynamic Typing)
定义:动态类型是指在运行时才确定变量的类型,通常与动态语言相关联,允许同一个变量在不同时间引用不同类型的值。
特点:动态类型使得变量的类型灵活多变,在运行时可以根据上下文或条件动态地改变变量的类型。
优势:动态类型可以带来更大的灵活性,适用于一些需要频繁变化类型的场景。
区别总结
时机差异:静态类型在编译期间进行类型检查,而动态类型是在运行时才确定变量的类型。
代码稳定性:静态类型有助于在编码阶段发现大部分类型错误,提高代码稳定性;动态类型对类型的要求较为灵活,但可能增加了代码的不确定性。
使用场景:静态类型适合于大型项目和团队,能够提供更强的类型安全性;动态类型适用于快速原型开发和灵活多变的场景,能够更快地迭代和测试代码。

99.ArkTS中的模块化是如何工作的,举例说明?

●TypeScript 中使用 ES6 模块系统,可以使用 import 和 export 关键字来导入和导出模块。
●可以通过 export default 导出默认模块,在导入时可以使用 import moduleName from 'modulePath'。

100.如何约束泛型参数的类型范围?

可以使用泛型约束(extends关键字)来限制泛型参数的类型范围,确保泛型参数符合某种特定的条件。

101.什么是泛型约束中的 keyof 关键字,举例说明其用法。

●keyof 是 TypeScript 中用来获取对象类型所有键(属性名)的操作符。
●可以使用 keyof 来定义泛型约束,限制泛型参数为某个对象的键。

102.什么是装饰器,有什么作用,如何在ArkTS中使用类装饰器?

●装饰器是一种特殊类型的声明,可以附加到类、方法、访问符、属性或参数上,以修改其行为。
●在 ArkTS 中,装饰器提供了一种在声明时定义如何处理类的方法、属性或参数的机制。

103.如何实现class继承?

继承是一种从另一个类获取一个类的属性和行为的机制。它是面向对象编程的一个重要方面,并且具有从现有类创建新类的能力,继承成员的类称为基类,继承这些成员的类称为派生类。

104.说一说对于递归的理解?

方法或函数调用自身的方式称为递归调用,调用称为递,返回称为归。 简单来说就是:自己调用自己
为什么使用递归 ?递归的优缺点 ?
●优点:代码的表达力很强,写起来简洁。
●缺点:空间复杂度高、有堆栈溢出风险、存在重复计算、过多的函数调用会耗时较多等问题。
递归常见问题及解决方案
●警惕堆栈溢出:可以声明一个全局变量来控制递归的深度,从而避免堆栈溢出。
●警惕重复计算:通过某种数据结构来保存已经求解过的值,从而避免重复计算。

105.都用过哪些装饰器?

@State 用于定义状态变量, 数据变化, 视图更新
@Watch如果开发者需要关注某个状态变量的值是否改变,可以使用 @Watch 为状态变量设置回调函数。而且, @Watch无法单独使用,必须配合状态装饰器,比如@State、@Prop、@Link

@Link父子双向同步
@Prop 父子组件通讯
@Observed,@ObjectLink
作用: 用于嵌套数据中,可以监测到数据的改变
数据和类型层面:interface->class ,@Observed修饰
组件层面: 子组件接受数据用@ObjectLink, 对象数组循环,更改对象的属性(可参照知乎评论,购物车案例)
@Provide\Consume 祖先和后代同步

106.项目中接口请求的数据,需要进行缓存吗?处理过程是啥?

根据自己项目中的业务逻辑描述,MapKit需要将当前屏幕中展示的6个瓦片数据图片缓存到沙盒中,杀掉进程后,断开网络,打开app会展示缓存的数据瓦片。

107.页面性能和内存优化?

性能和内存是两个指标,有时候追求性能,就需要做缓存处理,吃内存。
页面内存优化,主要在减少缓存的处理。
性能优化,主要在于减少嵌套,减少动态渲染的频率。

108.localStorage和appStorage的区别,和对应的装饰器?

localStorage是页面级数据存储,在页面中创建实例,组件中使用@LocalStorageLink和@LocalStorageProp装饰器修饰对应的状态变量,绑定对应的组件使用比状态属性更灵活
appStorage是进程级数据存储,进程启动时自动创建了唯一实例,在各个页面组件中@StorageProp和@StorageLink装饰器修饰对应的状态变量。
localStorage和appStorage数据存取都是在主线程进行的,且api只提供了同步接口,存取数据时要注意数据的大小。

109.三方应用调用系统应用,对于ability的交互和传值有什么限制?除了数据大小方面

重点介绍自己对ability的理解,描述显式want和隐式want的区别,带入到对应面试项目中场景来
●启动应用内的UIAbility, 启动应用内的UIAbility并获取返回结果
●启动其他应用的UIAbility, 启动其他应用的UIAbility并获取返回结果
●启动UIAbility的指定页面:
显式Want启动:在want参数中需要设置该应用bundleName和abilityName,当需要拉起某个明确的UIAbility时,通常使用显式Want启动方式。
隐式Want启动:不明确指出要启动哪一个UIAbility,在调用startAbility()方法时,其入参want中指定了一系列的entities字段(表示目标UIAbility额外的类别信息,如浏览器、视频播放器)和actions字段(表示要执行的通用操作,如查看、分享、应用详情等)等参数信息,然后由系统去分析want,并帮助找到合适的UIAbility来启动。

110.在使用自定义组件时, 外部如何传递UI?

使用@BuilderParam装饰器, 该装饰器可以用于声明任意UI描述的一个元素,类似前端vue中的slot占位符。
●如果外部使用自定义组件时, 只传递一个UI, 可以使用尾随闭包的方式传入
○组件内有且仅有一个使用 @BuilderParam 装饰的属性,即可使用尾随闭包
○内容直接在 {} 传入即可
注意:此场景下自定义组件不支持使用通用属性。

●如果外部使用自定义组件时, 需要传递多个UI, 必须通过参数的方式来传入
○自定义组件-定义: 添加多个 @BuilderParam ,并定义默认值
○自定义组件-使用: 通过参数的形式传入多个 Builder,比如:

111.说一说你对于路由的理解?

页面路由: 指的是在应用程序中实现不同页面之间的跳转以及数据传递; 通过 Router 模块就可以实现这个功能。
页面栈:用来存储程序运行时页面的一种数据结构,遵循先进后出的原则, 页面栈最大容量是32个页面, 可以用 router.getLength 进行查看页面栈中页面的个数。
常见的路由跳转方式:
●router.pushUrl() : 目标页面不会替换当前页,而是压入页面栈。这样可以保留当前页的状态,并且可以通过返回键或者调用router.back()方法返回到当前页。

●router.replaceUrl(): 目标页面会替换当前页,并销毁当前页。这样可以释放当前页的资源,并且无法返回到当前页。

●router.back(): 返回

常见的路由模式:
Standard(多实例模式) 默认的跳转模式, 目标页面会被添加到页面栈顶,无论栈中是否存在相同url的页面;
Single(单实例模式) 如果目标页面的url已经存在于页面栈中,则会将离栈顶最近的同url页面移动到栈顶,该页面成为新建页。如果目标页面的url在页面栈中不存在同url页面,则按照默认的多实例模式进行跳转。

112.怎么理解函数的防抖和节流?

1是什么
●防抖: 单位时间内,频繁触发事件,只执行最后一次
●节流: 单位时间内,频繁触发事件,只执行一次
2 实现防抖/节流
●JS库 => lodash.js
●通过延时器自己手写

四. 计算机网络相关知识:

113.http和https的区别是啥?

1是什么
超文本传输协议,是一个简单的请求-响应协议
2区别

http是超文本传输协议,信息是明文传输,https则是具有安全性的加密传输协议

http和https用的端口不一样,前者是80,后者是443

114.你对WebSocket了解哪些?

1是什么
WebSocket 是HTML5一种新的协议。它实现了浏览器与服务器全双工通信,能更好的节省服务器资源和带宽并达到实时通讯,它建立在TCP之上,同HTTP一样通过TCP来传输数据
2区别
WebSocket是一种双向通信协议,在建立连接后,WebSocket服务器和浏览器都能主动的向对方发送或接收数据
WebSocket需要类似TCP的客户端和服务器端通过握手连接连接成功后才能相互通信

115.tcp和udp的区别?

1是什么
●TCP(传输控制协议)和UDP(用户数据报协议)是互联网协议族中的两个重要传输层协议
2区别
●TCP是一种面向连接的协议,通过三次握手建立可靠的连接。
●UDP是一种无连接的协议,数据以数据报的形式独立发送。
3应用场景
●TCP的应用场景=>网页浏览/文件传输/数据库访问等
●UDP的应用场景=>实时通信/流媒体等

116.常见的网络请求方式 ?

1 get => 获取数据
2 post => 提交数据
3 delete => 删除数据
4 put => 修改数据

117.请求报文和响应报文是什么?

●HTTP 请求报⽂的组成:请求⾏(请求⽅法、URL、HTTP 协议版本)、请求头、(空⾏)、请求体
●HTTP 响应报⽂的组成: 响应⾏(协议版本、状态码、状态码)、响应头、空⾏、响应体

118.Get 和 Post的区别是什么?

1是什么
●GET和POST是两种最基本的HTTP请求方法
2区别
●GET请求相对不安全,POST请求相对安全
●GET请求可以缓存,POST请求不能缓存
●GET请求有长度限制(每个浏览器的限制长度不同),POST请求没有长度限制
●GET只能传输字符串,POST可以传输多种类型数据
●GET请求入参在URL上,POST请求入参在Request body上

119.项目中网络请求常遇到的状态码有哪些?

1. 200 请求成功, 2开头的异步表示请求成功
2. 304 请求被允许,但请求内容没有改变, 3开头的一般请求完成
3. 4xx开头状态码=>客户端错误
3.1. 400 => 传参格式不正确
3.2. 401 => 权限认证未通过
3.3. 403 => 禁止访问(例如:你没有权限访问/禁止访问)
3.3. 404 => 请求资源被拒绝(例如: 地址写错/页面删除了/没有网)
3.4. 408 => 客户端请求超时
4. 500 内部服务器错误, 5开头的一般都是指服务器错误

120.什么是同源策略?怎么解决跨域问题?

1. 是什么
●同源策略是浏览器的一种安全策略, 所谓同源是指域名、协议、端口完全相同,如果有一项不同源则出现跨域
2. 解决跨域的方法:
●跨域是浏览器的保护机制,如果绕过浏览器,使用代理服务器去请求目标服务器上的数据,就不会受跨域影响。因此前端可以通过webpack配置(见:webpack阶段)devSever下的proxy选项,将/api开头的请求转发到真实服务器上, 可以在 开发环境 下解决跨域问题
●在 生产环境 下也可以使用nginx配置反向代理来解决跨域
3.跨域问题解决方法扩展
●jsonp: 实现方式是通过script标签传递数据,因为script的src属性请求不会被同源策略禁止,所以通过script标签的src属性去请求跨域数据, 但是jsonp只支持GET请求不支持POST请求, 所以没有广泛应用
●CORS: 跨域资源共享是一种机制,它使用额外的 HTTP 头来告诉浏览器, 准许访问来自不同源服务器上的指定的资源, 后端设置

122.在地址栏输入网址,到数据返回的过程是什么?

1. 缓存解析:浏览器获取到输入的URL后,会先去缓存中查找资源(提高查询速度)。如果有就从缓存中显示界面,则不再发送请求;如果没有,则发送http请求
2. 当发现缓存中不存在资源时,则发送http请求。在发送http请求之前,需要进行DNS域名解析(DNS域名解析:域名到IP地址映射的过程,域名的解析由DNS服务器来完成,解析后便可以获得域名对应的IP地址)
3. 与服务器进行TCP的三次握手,建立连接
4. 客户端发送请求,找到相应的资源库
5. 服务器发送HTTP响应报文给客户端,客户端获取到页面静态资源
6. TCP四次挥手关闭客户端和服务器的连接
7. 浏览器解析文档资源并渲染页面

123.浏览器解析文档过程是什么?

1 解析html资源,构建DOM 树
2 解析css资源,浏览器将CSS解析成树形的数据结构
3 JS通过DOM API和CSS API来操作DOM 树和CSS 树
4 解析完成后综合DOM 树和CSS 树会生成渲染树,计算每个元素的位置,这个过程就是回流
5 调用操作系统图形用户接口的绘制页面
6 页面绘制完成

124.原生 Ajax 请求的步骤?

1 创建异步对象 var xhr = new XMLHttpRequest()
2 设置请求行 xhr.open()
3 设置请求头 xhr.setRequestHeader() get请求没有请求头
4 设置请求体 xhr.send get请求没有请求体,参数为null
5 监视异步对象的状态变化 xhr.onreadystatechange(){}

125.怎么理解三次握手?

三次握手
●第一次握手:建立连接时,客户端发送syn包到服务器,等待服务器确认
●第二次握手:服务器收到syn包,必须确认客户的SYN,同时自己也发送一个SYN包(syn=y)到客户端
●第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK,此包发送完毕,客户端和服务器进入(TCP连接成功)状态,完成三次握手
●通俗:主机1告诉主机2,我可以向你请求数据吗。主机2告诉主机1,可以请求数据。主机1告诉主机2,那我来请求数据了,请求完成,实现三次握手

126.怎么理解四次挥手?

四次挥手
● 第一次挥手:主机1(可以使客户端,也可以是服务器端)向主机2发送一个FIN报文段;此时,主机1进入FIN_WAIT_1状态;这表示主机1没有数据要发送给主机2了
●第二次挥手:主机2收到了主机1发送的FIN报文段,向主机1回一个ACK报文段,主机1进入FIN_WAIT_2状态;主机2告诉主机1,我“同意”你的关闭请求
●第三次挥手:主机2向主机1发送FIN报文段,请求关闭连接,同时主机2进入LAST_ACK状态
●第四次挥手:主机1收到主机2发送的FIN报文段,向主机2发送ACK报文段,然后主机1进入TIME_WAIT状态;主机2收到主机1的ACK报文段以后,就关闭连接;此时,主机1等待2MSL后依然没有收到回复,则证明Server端已正常关闭,那好,主机1也可以关闭连接了
●通俗:主机1告诉主机2,我没有数据要发送了,希望断开连接。主机2接到请求后说,同意断开。主机2告诉主机1可以关闭连接了。主机1接到可以关闭的指令后,关闭连接,四次挥手完成

127.什么是Promise,特点是什么?

Promise 是异步编程的一种解决方案, 可以比较优雅的解决回调地狱和多个并发请求的问题
特点:
1.Promise对象只有三种状态
●异步操作“未完成”(pending)
●异步操作“已完成”(resolved,又称fulfilled)
●异步操作“失败”(rejected)
●异步操作成功,Promise对象传回一个值,状态变为resolved
●异步操作失败,Promise对象抛出一个错误,状态变为rejected
●promise的回调是同步的,then是异步的

128.如何解决回调地狱?

●可以链式调用, 解决回调地狱(回调函数嵌套产生回调地狱)问题
●async和await也可以解决回调地狱问题

129.Promise的方法有哪些,能说明其作用?

实例API:
1.Promise.prototype.then()
●是 Promise 实例的回调函数。接受两个回调函数作为参数。第一个回调函数是Promise对象的状态变为resolved时调用,第二个回调函数是Promise对象的状态变为rejected时调用
●返回的是另一个Promise对象,后面还可以接着调用then方法。
2.Promise.prototype.catch()
●用于指定发生错误时的回调函数。
●返回的也是一个 Promise 对象,因此还可以接着调用then方法
3.Promise.prototype.finally()
●finally方法用于指定不管 Promise 对象最后状态如何,都会执行的回调函数

静态API:
1.Promise.resolve()
●返回一个新的状态为resolve的promise对象
2.Promise.reject()
●返回一个新的状态为reject的promise对象
3.Promise.all()
●所有异步操作执行完后才执行回调
4.Promise.race()
●哪个结果返回来的快就是,哪个结果,不管结果是成功还是失败

130.async和await是干什么的?

1.async 用于申明一个 function 是异步的,而 await 用于等待一个异步方法执行完成。
2.await 只能出现在 async 函数中。
3.async 函数返回的是一个 Promise 对象,后面可以用then方法。
4.async/await的错误处理
●使用try/catch来捕获错误
5.async/await的中断(终止程序)
●在async/await中想要中断程序就很简单了,因为语义化非常明显,其实就和一般的function写法一样,想要中断的时候,直接return一个值就行,null,空,false都是可以的

131.Promise与Async/Await的比较

1.promise是ES6,async/await是ES7
2.async/await相对于promise来讲,写法更加优雅
3.捕捉错误
●promise错误可以通过.catch来捕捉
●async/await既可以用.then/.catch又可以用try-catch捕捉
4.虽然async/await在很多情况下可以提供更清晰和简洁的代码,但Promise也有其独特的优势。例如,处理多个并行异步操作时,Promise.all()通常是更好的选择

132.怎么理解事件循环机制(Event Loop)?

1.JavaScript 是一门单线程语言.单线程可能会出现阻塞的情况,所js分了同步任务和异步任务。
2.同步和异步任务分别进入不同的执行环境,同步的进入主线程,即主执行栈,异步的进入事件队列 。主线程内的任务执行完毕后,会去事件队列 读取对应的任务,推入主线程执行。 上述过程的不断重复就是我们说的 Event Loop (事件循环)。

133.什么是宏任务和微任务?

1.宏任务: 主线程代码(同步宏任务), setTimeout(异步宏任务) 等属于宏任务, 上一个宏任务执行完, 才会考虑执行下一个宏任务
2.微任务: promise .then .catch 的需要执行的内容, 属于微任务, 满足条件的微任务, 会被添加到当前同步宏任务的后面去执行

134. 宏任务和微任务执行顺序?

1.先执行主线程上的同步代码=>常规代码/promise
2.执行微任务的代码 => .then/.catch
3.再执行(异步)宏任务代码 => 定时器/延时器

135.Git如何管理一个项目?

1是什么
●git是一个分布式版本控制工具
2常见的命令
1. git clone将远程仓库的项目资料下载下来
2. git checkout -b dev (dev 为本地分支名)
3. git add .将工作区文件存在暂存区
4. git commit -m ""从暂存区存到仓储区
5. 使用git push将其上传到远程仓库,推送之前先pull一下

136.Git如何解决合并冲突?

1冲突的原因
●不同分支, 不同代码, 相同位置就会产出合并冲突问题
2解决办法
2.1. 如果是双方的代码是不同的业务,那就保留双方双方更改(vscode有提示)
2.2. 如果是重复的业务逻辑,那就选择采用当前更改(vscode有提示)

137.开发中如何避免git冲突?

100%避免git冲突是不可能的,但可以尽可能避免解决冲突
1可以 '少量多次' 提交
2提交代码之前, 先pull一下
3保持团队良好沟通, 协同开发, 及时同步信息

138.正在A分支开发,但是有紧急需求需要在B分支处理,此时A分支代码没有开发完整,又不能提交代码,这个时候暂存本地代码,怎么做?

1 git stash 暂存当前所有的修改
2 git stash save '备注内容'
3 git stash pop 应用最近的一次stash并删除

139.git merge 和 git rebase的区别?

1是什么
●git rebase 与 git merge 解决了相同的问题, 都是将一个分支的提交合并到另一分支上
2区别
●rebase会改变提交历史;merge则会保留真实的历史
●merge 是一个合并操作,会将两个分支的修改合并在一起,默认操作的情况下会提交合并中修改的内容
●merge 的提交历史记录了实际发生过什么,关注点在真实的提交历史上面
●rebase 并没有进行合并操作,只是提取了当前分支的修改,将其复制在了目标分支的最新提交后面
●rebase 操作会丢弃当前分支已提交的 commit,故不要在已经 push 到远程,和其他人正在协作开发的分支上执行 rebase 操作
●merge 与 rebase 都是很好的分支合并命令,没有好坏之分,使用哪一个应由团队的实际开发需求及场景决定

五. Harmony能力:

1.权限管理

以申请使用 麦克风、网络、相册、通讯录、定位 权限为例进行说明。

1.打开系统权限:在module.json5里配置ohos.permission.MICROPHONE系统权限。 然后在string.json中配置对应的reason。

2.申请用户授权:需要在EntryAbility中申请用户授权,我们创建一个manager,在manager里使用 requestPermissionsFromUser() 接口请求相应的权限。这时候用户打开应用,就会弹出权限申请。不管同意是否,这个权限只会申请一次就会记住。理想状态是用户允许,但是如果用户禁止了,下次也不会再申请,那将无法使用麦克风,导致我们的业务无法顺利执行,所以我们应该在执行业务前,检测是否有权限。

3.检查权限:需要CheckAceessTokenSync方法来获取是否拥有了权限,但是CheckAceessTokenSync 需要tokenId, token Id(应用唯一标识)需要在应用信息中获取。如果没有权限,我们只能跳转系统设置,让用户自行打开。

参考话术:

向用户申请权限的步骤是比较固定的,而且在项目中要用到权限的地方比较多,所以我自己封装了一个权限管理的工具类PermissionManager。

这个工具类提供了完整的权限管理功能,包括检查权限、动态申请权限和引导用户去设置页打开权限。可以轻松地实现对应用权限的管理,确保应用在运行时有必要的权限。

逻辑易于理解和维护,使用了参数化的方式来传递权限类型和提示信息,使得在未来项目需要增加新的权限或修改提示信息时更加灵活和方便。

import { abilityAccessCtrl, bundleManager, common, Permissions } from '@kit.AbilityKit';
import { promptAction } from '@kit.ArkUI';

class PermissionManager {

  // 检查是否授权
  checkPermissions(permissions: Permissions[]) {
    // 程序访问控制管理器
    const atManager = abilityAccessCtrl.createAtManager()
    // 获取 bundle 应用信息
    const bundleInfo = bundleManager.getBundleInfoForSelfSync(bundleManager.BundleFlag
      .GET_BUNDLE_INFO_WITH_APPLICATION)
    // 提取 tokenID 标识(应用唯一标识)
    const tokenID = bundleInfo.appInfo.accessTokenId
    // 核心 API:checkAccessTokenSync,检查是否授予权限
    const grantStatus = permissions.map((item) => atManager.checkAccessTokenSync(tokenID, item))
    // every 检查每项结果是否都已授权
    return grantStatus.every(item => item === abilityAccessCtrl.GrantStatus.PERMISSION_GRANTED)
  }

  // 动态申请授权(首次弹窗申请)
  async requestPermissions(permissions: Permissions[]) {
    // 程序访问控制管理器
    const atManager = abilityAccessCtrl.createAtManager()
    // 核心 API:注意:只会在首次申请权限的时候弹出(异步的,等待用户操作)
    const requestResult = await atManager.requestPermissionsFromUser(getContext(), permissions)
    // every 检测是否所有的都是通过授权
    return requestResult.authResults.every(item => item === abilityAccessCtrl.GrantStatus.PERMISSION_GRANTED)
  }

  // 打开系统设置的权限管理页(处理授权结果)
  openPermissionSettingsPage() {
    // 设置页的 bundleName、abilityName、uri 都是固定写法
    // 注意:只修改 pushParams 为应用包名,用于打开应用的系统设置页
    const context = getContext() as common.UIAbilityContext
    // 获取 bundle 应用信息
    const bundleInfo = bundleManager.getBundleInfoForSelfSync(bundleManager.BundleFlag
      .GET_BUNDLE_INFO_WITH_APPLICATION)
    context.startAbility({
      bundleName: 'com.huawei.hmos.settings',
      abilityName: 'com.huawei.hmos.settings.MainAbility',
      uri: 'application_info_entry',
      parameters: {
        pushParams: bundleInfo.name // 打开指定应用的详情页面
      }
    })
  }

  // 如果拒绝,提醒去设置页中开启权限
  async requestPermissionsWithDialog(permissions: Permissions[], message: string) {
    // 申请权限
    const isAuth = await this.requestPermissions(permissions)
    // 如果拒绝权限
    if (isAuth === false) {
      // 开启弹窗提示
      const dialogRes = await promptAction.showDialog({
        alignment: DialogAlignment.Center,
        title: '温馨提示',
        message: message,
        buttons: [
          { text: '取消', color: $r('app.color.font_sub') },
          { text: '立即开启', color: $r('app.color.brand') }
        ]
      })
      // 点击立即开启按钮时,打开设置页
      if (dialogRes.index === 1) {
        this.openPermissionSettingsPage()
      }
    }
  }
}

// 导出 Manager
export const permissionManager = new PermissionManager()

2.身份认证

1.配置系统权限:在module.json5里配置ohos.permission.ACCESS_BIOMETRIC系统权限。

2.指定用户认证相关参数AuthParam(包括挑战值防止重放攻击、认证类型列表口令、人脸、指纹和认证等级)、通过WidgetParam配置认证界面的参数,调用getUserAuthInstance获取认证对象。

3.调用UserAuthInstance.on接口订阅认证结果

4.调用UserAuthInstance.start接口发起认证,通过IAuthCallback回调返回认证结果UserAuthResult。 当认证成功时返回认证通过类型(UserAuthType)和令牌信息(AuthToken)。

参考话术:

在项目中涉及到隐私空间,用户可以往隐私空间内保存照片、录音、笔记 等功能。用户在进入隐私空间前需要验证身份,以确保隐私空间访问的安全性。
隐私空间验证身份的方式整合了:
1图案锁组件 PatternLock,也就是手势解锁。
2人脸识别 / 指纹识别,通过鸿蒙内置的 User Auth Kit 实现
图案锁实现起来比较简单,我就介绍一下人脸识别/指纹解锁的认证方式吧:
我基于官方提供的接口进行二次封装,方便在后续项目中调用:
1多种认证方式支持: 这个封装支持多种认证方式,包括人脸识别和指纹解锁。
2全面的认证能力检测: 封装提供了更全面的认证能力检测功能,原本官方提供的 API (userAuth.getAvailableStatus) 只能检测一种认证能力,通过自己二次封装后可以同时检测多种认证能力是否支持。我建了一个认证列表,然后遍历认证类型列表,检测这两个检测能力是否可用。这样就可以在开启 人脸识别/指纹解锁 认证能力前进行能力检测,以确保所选认证方式在当前设备上可用。
3简洁的接口调用: 通过 Promise 对认证流程进行二次封装,使得发起原生用户认证流程变得简单。只需调用 我封装的startUserAuth() 方法,即可开始认证过程,不需要再配置参数。

import { userAuth } from '@kit.UserAuthenticationKit'

class UserAuthManager {
  // 挑战值
  challenge: Uint8Array = new Uint8Array([1, 2, 3, 411, 12])
  // 认证信任等级,等级越高,面容指纹检测时越严格
  authTrustLevel: userAuth.AuthTrustLevel = userAuth.AuthTrustLevel.ATL3
  // 认证类型列表
  authTypes: userAuth.UserAuthType[] = [
    userAuth.UserAuthType.FACE, // 面容
    userAuth.UserAuthType.FINGERPRINT,// 指纹
  ]

  // 查询认证能力是否支持
  checkUserAuthSupport() {
    // 遍历认证类型列表
    const res = this.authTypes.map((item) => {
      try {
        // 检测是否可用
        userAuth.getAvailableStatus(item, this.authTrustLevel)
        return true
      } catch {
        return false
      }
    })
    // some    条件中有一项为 true,结果就返回 true
    // every   条件中所有项为 true,结果才返回 true
    // 有一项支持即可
    return res.some(item => item === true)
  }

  // 发起原生用户认证
  startUserAuth(): Promise<boolean> {
    return new Promise((resolve, reject) => {
      // 获取认证对象实例
      const userAuthInstance = userAuth.getUserAuthInstance({
        challenge: this.challenge,
        authType: this.authTypes,
        authTrustLevel: this.authTrustLevel,
      }, {
        // 用户认证界面的标题, PIN(锁屏)密码时展示
        title: '请进行身份认证'
      })
      // 发起认证
      userAuthInstance.start()
      // 订阅认证结果
      userAuthInstance.on('result', {
        onResult(res) {
          if (res.result === userAuth.UserAuthResultCode.SUCCESS) {
            resolve(true)
          } else {
            resolve(false)
          }
          // 关闭订阅
          userAuthInstance.off('result')
        }
      })
    })
  }
}

export const userAuthManager = new UserAuthManager()

3.音视频能力

录音:

1.配置音频采集参数并创建AudioCapturer实例,通过AudioCapturerOptions配置音频采集参数包括采样率、通道数、编码格式、采样格式、音频渲染器类型等。
2.调用on(‘readData’)方法,订阅监听音频数据读入回调。
3.调用start()方法进入running状态,开始录制音频。
4.调用stop()方法停止录制。
5.调用release()方法销毁实例,释放资源。

播放:

1.配置音频渲染参数并创建AudioRenderer实例,通过AudioCapturerOptions配置音频采集参数。
2.调用on(‘writeData’)方法,订阅监听音频数据写入回调。
3.调用start()方法进入running状态,开始渲染音频。
4.调用stop()方法停止渲染。
5.调用release()方法销毁实例,释放资源。

录音AudioCapyurer:原理是监听话筒,通过音频流拿到buffer,不间断地写入一个音频文件,采用pcm格式。

播音AudioRenderer:原理是从一个文件中,不间断地拿出一段buffer交给AudioRenderer。

参考话术

我把录音和播放用一个类封装整合起来,方便管理和复用。

1权限管理: 该封装在音频录制前会先请求录音权限,如果权限未被授予,则会引导用户前往系统设置页面进行权限开启。

2音频流配置: 统一管理音频流的配置,包括采样率、通道数、采样格式和编码格式等,避免出现音频配置不统一而导致不可预知的问题。

3简洁的接口调用: 原本 PCM 录音和播放代码量其实不少,封装整合后,只需调用 startCapturer() 方法开始录制,调用 startRenderer() 方法开始播放。

4资源管理和释放: 在停止录制和播放结束 stop() 后及时释放资源 release(),包括释放音频采集器和音频渲染器等资源,防止资源泄露和内存占用过高,保证应用的稳定性和性能优化。

4.自动上报错误

参考话术
在应用中,如果程序中遇到了未被 try catch 捕获的错误,程序会闪退,这样的用户体验是非常糟糕的。我对未知错误进行了统一捕获,并自动上报至云端。


通过ErrorManager.on注册错误观测器,可以捕获到应用产生的 js crash,并且在注册错误观测器后,应用崩溃时进程不会闪退。


同时通过 faultLogger 获取故障日志,配合 http 或 axios 把故障日志内容自动上传到云端,定期监控和分析云端收集的错误日志,从而不断优化和迭代项目。

import { AbilityConstant, errorManager, UIAbility, Want } from '@kit.AbilityKit';
import { promptAction } from '@kit.ArkUI';
import { FaultLogger } from '@kit.PerformanceAnalysisKit';
import { axiosInstance } from '../common/utils';

let observerId = -1

export default class EntryAbility extends UIAbility {
  onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
    // 注册错误观测器。注册后可以捕获到应用产生的js crash,应用崩溃时进程不会退出!!!
    observerId = errorManager.on('error', {
      // 当应用产生未捕获的异常时的回调
      async onUnhandledException() {
        // 获取错误日志,用于上传到服务器
        const list = await FaultLogger.query(FaultLogger.FaultType.JS_CRASH)
        // 发送 POST 请求,上报第一条错误日志
        // 接口文档:https://apifox.com/apidoc/shared-575c0269-1ed9-4ffa-be46-bd5361822f36/
        await axiosInstance({
          method: 'POST',
          url: '/log/fault/report/single',
          data: list[0]
        })
      }
    })
  }

  onDestroy(): void {
    // 应用销毁 解除 errorManager 注册
    errorManager.off('error', observerId)
  }
}

5.地图

发送地图:

在AGC申请签名证书p12,csr,p7b,cer。

AGC中开启地图服务。

新建地图组件location,导入map Kit相关模块,放置MapCompoment{}组件, 就可以传入经纬度和zoom形成地图。

需要展示地图的话,可以新建弹层.bindContentCover(地图组件)

获取当前定位显示在地图上:

申请地图权限;

修改location组件:

初始化地图配置(默认经纬度,zoom),mapController来使能定位;

通过geoLocationManager.getCurrentLocation获取当前经纬度。

通过map.CameraUpdate=map.newCameraPosition移动相机位置。

还可以通过mapController.addCircle标记一个圈,可以传入半径等参数。

用geoLocationManager.getAddressesFromLocation拿街道名称。

还可以通过nearbySearch搜取周边的位置地点。

6.上拉加载,下拉刷新

下拉刷新:

list外包一个Refresh

参数双向绑定一个bool来控制

refresh往下拉的时候会触发一个事件.onStateChange(),它的RefreshStatus有四种状态,这样就可以实现下拉刷新了。

上拉加载:

list组件本身有一个.onReachEnd(),刷到list底部的时候先判断能否加载,然后打开loading动画,运行下拉加载的方法传入数据,然后关闭loading。

7.上传图片

文件上传主要借助于鸿蒙request这个原生api的 uploadFile方法。
它需要传入两个参数,一个是当前应用的上下文,一个是上传文件的配置参数,比如:接收文件的后端接口,文件路径设置等,还有在header里面要设置Content-Type为multipart/form-data,表示当前上传的是一个多部分表单数据,这是做上传时必须要设置的,后端会根据这个类型去做文件的接收处理。

request还有两个事件可以被我们监听,分别是progress 和 fail,其中progress事件会返回上传文件的总大小和当前上传的大小,我们可以用来做上传进度的计算和展示,当时碰到一个问题,就是CustomDialogController弹窗是不会因为调用方的状态属性值改变而改变的,后来通过研究使用emitter这个api来做的跨线程通信解决的。
如果上传有错误,可以在fail的监听事件中来获取错误信息

有两个需要特别注意的地方是
1我们从相册中选择一个文件是没有权限读取的,需要利用上下文对象以及fs这个api将相册的文件拷贝到应用沙箱中,然后通过 internal://cache/再加上沙箱中的文件名才能正常做上传操作
2需要在你的应用中开启intelnet访问权限

  • 首先在相册里选照片,通过result=picker.PhotoViewPicker().select({类型,最大张数})返回照片。
  • 把照片显示到预览区:声明个list接收上面的result,渲染在页面上。
  • 把图片拷贝到沙箱目录,通过file = fileIo.openSync(相册url,openMode.read_only)来读取打开相册照片。
  • 通过 存储路径 = getContext().cacheDir得到一个存储路径
  • 通过 存储名字 = util.generateRandomUUID() + ". jpg"生成名字
  • 通过fileIo.copyFileSync(file.fd , 存储路径+“/”+存储名字 )将相册文件拷贝到沙箱中去。
  • 生成一个参数fileParams,存入一个列表,便于后面上传。

  • 封装上传文件的api:upload图片
    • import request from ‘@ohos.request’
    • 配置参数request.UpLoadConfig=

content-type:还有application类型、text/xhtml结构、multipart/form-data结构

    • 用官方api,task =request.upLoadFile(context,参数config)实现上传。
    • 通过task监听
      • headerReceive接收上传成功得到地址
      • 返回地址
  • 使用这个封装的api:upload图片
    • 传入上下文,和拷贝完的沙箱图片地址

封装上传API pickUp,来写入上传到后端的数据结构和后端地址。

调用PickUp的时候我们就把upLoadImage返回的地址给pickUp,pickUp传到数据库地址里。就完成了。

8.数据埋点

数据埋点功能是用来收集用户访问App中各个页面的痕迹数据的。可以通过这些数据分析来判断用户的访问喜好,公司通过对这些数据的分析从而可以让制定更好的营销策略

我们项目的数据埋点,主要用在收集用户访问哪类题目的频率比较高。更好的把一些高频类目推送给用户观看。
主要的实现思路是:
1在项目中统一封装了一个类,包含四个方法:①进入页面时间收集 ② 离开页面时间收集 ③ 当前访问的题目id收集 ④ 将收集到的数据发送给服务
2当然因为每次访问都将数据发送给服务器,将会因为很频繁的访问服务器而给服务器带来很大的访问压力,所以我们优化成了,每收集到20条数据才统一发送给服务器,当然这个数量是可以动态调整的
3我们也可以考虑把这些数据持久化到应用的首选项中,这样就算应用退出后再打开,也不会让数据丢失
 

9.混合开发

混合开发其实就是鸿蒙系统原生语言 + web前端开发混合的一种模式,目前已经发展成为系统提供壳的原生能力sdk + web端提供页面及交互的一种方式。

网页容器- 用来容纳h5所实现的html页面和js渲染,本质上它就是存在我们app应用中的一个浏览器,所以我们只需要将访问地址放置到webview的网页容器中,这样混合开发就形成了。

Web组件需要 src和controller两个属性

src: 可以是网络地址 也可以是来自鸿蒙本身的沙箱或者资源原生地址。

controller: 用来管控Web组件的一些行为 包括后续的通信。

鸿蒙混合开发的两种通信方式:端口通信、调用方式传值 (注入SDK的模式)

端口通信:

1.需要使用controller创建两个端口。

2.需要把其中一个端口传给网页H5。

3.网页H5用端口给原生端发消息。

4.原生端用另一个端口接消息。

调用方式传值:

1.原生端通过JavaScriptProxy给h5页面注入可用的sdk应用方法集合,在h5完成初始化后,可以实现H5调用原生端方法。

2.原生端也可以直接调用h5端的方法 runJavascript,在这个方法里面传入方法调用传参数就可以。需要注意的点: 如果原生端的sdk方法是个异步方法,在h5端无法及时得到结果,此时需要再用原生反调h5进行传递结果

热更新下载h5资源包解压渲染方法:

1.检查-下载资源包

2.下载的包放入沙箱目录。

3.zip进行解压。

4.解压完成后直接预览。

10.端云一体+刷题APP

元服务:是有独立入口、免安装、可为用户提供一个或多个便捷服务的新型应用程序形态。类似微信小程序,无需安装,从目前编辑器更新的特性来说,元服务和应用的区别就是一个不需要安装,另外一个需要安装,其他基本一致。

元服务需要分包,单个包大小不超过2MB,所有包总和大小不超过10MB。

服务卡片:桌面上的一个小卡片,用来显示一些基本信息或者进行一些基本操作。注意不能做重要逻辑,所有重要逻辑全部交给应用。Form Kit可以将信息和操作前置到服务卡片。

卡片应用互相通信:

卡片→应用:使用postCardAction的call方法传入abilityName就可以点卡片拉起应用,还可以传数据给ability,ability通过callee监听接收存到全局状态APPStorage,应用就可以从AppStorage中获取信息了。

应用→卡片:在卡片的ability通过want获取FormID,卡片通过postCardAction把ID存入首选项,应用就可以从首选项中获取到卡片ID,使用formProvider,传入卡片ID和数据,就可以传数据了。

端云一体化开发:

以App Gallery Connect(简称AGC)Serverless云服务为底座、在传统的“端开发”基础上新增“云开发”能力,同时完成鸿蒙应用的端侧云侧开发,端云一体化协同开发。

方法:

1.在AGC上创建项目,开启云数据库、云存储。

2.把表结构导入云数据库,以一张表是题型,第二张表是试卷类型,第三个表是一个试卷的题的类型,第四个表存答案,第五个表存用户结果,还有一个表存用户ID。把结构也存在端的rawfile里。

3.导入数据到云数据库。

4.打开AGC的认证服务。

5.判断当前用户是否已经登录:新建一个首选项的类用来监测用户的登录状态,在Ability中判断当前是否已经登录,登录跳转主页,否则跳转登录页。

6.发验证码:检查手机号是否合法,用cloud.auth().requestVerifyCode发送验证码,在参数中可以设置国家,时间间隔和类型。

7.登录逻辑:正则表达式检查手机号和验证码合法性,向华为云发起登录请求。通过cloud.auth().signIn(),在参数里填入登录类型和国家,然后就可以拿到用户信息,写到首选项里,用AppStorage.setOrCreat写入全局状态,下次就不用登陆了。

8.从AppStorage中拿用户信息绑定到个人页面里。

9.登出方法:像华为云申请signOut,清空首选项,清空全局状态。

10.上传信息到AGC云存储,然后通过cloud.storage().upload()存入云存储。参数里可以写本地地址和云端地址。cloud.storage().getDownLoadURL()拿到云端的地址。通过云的updateProfile可以更新用户信息。

11.封装CloudDataBase云数据库访问工具类,实现端侧对云数据库访问并获取数据: cloud.database()读取云数据库,在参数里传入云数据库地址。

cloud.database().collection读取表,在参数里要传入表的结构和表名字,然后就可以用.upsert,.query增删改查。

12.题目类型入口显示:访问云数据库,拿表,放置渲染。

13.拿到所有题目:拿题目类型入口的ID,访问云数据库,拿表。

14.题目渲染:声明一个索引,声明一个获取当前正在做的题目的方法,然后渲染。

15.上一题/下一题:设置点击事件,索引++/--,加限制条件。

16.通过objectLink和observed来实现选择答案。

17.答案入库:判断是否满足入库条件,入库,下次进入直接读取答案,反馈到选项上。

18.判断答没答对:is_right来比较答案,返回bool值。

19.先获取所有答案getall,然后通过list.filter来获取所有做对了的项。

11.机器人回复

用的是思知AI大模型,封装一个request请求工具类,通过http.creatHttp创建一个请求,通过这个请求的.request()方法,在这个方法里传入大模型接口地址和我们的消息内容context,这样就把消息给大模型接口了,然后await返回回复,借口就封装好了。

在组件内调用这个接口,把回复push进消息数组里,进行渲染。

12.首选项管理

微信聊天记录管理:

拿到和某人全部聊天记录:

preferences.getPreferencesSync获取一个首选项仓库WeTalkStore,然后WeTalkStore.getPreferencesSync(userId)拿到和某人的仓库,仓库.getAllSync()拿到所有聊天记录,通过.map方法(对每个元素执行一次回调函数)存入一个list[]。

删除和某个人的全部聊天记录:

preferences.deletePreferences删除首选项库。

添加我和某人单条记录:

拿到和某人仓库,.putSync()添加记录,然后.flush()写入磁盘。

删除我和某人单条记录:

拿到和某人仓库,.deleteSync()删除记录,然后.flush()写入磁盘。

13.emitter不同线程通信

定义一个更新事件的key

在一个线程中通过emitter.emit(key)触发该事件

在另一线程中通过emitter.on(key,回调函数)监听到触发,执行回调函数。

14.相机

拍照发照片

cameraPicker.pick()里面传入PickerMediaType、前后置摄像头配置。

然后拍照,拍照后通过filelo.openSync()获取相片。

为了在聊天记录中保存照片,我们要在沙箱中拷贝一份照片。

首先读取相册照片

通过上下文+/+UUID+.JPG来获取一个来保存照片的沙箱路径。

拷贝到沙箱filelo.copyFilesync(文件,地址)

创建一个图片文件的数据结构,把地址给它

然后发送这个数据。

拍视频发视频

类比上面

15.语音文字互转

需要注意: 只能支持真机,采样配置只支持采样率为16000赫兹的pcm音频,通道1。

asr语音转文本:

引入CoreSpeechKit。

textToVoice.createEngine{}创建语音转文字引擎,参数里传入语言、PCM格式等。

创建回调对象,onStart,onEvent,onResult,onComplete,onError。

通过speechRecognizer.AudioInfo设置开始识别的相关参数,调用startListening方 法,开始合成。

传入音频流,调用writeAudio方法,开始写入音频流。

ttr文本转语音:

textToSpeech.createEngine{}创建文本转语音引擎,参数里传入语言、人。

拿到文本消息,把文本消息放入VoiceTransfer()就可以了。

16.扫码与二维码生成

只能真机

扫码:

scanBarcode.startScanForResult(上下文,回调函数)就可以获得扫码结果。

生成二维码:

随机生成一个随机数转成字符串;(真实业务是后台生成的密钥)

放置QRCode组件,把字符串放入,就生成二维码了。

生成条形码:

声明一个PixelMap对象,生成一段随机数给这个PixelMap。

然后generateBarCode(PixelMap){类型:ScanType}。

六. 面试题补充:

1.记单词APP的项目难点有哪些?

业务难点:
1.播放英语单词业务遇到了如何解决每个英语单词都能及时生成一个mp3文件的问题。
a:当时的设计思路是每个英文单词都生成一个mp3文件保存到应用缓存,但是出现的问题是,新加的单词还需要重新生成mp3文件,那么会导致应用重新发布,不可取
b:最终选择:使用有道在线地址来根据传入的单词及时生成mp3,使用AVPlayer来播放这个在线链接生成的mp3,可以完美的适配新加入的单词
2.如何对单词语音文件进行完整度、流利度、标准度的评分检测? 经过多家公司的技术验证后,最后采取了云知声公司的AI口语评测接口结合AudioCapturer进行录音后,将录音文件上传到AI评测接口完成结果的解析获得。

技术难点:
1.首页6大组件的拆分,相互传值模式设计->最终采用 @Prop 结合 @Watch以及传函数的方式来合理的完成业务并简化了代码的耦合
2.研究了使用AVPlayer状态机来控制整个单词播放的流程
3.研究了使用AudioCapturer来控制整个语音的录制
4.对比研究了多家公司的AI口语评测接口,都没有鸿蒙SDK,最后采用http协议来上传语音文件完成语音评测业务,http协议支持mp3、pcm和wav,采样率是16K。
5.录制语音采样率、音频采样深度、声道数、音频编码格式等参数要与AI接口要求的录音文件参数相匹配,使用AVRecorder技术方案不能设置采样深度这个参数而放弃,最终改用AudioCapturer完成技术攻关。

2.你的项目的亮点有哪些?

1.通用类的封装,提高代码复用性,例如:封装了统一日志类、沉浸式模式类、统一请 求处理类,统一上传文件类,录音控制类,语音播放控制类,AI评测接口访问类等。
2.通用组件(搜素组件、打卡组件、Loading组件,难易程度组件)、业务组件抽取(题 目分类组件、题目列表组件、单个题目显示内容组件),方便业务的复用。
3.鸿蒙原生应用与网页方法交互。
4.录制音频文件写入到应用程序缓存,播放在线音频。
5.鸿蒙原生api操作二进制流写文件。

3.项目开发遇到了哪些坑?

1.首选项使用异步方法的时候会出现运行紊乱的问题,当异步方法文件还没写完,如果去读就会读取失败,建议使用同步方法
2.文件操作的时候,如果使用异步方法执行会出现紊乱的问题,建议改为使用同步方法
3.async方法忘记使用await调用出现执行结果紊乱
4.AVPlayer播放在线mp3文件时,http协议的url播放不了,但是https协议的url可以正常播放
5.AudioCapturer实例化的时候,在win模拟器上实例化报错,真机可以
6.上传文件时直接读取系统相册文件没有权限,上传失败,需要把系统相册图片通过fs拷贝到应用程序缓存中使用internel://cache方式来读取就能成功
8.跨线程通讯问题 -> 头像上传百分比进度不更新->使用emitter解决
9.Web组件高度兼容性问题:Web组件加载完html后,再调用writeCode方法写入内容,显示不出来内容,在Web组件上增加固定高度可以解决

4.请讲一下鸿蒙开发中页面传值方式?

页面传值大概分为如下几种方式:

1.如果页面间有父子关系,我们使用的@Prop @Link 来实现 父传到子的数据,子传父我们使用的是回调函数的方式

2.如果某两个页面是在关系树上,比如:....->爷 -> 父 -> 子->.... 可以使用@Provider @Cusome

3.如果两个页面毫无关联,我们使用 emitter 这个核心api来完成跨页面传值,AppStorage,localStroage,首选项都可以

5.你们项目架构是什么样的?

1.最近开发的XXX项目不大,所以我们采用的是单架构模式,只有一个Ability 。
为了方便功能划分,我们按照不同功能做了文件夹划分:
例如:所有实体都放在了models文件夹中,页面组件放到了views中,通用组件放到了 common/components中,工具类放到了common/utils中等。

2.同时,因为有些页面需要用到沉浸式模式显示页面,为了防止底部tabbar闪烁,我们采取进入应用即开启沉浸式显示模式。


3.为了方便做日志消息过滤,我们统一对hilog做了日志打印类的封装,这样既简化了调用参数的输入数量又统一管理了项目的日志过滤标记,方便统一查看项目日志

6.项目中用的什么持久化方案?

1 项目中用的是鸿蒙【首选项】api来做的数据持久化方案
2 例如:在【搜索功能】中,我们就用到了【首选项】来把用户输入过的搜索关键字保存起来,专门提供了一个类来对数据进行,新增,查询,删除处理
需要注意的是:
3 首选项 只能保存字符串,所以我们借助了 JSON的stringify和parse两个方法来进行相互转换。在存的时候调用JSON.stringify将数组转换成json字符串来存储,读取的时候调用了JSON.parse将json字符串转换成了数组,方便操作
4 首选项大概能存储8KB数量的数据,所以存储的数据量应该是轻量级的,为了性能考虑,建议存储的数据不超过一万条,否则会在内存方面产生较大的开销。

7.如果遇到接口数据返回的格式不能很好满足我这边的开发需要?

如果遇到接口数据返回的格式不能很好满足我这边的开发需要,我会采取如下两种方式:
1自己通过数组的map函数进行数据格式转换,比如日历组件需要的对象数据规定是{date:'日期'},但是后端返回的是{createAt:'日期'},这里我就使用了map函数进行了数据转换来适配日历组件的需要。

2如果前端转换难度较大,我会与后端程序员进行沟通,将返回格式修改成方便前端解析展示的数据格式。

8.navigation和router的区别?

区别:

1.Navigation天然具备标题栏、内容栏、回退按钮的功能联动,开发者可以直接使用此能力。Router若要实现此能力,需要自行定义;

2.Navigation没有路由数量限制,Router限制32个;

3.Navigation可以嵌套在模态对话框中,也就是说可以模态框中定义路由,Router不支持;

4.Navigation传递参数性能更优,Navigation通过引用传递,Router通过深拷贝完成;

路由控制有2种方式来实现

  • router方式-更适用模块间与模块内页面切换,通过每个页面的url实现模块间解耦
  • Naviagtion-模块内页面跳转时,为了实现更好的转场动效场景不建议使用router该模块,推荐使Navigation

项目中实际还是适用router更较为简单和合理,Navigation的方式更适合简单页面的方式。

Navigation:

API9的用法- Navigation-NavRouter-(其他组件+NavDestination)

API11的用法- Navigation-NavPathStack+.navDestination()

router:

pushUrl会在当前页面层级再加一层页面

replaceUrl会替换当前页面

clear清空页面栈中的所有历史页面,仅保留当前页面作为栈顶页面。

back回到上一个页面

路由模式:

标准模式:

push就是一直追加,不管你有没有加载这个页面。

单例模式:

比如你加载过A 在栈底放着 ,再去追加时会把A从栈底拿出放到栈顶。单例模式不会造成线程的浪费

9.Promise是什么?它和async/await 的区别?

Promise 是处理异步操作的一种机制,它代表了异步操作的最终完成(或失败)及其结果值。在 JavaScript 早期,异步操作通常通过回调函数来实现,但这种方式存在一些问题,比如回调地狱,这使得代码难以阅读和维护。Promise 的引入就是为了解决这些问题。

一个promise 对象代表了一个异步操作,它有三种状态:

  • Pending(进行中):初始状态,异步操作尚未完成。
  • Fulfilled(已成功):异步操作成功完成。
  • Rejected(已失败):异步操作失败。

特点:

  1. Promise 的回调函数是在微任务队列中执行的,这意味着它们会在当前执行栈清空后立即执行,而不是等到下一个宏任务(如定时器或事件循环)。
  2. Promise 提供了 .then() 和 .catch() 方法,允许你将多个异步操作链接起来。每个 .then() 可以返回一个新的 Promise,这样你就可以继续链式调用
  3. Promise 通过 .catch() 方法提供了错误处理机制,可以捕获前面 Promise 链中抛出的任何错误。
  4. 当你有多个 Promise 需要同时处理时,可以使用 Promise.all()。它接收一个 Promise 数组作为参数,只有当所有的 Promise 都成功解析时,才会继续执行。
  5. Promise 的优势:
    • 更好的错误处理:通过.catch()方法,可以集中处理错误。
    • 代码的可读性和可维护性:链式调用使得异步代码更接近同步代码的风格,易于理解和维护。
    • 并行处理:可以同时处理多个异步操作,提高效率。

promise和async/await的区别:

1. async/await 是建立在 Promise 之上的语法糖,用于简化异步操作的编写。可以让你的异步代码看起来更像是同步代码,可读性比promise更好。

  1. async 关键字用于声明一个异步函数,该函数总是返回一个 Promise。await 关键字用于等待一个 Promise 解析。
  2. promise通过.catch()方法捕捉错误,而async/await 错误可以通过 try/catch ()块来捕获,提供了更直观的错误捕获机制,这使得错误处理更接近同步代码的方式。
  3. 代码看起来更像同步代码,提高了代码的可读性和可维护性。
  4. Promise 是 ES6 的一部分,而 async/await 是ES7的。
  5. 对于简单的异步操作,使用 Promise 可能更直接。而对于复杂的异步流程,async/await 可以提供更好的控制和更清晰的逻辑。

10.三层架构

三个层次:产品定制层products(HAP)、基础特性层features(HAR+HSP)和公共能力层commons(HSP)。

  • 产品定制层products包括UI设计、资源和配置,以及交互逻辑和功能特性。作为应用的入口,产品定制层是用户直接互动的界面。
  • 基础特性层features基础特性层位于公共能力层之上,用于存放基础特性集合,例如相对独立的功能UI组件和业务逻辑实现。该层的每个功能模块都具有高内聚、低耦合、可定制的特点。基础特性层为上层的产品定制层提供稳健且丰富的基础功能支持。
  • 公共能力层:存放公共基础能力,集中了例如公共UI组件、数据管理、外部交互、工具库等的共享功能。应用可以共享和调用这些公共能力。

11.双向绑定$$是什么?如何双向绑定$$?

双向绑定可以理解为是数据和视图的关系,数据驱动视图,视图中的内容发生变化,数据也会同步修改,统称为MVVM数据模型。简单来说就是数据和视图双向同步,数据变化-视图更新,视图更新-数据更新。

Model-View-ViewModel

组件中有的双向绑定是属性,有的双向绑定是参数

● 参数是在组件({ text: $$this.xx })

● 属性是在组件().text($$this.xxx)

● 不支持嵌套数据的双向绑定如: 组件({ text: $$this.xx.xx })

12.微信项目:如何实现联系人搜索?

定义一个筛选字段和一个数组,筛选字段绑定给TextInput,用watch监听筛选字段,字段一变化就更新数组,更新方法里采用filter把包含搜索字段的联系人放入数组,然后渲染这个数组。

13.如何实现键盘避让?

在ability中的windowStage的一个setKeyboardAvoidMode方法。

14.如何实现发送消息?

定义输入内容变量,双向绑定给输入框,点击回车发送后,调用父组件的方法,实现子传父把消息传给父组件chatDetails,然后父组件新建一个数组,把消息push进数组,下一步判断是不是我发的,接着进行渲染。

15.如何自动滚到到底下?

给List组件绑定一个scroller,然后scroller.scrollEdge(Edge.END)

16.如何实现长按显示浮层?

.gesture(longPressGesture()

.onAction(bool)

.bindPopup(builder:)

)

17.如何实现组合手势浮层?

判断fingerList.X/Y在屏幕宽度的哪个区域,然后条件渲染

18.如何用AVPlayer?

把音频文件存入rawfile,然后先AVPlayer.init,再AVPlayer.play(文件)

19.如何发送相册的照片和视频?

实例化相册选择器

通过.select()方法设置最大选择张数和选择类型。

为了在聊天记录中保存照片,我们要在沙箱中拷贝一份照片。

首先读取相册照片

通过上下文+/+UUID+.JPG来获取一个来保存照片的沙箱路径。

拷贝到沙箱filelo.copyFilesync(文件,地址)

创建一个图片文件的数据结构,把地址给它

然后发送这个数据。

20.如何计算波峰?

拿到一段一段的buffer,buffer最大32768,最小-32768

设置20个小方条

把buffer分成20分,buffer长度平均分成20份,得到平均值。

高度最大值/平均值,就可得到比例。

每个buffer乘以比例就可以得到一个个小长条的高度,存入数组。

21.px和vp是啥?

px是物理像素点,vp是虚拟像素点,在像素不同的设备看到的内容是一样大的。

22.多态样式是什么?

stateStyles可以设置获焦态,按压态,正常态,选中态。

23.循环渲染ForEach的第三个参数?

ForEach的第三个属性是一个回调,它是生成唯一key的, 不传的话会帮助我们生成独一无二的key,公式:index_ + JSON.stringify(item)

改变了key就更新。

24.waterflow瀑布流?

waterflow和grid布局非常的类似。

同样支持columnStemplate和rowsTemplate通过fr的形式对行和列进行分割。

25.如何实现局部更新?

Observed修饰一个class类型

ObjectLink再修饰被Observed修饰的class类型

可以直接修改被关联对象来局部更新UI

26.如何实现沉浸式和键盘避让?

沉浸式:

windowStage.fullScreen(true)

获取上下安全区域的高度

在组件内进行padding

键盘避让:

打开windowStage.KeybordAvoidMode

27.UIability的声明周期?

当用户打开、切换和返回到对应应用时,应用的UIAbility实例会在其生命周期的不同状态之间转换。

包括这四种状态:

Creat状态:UIAbility实例创建完成时触发,系统会调用onCreate()回调。

创建完成后会进入onWindowStageCreate()回调,可以在该回调中设置UI加载、设置WindowStage的事件订阅。

在UIAbility实例销毁之前,则会先进入onWindowStageDestroy()回调,可以在该回调中释放UI资源。

Foreground和Background状态:分别在UIAbility实例切换至前台和切换至后台时触发,对应于onForeground()回调和onBackground()回调。

可以在onForeground()回调中申请系统需要的资源,或者重新申请在onBackground()中释放的资源。

Destroy状态:在UIAbility实例销毁时触发。可以在onDestroy()回调中进行系统资源的释放、数据的保存等操作。

28.如何实现ability间通信和want传参?

新建两个ability,给此ability新建一个page;

在一个ability里用getContext().startAbility(want)

其中want=

这样就可以拉起另一个ability了。

如果想通过want传参,那么再给want加一个参数parameters,传啥都行

在另一个ability生命周期接口onCreat里拿到want.paramters["order_id"]存入APPstorage,因为AppStorage是全局单例。

这样就可以在ability中通过@StorageProp拿到order_id

但是这样不会更新了,所以我们需要onNewWant每次重新设进去

29.如何使用属性动画?

1.通过animation属性

2.使用animateTo函数

3.通过@animator

准备动画参数

准备animator

通过animator控制播放暂停动画

30.如何使用图片帧动画?

设置一个数组,存放一帧一帧的图片

用animateState来控制

31.如何实现转场动画?

使用bindCotentCover来实现模态转场。

TransitionEffect来实现组件内元素转场。

32.如何实现手势?

长按手势:.gesture.LongPressGesture()

手势组合:GestureGroup(),组合一个拖动手势PanGesture

33.如何实现画布?

Canvas组件

34.小时达项目 HAR包的目录如何设置的?

35.如何实现广告页面?

windowStage的createSubWindow来实现

36.如何实现token判断是否登录?

首先登录就存入token到首选项仓库。

写一个拿token的方法,登录的时候判断有无token跳转正常或登录页面。

37.如何封装统一请求工具request?

1.moudle.json5里申请网络权限。

2.封装一个统一的泛型工具,让返回的数据结构完成统一。泛型:开发者可以传入一个类型来1指定某个属性的类型。

3.在utils里封装请求工具类,用这个方法时要传url、类型 http.RequestMethod.get/post/delete/put、请求参数。

用http.creatHttp()创建一个请求对象request。

拼接url地址,基础地址+传入的地址。

通过http.HttpRequestOptions组装请求参数,包括:请求头的token,请求时 长。

发请求,request.request(url,请求参数)得到result

通过result判断状态码(http状态码和业务状态码),

如果result.responseCode=401,表示token失效,超时,或无效。

如果result.responseCode=404,表示请求地址错误。

判断完http状态码,判断业务状态码。

如果result.业务状态码=200,请求成功,返回消息

38.再把统一请求封装为api

39.如何用时间选择器?

DatePickerDialog

40.如何打电话?

iconClick: () => {

call.makeCall(电话号;

}

41.如何截屏?

componentSnapshot.get(组件名)

42.解释一下同步和异步?

同步

  1. 在同步操作中,执行任务的程序或线程会等待任务完成才能继续执行后续操作。
  2. 这意味着如果一个任务需要较长时间来完成,那么整个程序的执行流将会被阻塞,直到该任务完成。
  3. 同步操作通常比较简单,因为它们遵循严格的顺序执行,易于理解和调试。
  4. 但是,同步操作可能会导致资源浪费,比如CPU在等待I/O操作(如读写文件、网络请求)完成时闲置。

异步

  1. 异步操作允许程序在发起任务后立即继续执行后续操作,而不需要等待该任务完成。
  2. 任务会在后台执行,一旦完成,会通过回调函数、事件、通知等方式通知主程序。
  3. 这种方式可以避免阻塞,提高程序的效率和响应性,特别是在处理I/O密集型任务时。

43.回调函数是什么?

回调函数 是一种特殊的函数,它被作为参数传递给另一个函数,并在后者内部被调用。这种机制允许程序在执行特定操作时,能够“回调”到一个预先定义的函数。

非阻塞:在异步编程中,回调函数允许程序在等待某个操作完成时,继续执行其他代码,从而避免阻塞主线程。

44.context上下文是什么?

在编程中,上下文通常指的是程序执行时的当前状态,包括变量的值、程序的执行流程、当前执行的函数或方法等。

45.单例是什么?

单例模式确保了一个类在程序中只有一个实例,并且提供了一个全局访问点来获取这个唯一的实例。

类是一种抽象的概念,用于描述具有相同属性和方法的对象的模板或蓝图。

对象是类的实例,对象是由类定义的模板创建的,它包含了类的所有属性和方法。

七. 面试流程

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐