标签 javascript 下的文章

刚刚看 I/O 直播时听到 Kotlin gets officially supported,真的蛮意外的。

Kotlin 的 killer-app 大约就是用来写 Android app 的 Anko。从我初次用 Anko 到现在,已经过去快一年了。遗憾的是在之前的公司里,由于各种各样的原因,一直没法在线上产品中使用 Kotlin。

直到我上个月换了工作后——刚刚结束的四月里,使用 Anko,在不到一个月里就写完了两个漂亮的 Android App 并上线,生产力和舒适度实在前所未有。

当然,由于我很长时间里的主要在写 JavaScript,于是确实习惯了使用 Promise。我于是就简短地用 Kotlin 写了一个 Promise 实现—— https://github.com/kmxz/Votive/。API 尽可能地接近 JavaScript 中的 Promise;只是所有 Promise 的名称都改成了 Votive。

譬如,将 Android 的运行时权限申请封装为 Promise,只需要这么一点 boilerplating:

const val REQUEST_CODE_MIN = 6910
const val REQUEST_CODE_MAX = 7910

open class BaseActivity: AppCompatActivity() {

    private var lastRequestCode = REQUEST_CODE_MIN

    private var permissionCallback = mutableMapOf<Int, Pair<(Unit) -> Unit, ((Unit) -> Unit)>>()

    fun withPermission(permission: String) = Votive<Unit, Unit> { resolve, reject ->
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            if (ContextCompat.checkSelfPermission(this, permission) != PackageManager.PERMISSION_GRANTED) {
                val requestCode = lastRequestCode++
                if (lastRequestCode > REQUEST_CODE_MAX) { lastRequestCode = REQUEST_CODE_MIN }
                permissionCallbacks.put(requestCode, Pair(resolve, reject))
                ActivityCompat.requestPermissions(this, arrayOf(permission), requestCode)
            } else {
                resolve(Unit)
            }
        } else {
            resolve(Unit)
        }
    }

    final override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
        if (requestCode in REQUEST_CODE_MIN..REQUEST_CODE_MAX) {
            val callback = permissionCallbacks.remove(requestCode)
            if (grantResults.all { it == PackageManager.PERMISSION_GRANTED }) {
                callback?.first?.invoke(Unit)
            } else { // already removed anyway
                callback?.second?.invoke(Unit)
            }
        } else {
            super.onRequestPermissionsResult(requestCode, permissions, grantResults)
        }
    }

}

就可以使用 Promise 风格去申请运行时权限:

withPermission(Manifest.permission.ACCESS_FINE_LOCATION)
    .thenSimple { Log.i(locationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER)) }
    .catchSimple { alert("Don't give me permission? I'll crash") }

嗯,以及我现在越来越觉得我一个月前辞职实在是绝赞的决定。

此文同时发布于 https://zhuanlan.zhihu.com/p/26966006

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?

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.

:(