2013年5月16日 星期四

[JS] 『返樸歸真』讓jQuery慢慢淡出你的網站吧 (1) -- Basic part

其實兩三個月以前就想寫這篇文章了,直到最近看到越來越多的文章都在提jQuery的pros & cons,想想也是時候做個彙整,順便重新整理一下自己在前端設計的思維。下面這些連結都有很多不錯的建議及教學:
最後一個連結 - vanilla.js算是一個對前端framework重度使用者開的玩笑,「維護團隊」還煞有其事的做了一個客製化選單來幫你"build"需要的模組,但是下載下來的檔案中當然是一行程式碼都沒有。
官網中舉的一些case應該都能讓你反思「我們到底有沒有必要這麼依賴jquery?」

從事web開發以來,在前端開發時一直與jQuery脫不了關係,但隨著對javascript了解的增加,發現自己已經不知不覺陷入了jQuery的設計模式,很多普通的功能其實在原生的javascript中一點都不難做,效能也比較好,我們就一步步從簡單的部份開始,慢慢學習怎麼設計出jquery-free的程式。


$(document).ready()

通常需要載入一些比較肥大的js檔時(例如jquery)而且不考慮相依性時,很多人會將他塞進頁面的尾巴,最後載入。因為頁面的render是synchronise的,所以在最前面塞入一堆肥大的js檔會使得接下來的DOM物件的render被block住,而產生「很慢」的錯覺。
所以如果你是遵照這樣的best practice,在js程式碼中,只要將需要ready後執行的code放在最後即可,就用不到$(document).ready()了。


query dom物件

