星球的眼睛 发表于 2025-1-12 22:42:39

Harmony口试题

收集官网faq:文档中心
一. ArkTS&ArkUI

1. 根本理论

  1. 鸿蒙相关的生命周期都有哪些?

UIAbility生命周期:onCreate、onWindowStageCreate、onForeground、onBackground、onWindowStageDestroy、onDestroy。
https://i-blog.csdnimg.cn/direct/b2a4569047ea4771bf2a2da0ea8fd051.png


[*] onCreate:Create状态为在应用加载过程中,UIAbility实例创建完成时触发,系统会调用onCreate()回调。可以在该回调中进行页面初始化操纵,例如变量界说资源加载等,用于后续的UI展示。
[*] onWindowStageCreate():UIAbility实例创建完成之后,在进入Foreground之前,系统会创建一个WindowStage。WindowStage创建完成后会进入onWindowStageCreate()回调,可以在该回调中设置UI加载、设置WindowStage的事件订阅。事件订阅代码
[*] onForegound():在UIAbility的UI可见之前,如UIAbility切换至前台时触发。可以在onForeground()回调中申请系统需要的资源,或者重新申请在onBackground()中释放的资源。
[*] onWindowStageDestory():在UIAbility实例销毁之前,则会先进入onWindowStageDestroy()回调,可以在该回调中释放UI资源。
[*] onBackground():在UIAbility的UI完全不可见之后,如UIAbility切换至后台时候触发。可以在onBackground()回调中释放UI不可见时无用的资源,或者在此回调中执行较为耗时的操纵,例如状态保存等。
[*] onWindowStageDestory():在UIAbility实例销毁之前,则会先进入onWindowStageDestroy()回调,可以在该回调中释放UI资源。
[*] onDestroy():Destroy状态在UIAbility实例销毁时触发。可以在onDestroy()回调中进行系统资源的释放、数据的保存等操纵。
页面生命周期:onPageShow、onPageHide、onBackPress。


[*] 页面生命周期,说白了就是@Entry修饰的组件,才称之为页面。
[*] onPageShow:页面每次显示时触发一次,包罗路由过程、应用进入前台等场景,仅@Entry装饰的自界说组件见效。
[*] onPageHide:页面每次隐藏时触发一次,包罗路由过程、应用进入后台等场景,仅@Entry装饰的自界说组件见效。
[*] onBackPress:当用户点击返回按钮时触发,仅@Entry装饰的自界说组件见效。
组件生命周期:aboutToAppear(发起网络请求)、aboutToDisappear。


[*] aboutToAppear:在创建自界说组件的新实例后,在执行其build()函数之前执行。答应在aboutToAppear函数中改变状态变量,更改将在后续执行build()函数中见效。
[*] aboutToDisappear:函数在自界说组件销毁之前执行。不答应在aboutToDisappear函数中改变状态变量,特殊是@Link变量的修改大概会导致应用步伐行为不稳定。
按返回键页面执行生命周期方法:


[*] 打开第一个页面:

[*] Index:aboutToAppear
[*] Index:onPageShow

[*] 跳转第二个页面:

[*] Index:onPageHide
[*] Second:aboutToAppear
[*] Second:onPageShow

[*] 点击back: 如果是在第二个页面跳转到第一个页面:

[*] Second:onBackPress Second:onPageHide
[*] Second:onPageHide Index:aboutToAppear
[*] Index:onPageShow Index:onPageShow
[*] Second:aboutToDisappear

返回页面不走aboutToAppear:


[*] aboutToAppear函数在创建自界说组件的新实例后,在执行其build()函数之前执行。
[*] 返回页面时==不需要走重新创建==,不会执行aboutToAppear,只会执行onPageShow。
aboutToAppear和onAppear的区别?


[*] aboutToAppear:是组件的生命周期方法,当组件实例创建后,执行build函数之前执行aboutToAppear
[*] onAppear:是组件的属性方法,在该组件显示时触发此回调
Text()
.onAppear(()=>{}
2. ArkUI的两大开发范式是什么,区别是什么 ***



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


[*] 类Web开发范式:采取经典的HML、CSS、JavaScript三段式开发方式,即使用HML标签文件搭建布局、使用CSS文件形貌样式、使用JavaScript文件处置惩罚逻辑。该范式更符合于Web前端开发者的使用习惯,便于快速将已有的Web应用改造成方舟UI框架应用。
[*] 声明式开发范式:采取基于TypeScript声明式UI语法扩展而来的ArkTS语言,从组件、动画和状态管理三个维度提供UI绘制本领。
[*] ==延伸问题==:有听过下令式编程么,下令式编程与声明式编程的区别是什么?

[*] 其实这里的下令式编程,就相当于是类Web开发范式

[*] 我们来对比一下下令式和声明式
[*]https://i-blog.csdnimg.cn/direct/d3be00136c1145f8a684050f14fa226d.png
[*] 声明式开发范式 : 只需要形貌/声明 , 你要做什么 (通过封装好的组件以及相关认识方法 , 快速实现目标)
[*] 下令式开发范式 : 不但需要知道做什么 , 更需要知道怎样做 (需要通过最原始的方式 , 一步一步实现)
[*] 左侧是纯前端实现的按钮点击 , 也就是下令式

[*] 下令式需要自己一点一点实现 , 怎样做必须清楚

[*] 右侧是ArkTs实现的按钮点击 , 也就是声明式

[*] 声明式只需要知道做什么 , 调用对应api即可 , 不需要知道内部怎样实现

 
3. 项目使用的是harmoneyos还是openharmoney,区别是啥

我们公司的项目使用的是HarmonyOS。
https://i-blog.csdnimg.cn/direct/db1074a9e9844d23b3e8597b2e28620d.png
HarmonyOS:OpenHarmony+闭源应用和华为移动服务HMS(比如应用市场,视频,音乐等app)
Andoird:aosp(android open source project) + GMS(Google Mobile Service)
3. 关于context相关得内容有哪些 , 他们的区别?

各类Context的继承关系:
https://i-blog.csdnimg.cn/direct/7aaf0f3b4c4044c9a50230ad723a9659.png
使用:

[*] page中获取上下文:通过函数getContext获取
import common from '@ohos.app.ability.common';
private context:common.UIAbilityContext = getContext(this) as common.UIAbilityContext
[*] Ability中获取上下文: 直接通过this获取context
onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
   let uiAbilityContext = this.context;
   ...
}
理论:
基类Context提供了获取应用文件路径的本领,ApplicationContext、AbilityStageContext、UIAbilityContext和ExtensionContext均继承该本领。应用文件路径属于应用沙箱路径,详细请参见应用沙箱目次。
上述各类Context获取的应用文件路径有所不同。
通过ApplicationContext==获取应用级别的应用文件路径==,此路径是应用全局信息推荐的存放路径,这些文件会跟随应用的卸载而删除。
import common from '@ohos.app.ability.common';
@Entry
@Component
struct Page_Context {
 private context = getContext(this) as common.UIAbilityContext;
 build() {
   ...
   Button()
   .onClick(() => {
       let applicationContext = this.context.getApplicationContext();
       let cacheDir = applicationContext.cacheDir;//<路径前缀>/<加密等级>/base/cache
       let tempDir = applicationContext.tempDir;
       let filesDir = applicationContext.filesDir;
       let databaseDir = applicationContext.databaseDir;
       let bundleCodeDir = applicationContext.bundleCodeDir;
       let distributedFilesDir = applicationContext.distributedFilesDir;
       let preferencesDir = applicationContext.preferencesDir;
       // 获取应用文件路径
       let filePath = tempDir + 'test.txt';
       hilog.info(DOMAIN_NUMBER, TAG, `filePath: ${filePath}`);
       if (filePath !== null) {
         promptAction.showToast({
         message: filePath
       });
     }
   })
}
} 通过AbilityStageContext、UIAbilityContext、ExtensionContext==获取HAP级别的应用文件路径==。此路径是HAP相关信息推荐的存放路径,这些文件会跟随HAP的卸载而删除,但不会影相应用级别路径的文件,除非该应用的HAP已全部卸载。
import common from '@ohos.app.ability.common';
@Entry
@Component
struct Page_Context {
 private context = getContext(this) as common.UIAbilityContext;
 build() {
   ...
   Button()
   .onClick(() => {
       let cacheDir = this.context.cacheDir;//<路径前缀>/<加密等级>/base/haps/<module-name>/cache
       let tempDir = this.context.tempDir;
       let filesDir = this.context.filesDir;
       let databaseDir = this.context.databaseDir;
       let bundleCodeDir = this.context.bundleCodeDir;
       let distributedFilesDir = this.context.distributedFilesDir;
       let preferencesDir = this.context.preferencesDir;
       // 获取应用文件路径
       let filePath = tempDir + 'test.txt';
   })
}
}  
4. arkts中哪些类不能被继承, 口试官关注点是组件是否可以继承?(组件是否可以被继承)

组件不能被继承,被@compent修饰的自界说组件不能被继承,只能引用,或者对外暴露方法。
官网解释:


[*] struct:自界说组件基于struct实现,struct + 自界说组件名 + {...}的组合构成自界说组件,==不能有继承关系==。对于struct的实例化,可以省略new。
[*] @Component:@Component装饰器仅能装饰struct关键字声明的数据结构。==struct被@Component装饰后具备组件化的本领==,需要实现build方法形貌UI,一个struct只能被一个@Component装饰。@Component可以接受一个可选的bool范例参数。
@Component
struct MyComponent {
}  
5.先容Stage模型和FA模型


[*] Stage模型 : HarmonyOS 3.1推出 也就是API9 , 是目前==主推==且会长期演进的模型

[*] 由于提供了AbilityStage、WindowStage等类作为应用组件和Window窗口的“舞台”,因此称这种应用模型为Stage模型
[*] stage: 舞台 /steɪdʒ/

[*] FA模型: FA(Feature Ability)模型:HarmonyOS早期版本开始支持的模型,已经不再主推

[*] feature: 特点 /ˈfiːtʃə(r)/

[*] 区别: Stage模型与FA模型最大的区别在于

[*] Stage模型中,多个应用组件共享同一个ArkTS引擎实例;
[*] 而FA模型中,每个应用组件独享一个ArkTS引擎实例。
[*] 因此在Stage模型中,应用组件之间可以方便的共享对象和状态,同时减少复杂应用运行对内存的占用。
[*] Stage模型作为主推的应用模型,开发者通过它能够更加便利地开发出分布式场景下的复杂应用。

 
2. 装饰器

1. 你使用过哪些装饰器,分别阐述一下他们得作用 ***



[*] @State装饰器,使得变量变为状态变量,影响UI(数据变化,UI变化)
[*] @Prop装饰的变量和父组件建立单向的同步关系:

[*] 父组件的@State数据变化,会同步到子组件@Prop
[*] 详细用法
//父组件:Parent
@State num:number = 0
build(){
   Son({num:this.num})
}
//子组件:Son
@Prop num:number
[*] @Prop修饰的变量,api9不能初始化,api11能初始化

[*] @Link装饰的变量与其父组件中的数据源共享雷同的值。

[*] 父组件的@State数据变化,会同步到子组件@Link数据
[*] 子组件@link数据变化,会同步到父组件@State数据
[*] 详细用法
//父组件:Parent
@State num:number = 0
build(){
   Son({num:$num})//api9必须使用$,api11开始也可以使用this了
}
//子组件:Son
@Link num:number
[*] @Link修饰的变量,api9不能初始化,api11不能初始化

2. 有用过@Styles,@Extend,@Builder装饰器么?



[*] @Styles装饰器:界说组件重用样式 (多个组件通用的样式)
@Styles装饰器,用于封装重复的通用样式代码。
如果多个不同范例的组件,有着雷同的样式,例如宽高,配景致,字体巨细。那么就可以将这下雷同的样式代码抽取到一个@Styles装饰器修饰的方法中,供各人复用。
支持全局和局部界说:
// 全局
@Styles function functionName() { ... } //styles方法不能调用另一个styles方法***

