分类 Technical 下的文章

Back in high school, I read a brilliant book, DOM Scripting (first edition) by Jeremy Keith. The Chinese translation of the book had a even better title: "JavaScript DOM 编程艺术", which literally means "the art of JavaScript DOM programming". The book was the first book I bought about JavaScript. It was so enlightening that it showed me how to write JavaScript in a "good" way (graceful degradation, in particular), and it eventually turned me into a frontend developer a decade later. If you ask to me to recommend 3 books for learning JavaScript, I'll definitely have DOM Scripting in the list.

Screenshot_2016-06-06_02-06-10.png

Last year, I saw a JavaScript function appeared in a question on SegmentFault, where the post author asked for some explanation on the code he posted. I wrote an answer:

The code you posted is a piece of shit. You can learn nothing from it. Actually, I've never seen any JavaScript code worse than that.

The post author replied: "I read the code from Keith's DOM Scripting". I was astonished. Recalling that DOM Scripting is such a masterpiece, I cannot believe it. After realizing the code was really extracted from the book, I felt embarrassed and woke up to the fact that JavaScript had really changed a lot over the years.

Screenshot_2016-06-06_02-03-54.png

Therefore, I made it a interview question for frontend developer candidates: read the very code section, and tell me how will you improve it. Only include basic code issues (including code style issues). Don't change the logic. Don't use fancy ES5/6 things. Don't say "requestAnimationFrame API" can help the performance. Just focus on the code itself. Here's the original code, again:

function moveElement(elementID,final_x,final_y,interval) {
    if (!document.getElementById) return false;
    if (!document.getElementById(elementID)) return false;
    var elem = document.getElementById(elementID);
    if (elem.movement) {
        clearTimeout(elem.movement);
    }
    if (!elem.style.left) {
        elem.style.left = "0px";
    }
    if (!elem.style.top) {
        elem.style.top = "0px";
    }
    var xpos = parseInt(elem.style.left);
    var ypos = parseInt(elem.style.top);
    var dist = 0;
    if (xpos == final_x && ypos == final_y) {
        return true;
    }
    if (xpos < final_x) {
        var dist = Math.ceil((final_x - xpos)/10);
        xpos = xpos + dist;
    }
    if (xpos > final_x) {
        var dist = Math.ceil((xpos - final_x)/10);
        xpos = xpos - dist;
    }
    if (ypos < final_y) {
        var dist = Math.ceil((final_y - ypos)/10);
        ypos = ypos + dist;
    }
    if (ypos > final_y) {
        var dist = Math.ceil((ypos - final_y)/10);
        ypos = ypos - dist;
    }
    elem.style.left = xpos + "px";
    elem.style.top = ypos + "px";
    var repeat = "moveElement('"+elementID+"',"+final_x+","+final_y+","+interval+")";
    elem.movement = setTimeout(repeat,interval);
}

