Javascript 面向對象編程

Javascript 面向對象編程

作者: 陳皓 發布時間: 2012-01-10 13:11 閱讀: 17884 次 推薦: 16 原文鏈接 [收藏]

  Javascript是一個類C的語言,他的面向對象的東西相對于C++/Java比較奇怪,但是其的確相當的強大,在 Todd 同學的“對象的消息模型”一文中我們已經可以看到一些端倪了。這兩天有個前同事總在問我Javascript面向對象的東西,所以,索性寫篇文章讓他看去吧,這里這篇文章主要想從一個整體的角度來說明一下Javascript的面向對象的編程。(成文比較倉促,應該有不準確或是有誤的地方,請大家批評指正

  另,這篇文章主要基于 ECMAScript 5, 旨在介紹新技術。關于兼容性的東西,請看最后一節。

  初探

  我們知道Javascript中的變量定義基本如下:

var name = 'Chen Hao';
var email = 'haoel(@)hotmail.com';
var website = 'http://coolshell.cn';

  如果要用對象來寫的話,就是下面這個樣子:

var chenhao = {
   name : 'Chen Hao',
   email : 'haoel(@)hotmail.com',
   website : 'http://coolshell.cn'
};

  于是,我就可以這樣訪問:

//以成員的方式
chenhao.name;
chenhao.email;
chenhao.website;
//以hash map的方式
chenhao["name"];
chenhao["email"];
chenhao["website"];

  關于函數,我們知道Javascript的函數是這樣的:

var doSomething = function(){
alert('Hello World.');
}; 

  于是,我們可以這么干:

var sayHello = function(){
var hello = "Hello, I'm "+ this.name
+ ", my email is: " + this.email
+ ", my website is: " + this.website;
alert(hello);
};
//直接賦值,這里很像C/C++的函數指針
chenhao.Hello = sayHello;
chenhao.Hello();

  相信這些東西都比較簡單,大家都明白了。 可以看到Javascript對象函數是直接聲明,直接賦值,直接就用了。runtime的動態語言。

  還有一種比較規范的寫法是:

//我們可以看到, 其用function來做class。
var Person = function(name, email, website){
this.name = name;
this.email = email;
this.website = website;
this.sayHello = function(){
var hello = "Hello, I'm "+ this.name + ", \n" +
"my email is: " + this.email + ", \n" +
"my website is: " + this.website;
alert(hello);
};
};
var chenhao = new Person("Chen Hao", "haoel@hotmail.com",
"http://coolshell.cn");
chenhao.sayHello();

  順便說一下,要刪除對象的屬性,很簡單:

delete chenhao['email']

  上面的這些例子,我們可以看到這樣幾點:

  1. Javascript的數據和成員封裝很簡單。沒有類完全是對象操作。純動態!
  2. Javascript function中的this指針很關鍵,如果沒有的話,那就是局部變量或局部函數。
  3. Javascript對象成員函數可以在使用時臨時聲明,并把一個全局函數直接賦過去就好了。
  4. Javascript的成員函數可以在實例上進行修改,也就是說不同實例相同函數名的行為不一定一樣。

  屬性配置 – Object.defineProperty

  先看下面的代碼:

//創建對象
var chenhao = Object.create(null);
//設置一個屬性
Object.defineProperty( chenhao,
'name', { value: 'Chen Hao',
writable: true,
configurable: true,
enumerable: true });
//設置多個屬性
Object.defineProperties( chenhao,
{
'email' : { value: 'haoel@hotmail.com',
writable: true,
configurable: true,
enumerable: true },
'website': { value: 'http://coolshell.cn',
writable: true,
configurable: true,
enumerable: true }
}
);

  下面就說說這些屬性配置是什么意思。

  • writable:這個屬性的值是否可以改。
  • configurable:這個屬性的配置是否可以改。
  • enumerable:這個屬性是否能在for…in循環中遍歷出來或在Object.keys中列舉出來。
  • value:屬性值。
  • get()/set(_value):get和set訪問器。

  Get/Set 訪問器

  關于get/set訪問器,它的意思就是用get/set來取代value(其不能和value一起使用),示例如下:

var age = 0;
Object.defineProperty( chenhao,
'age', {
get: function() { return age+1; },
set: function(value) { age = value; }
enumerable : true,
configurable : true
}
);
chenhao.age = 100; //調用set
alert(chenhao.age); //調用get 輸出101(get中+1了);

  我們再看一個更為實用的例子——利用已有的屬性(age)通過get和set構造新的屬性(birth_year):

Object.defineProperty( chenhao,
'birth_year',
{
get: function() {
var d = new Date();
var y = d.getFullYear();
return ( y - this.age );
},
set: function(year) {
var d = new Date();
var y = d.getFullYear();
this.age = y - year;
}
}
);
alert(chenhao.birth_year);
chenhao.birth_year = 2000;
alert(chenhao.age);

  這樣做好像有點麻煩,你說,我為什么不寫成下面這個樣子:

var chenhao = {
name: "Chen Hao",
email: "haoel@hotmail.com",
website: "http://coolshell.cn",
age: 100,
get birth_year() {
var d = new Date();
var y = d.getFullYear();
return ( y - this.age );
},
set birth_year(year) {
var d = new Date();
var y = d.getFullYear();
this.age = y - year;
}
};
alert(chenhao.birth_year);
chenhao.birth_year = 2000;
alert(chenhao.age);

  是的,你的確可以這樣的,不過通過defineProperty()你可以干這些事:

  1)設置如 writable,configurable,enumerable 等這類的屬性配置。

  2)動態地為一個對象加屬性。比如:一些HTML的DOM對像。

  查看對象屬性配置

  如果查看并管理對象的這些配置,下面有個程序可以輸出對象的屬性和配置等東西:

//列出對象的屬性.
function listProperties(obj)
{
var newLine = "<br />";
var names = Object.getOwnPropertyNames(obj);
for (var i = 0; i < names.length; i++) {
var prop = names[i];
document.write(prop + newLine);
// 列出對象的屬性配置(descriptor)動用getOwnPropertyDescriptor函數。
var descriptor = Object.getOwnPropertyDescriptor(obj, prop);
for (var attr in descriptor) {
document.write("..." + attr + ': ' + descriptor[attr]);
document.write(newLine);
}
document.write(newLine);
}
}
listProperties(chenhao);

  call, apply, bind 和 this

  關于Javascript的this指針,和C++/Java很類似。 我們來看個示例:(這個示例很簡單了,我就不多說了)

function print(text){
document.write(this.value + ' - ' + text+ '<br>');
}
var a = {value: 10, print : print};
var b = {value: 20, print : print};
print('hello');// this => global, output "undefined - hello"
a.print('a');// this => a, output "10 - a"
b.print('b'); // this => b, output "20 - b"
a['print']('a'); // this => a, output "10 - a"

  我們再來看看call 和 apply,這兩個函數的差別就是參數的樣子不一樣,另一個就是性能不一樣,apply的性能要差很多。(關于性能,可到 JSPerf 上去跑跑看看)

print.call(a, 'a'); // this => a, output "10 - a"
print.call(b, 'b'); // this => b, output "20 - b"
print.apply(a, ['a']); // this => a, output "10 - a"
print.apply(b, ['b']); // this => b, output "20 - b"

  但是在bind后,this指針,可能會有不一樣,但是因為Javascript是動態的。如下面的示例

var p = print.bind(a);
p('a'); // this => a, output "10 - a"
p.call(b, 'b'); // this => a, output "10 - b"
p.apply(b, ['b']); // this => a, output "10 - b"

  繼承 和 重載

  通過上面的那些示例,我們可以通過Object.create()來實際繼承,請看下面的代碼,Student繼承于Object。

var Person = Object.create(null);
Object.defineProperties
(
Person,
{
'name' : { value: 'Chen Hao'},
'email' : { value : 'haoel@hotmail.com'},
'website': { value: 'http://coolshell.cn'}
}
);
Person.sayHello = function () {
var hello = "<p>Hello, I am "+ this.name + ", <br>" +
"my email is: " + this.email + ", <br>" +
"my website is: " + this.website;
document.write(hello + "<br>");
}
var Student = Object.create(Person);
Student.no = "1234567"; //學號
Student.dept = "Computer Science"; //系
//使用Person的屬性
document.write(Student.name + ' ' + Student.email + ' ' + Student.website +'<br>');
//使用Person的方法
Student.sayHello();
//重載SayHello方法
Student.sayHello = function (person) {
var hello = "<p>Hello, I am "+ this.name + ", <br>" +
"my email is: " + this.email + ", <br>" +
"my website is: " + this.website + ", <br>" +
"my student no is: " + this. no + ", <br>" +
"my departent is: " + this. dept;
document.write(hello + '<br>');
}
//再次調用
Student.sayHello();
//查看Student的屬性(只有 no 、 dept 和 重載了的sayHello)
document.write('<p>' + Object.keys(Student) + '<br>');

  通用上面這個示例,我們可以看到,Person里的屬性并沒有被真正復制到了Student中來,但是我們可以去存取。這是因為Javascript用委托實現了這一機制。其實,這就是Prototype,Person是Student的Prototype。

  當我們的代碼需要一個屬性的時候,Javascript的引擎會先看當前的這個對象中是否有這個屬性,如果沒有的話,就會查找他的Prototype對象是否有這個屬性,一直繼續下去,直到找到或是直到沒有Prototype對象。

  為了證明這個事,我們可以使用Object.getPrototypeOf()來檢驗一下:

Student.name = 'aaa';
//輸出 aaa
document.write('<p>' + Student.name + '</p>');
//輸出 Chen Hao
document.write('<p>' +Object.getPrototypeOf(Student).name + '</p>');

  于是,你還可以在子對象的函數里調用父對象的函數,就好像C++里的 Base::func() 一樣。于是,我們重載hello的方法就可以使用父類的代碼了,如下所示:

//新版的重載SayHello方法
Student.sayHello = function (person) {
Object.getPrototypeOf(this).sayHello.call(this);
var hello = "my student no is: " + this. no + ", <br>" +
"my departent is: " + this. dept;
document.write(hello + '<br>');
}

  這個很強大吧。

  組合

  上面的那個東西還不能滿足我們的要求,我們可能希望這些對象能真正的組合起來。為什么要組合?因為我們都知道是這是OO設計的最重要的東西。不過,這對于Javascript來并沒有支持得特別好,不好我們依然可以搞定個事。

  首先,我們需要定義一個Composition的函數:(target是作用于是對象,source是源對象),下面這個代碼還是很簡單的,就是把source里的屬性一個一個拿出來然后定義到target中。

function Composition(target, source)
{
var desc = Object.getOwnPropertyDescriptor;
var prop = Object.getOwnPropertyNames;
var def_prop = Object.defineProperty;
prop(source).forEach(
function(key) {
def_prop(target, key, desc(source, key))
}
)
return target;
}

  有了這個函數以后,我們就可以這來玩了:

//藝術家
var Artist = Object.create(null);
Artist.sing = function() {
return this.name + ' starts singing...';
}
Artist.paint = function() {
return this.name + ' starts painting...';
}
//運動員
var Sporter = Object.create(null);
Sporter.run = function() {
return this.name + ' starts running...';
}
Sporter.swim = function() {
return this.name + ' starts swimming...';
}
Composition(Person, Artist);
document.write(Person.sing() + '<br>');
document.write(Person.paint() + '<br>');
Composition(Person, Sporter);
document.write(Person.run() + '<br>');
document.write(Person.swim() + '<br>');
//看看 Person中有什么?(輸出:sayHello,sing,paint,swim,run)
document.write('<p>' + Object.keys(Person) + '<br>');

  Prototype 和 繼承

  我們先來說說Prototype。我們先看下面的例程,這個例程不需要解釋吧,很像C語言里的函數指針,在C語言里這樣的東西見得多了。

var plus = function(x,y){
document.write( x + ' + ' + y + ' = ' + (x+y) + '<br>');
return x + y;
};
var minus = function(x,y){
document.write(x + ' - ' + y + ' = ' + (x-y) + '<br>');
return x - y;
};
var operations = {
'+': plus,
'-': minus
};
var calculate = function(x, y, operation){
return operations[operation](x, y);
};
calculate(12, 4, '+');
calculate(24, 3, '-');

  那么,我們能不能把這些東西封裝起來呢,我們需要使用prototype。看下面的示例:

var Cal = function(x, y){
this.x = x;
this.y = y;
}
Cal.prototype.operations = {
'+': function(x, y) { return x+y;},
'-': function(x, y) { return x-y;}
};
Cal.prototype.calculate = function(operation){
return this.operations[operation](this.x, this.y);
};
var c = new Cal(4, 5);
Cal.calculate('+');
Cal.calculate('-');

  這就是prototype的用法,prototype 是javascript這個語言中最重要的內容。網上有太多的文章介始這個東西了。說白了,prototype就是對一對象進行擴展,其特點在于通過“復制”一個已經存在的實例來返回新的實例,而不是新建實例。被復制的實例就是我們所稱的“原型”,這個原型是可定制的(當然,這里沒有真正的復制,實際只是委托)。上面的這個例子中,我們擴展了實例Cal,讓其有了一個operations的屬性和一個calculate的方法。

  這樣,我們可以通過這一特性來實現繼承。還記得我們最最前面的那個Person吧, 下面的示例是創建一個Student來繼承Person。

function Person(name, email, website){
this.name = name;
this.email = email;
this.website = website;
};
Person.prototype.sayHello = function(){
var hello = "Hello, I am "+ this.name + ", <br>" +
"my email is: " + this.email + ", <br>" +
"my website is: " + this.website;
return hello;
};
function Student(name, email, website, no, dept){
var proto = Object.getPrototypeOf;
proto(Student.prototype).constructor.call(this, name, email, website);
this.no = no;
this.dept = dept;
}
// 繼承prototype
Student.prototype = Object.create(Person.prototype);
//重置構造函數
Student.prototype.constructor = Student;
//重載sayHello()
Student.prototype.sayHello = function(){
var proto = Object.getPrototypeOf;
var hello = proto(Student.prototype).sayHello.call(this) + '<br>';
hello += "my student no is: " + this. no + ", <br>" +
"my departent is: " + this. dept;
return hello;
};
var me = new Student(
"Chen Hao",
"haoel@hotmail.com",
"http://coolshell.cn",
"12345678",
"Computer Science"
);
document.write(me.sayHello());

  兼容性

  上面的這些代碼并不一定能在所有的瀏覽器下都能運行,因為上面這些代碼遵循 ECMAScript 5 的規范,關于ECMAScript 5 的瀏覽器兼容列表,你可以看這里“ES5瀏覽器兼容表”。

  本文中的所有代碼都在Chrome最新版中測試過了。

  下面是一些函數,可以用在不兼容ES5的瀏覽器中:

  Object.create()函數

function clone(proto) {
function Dummy() { }
Dummy.prototype = proto;
Dummy.prototype.constructor = Dummy;
return new Dummy(); //等價于Object.create(Person);
}
var me = clone(Person);

  defineProperty()函數

function defineProperty(target, key, descriptor) {
if (descriptor.value){
target[key] = descriptor.value;
}else {
descriptor.get && target.__defineGetter__(key, descriptor.get);
descriptor.set && target.__defineSetter__(key, descriptor.set);
}
return target
}

  keys()函數

function keys(object) { var result, key
result = [];
for (key in object){
if (object.hasOwnProperty(key)) result.push(key)
}
return result;
}

  Object.getPrototypeOf() 函數

function proto(object) {
return !object? null
: '__proto__' in object? object.__proto__
: /* not exposed? */ object.constructor.prototype
}

  bind 函數

var slice = [].slice
function bind(fn, bound_this) { var bound_args
bound_args = slice.call(arguments, 2)
return function() { var args
args = bound_args.concat(slice.call(arguments))
return fn.apply(bound_this, args) }
}

  參考

16
1

請先登錄 提交中…

標簽:JavaScript

文章列表

華麗麗的HTML5新特性

華麗麗的HTML5新特性

來源: 騰訊CDC 發布時間: 2011-07-26 16:20 閱讀: 4443 次 推薦: 0 原文鏈接 [收藏]

  Web2.0帶來的豐富互聯網技術讓所有人都享受到了技術發展和體驗進步的樂趣。作為下一代互聯網標準,HTML5自然也是備受期待和矚目,技術人員、設計者、互聯網愛好者們都在熱議HTML5究竟能帶來什么。那么就一起來窺探一下這個還未誕生就已經聲名在外的新標準吧。

  在探討HTML5的新特性之前,先說HTML5究竟離我們還有多遠?用一張時間軸來說明兩個關鍵點。

  如圖,在2012年,將會由W3C發布候選推薦版,這個版本的發布就代表著HTML5的規范編寫已經完成了。而2022年推出的計劃推薦版,則意味著至少會有兩個瀏覽器會完全的支持HTML5的所有特性。2022年聽起來似乎很遙遠,但通過觀察現階段chrome, firefox , safari,IE等瀏覽器對HTML5的支持程度,可以看出各大瀏覽器廠商都非常積極。應該不需要到2022年就會有至少兩個瀏覽器支持HTML5。因此現在關注和討論HTML5,了解HTML5的新特性,為以后的產品規劃并非毫無意義。

  HTML5其實是關于圖像,位置,存儲,速度的優化和改進,以下分別論述。

  圖像:

  到目前為止,基本上想要直接在網頁上進行繪圖還是不能輕易完成的,即使是幾何圖形也不可以。在瀏覽器當中直接能跟圖片的交互操作也很有限,多數是保存和點擊。如果希望能夠跟圖片進行更多的操作或者在瀏覽器當中畫出圖形,就需要flash, silverlight 這類插件來幫忙。

  HTML5了解人們的需求,HTML5已經確定引入canvas標簽,通過canvas,用戶將可以動態的生成各種圖形圖像,圖表以及動畫。下面是一個示例網站,展示了不通過插件,使用HTML5直接繪制圖片,有興趣的朋友可以自己親自去試玩一下。

  不僅如此,HTML5也賦予圖片圖形更多的交互可能,HTML5的canvas標簽還能夠配合javascript來利用鍵盤控制圖形圖像,這無疑為現有的網頁游戲提供了新的選擇和更好的維護性和通用性,脫離了flash插件的網頁游戲必然能夠獲得更大的訪問量,更多的用戶。一些統計數據表格也可以通過使用canvas標簽來達到和用戶的交互,例如某網站對2009年德國的大選情況統計就全部通過了HTML5來實現用戶點擊和數據的變更,點選某個區域就可以實時的看到該區域各黨派選票率,大大增強了統計圖表的可讀性。

  通過HTML5對圖形圖像的新特性,未來可能會有在線繪圖的工具和應用,人們將不再需要安裝painter這類基本的繪圖軟件,而直接使用基于瀏覽器的應用。而對用戶體驗人員和開發者來說,將能夠在用戶毫不知情的情況下收集和生成用戶鼠標的瀏覽軌跡,從而生成一部分可用的熱點圖,這對于找出網站的不足,提升用戶體驗有著重要作用。現在對canvas標簽的支持情況如下,可以看到,基本所有的瀏覽器都已經不同程度上支持了這一特性。

  位置:

  這個大頭針圖標從2010年到2011年在各類應用和互聯網上應該是非常火爆了吧?沒錯,就是地理位置,各處都可以看到人們在簽到,查找自己當前的地理位置和周邊。作為新標準的HTML5自然也不會置身事外,HTML5通過提供應用接口Geolocation API,在用戶允許的情況下共享當前的地理位置信息,并為用戶提供其他相關的信息。

  HTML5的Geolocation API主要特點在于:1. 本身不去獲取用戶的位置,而是通過其他三方接口來獲取,例如IP, GPS, WIFI等方式。2. 用戶可以隨時開啟和關閉,在被程序調用時也會首先征得用戶同意,保證了用戶的隱私。

  存儲以及速度:

  現在,web應用的火爆已經是不折不扣的現實,并且相對傳統的應用,web應用不需要安裝,所占空間小的特性使其具備傳統軟件應用所不具備的優勢,然而,目前制約web應用最大的問題在于網絡連接不能夠無時無處。在飛機上,汽車上,火車上,有很多地方都無法被網絡信號所覆蓋,因此web應用也就無法使用。

  HTML5的離線存儲使得這個問題迎刃而解。HTML5的web storage API 采用了離線緩存,會生成一個清單文件(manifest file),這個清單文件實質就是一系列的URL列表文件,這些URL分別指向頁面當中的HTML, CSS, Javascrpit, 圖片等相關內容。當使用離線應用時,應用會引入這一清單文件,瀏覽器會讀取這一文件,下載相應的文件,并將其緩存到本地。使得這些web應用能夠脫離網絡使用,而用戶在離線時的更改也同樣會映射到清單文件中,并在重新連線之后將更改返回應用,工作方式與我們現在所使用的網盤有著異曲同工之處。

  感興趣的朋友們可以試下這個網站,就屬于便攜筆記本的離線應用,可以在離線的時候記錄一些便簽,在下次上線,或使用其他平臺登錄時,仍然能夠看到之前的記錄。

  緩存的強大并不只在于離線應用,同樣在于對cookies的替代,目前我們經常使用的保存網站密碼,使用的就是cookies將密碼信息緩存到本地,當需要時再發送至服務器端。然而,cookies有其本身的缺點4KB的大小和反復在服務器和本地之間傳輸,并且無法被加密。對于cookies的反復傳輸,不僅浪費了使用者的帶寬、供應商的服務器的性能,更增加了被泄露的危險。

  Web storage API 解救了cookies, 據現有的資料,web storage API將至少支持4M的空間作為緩存,對于日常的清單文件和基礎信息,應該已經足夠使用了,畢竟4KB我們不是都使用了這么多年了?速度的提升方式在于,webstorage API 將不再無休止的傳輸相同的數據給服務器,而只在服務器請求和做出更改時傳輸變更的必須文件,這樣就大大節省了帶寬,也減輕了服務器的壓力。可謂是一舉數得!

  小結:

  HTML5還有很多令人心動的特性和新功能,限于篇幅無法一一舉出,但我對于HTML5的前景還是非常看好的,畢竟豐富web應用的大勢已經掀起,web2.0的浪潮也正在繼續,讓我們共同期待HTML5的降臨。