// 在组件内
@Component
struct FancyUse {
 @Styles fancy() {
 .height(100)
}
}
[*] @Extend装饰器:界说扩展组件样式 (某一种组件自己的样式,私有属性)
@Extend,用于扩展原生组件样式。
如果同一范例的组件,有着很多雷同的样式,例如按钮的范例,点击事件等。那么就可以将这些重复代码,抽过去到一个@Extend装饰器修饰的方法中,供此组件使用。
==仅支持全局界说==:(由于它相当于是给所有的此类组件使用)
// @Extend(Text)可以支持Text的私有属性fontColor
@Extend(Text) function fancy () {
.fontColor(Color.Red)
}
// superFancyText可以调用预定义的fancy
@Extend(Text) function superFancyText(size:number) { //Extend方法可以调用另一个Extend方法
 .fontSize(size)
 .fancy()
}
[*] @Builder装饰器:自界说构建函数
@Builder装饰器,用于封装重复的,复杂UI结构代码,例如List中的ListItem的布局结构,一般比较复杂就可以抽取到@Builder装饰的函数中
@Builder所装饰的函数遵照build()函数语法规则,开发者可以将重复使用的UI元素抽象成一个方法,在build方法里调用。
支持全局界说和局部界说:
//既然调用是通过this调用,那么说明是在组件内部定义
//组件内部定义不需要关键字function
@Builder MyBuilderFunction() { ... }

//全局定义
MyGlobalBuilderFunction()
[*] @BuilderParam装饰器:引用@Builder函数
当开发者创建了自界说组件,并想对该组件添加特定功能时,例如在自界说组件中添加一个点击跳转操纵。若直接在组件内嵌入事件方法,将会导致所有引入该自界说组件的地方均增长了该功能。
为办理此问题,ArkUI引入了@BuilderParam装饰器,该装饰器用于声明恣意UI形貌的一个元素,==类似slot占位符==。

[*] 使得自界说组件更加灵活
代码:
@Component
struct Child {
 @Builder customBuilder() {}
 // 使用父组件@Builder装饰的方法初始化子组件@BuilderParam
 @BuilderParam customBuilderParam: () => void = this.customBuilder;

 build() {
   Column() {
     this.customBuilderParam()
 }
}
}

@Entry
@Component
struct Parent {
 @Builder componentBuilder() {
   Text(`Parent builder `)
}

 build() {
   Column() {
     Child({ customBuilderParam: this.componentBuilder })
 }
}
}  

3. 还用过其他装饰器么?

1. Provide和Consume

[*] @Provide和@Consume,用于祖先与子女组件的双向数据同步,实现跨层级传递

[*] ==明白==:@Provide装饰器的变量是在祖先组件中,可以明白为被“提供”给子女的状态变量。@Consume装饰的变量是在子女组件中,去“消费”数据

[*] 语法特点:

[*] @Provide和@Consume可以通过雷同的变量名或者雷同的变量别名绑定
// 通过相同的变量名绑定
@Provide a: number = 0; //祖先组件中定义
@Consume a: number;  //子孙组件中定义

// 通过相同的变量别名绑定
@Provide('a') b: number = 0;//参数即为别名
@Consume('a') c: number;
 

2. ObjectLink和Observed


[*] @ObjectLink和@Observed类装饰器用于在涉及嵌套对象或数组的场景中进行双向数据同步:

[*] 被@Observed装饰的类,可以被观察到属性的变化;
[*] 子组件中@ObjectLink装饰器装饰的状态变量用于吸收@Observed装饰的类的实例,和父组件中对应的状态变量建立双向数据绑定。这个实例可以是数组中的被@Observed装饰的项,或者是class object中的属性,这个属性同样也需要被@Observed装饰。
[*] 单独使用@Observed是没有任何作用的,需要搭配@ObjectLink或者@Prop使用。

[*] 详情参考: 3.状态管理.md --> 2.5章节
3. Watch
概述

[*] @Watch应用于==对状态变量的监听==。如果开发者需要关注某个状态变量的值是否改变,可以使用@Watch为状态变量设置回调函数。
[*] @Watch用于监听状态变量的变化,当状态变量变化时,@Watch的回调方法将被调用
代码:
@Component
struct TotalView {
 @Prop @Watch('onCountUpdated') count: number = 0;
 @State total: number = 0;
 // 该函数是自定义组件的成员函数
 // @Watch 回调
 // propName是被watch的属性名
 // 多个状态绑定同一个@Watch回调时,通过propName区分到底是哪个状态改变了
 onCountUpdated(propName: string): void {
   this.total += this.count;
}

 build() {
   Text(`Total: ${this.total}`)
}
}

@Entry
@Component
struct CountModifier {
 @State count: number = 0;

 build() {
   Column() {
     Button('add to basket')
     .onClick(() => {
         this.count++
     })
     TotalView({ count: this.count })
 }
}
}  
 
3. 数据存储

1. LocalStorage和AppStorage的区别,和对应的装饰器以及PersistentStorage ***

LocalStorage
页面级UI状态存储,通常用于UIAbility内、页面间的状态共享。
localStorage是页面级数据存储,在页面中创建实例,组件中使用@LocalStorageLink和@LocalStorageProp装饰器修饰对应的状态变量,绑定对应的组件使用比状态属性更灵活
//应用逻辑使用LocalStorage
let para: Record<string,number> = { 'PropA': 47 };
let storage: LocalStorage = new LocalStorage(para); // 创建新实例并使用给定对象初始化 , 创建实例存储数据
let propA: number | undefined = storage.get('PropA') // propA == 47 ,get()获取数据
//link():如果给定的propName在LocalStorage实例中存在,则返回与LocalStorage中propName对应属性的双向绑定数据。 (双向同步)
let link1: SubscribedAbstractProperty<number> = storage.link('PropA'); // link1.get() == 47
let link2: SubscribedAbstractProperty<number> = storage.link('PropA'); // link2.get() == 47
//prop():如果给定的propName在LocalStorage中存在,则返回与LocalStorage中propName对应属性的单向绑定数据。(单向同步)
let prop: SubscribedAbstractProperty<number> = storage.prop('PropA'); // prop.get() == 47
link1.set(48); // two-way sync: link1.get() == link2.get() == prop.get() == 48 //双向同步
prop.set(1); // one-way sync: prop.get() == 1; but link1.get() == link2.get() == 48 //单向同步
link1.set(49); // two-way sync: link1.get() == link2.get() == prop.get() == 49

[*] new LocalStorage(数据Object)创建实例并存储数据
[*] set方法,设置数据
[*] get方法,获取数据
[*] link方法,返回一个双向同步的变量

[*] 与UI逻辑中@LocalStorageLink装饰器类似

[*] prop方法,返回单向同步的变量

[*] 与UI逻辑中@LocalStorageProp装饰器类似

//UI使用LocalStorage
//除了应用程序逻辑使用LocalStorage,还可以借助LocalStorage相关的两个装饰器@LocalStorageProp和@LocalStorageLink,在UI组件内部获取到LocalStorage实例中存储的状态变量。
// 创建新实例并使用给定对象初始化
let para: Record<string, number> = { 'PropA': 47 };
//这个变量一般就定义在某个@Entry组件的上方**
let storage: LocalStorage = new LocalStorage(para);

@Component
struct Child {
// @LocalStorageLink变量装饰器与LocalStorage中的'PropA'属性建立双向绑定
@LocalStorageLink('PropA') storageLink2: number = 1;

build() {
  Button(`Child from LocalStorage ${this.storageLink2}`)
    // 更改将同步至LocalStorage中的'PropA'以及Parent.storageLink1
    .onClick(() => {
      this.storageLink2 += 1
    })
}
}
// 使LocalStorage可从@Component组件访问
@Entry(storage)//storage是上边定义的变量**
@Component
struct Parent {
// @LocalStorageLink变量装饰器与LocalStorage中的'PropA'属性建立双向绑定
@LocalStorageLink('PropA') storageLink1: number = 1;

build() {
  Column({ space: 15 }) {
    Button(`Parent from LocalStorage ${this.storageLink1}`) // initial value from LocalStorage will be 47, because 'PropA' initialized already
      .onClick(() => {
        this.storageLink1 += 1
      })
    // @Component子组件自动获得对CompA LocalStorage实例的访问权限。
    Child()
  }
}
}
//上述代码:如果将LocalStorageLink改为LocalStorageProp就由双向变为了单向

[*] new LocalStorage(数据Object)创建实例并存储数据
[*] 父组件:

[*] @Entry(LocalStorage实例) , 将LocalStorage数据注册到页面中,使得页面内部可以使用数据
[*] @LocalStorageLink ,将页面变量与数据进行双向绑定,父子组件都可以使用
[*] @LocalStorageProp,将页面变量与数据进行单向绑定,父子组件都可以使用

AppStorage
AppStorage是进程级数据存储(==应用级的全局状态共享==),进程启动时自动创建了唯一实例,在各个页面组件中@StorageProp和@StorageLink装饰器修饰对应的状态变量。
//AppStorage是单例,它的所有API都是静态的
AppStorage.setOrCreate('PropA', 47);

let propA: number | undefined = AppStorage.get('PropA') // propA in AppStorage == 47
let link1: SubscribedAbstractProperty<number> = AppStorage.link('PropA'); // link1.get() == 47
let link2: SubscribedAbstractProperty<number> = AppStorage.link('PropA'); // link2.get() == 47
let prop: SubscribedAbstractProperty<number> = AppStorage.prop('PropA'); // prop.get() == 47

link1.set(48); // two-way sync: link1.get() == link2.get() == prop.get() == 48
prop.set(1); // one-way sync: prop.get() == 1; but link1.get() == link2.get() == 48
link1.set(49); // two-way sync: link1.get() == link2.get() == prop.get() == 49

AppStorage.get<number>('PropA') // == 49
link1.get() // == 49
link2.get() // == 49
prop.get() // == 49

[*] 语法根本上与LocalStorage类似,只不外是静态方法
[*] link双向,prop单向
AppStorage.setOrCreate('PropA', 47);

@Entry(storage)
@Component
struct CompA {
 @StorageLink('PropA') storageLink: number = 1;

 build() {
   Column({ space: 20 }) {
     Text(`From AppStorage ${this.storageLink}`)
     .onClick(() => {
         this.storageLink += 1
     })
 }
}
}

[*] 将@StorageLink换为@StorageProp,双向就变为单向的了
[*] 不建议使用@StorageLink , 其实就是由于AppStorage共享范围太多,更新效率低下,也大概造成不须要的更新
 
补充:
localStorage和appStorage数据存取都是在主线程进行的,且api只提供了同步接口,存取数据时要注意数据的巨细。


[*] 关于存储时数据的巨细问题

[*] AppStorage没有巨细限制,单条数据,v也没有限制,但是不建议单条v大于1kb,大于1kb建议使用数据库。多条使用没有限制,会动态分配的。
[*] LocalStorage底层实现是一个map,理论上没有巨细限制。



[*] 参考链接:文档中心
 
PersistentStorage
PersistentStorage将选定的AppStorage属性保存在设备磁盘上。应用步伐通过API,以决定哪些AppStorage属性应借助PersistentStorage长期化。UI和业务逻辑不直接访问PersistentStorage中的属性,所有属性访问都是对AppStorage的访问,AppStorage中的更改会自动同步到PersistentStorage。
PersistentStorage和AppStorage中的属性建立双向同步。应用开发通常通过AppStorage访问PersistentStorage,别的另有一些接口可以用于管理长期化属性,但是业务逻辑始终是通过AppStorage获取和设置属性的
从AppStorage中访问PersistentStorage初始化的属性

[*] 初始化PersistentStorage:
PersistentStorage.persistProp('aProp', 47);
[*] 在AppStorage获取对应属性:
AppStorage.get<number>('aProp'); // returns 47 或在组件内部界说:
@StorageLink('aProp') aProp: number = 48;
 
 
2.数据存储怎么存?都用过什么数据存储?用户首选项,键值型数据库,关系数据库

