Array.apply calls Function.prototype.apply with a calling context (a this value) of Array. If the second parameter is an array-like object, it will call the Array constructor with all of the elements of that array-like object as arguments. Eg:
Array.apply(null, [1, 2, 3, 4])
results in, and is equivalent to
Array(1, 2, 3, 4)
But argument lists have a size limit. It depends on the engine, but it looks like you probably shouldn't try to pass more than 10,000 arguments or so.
In contrast, Array.from invokes the iterator of the array-like object. If the iterator doesn't exist, but the object has a length property, it will iterate from 0 up to the value of the length - 1 and create an array from those values. Here, since a Uint8Array has an iterator, that iterator is what gets invoked when you use Array.from.
Iterators don't have the same sort of size limit that argument lists have (unless they're spread into an argument list, or something similar). The iterator's .next method is called, returning a value to put into the array, until the iterator is exhausted. It's pretty similar to the following:
const arr = [0, 1, 2];
// Create a new array by iterating through arr's iterator
// just like Array.from is doing:
const newArr = [];
const iterator = arr[Symbol.iterator]();
let iterObj = iterator.next();
while (!iterObj.done) {
  newArr.push(iterObj.value);
  iterObj = iterator.next(); 
}
console.log(newArr);
 
 
There are no limits on how long the iterator may be, either in the above code, or in Array.from.
So, if the iterable or array-like object you have is very large, constructing an array from it with Array.apply (or by spreading into the Array constructor) may throw an error, whereas using Array.from will not.