0
0

請先登錄 提交中…

標簽:HTML5

文章列表

解構Unity的腳本物件模型

解構Unity的腳本物件模型

作者: Milo Yip 來源: 博客園 發布時間: 2010-02-26 10:06 閱讀: 2170 次 推薦: 1 原文鏈接 [收藏]

Unity 是一個以 Mono 為基礎的游戲開發環境,能同時支持三種腳本語言,包括 C#、Javascript 和 Boo (類似 Python)。 由于 Unity 的開發工具暫時只有 Mac 的版本 (2010年2月25日更新: 現時已有Windows版本,而且有免費授權版,另外因為Unity iPhone版的出現使Unity的使用者大增),所以暫時未能測試。但是它有很詳細的文檔,看上來很易用,所以就從文字上學習它的 Script 使用方式。 跟據一些 Tutorial參考手冊,我用 Graphviz 畫了一個 (我認為) 最核心的 UML 類圖:

從這個類圖我們可以理解它的結構,及如何把一些常用功能映射至這系統里,以下分節討論。

GameObject 和 Component

Unity 的執行環境里,會有一個場境 (Scene)。這個場境包含一個 GameObject 對象的層階 (Hierarchy)。 這個 GameObject 類只是一個容器,本身沒有其他功能。使用者需要為 GameObject 加入各種 Component 對象來定義它的行為,而不是透過繼承 (inherit) GameObject 來加入 行為。 一個對象可擁有多個 Component 對象,但有一些 Component 類別只可以在一個 GameObject 中有一個 實例 (instance)。

MonoBehavior

我最感興趣的,是使用者如何自行定義行為來做出不同的 Gameplay。在 Unity 中,程式員編寫的 Script,其實也是 Component 的一種,所有的 Script 都會繼承自 MonoBehavior 類別。以下是一個簡單例子:

var speed = 5.0; function Update () { var x = Input.GetAxis("Horizontal") * Time.deltaTime * speed; var z = Input.GetAxis("Vertical") * Time.deltaTime * speed; transform.Translate(x, 0, z); } 

把這個 Script 加進一個 GameObject 的話 (成為該 GameObject 的一個 Component),Runtime 會在每幀呼叫 Update(),玩家就可以用上下左右鍵控制那個 GameObject 在水平方向移動。。

Transform

每個能在三維空間里的 GameObject 都會有 Transform Component (未有詳細看是否有一些 GameObject 可以省郤 Transform,例如一個用來定義一個游戲任務的 GameObject)。Transform 包括平移、旋轉及縮放。 之前的例子已用了 Transform Component,不過它其實是 Object 類別的一個簡寫,這簡寫其實等同:

GetComponent(Transform).Translate(x, 0, z) 

Component 的連結

在 Script Tutorial 里的例子是寫一個 Follow 的行為,擁有這個 Component 的 GameObject 會自動追蹤 (面對著) 一個目標對象:

var target : Transform; function Update () { transform.LookAt(target); } 

這個 Script 暴露了一個 target 變量 (應當作成員變量吧),使用者可以把其他對象的變 assign 至這個變量。這 assignment 有兩種方法實現,其一是利用 Unity 的 GUI 工具把一個 Component 實例的變量 (如Transform) drag-and-drop 至這個 Component 實例的 target 變量,而另一個方法是寫代碼:

var newTarget = GameObject.Find("Cube").transform; GetComponent(Follow).target = newTarget; 

用代碼就可以這樣動態改變這些 Component 之間的聯結方式。或者另一個說法是,GUI 工具是可以設定起始的聯結,而 Script 可以在執行期改變這些聯結。

渲染

一個可被渲染的 GameObject 需要有以幾個 Components,以 Mesh 為例:

  1. MeshFilter: 用來找出現時的 Mesh 對象
  2. MeshRenderer: 用來渲染 Mesh 的 Component,會參考一個 Material 對象

要注要 Mesh 和 Material 對象并非 Component,它們是繼承自 Object 的。你可以動態改變它們。但由于它們不是 Component ,所以可以被分享,例如多個 GameObject 的 MeshRenderer 都參考到同一個 Material。一個 Component 實例只屬于一個 GameObject (所以在 UML 中我用黑色鉆石表示 Composition)。 而 Light 和 Camera 則是 Component,這意未著可以簡單的設定聯結。

分析

Unity 的 Script 對象模型是以 Component 為基礎的。透過把 Component 實例加入 GameObject 實例來組合不同功能的對象,而 Component 實例之間可以建立聯結。 這種方式不需要透過繼承 (inheritance),而是透過聚合 (aggregation)加入對象的功能和行為。使用聚合的好處是不會產生復雜的繼承層階,亦可以動態改變聚合的結構 (例如在執行期加入或移除 Component)。 有一些細節我暫時未清楚,例如多個 Component 在一個 GameObject 中的執行次序如何設定;聯結會否有 cylic 的問題等等。可能要拿到軟件再試用才可以知道。

結語

Unity 的腳本系統給我的感覺是使用非常簡單。透過很少的代碼就能寫一些行為,甚至把行為組合到對象中。但是,通常容易的東西都會有相對的缺點,例如在效能上或是 Scalability 上。后者可能是一個很大的問題,當游戲規模擴大,Component 和聯結就會變成一個很復雜的 graph,由于連結是發生于執行期 (而非靜態),可能要作改動會變得困難。換句話說,就是改幾十個類別容易,改它們的幾千個 實例就會很困難。 軟件設計世界里當然沒有銀子彈,每個方案都適合不同的情況。我認為 Unity 的一個設計目標是容易使用,就是像 Virtools 之流,可以給沒有程式底子的人做游戲,相對來說做比較復雜的項目可能會遇到許多問題。但參考一下總可以給予對事物新的觀點,或分析另一個科案的優越之處。

之后還有一篇關于 CryEngine 的腳本分析,但現時我在家里開發的 Mil 引擎主要是采用 Unity 的物件模型。

本文原來是繁體中文,在2008-02-29發表于http://miloyip.seezone.net/?p=15,本文經過修正。

1
0

請先登錄 提交中…

標簽:Mono Unity

文章列表

深有體會的積極人生態度

深有體會的積極人生態度

作者: zeuslin 來源: 博客園 發布時間: 2008-09-09 17:25 閱讀: 1492 次 推薦: 1 原文鏈接 [收藏]

  回顧了剛剛走過的這一年,小結以下幾點深有體會的態度與大家共勉:

1. 要有“一定要”的決心,就像愛情,愛與不愛,沒有中間狀態,中間狀態帶來的往往更多的痛苦與傷害:當一個人不是一定要的時候,連小石頭都可擋住他的去路,只有“一定要”的人,再大的障礙都擋不住他想要的結果。
2. 找到人生的真正所求,對之有著強烈的渴望與不可阻擋的向往,并時刻提醒自己這就是自己真正想要。
3. 目標決定策略,策略決定計劃,計劃決定行動,行動決定結果。多做一些一般人不愿意做的和一般人做不到的事情,也許會成就一個不一般的人。
4. 充滿自信。解決問題,很多時候是由態度而不是技巧決定的。精神不滑坡,方法總比困難多。成功者先相信,后看見。
5. 專注。一個人能真正做好的事是很有限的,唯有專注才能讓你足夠優秀。因此,有所不為才能有所為。
6. 不要抱怨。對社會,對環境不要有太多的抱怨,多想想自己能做什么。生命的意義存在于我能為這個社會提供了些什么。
7. 所有借口都是蹩足的。弱者才需要借口,強者都選擇坦誠面對,然后尋求解決方法。
8. 胸懷,心有多大,舞臺就有多。有胸懷包容自已或他人的過錯,并隨時準備著給予鼓勵幫助。
9. 善于學習。善于從外界獲得力量,站在巨人的肩膀上,獲得比巨人更為廣闊的視野。
10. 甘于承受。保持敬畏之心,對時光,對生命,對美,對痛楚。
11. 身體健康。 這個是最基本的,少了它,什么事都做不成了。
12. 智慧。了解這個世界運作的各種規律,遵守并利用它創造更大的價值。
13. 成熟。不任性隨性。關注自身的時候多顧及他人的感受與需要——人的價值常常通過他人來體現的。

1
0

請先登錄 提交中…

標簽:經驗分享

文章列表

使用別樣的方法讓在IE中同樣享受使用高級CSS3選擇器的特權

使用別樣的方法讓在IE中同樣享受使用高級CSS3選擇器的特權

作者: 神飛 來源: 前端觀察 發布時間: 2010-07-15 22:38 閱讀: 1337 次 推薦: 0 原文鏈接 [收藏]
摘要:真沒想到老外的那些大牛也可以讓ie也享受css3帶來的快感

別誤會,IE是不支持CSS3高級選擇器,包括最新的IE8(詳見《CSS選擇器的瀏覽器支持》),但是CSS選擇器的確是很有用的,

它可以大大的簡化我們的工作,提高我們的代碼效率,并讓我們很方便的制作高可維護性的頁面。

然而IE對高級CSS選擇器特別是CSS3選擇器的支持讓我們一直不能將CSS選擇器推廣應用。不過,雖然我們無法左右瀏覽器的市場份額,

卻可以通過一些技術改善我們的工作。我們也可以使用其它的一些技術,讓IE可以變相支持CSS3選擇器。

一位來自英國的網頁開發工程師Keith Clark開發了一個JavaScript方案來使IE支持CSS3選擇器。該腳本支持從IE5到IE8的各個版本。

用法

你只需要下載Robert Nyman的DOMAssistant腳本和ie-css3.js并將它們在你的頁面的head標簽中導入,如下:

<head>http://DOMAssistantCompressed-2.7.4.js
http://ie-css3.js</head>

支持的選擇器

  • :nth-child
  • :nth-last-child
  • :nth-of-type
  • :nth-last-of-type
  • :first-child
  • :last-child
  • :only-child
  • :first-of-type
  • :last-of-type
  • :only-of-type
  • :empty

ie-css3的一些限制

  • 樣式表必須通過<link>標簽引入。頁面級的樣式表或者內聯的樣式表將無效。不過你可以在外部樣式文件中使用@import 導入其它樣式文件;
  • 樣式表文件必須和頁面放在同一個域名下面;
  • 使用file://路徑的樣式文件將由于瀏覽器的安全問題而不起作用;
  • :not()選擇器尚不支持;
  • 該方法不是動態的,樣式被應用之后再改變DOM,將會無效。

如何工作的?

ie-css3.js下載頁面的每一個樣式文件并解析它的CSS3偽選擇器。如果一個選擇器被找到,它就會被替換為同名的CSS class。

比如: div:nth-child(2)將會變成 div._iecss-nth-child-2 。

然后,Robert Nyman的DOMAssistant用于尋找匹配元素CSS3選擇器的DOM節點然后將相應的CSS類添加給它。

最終,元素的樣式表會被新的版本替代,然后用CSS3選擇器對相應元素添加對應的樣式。

避免IE的CSS解釋器

根據W3C的規定,一個瀏覽器應該無視它不認識的CSS規則。這就出現一個問題——我們需要利用樣式表文件中的CSS3選擇器,但是IE會將它們丟棄。