首选项
用户首选项(Preferences):提供了==轻量级配置数据的长期化本领==,并支持订阅数据变化的关照本领。不支持分布式同步,常用于保存==应用配置信息、用户偏好设置==等。
运作机制:
https://i-blog.csdnimg.cn/direct/39475104efd84dbdb0494ff6aa604474.png
束缚限制:


[*] Key键为string范例,要求非空且长度不超过==80个字节==
[*] 如果Value值为string范例,请使用UTF-8编码格式,可以为空,不为空时长度不超过==8192个字节==
[*] 内存会随着存储数据量的增大而增大,所以存储的数据量应该是轻量级的,建议存储的数据==不超过一万条==,否则会在内存方面产生较大的开销
代码:
import dataPreferences from '@ohos.data.preferences';
//1. 获取preference
private preferences: dataPreferences.Preferences =  dataPreferences.getPreferencesSync(this.context, { name: 'myStore' });

//2. 保存数据
this.preferences.putSync('key', value);
//3. 持久化数据
this.preferences.flush()
//4. 获取数据
let result = this.preferences.getSync("key",16)
this.changeFontSize = Number(result) 键值型数据库
键值型数据库存储键值对情势的数据,当需要存储的数据没有复杂的关系模型,比如存储商品名称及对应代价、员工工号及今日是否已出勤等,由于数据复杂度低,更轻易兼容不同数据库版本和设备范例,因此推荐使用键值型数据库长期化此类数据。
束缚:


[*] 设备协同数据库,针对每条记录,Key的长度≤896 Byte,Value的长度<4 MB。
[*] 单版本数据库,针对每条记录,Key的长度≤1 KB,Value的长度<4 MB。
[*] 每个应用步伐最多支持同时打开16个键值型分布式数据库。
[*] 键值型数据库事件回调方法中不答应进行阻塞操纵,例如修改UI组件。
剩余内容:文档中心
关系型数据库
关系型数据库基于SQLite组件,适用于存储包含==复杂关系数据==的场景,比如一个班级的弟子信息,需要包罗姓名、学号、各科结果等,又或者公司的雇员信息,需要包罗姓名、工号、职位等,由于数据之间有较强的对应关系,复杂水平比键值型数据更高,此时需要使用关系型数据库来长期化保存数据。
运作机制:
https://i-blog.csdnimg.cn/direct/5e801f25bfd5403b93e6299886da306e.png
束缚:


[*] 数据库中有4个读连接和1个写连接,线程获取到空闲读连接时,即可进行读取操纵。当没有空闲读连接且有空闲写连接时,会将写连接当做读连接来使用。
[*] 为保证数据的准确性,数据库同一时间只能支持一个写操纵。
[*] 当应用被卸载完成后,设备上的相关数据库文件及临时文件会被自动扫除。
[*] 为保证插入并读取数据成功,建议一条数据不要超过2M。超出该巨细,插入成功,读取失败。
代码:
import relationalStore from '@ohos.data.relationalStore'
import { common } from '@kit.AbilityKit'

export  class DBUtils {
 // 数据库名称
 private tableName: string = 'accountTable'
 // 建表语句
 private sqlCreate: string = 'CREATE TABLE IF NOT EXISTS accountTable(id INTEGER PRIMARY KEY AUTOINCREMENT, accountType INTEGER, ' +
   'typeText TEXT, amount INTEGER)'
 // 表字段
 private columns: string[] = ['id', 'accountType', 'typeText', 'amount']
 // 数据库核心类
 private rdbStore: relationalStore.RdbStore | null = null
 // 数据库配置
 DB_CONFIG: relationalStore.StoreConfig = {
   name: 'RdbTest.db', // 数据库文件名
   securityLevel: relationalStore.SecurityLevel.S1, // 数据库安全级别
};

 /**
  * 获取rdb
  * @param context:上下文
  * @param callback:回调函数,我们第一次获取数据时,需要在获取到rdb之后才能获取,所以有此回调
  */
 getRdbStore(context: common.UIAbilityContext, callback: Function) {
   relationalStore.getRdbStore(context, this.DB_CONFIG, (error, store) => {
     if (this.rdbStore !== null) {
       //如果已经有rdb,直接建表
       store.executeSql(this.sqlCreate)
       return
   }
     //保存rdb,下边会用
     this.rdbStore = store
     //建表
     store.executeSql(this.sqlCreate)
     console.log("test", "successed get dbStore")
     if (callback) callback()
 })
}

 /**
  * 插入数据
  * @param data:数据对象
  * @param callback:回调函数,这里的结果是通过回调函数返回的(也可使用返回值)
  */
 insertData(data: AccountData, callback: Function) {
   //将数据对象,转换为ValuesBucket类型
   const valueBucket: relationalStore.ValuesBucket = generateBucket(data);
   // 调用insert插入数据
   this.rdbStore && this.rdbStore.insert(this.tableName, valueBucket, (err, res) => {
     if (err) {
       console.log("test,插入失败", err)
       callback(-1)
       return
   }
     console.log("test,插入成功", res)
     callback(res) //res为行号
 })

}

 /**
  * 获取数据
  * @param callback:接收结果的回调函数
  */
 query(callback: Function) {
   //predicates是用于添加查询条件的
   let predicates = new relationalStore.RdbPredicates(this.tableName)
   // 查询所有,不需要条件
   // predicates.equalTo("字段",数据)
   this.rdbStore && this.rdbStore.query(predicates, this.columns, (error, resultSet: relationalStore.ResultSet) => {
     if(error){
       console.log("test,获取数据失败",JSON.stringify(error))
       return
   }
     let count: number = resultSet.rowCount
     console.log("test","数据库中数据数量:"+count) //没数据时返回-1或0
     if (count <= 0 || typeof count === 'string') {
       callback([])
       return
   }
     let result: AccountData[] = []
     //上来必须调用一次goToNextRow,让游标处于第一条数据,while(resultSet.goToNextRow())是最有写法
     while(resultSet.goToNextRow()) {
       let accountData:AccountData = {id:0,accountType:0,typeText:'',amount:0}
       accountData.id = resultSet.getDouble(resultSet.getColumnIndex('id'));
       accountData.typeText = resultSet.getString(resultSet.getColumnIndex('typeText'))
       accountData.accountType = resultSet.getDouble(resultSet.getColumnIndex('accountType'))
       accountData.amount = resultSet.getDouble(resultSet.getColumnIndex('amount'))
       result.push(accountData)
   }
     callback(result)
     resultSet.close()//释放数据集内容
 })
}
}
function generateBucket(account: AccountData): relationalStore.ValuesBucket {
 let obj: relationalStore.ValuesBucket = {};
 obj.accountType = account.accountType;
 obj.typeText = account.typeText;
 obj.amount = account.amount;
 return obj;
}

export class AccountData {
 id: number = -1;
 accountType: number = 0;
 typeText: string = '';
 amount: number = 0;
} 使用代码:(部分代码,从HelloDataManager中copy的)
// 数据库工具类
 private dbUitls: DBUtils = new DBUtils()

// 界面打开时,查询数据,展示胡静
 aboutToAppear(): void {
   this.dbUitls.getRdbStore(this.context, () => {
     this.queryData()
 })
}
 // 查询数据方法
 queryData(){
   this.dbUitls.query((result: AccountData[]) => {
     this.accountDataArray = result
     console.log("test,获取数据成功:", JSON.stringify(this.accountDataArray))
 })
}
 // 点击确定回调
 onConfirm(insertData: AccountData) {
   console.log("test", JSON.stringify(insertData))
   // 插入数据
   this.dbUitls.insertData(insertData, (res: number) => {
     if (res > 0) {
       // AlertDialog.show({ message: "添加成功" })
       this.queryData()
   } else {
       AlertDialog.show({ message: "添加失败" })
   }
 })
}  
4. 组件&布局

1.用过flex布局吗?flex存在的问题是什么?为什么会造成二次渲染?怎样优化?



[*] 用过flex

[*] 弹性布局(Flex)提供更加有用的方式对容器中的子元素进行排列、对齐和分配剩余空间。常用于页面头部导航栏的匀称分布、页面框架的搭建、多行数据的排列等。
容器默认存在主轴与交叉轴,子元素默认沿主轴排列,子元素在主轴方向的尺寸称为主轴尺寸,在交叉轴方向的尺寸称为交叉轴尺寸。

[*] flex会造成二次渲染

[*] flex中flexgrow=1时,子组件宽度和大于flex的宽度时,页面渲染后,会调整子组件宽度使之宽度和等于flex的宽度,造成二次布局渲染 flex中flexshrink=1时,子组件宽度和小于flex的宽度时,页面渲染后,也会调整子组件宽度使之宽度和等于flex的宽度,造成二次布局渲染
[*] flexGrow:设置父容器的剩余空间分配给此属性所在组件的比例。用于分配父组件的剩余空间。
flexShrink: 当父容器空间不敷时,子元素的压缩比例。
参考链接:harmony 鸿蒙Flex布局性能提拔使用指导

[*] 优化

[*] 使用Column/Row代替Flex。
[*] 巨细不需要变动的子组件主动设置flexShrink属性值为0。
[*] 优先使用layoutWeight属性替换flexGrow属性和flexShrink属性。
[*] 子组件主轴长度分配设置为最常用场景的布局结果,使子组件主轴长度总和等于Flex容器主轴长度。

[*] 参考链接:文档中心
2. flex导致二次布局的原因,以及调研的经历

flex中flexgrow=1时,子组件宽度和大于flex的宽度时,页面渲染后,会调整子组件宽度使之宽度和等于flex的宽度,造成二次布局渲染 ​ flex中flexshrink=1时,子组件宽度和小于flex的宽度时,页面渲染后,也会调整子组件宽度使之宽度和等于flex的宽度,造成二次布局渲染


[*] 参考链接:文档中心
 
3.使用了哪些组件,有没有写过自界说组件,自界说组件怎么设计的



[*] Button是按钮组件,通常用于相应用户的点击操纵,其范例包罗胶囊按钮、圆形按钮、普通按钮。Button做为容器使用时可以通过添加子组件实现包含笔墨、图片等元素的按钮。
Text是文本组件,通常用于展示用户视图,如显示文章的笔墨
[*] 写过自界说组件

[*] struct:自界说组件基于struct实现,struct + 自界说组件名 + {...}的组合构成自界说组件,不能有继承关系。对于struct的实例化,可以省略new。
[*] @Component:@Component装饰器仅能装饰struct关键字声明的数据结构。struct被@Component装饰后具备组件化的本领,需要实现build方法形貌UI,一个struct只能被一个@Component装饰。@Component可以接受一个可选的bool范例参数。
[*] build()函数:build()函数用于界说自界说组件的声明式UI形貌,自界说组件必须界说build()函数。
[*] @Entry:@Entry装饰的自界说组件将作为UI页面的入口。在单个UI页面中,最多可以使用@Entry装饰一个自界说组件。@Entry可以接受一个可选的LocalStorage的参数。
[*] @Reusable:@Reusable装饰的自界说组件具备可复用本领
[*] 参考链接:文档中心

[*] 自界说组件怎么设计,有两种情况

[*] 起首公司内部将常用的一些效果 , 都封装了组件

[*] 比如: listView下拉刷新动画 , 进度条等等
[*] 详细自界说组件怎样界说 , 参考上述链接

[*] 其次就是一个应用中 , 如果一个布局在多个页面中出现 , 我们就可以将这个布局效果封装为一个组件

[*] 比如: 页面的头部标题 , 点赞 , 收藏等
[*] 详细自界说组件怎样界说 , 参考上述链接


 
4. 自界说组件,实际开发中封装过哪些自界说组件

例如:下拉刷新、自界说弹窗、自界说LoadingProgress、统一标题栏的封装等...
自界说组件具有以下特点:


[*] 可组合:答应组合使用系统组件、及其属性和方法。
[*] 可重用:自界说组件可以被其他组件重用,并作为不同的实例在不同的父组件或容器中使用。
[*] 数据驱动UI更新:通过状态变量的改变,来驱动UI的刷新。
自界说组件基于struct实现,struct + 自界说组件名 + {...}的组合构成自界说组件,不能有继承关系。
struct被@Component装饰后具备组件化的本领,需要实现build方法形貌UI,一个struct只能被一个@Component装饰。
5.项目中用到arkTs哪些技能

arkTs的技能?
装饰器
渲染控制:
https://i-blog.csdnimg.cn/direct/cabd12c363db4db4a4e6e7a906ea3ea0.png
 
6.flex-shrink的使用

答: 设置父容器压缩尺寸分配给此属性所在组件的比例, 设置为==0表示不压缩==, 父容器为Row、Column时,默认值:0。 父容器为flex时,默认值:1。 在子组件的总宽度大于父容器的宽度时,子组件中有设置flex-shrink , 并且值大于0时,父容器会把剩余宽度按照比例压缩子组件。
https://i-blog.csdnimg.cn/direct/6ae544cb0532480b9631b80bb06a8acd.png
Flex布局-通用属性-组件通用信息-组件参考(基于ArkTS的声明式开发范式)-ArkTS API参考-HarmonyOS应用开发
关于flexShrink怎样计算
https://i-blog.csdnimg.cn/direct/8cd799d8ed5f4b54a3b0ccb3981f62ab.png
参考:flex-shrink的计算方法 - 简书
分析过程:
https://i-blog.csdnimg.cn/direct/5ed19da4247747ab864f31e0ce783b4a.png
自己总结公式:
溢出值:所有子元素宽度相加 - 父元素宽度
总权重:子元素1的flexShrink*子元素1宽度 + 子元素2的flexShrink*子元素2宽度 +...
子元素压缩值:溢出值 * 子元素的权重 = 溢出值 * 子元素1的flexShrink*子元素1宽度/总权重
 
7.webview组件怎样使用,ets文件怎样与h5通讯 ***

   总结:两种方式:
方式一:本题
runJavaScript() arkts-》H5 ​ javaScriptProxy() 和 registerjavaScriptProxy() H5-》arkts
方式二:下一题
createWebMessagePorts postMessage onMessageEvent 数据通道
 
这里的方案是,相互调用函数来通讯,12题是通过消息端口建立数据通道

[*] @ohos.web.webview提供web控制本领,web组件提供网页显示的本领
[*] webview组件怎样使用?

[*] 添加网络权限: ohos.permission.INTERNET
[*] 加载网页
// xxx.ets
import web_webview from '@ohos.web.webview'

@Entry
@Component
struct WebComponent {
controller: web_webview.WebviewController = new web_webview.WebviewController()
build() {
 Column() {
   //加载在线网页
   Web({ src: 'www.example.com', controller: this.controller })
   //加载本地网页
   // 通过$rawfile加载本地资源文件。
   Web({ src: $rawfile("index.html"), controller: this.controller })
   // 通过resource协议加载本地资源文件。
   Web({ src: "resource://rawfile/index.html", controller: this.controller })
}
}
}

[*] ets文件怎样与h5通讯?

[*] ets调用h5网页方法

[*] h5界说方法
<!-- index.html -->
<!DOCTYPE html>
<html>
<meta charset="utf-8">
<body>

</body>
<script type="text/javascript">
 function htmlFn() {
     console.log('run javascript test')
     return "This value is from index.html"
}
 </script>
</html>
[*] ets调用
 Button('runJavaScript')
     .onClick(() => {
         console.log("run-onclick")
         //点击按钮,运行js方法
         this.controller.runJavaScript('htmlFn()', (err, val) => {
           if (err) {
             console.log('run javascript err ' + JSON.stringify(err))
             return
         }
           if (val) {
             this.message = val
         }
           console.log('run javascript success ' + val);
       })
     })
     .width('30%')
     .height('30')

[*] h5调用ets方法

[*] ets界说方法
testObj = {

   test:() => {
     this.message = '调用了当前方法'
     console.log('javascript run test 调用了当前方法')
     return 'ArkUI Web Component'
 },
   toString:() => {
     console.log('Web Component toString')
 }
}
[*] 注入到h5
Button('Register JavaScript To Window')
     .onClick(() => {
         AlertDialog.show({message:'注册方法成功'})
         try {
             //注册方法到H5的控制器
           //参数1:传入调用方法的对象
           //参数2:H5在使用该对象的名字
           //参数3:方法列表(数组)
           this.controller.registerJavaScriptProxy(this.testObj, "testObjName", ["test", "toString"]);
       } catch (error) {
           console.error(`ErrorCode: ${error.code},Message: ${error.message}`);
       }
     })
//registerJavaScriptProxy注册的方法,必须刷新才能使用
Button('refresh')
     .onClick(() => {
         AlertDialog.show({message:'刷新'})
         try {
           this.controller.refresh();
       } catch (error) {
           console.error(`ErrorCode: ${error.code},Message: ${error.message}`);
       }
     })

//或者不适用registerJavaScriptProxy方法来注册,直接使用Web的方法javaScriptProxy
  Web({ src: $rawfile("second.html"), controller: this.controller })
       // 将对象注入到web端
     .javaScriptProxy({
         object: this.testObj,
         name: "testObjName",
         methodList: ["test", "toString"],
         controller: this.controller
     })
//有什么区别呢?目前官网文档中的例子是,如果ets的方法test返回的是复杂类型的数据,比如数组,那么使用的是registerJavaScriptProxy
//但是经过测试,本地模拟器和远程模拟器,都无法传递,可能是模拟器的问题
[*] h5调用
<!-- index.html -->
<!DOCTYPE html>
<html>
<meta charset="utf-8">
<body>
<button style="width:200px;height:200px" type="button" οnclick="htmlTest()">Click Me!</button>
<p id="demo"></p>
</body>
<script type="text/javascript">
   function htmlTest() {
     let str=testObjName.test();
     document.getElementById("demo").innerHTML=str;
     console.log('testObj.test result:'+ str)
 }
</script>
</html>

[*] 参考链接: 鸿蒙开发之Web与原生通讯_arkweb和原生的交互-CSDN博客或官网文档中心
[*] 代码在InterviewQuestion项目中的WebComponent2

8.webview怎样拿到外面的数据,怎样跟外部通讯 ***

可以通过WebView的WebMessagePort来进行通讯,详细通讯流程图如下:
https://i-blog.csdnimg.cn/direct/432d18a21af14d10aeddd756f284e499.png
完备代码,参考官网:文档中心
然后找到postMessage,查看完备示例代码(或找到InterviewQuestion项目中的WebComponent.ets)
 
==注意==:


[*] 应用端通过消息端口(WebMessagePort):发送消息使用postMessageEvent,吸收消息使用onMessageEvent
[*] html端通过消息端口:发送消息使用postMessage,吸收消息监听事件onmessage (都少了event)
 
 
==问题==: 模拟器中web加载网页后,网页的按钮无法点击。所以上述案例中,在模拟器中,是无法测试html端---应用端的数据传递。


[*] ==本地的模拟器现在不支持webview==
[*] 可以使用长途模拟器,或长途真机
==问题==: 在将端口0发送到html端时,使用的方法是postMessage,而真正发送数据是通过postMessageEvent,区别在哪?


[*] postMessage就是用于发送消息端口的
[*] https://i-blog.csdnimg.cn/direct/6034a38f9593473985b7fbb4c433c7fc.png


[*] postMessageEvent就是用于发送消息的
https://i-blog.csdnimg.cn/direct/af22d3e06c8445a49fe99dad65a2aba1.png
 
 
二. 网络请求&线程相关

 
1. 数据通讯 ***

eventHub : 普通事件发布,订阅


[*] eventHub:提供了事件中心,提供订阅,取消订阅,触发事件的本领,同hap内通讯,不跨线程
[*] eventHub.emit(数据标记,数据) ,触发事件
[*] eventHub.on(数据标记, ()=>{}) , 监听事件
emitter : 处置惩罚进程内,线程间事件 , 发送事件会放到事件队列 ,多hap通讯方案


[*] emitter.emit({eventId:xx},数据) ,触发事件
[*] emitter.on({eventId:xx},()=>{}) , 监听事件
worker: 处置惩罚线程间事件,主要处置惩罚耗时势件


[*] 验证- 同hap
[*] https://i-blog.csdnimg.cn/direct/2caf10eac97f4674a84249ca4b2861f3.png
[*] 跨hap
[*] https://i-blog.csdnimg.cn/direct/49cdbdaca96f4cc1bc7a8d9cd78c7d37.png
2、项目中接口请求的数据,需要进行缓存吗?鸿蒙化处置惩罚过程

根据自己项目中的业务逻辑形貌,MapKit需要将当前屏幕中展示的6个瓦片数据图片缓存到沙盒中, ​ 杀掉进程后,断开网络,打开app会展示缓存的数据瓦片。
2.promise怎样办理异步并发(Promise并发控制的实现方式)

异步并发涉及同时执行多个异步操纵。可以使用 Promise.all 或 Promise.allSettled 方法来处置惩罚多个异步操纵的并发执行。


[*] 使用Promise.all处置惩罚异步并发:
Promise.all 可以将多个Promise实例包装成一个新的Promise实例。同时,成功和失败的返回值是不同的,成功的时候返回的是一个结果数组,而失败的时候则返回最先被 reject 失败状态的值。
Promse.all 在处置惩罚多个异步处置惩罚时非常有用,比如说一个页面上需要等两个或多个ajax的数据回来以后才正常显示,在此之前只显示loading图标。
  需要特殊注意的是,Promise.all 得到的成功结果的数组内里的==数据次序==和Promise.all吸收到的数组次序是同等的,即p1的结果在前,即便p1的结果获取的比p2要晚。这带来了一个绝大的好处:在前端开发请求数据的过程中,偶尔会碰到发送多个请求并根据请求次序获取和使用数据的场景,使用Promise.all毫无疑问可以办理这个问题。
[*] race
顾名思义,Promse.race 就是竞走的意思,意思就是说,Promise.race()内里哪个结果得到的快,就返回谁人结果,不管结果自己是成功状态还是失败状态。
race 的使用场景就是,多台服务器摆设了同样的服务端代码,假如我要获取一个商品列表接口,我可以在 race 中写上所有服务器中的查询商品列表的接口地址,哪个服务器相应快,就从哪个服务器拿数据。
[*] 使用Promise.allSettled处置惩罚异步并发:
**Promise.allSettled()**方法返回一个在所有给定的promise都已经fulfilled或rejected后的promise,并带有一个对象数组,每个对象表示对应的promise结果。
  当您有多个彼此不依赖的异步任务成功完成时,或者您总是想知道每个promise的结果时,通常使用它。
顾名思义:allsettled,所有的都竣事,不管结果。
 
 
参考链接:Promise异步并发控制Promise 异步并发控制-JavaScript中文网-JavaScript教程资源分享流派
3.异步提交,代码封装 (Promise怎样使用,有几种是否用方式,分别是什么)***


[*] then().catch()
[*] async/await+try{}catch(){}
 
 
异步提交(例如异步请求提交数据),可以封装一个函数来处置惩罚异步操纵,使用 Promise 或者 async/await 提供异步编程的支持。


[*] 使用Promise封装异步提交:
function asyncSubmit(data) {
 return new Promise((resolve, reject) => {
   // 模拟异步提交
   setTimeout(() => {
     const success = true; // 模拟提交成功
     if (success) {
       resolve('提交成功');
   } else {
       reject('提交失败');
   }
 }, 1000);
});
}

// 调用异步提交
asyncSubmit({ /* 数据 */ })
.then((result) => {
   console.log(result);
})
.catch((error) => {
   console.error(error);
});
[*] 使用async/await封装异步提交:
async function asyncSubmit(data) {
 return new Promise((resolve, reject) => {
   // 模拟异步提交
   setTimeout(() => {
     const success = true; // 模拟提交成功
     if (success) {
       resolve('提交成功');
   } else {
       reject('提交失败');
   }
 }, 1000);
});
}

