新手问题 Turbolinks 5 下有没有可能让页面单独只执行自己的 JS/CoffeeScript

samport · 2016年09月01日 · 最后由 hww 回复于 2016年09月04日 · 4181 次阅读

碰到一个页面 js 的小问题,发现自己对于 Turbolinks 了解太少了。

例如首页上有个图片切换的 slide 幻灯片效果,看代码是使用 js 定时器,第一次打开首页时一切正常。

点击其它页面以后,再返回到首页,幻灯片出现了闪动。页面调试发现浏览器这时候已经有两个 js 定时器在同时运转。初步分析结果是打开其它页面时,首页上的 js 代码又被引用,又产生了一个新的定时器任务。

虽然可以通过追加一些 js 代码,将已经存在的定时器先清除,然后再执行新的 js 定时器,但是这个显然不是个好办法。使用了 turbolinks 以后所有的 js/coffeescript 一次性被读入以后,执行时候也是被全部执行的吗?如果不同页面上有相同的 js 变量,那不是会乱套吗?

使用 turbolinks 时候,有没有让页面只执行该页面单独的 js 脚本的好办法?

先不说单独执行页面脚本的好办法,就当这个方法你已经知道并且用上了吧。Turbolinks 实现的是页面无刷新的视图更新,当你从一个“页面”进入到下一个“页面”的时候,浏览器并没有重刷新。这就意味着在上个页面创建的对象(比如说定时器回调)依然存在于内存之中,你必须要先清除旧的定时器然后再执行新的,所以并非“不是个好办法”,而是必须得用这个办法。

你举的这个例子其实和“该页面单独执行脚本”这个诉求根本就没有关系,是一个 A/B 问题。

#1 楼 @nightire 感谢指点!已经修改了幻灯片 js 脚本,表面上看起来没啥问题了。

反复被触发的原因之一估计是因为我在 coffeescript 脚本里使用了$( document ).on 'turbolinks:load', ->,导致每次点击其它页面,首页的幻灯片脚本定时器都会被执行一遍。

另外,我感觉 Turbolinks 在加载时对于提高效率特别有帮助,但是这么多 js 程序同时活动在一个浏览器页面容器里,如果出现不同页面里面有相同的 html 元素 class 命名,或者是 js 命名,应该会出现一些麻烦吧。

例如, app/assets/javascripts/demo1.coffee里面的代码:

$ ->
  $(".sample").click ()->
   alert("test")

app/assets/javascripts/demo2.coffee里面的代码:

$ ->
  $(".sample").click ()->
   alert("this is a debug message")

当这两个脚本都被 Turbolinks 加载以后,应该会出现冲突。我的困惑其实是类似这种问题。

我在网上看到有人提出的解决方案是在 html 的 body 标签上添加 class,然后在 js 脚本中使用这些自定义的 class 标签作为过滤器。

ORGANIZING JAVASCRIPT IN RAILS APPLICATION WITH TURBOLINKS PAGE SPECIFIC JAVASCRIPT IN RAILS 别人整理好的 js 脚本:/jquery-readyselector

#2 楼 @samport Turbolinks5 概述及实现原理, 你这个问题是一个常规问题,你看看这里的文章就可以处理掉了。

#3 楼 @lyfi2003 你的文章很有价值。我多少开始有点明白在使用 turbolinks+jquery 时碰到的那些莫名奇妙的问题是怎么来的。

借鉴了各种方案,我的做法如下:

1). 在 layout 文件里给 body 动态加 class 标签。

<body id="home2" class="<%= controller_name %> <%= action_name %>">

2). 在 app/assets/javascripts/xxxx.coffee 文件里面对 html 元素的 class 进行过滤,防止在不相关的页面上重复执行 js 脚本

$( document ).on 'turbolinks:load', ->
  #如果当前页面检测不到指定的class标签,则停止执行后面的脚本
  return unless $(".static_pages.home").length > 0
 #.......

在学习过程中碰到的第一个坑是 turbolinks 有效的时候 jquery 的 ready 事件不会被触发。后来才发现 rails 5+ turbolinks 5 下需要用'turbolinks:load'来代替 jquery 的“ready”事件。

还曾经碰到的一个大坑是在集成 adminlte 模板的时候,解决的方式也是为 turbolinks 追加额外的事件处理,否则左侧的菜单收放会不正常。

谢谢各位大拿的帮助!

根据场景不同可以有不同写法。

如果只是一段很短的代码,并且只有一个页面用到,那么可以直接写在 body 的 script 里。 https://github.com/turbolinks/turbolinks#working-with-script-elements

<script>
$('.sample').on('click', function() {
  alert('this is a debug message');
});
</script>

很多情况下,你可以将事件绑定到 document 或 window,避免绑定 Turbolinks event。https://github.com/turbolinks/turbolinks#running-javascript-when-a-page-loads

$(document).on 'click', '.sample', ->
  alert('this is a debug message')

如果多个页面用到相似逻辑,那么可以抽取通用逻辑。

# <div data-alert-message="this is a debug message"></div>
$(document).on 'click', '[data-alert-message]', ->
   alert($(this).data('alert-message'))

更进一步,你可以用 MutationObserverCustom Elements 将可重用的前端逻辑组件化,让它自动在插入或被移除的时候执行初始化和清除逻辑。 https://github.com/turbolinks/turbolinks#responding-to-page-updates

<!-- https://github.com/basecamp/trix -->
<form >
  <input id="x" type="hidden" name="content">
  <trix-editor input="x"></trix-editor>
</form>

Turbolinks 不只是让你更改绑定的事件,而是把你的 Web 应用变成持久运行的进程,让你重新思考 JavaScript 的组织方式。

#5 楼 @Rei 谢谢指点,继续学习。。

目前我的做法还属于偷懒的做法,主要是不想花太多时间去研究和改动别人已经写好的 jquery 代码。对出现命名冲突的地方不得已而代码开头部分增加一个 body 标签的范围过滤器,来避免 js 脚本同时运行而造成的页面异常。

如果前端的 js 也全部是自己写的话,在编写 js 时就充分考虑到利用好 turbolinks 的「单页面」的特性应该才是正确的做法吧。

在 body 上加上 action_namecontroller_name 作为 class。
然后以是否包含对应 class 作为判断条件。例如:

initTyped: function(){
  if($('.home.index').length > 0){
    $('.website-description').typed({
      strings: ['xxx'],
      loop: true,
      showCursor: true,
      startDelay: 500,
      backSpeed: 10,
      backDelay: 1500,
      typeSpeed: 80,
      contentType: 'html'
     });
  }
},
initPage: {
  home: function(){
    if($('.home.index').length > 0){
      App.Home.messageForm();
    }
  }
},

@lyfi2003 的文章加上 ORGANIZING JAVASCRIPT IN RAILS APPLICATION WITH TURBOLINKS 应该就能上手了。

#8 楼 @hww 我也是用的这种方法,相对来说比较简单。

#7 楼 @aspirewit 看了一下这个js-namespace-rails,这个动态生成 js.erb 的方法也可以,不过我想这种方式生成的 js 应该已经不再属于 turbolinks 的加载对象了。

试试如下事件?

$(document).on('turbolinks:before-cache', function() {
  ...
})

@dongli1985 turbolinks:before-cache能解决大部分第三方库生成的 UI widgets 重复渲染的问题。 👍

需要 登录 后方可回复, 如果你还没有账号请 注册新账号