為了避免這個問題,每一個樣式文件都會通過XMLHttpRequest下載。這允許該腳本繞開瀏覽器內置的CSS解釋器并能夠讀取原始的CSS文件。

替代方案

顯然這個也并非完美的方案,對于Ajax網站來說,它基本上是不能用的,因為在生成的樣式表被應用之后再改變DOM,就不會有效了

但是事實上我們可以自己來自定義一個ie-css3的。只是沒有它這個這么智能。

使用 cssQuery

cssQuery是由業界大牛Dean Edwards開發的一個Javascript組件。它就是為CSS 選擇器而生的。

它可以支持幾乎所有的CSS 選擇器,包括CSS3選擇器。當然它在實現的時候進行了分級,分別針對CSS1,CSS2和CSS3

制作了一個獨立的js包,以及一個標準包。支持所有A級瀏覽器。

簡單的用法:

var tags = cssQuery("body> p");
var tags = cssQuery("[href]");
var tags = cssQuery("a[href='#']");

然后你就可以自己寫一些js為相應的對象添加想用的樣式了。

我建議對支持CSS高級選擇器的瀏覽器使用原生的CSS選擇器寫法,然后通過cssQuery在IE中動態的為響應的元素添加一個樣式名。

比如,我們可以這樣寫CSS:

a[herf='#'],a.empty{color:red}

這里的第一條CSS3選擇器是用于Firefox/webkit等支持CSS3選擇器的非IE瀏覽器的,a.empty是專門為IE而寫。

然后通過CSSQuery動態的在IE瀏覽器中為對應的元素添加樣式:

var tags = cssQuery("a[href='#']");
tags.className="empty";

當然,上面的這段js最好使用IE的條件注釋

使用 jQuery

據說jQuery的選擇器比cssQuery要快很多。當然,無可否認,jQuery的選擇器是目前流行的js框架中最好用的一個。

而使用jquery來實現類似上述功能要方便很多,因為jQuery的選擇器更好用。

OK,jQuery的做法和cssQuery有些類似,CSS可以寫成上面一樣的,JS可以這樣寫:

$("a[href='#']").addClass("empty");

更具體的用法可以查看我之前寫的一個小例子《使用jQuery創建個性化鏈接樣式

使用 DOMAssistant

DOMAssistant也是一個很不錯的JS庫,它提供了一些類似jQuery的功能,比如CSS 選擇器、事件以及一些DOM操作。它的優點就是小巧,

壓縮后只有9KB,我想這就是Keith Clark選擇DOMAssistant來作為ie-css3.js的基礎框架的主要原因吧。jQuery越來越肥胖了,而且用于實現這個功能有些浪費了。

而且DOMAssistant的用法和jQuery非常類似。

事實上,DOMAssistant的選擇器和對DOM的CSS Class的操作與jQuery一模一樣。

$("a[href='#']").addClass("empty");

欲了解更多,可查看DOMAssistant官方,以及下載官方中文PDF文檔

總結

其實,無論是ie-css3.js本身,還是后來我們討論的三種替代方法,都是一種方法,就是用js動態的添加class到頁面元素中。

不同是ie-css3自動化的完成了這些工作,而后面的三種方案要自己動手根據自己的項目寫一些js來實現。我認為ie-css3最方便,

基本不用怎么維護,但是它一刀切,效率比較低,而且靈活性差,不適合于ajax項目。而后面的這幾種方案靈活性強,

但是要做很多額外的工作,無論你是將IE專用樣式獨立到一個專用的css文件中還是像文中寫的那樣和CSS3選擇器寫到一起,都會大大的增加你的工作量和維護成本。

為IE,我們要額外付出很多。

有更好的想法或創意嗎?歡迎與我們分享。

0
0

請先登錄 提交中…

標簽:ie5 ie6 ie7 ie8 css3 js html5 選擇器

文章列表

構建高性能ASP.NET站點之優化HTTP請求

構建高性能ASP.NET站點之優化HTTP請求

作者: 小洋(燕洋天) 來源: 博客園 發布時間: 2011-02-28 21:53 閱讀: 1331 次 推薦: 0 原文鏈接 [收藏]
摘要:本篇就開始細化頁面的請求過程并且提出優化的方案.

  本篇就開始細化頁面的請求過程并且提出優化的方案.同時,在上篇文章中,不少朋友也提出了一些問題,在本篇中也對這些問題給出了回答!

  本篇的議題如下:

  HTTP請求的優化

  HTTP請求的優化

  在一個網頁的請求過程中,其實整個頁面的html結構(就是頁面的那些html骨架)請求的時間是很短的,一般是占整個頁面的請求時間的10%-20%.在頁面加載的其余的時間實際上就是在加載頁面中的那些flash,圖片,腳本的資源. 一直到所有的資源載入之后,整個頁面才能完整的展現在我們面前.

  下面,我們就從一個頁面開始講述:

1 <!DOCTYPE html PUBLIC “-//W3C//DTD XHTML 1.0 Transitional//EN" “http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd“>
2 <html xmlns="http://www.w3.org/1999/xhtml“>
3 <head>
4 <title>小洋,燕洋天</title>
5
6 http://../demo.js
8
9 </head>
10 <body>
11 <div>
12 <img src="../images/1.gif" />
13 <img src="../images/2.gif" />
14 <img src="http://yanyangtian.cnblogs.com/image/3.gif" />
15 <img src="http://yanyangtian.cnblogs.com/image/4.gif" />
16 <img src="http://yanyangtian.cnblogs.com/image/5.gif" />
17 <img src="http://yanyangtian.cnblogs.com/image/6.gif" />
18 <img src="http://yanyangtian.cnblogs.com/image/7.gif" />
19 <img src="http://yanyangtian.cnblogs.com/image/8.gif" />
20 <img src="http://yanyangtian.cnblogs.com/image/7.gif" />
21 <img src="http://yanyangtian.cnblogs.com/image/8.gif" />
22 </div>
23 </body>
24 </html>
25

  如果我們向服務器請求這個頁面,客戶端的瀏覽器首先請求到的數據就是html骨架,即:

1 <!DOCTYPE html PUBLIC “-//W3C//DTD XHTML 1.0 Transitional//EN" “http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd“>
2 <html xmlns="http://www.w3.org/1999/xhtml“>
3 <head>
4 <title>小洋,燕洋天</title>
5
6 http://../demo.js
8
9 </head>
10 <body>
11 <div>
12 <img src="../images/1.gif" />
13 <img src="../images/2.gif" />
14 <img src="http://yanyangtian.cnblogs.com/image/3.gif" />
15 <img src="http://yanyangtian.cnblogs.com/image/4.gif" />
16 <img src="http://yanyangtian.cnblogs.com/image/5.gif" />
17 <img src="http://yanyangtian.cnblogs.com/image/6.gif" />
18 <img src="http://yanyangtian.cnblogs.com/image/7.gif" />
19 <img src="http://yanyangtian.cnblogs.com/image/8.gif" />
20 <img src="http://yanyangtian.cnblogs.com/image/7.gif" />
21 <img src="http://yanyangtian.cnblogs.com/image/8.gif" />
22 </div>
23 </body>
24 </html>
25

  在此之前,首先來普及一下頁面加載的小知識:

當頁面的html骨架載入了之后,瀏覽器就開始解析頁面中標簽,從上到下開始解析.

首先是head標簽的解析,如果發現在head中有要引用的js腳本,那么瀏覽器此時就開始請求腳本,此時整個頁面的解析過程就停了下來,一直到js請求完畢.

之后頁面接著向下解析,如解析body標簽,如果在body中有img標簽,那么瀏覽器就會請求img的src對應的資源,如果有多個img標簽,那么瀏覽器就一個個的解析,解析不會像js那樣等待的,如果發現img的url地址是同一個地址,那么瀏覽器就會充分的利用這個已經打開的tcp連接順序的去一個個的請求圖片,如果發現有的img的url地址不同,那么瀏覽器就另開tcp連接,發送http請求.

注意之前請求js的區別:請求需要js,瀏覽器會一直等待,不在解析下面的html標簽

但是解析到img的時候,盡管此時需要加載圖片,但是頁面的解析過程還是會繼續下去的,然后決定是否發送新的tcp連接加載資源.

  大家可能覺得這個之前的代碼片段一樣,確實代碼是一樣的,但是,在最開始的時候,發送到瀏覽器中的只是那些html的代碼,任何的js腳本和圖片還沒有發送過來.

  當html代碼到了瀏覽器中,那么瀏覽器就開始一步步的解析這些代碼了,只要遇到了需要加載的資源,瀏覽器就向服務器發出http請求,請求所需的資源.

  整個頁面的加載時間圖如下:

  大家從圖中可以看出:

  第一條線中分為一半黃色和一半藍色,其實黃色的部分就代表了打開一個tcp連接花的時間,而后面的藍色的部分就是請求整個html骨架文檔的時間.可以看出,請求html骨架的時間是很短的.其余藍色的線就表示了圖片,腳本資源加載所花的時間.

  很顯然,這樣頁面的整個加載時間就很長了.因為頁面的加載幾乎是順序的載入,時間就是所有資源加載時間的總和.

  下面我們把上面的頁面代碼代為如下:

<!DOCTYPE html PUBLIC “-//W3C//DTD XHTML 1.0 Transitional//EN" “http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd“>
<html xmlns="http://www.w3.org/1999/xhtml“>
<head>
<title>小洋,燕洋天</title>

http://../demo.js

</head>
<body>
<div>
<img src="http://demo1.com/images/1.gif" />
<img src="http://demo1.com/images/2.gif" />
<img src="http://demo2.com/image/3.gif" />
<img src="http://demo2.com/image/4.gif" />
<img src="http://demo3.com/image/5.gif" />
<img src="http://demo3/image/6.gif" />
<img src="http://demo4.com/image/7.gif" />
<img src="http://demo4.com/image/8.gif" />
<img src="http://yanyangtian.cnblogs.com/image/7.gif" />
<img src="http://yanyangtian.cnblogs.com/image/8.gif" />
</div>
</body>
</html>

  我們再來看看頁面的加載時間圖

  這就是所謂的”并行”載入了.

  比較一下兩段代碼的不同:其實就在img的src屬性上面:

    第一段頁面的代碼:img的src屬性都是指向一個域名的.

    第二段頁面的代碼:img的src屬性指向了不同的域名

  這樣做的結果是什么?

  請大家注意比較img的src的不同.

  解釋之前,首先來看一個小的常識(在上篇文章中也提過):

當頁面請求向服務器請求資源的時候,如果瀏覽器已經在客戶端和服務器之前打開了一個tcp連接,而且請求的資源也在開了連接的服務器上,那么以后資源的請求就會充分的利用這個連接去獲取資源. 這樣也就是第一個時間圖的由來.

