2016年5月3日 星期二

JS 一定要放在Body 的最底部麼?聊聊瀏覽器的渲染機制


我們需要統一一下什麼叫我們經常掛在嘴邊的“頁面渲染出來了” ——指的是是“首屏顯示出來了”還是“頁面完整地加載好了”(後面統稱StepC) ? 
如果指的是首屏顯示出來了,那麼問題又來了:假設網頁首屏有圖片,這裡的“首屏”指的是“顯示了全部圖片的首屏”(後面統稱StepB)還是“沒有圖片的首屏”(後面統稱StepA)。

確定清楚“頁面渲染出來了” 指的是StepA、StepB、StepC 中的哪一個是非常關鍵的(雖然至今還沒有一個應聘者嘗試這麼做過),如果“頁面渲染出來了” 指的是StepC,那麼我的最後一問的答案是肯定的——script標籤不放在body底部不會拖慢頁面完整地加載好的時間。

瀏覽器的渲染過程

  1. Create/Update DOM And request css/image/js:瀏覽器請求到HTML代碼後,在生成DOM的最開始階段(應該是Bytes → characters後),並行發起css、圖片、js的請求,無論他們是否在HEAD裡。注意:發起js文件的下載request並不需要DOM處理到那個script節點,比如:簡單的正則匹配就能做到這一點,雖然實際上並不一定是通過正則:)。這是很多人在理解渲染機制的時候存在的誤區。
  2. Create/Update Render CSSOM:CSS文件下載完成,開始構建CSSOM
  3. Create/Update Render Tree:所有CSS文件下載完成,CSSOM構建結束後,和DOM一起生成Render Tree。
  4. Layout:有了Render Tree,瀏覽器已經能知道網頁中有哪些節點、各個節點的CSS定義以及他們的從屬關係。下一步操作稱之為Layout,顧名思義就是計算出每個節點在屏幕中的位置。
  5. Painting:Layout後,瀏覽器已經知道了哪些節點要顯示(which nodes are visible)、每個節點的CSS屬性是什麼(their computed styles)、每個節點在屏幕中的位置是哪裡(geometry)。就進入了最後一步:Painting,按照算出來的規則,通過顯卡,把內容畫到屏幕上。
以上五個步驟前3個步驟之所有使用“Create/Update” 是因為DOM、CSSOM、Render Tree都可能在第一次Painting後又被更新多次,比如JS修改了DOM或者CSS屬性。

Layout和Painting也會被重複執行,除了DOM、CSSOM更新的原因外,圖片下載完成後也需要調用Layout和Painting來更新網頁。


  • 首屏時間和DomContentLoad事件沒有必然的先後關係
  • 所有CSS儘早加載是減少首屏時間的最關鍵
  • js的下載和執行會阻塞Dom樹的構建(嚴謹地說是中斷了Dom樹的更新),所以script標籤放在首屏範圍內的HTML代碼段裡會截斷首屏的內容。
  • script標籤放在body底部,做與不做async或者defer處理,都不會影響首屏時間,但影響DomContentLoad和load的時間,進而影響依賴他們的代碼的執行的開始時間。

回到前面的問題:
script標籤的位置會影響首屏時間麼?
答案是:不影響(如果這里里的首屏指的是頁面從白板變成網頁畫面——也就是第一次Painting),但有可能截斷首屏的內容,使其只顯示上面一部分。

為什麼說是“有可能”呢?,如果該js下載地比css還快,或者script標籤不在第一屏的html裡,實際上是不影響的。明白這一影響邊界非常重要,這樣我們在考察頁面性能瓶頸的時候就有的放矢了。舉個例子:在網頁的第二屏有一個通用模塊,實際上我們是可以把它的js邏輯獨立成一個文件,將模塊的html和js標籤放在一起做成獨立的模板引進來的(如果它的js比較小或者說因為多了一個文件會多佔用一個TCP連接和帶寬,這實際上是另外​​一個話題了,請參考我文章開頭的聲明)。


