在 JavaScript 中編寫程式碼的最佳作法
組織並設定程式碼格式
在頂端編寫程式碼邏輯,在結尾編寫 helper 函數。
在頂端宣告全域變數。
如果您的服務超過 150 行,請將程式碼分割為函數或多個服務。
適當命名變數與函數以減少註解數,並提高程式碼的可讀性。
使用 ThingWorx Composer 中內建的 JavaScript 自動設定格式功能。
編寫程式碼
在條件陳述式中使用真與假的假設條件。
// *** 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";
* 
這可能不是設定預設值的理想方法,因為它使用的是假陳述式。例如,如果您的值為 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.
服務
請使用 Pascal 命名法來為服務命名。
請使用內嵌參數來呼叫服務。但是,如果輸入參數很大,或者如果您想要重新使用參數,請將輸入參數儲存為變數,然後再將變數傳遞至服務呼叫。
// *** 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.
請根據您的資料粒度需求,使用適當的記錄器層級。
請僅在必要時才寫入記錄器。記錄訊息太多會導致無法偵錯。
層級 (Level)
描述
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);
}
這是否有幫助?