本文为稀土掘金技术社区首发签约文章,30天内禁止转载,30天后未获授权禁止转载,侵权必究!
引言
老板:小p啊,这个鸿蒙需求你赶一下,很快的也就几个接口请求的事,你翻译一下安卓代码为arkts代码就行
小p:好的老板,我立马搞!
于是乎,以下java味道的实体类出来了:
scala
export class Base{
base:number = 200
}
export class Test extends Base{
arrtibute:number = 1
arrtibute2:number = 100
getArrt(){
return this.arrtibute
}
}
于是乎,验证了一下后端的返回内容,通过JSON.parse 解析json串
bash
let test:Test = JSON.parse("{ " arrtibute" :1}")
于是小p使用的时候,test.arrtibute.toString() 没问题,准备交差时,想起了java教材中的"范例",我们应该通过getxx方法返回属性,于是改了一下,调用Test类的getArrt方法返回~结果一看
vbscript
假设后端返回了以下json
let test:Test = JSON.parse("{ " arrtibute" :1}")
hilog.error(0,"hello","1:"+test.arrtibute.toString()) 没问题
hilog.error(0,"hello","3:"+test.getArrt().toString()) 崩溃了
在鸿蒙模拟器一看,居然崩溃了,test居然没有getArrt方法?
css
Error message:is not callable
SourceCode:
hilog.error(0, "hello", "3:" + test.getArrt().toString());
这个时候,心里有一万只xx涌上,好好好,大不了我不用这个方法,我再试试拿一下arrtibute2,这个虽然json没有返回,但是我声明了默认值arrtibute2:number = 100,这个应该问题不大吧
scala
export class Test extends Base{
arrtibute:number = 1
arrtibute2:number = 100
getArrt(){
return this.arrtibute
}
}
接着使用test,使用一下arrtibute2
vbscript
hilog.error(0,"hello","1:"+test.arrtibute2.toString())
结果又崩溃了,Cannot read property toString of undefined,也就是说arrtibute2没有默认值
vbnet
Error message:Cannot read property toString of undefined
SourceCode:
hilog.error(0, "hello", "1:" + test.arrtibute2.toString());
^
Stacktrace:
at anonymous (entry/src/main/ets/pages/Index.ets:50:40)
at (entry/src/main/ets/pages/Index.ets:14:7)
于是乎,小p慌了,直接告诉老板"我是一名Android开发,我不想搞Arkts,你给其他人搞吧",老板听后一怒之下,让小p领了n+1
好了,上面段子相信是很多客户端开发使用arkts开发鸿蒙next应用时遇到的坑之一,即反序列化获取的实体类对象,如果json中没有对应的成员属性, 那么成员属性的默认值将会失效(包括父类的默认值也会失效),同时实体类定义的方法也会被抹除
相信这个对于很多习惯了java/kotlin的开发者来说,肯定是一个坑。当然,这个并不是arkts的锅,而是动态语言typescript与js的特性。即便你有了一个声明了类的实例,它本身已经有可能不是"合格的"对象,我们拿一个例子来说:
typescript
class MyClass1{
attr:number = 1
attr2:number = 2
}
class MyClass2{
attr:number = 4
attr2:number = 6
}
export function testMyClass(){
let myclass2:MyClass2 = new MyClass2()
//把MyClass2的对象给了MyClass1
let myclass1:MyClass1 = myclass2
hilog.error(0,"hello",`myclass1 ${myclass1.attr} ${myclass1.attr2}`)
}
我们可以看到,即使MyClass1跟MyClass2本身什么关系也没有(只是内部定义了两个同名的属性),它已经被赋值成功,而且也能够成功运行
E myclass1 4 6
上面这个例子想要说明的是,类在ts或者js中的范围限制其实是非常小的,只要有需要的属性你就能够构造一个毫无相关的类对象
这里有个哲学就是,人有腿跟脚,它就能够变成其他有腿有脚的生物,比如"牛马",哈哈哈哈,玩笑就到此结束
真正辨析一个ts对象他们有没有能力调用其他东西,其实就在一个内置的属性,叫做"proto" 中
也就是说,我们构造的myclass1,其实它的__proto__ 指向的是MyClass2的prototype。 后面我们也会说到prototype 是个什么东西,这里我们暂时把它当作MyClass2的标识即可,也就是说,myclass1是可以调用到MyClass2特有方法的,比如
typescript
class MyClass1{
attr:number = 1
attr2:number = 2
}
class MyClass2{
attr:number = 4
attr2:number = 6
printMyself(){
hilog.error(0,"hello","我其实是MyClass2")
}
}
export function testMyClass(){
let myclass2:MyClass2 = new MyClass2()
let myclass1:MyClass1 = myclass2
// 为了绕过编译报错
let temp = myclass1 as MyClass2
temp.printMyself()
}
输出
E 我其实是MyClass2
至此,我们对ts/js的类对象应该有了一个初步的认识,记不住的话把上面demo敲一下就可以了~
跑不掉的原型链
如果你也是第一次接触到"原型链" 这一概念,可能上网搜的话资料也比较沉杂,其实原型链这个概念很好理解,我们有这几点前置定义即可
1.在js/ts 世界中,每个对象,都有一个属性,叫做__proto__
2.每个函数,都有一个属性,叫做prototype
3.我们常说的类,其实是一个构造函数,记得,比如上文中
ini
class MyClass1{
attr:number = 1
attr2:number = 2
}
MyClass1 写java/kotlin的人,一般把MyClass1这个东西叫做类,其实MyClass1 是一个函数,即构造函数
4.每个类对象的__proto__ 会指向构造这个类对象的构造函数的prototype
ini
let myclass2 = new MyClass2()
let result = myclass2.__proto__ === MyClass2.prototype // 为true
把这些对象一个个链接起来,就是常说的原型链
值得一提,proto => prototype 这个关系,会一直往上推,直到Function这个类,Function的__proto__会等于prototype,即它们是同一个东西,而一个对象最终的__proto__ 其实就等于Object.prototype,而Object.proto 等于Function.prototype,即以下代码都为true
javascript
//都为ture
let objresult = Object.__proto__ === Function.prototype
let result = Function.prototype === Function.__proto__
这里有一个概念,即链的最顶端,其实就是Function,而并非Object。
proto 中存储着js对象的公共方法,比如一个Object类对象 的__proto__(即Object的prototype),就有着常见的定义方法,比如
通过obj对象的__proto__查看
通过Object 对象的prototype查看
这就是我们为什么自定义的类对象也有toString这种基础方法,因为按照链的关系它也继承了Object的东西,即能够使用Object的prototype对象定义的方法
这里我们知道了,对象的类方法,其实就放在了类构造函数的prototype对象中, 那么类的属性呢?其实就放在对象本身。
明白了上面两点概念,我们就可以解决反序列化中函数被抹除以及默认属性不生效的问题了
如何解决反序列化的问题
对象的类方法,其实就放在了类构造函数的prototype对象中, 那么类的属性呢?其实就放在对象本身。
我们再看上面反序列化的例子:
bash
let test:Test = JSON.parse("{ " arrtibute" :1}")
我们可以观察到,test对象反序列化后的__proto__,其实指向了Object.prototype,同时其本身并没有默认的属性参数(为什么这样我们上文讲到了,js允许因为我们可以用一个Object去初始化其他类对象,即便两类不相关)
回到上文我们讲到的知识,要让test对象拥有Test类的方法,我们把test.__proto__修改为Test.prototype即可,如果我们想要让test对象拥有其他属性,我们就复制一个Test类对象,把Test类对象的属性填入到test即可。
因为Arkts不允许我们操作__proto__复制,我们可以直接写一个js方法,让Arkts调用即可,方法如下
javascript
//js代码
export function convert(test,func){
test.__proto__ = func.prototype
let temp = new func()
for (let a in temp) {
test[a] = temp[a]
}
}
以下代码就可以完美运行
scss
Button("Button")
.attributeModifier(this.modifier)
.onClick(() => {
let test:Test = JSON.parse("{ " arrtibute" :1}")
hilog.error(0,"hello","1:"+test.arrtibute.toString())
convert(test,Test)
hilog.error(0,"hello","2:"+test.arrtibute2.toString())
hilog.error(0,"hello","3:"+test.getArrt().toString())
hilog.error(0,"hello","4:"+test.base.toString())
})
.width(200)
总结
通过反序列化这个例子,相信大家能够更加明白JS中的原型链机制,我们也可以通过原因链的定义,解决反序列化中的对象方法被抹除,默认值属性被删除等等问题。鸿蒙next开发对于客户端开发同学来说,因为语言的问题以及对语言背后的知识可能不太熟悉,但是当我们掌握这些概念的时候,掌握了知识的本质,我们可以更快且更高效的开发鸿蒙next相关应用。