// 调用异步提交
async function submit() {
 try {
   const result = await asyncSubmit({ /* 数据 */ });
   console.log(result);
} catch (error) {
   console.error(error);
}
}

submit();
 
参考链接:

[*] promise封装异步操纵promise封装异步操纵_promise封装异步函数-CSDN博客
[*] Promise、async、await,封装异步代码,办理回调地狱Promise、async、await,封装异步代码,办理回调地域_async await 包装promise-CSDN博客
 
15.请先容一下Async await ,await是否阻塞线程?

async/await 是 JavaScript 中处置惩罚异步代码的一种方式,它建立在 Promise 根本之上,提供了更清晰和易读的语法。使用 await 关键字可以暂停异步函数的执行,等待 Promise 对象的办理(resolve)。
await 并不会阻塞整个线程。当碰到 await 时,它会让出线程的执行,使得其他任务可以继承执行。在背后,await 会暂停当前函数的执行,等待 Promise 对象的状态发生变化。
 
参考链接:async/await详解async、await详解_async await-CSDN博客
 
 
1. 鸿蒙系统http在接口请求时,系统自己应该是开启子线程来请求,那么为什么在项目中还要使用taskpool开辟子线程呢

鸿蒙底层的http请求实现是在子线程中进行的,但针对于请求参数的编码与返回值的剖析,这部分需要耗费时间来处置惩罚的业务逻辑,还是建议使用taskpool开辟子线程来处置惩罚
参考答案二:
鸿蒙底层的http请求实现是在子线程中进行的。但在http在接口请求时,我们再开辟的子线程主要用于单次任务的执行,并对于请求返回值的剖析,由于在返回大量时值剖析是比较耗费费时间的; 在多任务请求时TaskPool开辟的子线程可以执行麋集型任务的开发,更加的灵活方便,
2. http数据请求采取什么方案? 请求参数范例是怎么设计的,如果是object, Object与object有啥区别?

已融入ppt: 进阶day01: 61
在进行网络请求前,您需要在module.json5文件中申明网络访问权限。由于HarmonyOS提供了一种访问控制机制即应用权限,用来保证这些数据或功能不会被不当或恶意使用。
{
   "module" : {
       "requestPermissions":[
          {
            "name": "ohos.permission.INTERNET"
          }
     ]
 }
}
[*] 导入http模块。
import http from '@ohos.net.http';
[*] 创建httpRequest对象。 使用createHttp()创建一个httpRequest对象,内里包罗常用的一些网络请求方法,比如request、destroy、on(‘headerReceive’)等。
let httpRequest = http.createHttp();
[*] 订阅请求头(可选)。 用于订阅http相应头,此接口会比request请求先返回,可以根据业务需要订阅此消息。
httpRequest.on('headersReceive', (header) => {
   console.info('header: ' + JSON.stringify(header));
});
[*] 发起http请求。
http模块支持常用的POST和GET等方法,封装在RequestMethod中。调用request方法发起网络请求,需要传入两个参数。第一个是请求的url地址,第二个是可选参数,范例为HttpRequestOptions,用于界说可选参数的范例和取值范围,包含请求方式、连接超时时间、请求头字段等。
请求参数通常以键值对的情势进行传递,此中参数范例可以是字符串、数字等根本范例。如果涉及到发送复杂的数据结构,比如JSON对象,那么请求参数的范例大概是一个对象(Object)。
Object:通常指的是 ArkTS 中的内置对象范例,用于表示键值对集合。
object:通常指的是 ArkTS 中的对象范例。在 ArkTS 中,对象是一种复合数据范例,它可以包含多个键值对。对象可以是根本范例的包装对象,也可以是用户自界说的对象。
 
参考链接:

[*] http数据请求参考:文档中心
[*] 应用权限参考:文档中心
 
3. 都说服务端相应整个流程慢,到底慢在哪里

服务端相应整个流程慢大概有多个原因,这需要进行系统性的性能分析以确定根本原因。以下是一些大概导致服务端相应慢的常见原因:

[*] 网络延迟: 如果服务端与客户端之间的网络延迟较高,数据在两者之间传输的时间将会增长。
[*] 服务器性能不敷: 服务器硬件性能不敷,或者服务器资源(CPU、内存、磁盘)被太过占用,大概导致相应时间延迟。
[*] 数据库性能: 如果应用步伐需要从数据库中检索大量数据,或者数据库查询性能较差,会导致相应时间延迟。优化数据库查询、使用索引、缓存等可以改善这方面的问题。
[*] 第三方服务延迟: 如果应用步伐依赖于外部的第三方服务,而这些服务的相应速度较慢,也会影响团体的相应时间。
[*] 未优化的代码: 代码的性能问题大概导致相应时间延迟。循环次数过多、未经优化的算法、内存走漏等都大概影响代码的性能。
[*] 未优化的资源: 大量未经优化的静态资源(如图片、CSS、JavaScript)大概导致页面加载慢,从而影响团体的用户体验。
[*] 缺乏合适的缓存机制: 缺乏恰当的缓存机制大概导致雷同的请求重复执行雷同的计算,增长相应时间。
为了准确定位服务端相应慢的原因,可以使用性能分析工具、日志分析工具以及监控工具进行详细的诊断。系统性的性能测试和监控可以帮助发现瓶颈,从而采取相应的优化策略。
 
参考链接:

[*] 应用服务器相应缓慢的原因及分析技巧 (应用服务器慢怎么分析)应用服务器相应缓慢的原因及分析技巧 (应用服务器慢怎么分析)-数据运维技能
[*] 服务器运行慢的原因有哪些服务器运行慢的原因有哪些(服务器运行慢的原因有哪些呢)_服务器知识_侠客网
 
4. 服务端相应期间会不会卡页面渲染

HarmonyOS(鸿蒙)是一个分布式操纵系统,与传统的 Web 开发有所不同。在 Web 开发中,服务端相应慢大概会导致页面在浏览器中渲染时出现卡顿,由于浏览器通常会在主线程上执行 JavaScript 代码,包罗处置惩罚相应数据和更新 DOM。
对于 HarmonyOS,服务端相应慢不会直接导致页面渲染的卡顿,由于 HarmonyOS 应用通常是本地应用,而不是在浏览器中运行的 Web 应用。HarmonyOS 应用的渲染通常是在本地设备上进行的,与服务端相应的速度关系不大。
但是,在 HarmonyOS 应用中,如果你的应用在进行网络请求时阻塞了主线程,例如在主线程上进行了同步的网络请求,那么这大概会导致 UI 的卡顿。因此,建议在进行网络请求时使用异步的方式,以制止阻塞主线程。
总体而言,HarmonyOS 应用的相应速度主要与本地设备的性能和网络请求的优化有关,与服务端相应慢是否会卡页面渲染关系较小。在应用开发中,建议合理使用异步操纵、优化网络请求和合理设计本地业务逻辑,以提供更好的用户体验。
 
参考链接:

[*] 系统界说文档中心
[*] 技能特性文档中心
[*] 系统安全文档中心
 
5. 网络请求怎么使用 (你的项目中的网络请求用的是哪个api)***



[*] 官方提供的HTTP,我们进一步进行封装
[*] 三方提供的Axios
 
起首需要请求相应的权限。


[*] HTTP数据请求:通过HTTP发起一个数据请求。
[*] WebSocket连接:使用WebSocket建立服务器与客户端的双向连接。
[*] Socket连接:通过Socket进行数据传输。
[*] 网络连接管理:网络连接管理提供管理网络一些根本本领,包罗WiFi/蜂窝/Ethernet等多网络连接优先级管理、网络质量评估、订阅默认/指定网络连接状态变化、查询网络连接信息、DNS剖析等功能。
[*] MDNS管理:MDNS即多播DNS(Multicast DNS),提供局域网内的本地服务添加、移除、发现、剖析等本领。
 
参考链接:文档中心
 
6. 联系人中涉及到哪些数据请求,畅连caas接口提供方

联系人(contact)提供了联系人管理本领,包罗添加联系人、删除联系人、更新联系人等。
HarmonyOS 提供了本领开发框架,你可以通过调用系统本领来获取联系人信息。如果联系人信息存储在设备本地,你大概需要使用数据存储和访问的 API 来获取这些信息。鸿蒙系统提供了分布式数据管理的本领,可以用于访问本地数据。 访问用户的敏感信息通常需要相应的权限。
关于 CaaS(Capability as a Service)接口提供方,鸿蒙系统提供了本领框架,使开发者可以调用系统本领。
 
参考链接:@ohos.contact(联系人)文档中心
 
7. axios请求参数怎样写范例

在Axios中,如果想指定请求参数的范例,可以通过设置HTTP头部来实现。发送JSON数据,可以设置Content-Type头部为application/json。
告诉服务器正在发送JSON格式的数据。data 对象会被自动转换为JSON字符串。
 
参考链接:axios请求参数范例及传参方式axios请求参数范例及传参方式_axios 请求参数是多个对象范例怎么传递-CSDN博客
 
8. 项目中接口请求的数据,需要进行缓存吗?鸿蒙化处置惩罚过程

根据自己项目中的业务逻辑形貌,MapKit需要将当前屏幕中展示的6个瓦片数据图片缓存到沙盒中,杀掉进程后,断开网络,打开app会展示缓存的数据瓦片。
 
答:根据项目标实际情况决定,如有些数据长期大概不更改并且常常需要获取使用的数据(比如: 用户id, 登录态,token,以及项目中某些特殊页面的数据等)。另有就是有些应用或界面需要的做把文件缓存到本地再做对应的逻辑处置惩罚的情况也需要缓存。鸿蒙提供了多种方式的数据缓存,比如首选项、键值型数据库、关系型数据库等。
我们可以根据实际的数据范例进行选择对应的缓存方式。如:
键值型数据库:以键值对的情势存储数据,当需要存储的数据没有复杂的关系模型,比如存储商品名称及对应代价、员工工号及今日是否已出勤等,由于数据复杂度低,更轻易兼容不同数据库版本和设备范例,因此推荐使用键值型数据库长期化此类数据。
关系型数据库:基于SQLite组件,适用于存储包含复杂关系数据的场景,比如一个班级的弟子信息,需要包罗姓名、学号、各科结果等,又或者公司的雇员信息,需要包罗姓名、工号、职位等,由于数据之间有较强的对应关系,复杂水平比键值型数据更高,此时需要使用关系型数据库来长期化保存数据。
 
9. taskpool开启子线程和promise的区别

taskpool开辟子线程能够充分使用当代手机设备多核配置,提高步伐的并发性和运行效率;
Promise是在当火线程进行异步调用,可以制止阻塞主线程,保证步伐的相应速度,提高用户体验;
详细使用场景:当需要执行I/O操纵时,使用异步操纵比使用线程+同步I/O操纵更合适。
I/O操纵不但包罗了直接的文件、网络的读写,还包罗数据库操纵、Web Service、
HttpRequest以及.Net Remoting等跨进程的调用。当需要长时间CPU运算的场所,
例如耗时较长的图形处置惩罚和算法执行时,建议使用子线程操纵。
 
 
taskpool开辟子线程能够充分使用当代手机设备多核配置,提高步伐的并发性和运行效率;
Promise是在当火线程进行异步调用,可以制止阻塞主线程,保证步伐的相应速度,提高用户体验; ​
详细使用场景:
当需要执行I/O操纵时,使用异步操纵比使用线程+同步I/O操纵更合适。I/O操纵不但包罗了直接的文件、网络的读写,还包罗数据库操纵、Web Service、HttpRequest以及.Net Remoting等跨进程的调用。
当需要长时间CPU运算的场所,例如耗时较长的图形处置惩罚和算法执行时,建议使用子线程操纵。
 
参考二:
taskpool:是多线程异步,是通过调用线程池内的线程去异步完成任务;
promise:是同一个线程在各个任务间进行切换,通过临时挂起任务实现异步 ,异步代码会被挂起并在之后继承执行,并且同一时间只有一段代码执行。
10. 任务池(taskPool),worker,他们之间的区别及使用场景,实际项目怎么使用?

