V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
V2EX 提问指南
taojing10
V2EX  ›  问与答

9 个 Javascript 你可能不知道的知识点 (9 JavaScript Tips You May Not Know)

  •  
  •   taojing10 · 2017-07-12 02:15:25 +08:00 · 1460 次点击
    这是一个创建于 2721 天前的主题,其中的信息可能已经有所发展或是发生改变。

    用 markdown 转换了下,貌似还是有点问题,大家凑合凑合(字数限制原文加不上去了),如果想体验舒适的阅读,可以点击原文: https://blog.jing.do/4212

    前言:本篇文章是我在查询时偶然间发现的,虽然年代久远但是依旧非常适合入门学习,特此翻译下分享给大家,顺便给大家加了一些备注方便阅读(特意加粗刷存在感)。原文全文和链接在最后

    ———————-以下是正文———————-

    备注:本篇文章是 34 岁的程序员 Ayman Hourieh 在 2006 年发布的。我找到了他网站的链接,但是网站已经不存在了。我用 Wayback Machine 找到了文章的镜像。为了让更多人看到把他摘录分享出来。我只做了一些链接的修改。

    JavaScript 是一个功能强大的面向对象语言。表面上看他他和 JAVA 和 C 非常相似,但是他却截然不同,其核心在于 JavaScript 更像一个功能性语言。本篇文章是一些 JavaScript 的小提示,一部分提供一些类 C 语言的功能模拟,另一部分是想要提高性能并探讨下脚本语言中一些比较难懂的东西。索引如下:

    • 多用途的 Array
    • 队列
    • 二叉树
    • String Concatenation 对比 Array.join
    • 给对象绑定方法
    • 使用自定义排序
    • Assertion
    • 静态局部变量
    • null, undefined, and delete
    • 深层嵌套
    • 使用 Firebug
    • $ 和 $
    • console.log(format, obj1, …)
    • console.trace()
    • inspect(obj)

    多用途的 Array

    尽管 JavaScript 在数据结构方面看起来很奇特,他的 Array 比其他编程语言(如 C ++或 Java )用途更加广泛。 它通常用作数组或关联数组,后面将演示如何将其用作堆栈,队列和二叉树。 复用 Array 而不是编写其他的数据结构有两个好处:首先,不用浪费时间去重写已经存在的功能,其次,内置浏览器对 JavaScript 的运行将更高效。

    大家都知道栈是后进先出:最后插入的会被最先移除。array 有两个方法来实现栈的功能。他们是push() 和 pop()。 push()用于插入 item 到 array 的尾部,而pop() 则用于移除并返回最后一个 item。以下是代码的实例:

    var stack = [];
    stack.push(2);       // 当前栈是 [2]
    stack.push(5);       // 当前栈是 [2, 5]
    var i = stack.pop(); // 当前栈是 [2]
    alert(i);            // 显示 5
    

    队列

    队列是先进先出的:现行插入的将会被最先移除。array 可以用 push() 和 shift() 来实现队列。 push() 用于插入 item 到尾部, shift()用于移除第一个 item。案例如下:

    var queue = [];
    queue.push(2);         // 现在的队列 [2]
    queue.push(5);         // 现在的队列 [2, 5]
    var i = queue.shift(); // 现在的队列 [5]
    alert(i);              // 显示 2
    

    值得注意的是,array 还有一个unshift()的函数。这个函数用于将 item 放到 array 的头部。所以栈也可以使用 unshift()/shift(),而队列可以用unshift()/pop()

    如果这些函数名称让你迷茫了(译者注:因为unshift()是处理头部,所以相对应的栈需要从头部出去,队列需要换到尾部),你可以给他们取个别名,比如,创建队列的方法名为add 和 remove

    var queue = [];
    queue.add = queue.push;
    queue.remove = queue.shift;
    
    queue.add(1);
    var i = queue.remove();
    alert(i);
    

    二叉树

    二叉树是在树的节点表示数据。每个节点有一个数据并且有两个子节点(左叉树和右叉树)。在 C 语言里,这种数据结构通常使用指针来实现。这种实现方法也可以在 JavaScript 中使用对象和引用来实现。然而,对于一些小的二叉树,有一种更简单便捷的方法,array 的第一个 item 作为树的头。 如果可以使用以下公式计算节点 i,则索引左右节点:

    leftChild(i) = 2i + 1
    rightChild(i) = 2i + 2
    

    这张图揭示了这个算法: (来自于 Wikipedia):![9 个 Javascript 的知识点 (9 JavaScript Tips You May Not Know)]( https://blog.jing.do/wp-content/uploads/2017/07/9 个 javascript 的知识点-9-javascript-tips-you-may-not-know.png "9 个 Javascript 的知识点 (9 JavaScript Tips You May Not Know)")

    正如你所看到的,这种方法并不是 JavaScript 的独有之处,但是在处理小的二叉树时非常有用。 例如,您可以编写自己的函数来获取和设置节点的值或子节点,并遍历二叉树,这些方法与做计算或 for 循环一样简单。但是,这种方法的缺点是随着树的深度增加,浪费的存储空间越来越多。

    String Concatenation 对比 Array.join

    大家都知道,如果做太多的字符串链接会让性能下降(译者注:不知道的去补课),并且这个非常容易避免。如果你想要用各个字符来组成一个字符串,最差的方法是使用+把所有的字符结合到一起:

    str = '';
    for (/* each piece */) {
      str += piece; // bad for performance!
    }
    return str;
    

    这种方法将使用太多的字符串链接,会让性能枯竭。

    在 JavaScript 中有个更好的办法,就是 Array.join(),他可以让所有 array 内的元素连接成一个字符串:

    var tmp = [];
    for (/* each piece */) {
      tmp.push(piece);
    }
    str = tmp.join(' '); // 用空格作为分隔符
    return str;
    

    该方法不会受到额外的字符串对象的影响,通常执行的非常高效

    给对象绑定方法

    任何使用 JavaScript 事件的人都可能遇到了一种情况,他们需要将对象的方法分配给事件处理程序。 这里的问题是事件处理程序会被 HTML 调用,即使它们最初被绑定到另一个对象。 为了解决这个问题,我用一个函数将对象和方法绑定; 它他会运行对象和方法,并返回一个在该对象调用该方法的函数。 我在 Prototype 中找到了一个窍门,并且写了以下函数来在不包含 Prototype 的项目中使用它:

    function bind(obj, method) {
      return function() { return method.apply(obj, arguments); }
    }
    

    如何使用:

    var obj = {
      msg: 'Name is',
      buildMessage: function (name) {
        return this.msg + ' ' + name;
      }
    }
    
    alert(obj.buildMessage('John')); // displays: Name is John
    
    f = obj.buildMessage;
    alert(f('Smith')); // displays: undefined Smith
    
    g = bind(obj, obj.buildMessage);
    alert(g('Smith')); // displays: Name is Smith
    

    使用自定义排序

    排序是一个常见的工作。JavaScript 提供了一种排序数组的方法。 但是,该方法默认按字母顺序排列 —— 非字符串元素在排序之前被强制转换为字符串,这个使得数字排序会有意想不到的结果:

    var list = [5, 10, 2, 1];
    list.sort()
    // list is now: [1, 10, 2, 5]
    

    这个解释很容易: 数字被强制转换成了字符串,所以 10 编程了’ 10 ’而 2 变成了’ 2 ’,那么 JavaScript 对比两个字符串的时候,先对比第一个字符。如果 str1 的第一个字符出现在字符集中的第一个字符之前,则 str1 被认为是“小于” str2。 在我们的情况下,’ 1 ’在’ 2 ’之前,所以’ 10 ’小于’ 2 ’。

    幸运的是,JavaScript 提供一个重写机制,让我们可以自定义如何排序,我们用 a 和 b 两个元素最为例子:

    • 如果 a<b,返回小于 0
    • 如果 a=b,返回 0
    • 如果 a>b,返回大于 0

    写这样的程序比较容易:

    function cmp(a, b) {
      return a - b;
    }
    

    我们现在可以使用这个方法来做排序:

    var list = [5, 10, 2, 1];
    list.sort(cmp);
    // list is now: [1, 2, 5, 10]
    

    Array.sort() 牛逼的地方是允许更复杂的排序。 假设你有一个论坛帖子,每个帖子看起来像:

    var post = {
      id: 1,
      author: '...',
      title: '...',
      body: '...'
    }
    

    如果你想用 id 排序,只需要创建以下函数:

    function postCmp(a, b) {
      return a.id - b.id;
    }
    

    可以说,使用浏览器的方法进行排序将比在 JavaScript 中实现排序函数更有效。 但是,数据应该在服务器端进行排序。所以,除非必要,否则不应该让数据在客户端排序。

    Assertion

    Assertion 是一种常用的调试技术,它用于确保表达式在执行期间计算为真。 如果表达式计算为假,则表示代码中可能出现的错误。JavaScript 缺少一个内置的 Assertion 功能,但幸运的是,它很容易编写一个。 如果传递的表达式的计算结果为假,以下实现会抛出 AssertException 类型的异常:

    function AssertException(message) { this.message = message; }
    AssertException.prototype.toString = function () {
      return 'AssertException: ' + this.message;
    }
    
    function assert(exp, message) {
      if (!exp) {
        throw new AssertException(message);
      }
    }
    

    自己抛出异常并不是非常有用,但是当与有用的错误消息或调试工具结合使用时,您可以检测到有问题的部分。

    您还可以使用以下代码段检查异常是否为 Assertion 异常:

    try {
      // ...
    }
    catch (e) {
      if (e instanceof AssertException) {
        // ...
      }
    }
    

    该函数的使用类似于 C 或 Java:

    assert(obj != null, 'Object is null');
    

    如果 obj 碰巧为 null,Firefox 将在 JavaScript 控制台中打印以下消息:

    uncaught exception: AssertException: Object is null
    

    Static Local Variables

    大家知道,一些语言像 C ++,他们有静态变量的概念,用于函数调用。JavaScript 并不支持此技术。 然而,“功能也是对象”让这个功能成为可能。 方法是:将静态变量变为函数的属性。 假设我们要创建一个计数器函数:

    function count() {
      if (typeof count.i == 'undefined') {
        count.i = 0;
      }
      return count.i++;
    }
    

    当第一次调用 count 时,count.i 是未定义的,所以 if 条件为 true,count.i 为 0。因为我们将变量存储为一个函数属性,它将在函数调用之间保留它的值, 因此它可以被认为是一个静态变量。

    这里有个性能提升小技巧:

    function count() {
      return count.i++;
    }
    count.i = 0;
    

    虽然第一个例子将 Count 的所有逻辑封装在主体中,但第二个例子更为有效。

    null, undefined, and delete

    因为 JavaScript 有undefined 和 null 所以他不同于其他语言。null是一个特别的数值,他表示没有数值。null会被认为一个特别的对象,因为typeof null会返回 null。

    undefined表示变量没有被定义,或者定义了但没有给数值。以下情况都会显示undefined

    // i is not declared anywhere in code
    alert(typeof i);
    
    var i;
    alert(typeof i);
    

    虽然undefined 和 null是两个不同的类型,但是如果使用 == ,会被判定为相等,但是如果是 === 则不等。

    JavaScript 还有一个删除操作符,” undefines ”一个属性,可以将其应用于对象属性和数组成员,使用 var 声明的变量不能被删除,而是隐式声明( implicitly declared )的变量可以:

    var obj = {
      val: 'Some string'
    }
    
    alert(obj.val); // displays 'Some string'
    
    delete obj.val;
    alert(obj.val); // displays 'undefined'
    

    深层嵌套

    如果您需要在深层嵌套对象上执行多个操作,最好将其引用到临时变量中,而不是每次对其进行解引用。 例如,假设您想在文本字段上执行一系列操作:

    document.forms[0].elements[0]
    

    建议您存储到变量中,并使用此变量而不是以上构造:

    var field = document.forms[0].elements[0];
    // Use field in loop
    

    每个点都导致一个操作来检索一个属性,在一个循环中,这些操作是相加的,所以最好做一次,将对象存储在变量中并重新使用它。

    Using Firebug

    Firefox 有一个非常棒的扩展,用于调试名为 Firebug 的 JavaScript 代码。 它提供一个具有断点和堆栈视图的调试器,以及一个 JavaScript 控制台。 它还可以监视 Ajax 请求。 此外,扩展提供了一组 JavaScript 函数和对象来简化调试。 您可以在 Firebug 的文档页面中详细研究它们。 这里有一些我觉得有用的:

    $ and $

    熟悉 Prototype 的人马上就认出他们了,

    $() 接受一个字符串参数,并返回其 ID 是传递的字符串的 DOM 元素。(译者注:Jquery

    $('nav') // returns the element whose id is #nav.
    

    $() 返回 DOM 的数组

    $('div li.menu') // returns an array of li elements that are 
                      // located inside a div and has the .menu class
    

    console.log(format, obj1, …)

    console 对象提供显示 log 消息的方法,这个将比 alert 更加好用, console.log()有点像 C 里面的printf,他会将输入转化为字符串在 console 中展示:

    var i = 1;
    console.log('i value is %d', i);
    // prints:
    // i value is 3
    

    console.trace()

    此方法打印一个堆栈跟踪调用它。 它不需要输入参数

    inspect(obj)

    该功能需要一个参数。 它切换到检查选项卡并检查传递的对象。

    4 条回复    2017-07-12 08:48:15 +08:00
    msg7086
        1
    msg7086  
       2017-07-12 05:32:49 +08:00
    前几条和 JavaScript 都没啥关系……
    binux
        2
    binux  
       2017-07-12 05:57:33 +08:00 via Android
    10 年前的内容还是别参考了。。
    有些已经是错误的了
    xcatliu
        3
    xcatliu  
       2017-07-12 08:41:16 +08:00 via iPhone
    Sorry I know all of those
    kyuuseiryuu
        4
    kyuuseiryuu  
       2017-07-12 08:48:15 +08:00 via iPhone
    最烦这种你字派标题。
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   1515 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 23ms · UTC 17:07 · PVG 01:07 · LAX 09:07 · JFK 12:07
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.