Here are some possible answers I'll give, listed in the order they appeared:

  • Use var moveElement = function (...) syntax instead of function moveElement(...) to make the program more consistent (okay, it's just my personal preference).
  • Leave spaces after commas in argument list (i.e. moveElement(elementID, final_x, final_y, interval) instead of moveElement(elementID,final_x,final_y,interval)). Similarly, leave spaces around operators like /.
  • Either use camel case (better) or underscore case. Don't mix them up (elementID and final_x).
  • Always put curly brackets around the body of if, even when there is only one statement inside (return false in this example).
  • Always have consistent return type (don't return false at the beginning, but returning nothing at the end).
  • Don't declare variables using var inside if blocks. JavaScript's var keyword only gives function-level locality.
  • Don't use var to declare the same variable more than one time (there are var dist for multiple times in the code).
  • The is no point in assigning zero to dist at the beginning.
  • It's a very bad practice to pass a string as the argument to setTimeout. Pass a function instead.
  • Using == to compare is discouraged. Use === instead.
  • Some code quality tools (JSLint or JSHint) will ask you to supply the second argument (10) to parseInt.
  • Don't call to getElementById each time moveElement is called. Call getElementById just once, and use the element itself in recurring calls.
  • Similarly, don't call parseInt again and again. Put numeric values in variables instead.

Here's the revised code I'll have:

var moveElement = function (elementID, finalX, finalY, interval) {
    if (!document.getElementById) { return; }
    if (!document.getElementById(elementID)) { return; }
    var elem = document.getElementById(elementID);
    if (elem.movement) {
        clearTimeout(elem.movement);
    }
    var xpos = 0;
    var ypos = 0;
    var run = function () {
        var dist;
        if (xpos === finalX && ypos === finalY) {
            return;
        }
        if (xpos < finalX) {
            dist = Math.ceil((finalX - xpos) / 10);
            xpos = xpos + dist;
        }
        if (xpos > finalX) {
            dist = Math.ceil((xpos - finalX) / 10);
            xpos = xpos - dist;
        }
        if (ypos < finalY) {
            dist = Math.ceil((finalY - ypos) / 10);
            ypos = ypos + dist;
        }
        if (ypos > finalY) {
            dist = Math.ceil((ypos - finalY) / 10);
            ypos = ypos - dist;
        }
        elem.style.left = xpos + "px";
        elem.style.top = ypos + "px";
        elem.movement = setTimeout(run, interval);
    };
    run();
};

What will the frontend developer candidates answer? Maybe they're used to shims, polyfills, transpilers. Will they know the hard years we had with IE 6?

Recently, I made a decision to disallow programmers to use android:onClick XML attributes to add click event listeners. Existing usages will be removed too. Actually, the attribute itself was a very bad design, considering that web frontend developers has switched from onclick HTML attribute to addEventListener API for a long time.

Here are the real reasons why android:onClick XML attribute should be avoided:

  • If an ancestor element has theme applied, android:onClick no longer works, because the program will look for the click handler from ContextThemeWrapper instead of Activity (see http://stackoverflow.com/questions/29525644/). When applying a theme, programmers may not realize the problem.
  • Android look for the specified method from the parent Activity. When programmers change an Activity into a Fragment, they may be ignoring this problem.
  • To make the two problems above more severe, onClick uses reflection. It therefore prevents the compiler from telling you problems. You app will crash at run time.
  • As it uses reflection, performance will be compromised.
  • Android provides android:onClick attribute, but no android:onChange or any other XML attributes for event listeners. Considering click listeners aren't really different from other event listeners, keeping them consistent will be good.
  • When using android:onClick, one must create a public method with parameter (View view). Android Lint will then complain "unused parameter".
  • Adding a public method is not elegant in any way.

Why

The reason to mount NFS over SSH is simple for my case: my NAS is in my home, but I need to access it from my laptop when I'm outside my home. I chose NFS as I stay at home most of the time, in which case my laptop is in the same LAN as the NAS. There is obviously no need to set up another set of mechanism.

To secure the NAS, it is better that it stays in the LAN. It would be simpler to set only one port mapping rule from the configuration interface of the router, that is the port for SSH (I still need SSH anyway), from LAN port 22 to WAN port 20022.

In this way, I don't need to set up the authentication and encryption for NFS separately.

Installing NFS on the both machines is easy. Mounting can be easily done when connecting directly. However, when trying to mount over SSH, I found the steps found by Google does not work.

How

In my case, NFSv3 is used.

In addition to nfsd, mountd also need to be accessed from the client. By default, mountd uses a random port. However, to access mountd easily, I fixed the port used by mountd by adding those lines to /etc/services of the server:

mount 32759/udp
mount 32759/tcp

After restarting the server to make the changes into effect, running ssh on the client machine to forward the ports:

ssh home.kmxz.net -fNv -p 20022 -L 3049:localhost:2049  
ssh home.kmxz.net -fNv -p 20022 -L 33759:localhost:32759

The next step is to mount the NFS on the client machine:

sudo mount -v -t nfs -o soft,intr,nolock,port=3049,mountport=33759,tcp localhost:/mnt/aufs /mnt/nas

nolock option is necessary, otherwise the client machine will try to lock files on the client itself. tcp is also necessary, as mountproto will be UDP by default, but SSH only support TCP forwarding.

Different languages have different behaviors for a loop over a list that modifies the list itself:

JavaScript:


var l = [16]
l.forEach(function(x) {
    var p = x / 2
    if (l.indexOf(p) < 0) {
        l.push(p)
    }
})
l

[16, 8] is returned (results are the same using for-in loop). Newly-added item (8) is not iterated over. PHP acts in the similar way.

Python:

l = [16]
for x in l:
    p = x/2
    if p not in l:
        l.append(p)
l

[32, 16, 8, 4, 2, 1, 0] is returned. Newly-added items are iterated over. Ruby acts in the similar way.

Scala:

val l = MutableList(16)
for (x <- l) {
    val p = x/2
    if (!(l contains p)) { 
        l += p 
    }
}
l

A list of (32, 16, 8) is returned (as of Scala 2.10.4) (results are the same using .foreach). I cannot think of any reason that Scala does this.

:(

Scala does not provide anything like EnumSet in Java standard library. However, since Scala 2.10, Enumeration.ValueSet is providing toBitMask method. Therefore it is possible to store structures like MySQL's SET type in some database systems supporting only integers.

With Slick's lifted embedding, we can have it converting the structure to/from the integer type used in database automatically, utilizing MappedColumnType:

// Enumeration definition
object Weekday extends Enumeration {
    val Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday = Value
    val mapping = MappedColumnType.base[ValueSet, Long](_.toBitMask(0), long => ValueSet.fromBitMask(Array(long))) // not necessarily to be defined here
}

// Table definition
class Timeslot(tag: Tag) extends Table[Weekday.ValueSet, Int](tag, "timeslot") {
    def weekday= column[Weekday.ValueSet]("weekday")(Weekday.mapping)
    def hour = column[Int]("hour")
    def * = (weekday, hour)
}

In this way, for example, the set of "Tuesday, Thursday and Saturday" will be represented by 42 in the database.

By the way, to convert a Enumeration.ValueSet it to a List of String, one can use:

(input: Weekday.ValueSet) => input.toList.map(_.toString)

To convert a List of String back to a Enumeration.ValueSet:

(input: List[String]) => Weekday.ValueSet(input.map(Weekday.withName):_*)