实现TaskPoolWorker内存模型线程隔断离,内存不共享。线程隔断离,内存不共享。参数传递机制采取标准的结构化克隆算法(Structured Clone)进行序列化、反序列化,完成参数传递。支持ArrayBuffer转移和SharedArrayBuffer共享。采取标准的结构化克隆算法(Structured Clone)进行序列化、反序列化,完成参数传递。支持ArrayBuffer转移和SharedArrayBuffer共享。参数传递直接传递,无需封装,默认进行transfer。消息对象唯一参数,需要自己封装。方法调用直接将方法传入调用。在Worker线程中进行消息剖析并调用对应方法。返回值异步调用后默认返回。主动发送消息,需在onmessage剖析赋值。生命周期TaskPool自行管理生命周期,无需关心任务负载高低。开发者自行管理Worker的数量及生命周期。任务池个数上限自动管理,无需配置。同个进程下,最多支持同时开启8个Worker线程。任务执行时长上限无穷制。无穷制。设置任务的优先级不支持。不支持。执行任务的取消支持取消任务队列中等待的任务。不支持。  
TaskPool中超长任务(大于3分钟)会被系统自动采取,怎样制止超长任务?

将超大数据进行分段处置惩罚
CPU麋集型任务开发指导-使用多线程并发本领进行开发-并发-ArkTS语言根本类库-开发-HarmonyOS应用开发
 
补充:
Worker:Worker中不能直接更新Page。Worker是否配置文件:在工程的模块级build-profile.json5文件的buildOption属性中添加配置信息。
taskPool与worker参数传递中数据范例支持:
根本范例数据、传递通过自界说class创建出来的object时,不会发生序列化错误
@ohos.worker (启动一个Worker)-语言根本类库-ArkTS接口参考-ArkTS API参考-HarmonyOS应用开发
 
常见的一些开发场景及适用详细分析如下:

[*] 有关联的一系列同步任务。例如在一些需要创建、使用句柄的场景中,句柄创建每次都是不同的,该句柄需永世保存,保证使用该句柄进行操纵,需要使用Worker。
[*] 需要频仍取消的任务。例如图库大图浏览场景,为提拔体验,会同时缓存当前图片左右侧各2张图片,往一侧滑动跳到下一张图片时,要取消另一侧的一个缓存任务,需要使用TaskPool
[*] 大量或者调度点较分散的任务。例如大型应用的多个模块包含多个耗时任务,不方便使用8个Worker去做负载管理,推荐采取TaskPool。
11. 异步方法Promise,callback,promise是在哪个线程? 是否会卡主线程?实际开发中会优先使用哪种方式做回调?await是否会卡线程?

答:
Promise是在主线程执行的,不会卡主线程,是一种异步编程方式,用于处置惩罚异步操纵。
await 不会卡线程,await 关键字会暂停当前异步函数的执行,并将控制权交还给事件循环,直到promise对象剖析或拒绝。
通过使用async关键字声明一个函数为异步函数,并使用await关键字等待Promise的剖析(完成或拒绝),以同步的方式编写异步操纵的代码。
优先使用promise。
 
12.promise怎样使用?会不会阻塞UI线程(可了解下底层实现逻辑)

Promise 是 JavaScript 中用于处置惩罚异步操纵的一种机制,它提供了更优雅的方式来处置惩罚异步代码,制止了回调地狱 。

[*] 使用new Promise() 创建Promise对象。
[*] 处置惩罚成功和失败,在执行器函数中进行异步操纵,根据结果调用resolve或者reject。
[*] 使用then和catch处置惩罚结果,使用then处置惩罚成功的情况,使用catch处置惩罚失败的情况。
Promise不会阻塞UI线程,Promise设计的目标之一就是制止阻塞主线程。异步操纵的执行不会阻塞 UI 线程,而是通过事件循环机制在后台执行。当异步操纵完成后,它会调用相应的 resolve 或 reject,然后触发注册的 then 或 catch 处置惩罚步伐。
 
 
16. 子线程和主线程的消息传递

Worker开辟的子线程需要配合postMessage和onMessage实现消息传递;
TaskPool开辟的子线程通过回调传递进行传递;对连续订阅事件或单次订阅事件的处置惩罚、取消订阅事件、发送事件到事件队列等是通过Emitter发送和处置惩罚事件的。
 


[*] 子线程传递给主线程一个方法,是否支持?
不支持,自界说class的属性(如Function)无法通过序列化传递。
   let func1 = function() {
       console.log("post message is function");
 }
   // workerPort.postMessage(func1); 传递func1发生序列化错误  
@ohos.worker (启动一个Worker)-语言根本类库-ArkTS接口参考-ArkTS API参考-HarmonyOS应用开发
 
17. 主线程上的数据对象序列化后传到子线程,子线程改变了对象的属性值,主线程跟着一起改变吗

不会,序列化是转换为字节码,已经和之前的对象没有关系了,就算反序列化成对象也是一个新对象。


[*] 序列化传输的数据量巨细限制为16MB。
[*] 目前支持传输的数据对象可以分为普通对象、可转移对象、可共享对象、Native绑定对象四种。
 
18. then调用和await调用的区别

已融入ppt: 进阶day02-21
当使用Promise进行异步操纵时,可以使用.then和await两种方式来调用异步接口;.then调用不阻塞当火线程,异步回调竣事后会直接走到then中,await会等待当前异步方法执行竣事才会继承往下执行。
 
参考答案二:
.then是promise接口的一个属性,可以通过.then方法链式的添加回调函数处置惩罚结果。
await是一个语法糖,是将后面的代码临时挂起,比及任务执行完成后再继承执行。
在语法结构上也有不同:.then是通过方法链式调用处置惩罚Promise的结果,而await是异步函数内使用的关键字。
在处置惩罚错误的方式不同:.then是通过.catch方法来捕获Promise的拒绝结果,而await是通过try/catch块来捕获异步操纵中的错误。
执行的次序也不同:.then中的回调函数会在Promise完成后按次序执行,而await会暂停当前函数的执行,直到等待Promise的办理或者拒绝。
 
19.鸿蒙在网络请求的时候怎样校验CA(安全证书)

获取服务器证书->验证证书链->检查证书有用性->主机名验证 在鸿蒙系统中,这些步骤通常由底层网络库或框架来处置惩罚,而不需要应用步伐开发者自己实现。
 
20.服务端相应期间调用三方库卡顿会不会卡页面渲染

过多引用三方库大概会造成卡顿
 
21.联系人中sdk的调用

联系人是通过调用full sdk(完备sdk)调用系统级别的功能,包含了public sdk(公共sdk)内容外还包含了系统底层的开发接口和功能,public sdk是包含了用于鸿蒙应用步伐开发的根本组件和接口。适用于对资源要求较低、功能相对简朴的应用场景。
 
22.promise异步并发碰到的场景有什么

比如并行请求多个资源的情况: 当需要同时获取多个资源时,可以使用Promise.all()方法将多个Promise对象包装成一个新的Promise对象,以便等待它们全部完成。
 
23.async是否答应修饰在生命周期方法前

语法不报错,但是不建议添加,大概会引起阻塞
 
 
三. 性能&优化

1. 页面性能内存优化***?

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


[*] 精简节点数:

[*] 不要出现冗余节点,

[*] 比如有些效果,使用一个节点可以实现的,不要用多个节点
[*] 例如:一断笔墨,有隔断,完全可以使用一个Text,中间添加一些空格

[*] 扁平化减少中间的嵌套层级,使总的组件节点数减少。

[*] 使用更高级的布局使得页面变得更加扁平化
[*] 例如:RelativeContainer通过相对布局实现扁平化,不消Row嵌套Column,多层嵌套


https://i-blog.csdnimg.cn/direct/9375d804582645df8caa754224c94126.png
 
17.arkTs语言高性能写法有哪些



[*] 变量声明

[*] 使用const声明常量

[*] 对于初期明确不会改变的变量,尽量使用const进行初始化,这里的常量包含根本范例和引用范例。通过const保证地址不会发生变化,能够极大减少由于编码时误操纵导致的赋值等行为,造成对原有逻辑的改变,声明为const能够在编辑时及时发现错误。此中当const声明的是引用范例时,引用范例内部的属性变化是答应的,对于这种不存在地址变化的情况下,也建议使用const声明。

[*] 指定number的范例

[*] 对于number类型,编译器在优化时会区分int和double类型。开发者在初始化number类型的变量时,如果预期是整数类型就初始化为0,小数类型就初始化为0.0,避免将一个number类型初始化为undefined或者null。

[*] 减少使用ESObject

[*] ESObject主要用于在ArkTS和TS/JS跨语言调用的场景中作为范例标注,在非跨语言场景中使用ESObject标注范例,会引入不须要的跨语言调用,造成额外的性能开销,建议在非跨语言调用的场景下,制止使用ESObject,引入明确的范例进行注释。


[*] 属性访问

[*] 减少变量的属性查找

[*] 在要求性能的场景下,建议通过使用将全局变量存储为局部变量的方式来减少全局查找,因为访问局部变量的速度要比访问全局变量的速度更快。重复的访问同一个变量,将造成不必要的消耗,尤其当类似的访问出现在循环过程中,其对于性能的影响更大。

[*] 给类属性添加访问修饰符

[*] 在ArkTS中,对于类结构的属性提供了private、protected和public可访问修饰符。默认情况下一个属性的可访问修饰符为public。选取恰当的可访问修饰符可以提拔代码的安全性、可读性。


[*] 数值计算

[*] 数值计算使用TypedArray

[*] 如果是纯数值计算的场合,推荐使用TypedArray数据结构。TypedArray类型化数组是一种类似数组的对象,其提供了一种用于在内存缓冲中访问原始二进制数据的机制。在一些图像数据处理、加解密的数据计算过程中使用TypedArray可以提高数据处理的效率,因为TypedArray是基于ArrayBuffer实现,在性能方面也能够进行较大提升。


[*] 数据结构的使用

[*] 选取合适的数据结构

[*] 有些时候会采取Record的方式作为临时容器来处置惩罚属性存取的逻辑,例如如下案例中,对于info执行的操纵是set存储以及读取的操纵,这里更好的方式是采取标准内置Map以及根本类库提供的高性能容器类如HashMap。HashMap是ArkTS提供的高性能容器类,底层使用红黑树实现,提供了高性能的数据读写操纵,可以用来实现快速读写键值。

[*] 制止造成稀疏数组

[*] 分配数组时,需要制止使用如下的方式进行处置惩罚。当虚拟机在分配巨细超过1024巨细的数组时,会酿成用哈希表来存储元素,相对数组用偏移来访问元素速度较慢,在开发时,尽量制止数组酿成稀疏数组。


[*] 函数声明与使用

[*] 函数内部变量尽量使用参数传递

[*] 能传递参数的尽量传递参数,不要使用闭包,闭包作为参数会多一次闭包的创建和访问。在普通函数中,修改外部作用域的变量时,建议通过函数的参数传递,由于在直接声明时引用外部作用域的变量,如果没有及时清理,大概有内存走漏的风险。ArkTS中函数参数是引用范例时,作用于引用范例的修改会进行引用传递,函数内对形参的修改也会作用在实参上。


[*] 参考链接:文档中心
 
6. 页面布局上的性能和内存上的注意事项

 
1、使用row/column+layoutweight代替flex容器使用 2、scroll嵌套list/grid容器时,要设置容器的宽高,数组数据渲染尽量使用lazyforeach渲染item 3、组件的显隐设置,要使用if语句来判断,制止使用visibility 4、list/grid容器要根据详细场景来使用cachecount,制止卡顿
 
 
1.使用row/column代替flex容器使用


[*] 由于Flex容器组件默认情况下存在shrink导致二次布局,这会在一定水平上造成页面渲染上的性能问题。
[*] 二次布局到底怎样观测? harmony 鸿蒙Flex布局性能提拔使用指导

