点击登录

米环7教程 全坛最详细的开发Zepp OS小程序教程 2023年2月21日更新第二版

从零开始学习!感谢LZ写的教程,很详细。
顺带问一个问题,为什么有些小程序解包后,不是index.page.js和app.js,而是以bin为后缀的二进制文件,如:app.bin,index.page.bin
 
从零开始学习!感谢LZ写的教程,很详细。
顺带问一个问题,为什么有些小程序解包后,不是index.page.js和app.js,而是以bin为后缀的二进制文件,如:app.bin,index.page.bin
这些是quick加密后的文件,也就是将文件转换为手环能听懂的语言,类似于汇编语言,目前市民上没有破译工具,除非有大佬开发出quick反编译工具
 
众所周知,小米手环7搭载Zepp OS,Zepp OS的小程序开发语言是JavaScript(简称JS)
Zepp OS是纯JavaScript的环境(CSS可以使用index.style代替,HTML不可用)
所以我们要掌握JavaScript的一些基本知识
一.JavaScript的基本知识
1.变量
声明变量的方式有两种,分别是 var ,let
变量就像一个装东西的盒子,里面装东西就叫赋值
其中var的全称叫做variable,意思是变量,可变物,而let是"让"的意思,所以就是让×××=×××
其中var是很老很老的声明变量方式了,它可以重新赋值,用于声明一个函数范围或全局范围的变量
let是偏现代的声明变量方式,let语句声明一个块级作用域的局部变量,也可以重新赋值。还有一种和变量相似的,叫做常量,常量只有const,常量不能重新赋值,而const全称是constant,常量,恒量,总之,以后变量用let,常量用const

变量的使用
注意!变量名只能以$,字母,下划线开始,如果变量赋值是大写,使用时是小写,则不是同一个变量,输出结果为undefined
我这里拿Apple健康的做演示
let step = hmSensor.createSensor(hmSensor.id.STEP);
其中var后面的step,就是变量名,等于号后面的则是将此变量赋值为获取到的系统步数
也可以这样写
let step;
step = hmSensor.createSensor(hmSensor.id.STEP);
这种是先声明一个变量,再对其进行赋值,但这种比较麻烦,而且当你忘记赋值时获取变量会被输出undefined
而我们后续使用这个变量时,我们可以这样写
hmUI.createWidget(hmUI.widget.TEXT, {//创建控件
x: 5,//x坐标
y: 10,//y坐标
w: 200,//宽度
h: 70,//长度
color: 0xFFF200,//文本颜色
text_size: 40,//文本大小
text: step.current//将文本输出为先前定义好的变量值,.current是当前值,仅部分更新式变量使用,其它变量请自行去掉
});//结束
当然,我们还可以设置变量为x坐标,y坐标,宽度还有长度
此处参考Battery program
变量:let battery = hmSensor.createSensor(hmSensor.id.BATTERY);
控件hmUI.createWidget(hmUI.widget.FILL_RECT, {
x: 20,
y: 80,
w: battery.current,
h: 50,
radius: 15,
color: 0x078707,
});
一般变量过多可以全部放开头
2.注释
//是单行注释,/* */是多行注释,可以用来暂时封存代码
3.字符串,布尔值
字符串就是单双引号包起来的文字
布尔值是true或false
4.条件语句
(1).switch语句
switch语句简单讲就是分情况匹配相应代码
switch语句的作用一般用于小程序简易语言国际化,如环管和像素鸟都采用了这种方法
const language = hmSetting.getLanguage();// const 常量
switch(language){// ()是赋值的变量
case 0:// 当输出结果为0,则执行以下代码(0在官方文档里的是中文)
Battery_text = "电量";
break;
case 1:
Battery_text = "電量";
break;
case 2:
Battery_text = "Battery";
break;
default: // 当输出结果不属于以上的任何代码时执行以下代码
Battery_text = "Battery";
}
这段代码可以使小程序语言简易国际化,我们也可以用if语句代替(或者直接用po文件)
(2).if语句
if语句和switch语句差不多,只不过if语句一般用来确定一个范围的值,而switch只能用来返回数值
if语句也有多分支
双分支一般是if (/条件判断/){/执行语句/} else {/反之执行语句/}
多分支一般是if(/条件判断/) {/执行语句/}} else if(/条件判断/) {/执行语句/}}
其中圆括号里填的是条件判断,建议和下面讲到的运算符搭配使用,如果条件判断里没有运算符,则是判断是否有这个变量
具体事例
if (indexcolortext == undefined) {
indexcolortext = "0x000000";
indexcolorrec = "0xFFFFFF";
} else if (indexcolortext == 1) {
indexcolortext = "0xFFFFFF";
indexcolorrec = "0x000000";
} else if (indexcolortext == 2) {
indexcolortext = "0x000000";
indexcolorrec = "0xFFFFFF";
}
5.break,continue 和 return
前两者可以提前跳出循环
break 语句中止当前循环,switch,并把程序控制流转到紧接着被中止语句后面的语句
continue 并不会终止循环的迭代,而是在 while 循环中,控制流跳转回条件判断,在 for 循环中,控制流跳转到更新语句
return 语句终止函数的执行,并返回一个指定的值给函数调用者
给大家举个简单的例子
function getRectArea(width, height) {
if (width > 0 && height > 0) {
return width * height;
}
return 0;
}

console.log(getRectArea(3, 4));

console.log(getRectArea(-3, 4));
那么大家觉得这个输出结果是多少
答案分别是12,0
首先我们看第一行,我定义了一个名为getRectArea的函数,里面的参数是width和height,这个函数里面有一个if语句,用来条件判断
当width和height大于0,那么返回结果是width乘以height,反之输出0,之后打印的语句就无需多讲了,第一个都大于0,所以返回结果是12,第二个-3小于0,所以返回结果是0
6.运算符
(1).算数运算符
JavaScript算数运算符,无非加、减、乘、除、取余、自增,自减运算符
比如
var a = 10;
var b = 2;
var c = a++;
var d = a--;
var e = ++a;
var f = --a;
var s1 = a + b;
var s2 = a - b;
var s3 = a * b;
var s4 = a / b;
console.log(c);
console.log(d);
console.log(e);
console.log(f);
console.log(s1);
console.log(s2);
console.log(s3);
console.log(s4);
前几个好说,但是cd和ef有什么区别呢
区别就是运行c和d时,得出结果是10和11,而e和f得出来是11和10
这是为什么呢
因为自增或自减运算符,它分为前置和后置,前置就是在变量的
前面写一个“++或--”,后置就是在变量的后面写一个“++或--”
后置的特点是先让变量参与运算,运算结束以后再进行自增
而前置就是a的意思是先让a的值自增一次,a本来是10,经过自增就变成了11,因为前置++的含义是先让变量自增,再放进式子里面运算,所以这个代码的结果就是11
(2).比较运算符
==等于
!=不等于
大于号是大于
<小于
大于号加=是大于或等于
<=小于或等于
(3).逻辑操作符
((1)).逻辑与操作符
用处是确定一个范围
简单写一个
let battery = hmSensor.createSensor(hmSensor.id.BATTERY);
if (battery.current => 10 && battery.current <= 20) {
hmUI.showToast({text: '电量不足20%'})
}
其中&&就是逻辑与操作符,可以理解为"且"这个字
((2)).逻辑或操作符
用处是有多个条件而且只要有一个符合条件就执行
我们就需要用到逻辑或运算符||,不要以为我打错了啊,它就是两条竖线
((3)).逻辑非操作符
它就是一个!,用处是取反,就是当得到结果是true,转为false,false转为true,如果是在if语句或switch语句中要将条件判断再加一个括号
7.console的多种用途

console.assert()
如果第一个参数为 false ,则将消息和堆栈跟踪记录到控制台

console.clear()
清空控制台,并输出 console was cleared

console.count()
以参数为标识记录调用的次数,调用时在控制台打印标识以及调用次数

console.countReset()
重置指定标签的计数器值

console.debug()
在控制台打印一条 调试 级别的消息

console.dir()
显示一个由特定的 JavaScript 对象列表组成的可交互列表。这个列表可以使用三角形隐藏和显示来审查子对象的内容。

console.error()
打印一条错误信息

console.group()
创建一个新的内联 group, 后续所有打印内容将会以子层级的形式展示。调用 groupEnd()来闭合组

console.groupCollapsed()
创建一个新的内联 group。使用方法和 group() 相同,不同的是,groupCollapsed() 方法打印出来的内容默认是折叠的。调用groupEnd()来闭合组

console.groupEnd()
闭合当前内联 group

console.info()
打印资讯类说明信息

console.log()
打印内容的通用方法

console.table()
将列表型的数据打印成表格

console.time()
启动一个以入参作为特定名称的计时器,在显示页面中可同时运行的计时器上限为 10,000

console.timeEnd()
结束特定的 计时器 并以毫秒打印其从开始到结束所用的时间

console.timeLog()
打印特定 计时器 所运行的时间

console.trace()
输出一个 stack trace

console.warn()
打印一个警告信息,可以使用 string substitution 和额外的参数

示例
打印文本
console 对象中较多使用的主要有四个方法 console.log(), console.info(), console.warn(), 和console.error()。每一个结果在日志中都有不同的样式,可以使用浏览器控制台的日志筛选功能筛选出感兴趣的日志信息。

