Instead of building vm.squares with an array of objects with ID's, why not just use the index of the array as the ID? You could just subtract 1 from every value of your winnerRows array and remove the object access from your win checking code.
Despite that (and to be super nitpicky), every time your outer loop iterates, it is going to check winnerRows.length, and your inner loop is going to check winnerRows[i].length. It's not going to make any real noticeable difference in this application with the number of items in your array, but since we're talking about best practices, it is always faster to compare to a literal than to compare to the result of a function or an array access. Since we know that the outer loop is always going to run 8 times, we can replace winnerRows.length with 8. And since every array in winnerRows has 3 entries, we can replace winnerRows[i].length with 3.
We can cut this down even more slightly by taking out the inner loop altogether and use the transitive property (if a = b and b = c -> a = c) to declare a winner.
We can nitpick further and possibly eliminate the checks of each piece if the first square you are checking isn't the type of the winning piece. Loops will short circuit on && if the first condition is false and will not execute the rest of the logical expression.
The following code implements all of these changes using your current implementation of vm.squares:
for (i=0; i < 8; i++) {
if (vm.squares[winnerRows[i][0]].piece === winningPiece &&
vm.squares[winnerRows[i][0]].piece === vm.squares[winnerRows[i][1]].piece &&
vm.squares[winnerRows[i][1]].piece === vm.squares[winnerRows[i][2]].piece) {
alert ('winner');
}
}
Of course, these are all just minor optimizations, and you probably won't see huge returns for an application of this size. Just some good practices in there and you eliminate the second loop.