我們來總結一下:
  • 如果script標籤的位置不在首屏範圍內,不影響首屏時間
  • 所有的script標籤應該放在body底部是很有道理的
  • 但從性能最優的角度考慮,即使在body底部的script標籤也會拖慢首屏出來的速度,因為瀏覽器在最一開始就會請求它對應的js文件,而這,佔用了有限的TCP鏈接數、帶寬甚至運行它所需要的CPU。這也是為什麼script標籤會有async或defer屬性的原因之一。

2016年5月2日 星期一

Nested transactions



User.transaction do
  User.create(username: 'Kotori')
  User.transaction do
    User.create(username: 'Nemu')
    raise ActiveRecord::Rollback
  end
end
both Koori, Nemu created

As mentioned previously ActiveRecord::Rollback does not propagate outside of the containing transaction block and so the parent transaction does not receive the exception nested inside the child. Since the contents of the child transaction are lumped into the parent transaction both records are created! I find it easier to think of nested transactions like the child who dumps its contents into the parent container, leaving the child transaction empty.

User.transaction do
  User.create(username: 'Kotori')
  User.transaction(requires_new: true) do
    User.create(username: 'Nemu')
    raise ActiveRecord::Rollback
  end
end

only “Kotori” is created 

2016年5月1日 星期日

load, autoload, require, require_relative




load, autoload, require, require_relative

load:
load 命令提供了一種最原始的方法,即每次都會重新加載整個文件,刷新內存中的類定義.

autoload:
load 命令每次都加載類有些浪費,很多類並不是一開始就需要,可以用autoload 來先創建一個鉤子,等到真的訪問到的時候再加載:
autoload :Calendar, './calendar.rb'


但這種方式有個問題: 相同常量如果多次定義autoload 鉤子,只有最後一個會被觸發. 設想在實際開發中,類定義可能分佈在多個文件中,所以這種方式並不常用.

require:
和autoload 一樣, require 想解決的也是性能問題: require 只在第一次被調用的時候被觸發,之後針對相同文件的require 就不會真正執行了

require_relative:
require_relative 相當於是默認將當前路徑加入了$LOAD_PATH,不用給相對路徑或絕對路徑, 其他和require 是一致的

2016年4月30日 星期六

Thread-Safety


Rack::Lock => make sure thread safe
def threadsafe!
  @preload_frameworks = true
  @cache_classes      = true
  @dependency_loading = false
  @allow_concurrency  = true
  self
end
sometimes on production rails will remove Rack::Lock => because Web server will handle it => unicorn, Fusion Passenger => single thread

puma => multi thread 

Rails Middleware Walkthrough


ActionDispatch::Static => provide static file on public
Rack::Lock => lock the app down to a single thread
ActiveSupport::Cache::Strategy::LocalCache::Middleware => cache method based on ActiveSupport::Cache::FileStore
easy to use by Rails.cache.read or Rails.cache.write
Rack::Runtime => sets an X-Runtime response header to show spending time
Rack::MethodOverride => if set params[:_method], could reset http method, ex put, delete on form
ActionDispatch::RequestId => set unique request id
Rails::Rack::Logger => log when request start, and request end
ActionDispatch::ShowExceptions & ActionDispatch::DebugExceptions => rescue error, and custom format
ActionDispatch::RemoteIp => detect ip attack
ActionDispatch::Reloader => reload classes in development mode
ActionDispatch::Callbacks =>  We can call before or after methods on this and pass in a block which will then be triggered on each request.
ActiveRecord::ConnectionAdapters::ConnectionManagement => clear db connections
ActiveRecord::QueryCache => active record query cache
ActionDispatch::Cookies, Session::CookieStore and Flash =>  set cookie, sessions
ActionDispatch::ParamsParser => prepare params
ActionDispatch::Head => transfer head request to get
Rack::ConditionalGet and Rack::ETag => set ETag header based on the response body, conditional => if not changed, not process => only send 304

ActionDispatch::BestStandardsSupport => add recommend browser to client 

LOG TAGGING IN RAILS

My::Application.config.log_tags = [ :uuid ]

Now you can filter the log content by a particular request ID to see all output related to a single request 

Clickjacking



Kidnap your click, to create unexpected behavior:
ex: use invisible iframe to controller your click


user X-FRAME-OPTIONS to prevent it