有两种途径使用这些方法,可以简单的传入一组对象,其中的字符串对象会被连接到一起,输出到控制台。或者可以传入包含零个或多个的替换的字符串,后面跟着被替换的对象列表
8.循环
(1).for循环
for循环的用处是一遍又一遍地运行相同的代码,并且每次的值都不同
for (表达式1;表达式2;表达式3)
{
语句
}
首先要强调两点:
((1)).表达式1、表达式2和表达式3之间是用分号;隔开的,千万不要写成逗号
((2)).for(表达式1;表达式2;表达式3)的后面千万不要加分号,很多新手都会犯这种错误——会情不自禁地在后面加分号
实践
for(var i = 0;i<10;i++){
console.log(i);
}
(2).while循环
和for循环差不多
实践
var i = 0;
while(i<10){
console.log(i);
i++;
}
它和for循环只是在语法上有所不同,其作用和for循环是一样的
9.数组
在JavaScript中,数组是一个非常灵活的类型。简单来说,数组就
是一个容器,可以存放一个或者多个对象。当然,这些对象的类型是
没有限制的,不管它是什么,数组都可以存放
数组的定义方式
数组有4种定义方式
这里我就采用最常用的直接量
例如这样
var arr = ["first","second","third"];
console.log(arr);
得到的结果就是生成了一个拥有3个元素的数组对象,对象的名字是arr,这种方法的好处是在定义数组的时候可以直接对这个数组进行初始化
10.函数
函数 function 翻译过来是功能
函数有八大要素
(1).函数的定义
函数是一组可以被重复调用的代码语句,它可以大大减少你的代码,函数的格式是这样的
funtion 函数名(参数,可不填){
执行的代码,叫做函数体
}
做个示范
funtion showToast(){
hmUI.showToast({text: '函数被调用了'});
}
我定义了一个函数,函数的名字就是showToast,后面的这小括号里面是用来放参数的,也就是说,函数里面如果需要用到一些从外面传进来的数据,就可以通过参数变量做传递。最后就是函数体了,用大括号扩起来的部分就是函数的函数体。在函数体中可以编写多条JavaScript代码,当然,因为只是举一个例子,所以我的函数里面只写了一个最基本的showToast控件,只会弹出一个提示,接下来,我们需要调用函数
showToast();
调用函数只需写一个函数名加一个括号(里面写的是参数,也可不写)然后加分号即可调用函数,执行函数的函数体了
这是第一种函数定义方法,还有的二种方法
var showToast = function(){
hmUI.showToast({text: '函数被调用了'});
}
这是第二种定义函数的方法,和第一种方法有所不同,第二种定义函数的方法就是按照变量的方式来定义函数,变量的名字就是函数的名字,也就是说,我要想调用这个函数,就直接调用变量的名字即可
不过这种方法也有缺点,比如我在这个定义这个变量前调用这个变量(函数),那就会报错,而用第一种方法则不会有这种问题
(2).作用域
在JavaScript中,作用域分为两种,第一种是全局作用域,第二种是函数作用域,作用域就是指当你要查找某一个变量的时候,你可以在什么范围内找到这个变量。这个寻找的范围,就是作用域,关于全局作用域,先看个比较简单的例子
var a = 10;
function test(){
console.log(a);
}
变量a和test函数都直接暴露在外面,因此它们都是全局作用域,而test函数的代码(函数体)就是函数作用域,因为test函数属于全局作用域,而它自己还有一个函数作用域,那么这样一来,全局作用域里面有一个函数作用域,函数体可以访问全局作用域中的变量,但是反过来不行,请看刚才的例子
function test(){
console.log(a);
}
var a = 10;
test();
如果我直接调用test函数,答案必然是10。在这个例子中,函数作用域里面的a会先去函数作用域里面找变量a。找不到就去全局作用域中找。全局作用域中有一个变量a。那么,在执行函数体的时候,就访问全局作用域里的变量a,但是反过来就不行,比如这样
function test(){
var a = 10;
}
console.log(a);
刚才已经说了,函数作用域里可以访问全局作用域里面的变量。但是全局作用域想调用函数作用域中定义的变量却是做不到的,因此当发生作用域嵌套的时候,只能函数访问全局,全局无法访问函数。而且作用域嵌套一般是全局作用域和函数作用域,或者是函数作用域和其他函数作用域。比如,下面这种形式就不是作用域嵌套
if(true){
var a = 20;
}
console.log(a);
代码运行结果是20
虽然变量a的定义写在了花括号里面,但是JavaScript只有全局作用域和函数作用域以及块级作用域,这里没有函数所以不算作用域嵌套,这里的a是全局作用域里的,console.log也是全局作用域里的,自然可以访问同为全局作用域里面的变量a
块作用域就是用花括号包起来的,ECMAScript6中新增了块级作用域,使用let声明的变量只能在块级作用域里访问,块作用域由 { } 包括,if语句和for语句里面的{ }也属于块作用域
(3).参数传递
参数就是函数调用时传进来的值,也就是说,我们在定义参数的时候并不知道调用的过程中会有什么样的值传过来
function add(a,b,c){
var sum = a + b + c;
console.log(sum);
}add(1,2,3);
代码运行结果是6
这是一个最简单的参数传递的例子,函数名字的定义要让人一看便知,这个例子中,add函数就是做加法。传进去了三个参数,分别是1,2,3,这三个参数分别对应add函数中圆括号内,也就是参数列表内的a、b、c三个变量
在刚才的例子中,如果你要调用add函数,就必须传入三个参数,就像下面的代码
add(1,2,3);
函数的调用就是在函数名字的右边加上一对小括号,这样就会执行函数里面的代码体,也就是下面这个部分
function add (a,b,c){
var sum = a + b + c;
hmUI.showToast({text: sum})
}
函数的代码体一般都是用花括号扩起来的,每写完一句,就要打一个分号,现在我们来看一下这个函数的函数体里面都发生了些什么事情,首先是第一行
var sum = a + b + c;
sum是一个新定义的变量,这个变量是定义在函数作用域里面的,外面的全局作用域是没有办法直接访问函数作用域里面的sum变量的。所以,这个sum变量只能被这个函数的函数体被访问,它被叫作局部变量,然后是加法和赋值,把a、b、c三个变量相加之后得到一个总量,然后把这个总量赋给局部变量sum。下面是一个showToast控件,就是把sum变量弹出提示
如果我们调用函数是只传了一个参数该怎么办
对于这个问题,我们可以把它单独拆分出来看。比如,我定义了一个函数,设置了一个参数,但是传参的时候却一个参数都没有传
function fun(a){
hmUI.showToast({text: a})
}
这是一个简单的函数,fun函数设置了一个叫a的参数,这个a还没有说明是什么,我在这个函数的函数体中写了一条showToast控件,接下来,我要调用这个函数,而且不写参数,就像这样
fun();
这是一个非常古怪的例子,因为fun函数是要求填写一个叫a的参数的,可是在调用函数时没有参数传递进来,这是不允许的,可是当这种情况真的发生了会怎样呢?试一下便知
输出结果是undefined
没错,结果就是undefined。其实,可以把这个例子再次细分。刚才的函数中有一个参数a,那么这个参数属于函数作用域,就相当于这样
function fun(){
var a;
hmUI.showToast({text: a});
}
函数的参数可以简单地看成是在函数体,即里面的第一行定义了一个变量。因为我们并没有给这个变量赋值,所以这个局部变量就是undefined,任何变量在被赋值之前,都是undefined,这些函数的参数可以被理解为一种预备变量。接下来说说正常的情况,比如我调用fun函数,传递一个参数18。传参的过程就相当于是给预备变量赋值的过程。如果没有传参,那么预备变量自然还是undefined。再回到刚开始的例子,看一下如果只传一个参数的情况
function add(a,b,c){
var sum = a + b + c;
console.log(sum);
}
add(1);
这种情况下,a的值是1,b和c的值就是undefined,那么数字1和2个undefined相加会是多少呢?真是有意思的问题。结果是NaN,代表无法计算。没错,如果真的那样做,那么就是没有任何意义的。最起码在这个函数中,那样的做法是毫无意义的
那么我们多传一个参数会怎么样呢
对于这个问题,其实也可以单独拆解出来。好比我定义了一个函数fun,但没有参数,如果我在调用fun函数的时候故意给它加了一个参数,会发生什么?比如像这样
function fun(){
}
fun(10);
结果可想而知,自然是什么都不会发生啦。再回到刚才的例子中,就算你强行加了第四个参数,对结果也不会有什么影响
(4).闭包
用处:
((1)).子函数使用父函数变量的行为
直接说你可能不懂,我们来举个例子吧
function a(){
let leo = 1;
function b(){
hmUI.showToast({text: leo});
};
b();
}
可以理解为a函数是一个大圈,b函数是一个小圈,a函数包着b函数,a函数有一个leo变量,然后b函数访问a的变量
((2)).延长被调用父函数变量的生命周期
首先,JavaScript中一个变量如果你后面不使用了,就会被回收,拿上面的例子举例,因为b函数调用了a函数里的leo变量,所以可以延长a函数的变量的生命周期,也就是父函数的生命周期
((3)):拓展父函数的空间
简单来说就是父函数代码变多了,因为里面有个子函数
(5).自执行函数
很多时候,我们只想执行一个函数,却无所谓这个函数叫什么名字。那么在这种情况下,就可以考虑使用自执行函数了。自执行函数的格式是这样子的
语法 : (定义一个没有名字的函数)();
接下来举一个具体的例子,看看如何定义一个自执行函数
(
function(){
console.log(123);
}
)();
这便是一个简单的自执行函数了,所谓自执行函数,顾名思义,就是在定义之后就立刻执行的函数,它一般是没有名字的。也正因为自执行函数没有名字,所以它虽然会被立刻执行,但是它只会被执行一次
自执行函数一般可以和闭包配合使用,举个例子
function test (){
var a = 0;
return function (increment){
a = a + increment;
hmUI.showToast({text: a})
}
}
在这个闭包的例子中,其实我真正想要得到的是test函数里面的内部函数。因为这个原因,所以我并不是很需要知道外面这个函数叫什么名字,它可以叫任何名字,无所谓的。那么像这样的情况,不妨就使用一个自执行函数直接获取内部函数,这是一个相当不错的选择,比如,我可以这样改写一下代码
var inner = (function test (){
var a = 0;
return function (increment){
a = a + increment;
hmUI.showToast({text: a});
}
})();
inner(2);
inner(2);
inner(2);
//inner(2)是让变量a递增2
这样我就可以直接得到闭包环境下的内部函数了,外部函数只是为了产生闭包环境而临时定义的函数,正因为如此,所以根本没有必要给外部函数取一个名字
(6).this
先看代码
function hello(){
console.log(this);
}
我定义了一个函数hello,里面只有一个打印语句,打印出this对象。this是JavaScript中的一个关键字,它的意思就是this永远指向当前函数的调用者 这句话是关于this的一条铁律,首先,这句话透露出的第一个信息是,this只出现在函数中。第二个信息是,这个函数是谁调用的,this就是谁
(8).new函数
前面说过,JavaScript里面分为全局作用域和函数作用域,在全局作用域里面定义的任何东西,都属于window对象。也就是说,hello函数也是window对象的hello函数。而对象可以通过两种方式调用它里面的属性。第一种是点的方式,比如这样
window.hello();
第二种方式是使用中括号,即对象[属性名称],属性名称可以是一个字符串,也可以是一个变量,比如我这样写都是可以的
window['hello']();
var p = 'hello';
window[p]();
我之前说了,this永远指向当前函数的调用者。那么,我们调用hello函数,其实也就是window对象调用了这个hello函数。既然如此,hello函数里面的this自然就指向了window对象。因此,hello函数调用后打印出来的就是window。好了,再回到new关键字的问题上,如果我在调用函数的时候使用了new,那么会发生什么呢
function hello(){
console.log(this)
new hello();
就是函数内部产生了一个新对象,并且this指向了这个新对象,然后函数默认返回了这个新对象
function hello(){
console.log(this);
}
new hello();

var newObject = new hello();
console.log(newObject);
这样的结果就是,newObject就是函数里面的this,也就是函数内部新产生的那个对象了
这种函数还有一个别称,叫作构造函数,我可以通过构造函数构建一个对象模板
所谓对象模板,就是指用一个函数的形式设计一种对象的种类。说得简单些,比如苹果、香蕉、板栗这些食物,它们都是食物,那么我就可以设计一个对象模板描述食物的一些共同特点。比如,食物有名字、味道、颜色等属性,那么我就可以在函数中用this关键字设计这些属性
function Fruit(name,smell,color){
this.name = name;
this.smell = smell;
this.color = color;
}
我定义了一个函数Fruit,一般来说,如果这是一个构造函数,那么首字母就需要大写

因为函数在使用了new关键字以后会从内部新产生一个对象出来,而this就指向了这个对象,我就可以直接在函数里面给未来即将生成的那个对象设置属性,也可以理解为特点,在这个例子中,我设计的是一个水果构造函数,配合new关键字就会产生很多种水果对象。我也可以在这个构造函数里面给食物对象的名字属性、食用属性和效果属性,Fruit本来就是一个函数,既然是函数,自然是可以传递参数的,所以我干脆就把这些食物特点作为参数传进去
var apple = new Fruit('屎','一天吃三斤屎','越吃越牛逼');
除了用函数创建一个对象,也可以直接制作一个自定义对象出来
var apple2 ={
name:"屎",
today:"一天吃三斤屎",
niuboyi:"越吃越牛逼"
}
这是创建对象的一种方式,使用花括号就可以直接定义一个对象了。对象里面的属性和属性值都是键值对的形式,当中用冒号,不同的键值对只能用逗号分隔。键值对,键就是属性的名字,值就是属性的值。键可以用引号,也可以不用。值并不是只能是字符串,它可以是数字,也可以是字符串,甚至是函数或者另外一个对象
用构造函数的方法定义对象是有好处的。比如我需要2个苹果,使用构造函数的话,直接调用两次new函数就行了,可以非常方便地获得两个苹果。而使用大括号的方式就得写两次
var apple1 = new Fruit('大苹果','香甜可口','红色');
var apple2 = new Fruit('大苹果','香甜可口','红色');
var apple3 = {
name:"苹果",
smel1:"甜的",
color:"红色"
}
var apple4 = {
name:"苹果”,
smell:"甜的”,
color:"红色”
}
你可能会说,这太TM麻烦了吧,直接
var apple3 = {
name:"苹果",
smel1:"甜的",
color:"红色"
}
var apple4 = apple3;
不就行了吗

卜卜卜卜卜卜卜卜卜卜卜卜,这样写的话,apple3和apple4其实都是一个苹果的,不信的话,我来做一个测试
首先,给苹果对象添加一个是否被吃掉的属性
var apple3 = {
name:"苹果",
smel1:"甜的",
color:"红色",
isEat:false
}
然后让变量apple4等于apple3,修改apple4的eat属性,把没有被吃掉的状态改成已经被吃掉。接着,查看apple3是否跟着一起被改变就知道了
var apple3 = {
name:"苹果",
smel1:"甜的",
color:"红色",
isEat:false
}

var apple4 = apple3;
apple4.isEat = true;
console.log(apple3);
输出结果是isEat:true
8.回调函数
回调函数就是把一个函数的定义当作参数传递给另一个函数,举个例子
正常情况下,函数传参可以是一个数字,也可以是一个字符串,这都没有问题。但是JavaScript提供了一种强大的特性,就是:函数也可以作为另一个函数的参数。比如我现在有一个‘吃饭’的函数,既然是‘吃饭’的函数,我就得考虑吃什么、怎么吃、要加什么佐料的问题
function eat(food,howToEat,tiaoliao){
hmUI.showToast({text:tiaoliao + ',' + howToEat + "吃" + food});
}
这是一个‘吃饭’函数,并且我把食物、怎么吃和佐料都当作参数传了进去。那么,当我要调用这个‘吃饭’函数的时候,可能是这个样子的
eat('羊肉串','笑嘻嘻的','撒上一撮孜然');
代码运行结果是:撒上一撮孜然,笑嘻嘻地吃羊肉串。

这样做is very good,但是如果我要给这个函数添加新的条件该怎么办?我就要修改函数的参数列表,函数的代码体。如果改动比较大,会非常麻烦。所以怎么吃难道不是应该在我真的吃饭的时候才决定吗?所以,食物变量照样可以当作参数传递,因为吃什么可能是预先就想好的,但是怎么吃就很难预先考虑好了,而加什么佐料则有很大的随机性。那么,我能不能把吃的方法当作一个参数传入这个eat方法呢?到时候,在真正需要考虑怎么吃时直接调用这个已经作为参数的函数不就好了吗
function eat (food,callback){
callback(food);
}
你可能会觉得这个代码有点奇怪
不要着急,容我慢慢道来。首先,callback就是那个函数,既然是函数,我们都知道打一个括号就可以执行函数。那么在这个eat函数中,直接执行了callback,并且把另一个food参数传入了callback。意思就是说,到底怎么吃是在你调用eat函数的时候通过临时编写一个新的函数实现的!
eat('羊肉串',function(food){
hmUI.showToast({"笑嘻嘻的,撒上一撮孜然,开心地吃" + food});
}
这个函数我希望它进入eat函数执行。同时,我在编写这个匿名函数的时候也设计了参数food,这就是要吃的食物
二.开发小程序
准备工作
首先我们在米坛下载我的联网小程序模板\helloworld模板
手机用mt管理器打开,电脑解压后用VSCode(Visual Studio Code)打开
概览
我们会看到两个文件和六个文件夹(helloworld模板后四个没有)
assets:储存一些小程序需要的文件,如图标图片,电子书文本,图片控件等
page(不同小程序显示不同):储存.js代码文件
app.js:小程序逻辑
app.json:小程序信息
app-side:联网小程序会有
setting:使用设置应用的小程序会有
utils:跑龙套的
shared:共享
首先我们进入到app.json来配置小程序信息
{
"configVersion": "v2",
"app": {
"appIdType": 0,
"appType": "app",
"version": {
"code": 1,//第几个版本
"name": "1.0.0"//小程序版本号
},
"appId": 100001,//appid,来保证小程序不会被覆盖,类似安卓包名
"appName": "小程序",//小程序名字
"icon": "icon.png",//小程序图标
"vender": "bandbbs",//开发者名字
"description": "helloworld"//小程序简介
},
"permissions": ["gps"],//小程序权限
"runtime": {
"apiVersion": {
"compatible": "1.0.0",
"target": "1.0.1",
"minVersion": "1.0.0"
}
},
"i18n": {
"en-US": {
"name": "helloworld"//多语言
}
},
"defaultLanguage": "en-US",//默认语言
"debug": false,//调试选项
"module": {
"page": {
"pages": [
"page/192x490_s_l66/index.page"//默认打开路径
],
"window": {
"navigationBarBackgroundColor": "#ffffff",
"navigationBarTextStyle": "black",
"navigationBarTitleText": "",
"backgroundColor": "#eeeeee",
"backgroundTextStyle": "light"
}
}
},

"platforms": [
{
"name": "l66",
"deviceSource": 260
},
{
"name": "l66w",
"deviceSource": 261
},
{
"name": "l66_1",
"deviceSource": 262
},
{
"name": "l66w_2",
"deviceSource": 263
},
{
"name": "l66_3",
"deviceSource": 264
},
{
"name": "l66_4",
"deviceSource": 265
},
{
"name": "l66_5",
"deviceSource": 266
}
],
"designWidth": 192,
"packageInfo": {
"mode": "production",
"timeStamp": 1648534836,
"expiredTime": 172800,
"zpm": "2.1.49"
}
}
反正看着改就行了PS:我的联网小程序模板appld更改后要在app.js里更改appld
接着进入page中的192x490_s_l66的index.page
然后你就可以写代码了
小程序开发注意事项:
1.小米手环7搭载的系统是Zepp OS1.0,无法使用2.0的API,PS:也不可能升到2.0,官方的解释是小米手环7升级2.0需要综合评估设备性能及用户体验,短期内无法实现
3.不支持使用 new Function 创建函数(可使用Vepp框架解决)
new Function('return this') 除外
4.Zepp OS的ECMAScript不支持以下特性
Promise
Generator 函数
定时器
5.小米手环7不支持异步,比如不能提前加载好界面再进入

这里汇总一下开放时用的工具
电脑手机通用
官方文档:https://docs.zepp.com/zh-cn/docs/1.0/reference/app-json/
小程序模板:https://www.bandbbs.cn/threads/4250/
小程序表盘模板:https://www.bandbbs.cn/threads/4007/
修改图片尺寸:https://www.gaitubao.com/
图片转换:https://watchface-web-editor.rth1.one/
跳转官方应用API:https://www.bandbbs.cn/threads/4859/page-2#post-221632
MDN文档(学JavaScript必备):https://developer.mozilla.org/zh-CN/
电脑专属
图片转换:https://www.bandbbs.cn/threads/3953/或https://melianmiko.ru/zmake/
Zeus开发:https://www.bandbbs.cn/threads/5237/
Vepp框架:https://www.bandbbs.cn/threads/5754/(使用方法见下面)
模拟器:https://melianmiko.ru/zepp_player/(电脑端强烈建议,无需配置)
常用API
控件方面
文本
hmUI.createWidget(hmUI.widget.TEXT, {//创建控件
x: 5,//x坐标
y: 10,//y坐标
w: 200,//宽度
h: 70,//长度
color: 0xFFF200,//文本颜色
text_size: 40,//文本大小
text: "hhh"
});//结束
图片
hmUI.createWidget(hmUI.widget.IMG, {
x: 78,
y: 421,
src: "help.png"
})
跳转页面,跟在非按钮控件后
.addEventListener(hmUI.event.CLICK_UP, function (c) {
hmApp.gotoPage({
url: "page/192x490_s_l66/index.page2",
param: "..."
})
});
图片帧
hmUI.createWidget(hmUI.widget.IMG_ANIM, {
x: 0,
y: 189,
anim_path: "images/Mars", //目录
anim_prefix: "bg",//前缀
anim_ext: "png",//后缀
anim_fps: 3,//帧率
anim_size: 313,//数量
repeat_count: 5,
anim_repeat: false,
display_on_restart: true,
});
填充矩形
hmUI.createWidget(hmUI.widget.FILL_RECT, {
x: 125,
y: 125,
w: 230,
h: 150,
radius: 20,//圆角
color: 0xfc6950//颜色,十六进制
})
描边矩形
hmUI.createWidget(hmUI.widget.STROKE_RECT, {
x: 125,
y: 125,
w: 230,
h: 150,
radius: 20,
line_width: 4,//线宽
color: 0xfc6950
})
圆形
hmUI.createWidget(hmUI.widget.CIRCLE, {
center_x: 240,//圆心x坐标
center_y: 240,//圆心y坐标
radius: 120,//半径
color: 0xfc6950,
alpha: 200//透明度
})
按钮
hmUI.createWidget(hmUI.widget.BUTTON, {
x: 40,
y: 240,
w: 400,
h: 100,
radius: 12,//圆角
normal_color: 0xfc6950,//一般按钮色
press_color: 0xfeb4a8,//按压按钮色
text: 'Hello',//文字
click_func: () => {//回调,触发事件
hmApp.gotoPage({url: "page/192x490_s_l66/index.page4",param: "..."});//跳转页面
}
})
提示
hmUI.showToast({
text: 'Zepp'//文本内容
})
其它方面
跳转页面
hmApp.gotoPage({url: "page/192x490_s_l66/index.page4",param: "..."});
简易全局变量
var fps=hmFS.SysProSetInt('fps');
var fps=hmFS.SysProGetInt('fps');
简易主题
var menusittuughhsdgbfjhgwaqeurdfgaiehriuaq = menufou.createWidget(hmUI.widget.BUTTON, {
x: 0,
y: 5,
w: 192,
h: 45,
radius: 10,
color: 0xffffff,
text_size: 25,
text: "黑配白",
click_func: () => {
hmFS.SysProSetInt("indexcolor", 1);
}
})
var menugale = menufou.createWidget(hmUI.widget.BUTTON, {
x: 0,
y: 50,
w: 192,
h: 45,
radius: 10,
color: 0xffffff,
text_size: 25,
text: "白配黑",
click_func: () => {
hmFS.SysProSetInt("indexcolor", 2);
}
})
调用:
var indexcolortext = hmFS.SysProGetInt("indexcolor");
var indexcolorrec;
if(indexcolortext == undefined){
indexcolortext="0xffffff"
indexcolorrec="0x000000"
}else if(indexcolortext == 1){
indexcolortext="0xffffff"
indexcolorrec="0x000000"
}else if(indexcolortext == 2){
indexcolortext="0x000000"
indexcolorrec="0xffffff"
}
多语言
const language = hmSetting.getLanguage();
switch(language){
case 0:
Battery_text = "电量";
case 1:
Battery_text = "電量";
break;
case 2:
Battery_text = "Battery";
break;
default:
Battery_text = "Battery";
}
hmUI.createWidget(hmUI.widget.TEXT, {
x: 5,//x坐标
y: 10,//y坐标
w: 200,//宽度
h: 70,//长度
color: 0xFFF200,//文本颜色
text_size: 40,//文本大小
text: Battery_text
});//结束
然后再来讲一下Vepp的功能以及如何使用
官方的解释是Vepp的核心功能是声明式渲染,订阅式更新
首先除声明式之外还有一种叫做命令式,两个的区别是
命令式:在哪里做什么,怎么做
声明式:在哪里做什么
比如说我们有一个数组,要让里面的每个数字乘以2
命令式:
var arr=[2,4,5,6]
var arr2=[]
for(var i=0;i<arr.length;i++){
arr2.push(arr*2)
}
声明式:
var arr=[2,4,5,6]
var arr2=arr.map(item=>item*2)
console.log(arr2)
这样就少写了很多的代码,至于订阅式更新的意思就是让手环在云端注册并自动接收最新的资源
另外,Vepp绕过了new Functuon,而且可以在官方的基础上套壳打造控件系统
如何使用
电脑打开https://github.com/coreybutler/nvm-windows/releases下载并安装
电脑同时按住Win+R,输入cmd,输入npm install vepp
下载完成后返回桌面
右键,新建文本文档
将后缀名改为.js,用Vscode打开
然后看着Vepp官方的介绍图
第一行是import Vepp from 'vepp'
其实就是使用下载好的Vepp框架
把这段代码插入到js文件顶部即可
然后后两行就是写好的小程序逻辑(helloword模板不需要,我的联网小程序模板直接复制即可)
第四行是一个let变量,也是用VML创建控件,这一行直接复制粘贴即可
然后ui这里就是控件的属性,可参照官方的改

然后data这里也是配置这个控件的一些信息

这段代码总结起来就是Vepp创建新页面,该页面会有一个名为mytext的文本控件,这个文本控件可以被点击,然后触发myfunc函数,这样就能打印出mytext的值
太牛了,點個贊,製作真的不易👍👍😄
 
这些是quick加密后的文件,也就是将文件转换为手环能听懂的语言,类似于汇编语言,目前市民上没有破译工具,除非有大佬开发出quick反编译工具
抱歉现在才关注到你的回复。我也想让我的js文件变成bin文件,我搜索引擎检索了quick加密但是没找到相关资料,如果我想让其变成bin文件我该怎么做?使用官方的工具Zeus吗?
 
很简洁了,别的教程里都是作用域提升,回调函数,我这个只是把一些专业的叫法融入进去了,这些都是JavaScript最基础的了,一些math对象,Array方法什么的都没讲啊,实在不行,我编成童话故事得了
建议做个flash动画)doge
 
大佬,我在制作表盘的时候遇到一点问题吗,我的表盘在模拟器上能跑,但是安装到手环上时一直表盘同步失败,不知道你有没有遇到这种情况呢
 
大佬,我在制作表盘的时候遇到一点问题吗,我的表盘在模拟器上能跑,但是安装到手环上时一直表盘同步失败,不知道你有没有遇到这种情况呢
额,请尝试将手环与手机重连
 
目录
一.JavaScript的基本知识

二.开发小程序

三.Vepp框架的使用

四.手机也能用vscode?


那让我们开始吧

众所周知,小米手环7搭载Zepp OS,Zepp OS的小程序开发语言是JavaScript(简称JS),它的解释器是quick.js
//解释器就是把你写的代码转换为Zepp OS能听懂的代码,加密小程序也就是提前把小程序转换为Zepp OS能听懂的代码
Zepp OS是纯JavaScript的环境,类node(CSS可以使用外挂CSS代替,但没必要,动效照样实现不了)
所以我们要掌握JavaScript的一些基本知识
一.JavaScript的基本知识
1.变量
声明变量的方式有两种,分别是 var ,let
变量就像一个装东西的盒子,里面装东西就叫赋值
其中var的全称叫做variable,意思是变量,可变物,而let是"让"的意思,所以就是让×××=×××
其中var是很老很老的声明变量方式了,它可以重新赋值,用于声明一个函数作用域或全局作用域的变量//看不懂也
let是偏现代的声明变量方式,let语句声明一个块级作用域的局部变量,也可以重新赋值。还有一种和变量相似的,叫做常量,常量只有const,常量不能重新赋值,而const全称是constant,常量,恒量,总之,以后变量用let,常量用const

变量的使用
注意!变量名只能以$,字母,下划线开始,如果变量赋值是大写,使用时是小写,则不是同一个变量,输出结果为undefined
我这里拿Apple健康的做演示
let step = hmSensor.createSensor(hmSensor.id.STEP);
其中var后面的step,就是变量名,等于号后面的则是将此变量赋值为获取到的系统步数
也可以这样写
let step;
step = hmSensor.createSensor(hmSensor.id.STEP);
这种是先声明一个变量,再对其进行赋值,但这种比较麻烦,而且当你忘记赋值时获取变量会被输出undefined
而我们后续使用这个变量时,我们可以这样写
hmUI.createWidget(hmUI.widget.TEXT, {//创建控件
x: 5,//x坐标
y: 10,//y坐标
w: 200,//宽度
h: 70,//长度
color: 0xFFF200,//文本颜色
text_size: 40,//文本大小
text: step.current//将文本输出为先前定义好的变量值,.current是当前值,仅部分更新式变量使用,其它变量请自行去掉
});//结束
当然,我们还可以设置变量为x坐标,y坐标,宽度还有长度
此处参考Battery program
变量:let battery = hmSensor.createSensor(hmSensor.id.BATTERY);
控件hmUI.createWidget(hmUI.widget.FILL_RECT, {
x: 20,
y: 80,
w: battery.current,
h: 50,
radius: 15,
color: 0x078707,
});
一般变量过多可以全部放开头
2.注释
//是单行注释,/* */是多行注释,可以用来暂时封存代码或者解释某段代码,在团队开发中能提高代码可读性,从而提升开发效率
3.字符串,布尔值
字符串就是单双引号包起来的文字
布尔值是true或false
4.条件语句
(1).switch语句
switch语句简单讲就是分情况匹配相应代码
switch语句的作用一般用于小程序简易语言国际化,如环管和像素鸟都采用了这种方法
const language = hmSetting.getLanguage();// const 常量
switch(language){// ()是赋值的变量
case 0:// 当输出结果为0,则执行以下代码(0在官方文档里的是中文)
Battery_text = "电量";
break;
case 1:
Battery_text = "電量";
break;
case 2:
Battery_text = "Battery";
break;
default: // 当输出结果不属于以上的任何代码时执行以下代码
Battery_text = "Battery";
}
这段代码可以使小程序语言简易国际化,我们也可以用if语句代替(或者直接用po文件)
(2).if语句
if语句和switch语句差不多,只不过if语句一般用来确定一个范围的值,而switch只能用来返回数值
if语句也有多分支
双分支一般是if (/条件判断/){/执行语句/} else {/反之执行语句/}
多分支一般是if(/条件判断/) {/执行语句/}} else if(/条件判断/) {/执行语句/}}
其中圆括号里填的是条件判断,建议和下面讲到的运算符搭配使用,如果条件判断里没有运算符,则是判断是否有这个变量
具体事例
if (indexcolortext == undefined) {
indexcolortext = "0x000000";
indexcolorrec = "0xFFFFFF";
} else if (indexcolortext == 1) {
indexcolortext = "0xFFFFFF";
indexcolorrec = "0x000000";
} else if (indexcolortext == 2) {
indexcolortext = "0x000000";
indexcolorrec = "0xFFFFFF";
}
5.break,continue 和 return
前两者可以提前跳出循环
break 语句中止当前循环,switch,并把程序控制流转到紧接着被中止语句后面的语句
continue 并不会终止循环的迭代,而是在 while 循环中,控制流跳转回条件判断,在 for 循环中,控制流跳转到更新语句
return 语句终止函数的执行,并返回一个指定的值给函数调用者
给大家举个简单的例子
function getRectArea(width, height) {
if (width > 0 && height > 0) {
return width * height;
}
return 0;
}