[*] harmony 鸿蒙Flex布局性能提拔使用指导

[*] 代码观测,目前还不清楚,组件和页面的声明周期无法观测
@Entry
@Component
struct MyComponent {
 build() {
   Flex({ direction: FlexDirection.Column }) {
     Flex().width(300).height(200).backgroundColor(Color.Pink)
     Flex().width(300).height(200).backgroundColor(Color.Yellow)
     Flex().width(300).height(200).backgroundColor(Color.Grey)
 }
}
}

[*] 上述代码可将Flex替换为Column、Row,在保证实现的页面布局效果雷同的条件下制止Flex二次布局带来的负面影响。
@Entry
@Component
struct MyComponent {
 build() {
   Column() {
     Row().width(300).height(200).backgroundColor(Color.Pink)
     Row().width(300).height(200).backgroundColor(Color.Yellow)
     Row().width(300).height(200).backgroundColor(Color.Grey)
 }
}
}

[*] 效果
[*] https://i-blog.csdnimg.cn/direct/efcaa18de9104595bde2d1a1e3ebec0f.png
2. 使用row/column代替flex容器使用


[*] 在使用Scroll容器组件嵌套List组件加载长列表时,若不指定List的宽高尺寸,则默认全部加载。

[*] List没有设置宽高,会布局List的所有子组件。
[*] List设置宽高,会布局List显示地区内的子组件。
[*] List使用ForEach加载子组件时,无论是否设置List的宽高,都会加载所有子组件。
[*] List使用LazyForEach加载子组件时,没有设置List的宽高,会加载所有子组件,设置了List的宽高,会加载List显示地区内的子组件。

[*] 详细演示如下:
[*] 使用LazyForEach,但不知道宽高 , 加载了所有 (怎么观察加载了所有,通过Tt审查元素)
[*] https://i-blog.csdnimg.cn/direct/cc8dd77c12c6405aa2e887fda1993706.png
[*] 使用LazyForEach,同时指定宽高 , 发现只加载了显示地区内的子item
[*] https://i-blog.csdnimg.cn/direct/65010c6bc670497c9890df65fb1d61c1.png
[*] 参考链接: harmony 鸿蒙性能提拔的其他方法
[*] 完备代码: ScrollContainListPage.ets
class BasicDataSource implements IDataSource {
 private listeners: DataChangeListener[] = []

 public totalCount(): number {
   return 0
}

 public getData(index: number): number {
   return index
}

 registerDataChangeListener(listener: DataChangeListener): void {
   if (this.listeners.indexOf(listener) < 0) {
     console.info('add listener')
     this.listeners.push(listener)
 }
}

 unregisterDataChangeListener(listener: DataChangeListener): void {
   const pos = this.listeners.indexOf(listener);
   if (pos >= 0) {
     console.info('remove listener')
     this.listeners.splice(pos, 1)
 }
}

 notifyDataReload(): void {
   this.listeners.forEach(listener => {
     listener.onDataReloaded()
 })
}

 notifyDataAdd(index: number): void {
   this.listeners.forEach(listener => {
     listener.onDataAdd(index)
 })
}

 notifyDataChange(index: number): void {
   this.listeners.forEach(listener => {
     listener.onDataChange(index)
 })
}

 notifyDataDelete(index: number): void {
   this.listeners.forEach(listener => {
     listener.onDataDelete(index)
 })
}

 notifyDataMove(from: number, to: number): void {
   this.listeners.forEach(listener => {
     listener.onDataMove(from, to)
 })
}
}

class MyDataSource extends BasicDataSource {
 private dataArray: Array<string> = new Array(100).fill('test')

 public totalCount(): number {
   return this.dataArray.length
}

 public getData(index: number): number {
   return Number(this.dataArray)
}

 public addData(index: number, data: string): void {
   this.dataArray.splice(index, 0, data)
   this.notifyDataAdd(index)
}

 public pushData(data: string): void {
   this.dataArray.push(data)
   this.notifyDataAdd(this.dataArray.length - 1)
}
}

@Entry
@Component
struct MyComponent {
 private data: MyDataSource = new MyDataSource()

 build() {
   Scroll() {
     List() {
       LazyForEach(this.data, (item: string, index?: number|undefined) => {
         ListItem() {
           if(index){
             Text('item value: ' + item + (index + 1)).fontSize(20).margin(10)
         }
       }.width('100%')
     })
   }.width('100%').height(500)
 }.backgroundColor(Color.Pink)
}
}  
3. 组件的显隐设置,要使用if语句来判断,制止使用visibility



[*] VisiblePage.ets
[*] harmony 鸿蒙性能提拔的其他方法
4. list/grid容器要根据详细场景来使用cachedcount,制止卡顿 (看官网的list组件有cachedcount的先容)

文档中心
7.页面性能优化

TODO:这个比及最佳实践处置惩罚完之后,完善
https://i-blog.csdnimg.cn/direct/9a1e383863c843aa98173f53da9f05f4.png


[*] 指定number的范例
对于number类型,编译器在优化时会区分int和double类型。开发者在初始化number类型的变量时,如果预期是整数类型就初始化为0,小数类型就初始化为0.0,避免将一个number类型初始化为undefined或者null
[*] 减少变量的属性查找
在要求性能的场景下,建议通过使用将全局变量存储为局部变量的方式来减少全局查找,因为访问局部变量的速度要比访问全局变量的速度更快。重复的访问同一个变量,将造成不必要的消耗,尤其当类似的访问出现在循环过程中,其对于性能的影响更大
[*] 数值计算使用TypedArray
如果是纯数值计算的场合,推荐使用TypedArray数据结构。TypedArray类型化数组是一种类似数组的对象,其提供了一种用于在内存缓冲中访问原始二进制数据的机制。在一些图像数据处理、加解密的数据计算过程中使用TypedArray可以提高数据处理的效率,因为TypedArray是基于ArrayBuffer实现,在性能方面也能够进行较大提升。
[*] 选取合适的数据结构
使用推荐的高性能容器
[*] 制止造成稀疏数组
分配数组时,需要避免使用如下的方式进行处理。当虚拟机在分配大小超过1024大小的数组时,会变成用哈希表来存储元素,相对数组用偏移来访问元素速度较慢,在开发时,尽量避免数组变成稀疏数组。

// 下面情形会变成稀疏数组
// 1. 直接分配100000大小的数组,虚拟机会处理成用hash表来存储元素
let count = 100000;
let result: number[] = new Array(count);
// 2. 分配数组之后直接,在9999处初始化,会变成稀疏数组
result = 0;  
5.如果简历中有写相关性能优化得东西,形貌一下项目中是怎样做得



[*] 布局性能优化:
[*] 减少嵌套层级: 使用扁平化布局优化嵌套层级

[*] 开发者在实现布局的时候,常使用线性布局来实现效果,这大概会造成多级嵌套。建议采取相对布局RelativeContainer进行扁平化布局,有用减少容器的嵌套层级,减少组件的创建时间
[*] https://i-blog.csdnimg.cn/direct/bc7e77145edb43d8ad1b670dfcd31e5c.png

[*] 参考链接: harmony 鸿蒙优化布局性能
四. 其他:

1. hap,har和hsp有啥区别***,自己平常是怎么打包的?有没有单独编写脚本打包?

har和hsp区别?只有体积巨细的区别吗? 办理体积膨胀的问题,har是安装包直接安装在进程里
 


[*] hap,har和hsp有啥区别?

[*] 起首需要搞清楚鸿蒙项目标模块Module的分类: Module分为“Ability”和“Library”两种范例
[*] HAP

[*] HAP: Harmony Ability Package , 叫做鸿蒙Ability包
[*] “Ability”范例的Module编译后叫做HAP。
[*] 一个HAP , 它是由代码、资源、第三方库及应用/服务配置文件构成,HAP可以分为Entry和Feature两种范例。
[*] Entry是主模块 , Feature是动态特性模块。

[*] HAR

[*] HAR: Harmony Archive /ˈɑːkaɪv/, 叫做鸿蒙静态共享包。
[*] “Library”范例的Module编译后叫做HAR , 或者 HSP。
[*] 一个HAR , 它可以包含代码、C++库、资源和配置文件。
[*] HAR不同于HAP,不能独立安装运行在设备上,只能作为应用模块的依赖项被引用。

[*] HSP

[*] HSP: Harmony Shared Package , 叫做鸿蒙动态共享包。
[*] 一个HSP , 它可以包含代码、C++库、资源和配置文件。
[*] HSP依然不能独立运行 , 需要作为HAP的依赖项。
[*] HSP不同于HAR , 可以被多个HAP同时引用 , HSP旨在办理多个模块引用雷同的HAR,导致APP包巨细膨胀的问题。https://i-blog.csdnimg.cn/direct/93a144b12c3342f4bd23f0867507bfb9.png


[*] 自己平常是怎么打包的

[*] 一般应用直接使用DevEco进行打包即可: Build Hap(s)

[*] 有没有单独编写脚本打包?

[*] 如果此应用针对不同应用市场 , 有不同的调整 , 比如应用闪屏 , 那么就需要使用脚本打包
[*] 详细先容:

[*] 除了使用DevEco Studio一键式构建应用/服务外,还可以使用下令行工具来调用Hvigor任务进行构建
[*] 首选需要搭建构建环境 , 安装node.js , jdk , sdkmgr
[*] 配置sdk环境变量
[*] 终极编写打包脚本: ./hvigorw clean assembleApp --no-daemon
[*] 以上所有的步骤 , 都可以通过脚本来处置惩罚
#!/bin/bash
set -ex

NODE_HOME=xxx #指定Node.js的安装目录
JAVA_HOME=xxx #指定JDK的安装目录
COMMANDLINE_TOOL_DIR=xxx #命令行工具的安装目录
HOS_SDK_HOME=xxx #HarmonyOS SDK根路径

