你能“过度组织” JavaScript 吗?

Avatar of Chris Coyier
Chris Coyier

DigitalOcean 为您旅程的每个阶段提供云产品。 立即开始使用 价值 200 美元的免费积分!

毫无疑问,在拥有大量 JavaScript 的网站上,您需要一个严格的组织模式来确保一切都有意义并且代码易于理解。 我过去曾提到我喜欢将事物分组到单独的文件中,每个文件包含一个功能特定的对象文字。 进一步,我们可以严格遵守这种模式,并确保我们将所有部分放在一个地方,所有“init”函数放在一个地方,所有事件绑定放在一个地方,并将其余部分作为一些命名良好的迷你函数,执行非常特定的操作。

不过我想知道,这是否过于组织了?

我毫不怀疑,在一个大型的 JavaScript 密集型网站上,这种严格的组织方式会带来巨大的益处。 但是对于较小的网站来说,可能就不那么明显了。

最近,我需要修复 CSS-Tricks 上的一些 JavaScript 代码。 由于我一直在使用对象文字模式编写大量 JavaScript,因此我认为将其转换为我的常规格式。 CSS-Tricks 不是一个 JavaScript 密集型网站。 大部分 JS 代码都在一个 global.js 文件中。 我保存了 JavaScript 的“之前”和“之后”状态,这样我们就可以查看并讨论一下。

“之前”

依赖项在顶部。 它们被压缩/合并到最终的 global.js 文件中。 脚本的其余部分被包装在一个 IIFE 中,以便变量不会成为全局变量。 您可以像阅读从上到下的内容一样阅读它,因为它只是一部分接一部分的功能。 所有处理该功能的代码都集中在这些代码块中,仅由空格和简短的注释隔开。

// @codekit-prepend "jquery.fitvids.js"
// @codekit-prepend "placeholder.js"
// @codekit-append "prism.js"

// Protect global namespace
(function($) {


  // Make videos fluid width
  $("article, .photo-grid, .single-video-wrapper, .gallery-grid .grid-5-6").fitVids({
    customSelector: "video"
  });



  // IE 9 placeholders
  $('input, textarea').placeholder();



  // Search stuff
  var openSearch = $(".open-search, #x-search, #refine-search");
  var body = $("body");

  function toggleSearch() {
    if (body.hasClass("show-nav")) {
      $("body").toggleClass("show-nav");
    } else {
      $(".search").toggleClass("open");
      $(".open-search").toggle();
      $(".close-search").toggle();
    }
  }
  openSearch.on("click", function(e) {
    e.preventDefault();
    toggleSearch();
    setTimeout(function(){
      $(".search-field").focus();
    }, 100);
  });
  var searchParts = $(".search-parts > a:not(.x-search)");
  var searchForm = $("#search-form");
  searchParts.on("click", function() {
    var el = $(this);
    var newActionURL = el.data("url");
    searchForm.attr("action", newActionURL);
    searchParts.removeClass("active");
    el.addClass("active");
  });



  // Small screen navigation stuff
  $("#show-hide-navigation").on("click", function(e) {
    e.preventDefault();
    $("body").toggleClass("show-nav");
  });



  // Code highlighting stuff
  $("pre.lang-html, pre[rel=HTML]").find("code").addClass("language-markup");
  $("code.html, code.lang-html").removeClass().addClass("language-markup").parent().attr("rel", "HTML");

  $("code.javascript").removeClass().addClass("language-javascript").attr("rel", "JavaScript");
  $("pre[rel=JavaScript], pre.lang-js, pre[rel=jQuery], pre.JavaScript").attr("rel", "JavaScript").find("code").removeClass().addClass("language-javascript");

  $("pre[rel='CSS'], pre[rel='LESS'], pre[rel='Sass'], pre[rel='SASS'], pre[rel='SCSS']").find("code").removeClass().addClass("language-css");
  $("code.css, code.lang-css").removeClass().addClass("language-css").parent().attr("rel", "CSS");

  $("pre[rel=PHP]").attr("rel", "PHP").find("code").removeClass().addClass("language-javascript");
  $("code.php").removeClass().addClass("language-javascript").parent().attr("rel", "PHP");



  // Comments Stuff
  $(".comment.buried").on("click", function() {
    $(this).removeClass("buried");
  });
  $("#view-comments-button").on("click", function(e) {
    e.preventDefault();
    $("#comments").show();
    $(this).hide();
  });



  // Illustrator links
  var timer;
  var illustratorLink = $(".illustrator-link").hide();
  $(".deals-header, .almanac-title, .videos-title, .snippets-title, .demos-title, .gallery-header, .forums-title")
    .mouseenter(function() {
      timer = setTimeout(function() {
        illustratorLink.slideDown(200);
      }, 3000);
    })
    .mouseleave(function() {
      clearTimeout(timer);
    });
    

})(jQuery);

“之后”