console.log(getRectArea(3, 4));

console.log(getRectArea(-3, 4));
那么大家觉得这个输出结果是多少
答案分别是12,0
首先我们看第一行,我定义了一个名为getRectArea的函数,里面的参数是width和height,这个函数里面有一个if语句,用来条件判断
当width和height大于0,那么返回结果是width乘以height,反之输出0,之后打印的语句就无需多讲了,第一个都大于0,所以返回结果是12,第二个-3小于0,所以返回结果是0
6.运算符
(1).算数运算符
JavaScript算数运算符,无非加、减、乘、除、取余、自增,自减运算符
比如
var a = 10;
var b = 2;
var c = a++;
var d = a--;
var e = ++a;
var f = --a;
var s1 = a + b;
var s2 = a - b;
var s3 = a * b;
var s4 = a / b;
console.log(c);
console.log(d);
console.log(e);
console.log(f);
console.log(s1);
console.log(s2);
console.log(s3);
console.log(s4);
前几个好说,但是cd和ef有什么区别呢
区别就是运行c和d时,得出结果是10和11,而e和f得出来是11和10
这是为什么呢
因为自增或自减运算符,它分为前置和后置,前置就是在变量的
前面写一个“++或--”,后置就是在变量的后面写一个“++或--”
后置的特点是先让变量参与运算,运算结束以后再进行自增
而前置就是a的意思是先让a的值自增一次,a本来是10,经过自增就变成了11,因为前置++的含义是先让变量自增,再放进式子里面运算,所以这个代码的结果就是11
(2).比较运算符
==等于
!=不等于
大于号是大于
<小于
大于号加=是大于或等于
<=小于或等于
(3).逻辑操作符
((1)).逻辑与操作符
用处是确定一个范围
简单写一个
let battery = hmSensor.createSensor(hmSensor.id.BATTERY);
if (battery.current => 10 && battery.current <= 20) {
hmUI.showToast({text: '电量不足20%'})
}
其中&&就是逻辑与操作符,可以理解为"且"这个字
((2)).逻辑或操作符
用处是有多个条件而且只要有一个符合条件就执行
我们就需要用到逻辑或运算符||,不要以为我打错了啊,它就是两条竖线
((3)).逻辑非操作符
它就是一个!,用处是取反,就是当得到结果是true,转为false,false转为true,如果是在if语句或switch语句中要将条件判断再加一个括号
7.console的多种用途

