2013/09/26

用CSS border畫出三角形

最近才知道的小技巧,先看看一般的border如何呈現:


可以看出邊界的交界處會呈現斜線的狀態,利用這點,搭配將border-color設為透明(transparent)就可以畫出三角形的效果,不過要注意ie6以下不支援transparent屬性。


還可以利用border的寬度及元件大小來調整交界位置,組合出不一樣的多邊形。


剩下的就是發揮創意去應用了,像是對話框上方的小三角甚至可以用pseudo element來達成:

.cbox:before {
    position: absolute;
    top: -7px;
    left: 9px;
    display: inline-block;
    border: 7px solid transparent;
    border-bottom-color: #ccc;
    border-top-width: 0;
    content: '';
}
.cbox:after {
    position: absolute;
    top: -6px;
    left: 10px;
    display: inline-block;
    border: 6px solid transparent;
    border-bottom-color: white;
    border-top-width: 0;
    content: '';
}

2013/08/18

AngularJS雜記 (基於1.2的版本)

  • $timeout這個服務基本上是一個window.setTimetout的wrapper,它有一個很大的用處是其function執行時間會在Angular DOM rendering之後 (譬如在data變動之後,view跟著重新render...)
  • 避免angular在render時DOM的閃現問題,可以使用ngCloak先將DOM隱藏,不過此directive要配合一段css才有作用,在1.2版是
    [ng\:cloak], [ng-cloak], [data-ng-cloak], [x-ng-cloak], .ng-cloak, .x-ng-cloak {
      display: none !important;
    }
  • 關於Directive (這邊Directive Definition Object簡稱DDO):
    1. DDO裡的controller,有可能建立不只一個instance (同樣的directive出現多個的情況),所以這種controller比較適合用來與child /sibling directive溝通;而要跨同種directive間的溝通用injected service比較好 (angular內的service全都是singleton,不論是透過Module.service/ Module.factory或$provide.provider註冊的)。
  • 有繼承關係的Scope要注意存取parent Scope變數的問題,尤其是一些directive會建立Child Scope而使用者沒查覺的時候;請參考這篇文章。

跨瀏覽器相容性問題雜記

  • 目前webkit瀏覽器(Safari/ Chrome v.28)在連結被點擊時不會把CSS中的:focus樣式給套上,目前的解法是替連結加上tabindex的屬性。
     

2013/08/10

用jQuery寫出scrolling動畫效果

網路上的範例大多以body為容器,稍微修改了一下以適應在其他DOM內的scrolling效果:
/**
 * @param target a jQuery selector string, target DOM; or scrollTop value
 * @param container a jQuery selector string, represents DOM which contains target, default is 'html, body'
 */
function smoothScrollTo(target, container) {
    container = container ? container : 'html, body';

    $(container).animate({
        scrollTop: $.isNumeric(target) ? 
            target : 
            $(target).offset().top 
                + $(container).scrollTop() 
                - $(container).offset().top
    }, 300);
}

// Ex: smoothScrollTo('#anchor');

Anchor 1

This page is made to demonstrate what i learned from some popular javascript library/framework. For now, it used H5BP as basic template, Bootstrap for layout and CSS style, and jQuery. (Backbone.js is going to be added for MVC implemenation)

Anchor 2

This page is made to demonstrate what i learned from some popular javascript library/framework. For now, it used H5BP as basic template, Bootstrap for layout and CSS style, and jQuery. (Backbone.js is going to be added for MVC implemenation)

Anchor 3

This page is made to demonstrate what i learned from some popular javascript library/framework. For now, it used H5BP as basic template, Bootstrap for layout and CSS style, and jQuery. (Backbone.js is going to be added for MVC implemenation)




2013/07/19

float元素生成順序的差異

用下面的html,我預期得到左右各有一個button(float div),剩下空間是status bar(div)
結果卻會是:
<div>
    <div class="pull-left btn">Button</div>    
    <div class="alert alert-info">Status bar</div>
    <div class="pull-right btn">Button</div>    
</div>
Button
Status bar
Button