#下载并配置Node.js
function init_Node() {
 if [ ! -d "${NODE_HOME}" ]; then
    mkdir "${NODE_HOME}"
 fi
 cd ${NODE_HOME}
 wget --no-check-certificate -q "${node下载路径}" -O node-linux.tar.xz #下载node,需要替换node下载路径
tar -vxf node-linux.tar.xz
 NODE_DIR=xxx #node压缩包文件里面的目录
 cd ${NODE_DIR}
 mv -f ./* .[^.]* ../
 cd ..
 rm -rf NODE_DIR node-linux.tar.xz
 export NODE_HOME=${NODE_HOME}
 export PATH=$NODE_HOME/bin:$PATH
 node -v
 npm config set registry=https://repo.huaweicloud.com/repository/npm/
 npm config set @ohos:registry=https://repo.harmonyos.com/npm/
 
 
 
 npm config get @ohos:registry
 npm config set proxy=http://user:password@proxy.server.com:port #配置npm http代理,企业网络受限的情况下需要配置
 npm config set https-proxy=http://user:password@proxy.server.com:port #配置npm https代理,企业网络受限的情况下需要配置
 npm info express
}

#下载并配置JDK
function init_JDK() {
 if [ ! -d "${JAVA_HOME}" ]; then
    mkdir "${JAVA_HOME}"
 fi
 cd ${JAVA_HOME}
 wget --no-check-certificate -q "${jdk下载路径}" -O jdk-linux.tar.xz #下载jdk,需要替换jdk下载路径
tar -vxf jdk-linux.tar.xz
 JDK_DIR=xxx #jdk压缩包文件里面的目录
 cd ${JDK_DIR}
 mv -f ./* .[^.]* ../
 cd ..
 rm -rf JDK_DIR jdk-linux.tar.xz
 export JAVA_HOME=${JAVA_HOME}
 export PATH=$JAVA_HOME/bin:$PATH
java -version
}


#配置SDK(已获取离线SDK并解压完成)
function init_SDK() {
 export HDC_HOME=/opt/HarmonyOS/SDK/openharmony/10/toolchains #设置hdc工具的环境变量,hdc工具在toolchains所在路径下,请以实际路径为准
 export PATH=$HDC_HOME:$PATH
 export OHOS_NATIVE_HOME=/opt/HarmonyOS/SDK/openharmony/10/native #如果工程中涉及C/C++,才需要设置,指向Native SDK所在的目录
 export HOS_SDK_HOME=${HOS_SDK_HOME}
}
# 安装ohpm, 若镜像中已存在ohpm,则无需重新安装
function init_ohpm() {
   # 初始化ohpm
   OHPM_HOME=${COMMANDLINE_TOOL_DIR}/command-line-tools/ohpm
   export PATH=${OHPM_HOME}/bin:$PATH
 ohpm -v
   # 配置ohpm仓库地址
   
   
 ohpm config set registry=https://ohpm.openharmony.cn/ohpm/
}

# 初始化相关路径
PROJECT_PATH=xxx  # 工程目录
# 进入package目录安装依赖
function ohpm_install {
   cd $1
 ohpm install
}
# 环境适配
function buildHAP() {
   # 根据业务情况适配local.properties
   cd ${PROJECT_PATH}
   echo "hwsdk.dir=${HOS_SDK_HOME}"> ./local.properties
   # 根据业务情况安装ohpm三方库依赖
 ohpm_install "${PROJECT_PATH}"
 ohpm_install "${PROJECT_PATH}/entry"
 ohpm_install "${PROJECT_PATH}/xxx"
   # 如果构建过程报错 ERR_PNPM_OUTDATED_LOCKFILE,需要增加配置:lockfile=false, 根据node版本选择设置方式:
   # node.version<18
   npm config set lockfile=false # 如果执行此命令报错,建议直接在镜像的.npmrc文件中需要增加一行配置:lockfile=false
   # node.version>=18
   #cat ${HOME}/.npmrc | grep 'lockfile=false' || echo 'lockfile=false' >> ${HOME}/.npmrc
   # 根据业务情况,采用对应的构建命令,可以参考IDE构建日志中的命令
   cd ${PROJECT_PATH}
   chmod +x hvigorw
 ./hvigorw clean --no-daemon
 ./hvigorw assembleHap --mode module -p product=default -p debuggable=false --no-daemon # 流水线构建命令建议末尾加上--no-daemon
}
function install_hap() {
 hdc file send "${PROJECT_PATH}/entry/build/default/outputs/default/entry-default-signed.hap" "data/local/tmp/entry-default-signed.hap"
 hdc shell bm install -p "data/local/tmp/entry-default-signed.hap"
 hdc shell rm -rf "data/local/tmp/entry-default-signed.hap"
 hdc shell aa start -a MainAbility -b com.example.myapplication -m entry
}

# 使用ohpm发布har
function upload_har {
ohpm publish pkg.har
}

function main {
local startTime=$(date '+%s')
init_Node
init_JDK
init_SDK
init_ohpm
buildHAP
install_hap
upload_har
local endTime=$(date '+%s')
local elapsedTime=$(expr $endTime - $startTime)
 echo "build success in ${elapsedTime}s..."
}
main

[*] 参考链接: 文档中心

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

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


[*] 显式Want:在启动目标应用组件时,调用方传入的want参数中指定了abilityName和bundleName,称为显式Want。
//这个代码可以不用详细介绍,不然就跑题了,这里给大家提供代码是为了方便大家回顾
import Want from '@ohos.app.ability.Want';

let wantInfo: Want = {
 deviceId: '', // deviceId为空表示本设备
 bundleName: 'com.example.myapplication',
 abilityName: 'FuncAbility',
}
[*] 隐式Want:在启动目标应用组件时,调用方传入的want参数中未指定abilityName,称为隐式Want。
//这个代码可以不用详细介绍,不然就跑题了,这里给大家提供代码是为了方便大家回顾
import Want from '@ohos.app.ability.Want';

let wantInfo: Want = {
 // uncomment line below if wish to implicitly query only in the specific bundle.
 // bundleName: 'com.example.myapplication',
 action: 'ohos.want.action.search',
 // entities can be omitted
 entities: [ 'entity.system.browsable' ],
 uri: 'https://www.test.com:8080/query/student',
 type: 'text/plain',
};

[*] 在调用startAbility()方法时,其入参want中指定了一系列的entities字段(表示目标UIAbility额外的类别信息,如浏览器、视频播放器)和actions字段(表示要执行的通用操纵,如查看、分享、应用详情等)等参数信息,
[*] 然后由系统去分析want,并帮助找到合适的UIAbility来启动。

[*] 回答问题时,尽量与自己的项目/经历关联起来
==关于传值== , 我之前做过授权的功能 , 如果用户第一次拒绝授权,第二次再次进去此页面,则可以直接引导用户调到应用设置界面
https://i-blog.csdnimg.cn/direct/fe039dc2591343f3841c1aa92e84a41b.png


[*] 跳转到应用设置之后的效果如下
https://i-blog.csdnimg.cn/direct/b6deb39c944b499ebca25a135f2c3706.png
==对数据范例的限制==,目前支持的数据范例有:字符串、数字、布尔、对象、数组和文件形貌符等。
 
3. 项目中是否使用过泛型, 联合项目使用展开


[*] 首选了解一下, 什么是泛型

[*] 泛型(Generics)是一种编程语言特性,用于支持范例参数化。
[*] 详细而言,它答应用户在界说类、接口和方法时使用一个或多个范例参数,这些参数表示一种或多种范例而非详细的范例。
[*] 在使用泛型时,可以使用任何符合泛型束缚的范例作为范例参数,使得代码重用性更高,更安全,更可读。泛型在许多当代编程语言中得到广泛的支持和应用
[*] 明白: 简朴一点就是 , 可以在界说接口时 ,可以让==参数的范例更加灵活== , 使得功能更加强盛

[*] 项目中使用泛型

[*] 在项目中 , 使用LazyForEach循环ListItem的时候 , 这个LazyForEach的数据源必须实现IDataSource接口
[*] 而使用LazyForEach循环遍历的数据其实是不同数据实体对象的数组
[*] 而这些数组 , 都得转换为实现了IDataSource的数据源 , 所以这时候我们就可以搞一个公共的数据源CommonDataSource
[*] 代码如下
export class CommonDataSource<T> implements IDataSource {
    private dataArray: T[] = [];
    private listeners: DataChangeListener[] = [];

    constructor(element: T[]) {
      this.dataArray = element;
    }

    public getData(index: number) {
      return this.dataArray
    }

    public totalCount(): number {
      return this.dataArray.length;
    }

    public addData(index: number, data: T[]): void {
      this.dataArray = this.dataArray.concat(data);
      this.notifyDataAdd(index);
    }

    public pushData(data: T): void {
      this.dataArray.push(data);
      this.notifyDataAdd(this.dataArray.length - 1);
    }

    ...
}
[*] 页面使用时
//页面1
LazyForEach(new CommonDataSource<Article>(this.articleList), (item: Article) => {
       ListItem() {
     }
})
//页面2
LazyForEach(new CommonDataSource<Comment>(this.commentList), (item: Comment) => {
         ListItem() {
       }
})  

4. 从先容中延伸出har和下沉到系统的so的优缺点

har分为静态分享库和动态分享库,静态分享库天生har包集成到应用中,应用运行时直接加载到进程中,动态分享库天生hsp和har包,har集成到应用中,hsp需要安装到手机设备中,应用运行时,har直接加载到进程中,但hsp需要业务调用才会加载; ​ so是c++和arkts的混编,arkts开发完成后,编译天生对应的js文件,使用华为工具将js文件和NAPI混编为so; ​ 目前api10和api11不支持har包下沉到系统称为预置SDK,仅支持so。
 
待完善
12、了解过鸿蒙的Napi吗?如果我需要编译三方库怎样链接? 组件是一套对外接口基于Node.js N-API规范开发的原生模块扩展开发框架在HarmonyOS中, C API中的N-API接口可以实现ArkTS/TS/JS与C/C++之间的交互。N-API提供的接口名与三方Node.js同等,目前支持部分接口
10、鸿蒙怎样实现自界说Canvas?底层怎样进行Ui的渲染 flutter skia 11、Navigation组件和NavPathStack使用
 
 


[*]  
 
8.Record 和 map 的区别


[*] 应用场景对比

[*] Record 是属于一个轻量级的 type 范例 , Map 相对 Record 是重量级。
[*] Map 不能像 Record 一样直接转换为普通的对象,Record 主要应对只有查询的实际问题,只是为了频仍的查询去 new 一个 Map 是一种不须要的浪费。
[*] 如果读取数据和显示数据频仍,就应该采取 Record。
[*] 如果增删改比较多,那还是使用 Map

[*] 语法对比

[*] Record就是强范例的Map
[*] 不消Record的写法
const obj: { : number } = {}
[*] 用了Record的写法
//比如我需要一个对象,有 ABC 三个属性,属性的值必须是数字,那么就这么写:
type keys = 'A' | 'B' | 'C'
const result: Record<keys, number> = {
 A: 1,
 B: 2,
 C: 3
}
[*] 总结:

[*] map比record灵活,mas可以恣意添加key和value,record就不行,一旦界说了record,就只能使用界说里的字段,因此,可以认为record是强范例的
[*] record在idea下有语法提示,maps如果不小心把key写错了,会导致运行的时候匹配或get出错
[*] 最重要的一点,maps的key可以是恣意数据范例,而record的key只能是原子


9.string array map 和 object 关系



[*] 统统皆为对象 , 所以无论是内置对象,还是自界说对象,都是继承自Object
[*] 内置对象中有 string array map等
 
 
14.踩坑文档有什么内容,印象最深的问题是啥

偶尔候官方文档不够详细。比如:

[*] 组件样式问题https://i-blog.csdnimg.cn/direct/ad3435bec0ad4f8f8d009be3f7071251.png
[*] web打开网页模拟机不能交互。只有真机可以使用交互。

[*] 关照,数据存储

[*] hdc环境问题https://i-blog.csdnimg.cn/direct/8dd8fc47cdb843b8ab7d7017e043dcdc.png
[*] previewer和模拟器的问题(印象最深的,坑人的环境)

[*] 偶尔候previewer的代码不会更新,明明修改过了,preivewer还是报错,重新rebuild project也不行,这时候可以删除工程目次中的.preivew目次即可
[*] 偶尔候模拟器出问题,效果出不来(权限验证的弹框),删除模拟器重新创建一个好了

[*] list的高度默认是占满全屏幕高度 , 如果同级存在固定高度的组件时 , 需要开发职员显性指定List组件占满剩余高度 , 而不是全屏幕高度 , 一般用layoutWeight(1)(待验证 , 这个问题没有复现)

[*] list如果不指定高度 , 则无法滚动 , 如果想要占剩余空间使用layoutWeight(1)

 
15.加密怎么实现的,怎样存储

关于数据库加密:键值型数据库和关系型数据库均支持数据库加密操纵,都是在创建数据库的时候通过options配置参数来开启加密。加密之后则无法通过其他方式打开数据库文件,只能通过接口访问。

[*] 键值型数据库,通过options中encrypt参数来设置是否加密,默认为false,表示不加密。encrypt参数为true时表示加密。
[*] 关系型数据库,通过options中encrypt属性来设置是否加密,默认为false,表示不加密。 encrypt参数为true时表示加密。
关于数据加密:Samples: We provide a series of app samples to help you quickly get familiar with the APIs and app development process of the HarmonyOS SDKs. | 为帮助开发者快速认识HarmonyOS SDK所提供的API和应用开发流程,我们提供了一系列的应用示例 - Gitee.com


[*] 关于上边的链接中的案例,数据加密使用到的是api3里的cipher,但是此接口已经在api9废弃了
[*] 建议使用@ohos.security.cryptoFramework的Cipher
[*] 详细案例
[*] https://i-blog.csdnimg.cn/direct/49652ff38458413fb1e4ff5fe5d73cfe.png
[*] 代码待研究,有点复杂
[*] CipherModel:rsaEncrypt , rsaDecrypt
 
 

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
页: [1]
查看完整版本: Harmony口试题