console.clear()
清空控制台,并输出 console was cleared

console.error()
打印一条错误信息

console.log()
打印内容

console.warn()
打印警告信息
8.循环
(1).for循环
for循环的用处是一遍又一遍地运行相同的代码,并且每次的值都不同
for (表达式1;表达式2;表达式3)
{
语句
}
首先要强调两点:
((1)).表达式1、表达式2和表达式3之间是用分号;隔开的,千万不要写成逗号
((2)).for(表达式1;表达式2;表达式3)的后面千万不要加分号,很多新手都会犯这种错误——会情不自禁地在后面加分号
实践
for(var i = 0;i<10;i++){
console.log(i);
}
(2).while循环
和for循环差不多
实践
var i = 0;
while(i<10){
console.log(i);
i++;
}
它和for循环只是在语法上有所不同,其作用和for循环是一样的
9.数组
数组可以生成一个有多个数据的变量
数组有4种定义方式
这里我就采用最常用的直接量
var arr = ["first","second","third"];
hmUI.showToast({text: arr})
得到的结果就是生成了一个拥有3个元素的数组,数组的名字是arr
或者我们可以先创建一个数组,不往里面放数据
var arr = new Array();
这个括号里放的是数组的数据数量,不填也可以
然后再给它填入数据
arr [0] = "first";
arr [1] = "second";
arr [2] = "third";
hmUI.showToast({text: arr});
如果我们需要指定输出的数据,只需这样写
hmUI.showToast({text: arr [0]})
如果我们想用数组创建多个数组可以这么写
const dataList = [
{ text: '九转大肠', x: 5, y: 10, w: 200, h: 70, color: 0xFFF200, text_size: 40, id: hmUI.widget.TEXT},
{ text: '老马', x: 5, y: 10, w: 200, h: 70, color: 0xFFF200, text_size: 40, id: hmUI.widget.TEXT}
]
hmUI.showToast({text: dataList.text})

