使用 JavaScript 编码的最佳做法
组织和格式化代码
在顶部编写代码逻辑,在末尾编写 helper 函数。
在顶部声明全局变量。
如果服务中的行数超过 150,请将代码分为函数或多个服务。
适当命名变量和函数以减少备注数,并提高代码的可读性。
使用 ThingWorx Composer 中内置的 JavaScript 自动格式化功能。
编写代码
在条件语句中使用 truthy 和 falsy 情况。
// *** will act like a false statement ***
if (false)
if (null)
if (undefined)
if (0)
if (-0)
if (0n)
if (NaN)
if ("")
// *** will act like a true statement ***
if (true)
if ({})
if ([])
if (1)
if ("0")
if ("false")
if (new Date())
if (-1)
if (12n)
if (3.14)
if (-3.14)
if (Infinity)
if (-Infinity)
// *** you can force it into a boolean value by using the !! operator ***
// i.e.:
var myNullValue = null;
var myBooleanValue = !!myNullValue; // it will store as false
执行批处理 CRUD 操作以最小化与数据库的交互作用。但是,如果从数据库接收到过多信息,则可能希望对数据库执行多次单一调用。
// *** bad practice ***
for (let i = 0; i < Things["thingName"].GetPropertyDefinitions().length; i++) {
var newEntry = new Object();
newEntry.name = Things["thingName"].GetPropertyDefinitions().rows[i].name;
newEntry.description = Things["thingName"].GetPropertyDefinitions().rows[i].description;
result.AddRow(newEntry);
}
// GetPropertyDefinitions() is called multiple times because of the looping

// *** preferred practice ***
let propertyDefinitions = Things["thingName"].GetPropertyDefinitions();
propertyDefinitions.rows.toArray().forEach((propertyDefinition) => {
result.AddRow({
name : propertyDefinition.name,
description : propertyDefinition.description
});
});
// By storing the content of GetPropertyDefinitions() ahead of time, you will prevent many unnecessary REST calls.
避免直接处理事物属性,否则可能会导致持续同步问题以及性能损失。
// *** common practice ***
var referencedInfoTable = Things["thingName"].myInfoTable;
for (let i = 0; i < 10; i++) {
var row = new Object();
row.field1 = i;
row.field2 = i * 2;
row.field3 = i * 3;
referencedInfoTable.AddRow(row);
}
Things["thingName"].myInfoTable = referencedInfoTable;
// the .AddRow() will implicitely write to cache, thus causing some performance issues

// *** preferred practice ***
var deepCloneInfoTable = Things["thingName"].myInfoTable.clone();
for (let i = 0; i < 10; i++) {
deepCloneInfoTable.AddRow({
field1 : i,
field2 : i * 2,
field3 : i * 3
});
}
Things["thingName"].myInfoTable = deepCloneInfoTable;
// the .AddRow() of the cloned infotable does not have a reference to the original infotable, therefore does not write to cache.

// *** for your information ***
var referencedInfoTable = Things["thingName"].myInfoTable;
var tableLength = referencedInfoTable.rows.length;
for (let i = 0; i < tableLength; i++) {
referencedInfoTable.rows[i] = {
field1 : i,
field2 : i * 2,
field3 : i * 3
};
}
Things["thingName"].myInfoTable = referencedInfoTable;
// because you are not using .AddRow() it doesn't require access to the referenced object's .AddRow() method, therefore it doesn't need to access the cache.
有关详细信息,请参阅 HA 应用程序的最佳做法
要在下一行中隐含 linting,请添加备注。这会略过只能通过修复 linting 库来解决的 Linting 错误,而仍然在其他地方获取 Linting。
对象
{} 代替 new Object(); 来创建对象。尝试创建内联对象值,因为它更易于读取且写入速度更快。
// *** old practice ***
var myObject = new Object();
myObject.item1 = "value1";
myObject.item2 = "value2";
myObject.item3 = "value3";
// *** preferred practice ***
var myObject = {
item1:"value1",
item2:"value2",
item3:"value3"
};
对于深层复制对象,请使用 JSON.parse(JSON.stringify(object))
变量
let 代替 var 来声明变量。用 const 来声明常量变量。
使用 let 会阻止您重新声明和覆盖变量。这样做也会阻止创建具有相同名称的函数。
// *** common mistake ***
var value = 1;
if (true) {
var value = 2;
}
// value = 2;because it was overwritten by `value` inside if statement
// *** how to avoid mistake ***
let value = 1;
if (true) {
let value = 2;
// value = 2; inside the if statement
}
// value = 1;because the `value` inside if statement does not affect the first `value`