如果請求的圖片分別位于不同的服務器網站,或者那個請求的服務器網站有多個域名,那么因為瀏覽器就會為每一個域名去開一個tcp連接,發送http請求,這樣,結果就是同時開了多tcp連接,這也是第二個時間圖的由來.

  雖然說并行加載,確實使得頁面的載入快了不少,但是也不是每一個圖片或者其他的資源都去搞一個不同的域名,像之前的第二個并行載入圖片的例子,也是讓兩個圖片利用一個tcp連接.如果每個圖片都去開一個連接,這樣瀏覽器就開了很多個連接,也是很費資源的,而且瀏覽器還可能會”僵死”.而且有時還會嚴重的影響性能.

  所以,這是需要權衡的.

  除了上面的優化方式,還有其他的圖片優化的加載方式.主要是通過減少http的請求達到優化

  大家都知道網站的一個menu菜單,有些菜單就是用圖片作出來的.如

  如果上面的圖片一個個載入,勢必影響速度,如果開多和請求,有點得不償失.而且圖片也不是很大,那么就一次把整個menu需要的圖片作為整個圖片,一次加載,然后通過map的方式,控制點擊圖片的位置來達到導航的效果.

  這樣一個問題就是:算出圖片的坐標,不能點擊了”主頁”圖片,然后卻跳到了”幫助”頁面了.

  本篇就講述到這里,下篇講述其他的資源文件的優化,希望 多多提出建議,爭取把這個系列寫好!

0
0

請先登錄 提交中…

標簽:ASP.NET 站點 優化 HTTP請求

文章列表

CSS3 基本要素概覽

CSS3 基本要素概覽

作者: 孟晨 來源: 博客園 發布時間: 2011-11-08 18:19 閱讀: 3024 次 推薦: 2 原文鏈接 [收藏]

  這篇文章將對 CSS 的幾個新屬性 (text-shadow,box-shadow,and border-radius) 做基本介紹。這些 CSS3 屬性通常用來加強頁面布局。

  RGBA

  前面的 3 個值是 RGB 顏色值,最后一個值是透明度的級別(0 = 透明,1 = 不透明)。

  RGBA 可以應用于與顏色的任何屬性,如字體顏色,邊框顏色,背景顏色,陰影顏色等。

  Text Shadow

  文字陰影的結構順序為:x 軸偏移,y 軸偏移,模糊,顏色。

  設置一個負值的 x 軸偏移將陰影轉移到左側。設置一個負值的 y 軸偏移轉將陰影轉移到頂部。別忘了,你可以在陰影顏色中應用 RGBA 值。

  您也可以指定一個文本陰影列表(以逗號分隔)。下面的示例使用兩個文本陰影聲明制作了文字凸版效果(頂部 1px 和底部1px)。

text-shadow: 0 1px 0 #fff, 0 -1px 0 #000;

  Border Radius

  邊界半徑 (border radius) 的寫法類似內邊距 (padding) 和 外邊距 (margin) 屬性(例如:border-radius:20px)。為使瀏覽器渲染邊界半徑屬性,需要在屬性名稱加上前綴,Webkit 引擎的瀏覽器為 “-webkit-”,Firefox 則為 “-moz-”。

  您可以為每個邊角指定不同的值。注意:Firefox 和 Webkit 的邊角屬性名稱是不相同的。

  Box Shadow

  盒陰影的結構和 text-shadow 相同,x 軸偏移,y 軸偏移,模糊,顏色。

  同樣,您可以設置一個以上的盒陰影。下面是三個盒陰影聲明示例。

-moz-box-shadow:
-2px -2px 0 #fff,
2px 2px 0 #bb9595,
2px 4px 15px rgba(0, 0, 0, .3);

  英文原稿:The Basics of CSS3 | WebDesignWall

  翻譯整理:CSS3 基本要素概覽 | 孟晨

2
0

請先登錄 提交中…

標簽:CSS3

文章列表

拒絕平庸——淺談WEB登錄頁面設計

拒絕平庸——淺談WEB登錄頁面設計

作者: 大C 來源: 騰訊CDC 發布時間: 2012-01-17 11:32 閱讀: 10938 次 推薦: 17 原文鏈接 [收藏]

  用戶活躍度是檢驗產品成功與否的重要指標之一,傳統行業的商家極為重視門面的裝潢,因為一個好的門面可以聚集人氣,招攬更多的顧客。古時候的大戶人家院子門口的石獅子或其他的擺件的擺放極為講究,有一定的風水學說道理,更能彰顯主人家的身份地位.由此可見,“門面’就如人的臉面之于人的形象一樣重要,而 WEB 的登錄頁面就相當傳統的“門面”。

  現在越來越多的大型網站把登錄和首頁放在一起設計,由此可見登錄頁面的重要性,一個出彩的登錄界面,將提升產品的品質,賦予產品獨特的氣質,登錄界面也是一個發揮情感化設計,提升用戶體驗,拉近與用戶之間距離的兵家必爭之地,本文不談趨勢,不講交互大道理,不涉及技術,就侃侃用戶登錄頁面的一些設計表現形式。希望這些設計表現手法能給大家帶來一些啟發和靈感。

  優雅大方

  如果說 iPad 是介于傳統電腦和手機之間的產品,那么 tumblr 則是介于 blog 和 twitter 之間的服務。相比 twitter,它的功能更復雜、內容展示性更強、更加重視多媒體的應用。Tumblr 做為輕博客的鼻祖,帶來一種全新的視覺體驗, 安東尼·德·圣-埃克蘇佩里曾說過,“完美就是多一點則太多,少一點則太少。” Tumblr 的登錄頁面沒有過多的視覺干擾,優雅大方,一切元素的存在都是為了用戶更好的登錄,登錄過程非常流暢。

  

  精致的質感表現

  iCloud 是蘋果公司所提供的云端同步服務,用戶有 5GB 的免費存儲空間。 負責 Macintosh 用戶界面設計的柯戴爾·瑞茨拉夫回憶說:“喬布斯會一個像素一個像素地檢查屏幕上的每個細節,確保相關的圖像準確對齊。他非常重視細節,細致程度居然達到了像素的層面。如果發現問題,喬布斯就會立即沖著某個工程師大吼起來。”iCloud 登錄頁面的設計繼承了蘋果公司對細節的苛求, 細致的紋理,微妙的陰影,精致的質感,完美的細節,金屬光澤可以隨著鼠標指針移動,底部的圖標可以隨著分辨率的大小自適應,改變排列方式,確保用戶的瀏覽體驗。

  iCloud 給我們上了很好的一課,有句大家都聽過卻未必做到的話——細節決定成敗,豐富的細節可以提升設計的價值,也是判斷一個設計高下的一條很重要的標準之一,精致舒適的質感紋理,給用戶一種沉浸式,充滿驚喜的登錄體驗,是一種很棒的表現方式。

  

  小清新的插圖

  在網頁設計中,插圖非常具有表現力,它與繪畫藝術關系密切。所以大部分設計職位,對手繪能力出眾者格外青睞,許多表現技法都是借鑒了繪畫藝術的表現技法。插畫藝術與網頁設計的的結合,具有獨特的藝術魅力,從而更具表現力。越來越多的設計師,將插畫運用到網頁設計中來,生動有趣溫情的清新插圖,能迅速的抓住用戶的眼球,讓登錄界面的更加具有親和力,

  163郵箱的登錄頁面就采用了大幅的插圖,小郵差很快喚醒了 80 后等待來信的記憶,有故事的插圖與用戶建立情感的聯系,喚起用戶的心靈共鳴,讓用戶更有歸屬感。

  Vimeo 是一家提供高清視頻存放服務的網站,在這里可以找到很多來自世界各地非常有創意的設計師。相信登錄過 Vimeo 的朋友都對 Vimeo 的登陸頁面記憶深刻。

  

  人文關懷的品牌傳達

  設計以人為本,以人為本的設計不僅能提高產品的品質,還能提高設計的藝術水平,而登錄頁面是體現人文關懷,傳播品牌理念的絕佳位置。

  QQ 郵箱登陸頁面每一次刷新都能看到不同的內容,或用海子的詩,或用邁克爾·杰克遜的歌詞,喚起用戶的共鳴,設計手法簡潔,主體信息突出,引導清晰,并沒有多余的元素,界面中最重要的操作“登錄”按鈕使用了交通中通行的綠色,而沒有使用常用的藍色,細節設計非常考究,對每個細節都注入人文的關懷。

  新浪微博將登錄框設計成一條圍脖的樣式,用戶的每一次登錄都是一次品牌傳達的過程,切合新浪力推的圍脖品牌理念,織圍脖的概念深入人心。

  越來越大的登錄框

  越來越大的輸入框設計,讓用戶輸入起來感到心情舒暢,登錄過程非常愉悅,在顯示器越來越大的今天,mailchimp 大輸入框顯的霸氣十足,并且一反常態的可以看到自己的密碼,第一次在 WEB 登錄框里見到這種設計,非常貼心。正是這種不拘一格的設計,讓 mailchimp 從一個內部項目蛻變成一個該公司最成功的商業產品。

  

  簡約而不簡單

  WEB 設計的風格越來越趨向于簡潔,登錄頁面大量地使用留白可以讓登錄框更加突出。最大程度的減少用戶分心,從視覺的角度來看,簡約的設計是平靜的,砍掉了多余的元素、顏色、形狀和紋理,不能使用讓人眼前一亮的設計元素,只能靠空白去做視覺吸引力。布局的權衡及簡化設計做的不到位的話很容易變的單調乏味,wordpress 后臺登陸頁面采用適當的投影,灰色的巧妙運用,以及出錯的抖動提示,讓整個登錄頁面簡約而不簡單,堪稱典范。

  隨著互聯網的高速發展,移動互聯網的到來,WEB 設計越來越呈現多元化。盡管一個好的設計并代表產品就一定會成功,但卻能為產品加分,為產品注入設計 DNA,創造獨特的風格和視覺感受。好了,今天就先侃到這,作為用戶使用的入口,希望這篇小文可以讓大家對登錄頁面重視起來,設計出更多精彩的登錄頁面。

17
0

請先登錄 提交中…

標簽:網頁設計

文章列表

高效 JavaScript 單元測試

高效 JavaScript 單元測試

來源: IBM developerWorks 發布時間: 2011-12-01 13:26 閱讀: 6912 次 推薦: 2 原文鏈接 [收藏]
摘要:能在一個瀏覽器上運行的 JavaScript 并不一定能在其他瀏覽器上運行。如果沒有對代碼進行單元測試,那么在決定升級或支持新瀏覽器的時候,組織就需要花錢測試或重新測試 Web 應用程序。在本文中,了解 JavaScript 單元測試如何幫助您降低測試成本,輕松支持更多瀏覽器。

  一個損壞的 JavaScript 代碼示例

  Web 應用程序面臨的一個最大挑戰是支持不同版本的 Web 瀏覽器。能在 Safari 上運行的 JavaScript 代碼不一定能在 Windows® Internet Explorer (IE)、Firefox 或 Google Chrome 上運行。這個挑戰的根源是呈現層中的 JavaScript 代碼從一開始就沒有進行測試。如果沒有對代碼進行單元測試,那么在升級或支持新瀏覽器后,組織可能需要花錢反復測試 Web 應用程序。本文將展示如何通過高效的 JavaScript 代碼單元測試降低測試成本。

  一個常見用例是登錄表單 JavaScript 驗證。考慮清單 1 中的表單。

  清單 1. 登錄表單