依赖项仍然在顶部,只是我将整个代码突出显示块移动到其开放的单独文件中,因为它不需要绑定并且没有创建任何变量。

所有选择器都在顶部,分组在一起。 所有 init 函数都在一起。 所有事件绑定都在一起。 所有“操作”都是命名良好的迷你函数。

// @codekit-prepend "jquery.fitvids.js"
// @codekit-prepend "placeholder.js"
// @codekit-prepend "highlighting-fixes.js"
// @codekit-append "prism.js"

var csstricks = {

  el: {

    body: $("body"),

    allInputs: $('input, textarea'),

    searchForm: $("#search-form"),
    searchOpeners: $(".open-search, #x-search, #refine-search"),
    searchSections: $(".search-parts > a:not(.x-search)"),
    searchField: $(".search-field"),
    search: $(".search"),
    openSearch: $(".open-search"),
    closeSearch: $(".close-search"),

    videoWrappers: $("article, .photo-grid, .single-video-wrapper, .gallery-grid .grid-5-6"),

    navToggle: $("#show-hide-navigation"),

    illustratorLink: $(".illustrator-link"),
    headerAreas: $(".deals-header, .almanac-title, .videos-title, .snippets-title, .demos-title, .gallery-header, .forums-title"),

    buriedComments: $(".comment.buried"),
    viewCommentsButton: $("#view-comments-button"),
    commentsArea: $("#comments")

  },

  timer: 0,

  init: function() {
    csstricks.bindUIActions();

    csstricks.makeVideosFluidWidth();
    csstricks.polyfillPlaceholders();

    csstricks.el.illustratorLink.hide();
  },

  bindUIActions: function() {
    csstricks.el.searchOpeners.on("click", csstricks.handleSearchClick);
    csstricks.el.searchSections.on("click", csstricks.handleSearchPartsClick);

    csstricks.el.navToggle.on("click", csstricks.mobileNavToggle);

    csstricks.el.headerAreas.on("mouseenter", csstricks.openIllustratorLinkArea);
    csstricks.el.headerAreas.on("mouseleave", csstricks.closeIllustratorLinkArea);

    csstricks.el.buriedComments.on("click", csstricks.revealComment);
    csstricks.el.viewCommentsButton.on("click", csstricks.revealCommentsArea);
  },

  makeVideosFluidWidth: function() {
    csstricks.el.videoWrappers.fitVids({
      customSelector: "video"
    });
  },

  polyfillPlaceholders: function() {
    csstricks.el.allInputs.placeholder();
  },

  handleSearchClick: function(event) {
    event.preventDefault();
    csstricks.toggleSearch();
    setTimeout(function() {
      csstricks.focusSearchField();
    }, 100);
  },

  mobileNavToggle: function(event) {
    event.preventDefault();
    csstricks.el.body.toggleClass("show-nav");
  },

  focusSearchField: function() {
    csstricks.el.searchField.focus();
  },

  handleSearchPartsClick: function(event) {
    var el = $(event.target);
    var newActionURL = el.data("url");
    csstricks.el.searchForm.attr("action", newActionURL);
    csstricks.el.searchSections.removeClass("active");
    el.addClass("active");
  },

  toggleSearch: function() {
    if (csstricks.el.body.hasClass("show-nav")) {
      csstricks.el.body.toggleClass("show-nav");
    } else {
      csstricks.el.search.toggleClass("open");
      csstricks.el.openSearch.toggle();
      csstricks.el.closeSearch.toggle();
    }
  },

  openIllustratorLinkArea: function() {
    csstricks.timer = setTimeout(function() {
      csstricks.el.illustratorLink.slideDown(200);
    }, 3000);
  },

  closeIllustratorLinkArea: function() {
    clearTimeout(csstricks.timer);
  },

  revealComment: function(event) {
    $(event.target).removeClass("buried");
  },

  revealCommentsArea: function(event) {
    event.preventDefault();
    csstricks.el.commentsArea.show();
    csstricks.el.viewCommentsButton.hide();
  }

};

csstricks.init();

它们如何比较?

关于“之前”,我喜欢从上到下阅读的方式,所有相关的代码都分组在一起。 由于每个组平均只有大约 10 行代码,因此它非常易读。 如果事情变得更加复杂,我会担心该文件变得难以管理。

关于“之后”,看到所有选择器分组在一起有点奇怪。 您不知道为什么要选择这些元素,但您可以看到页面上所有与该文件相关的元素,并且可以独立于其功能管理它们。 如果您完全不熟悉这个文件,并且试图熟悉它,阅读initbindUIActions函数是否让您明白了这一点? 阅读这些函数的速度是否比扫描整个“之前”文件更快? 元素和函数都以“csstricks.”开头是否会造成混乱?

尽管将一大块代码移到了单独的文件中,“之后”的代码行数也增加了 25 行。

我没有所有答案。 我仍然不确定我更喜欢哪一种,或者混合方法是好是坏。