// *** you can still access the variable in the outter scope ***
let value = 1;
if (true) {
value = 2;
}
// value = 2;because the content of `value` was replaced
使用骆驼式命名法命名变量。
要为 undefinednull 值设置默认变量值,请使用 || (OR) 运算符。
// *** traditional way ***
var myNullValue; //undefined
if (myNullValue == null || myNullValue == undefined || myNullValue == "") {
myNullValue = "something";
}
var output = myNullValue;
// *** alternate way with ternary operator ***
var myNullValue; //undefined
var output = (myNullValue == null || myNullValue == undefined || myNullValue == "") ? "something" : myNullValue;
// *** preferred practice ***
var myNullValue; //undefined
var output = myNullValue || "something";
* 
这可能不是设置默认值的理想方式,因为它使用了 falsy 语句。例如,如果您的值为 0,则 || 运算符可能会设置另一个非 0 值。
使用点符号来访问变量。括号符号仅在键中存在特殊字符时使用,例如:object.row["field name with space"]
创建变量以存储事物,并于循环中使用此变量。
// *** common mistake ***
for (let i = 0; i < 100; i++ ){
Things["thingName"].myProperty;
}
// *** best practice practice ***
let myThing = Things["thingName"];
for (let i = 0; i < 100; i++){
myThing.myProperty;
}
函数
使用骆驼式命名法命名函数。
将函数声明为函数声明,而非变量。
您无法将函数表达式和函数箭头表达式移至代码底部。
// *** function declaration (Preferred practice) ***
function doSomething1 (param) {
return param;
}
// *** function expression (Avoid) ***
const doSomething2 = function (param) {
return param;
}
// *** function arrow expression (Can be used inside .map(), .redude(), .filter()) ***
const doSomething3 = (param) => {
return param;
}
利用函数中的 return 来中断代码逻辑流。这样做有助于保存某些 CPU 操作并提高可读性。
// given that we have multiple validations, you can save some operations by leveraging a function return
// given dice1 = undefined, dice2 = null
// common way
let outputMessage;
if (dice1 > 0 && dice1 < 7) {
if (dice2 > 0 && dice2 < 7) {
if (dice1 + dice2 == 7) {
outputMessage = SUCCESS;
} else {
outputMessage = ERROR_NOT_SEVEN;
}
} else {
outputMessage = ERROR_BAD_DICE2;
}
} else {
outputMessage = ERROR_BAD_DICE1;
}
result = outputMessage;
// preferred way
result = rollSeven(dice1, dice2);
function rollSeven (dice1, dice2) {
if (!(dice1 > 0 && dice1 < 7)) {
return ERROR_BAD_DICE1;
}

if (!(dice2 > 0 && dice2 < 7)) {
return ERROR_BAD_DICE2;
}

if (dice1 + dice2 != 7) {
return ERROR_NOT_SEVEN;
}

return SUCCESS;
}
// the preferred way promotes readability. By using a return statement and only testing for failing cases it reduces the need for nested if statements.
服务
使用帕斯卡命名法命名服务。
使用内联参数调用服务。但是,如果输入参数很大,或者希望重用这些参数,那么请将输入参数存储为变量,然后将该变量传递至服务调用。
// *** old practice ***
var params = {
"param1" : "paramValue1",
"param2" : "paramValue2"
}
Thing["thingName"].CallServiceName(params);
// *** preferred practice ***
Thing["thingName"].CallServiceName({
"param1" : "paramValue1",
"param2" : "paramValue2"
});
避免使用在循环中调用数据库的服务。
信息表
使用以下方法创建信息表:
// common way
var table = Resources["InfoTableFunctions"].CreateInfoTableFromDataShape({
infoTableName : "InfoTable",
dataShapeName : "GenericStringList"
});
// easier way
var table = DataShapes["GenericStringList"].CreateValues();
使用以下方法获取信息表的长度或行:
// fetching a row
myInfoTable[i] = myInfoTable.rows[i] = myInfoTable.getRow(i)
// getting the length
myInfoTable.length = myInfoTable.rows.length = myInfoTable.getRowCount()
使用点符号访问信息表中第一行的数据:
// if you have an infotable with rows
// classical way to access the first row field
myInfoTable.row[0].field
// trick to access the first row field
myInfoTable.field
使用数组方法,例如 .map().reduce().filter().some().every(),来操控信息表或对多个信息表行进行更改。如果使用了 infotable.rows.toArray().[map() | reduce() | filter()],则可将这些方法用于信息表。
使用 .map()
// *** What you have ***
var officers = [
{ id: 20, name: 'Captain' },
{ id: 24, name: 'General' },
{ id: 56, name: 'Admiral' },
{ id: 88, name: 'Commander' }
];
// *** What you need ***
//[39, 44, 57, 95]
var officersIds = officers.map((officer) => officer.id);
result = officersIds;
将更改应用于多个信息表行:
var table = Resources["InfoTableFunctions"].CreateInfoTableFromDataShape({
infoTableName : "InfoTable",
dataShapeName : "GenericStringList"
});
table.AddRow({item:1});
table.AddRow({item:2});
table.AddRow({item:3});
table.AddRow({item:4});
Array.from(table.rows).map(row => {row.item = row.item * 2 + "hello";});
result = table;
使用 .reduce()
var table = Resources["InfoTableFunctions"].CreateInfoTableFromDataShape({
infoTableName : "InfoTable",
dataShapeName : "GenericStringList"
});
table.AddRow({item:1});
table.AddRow({item:2});
table.AddRow({item:3});
table.AddRow({item:4});
// total sum should be 1 + 2 + 3 + 4 = 10
result = Array.from(table.rows).reduce((total, row) => total + parseInt(row.item), 0);
要迭代和遍历信息表中的数据,请使用 .forEach() 循环。这样一来,性能便会得以提高。
table.rows.toArray().forEach(row => {
data += row.item;
});
对于深层复制信息表,请使用 var deepClonedInfoTable = myInfoTable.clone()
记录器消息
try … catch 语句中,将异常对象命名为 err
try {
Things["thingName"].CallServiceName();
} catch (err) {
logger.error(err);
} finally { // finally is optional
// will go here whether an exception occured or not
}
* 
建议不要将错误 (err) 记录为 JSON 格式,且必须改为将 err.message 用作最佳做法。
每次创建事物时,均在其周围使用 try catch 语句,并在 catch 语句失败的情况下将其删除。这样做可消除幻影实体的创建。如果您的系统中有幻影实体,请参阅幻影实体服务以删除幻影实体。
每当有创建实体的服务时,都要确保处理幻影实体用例。
try {
// Successfully creates the GhostThing entity.
Resources["EntityServices"].CreateThing({
name: "GhostThing" /* STRING */,
description: "Ghost" /* STRING */,
thingTemplateName: "GenericThing" /* THINGTEMPLATENAME */
});
} catch (err) {
// If an exception is caught, we need to delete the entity
// that was created to prevent it from becoming a Ghost Entity
Resources["EntityServices"].DeleteThing({
name: "GhostThing" /* THINGNAME */
});
}
如果您有任何要在监控脚本日志中显示的错误日志,那么请包括事物名称、服务名称、行号和原始错误消息等信息。切记,您可在日志中将 {} 用作字符串格式的占位符。请注意,经格式化的消息会泄露可能导致安全问题的不必要信息。如果您正在处理将在混搭或外部系统中使用的服务,请避免显示过多的详细信息。
异常对象中提供了如下属性:
err.lineNumber - 引发异常的行号。
err.name - 异常类型,例如 JavaException
err.fileName - 从中触发异常的服务名称。
err.message - 异常消息。
try {
// error is fired here
} catch (err) {
let errMsg = "Thing [{}] Service [{}] error at line [{}] : {}";
logger.error(errMsg, me.name, err.fileName, err.lineNumber, err);
throw err; // throw original error message
}
// log output => Thing [PTC.SCA.SCO.MyThing] Service [CallMyService] error at line [2]: TypeError: Cannot find function CallMyService in object com.thingworx.things.MyThing@251c8397.
请根据您的粒度需求使用适当的记录器级别。
仅在必要时写入记录器。日志消息过多会导致调试无法进行。
级别
说明
DEBUG
指定最适用于调试应用程序的细粒度信息性事件。
// for example, show the length of time a function takes to run.
let startTime = new Date().getTime();
// do something
let endTime = new Date().getTime();
let time = endTime - startTime;
logger.debug("How long did it take to run something (ms) : {}", time);
INFO
指定在粗粒度级别突出显示应用程序进度的信息性消息。
// for example, you wish to track the progress of your service
logger.info("Step 1 - creating variable");
let step1 = "do something";
logger.info("Step 2 - run a function");
myFunctionDoSomething(step1);
WARN
指定潜在的有害情况。
// for example, you see a potential issue here
let resultFromService = Things["thingName"].CallServiceName();
if (resultFromService == undefined) {
logger.warn("My variable is undefined... strange");
}
myFunctionDoSomething(resultFromService);
ERROR
指定可能仍允许应用程序继续运行的错误事件。
// for example, a service was not suppose to throw an error
try {
var resultFromService = Things["thingName"].CallServiceName();
} catch (err) {
logger.error("Something really bad happened: {}", err);
}
这对您有帮助吗?