<FORM>
<table>
<tr>
<td>Username</td>
<td><input type="text" id="username"/></td>
<td><span id="usernameMessage"></span></td>
</tr>
<tr>
<td>Password</td>
<td><input type="password" id="password"/></td>
<td><span id="passwordMessage"></span></td>
</tr>
<tr>
<td><input type="button" onclick="new
appnamespace.ApplicationUtil().validateLoginForm()" value="Submit"/></td>
 </tr>
</table>
</FORM>

  這個表單很簡單,僅包含用戶名和密碼字段。單擊提交按鈕時,將通過 ApplicationUtil 執行一個特定的表單驗證。以下是負責驗證 HTML 表單的 JavaScript 對象。清單 2 顯示了 ApplicationUtil 對象的代碼。

  清單 2. 損壞的 ApplicationUtil 對象代碼

appnamespace = {};

appnamespace.ApplicationUtil = function() {};

appnamespace.ApplicationUtil.prototype.validateLoginForm = function(){
var error = true;
document.getElementById ("usernameMessage").innerText = "";
document.getElementById ("passwordMessage").innerText = "";

if (!document.getElementById ("username").value) {
document.getElementById ("usernameMessage").innerText =
"This field is required";
error = false;
}

if (!document.getElementById ("password").value) {
document.getElementById ("passwordMessage").innerText =
"This field is required";
error = false;
}

return error;
};

  在清單 2 中,ApplicationUtil 對象提供一個簡單驗證:用戶名和密碼字段都已填充。如果某個字段為空,就會顯示一條錯誤消息:This field is required

  上面的代碼能夠在 Internet Explorer 8 和 Safari 5.1 上工作,但無法在 Firefox 3.6 上工作,原因是 Firefox 不支持 innerText 屬性。通常,(上述代碼和其他類似 JavaScript 代碼中的)主要問題是不容易發現編寫的 JavaScript 代碼是不是跨瀏覽器兼容的。

  這個問題的一個解決方案是進行自動化單元測試,檢查代碼是不是跨瀏覽器兼容。

  JsTestDriver

  JsTestDriver library 是最好的 JavaScript 單元測試框架之一,它為 JavaScript 代碼提供了跨瀏覽器測試。圖 1 展示了 JsTestDriver 的架構。

  圖 1. JsTestDriver 架構

JsTestDriver 架構

  捕獲不同的瀏覽器之后,服務器會負責將 JavaScript 測試用例運行程序代碼加載到瀏覽器中。可以通過命令行捕獲瀏覽器,也可以通過將瀏覽器指向服務器 URL 來捕獲瀏覽器。一旦捕獲到瀏覽器,該瀏覽器就被稱為從屬瀏覽器。服務器可以加載 JavaScript 代碼,在每個瀏覽器上執行測試用例,然后將結果返回給客戶端。

  客戶端(命令行)需要以下兩個主要項目:

  1. JavaScript 文件,即源文件和測試文件
  2. 配置文件,用于組織源文件和測試文件的加載

  這個架構比較靈活,允許單個服務器從網絡中的其他機器捕獲任意數量的瀏覽器。例如,如果您的代碼在 Linux 上運行但您想針對另一個 Windows 機器上的 Microsoft Internet Explorer 運行您的測試用例,那么這個架構很有用。

  要使用 JsTestDriver 庫,請先下載最新版的 JsTestDriver 1.3.2

jsTestDriver 是開源項目 jsTestDriver 是 Apache 2.0 許可 下的一個開源項目,托管在 Google Code 上,后者是一個類似于 SourceForge 的項目存儲庫。只要使用 Open Source Initiative 批準的 許可,開發人員就能在這個存儲庫中創建和管理公共項目。

還有許多其他 JavaScript 單元測試工具,請參見下面的 參考資料 部分中的其他工具,比如 Dojo Objective Harness (DOH)。

  編寫單元測試代碼

  現在開始編寫 JavaScript 測試用例。為簡單起見,我將測試以下用例:

  • 用戶名和密碼字段均為空。
  • 用戶名為空,密碼不為空。
  • 用戶名不為空,密碼為空。

  清單 3 顯示了表示 TestCase 對象的 ApplicationUtilTest 對象的部分代碼。

  清單 3. ApplicationUtilTest 對象代碼的一部分

ApplicationUtilTest = TestCase ("ApplicationUtilTest");

ApplicationUtilTest.prototype.setUp = function () {
/*:DOC += <FORM action=""><table><tr><td>Username</td><td>
<input type="text" id="username"/></td><td><span id="usernameMessage">
</span></td></tr><tr><td>Password</td><td>
<input type="password" id="password"/></td><td><span id="passwordMessage"
></span></td></tr></table></FORM>*/
};

ApplicationUtilTest.prototype.testValidateLoginFormBothEmpty = function () {
var applicationUtil = new appnamespace.ApplicationUtil();

/* Simulate empty user name and password */
document.getElementById("username").value = "";
document.getElementById("password").value = "";
applicationUtil.validateLoginForm ();
assertEquals ("Username is not validated correctly!", "This field is required",
document.getElementById("usernameMessage").innerHTML);
assertEquals ("Password is not validated correctly!", "This field is required",
document.getElementById("passwordMessage").innerHTML);
};

  ApplicationUtilTest 對象通過 JsTestDriver TestCase 對象創建。如果您熟悉 JUnit 框架,那么您肯定熟悉 setUptestXXX 方法。setUp 方法用于初始化測試用例。對于本例,我使用該方法來聲明一個 HTML 片段,該片段將用于其他測試用例方法。

  DOC 注釋是一個 JsTestDriver 慣用語,可以用于輕松聲明一個 HTML 片段。

  在 testValidateLoginFormBothEmpty 方法中,創建了一個 ApplicationUtil 對象,并在測試用例方法中使用該對象。然后,代碼通過檢索用戶名和密碼的 DOM 元素并將它們的值設置為空值來模擬輸入空用戶名和密碼。可以調用 validateLoginForm 方法來執行實際表單驗證。最后,將調用 assertEquals 來確保 usernameMessagepasswordMessage span 元素中的消息是正確的,即:This field is required

  在 JsTestDriver 中,可以使用以下構件:

  • fail("msg"):表明測試一定會失敗,消息參數將顯示為一條錯誤消息。
  • assertTrue("msg", actual):斷定實際參數正確。否則,消息參數將顯示為一條錯誤消息。
  • assertFalse("msg", actual):斷定實際參數錯誤。否則,消息參數將顯示為一條錯誤消息。
  • assertSame("msg", expected, actual):斷定實際參數與預期參數相同。否則,消息參數將顯示為一條錯誤消息。
  • assertNotSame("msg", expected, actual):斷定實際參數與預期參數不相同。否則,消息參數將顯示為一條錯誤消息。
  • assertNull("msg", actual):斷定參數為空。否則,消息參數將顯示為一條錯誤消息。
  • assertNotNull("msg", actual):斷定實際參數不為空。否則,消息參數將顯示為一條錯誤消息。

  其他方法的代碼包含其他測試用例。清單 4 顯示了測試用例對象的完整代碼。

  清單 4. ApplicationUtil 對象完整代碼

ApplicationUtilTest = TestCase ("ApplicationUtilTest");

ApplicationUtilTest.prototype.setUp = function () {
/*:DOC += <FORM action=""><table><tr><td>Username</td><td>
<input type="text" id="username"/></td><td><span id="usernameMessage">
</span></td></tr><tr><td>Password</td><td>
<input type="password" id="password"/></td><td><span id="passwordMessage"
></span></td></tr></table></FORM>*/
};

ApplicationUtilTest.prototype.testValidateLoginFormBothEmpty = function () {
var applicationUtil = new appnamespace.ApplicationUtil();

/* Simulate empty user name and password */
document.getElementById ("username").value = "";
document.getElementById ("password").value = "";

applicationUtil.validateLoginForm();

assertEquals ("Username is not validated correctly!", "This field is required",
document.getElementById("usernameMessage").innerHTML);
assertEquals ("Password is not validated correctly!", "This field is required",
document.getElementById("passwordMessage").innerHTML);
};

ApplicationUtilTest.prototype.testValidateLoginFormWithEmptyUserName = function () {
var applicationUtil = new appnamespace.ApplicationUtil ();

/* Simulate empty user name and password */
document.getElementById("username").value = "";
document.getElementById("password").value = "anyPassword";

applicationUtil.validateLoginForm ();

assertEquals("Username is not validated correctly!",
"This field is required", document.getElementById("usernameMessage").innerHTML);
assertEquals("Password is not validated correctly!",
"", document.getElementById("passwordMessage").innerHTML);
};

ApplicationUtilTest.prototype.testValidateLoginFormWithEmptyPassword = function () {
var applicationUtil = new appnamespace.ApplicationUtil ();

document.getElementById("username").value = "anyUserName";
document.getElementById("password").value = "";

applicationUtil.validateLoginForm ();

assertEquals("Username is not validated correctly!",
"", document.getElementById ("usernameMessage").innerHTML);
assertEquals("Password is not validated correctly!",
"This field is required", document.getElementById("passwordMessage").
innerHTML);
};

  配置用于測試的不同瀏覽器

  測試 JavaScript 代碼的一個推薦實踐是將 JavaScript 源代碼和測試代碼放置在不同的文件夾中。對于圖 2 中的示例,我將 JavaScript 源文件夾命名為 “js-src",將 JavaScript 測試文件夾命名為 “js-test",它們都位于 “js" 父文件夾下。

  圖 2. JavaScript 測試文件夾結構

JavaScript 測試文件夾結構

  組織好源和測試文件夾后,必須提供配置文件。默認情況下,JsTestDriver 運行程序會尋找名為 jsTestDriver.conf 的配置文件。您可以從命令行更改配置文件名稱。清單 5 顯示了 JsTestDriver 配置文件的內容。

  清單 5. JsTestDriver 配置文件內容

