Примерно так и делается. В компоненте bitrix:catalog.section со списком товаров уникальный ID для товара генерируется так:
$uniqueId = $item['ID'].'_'.md5($this->randString().$component->getAction());
//$item['ID'] - ID товара
//$this->randString() - рандомная строка
//$component->getAction() - по документации "Метод возвращает название текущего действия."
//Ниже пример подключения JS к списку товаров
$obName = 'ob'.preg_replace('/[^a-zA-Z0-9_]/', 'x', $this->GetEditAreaId($navParams['NavNum'])); //название переменной в которой, помещен JS-объект с логикой компонента.
//Ну и создание объекта в который передаются параметры.
var <?=$obName?> = new JCCatalogSectionComponent({
siteId: '<?=CUtil::JSEscape($component->getSiteId())?>',
componentPath: '<?=CUtil::JSEscape($componentPath)?>',
navParams: <?=CUtil::PhpToJSObject($navParams)?>,
deferredLoad: false, // enable it for deferred load
initiallyShowHeader: '<?=!empty($arResult['ITEM_ROWS'])?>',
bigData: <?=CUtil::PhpToJSObject($arResult['BIG_DATA'])?>,
lazyLoad: !!'<?=$showLazyLoad?>',
loadOnScroll: !!'<?=($arParams['LOAD_ON_SCROLL'] === 'Y')?>',
template: '<?=CUtil::JSEscape($signedTemplate)?>',
ajaxId: '<?=CUtil::JSEscape($arParams['AJAX_ID'])?>',
parameters: '<?=CUtil::JSEscape($signedParams)?>',
container: '<?=$containerName?>'
});