10.函数
函数 function 翻译过来是功能
函数有八大要素
(1).函数的定义
函数是一组可以被重复调用的代码语句,它可以大大减少你的代码,函数的格式是这样的
funtion 函数名(参数,可不填){
执行的代码,叫做函数体
}
做个示范
funtion showToast(){
hmUI.showToast({text: '函数被调用了'});
}
我定义了一个函数,函数的名字就是showToast,后面的这小括号里面是用来放参数的,也就是说,函数里面如果需要用到一些从外面传进来的数据,就可以通过参数变量做传递。最后就是函数体了,用大括号扩起来的部分就是函数的函数体。在函数体中可以编写多条JavaScript代码,当然,因为只是举一个例子,所以我的函数里面只写了一个最基本的showToast控件,只会弹出一个提示,接下来,我们需要调用函数
showToast();
调用函数只需写一个函数名加一个括号(里面写的是参数,也可不传)然后加分号即可调用函数,执行函数的函数体了
这是第一种函数定义方法,还有的二种方法
var showToast = function(){
hmUI.showToast({text: '函数被调用了'});
}
这是第二种定义函数的方法,和第一种方法有所不同,第二种定义函数的方法就是按照变量的方式来定义函数,变量的名字就是函数的名字,也就是说,我要想调用这个函数,就直接调用变量的名字即可
不过这种方法也有缺点,比如我在这个定义这个变量前调用这个变量(函数),那就会报错,而用第一种方法则不会有这种问题
(2).作用域
在JavaScript中,作用域分为两种,第一种是全局作用域,第二种是函数作用域,作用域就是指当你要查找某一个变量的时候,你可以在什么范围内找到这个变量。这个寻找的范围,就是作用域,关于全局作用域,先看个比较简单的例子
var a = 10;
function test(){
console.log(a);
}
变量a和test函数都直接暴露在外面,因此它们都是全局作用域,而test函数的代码(函数体)就是函数作用域,因为test函数属于全局作用域,而它自己还有一个函数作用域,那么这样一来,全局作用域里面有一个函数作用域,函数体可以访问全局作用域中的变量,但是反过来不行,请看刚才的例子
function test(){
console.log(a);
}
var a = 10;
test();
如果我直接调用test函数,答案必然是10。在这个例子中,函数作用域里面的a会先去函数作用域里面找变量a。找不到就去全局作用域中找。全局作用域中有一个变量a。那么,在执行函数体的时候,就访问全局作用域里的变量a,但是反过来就不行,比如这样
function test(){
var a = 10;
}
console.log(a);
刚才已经说了,函数作用域里可以访问全局作用域里面的变量。但是全局作用域想调用函数作用域中定义的变量却是做不到的,因此当发生作用域嵌套的时候,只能函数访问全局,全局无法访问函数。而且作用域嵌套一般是全局作用域和函数作用域,或者是函数作用域和其他函数作用域。比如,下面这种形式就不是作用域嵌套
if(true){
var a = 20;
}
console.log(a);
代码运行结果是20
虽然变量a的定义写在了花括号里面,但是JavaScript只有全局作用域和函数作用域以及块级作用域,这里没有函数所以不算作用域嵌套,这里的a是全局作用域里的,console.log也是全局作用域里的,自然可以访问同为全局作用域里面的变量a
块作用域就是用花括号包起来的,ECMAScript6中新增了块级作用域,使用let声明的变量只能在块级作用域里访问,块作用域由 { } 包括,if语句和for语句里面的{ }也属于块作用域
(3).参数传递
参数就是函数调用时传进来的值,也就是说,我们在定义参数的时候并不知道调用的过程中会有什么样的值传过来
function add(a,b,c){
let sum = a + b + c;
hmUI.showToast({text: sum})
}
add(1,2,3);
代码运行结果是6
这是一个最简单的参数传递的例子,函数名字的定义要让人一看便知,这个例子中,add函数就是做加法。传进去了三个参数,分别是1,2,3,这三个参数分别对应add函数中圆括号内,也就是参数列表内的a、b、c三个变量
在刚才的例子中,如果你要调用add函数,就必须传入三个参数,就像下面的代码
add(1,2,3);
函数的调用就是在函数名字的右边加上一对小括号,这样就会执行函数里面的代码体,也就是下面这个部分
function add (a,b,c){
var sum = a + b + c;
hmUI.showToast({text: sum})
}
函数的代码体一般都是用花括号扩起来的,每写完一句,就要打一个分号,现在我们来看一下这个函数的函数体里面都发生了些什么事情,首先是第一行
var sum = a + b + c;
sum是一个新定义的变量,这个变量是定义在函数作用域里面的,外面的全局作用域是没有办法直接访问函数作用域里面的sum变量的。所以,这个sum变量只能被这个函数的函数体被访问,它被叫作局部变量,然后是加法和赋值,把a、b、c三个变量相加之后得到一个总量,然后把这个总量赋给局部变量sum。下面是一个showToast控件,就是把sum变量弹出提示
如果我们调用函数是只传了一个参数该怎么办
对于这个问题,我们可以把它单独拆分出来看。比如,我定义了一个函数,设置了一个参数,但是传参的时候却一个参数都没有传
function fun(a){
hmUI.showToast({text: a})
}
这是一个简单的函数,fun函数设置了一个叫a的参数,这个a还没有说明是什么,我在这个函数的函数体中写了一条showToast控件,接下来,我要调用这个函数,而且不写参数,就像这样
fun();
这是一个非常古怪的例子,因为fun函数是要求填写一个叫a的参数的,可是在调用函数时没有参数传递进来,这是不允许的,可是当这种情况真的发生了会怎样呢?试一下便知
输出结果是undefined
没错,结果就是undefined。其实,可以把这个例子再次细分。刚才的函数中有一个参数a,那么这个参数属于函数作用域,就相当于这样
function fun(){
var a;
hmUI.showToast({text: a});
}
函数的参数可以简单地看成是在函数体,即里面的第一行定义了一个变量。因为我们并没有给这个变量赋值,所以这个局部变量就是undefined,任何变量在被赋值之前,都是undefined,这些函数的参数可以被理解为一种预备变量。接下来说说正常的情况,比如我调用fun函数,传递一个参数18。传参的过程就相当于是给预备变量赋值的过程。如果没有传参,那么预备变量自然还是undefined。再回到刚开始的例子,看一下如果只传一个参数的情况
function add(a,b,c){
var sum = a + b + c;
console.log(sum);
}
add(1);
这种情况下,a的值是1,b和c的值就是undefined,那么数字1和2个undefined相加会是多少呢?真是有意思的问题。结果是NaN,代表无法计算。没错,如果真的那样做,那么就是没有任何意义的。最起码在这个函数中,那样的做法是毫无意义的
那么我们多传一个参数会怎么样呢
对于这个问题,其实也可以单独拆解出来。好比我定义了一个函数fun,但没有参数,如果我在调用fun函数的时候故意给它加了一个参数,会发生什么?比如像这样
function fun(){
}
fun(10);
结果可想而知,自然是什么都不会发生啦。再回到刚才的例子中,就算你强行加了第四个参数,对结果也不会有什么影响
(4).闭包
用处:
((1)).子函数使用父函数变量的行为
直接说你可能不懂,我们来举个例子吧
function a(){
let leo = 1;
function b(){
hmUI.showToast({text: leo});
};
b();
}
可以理解为a函数是一个大圈,b函数是一个小圈,a函数包着b函数,a函数有一个leo变量,然后b函数访问a的变量
((2)).延长被调用父函数变量的生命周期
首先,JavaScript中一个变量如果你后面不使用了,就会被回收,拿上面的例子举例,因为b函数调用了a函数里的leo变量,所以可以延长a函数的变量的生命周期,也就是父函数的生命周期
((3)):拓展父函数的空间
简单来说就是父函数代码变多了,因为里面有个子函数
(5).自执行函数
很多时候,我们只想执行一个函数,却无所谓这个函数叫什么名字。那么在这种情况下,就可以考虑使用自执行函数了。自执行函数的格式是这样子的
语法 : (定义一个没有名字的函数)();
接下来举一个具体的例子,看看如何定义一个自执行函数
(
function(){
console.log(123);
}
)();
这便是一个简单的自执行函数了,所谓自执行函数,顾名思义,就是在定义之后就立刻执行的函数,它一般是没有名字的。也正因为自执行函数没有名字,所以它虽然会被立刻执行,但是它只会被执行一次
自执行函数一般可以和闭包配合使用,举个例子
function test (){
var a = 0;
return function (increment){
a = a + increment;
hmUI.showToast({text: a})
}
}
在这个闭包的例子中,其实我想要得到test函数里面的内部函数,所以我并不是很需要知道外面这个函数叫什么名字,它可以叫任何名字,甚至不需要有名字,所以我要使用自执行函数
(6).this
先看代码
function hello(){
console.log(this);
}
我定义了一个函数hello,里面只有一个打印语句,打印出this对象。this是JavaScript中的一个关键字,它的意思就是this永远指向当前函数的调用者 这句话是关于this的一条铁律,首先,这句话透露出的第一个信息是,this只出现在函数中。第二个信息是,这个函数是谁调用的,this就是谁
(8).new函数
前面说过,JavaScript里面分为全局作用域和函数作用域,在全局作用域里面定义的任何东西,都属于window对象。也就是说,hello函数也是window对象的hello函数。而对象可以通过两种方式调用它里面的属性。第一种是点的方式,比如这样
window.hello();
第二种方式是使用中括号,即对象[属性名称],属性名称可以是一个字符串,也可以是一个变量,比如我这样写都是可以的
window['hello']();
var p = 'hello';
window[p]();
我之前说了,this永远指向当前函数的调用者。那么,我们调用hello函数,其实也就是window对象调用了这个hello函数。既然如此,hello函数里面的this自然就指向了window对象。因此,hello函数调用后打印出来的就是window。好了,再回到new关键字的问题上,如果我在调用函数的时候使用了new,那么会发生什么呢
function hello(){
hmUI.showToast({text: this})
new hello();
就是函数内部产生了一个新对象,并且this指向了这个新对象,然后函数默认返回了这个新对象
function hello(){
hmUI.showToast({text: this})
}
new hello();

var newObject = new hello();
console.log(newObject);
这样的结果就是,newObject就是函数里面的this,也就是函数内部新产生的那个对象了
这种函数还有一个别称,叫作构造函数,我可以通过构造函数构建一个对象模板
所谓对象模板,就是指用一个函数的形式设计一种对象的种类。说得简单些,比如苹果、香蕉、板栗这些食物,它们都是食物,那么我就可以设计一个对象模板描述食物的一些共同特点。比如,食物有名字、味道、颜色等属性,那么我就可以在函数中用this关键字设计这些属性
function Fruit(name,smell,color){
this.name = name;
this.smell = smell;
this.color = color;
}
我定义了一个函数Fruit,一般来说,如果这是一个构造函数,那么首字母就需要大写

因为函数在使用了new关键字以后会从内部新产生一个对象出来,而this就指向了这个对象,我就可以直接在函数里面给未来即将生成的那个对象设置属性,也可以理解为特点,在这个例子中,我设计的是一个水果构造函数,配合new关键字就会产生很多种水果对象。我也可以在这个构造函数里面给食物对象的名字属性、食用属性和效果属性,Fruit本来就是一个函数,既然是函数,自然是可以传递参数的,所以我干脆就把这些食物特点作为参数传进去
var apple = new Fruit('屎','一天吃三斤屎','越吃越牛逼');
除了用函数创建一个对象,也可以直接制作一个自定义对象出来
var apple2 ={
name:"屎",
today:"一天吃三斤屎",
niuboyi:"越吃越牛逼"
}
这是创建对象的一种方式,使用花括号就可以直接定义一个对象了。对象里面的属性和属性值都是键值对的形式,当中用冒号,不同的键值对只能用逗号分隔。键值对,键就是属性的名字,值就是属性的值。键可以用引号,也可以不用。值并不是只能是字符串,它可以是数字,也可以是字符串,甚至是函数或者另外一个对象
用构造函数的方法定义对象是有好处的。比如我需要2个苹果,使用构造函数的话,直接调用两次new函数就行了,可以非常方便地获得两个苹果。而使用大括号的方式就得写两次
var apple1 = new Fruit('大苹果','香甜可口','红色');
var apple2 = new Fruit('大苹果','香甜可口','红色');
var apple3 = {
name:"苹果",
smel1:"甜的",
color:"红色"
}
var apple4 = {
name:"苹果”,
smell:"甜的”,
color:"红色”
}
你可能会说,这太TM麻烦了吧,直接
var apple3 = {
name:"苹果",
smel1:"甜的",
color:"红色"
}
var apple4 = apple3;
不就行了吗

卜卜卜卜卜卜卜卜卜卜卜卜,这样写的话,apple3和apple4其实都是一个苹果的,不信的话,我来做一个测试
首先,给苹果对象添加一个是否被吃掉的属性
var apple3 = {
name:"苹果",
smel1:"甜的",
color:"红色",
isEat:false
}
然后让变量apple4等于apple3,修改apple4的eat属性,把没有被吃掉的状态改成已经被吃掉。接着,查看apple3是否跟着一起被改变就知道了
var apple3 = {
name:"苹果",
smel1:"甜的",
color:"红色",
isEat:false
}

var apple4 = apple3;
apple4.isEat = true;
console.log(apple3);
输出结果是isEat:true
8.回调函数
回调函数就是把一个函数的定义当作参数传递给另一个函数,举个例子
正常情况下,函数传参可以是一个数字,也可以是一个字符串,这都没有问题。但是JavaScript提供了一种强大的特性,就是:函数也可以作为另一个函数的参数。比如我现在有一个‘吃饭’的函数,既然是‘吃饭’的函数,我就得考虑吃什么、怎么吃、要加什么佐料的问题
function eat(food,howToEat,tiaoliao){
hmUI.showToast({text:tiaoliao + ',' + howToEat + "吃" + food});
}
这是一个‘吃饭’函数,并且我把食物、怎么吃和佐料都当作参数传了进去。那么,当我要调用这个‘吃饭’函数的时候,可能是这个样子的
eat('羊肉串','笑嘻嘻的','撒上一撮孜然');
代码运行结果是:撒上一撮孜然,笑嘻嘻地吃羊肉串。

这样做is very good,但是如果我要给这个函数添加新的条件该怎么办?我就要修改函数的参数列表,函数的代码体。如果改动比较大,会非常麻烦。所以怎么吃难道不是应该在我真的吃饭的时候才决定吗?所以,食物变量照样可以当作参数传递,因为吃什么可能是预先就想好的,但是怎么吃就很难预先考虑好了,而加什么佐料则有很大的随机性。那么,我能不能把吃的方法当作一个参数传入这个eat方法呢?到时候,在真正需要考虑怎么吃时直接调用这个已经作为参数的函数不就好了吗
function eat (food,callback){
callback(food);
}
你可能会觉得这个代码有点奇怪
不要着急,容我慢慢道来。首先,callback就是那个函数,既然是函数,我们都知道打一个括号就可以执行函数。那么在这个eat函数中,直接执行了callback,并且把另一个food参数传入了callback。意思就是说,到底怎么吃是在你调用eat函数的时候通过临时编写一个新的函数实现的!
eat('羊肉串',function(food){
hmUI.showToast({"笑嘻嘻的,撒上一撮孜然,开心地吃" + food});
}
这个函数我希望它进入eat函数执行。同时,我在编写这个匿名函数的时候也设计了参数food,这就是要吃的食物了
这就是JavaScript的常识,毕竟大部分时间看官方文档就行了
二.开发小程序
准备工作
首先我们在米坛下载我的联网小程序模板\helloworld模板
手机用mt管理器打开,电脑解压后用VSCode(Visual Studio Code)打开
概览
我们会看到两个文件和六个文件夹(helloworld模板后四个没有)
assets:储存一些小程序需要的文件,如图标图片,电子书文本,图片控件等
page(不同小程序显示不同):储存代码文件
app.js:小程序逻辑
app.json:小程序信息
app-side:联网小程序会有
setting:使用设置应用的小程序会有
utils:跑龙套的
shared:共享
首先我们进入到app.json来配置小程序信息
{
"configVersion": "v2",
"app": {
"appIdType": 0,
"appType": "app",
"version": {
"code": 1,//第几个版本
"name": "1.0.0"//小程序版本号
},
"appId": 100001,//appid,来保证小程序不会被覆盖,类似安卓包名
"appName": "小程序",//小程序名字
"icon": "icon.png",//小程序图标
"vender": "bandbbs",//开发者名字
"description": "helloworld"//小程序简介
},
"permissions": ["gps"],//小程序权限
"runtime": {
"apiVersion": {
"compatible": "1.0.0",
"target": "1.0.1",
"minVersion": "1.0.0"
}
},
"i18n": {
"en-US": {
"name": "helloworld"//多语言
}
},
"defaultLanguage": "en-US",//默认语言
"debug": false,//调试选项
"module": {
"page": {
"pages": [
"page/192x490_s_l66/index.page"//默认打开路径
],
"window": {
"navigationBarBackgroundColor": "#ffffff",
"navigationBarTextStyle": "black",
"navigationBarTitleText": "",
"backgroundColor": "#eeeeee",
"backgroundTextStyle": "light"
}
}
},

"platforms": [
{
"name": "l66",
"deviceSource": 260
},
{
"name": "l66w",
"deviceSource": 261
},
{
"name": "l66_1",
"deviceSource": 262
},
{
"name": "l66w_2",
"deviceSource": 263
},
{
"name": "l66_3",
"deviceSource": 264
},
{
"name": "l66_4",
"deviceSource": 265
},
{
"name": "l66_5",
"deviceSource": 266
}
],
"designWidth": 192,
"packageInfo": {
"mode": "production",
"timeStamp": 1648534836,
"expiredTime": 172800,
"zpm": "2.1.49"
}
}
反正看着改就行了
//我的联网小程序模板appld更改后要在app.js里更改appld
接着进入page中的192x490_s_l66的index.page
然后你就可以写代码了
小程序开发注意事项:
1.小米手环7搭载的系统是Zepp OS1.0,无法使用2.0的API
//也不可能升到2.0,官方的解释是小米手环7升级2.0需要综合评估设备性能及用户体验,短期内无法实现
3.不支持使用 new Function 创建函数(可使用Vepp框架解决)
new Function('return this') 除外
4.Zepp OS的ECMAScript不支持以下特性
Promise
Generator 函数
定时器
5.小米手环7不支持异步,比如不能提前加载好界面再进入

这里汇总一下开放时用的工具
电脑手机通用
官方文档:https://docs.zepp.com/zh-cn/docs/1.0/reference/app-json/
小程序模板:https://www.bandbbs.cn/threads/4250/
小程序表盘模板:https://www.bandbbs.cn/threads/4007/
修改图片尺寸:https://www.gaitubao.com/
图片转换:https://watchface-web-editor.rth1.one/
跳转官方应用API:https://www.bandbbs.cn/threads/4859/page-2#post-221632
MDN文档(学JavaScript必备):https://developer.mozilla.org/zh-CN/
电脑专属
图片转换:https://www.bandbbs.cn/threads/3953/或https://melianmiko.ru/zmake/
Zeus开发:https://www.bandbbs.cn/threads/5237/
Vepp框架:https://www.bandbbs.cn/threads/5754/(使用方法见下面)
模拟器:https://melianmiko.ru/zepp_player/(电脑端强烈建议,无需配置)
常用API
控件方面
文本
hmUI.createWidget(hmUI.widget.TEXT, {//创建控件
x: 5,//x坐标
y: 10,//y坐标
w: 200,//宽度
h: 70,//长度
color: 0xFFF200,//文本颜色
text_size: 40,//文本大小
text: "hhh"
});//结束
图片
hmUI.createWidget(hmUI.widget.IMG, {
x: 78,
y: 421,
src: "help.png"
})
跳转页面,跟在非按钮控件后
.addEventListener(hmUI.event.CLICK_UP, function (c) {
hmApp.gotoPage({
url: "page/192x490_s_l66/index.page2",
param: "..."
})
});
图片帧
hmUI.createWidget(hmUI.widget.IMG_ANIM, {
x: 0,
y: 189,
anim_path: "images/Mars", //目录
anim_prefix: "bg",//前缀
anim_ext: "png",//后缀
anim_fps: 3,//帧率
anim_size: 313,//数量
repeat_count: 5,
anim_repeat: false,
display_on_restart: true,
});
填充矩形
hmUI.createWidget(hmUI.widget.FILL_RECT, {
x: 125,
y: 125,
w: 230,
h: 150,
radius: 20,//圆角
color: 0xfc6950//颜色,十六进制
})
描边矩形
hmUI.createWidget(hmUI.widget.STROKE_RECT, {
x: 125,
y: 125,
w: 230,
h: 150,
radius: 20,
line_width: 4,//线宽
color: 0xfc6950
})
圆形
hmUI.createWidget(hmUI.widget.CIRCLE, {
center_x: 240,//圆心x坐标
center_y: 240,//圆心y坐标
radius: 120,//半径
color: 0xfc6950,
alpha: 200//透明度
})
按钮
hmUI.createWidget(hmUI.widget.BUTTON, {
x: 40,
y: 240,
w: 400,
h: 100,
radius: 12,//圆角
normal_color: 0xfc6950,//一般按钮色
press_color: 0xfeb4a8,//按压按钮色
text: 'Hello',//文字
click_func: () => {//回调,触发事件
hmApp.gotoPage({url: "page/192x490_s_l66/index.page4",param: "..."});//跳转页面
}
})
提示
hmUI.showToast({
text: 'Zepp'//文本内容
})
其它方面
跳转页面
hmApp.gotoPage({url: "page/192x490_s_l66/index.page4",param: "..."});
简易全局变量
hmFS.SysProSetInt('fps');
let fps = hmFS.SysProGetInt('fps');
简易主题
var menusittuughhsdgbfjhgwaqeurdfgaiehriuaq = menufou.createWidget(hmUI.widget.BUTTON, {
x: 0,
y: 5,
w: 192,
h: 45,
radius: 10,
color: 0xffffff,
text_size: 25,
text: "黑配白",
click_func: () => {
hmFS.SysProSetInt("indexcolor", 1);
}
})
var menugale = menufou.createWidget(hmUI.widget.BUTTON, {
x: 0,
y: 50,
w: 192,
h: 45,
radius: 10,
color: 0xffffff,
text_size: 25,
text: "白配黑",
click_func: () => {
hmFS.SysProSetInt("indexcolor", 2);
}
})
调用:
var indexcolortext = hmFS.SysProGetInt("indexcolor");
var indexcolorrec;
if(indexcolortext == undefined){
indexcolortext="0xffffff"
indexcolorrec="0x000000"
}else if(indexcolortext == 1){
indexcolortext="0xffffff"
indexcolorrec="0x000000"
}else if(indexcolortext == 2){
indexcolortext="0x000000"
indexcolorrec="0xffffff"
}
多语言
const language = hmSetting.getLanguage();
switch(language){
case 0:
Battery_text = "电量";
case 1:
Battery_text = "電量";
break;
case 2:
Battery_text = "Battery";
break;
default:
Battery_text = "Battery";
}
hmUI.createWidget(hmUI.widget.TEXT, {
x: 5,//x坐标
y: 10,//y坐标
w: 200,//宽度
h: 70,//长度
color: 0xFFF200,//文本颜色
text_size: 40,//文本大小
text: Battery_text
});//结束
三.Vepp框架的使用
然后再来讲一下Vepp的功能以及如何使用
官方的解释是Vepp的核心功能是声明式渲染,订阅式更新
首先除声明式之外还有一种叫做命令式,两个的区别是
命令式:在哪里做什么,怎么做
声明式:在哪里做什么
比如说我们有一个数组,要让里面的每个数字乘以2
命令式:
var arr=[2,4,5,6]
var arr2=[]
for(var i=0;i<arr.length;i++){
arr2.push(arr*2)
}
声明式:
var arr=[2,4,5,6]
var arr2=arr.map(item=>item*2)
console.log(arr2)
这样就少写了很多的代码,至于订阅式更新的意思就是让手环在云端注册并自动接收最新的资源
另外,Vepp绕过了new Functuon,而且可以在官方的基础上套壳打造控件系统
使用教程
电脑打开https://github.com/coreybutler/nvm-windows/releases下载并安装
电脑同时按住Win+R,输入cmd,输入npm install vepp
下载完成后返回桌面
右键,新建文本文档
将后缀名改为.js,用Vscode打开
然后看着Vepp官方的介绍图
第一行是import Vepp from 'vepp'
其实就是使用下载好的Vepp框架
把这段代码插入到js文件顶部即可
然后后两行就是写好的小程序逻辑(helloword模板不需要,我的联网小程序模板直接复制即可)
第四行是一个let变量,也是用VML创建控件,这一行直接复制粘贴即可
然后ui这里就是控件的属性,可参照官方的改

然后data这里也是配置这个控件的一些信息

这段代码总结起来就是Vepp创建新页面,该页面会有一个名为mytext的文本控件,这个文本控件可以被点击,然后触发myfunc函数,这样就能打印出mytext的值
试练一下
#BUTTON x: 0, y: 6, w: 2, h: 1, color: 0x49406d, normal_color: 0xc9bdff, press_color: 0xeae5ff, radius: 12, text: "是故意的", click_func: () => hmUI.showToast({text: '你尝了一口吗'})
我创建了一个按钮,是不是非常简单,使用VML创建控件比官方使用的代码要少得多

四.手机也能用vscode?
你是否觉得,手机端的开发工具mt管理器功能太匮乏了?甚至连代码纠错的功能都没有,而电脑端的vscode强大的一批,那我们有没有办法使手机用上vscode呢
答案是
有!不仅有官方的网页版vscode还有第三方移植的vscode,那让我们一起来看看吧
1.官方网页
在去年,啊不,在前年10月末的时候,微软发布了在网页端运行的vscode,网址是vscode.dev,切记!一定要用edge或chrome浏览器打开,否则白屏
不过这个网页版虽然方便,但是啊,它不支持插件(或许对于手机端根本不需要插件)
2.Code FA
虽然有很多第三方的vscode,但是Code FA是我感觉最流畅的
具体使用
下载并安装https://www.coolapk.com/apk/com.nightmare.code
下载https://www.123pan.com/s/YOtKVv-BXN0A
解压第二个链接里面的文件到/storage/emulated/0/目录
打开Code FA,将版本号改为4.6.1
加载完成后如果白屏,就用有独立内核的浏览器打开白屏的网址(我也忘了)
找到下载的两个插件,选择并等待安装
没了
感谢大佬写出这么详细的教程,先收藏了,等有空好好看
 
请问一下,那个codefa的文件目录在哪儿啊,手机文件不能共享
 

*这是一则由 Google AdSense 自动推荐的广告,与本站无关,不对其真实性与可靠性负责

相似主题

米环7小程序
58K
824

Home 首页
Home 资源
News 发现
Account 我的
顶部