這是由於第二個div在生成的時候就先將其父元素剩下的空間填滿了(block元素的特性)
第三個float div則被擠到了下面

所以將生成順序調整一下,讓float div先生成,這樣就不會被擠下去了
<div>
    <div class="pull-left btn">Button</div>    
    <div class="pull-right btn">Button</div>
    <div class="alert alert-info">Status bar</div>
</div>
Button
Button
Status bar


不過div的背景依舊會填滿父元素的空間,只有content會跟著float元素排版
此時CSS加上overflow,設成hidden或auto就可以正確的調整背景大小到float元素占據後剩下的空間了
<div>
    <div class="pull-left btn">Button</div>    
    <div class="pull-right btn">Button</div>
    <div class="alert alert-info" style="overflow:hidden;">Status bar</div>
</div>
Button
Button
Status bar


延伸閱讀:
All about floats

文字顯示過長時以省略符號顯示並提示完整文字

此CSS只作用於單行文字的顯示上
.text-overflow-ellipsis {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

為了達到只在過長被省略才顯示提示,需要用javascript判斷一下 (這邊以jQuery為例)
$(function() {
    $('.text-overflow-ellipsis').each(function() {
        if (this.offsetWidth < this.scrollWidth && !$(this).attr('title')) {
            $(this).attr('title', $(this).text());
        }
    });
});

2013/07/17

取得瀏覽器顯示的Scrollbar寬度


有些情況下,需要取得Scrollbar寬度來動態計算元件長寬
參考了一下網路上的作法,用jQuery寫了一個function來做這件事
/**
 * 1. Generate nested divs
 * 2. Calculate the width of inner div before and after scrollbar becomes visible.
 * @returns calculated scrollbar width
 */
function getScrollbarWidth() {
    var $tempNode = 
        $('<div/>')
            .css({
                width: 100,
                position: 'absolute',
                top: -1000
            })
            .append($('<div/>', { height: 100 }))
            .appendTo('body');
 
    var withoutSroll = $tempNode.children(':first').innerWidth();
    var withScroll = $tempNode.css({ 'overflow-y': 'scroll' })
                        .children(':first').innerWidth();

    $tempNode.remove();
    return withoutSroll - withScroll;
}

Backbone.js 雜記

版本1.0.0
  • extend Backbone.View 時傳入的參數:events,格式為
    {'event selector': 'callback'}
    底層使用 jQuery.on(event, selector, callback) 來完成事件註冊,要注意的是如果 selector 有值,根據 jQuery api 這事件只會發生在根 DOM 下符合 selector 條件的子 DOM 上。
    When a selector is provided, the event handler is referred to as delegated. The handler is not called when the event occurs directly on the bound element, but only for descendants (inner elements) that match the selector.
  • Backbone的View與Model要達到資料與顯示同步,得自行從View用listenTo()對Model的資料變動時的事件作處理

2012/10/23

alert('8' > '10'); // true?

在javascript內字串比較是依據長度,由左而右依序比較,規則是:
  • 相等則往下一位字元比較,反之則得出結果
  • 有值的比沒值的大

A的編碼是0041
B的編碼是0042
alert(‘A’ > ‘AB’); // false,因為最高位(最左邊)字元相等,而AB比較長
alert(‘B’ > ‘AB’); // true,最高位字元B比A大

再來看 ‘8’,‘1’,‘0’
0的編碼為0030
1的編碼為0031
8的編碼為0038
alert(‘8’ > ‘10’); // true
由於最高位’8’比’1’來的大,所以結果為true

2012/04/16

ResouseBundle.getBundle(String)在static情境下的問題


如果在一個static block或是static變數中呼叫ResourseBundle.getBundle(String)
在某些特定的情況下可能會造成MissingResourceException

其原因出在官方文件中敘述此method等同於呼叫
getBundle(baseName, Locale.getDefault(), this.getClass().getClassLoader())

問題在於this.getClass().getClassLoader()的部分,this在static內是取不到參照的
而實際上底層的實作是透過下列這段code取得ClassLoader
/*
 * Automatic determination of the ClassLoader to be used to load
 * resources on behalf of the client.  N.B. The client is getLoader's
 * caller's caller.
 */
private static ClassLoader getLoader() {
    Class[] stack = getClassContext();
    /* Magic number 2 identifies our caller's caller */
    Class c = stack[2];
    ClassLoader cl = (c == null) ? null : c.getClassLoader();
    if (cl == null) {
        // When the caller's loader is the boot class loader, cl is null
        // here. In that case, ClassLoader.getSystemClassLoader() may
        // return the same class loader that the application is
        // using. We therefore use a wrapper ClassLoader to create a
        // separate scope for bundles loaded on behalf of the Java
        // runtime so that these bundles cannot be returned from the
        // cache to the application (5048280).
        cl = RBClassLoader.INSTANCE;
    }
    return cl;
}

這樣會導致你取得的ClassLoader跟預期的結果不同
進而取不到目標resource

若要在static中使用ResourceBundle.getBundle(String)最好的方式還是自行呼叫
ResourseBundle.getBundle(baseName, Locale.getDefault(), [TargetClass].class.getClassLoader())

2011/10/04

Java中的Singleton實作

在Java 1.5之前
以往我們可能用提供 private constructor 和 static final field 的方式,來確保只會得到唯一的 Class 實例
/* 
 * 獨一無二的貓王(Elvis),這世上就只有一個貓王 
 * Singleton with public final field 
 */
public class Elvis {
   public static final Elvis INSTANCE = new Elvis();
   private Elvis() { //忽略細節 }
   public void leaveTheBuilding() { //忽略細節 }
}
當需要取得貓王實例時,簡單的存取 Elvis.INSTANCE 即可。不過 private constructor 依然有一個問題,用 reflection 機制的 AccessibleObject.setAccessible 可以破解 private 的限制,如果要防範此一漏洞,還是得在 constructor 內做些手腳。


或是用 static factory 的方式,來提供唯一的 Class 實例
/* 
 * 獨一無二的貓王(Elvis),這世上就只有一個貓王 
 * Singleton with static factory
 */
public class Elvis {
   private static final Elvis INSTANCE = new Elvis();
   private Elvis() { //忽略細節 }
   public static Elvis getInstance() { return INSTANCE; }
   public void leaveTheBuilding() { //忽略細節 }
}
雖然有同上的問題(private constructor 部分),但是此作法相較起來更有彈性,如果你改變了想法,不想用 singleton 的方式實作此 Class,可以在不變動 API 的情況下修改 getInstance() 達成。


但是無論用上面哪種方式,當你需要一個 Serializable 的 Singleton 時,都會遇到一個同樣的問題:每次 deserialize 時都會建立一個新的實例。

解法就是將所有 field 宣告為 transient,並加上 readResolve 方法
// readResolve method to preserve singleton property
private Object readResolve() {
   // Return the one true Elvis and let the garbage collector
   // take care of the Elvis impersonator.
   return INSTANCE;
}
如果有定義此方法,則它會自動在 deserialize 時被呼叫,並回傳我們指定的物件。

Java 1.5之後的最好的實作方式
現在有了第三種簡明又實用的新解法 - 實作單一元素的 Enum。
// Enum singleton - the preferred approach
public enum Elvis {
   INSTANCE;
   public void leaveTheBuilding() { ... }
}
這種做法接近 public final field 而且
  • 程式碼更簡潔
  • 沒有 private constructor 漏洞
    (因為 Enum 型別像抽象類別一樣不能被實例化)
  • 本身還提供了 Serialization 機制
  • Enum 本身的特性確保只會有一個實體

所以單一元素的 Enum 是目前最好的 Singleton 實作方式。

2011/09/28

Eclipse Plugin開發遇到No Schema Found的問題

開發 Plugin 時遇到一個問題,在設定 plugin.xml 內的 Extensions 時,遇到 no schema found for the 'org.eclipse.ui.views' extension point 這樣的訊息,而且右鍵點該 Extension 只能 New → Generic,無法建立對應的元件...

google 了一下之後發現原來跟 Eclipse plugin 的 library 有關,上官網查了一下後,發現原來我用的 J2EE 版並沒有含 PDE/RCP 套件的 source code,所以也就沒有含 schema 和其他文件。

既然知道原因,那就比較好解了,Classic 版的 PDE/RCP 套件是有含 source code 的,於是重新抓相同版號(這裡用 3.6.2)的 Eclipse 下來,將對應的 source code jar 檔複製到 Eclipse 安裝目錄/plugins 下就好了,個人是整個把 plugins 目錄覆蓋掉,懶人作法 ._.

怕有衝突的話最好先做個備份,或是用 import library 的方式把 Classic 版的 plugin library 加到 project 也行。

2011/09/26

java中的Class.this


首先先複習一下基本:
在 Java 的 Class 內宣告的 Class,稱為 Nested Class(巢狀類別)。

Nested Class 又分為 static 和 non-static 兩種,其中加上 static 宣告的就叫做 Static Nested Class;而 non-static 的 Nested Class 又稱為 Inner Class(內部類別)。

Inner Class 還可以再細分為三種又是另外一個故事了,這邊就不提
那 Class.this 究竟是代表甚麼呢...
public class ClassThisTest {
	public static void main(String[] args) {
		OuterClass outerInst1 = 
				new OuterClass("Outer class no.1");
		OuterClass.InnerClass innerInst1 = 
				outerInst1.new InnerClass();
		
		System.out.println(innerInst1.toString()); 
											//Inner class
		System.out.println(innerInst1.outerToString()); 
											//Outer class no.1
												   
		OuterClass outerInst2 = 
				new OuterClass("Outer class no.2");
		OuterClass.InnerClass innerInst2 = 
				outerInst2.new InnerClass();
		
		System.out.println(innerInst2.outerToString()); 
											//Outer class no.2
	}	
}

class OuterClass {
	private String outerMsg;
	
	public OuterClass(String msg) {
		this.outerMsg = msg;
	}
	
	public String toString() {
		return outerMsg;
	}
	
	public class InnerClass {
		private final String innerMsg = "Inner class";
		
		public String toString() {
			return innerMsg;
		}
		
		public String outerToString() {
			//Class.this
			return OuterClass.this.toString();
		}
	}
}
簡單說就是 Inner Class 用來參照相依的 Outer Class 實體用的。

因為在 Inner Class 裡面用 this 關鍵字的話,將會指向 Inner Class 實體,所以若是需要參照 Outer Class 實體時,用 Class.this 的方式就可以了。


JadEclipse安裝步驟

這邊用的是 Eclipse Helios(3.6.x)

Plug-in 安裝
首先從上方選單列
Help → Install New Software...

開啟 Install 視窗後,按下 Add... 按鈕
Name 可隨意輸入,這邊用 JadClipse
Location 輸入 http://jadclipse.sf.net/update

然後 Install 視窗中間出現 JDT Decompiler Features 可勾選
只有 3.4 版的可選擇,與 3.6 似乎沒有相容性問題,選了之後就按 Next 開始安裝吧

Plug-in 設定
由於 plug-in 似乎只是連結 Jad 與 Eclipse 之間的橋梁
所以裝 plug-in 之前還得先裝 Jad (只是一個執行檔而已)
可以到這個網址去下載 JAD Java Decompiler Download Mirror
根據 OS 選擇對應版本後下載,解壓縮後放在隨意位置即可

一樣是上方選單列
Window → Preferences

如果安裝成功的話,在 Preferences 視窗依下面的順序應該可以打開 Jad 的設定頁面
Java → Decopilers → Jad

在 Path to decompiler 的欄位輸入 Jad.exe 的絕對路徑
然後重啟 Eclipse 即可

2011/08/29

OpenEJB for Tomcat

其實本來專案中的 EJB 是要部署在其他 container 上的,只是懶的重新建立 server,於是找了一下有沒有可以讓 Tomcat 可以支援 EJB 的東西,沒想到還真的有呢!於是簡單的紀錄一下測試的步驟。

環境設定
基本需求:
快速安裝步驟:
  1. 首先你要有一個能正常運行的 Tomcat server
  2. 把從 OpenEJB 抓下來的 openejb.war 放到 ${tomcat.home}/webapps/ 下
    (注意:這個檔案必須名為openejb.war,所以不要去改名稱...)
  3. 啟動 Tomcat,打開網址 http://localhost:8080/openejb/installer 並點下 Install 按鈕

EJB 程式碼部分
由於是舊系統,所以這邊用的是 EJB 2.x 的規格來寫,範例寫的是一顆 stateless 的 session bean,EJB 的介紹在這邊就不詳述了。

Remote介面
import java.rmi.RemoteException;
import javax.ejb.EJBObject;

public interface FruitShop extends EJBObject {
    public String getBanana() throws RemoteException;
}
Home介面
import java.rmi.RemoteException;
import javax.ejb.CreateException;
import javax.ejb.EJBHome;

public interface FruitShopHome extends EJBHome {
    public FruitShop create() throws CreateException, RemoteException;
}
Session Bean
import java.rmi.RemoteException;
import javax.ejb.*;

public class FruitShopBean implements SessionBean {
    public void ejbActivate() throws EJBException, RemoteException {}
    public void ejbPassivate() throws EJBException, RemoteException {}
    public void ejbRemove() throws EJBException, RemoteException {}
    public void setSessionContext(SessionContext context)
        throws EJBException, RemoteException {}
		
    public String getBanana() {
        return "You get a banana.";
    }
}
別忘了在 classpath 上加上 javaEE 的 jar 檔,以免編譯錯誤。如果照上面的步驟安裝的話,在 ${tomcat.home}/webapps/openejb/lib/ 下可以找到 openEJB 提供的 javaee-api-5.0-3.jar

然後建立一個 ejb-jar.xml 如下
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE ejb-jar PUBLIC "-//Sun Microsystems, Inc.//DTD Enterprise 
JavaBeans 2.0//EN" "http://java.sun.com/dtd/ejb-jar_2_0.dtd">

<ejb-jar>
    <enterprise-beans>
    <session>
		<ejb-name>FruitShop</ejb-name>
		<remote>FruitShop</remote>
        <home>FruitShopHome</home>                
        <ejb-class>FruitShopBean</ejb-class>
        <session-type>Stateless</session-type>
        <transaction-type>Bean</transaction-type>
        <security-identity>
            <description></description>
            <use-caller-identity></use-caller-identity>
        </security-identity>
    </session>
    </enterprise-beans>
</ejb-jar>

部署 EJB
編譯成 jar 檔後,放在 ${tomcat.home}/conf/openejb.xml 中設定的路徑下
<!--
#
#  The <Deployments> element can be used to configure file
#  paths where OpenEJB should look for ejb jars or ear files.
#
#  See http://openejb.apache.org/3.0/deployments.html
#
#  The below entry is simply a default and can be changed or deleted
-->
<!-- 可以用相對路徑的方式, 相對於openejb.base設定的路徑 -->
<Deployments dir="ejb/" /> 
<!-- 或是指定一個目錄, 讓openEJB自動搜尋相關jar檔 -->
<Deployments dir="D:/Tomcat/ejb/" /> 
<!-- 最後還可以直接指定目標jar檔 -->
<Deployments jar="D:/Tomcat/ejb/FruitShop.jar" /> 

測試 client 端呼叫 EJB
根據官方的說明文件,用 Remote Client with HTTP (in tomcat) 的方式來呼叫 EJB
import java.util.Properties;
import javax.naming.InitialContext;
import javax.rmi.PortableRemoteObject;

public class EJBTest {
	public static void main(String args[]) throws Exception {
		Properties p = new Properties();
		p.put("java.naming.factory.initial",
		    "org.apache.openejb.client.RemoteInitialContextFactory");
		p.put("java.naming.provider.url",
		    "http://localhost:8080/openejb/ejb");
		InitialContext ctx = new InitialContext(p);
		Object ref = ctx.lookup("FruitShopRemoteHome");
		FruitShopHome home = (FruitShopHome)
		    PortableRemoteObject.narrow(ref, FruitShopHome.class);
		FruitShop remote = home.create();
		System.out.println(remote.getBanana()); //You get a banana.
	}
}

沒意外的話應該可以從 console 看到輸出。
另外... 關於 eclipse helios 怎樣將 EJB project 輸出 client 用的 jar 檔還有待研究。

2011/08/25

javascript中的null與undefined的差異

有些時候會作 object detection 來判斷物件是否存在(像是在 function 內判斷傳進來的參數存在與否),而判斷的標準用 null 或 undefined 其實會有些微的不同。
ECMA-262標準(*註1)中關於這部份的敘述:
  • undefined value

    primitive value used when a variable has not been assigned a value.
  • null value

    primitive value that represents the intentional absence of any object value.
簡單說,null 表示無值,而 undefined 表示沒有定義的變數。
下面就分別對其做了一些測試:

null
雖然 type 是 objet,null 但不算一個真正的物件,不是空字串'',也不是 false 值,但如果直接當成條件式來判斷,會得到 false
alert(null); //null
alert(typeof null); //object
alert(null == ''); //false
alert(null == false); //false
alert(null instanceof Object); //false
alert(null ? true: false); //false
理所當然的也不能對 null 的屬性作操作
var nullObj = null;
nullObj.property = 'value'; //丟出TypeError
alert(nullObj.someProperty) //丟出TypeError

undefined
undefined 的 type 就是 undefined,當成條件式使用一樣會得到 false
alert(typeof undefined); //undefined
alert(undefined == ''); //false
alert(undefined == false); //false
alert(undefined instanceof Object); //false
alert(undefined ? true: false); //false
沒宣告過的變數,雖然 type 是 undefined,但存取它則會丟出 ReferenceError
alert(typeof undefinedObj); //undefined
alert(undefinedObj); //丟出ReferenceError
alert(undefinedObj.property); //丟出ReferenceError
alert(undefinedObj === undefined); //丟出ReferenceError
宣告卻沒給值的變數,預設會給它 undefined 的值
var undefinedObj;
alert(typeof undefinedObj); //undefined
alert(undefinedObj); //undefined
alert(undefinedObj.property); //丟出ReferenceError
alert(undefinedObj === undefined); //true
定義過的物件,屬性未定義(宣告)前都屬於"宣告卻沒給值的變數",也就是帶 undefined 值
var definedObj = new Object();
alert(typeof definedObj.property) //undefined
alert(definedObj.property); //undefined
alert(definedObj.property === undefined); //true
從 function 內接收的參數如果有宣告且呼叫時未傳入值,等同宣告卻沒給值的變數
function methodCall(parameter) {
 alert(typeof parameter); //undefined
 alert(parameter); //undefined
}
methodCall();
微妙的是,用 == 運算子做比較,null 跟 undefined 會得到 true
alert(null == undefined) //true
alert(null === undefined); //false

結論
null 跟 undefined 就像 true/false 一樣表示一個值;而用於 object detection 的時候,遇到一個不知道宣告與否的變數,用 typeof 來判斷比較好。因為除非你自己設 null 值,不然在程式中遇到的機率很低。(目前我只知道 document.getElementById() 如果找不到元素,會回傳 null)
//如果用其他運算子來判斷,遇到未宣告過的變數會直接丟error
if(typeof obj == 'undefined') {
 //do something here...
}

//真的擔心會遇到null值,可以再加上後面的判斷式
if(typeof obj == 'undefined' && obj) {
 //do something here...
}
對於已宣告的物件,則可以直接當成條件式來使用
var definedObj = new Object();
//javascript中&&和||只要運算到可以得出結果就會忽略剩下的運算式
//所以如果otherObj無定義,則直接跳出此if判斷
if(definedObj.otherObj && definedObj.otherObj.someFunction) {
 definedObj.otherObj.someFunction();
}

//函式呼叫
function methodCall(parameter) {
 if(parameter) {
  //do something with parameter...
 }
}

註1:ECMA-262 是 javascript 的標準,由 ECMA 這個組織定的

2011/08/19

CSS測試 - 文章小圖示及段落標題

H1 TEXT

H2 TEXT

H3 TEXT

H4 TEXT

H5 TEXT
H6 TEXT

This is a quote block.

SPAN TEST
The background-image property sets the background image for an element.The background of an element is the total size of the element, including padding and border (but not the margin).By default, a background-image is placed at the top-left corner of an element, and repeated both vertically and horizontally.

DIV TEST
The background-image property sets the background image for an element.The background of an element is the total size of the element, including padding and border (but not the margin).By default, a background-image is placed at the top-left corner of an element, and repeated both vertically and horizontally.

javascript的物件導向程式設計

javascript 是一種 prototype-based 的語言,在 OOP 的寫法上相較於 java 更為麻煩,所以特別研究了一下幾篇文章,順便紀錄一下心得,免得以後忘記。


物件的建立
一種是透過 new 關鍵字
//這個牽涉到建構子,稍後會提到
var banana = new Fruit();
另一種則是使用 {} 宣告一個物件
//隱含的建立一個 javascript 內建的 Object 物件
var banana = {
 //物件屬性
 color: 'yellow',
 size: '15cm',
 //物件方法
 destroy: function() {
  alert('Noooooooooo!');
 }
};


建構子(Constructor)、物件屬性和物件方法的宣告
使用 function 宣告不只是可以建立函式,也可以建立一個建構子。而屬性和方法可以在建構子內宣告,這種方式相當於在每個物件建立時才把屬性和方法附加在物件上;不過這樣做有個缺點,如果父類別採用這種方式宣告,當存取父類別的屬性或方法時,會變成 undefined,因為屬性和方法是附加在每個建立好的物件上,而不是類別本身。
//建構子乍看之下就只是一個函式而已
function Fruit(name, color) {
 //私用屬性(private)
 //其實就是區域變數的概念而已
 var secret = 'You cant see this property.';
 //公用屬性(public)
 this.name = name;
 this.color = color; 
 
 //私用方法(private)
 function doNothing() {};
 //公用方法(public)
 this.changeColor = function(newColor) {
  this.color = newColor;
 }
}
之後便可以透過 new 來產生新的類別實體
//建立類別實體
var fruit1 = new Fruit('banana', 'yellow');
var fruit2 = new Fruit('apple', 'red');

alert(fruit1 instanceof Fruit); //true
alert(fruit1 === fruit2); //false
alert(fruit1.color); //yellow

fruit1.changeColor('green');
alert(fruit1.color); //green

alert(typeof fruit1.secret); //undefined
alert(typeof fruit1.doNothing); //undefined
alert(typeof fruit1.stillDoNothing); //undefined
但是如果直接對變數宣告屬性或方法,只會附加在目標變數指向的物件上,而不是類別本身。
//直接對變數宣告屬性
fruit1.looks = 'Like a banana.';
var fruit3 = new Fruit('grape', 'purple');

alert(fruit1.looks); //Like a banana.
alert(typeof fruit3.looks); //undefiend


javascript 的 prototype 與繼承(Inheritance)
在 javascript 中物件的繼承跟 prototype 這個屬性有很大的關係。

在取出物件的屬性時,會先去檢查該物件是否有直接定義目標屬性;若是沒有,則檢查該物件的 prototype 物件有沒有定義,如果還是沒有,會往 prototype chain (透過繼承將 prototype 鏈結起來)的上一層找,直到根部的 prototype 為止。特別要注意的是所有的物件都會隱含的參照到一個 prototype,但是只有 Function 物件(建構子)可以直接存取 prototype 屬性。

而物件的屬性和方法比較正規的宣告方式也是使用 prototype 來宣告,這樣做有一個好處,就是可以設定屬性初始值。
//改用繼承的方式來寫
//水果建構子
function Fruit(name) {
 this.name = name ? name : this.name;
}

//使用 prototype 作宣告並初始化
Fruit.prototype.name = 'Fruit';
Fruit.prototype.inFruit = 'inFruit';
Fruit.prototype.show = function() {
 alert(this.name);
};

//香蕉建構子,香蕉是一種水果我跟你說
function Banana() {
 //呼叫父類別的建構子
 this.super.call(this, 'Banana');
}

//繼承自 Fruit
//也是把 Banana 接上 Fruit 的 prototype chain
Banana.prototype = new Fruit();
//修正 Banana 的建構子指標,根據網路上的說法他會指向 Fruit 的建構子
//但測試後,有沒有這行其實沒差,因為上面已經宣告過 Banana 的建構子了
Banana.prototype.constructor = Banana;

//使用 prototype 作宣告並初始化
Banana.prototype.super = Fruit; //父類別參照(super)得自己宣告,
                                //因為 javascript 不提供直接參
                                //照到父類別的變數
Banana.prototype.color = 'yellow';
Banana.prototype.changeColor = function(newColor) {
 this.color = newColor;
};
//覆寫父類別的function
Banana.prototype.show = function() {
 alert(this.name + '\'s color is ' + this.color + '.');
};

var banana = new Banana();
alert(banana instanceof Banana); //true
alert(banana instanceof Fruit); //true
alert(banana.inFruit); //inFruit
banana.show(); //Banana's color is yellow.

banana.changeColor('green');
banana.show(); //Banana's color is green.
若要存取父類別的屬性或方法,還是要透過 prototype 來取得。
alert('Parent\'s name is ' + banana.super.prototype.name + '.');
                                       //Parent's name is Fruit.
banana.super.prototype.show(); //Fruit

2011/08/18

javascript的Function.apply()與Function.call()

有時候寫網頁時,DOM 元素的事件處理函式,裡面都是用 this 來存取觸發事件的元素;若想直接呼叫這些處理函式,則 this 會變成指向 window 物件。在沒有辦法指定 this 的情況下要如何直接呼叫這些函式呢?老師有沒有蕉你要多翻API...還愣著做什麼!

原生的 Function 物件提供了兩個函式 apply 與 call,可以在呼叫函式時改變函式內 this 指向的物件
Function.apply(obj, [argument1, argument2, ..., argumentN]);
Function.call(obj, argument1, argument2, ..., argumentN);

而這兩個函式的差異只在參數的傳遞方式而已,第一個參數皆為 this 的指標,而呼叫函式時的參數,apply 用陣列把參數包起來,call 則是依序列出參數
function alertColor(name) {
 //若直接呼叫此函式,this 會指向底層的 window 物件
 alert(name + '\'s color is ' + this.color + '!');
} 

function Fruit(color) {
 this.color = color; 
}

var banana = new Fruit('yellow');
var apple = new Fruit('red');
alertColor.apply(banana, ['Banana']);
alertColor.call(apple, 'Apple');

據說這在實作物件階層時特別有用(呼叫父類別的函式?),有機會研究在補上。

jQuery與其他library的相容問題

以前傻傻的沒碰過,到了真的遇到其他 javascript library 也用 $ 當變數的時候,要改起來的時候還真有些麻煩咧...好在官方也不是傻子,他們提供了一個方便又好用的函式 jQuery.noConflict,可以將 $ 的控制權釋放出去,於是就有了下列幾種解法:


jQuery 程式碼包成函式
jQuery.noConflict();
(function($) {
   //Write you jQuery code here, use $ instead of jQuery...
   $('div').html('testing');
})(jQuery);
將程式碼包在一個函式裡面,將 jQuery 作為一個參數傳遞進去,在接收時就可以任意命名了 (這裡是使用預設的 $ ),缺點是只有在這函式內可以使用 $,有 scope 上的限制


用其他別名取代 $
var j = jQuery.noConflict();
//Do something with jQuery...
j('input').hide();
//Do something with another library's $
$.something();
宣告一個變數接受 jQuery.noConflict() 回傳的值,就可以直接當成別名使用了


直接使用原來的變數名稱
jQuery.noConflict();
//Do something with jQuery...
jQuery('input').hide();
//Do something with another library's $
$.something();
跟上面那個沒什麼不同,變數名稱的差異而已


自己寫的網頁應該是不容易發生這種問題,倒是如果要改別人寫的東西,要加東加西時最好先看一下用了哪些 library,避免浪費無謂的時間。