server: http://localhost:9876
load:
- js-src/*.js
- js-test/*.js

  配置文件采用 YAML 格式。server 指令指定測試服務器的地址,load 指令指出了將哪些 JavaScript 文件加載到瀏覽器中以及加載它們的順序。

  現在,我們將在 IE、Firefox 和 Safari 瀏覽器上運行測試用例類。

  要運行測試用例類,需要啟動服務器。您可以使用以下命令行啟動 JsTestDriver 服務器:

java -jar JsTestDriver-1.3.2.jar --port 9876 --browser "[Firefox Path]",
"[IE Path]","[Safari Path]"

  使用這個命令行,服務器將以 Port 9876 啟動,捕獲您的機器上的 Firefox、IE 和 Safari 瀏覽器。

  啟動并捕獲瀏覽器后,可以通過以下命令行運行測試用例類:

java -jar JsTestDriver-1.3.2.jar --tests all

  運行命令后,您將看到第一輪結果,如清單 6 所示。

  清單 6. 第一輪結果

Total 9 tests (Passed: 6; Fails: 3; Errors: 0) (16.00 ms)
Firefox 3.6.18 Windows: Run 3 tests (Passed: 0; Fails: 3; Errors 0) (8.00 ms)
ApplicationUtilTest.testValidateLoginFormBothEmpty failed (3.00 ms):
AssertError: Username is not validated correctly! expected "This field
is required" but was "" Error ("Username is not validated correctly!
expected \"This field is required\" but was \"\"")@:0()@http://localhost
:9876/test/js-test/TestApplicationUtil.js:16

ApplicationUtilTest.testValidateLoginFormWithEmptyUserName failed (3.00 ms):
AssertError: Username is not validated correctly! expected "This field is
required" but was "" Error ("Username is not validated correctly! expected
\"This field is required\" but was \"\"")@:0()@http://localhost:9876/test
/js-test/TestApplicationUtil.js:29

ApplicationUtilTest.testValidateLoginFormWithEmptyPassword failed (2.00 ms):
AssertError: Password is not validated correctly! expected "This field is
required" but was "" Error ("Password is not validated correctly! expected
\"This field is required\" but was \"\"")@:0()@http://localhost:9876/test/
js-test/TestApplicationUtil.js:42

Safari 534.50 Windows: Run 3 tests (Passed: 3; Fails: 0; Errors 0) (2.00 ms)
Microsoft Internet Explorer 8.0 Windows: Run 3 tests (Passed: 3; Fails: 0;
Errors 0) (16.00 ms)
Tests failed: Tests failed. See log for details.

  注意,在清單 6 中,主要問題出在 Firefox 上。測試在 Internet Explorer 和 Safari 上均可順利運行。

  修復 JavaScript 代碼并重新運行測試用例

  我們來修復損壞的 JavaScript 代碼。我們將使用 innerHTML 替代 innerText。清單 7 顯示了修復后的 ApplicationUtil 對象代碼。

  清單 7. 修復后的 ApplicationUtil 對象代碼

appnamespace = {};

appnamespace.ApplicationUtil = function() {};

appnamespace.ApplicationUtil.prototype.validateLoginForm = function(){
var error = true;
document.getElementById("usernameMessage").innerHTML = "";
document.getElementById("passwordMessage").innerHTML = "";

if (!document.getElementById("username").value) {
document.getElementById("usernameMessage").innerHTML =
"This field is required";
error = false;
}

if (!document.getElementById("password") .value) {
document.getElementById("passwordMessage").innerHTML =
"This field is required";
error = false;
}

return error;
};

  使用 --test all 命令行參數重新運行測試用例對象。清單 8 顯示了第二輪運行結果。

  清單 8. 第二輪運行結果

Total 9 tests (Passed: 9; Fails: 0; Errors: 0) (9.00 ms)
Firefox 3.6.18 Windows: Run 3 tests (Passed: 3; Fails: 0; Errors 0) (9.00 ms)
Safari 534.50 Windows: Run 3 tests (Passed: 3; Fails: 0; Errors 0) (5.00 ms)
Microsoft Internet Explorer 8.0 Windows: Run 3 tests (Passed: 3; Fails: 0;
Errors 0)
(0.00 ms)

  如清單 8 所示,JavaScript 代碼現在在 IE、Firefox 和 Safari 上都能正常運行。

  結束語

  在本文中,您了解了如何使用一個最強大的 JavaScript 單元測試工具 (JsTestDriver) 在不同的瀏覽器上測試 JavaScript 應用程序代碼。還了解了什么是 JsTestDriver,如何配置它,以及如何在 Web 應用程序中使用它來確保應用程序的 JavaScript 代碼的質量和可靠性。

  下載

描述 名字 大小 下載方法
源代碼 simple.zip 3. 35MB HTTP

  關于下載方法的信息

  參考資料

  學習

  • 訪問 JUnit.org,了解如何使用 JUnit 測試框架。
  • 詳細了解 YAML,這是一個針對所有編程語言的人類友好的數據序列化標準。
  • 訪問 developerWorks Open source 專區獲得豐富的 how-to 信息、工具和項目更新以及最受歡迎的文章和教程,幫助您用開放源碼技術進行開發,并將它們與 IBM 產品結合使用。

  獲得產品和技術

  討論

  關于作者

  Hazem Saleh 有 6 年的 JEE 和開源技術經驗。他致力于 Apache MyFaces 方面的工作,是 MyFaces 項目許多組件的發起人,比如 Tomahawk CAPTCHA、Commons ExportActionListener、Media、PasswordStrength 等等。他是 GMaps4JSF(一個集成 Google Maps 和 Java ServerFaces 的集成項目)和 Mashups4JSF(集成 mashup 服務和 JavaServer Faces 的集成項目)的創始人,是《The Definitive Guide to Apache MyFaces and Facelets (Apress)》和其他許多 JSF 文章的作者,并且是 developerworks 的投稿人和 JSF 演講家。他現在是 IBM Egypt 的資深軟件工程師和 Web 2.0 技術的主題專家。

2
0

請先登錄 提交中…

標簽:JavaScript 單元測試

文章列表

淺談 HTML5 的 DOM Storage 機制

淺談 HTML5 的 DOM Storage 機制

來源: IBM 發布時間: 2011-11-24 16:52 閱讀: 5689 次 推薦: 2 原文鏈接 [收藏]
摘要:在開發 Web 應用時,開發者有時需要在本地存儲數據。當前瀏覽器支持 cookie 存儲,但其大小有 4KB 的限制。這對于一些 Ajax 應用來說是不夠的。更多的存儲空間需要瀏覽器本身或是插件的支持,如 Google Gears 和 Flash。不過開發人員需要通過檢測當前瀏覽器所支持的插件類型來使用對應的接口。 HTML5 中新引入了 DOM Storage 機制,通過使用鍵值對在客戶端保存數據,并且提供了更大容量的存儲空間。本文將詳細論述 HTML5 對本地存儲的支持,并對存儲事件綁定和數據存儲與 JSON 的結合使用進行討論。當一些老版本的瀏覽器不支持 DOM Storage 時,可以考慮用其他的技術如 Dojo 來實現相同的功能。本文也會對其進行簡單的介紹。

  HTML5 是下一代 HTML 標準,開始吸引越來越多人的目光。HTML5 的 DOM Storage 機制提供了一種方式讓程序員能夠把信息存儲到本地的計算機上,在需要時獲取。這點和 cookie 相似,區別是 DOM Storage 提供了更大容量的存儲空間。

  目前,在客戶端保存數據使用最多的是 cookie,但 cookie 的大小上限為 4KB,并且每次請求一個新頁面時 cookie 都會被發送過去。更多的存儲空間需要瀏覽器本身或是插件的支持,例如只在 Internet Explorer 上使用的 userData,需要額外安裝插件的 Google Gears 和 Flash。現在,HTML5 提供了一種標準的接口,使程序員可以簡單地訪問存儲的數據。由于鍵值對存儲在本地計算機上,在頁面加載完畢后可以通過 JavaScript 來操作這些數據。

  DOM Storage

  示例應用程序:用戶注冊

  本文使用的示例應用程序是一個簡單的用戶注冊過程,表單包含三個字段:name、age 和 address,我們將其拆分為兩個表單,分兩個頁面顯示。借助簡化了的數據模型,主要介紹如何利用 DOM Storage 功能處理表單跨頁問題。

  DOM Storage 兩個分類

  DOM Storage 分為 sessionStorage 和 localStorage。

  localStorage 對象和 sessionStorage 對象使用方法基本相同,它們的區別在于作用的范圍不同。sessionStorage 用來存儲與頁面相關的數據,它在頁面關閉后無法使用。而 localStorage 則持久存在,在頁面關閉后也可以使用。

  DOM Storage 接口

  下面是 DOM Storage 的接口定義:

interface Storage {
readonly attribute unsigned long length;
getter DOMString key(in unsigned long index);
getter any getItem(in DOMString key);
setter creator void setItem(in DOMString key, in any data);
deleter void removeItem(in DOMString key);
void clear();
};

  length:返回當前存儲在 Storage 對象中的鍵值對數量。

  key(index):返回列表中第 n 個鍵的名字。Index 從 0 開始。

  getItem(key):返回指定鍵對應的值。

  setItem(key, value):存入一個鍵值對。

  removeItem(key) :刪除指定的鍵值對。

  clear():刪除 Storage 對象中的所有鍵值對。

  通常,使用最多的方法是 getItem 和 setItem。

  以 sessionStorage 為例:

  存儲鍵值對:

  window.sessionStorage.setItem(“key1″, value1);

  通過鍵名來讀取值:

  var value1 = window.sessionStorage.getItem(“key1″);

  判斷瀏覽器是否支持 DOM Storage

  要使用 DOM Storage,首先,需要查看當前的瀏覽器是否支持。目前 Internet Explorer 8.0 以上,Firefox 3.5 以上,Chrome 4.0 以上都是支持 DOM Storage 的。

  如果瀏覽器不支持 DOM Storage,可以用其他的方法作為備選,本文還使用 Dojo 提供的 dojox.storage 模塊來實現相同的功能。

  清單 1. 查看瀏覽器是否支持 DOM Storage

 //sessionStorage
if(window.sessionStorage){
alert(“support sessionStorage”);
}else{
alert(“not support sessionStorage”);
// 不支持 sessionStorage
// 用 dojox.storage 來實現相同功能
}

//localStorage
if(window.localStorage){
alert(“support localStorage”);
}else{
alert(“not support localStorage”);
// 不支持 localStorage
// 用 dojox.storage 來實現相同功能
} 

  下面是用戶注冊的兩個表單。清單 2 中的第一個表單有兩個字段 name 和 age 需要用戶填寫內容。填寫完后點擊 Next 按鈕進入下一個頁面,此時函數 saveToStorage 會被調用,把在該頁面輸入的兩個字段的值保存到 sessionStorage 對象中。

  當從下一個頁面退回到本頁面時,使用 windows.onload 在加載頁面的時候將數據從 sessionStorage 中取出,并顯示在輸入框中,方便用戶修改。

  另外,給對象賦值除了用 setItem 方法外,也可以用 window.sessionStorage.key1 = value1。

  清單 2. 第一個表單頁面

 <script type="text/javascript">
// 當退回到第一個頁面時,從 sessionStorage 得到用戶之前輸入的值并顯示在頁面,方便修改
window.onload = function(){
if (window.sessionStorage) {
var name = window.sessionStorage.getItem("name");
var age = window.sessionStorage.getItem("age");
if (name != "" || name !=null){
document.getElementById("name").value = name;
}
if (age !="" || age !=null){
document.getElementById("age").value = age;
}
} else {
// 不支持 sessionStorage,用 Dojo 實現相同功能
}
};

// 將數據保存到 sessionStorage 對象中
function saveToStorage() {
//sessionStorage
if (window.sessionStorage) {
var name = document.getElementById("name").value;
var age = document.getElementById("age").value;
window.sessionStorage.setItem("name", name);
window.sessionStorage.setItem("age", age);
window.location.href = "form2.html";
} else {
// 不支持 sessionStorage,用 Dojo 實現相同功能
}
}
</script>

<form action="./form2.html">
<input type="text" name="name" id="name">
<input type="text" name="age" id="age">
<input type="button" value="Next" onclick="saveToStorage()"></input>
</form> 

  清單3 的第二個頁面有一個 address 字段。當用戶填寫完畢后,點擊 Submit 按鈕提交頁面,此時 addStorageValue 函數被調用,把保存在 sessionStorage 中的 name 和 age 值先賦給當前表單的兩個隱藏字段,隨后一起提交給下一個處理表單的頁面。最后調用 removeItem 函數刪除 name 和 age 值。

  如果用戶需要修改第一個頁面填寫的內容,可以點擊 Back 按鈕回到前一個頁面,用戶在前一個頁面已經填寫的內容會出現在 text 框中。

  清單 3. 第二個表單頁面

 <script type="text/javascript">
// 將保持在 sessionStorage 中的數據賦給表單的隱藏屬性
function addStorageValue() {
//sessionStorage
if (window.sessionStorage) {
var name = window.sessionStorage.getItem("name");
var age = window.sessionStorage.getItem("age");
document.getElementById("name").value = name;
document.getElementById("age").value = age;
window.sessionStorage.removeItem("name");
window.sessionStorage.removeItem("age");
} else {
// 不支持 sessionStorage,用 Dojo 實現相同功能
}
}

function backToPreviousForm() {
window.location.href = "form1.html";
}
</script>

<form action="./form3.php" method="post">
<input type="hidden" name="name" id="name">
<input type="hidden" name="age" id="age">
<input type="text" name="address" id="address">
<input type="button" value="Back" onclick="backToPreviousForm()">
<input type="submit" value="Submit" onclick="addStorageValue()"></input>
</form> 

  使用 DOM Storage 需要注意的幾點

  保存在 Storage 對象的數據類型

  當使用 DOM Storage 進行本地存儲時,任何數據格式在 Storage 對象中都以字符串類型保存,所以如果保存的數據不是字符串,在讀取的時候需要自己進行類型的轉換。這里我們使用 JSON 將對象序列化之后再存儲。

  JSON (JavaScript Object Notation) 是一種輕量級的數據交換格式。易于人閱讀和編寫,同時也易于機器解析和生成。目前,JSON 已經是 JavaScript 標準的一部分,主流的瀏覽器對 JSON 支持都非常完善。

  本文用到兩個相關的函數

  JSON.parse() 函數會把 JSON 對象轉換為原來的數據類型。

  JSON.stringify() 函數會把要保存的對象轉換成 JSON 對象保存。

  在清單 4 中,先把一個布爾型的數據存到 Storage 對象中,然后再取出,可以看到布爾類型的數據在取出的時候變為字符串。接下來換一種方式保存數據,先用 JSON.stringify 方法序列化數據,然后保存到 Storage 對象中,在取出的時候用 JSON.parse 方法進行反序列化,可以看到讀取出的數據還是布爾類型。

  另外,使用 JSON 保存一個字符串,通過 Chrome 的 Storage 工具,可以看到存入的字符串兩邊有雙引號,這個雙引號表示存入的是一個字符串。當用 JSON 表示一個簡單的字符串時,會在字符串兩邊加上雙引號。最后,該頁面加載后的輸出如下:

  string1 boolean2 string3

  清單 4. 使用 JSON 對 DOM Storage 的復雜數據進行處理

 // 生成一個 Boolean 類型的變量 data1
var data1 = new Boolean(true);

// 不用 JSON 處理數據
sessionStorage["key1"] = data1;
if(sessionStorage["key1"] == "true"){
// 從 Storage 對象讀取出來的數據 data1 變為 String 類型
document.write("string1 ");
}

// 使用 JSON 處理數據 data1
sessionStorage["key2"] = JSON.stringify(data1);
if(JSON.parse(sessionStorage["key2"]) == true){
// 從 Storage 對象讀取的數據 data1,用 JSON 將變量轉換為原來的 Boolean 類型
document.write("boolean2 ");
}

// 生成一個 String 類型的變量
var data2 = new String("true");
// 使用 JSON 處理數據,在 Storage 對象中保存的是 “string”
sessionStorage["key3"] = JSON.stringify(data2);
data2 = JSON.parse(sessionStorage["key3"]);
if(data2 == "true"){
// 變量轉換回來還是 String 類型
document.write("string3");
} 

  使用 Chrome 瀏覽器可以查看當前的 sessionStorage 和 localStorage 的鍵值對。在工具欄選擇工具到開發人員工具到Resources到Local Storage或Session Storage, 可以查看 key 和 value。

  圖 1. Chrome 瀏覽器的 Storage 工具欄

  綜上所述,我們可以如清單 5 一樣,在加載頁面的時候用 JSON 轉換數據類型,在離開頁面的時候將數據保存為 JSON 對象。這樣,保存在 Storage 中任何類型的數據在讀取的時候都可以轉換為原來的類型。

  清單 5. 使用 JSON 對 DOM Storage 的復雜數據進行處理

 <script type="text/javascript">
var value;
function loadValue() {
value1 = JSON.parse(window.sessionStorage.getItem(“key1”));
}
function saveValue() {
window.sessionStorage.setItem(“key1”) = JSON.stringify(value1);
}

window.addEventListener(“load”, loadValue. true);
window.addEventListener(“unload”, saveValue. true);
</script> 

  空間大小

  HTML5 的建議是每個網站提供給 Storage 的空間是 5MB,一般來說足夠存字符串。如果存入的數據太大,有些瀏覽器如 Chrome 會拋出 QUOTA_EXCEEDED_ERR 異常。所以雖然 DOM Storage 提供的空間比 cookie 要大很多,但在使用需要注意限制。

  圖 2. Chrome 瀏覽器拋出異常

  安全性

  一般不要在客戶端存儲敏感的信息,使用 localStorage、globalStorage 等在客戶端存儲的信息都非常容易暴露。應該在完成數據存儲后使用 clear 或者 removeItem 方法清除保存在 Storage 對象中的數據。

  存儲事件驅動

  如果想在存儲成功或修改存儲的值時執行一些操作,可以用 DOM Storage 接口提供的事件。可以使用如下方法注冊事件:

  window.addEventListener(storage, handleStorageEvent, false);

  存儲事件接口定義

interface StorageEvent : Event {
readonly attribute DOMString key;
readonly attribute any oldValue;
readonly attribute any newValue;
readonly attribute DOMString url;
readonly attribute Storage storageArea;
void initStorageEvent(
in DOMString typeArg, inboolean canBubbleArg, inboolean cancelableArg, in DOMString keyArg, in any oldValueArg, in any newValueArg, in DOMString urlArg, in Storage storageAreaArg);
};

  key:發生改變的鍵。

  oldValue:鍵改變之前的值。

  newValue:鍵改變之后的值。

  url:觸發存儲事件的頁面 url。

  在清單 6 中注冊完存儲事件后,當 sessionStorage 或者 localStorage 對象的值發生改變時,會觸發 handleStorageEvent 函數,在頁面顯示發生改變的鍵和改變之前與之后的值。

  清單 6. 添加存儲事件

// 顯示存儲事件的相關內容
function handleStorageEvent(e) {
document.write(key + e.key + oldValue + e.oldValue + newValue + e.newValue);
}
// 添加存儲事件監聽
window.addEventListener(storage, handleStorageEvent, false);

  使用 Dojo 實現之前用戶注冊的功能

  Dojo 是一個 JavaScript 實現的開源工具包,很大程度上屏蔽了瀏覽器之間的差異性。Dojo 擴展庫 (dojox) 是 Dojo 在其基本庫、核心庫和 Dijit 庫的基礎上提供的一個非常豐富的組件倉庫。本文用到的 dojox.storage 模塊能夠將數據保存在本地存儲中,實現和之前 DOM Storage 一樣的功能。

  由于一些老版本瀏覽器不支持 HTML5,我們還可以用 Dojo 來實現之前用戶注冊的功能。相對于 HTML5 的 DOM Storage 接口,Dojo 的 dojox.storage.Provider 接口提供的方法更多。這里我們列出幾個常用的方法。

  get(key, namespace):返回指定鍵對應的值。

  put(key, value, resultsHandler, namespace):存入一個鍵值對。

  remove(key, namespace):刪除指定的鍵值對。

  clear(namespace):刪除對象中的所有鍵值對。

  現在對第一個表單的 JavaScript 代碼做部分修改,并在頁面中引入 dojox.storage 模塊。這樣,程序在不支持 HTML5 的瀏覽器中能夠通過調用 Dojo 提供的方法正常運行。dojo.require(“dojox.storage") 表示引入 dojox.storage 功能模塊。然后通過 dojox.storage.manager.isInitialized() 查看 dojox.storage.manager 是否已經初始化,如果沒有的話,則需要等待其初始化完成之后,再進行存儲操作。

  清單 7. 經過修改后的第一個表單頁面的部分代碼

<script type="text/javascript">
dojo.require(“dojox.storage");
// 當退回到第一個頁面時,從 Storage 中得到用戶之前輸入的值并顯示在頁面,方便修改
// 這里先進行 dojox.storage.manager 的初始化
if(!dojox.storage.manager.isInitialized()){
dojo.connect(dojox.storage.manager, “loaded", saveAndLoad);
} else{
dojo.connect(dojo, “loaded", saveAndLoad);
}
function saveAndLoad(){
var name;
var age;
//sessionStorage
if (window.sessionStorage) {
name = window.sessionStorage.getItem(“name");
age = window.sessionStorage.getItem(“age");
if (name !=""|| name !=null){
document.getElementById(“name").value = name;
}
if (age !=""|| age !=null){
document.getElementById(“age").value = age;
}
}//dojox.storage
else
{
name = dojox.storage.get(“name");
age = dojox.storage.get(“age");
if (typeof name !="undefined" ){
document.getElementById(“name").value = name;
}
if (typeof age !="undefined" ){
document.getElementById(“age").value = age;
}
}
}
// 保存數據 function saveToStorage() {
var name = document.getElementById(“name").value;
var age = document.getElementById(“age").value;
//sessionStorage
if (window.sessionStorage) {
window.sessionStorage.setItem(“name", name);
window.sessionStorage.setItem(“age", age);
}//dojox.storage
else {
dojox.storage.put(“name", name);
dojox.storage.put(“age", age);
}
window.location.href="form2.html";
}
</script>

  清單 8. 經過修改后的第二個表單頁面的部分代碼

<script type="text/javascript">
dojo.require(“dojox.storage");
// 將保存在 sessionStorage 中的數據賦給表單的隱藏屬性
function addStorageValue() {
var name;
var age;
//sessionStorage
if (window.sessionStorage) {
name = window.sessionStorage.getItem(“name");
age = window.sessionStorage.getItem(“age");
document.getElementById(“name").value = name;
document.getElementById(“age").value = age;
window.sessionStorage.removeItem(“name");
window.sessionStorage.removeItem(“age");
}//dojox.storage
else {
name = dojox.storage.get(“name");
age = dojox.storage.get(“age");
document.getElementById(“name").value = name;
document.getElementById(“age").value = age;
dojox.storage.remove(“name");
dojox.storage.remove(“age");
}
}
function backToPreviousForm() {
window.location.href ="form1.html";
}
</script>

  結束語

  HTML5 中引入了 DOM Storage 機制用于存儲鍵值對,它的設計目的是提供大規模、易用的存儲功能,并且程序員可以通過調用標準的接口,簡單地訪問存儲的數據。目前,許多新版本的瀏覽器都支持 DOM Storage 功能。當老版本的瀏覽器不支持 HTML5 提供的 DOM Storage 機制時,可以考慮用 Dojo 來實現相同的功能。

2
0

請先登錄 提交中…

標簽:HTML5 Javascript

文章列表