在使用jQuery的時候,我們已經很習慣使用$(".class #id tag")這樣的方法來query出我們要的dom物件,但這樣的query方式並不是jquery獨創的,而是從css那邊學來的。且在w3c也早有規劃去實作這個api(http://www.w3.org/TR/selectors-api/),那就是querySelectorAllquerySelector
querySelectorAll及querySelector的差別只在前者會query出所有符合條件的element,回傳nodeList;後者只會回傳第一個符合條件的element。兩著的使用時機如下:
  • document.querySelectorAll
  • document.querySelector
  • nodeList.querySelectorAll
  • nodeList.querySelector
  • element.querySelectorAll
  • element.querySelector
支援ie8以上及其他瀏覽器

dom物件的操作

dom物件本身就有相當豐富的method可供使用,了解他們之後你會發現很多情況下我們真的沒有必要用到jquery,這邊舉幾個例子:

Style:

$(elem).css("color","red"); 
vs
elem.style.color="red";

Class:

$(elem).addClass("active"); 
vs
elem.className+=" active"; 
or
elem.classList.add("active"); //ie10+


$(elem).removeClass("active"); 
vs
elem.className= elem.className.split("active").join(""); 
or
elem.classList.remove("active"); //ie10+

HTML、text

$(elem).text("content");
$(elem).html("<p>TEST</p>"); 
vs
elem.innerText="content";
elem.innerHTML="<p>TEST</p>";

Append:

$(elem).append(child);
vs
elem.appendChild(child);

Remove:

$(elem).remove();
vs
elem.remove();


Event-binding:

$(elem).click(function(){});
vs
elem.onclick = function(){};

It sucks, I know. 因為只能綁一個function,第二次綁定時就會把前一個給覆蓋掉了。

$(elem).bind("click", function(){});
vs
elem.addEventListener("click", function(){});


這種方式要bind幾次都ok,要注意的是在unbind的時候必須在第二個參數傳入相同的reference才行,例如:
var foo = function(){};
elem.addEventListener("click", foo);
elem.removeEventListener("click", foo);

2013年5月8日 星期三

[ROR] ActiveRecord效能調校

雖然在使用Rails的過程中感受到orm的方便,但是ActiveRecord用爽爽的同時,許多對sql不是很瞭解的開發者可能會犯下了許多問題導致無謂的浪費了sql資源。在系統剛做好,資料量不大的時候不太是問題,但在使用者及資料量多了以後,問題就會一一浮現了,我們先來看一下一些常見的問題:


Resource.find(id).child_resources


這樣的寫法很直覺,也很方便,但是會造成兩個query:
Resource Load (x ms)  SELECT "resource".* FROM "resource" where id = n
ChildResource Load (x ms)  SELECT "child_resource".* FROM "child_resource" WHERE "child_resource"."resource_id" = n

如果改成這樣的寫法只需要一次的query:
ChildResources.where("resource_id=#{id}")

雖然只少了一次的query,但最多能減少50%的sql操作時間



Resource.all (沒有加select)


當你可能只是很單純的要Resource的id及name,而不需要肥肥的"content"欄位,使用select只拉出需要的欄位,可以幫以省下不少傳輸時間和記憶體。
雖然可以使用slim_scrooge這種gem,不過別這麼懶,自己養成好習慣吧!


利用joins將需要的資料一次拿完

剛開始開發rails的時候,常常會拿出model後就直接往view丟,相關的relation到時候再拿就好,例如這樣:
<%= @category.products.counts %>
但是這樣常常會產生無謂的sql query,少量還好,當你在前台對一堆@catgories跑each的時候會發生很可怕的情形....也就是所謂的 "N+1 Query"
所以盡量養成一個習慣,就是要用到的資料一次拉進來就好,最嚴謹的檢查方式就是將產出的資料用attributes轉成hash,所有要拿的資料都在裡面了,沒有query進來的relation絕對拿不到。不過這樣的作法可能有點矯枉過正就是XD
要一次拿完所需資料最簡單就是用join+select操作,例如:
@products=Category.joins(:products=>:photo).select("categories.name cat_name, products.name pro_name, photos.url")
<% @products.each do |product| %>
  <%= product.cat_name %>
  <%= product.pro_name %>
  <%= image_tag product.url %>
<% end %>
而不是
@products=Product.all
<% @products.each do |product| %>
  <%= product.category.name %>
  <%= product.name %>
  <%= image_tag product.photo.url %>
<% end %>


適時使用counter_cache

相信這種情境一定很常見:
進入網站後,螢幕顯示出你有幾個朋友、幾個相簿、幾篇部落格
這時,在controller裡面....

Bad code:

  @friend_count = current_user.friends.count
  @album_count = current_user.albums.count
  @blog_count = current_user.blogs.count


這時我們該使用counter cache把他cache起來:
在users增加friends_count、albums_count、blogs_count等欄位
在friend.rb、album.rb、blog.rb中的
belongs_to :user
改成
belongs_to :user, :counter_cache => true
如果之前已經有資料了,記得把user.xxx_count給update到目前的count數,但為了保護counter的正確性,counter是read-only的,我們只能使用Base.reset_counter來設定他。
而counter的reset程式碼最好是寫在migration.rb裡,讓各種環境下migrate時順便reset counter:
class AddCounterCacheToUsers < ActiveRecord::Migration
  def self.up
    add_column :users, :comments_count, :integer, :default => 0
    User.find_each do |user|
      User.reset_counter user.id, :friends, :albums, :blogs
    end
  end

  def self.down
    remove_column :users, :comments_count
  end
end

Good code:

@counts = User.find(current_user.id).select(:friends_count, :albums_count, :blogs_count).attributes



善用Transaction


利用transaction


find.each

2013年5月2日 星期四

[RB] class, instance, class instance variable

Instance variable

在程式碼中我們常能看到單一個@開頭的變數,但是可別一味把它當成是instance variable喔,如果是在instance method中被創造出來的那肯定是instance變數,但要小心這種情況:
class Med

  @@c='This is class variable'
  @c='This is class instance variable'

  def self.class_instance_c
    @c
  end
  def class_c
    @@c
  end
  def instance_c
    @c
  end
end

m = Med.new
puts m.class_c #->'This is class variable'
puts m.instance_c #->''
puts Med.class_instance_c #->'This is class instance variable'
上面的@c其實是class instance variable,是不能被物件實例存取的。
而當物件實例想要存取的@c是instance variable,並沒有被定義出來,所以才會是nil,印不出東西。


Class instance & Class instance variable

相較Class instance variable,Class variable比較好辨認,"@@"開頭的就是了。
這兩種變數的差別在於,Class variable是共用於繼承鏈上的,當Class variable被改動時,所有繼承鏈上的其他類別的此變數也會被改動。
可以想像成是直接改動javascript中的prototype
但是Class instance variable是各個類別獨立的,「可以被繼承,但是繼承給子類別後就沒有任何關係了」



cattr_accessor

就像是attr_accessor一樣,只是他提供的是對Class variable的讀寫,而不是instance variable



參考資料

[ihower] 深入Rails3: ActiveSupport 的 class_attribute
[ihower] 一些 Ruby Dynamic Features 記事 (1) OOP