一,function
從一開始接觸到js就感覺好靈活,每個人的寫法都不一樣,比如一個function就有N種寫法
如:function showMsg(){},var showMsg=function(){},showMsg=function(){}
似乎沒有什麼區別,都是一樣的嘛,真的是一樣的嗎,大家看看下面的例子
複製代碼 代碼如下:
///-----------------------------------------------------------------------------------------
-------
//函數定義:命名函數(聲明式),匿名函數(引用式)
//聲明式,定義代碼先於函數執行代碼被解析
function t1(){
dwn("t1");
}
t1();
function t1(){
dwn("new t1");
}
t1();
//引用式,在函數運行中進行動態解析
var t1=function(){
dwn("new new t1");
}
t1();
var t1=function(){
dwn("new new new t1");
}
t1();
//以上輸出:new t1,new t1,new new t1,new new new t1
可能想著應該是輸出t1,new t1,new newt1,new new new t1,結果卻並不是這樣,應該理解這句話:聲明
式,定義代碼先於函數執行代碼被解析
如果深入一步,應該說是scope鏈問題,實際上前面兩個方法等價於window.t1,可以理解為t1是window的
一個公有屬性,被賦了兩次值,以最後一次賦值為最終值
而後面兩個方法,可以理解為是t1是個變數,第四個方法的var去掉之後的結果仍然不會改變
然而,當第四個方法改成function t1(){}這樣的聲明式時,結果變成了new new new t1,new new new
t1,new new t1,new new t1
前面兩個按照我的理解可以很好的理解為什麼是這個答案,第三個也可以理解,但是最後一個輸出讓我比
較糾結,希望有高手出現解答一下
另外匿名函數還有(function(){...})()這樣的寫法,最後一個括弧用於參數輸入
還有var t1=new function(){..}這樣的聲明,實際上t1已經是一個對象了
例:
複製代碼 代碼如下:
var t2 = new function()
{
var temp = 100; //私人成員
this.temp = 200; //公有成員,這兩個概念會在第三點以後展開說明
return temp + this.temp;
}
alert(typeof(t2)); //object
alert(t2.constructor()); //300
除此之外,還有使用系統內建函數對象來構建一個函數,例:
var t3 = new Function('var temp = 100; this.temp = 200; return temp + this.temp;'); //這個位置加不加new結果都一樣,WHY
alert(typeof(t3)); //function
alert(t3()); //300
二,建立對象
首先我們理解一下物件導向編程(Object-Oriented Programming,OOP),使用OOP技術,常常要使用許多
代碼模組,每個模組都提供特定的功能,每個模組都是孤立的,甚至與其它模組完全獨立
。這種模組化編程方法提供了非常大的多樣性,大大增加了代碼的重用機會。可以舉例進一步說明這個問
題,假定電腦上的一個高效能應用程式是一輛一流賽車。如果使用傳統的編程技巧,這輛賽車就是
一個單元。如果要改進該車,就必須替換整個單元,把它送回廠商,讓汽車專家升級它,或者購買一個新
車。如果使用OOP技術,就只需從廠商處購買新的引擎,自己按照說明替換它,而不必用鋼鋸切割車體。
不過大部分的論點是,javascript並不是直接的物件導向的語言,但是通過類比可以做到很多物件導向語
言才能做到的事,如繼承,多態,封裝,javascript都能幹(沒有做不到,只是想不到)
複製代碼 代碼如下:
///-----------------------------------------------------------------------------------------
-------
//以下三種構造對象的方法
//new Object,執行個體化一個Object
var a=new Object();
a.x=1,a.y=2;
//對象直接量
var b={x:1,y:2};
//定義類型
function Point(x,y){ //類似於C#中的類
this.x=x;
this.y=y;
}
var p=new Point(1,2); //執行個體化類
第一種方法通過構造基本對象直接添加屬性的方法來實現,第二種和第一種差不多,可以看成是第一種方
法的快捷標記法
第三種方法中,可以以”類“為基礎,創造多個類型相同的對象
三,對象屬性的封裝(公有和私人)
以例子來說明
function List(){
var m_elements=[]; //私人成員,在對象外無法訪問,如果此處無var聲明,則m_elements將變成全域變
量,這樣外部是可以直接存取到的,如alert(m_elements[0])
複製代碼 代碼如下:
m_elements=Array.apply(m_elements,arguments);
//此處類比getter,使用時alist.length;
//等價於getName()方式:this.length=function(){return m_elements.length;},使用時
alist.length();
//公有屬性,可以通過"."運算子或下標來訪問
this.length={
valueOf:function(){
return m_elements.length;
},
toString:function(){
return m_elements.length;
}
}
//公有方法,此方法使用得alert(alist)相當於alert(alist.toString())
this.toString=function(){
return m_elements.toString();
}
//公有方法
this.add=function(){
m_elements.push.apply(m_elements,arguments);
}
//私人方法如下形式,這裡涉及到了閉包的概念,接下來繼續說明
//var add=function()或function add()
//{
//m_elements.push.apply(m_elements,arguments);
//}
}
var alist=new List(1,2,3);
dwn(alist); //=alert(alist.toString()),輸出1,2,3
dwn(alist.length); //輸出3
alist.add(4,5,6);
dwn(alist); //輸出1,2,3,4,5,6
dwn(alist.length); //輸出6
四,屬性和方法的類型
javascript裡,對象的屬性和方法支援4種不同的類型:private property(私人屬性),dynamic public
property(動態公有屬性),static public property/prototype property(靜態公有屬性或原型屬性),
static property(靜態屬性或類屬性)。私人屬性對外界完全不具備訪問性,可以通過內部的getter和
setter(都是類比);動態公有屬性外界可以訪問,每個對象執行個體持有一個副本,不會相互影響;原型
屬性每個對象執行個體共用唯一副本;類屬性不作為執行個體的屬性,只作為類的屬性。
以下是例子:
複製代碼 代碼如下:
///-----------------------------------------------------------------------------------------
-------
//動態公有類型,靜態公有類型(原型屬性)
function myClass(){
var p=100; //private property
this.x=10; //dynamic public property
}
myClass.prototype.y=20; //static public property or prototype property,動態為myClass的原型添
加了屬性,將作用於所有執行個體化了的對象,注意這裡用到了prototype,這是一個非常有用的東東
//要想成為進階javascript階段,prototype和閉包必須得理解和適當應用
myClass.z=30; //static property
var a=new myClass();
dwn(a.p) //undefined
dwn(a.x) //10
dwn(a.y) //20
a.x=20;
a.y=40;
dwn(a.x); //20
dwn(a.y); //40
delete(a.x); //刪除對象a的屬性x
delete(a.y); //刪除對象a的屬性y
dwn(a.x); //undefined
dwn(a.y); //20 靜態公有屬性y被刪除後還原為原型屬性y
dwn(a.z); //undefined 類屬性無法通過對象訪問
dwn(myClass.z);
五,原型(prototype)
這裡只講部分,prototype和閉包都不是幾句話都能講清楚的,如果這裡可以給你一些啟蒙,則萬幸矣
習語”照貓畫虎“,這裡的貓就是原型,虎是類型,可以表示成:虎.prototype=某隻貓 or
虎.prototype=new 貓()
因為原型屬性每個對象執行個體共用唯一副本,所以當執行個體中的一個調整了一個原型屬性的值時,所有執行個體調
用這個屬性時都將發生變化,這點需要注意
以下是原型關係的類型鏈:
複製代碼 代碼如下:
function ClassA(){
}
ClassA.prototype=new Object();
function ClassB(){
}
ClassB.prototype=new ClassA();
function ClassC(){
}
ClassC.prototype=new ClassB();
var obj=new ClassC();
dwn(obj instanceof ClassC); //true
dwn(obj instanceof ClassB); //true
dwn(obj instanceof ClassA); //true
dwn(obj instanceof Object); //true
帶預設值的Point對象:
function Point2(x,y){
if (x) this.x=x;
if (y) this.y=y;
}
//設定Point2對象的x,y預設值為0
Point2.prototype.x=0;
Point2.prototype.y=0;
//p1是一個預設(0,0)的對象
var p1=new Point2(); //可以寫成var p1=new Point2也不會出錯,WHY
//p2賦值
var p2=new Point2(1,2);
dwn(p1.x+","+p1.y); //0,0
dwn(p2.x+","+p2.y); //1,2
delete對象的屬性後,原型屬性將回到初始化的狀態:
function ClassD(){
this.a=100;
this.b=200;
this.c=300
}
ClassD.prototype=new ClassD(); //將ClassD原有的屬性設為原型,包括其值
ClassD.prototype.reset=function(){ //將非原型屬性刪除
for (var each in this) {
delete this[each];
}
}
var d=new ClassD();
dwn(d.a); //100
d.a*=2;
d.b*=2;
d.c*=2;
dwn(d.a); //200
dwn(d.b); //400
dwn(d.c); //600
d.reset(); //刪掉非原型屬性,所有回來原型
dwn(d.a); //100
dwn(d.b); //200
dwn(d.c); //300
六,繼承
如果兩個類都是同一個執行個體的類型,那麼它們之間存在著某種關係,我們把同一個執行個體的類型之間的泛化
關係稱為繼承。C#和JAVA中都有這個,具體的理解就不說了。
在javascript中,並不直接從方法上支援繼承,但是就像前面說的,可以類比啊
方法可以歸納為四種:構造繼承法,原型繼承法,執行個體繼承法和拷貝繼承法。融會貫通之後,還有混合繼
續法,這是什麼法,就是前面四種挑幾種混著來~
以下例子來源於王者歸來,其中涉及到了apply,call和一些Array的用法,有興趣的可以自己在園子裡搜尋
一下
1,構造繼續法例子:
複製代碼 代碼如下:
//定義一個Collection類型
function Collection(size)
{
this.size = function(){return size}; //公有方法,可以被繼承
}
Collection.prototype.isEmpty = function(){ //靜態方法,不能被繼承
return this.size() == 0;
}
//定義一個ArrayList類型,它"繼承"Collection類型
function ArrayList()
{
var m_elements = []; //私人成員,不能被繼承
m_elements = Array.apply(m_elements, arguments);
//ArrayList類型繼承Collection
this.base = Collection;
this.base.call(this, m_elements.length);
this.add = function()
{
return m_elements.push.apply(m_elements, arguments);
}
this.toArray = function()
{
return m_elements;
}
}
ArrayList.prototype.toString = function()
{
return this.toArray().toString();
}
//定義一個SortedList類型,它繼承ArrayList類型
function SortedList()
{
//SortedList類型繼承ArrayList
this.base = ArrayList;
this.base.apply(this, arguments);
this.sort = function()
{
var arr = this.toArray();
arr.sort.apply(arr, arguments);
}
}
//構造一個ArrayList
var a = new ArrayList(1,2,3);
dwn(a);
dwn(a.size()); //a從Collection繼承了size()方法
dwn(a.isEmpty); //但是a沒有繼承到isEmpty()方法
//構造一個SortedList
var b = new SortedList(3,1,2);
b.add(4,0); //b 從ArrayList繼承了add()方法
dwn(b.toArray()); //b 從ArrayList繼承了toArray()方法
b.sort(); //b 自己實現的sort()方法
dwn(b.toArray());
dwn(b);
dwn(b.size()); //b從Collection繼承了size()方法
2,原型繼承法例子:
複製代碼 代碼如下:
//定義一個Point類型
function Point(dimension)
{
this.dimension = dimension;
}
//定義一個Point2D類型,"繼承"Point類型
function Point2D(x, y)
{
this.x = x;
this.y = y;
}
Point2D.prototype.distance = function()
{
return Math.sqrt(this.x * this.x + this.y * this.y);
}
Point2D.prototype = new Point(2); //Point2D繼承了Point
//定義一個Point3D類型,也繼承Point類型
function Point3D(x, y, z)
{
this.x = x;
this.y = y;
this.z = z;
}
Point3D.prototype = new Point(3); //Point3D也繼承了Point
//構造一個Point2D對象
var p1 = new Point2D(0,0);
//構造一個Point3D對象
var p2 = new Point3D(0,1,2);
dwn(p1.dimension);
dwn(p2.dimension);
dwn(p1 instanceof Point2D); //p1 是一個 Point2D
dwn(p1 instanceof Point); //p1 也是一個 Point
dwn(p2 instanceof Point); //p2 是一個Point
以上兩種方法是最常用的
3,執行個體繼承法例子:
在說此法例子之前,說說構造繼承法的局限,如下:
複製代碼 代碼如下:
function MyDate()
{
this.base = Date;
this.base.apply(this, arguments);
}
var date = new MyDate();
alert(date.toGMTString); //undefined,date並沒有繼承到Date類型,所以沒有toGMTString方法
核心對象的某些方法不能被構造繼承,原因是核心對象並不像我們自訂的一般對象那樣在建構函式裡進
行賦值或初始化操作
換成原型繼承法呢?,如下:
複製代碼 代碼如下:
function MyDate(){}
MyDate.prototype=new Date();
var date=new MyDate();
alert(date.toGMTString); //'[object]'不是日期對象,仍然沒有繼承到Date類型!
現在,換成執行個體繼承法:
複製代碼 代碼如下:
function MyDate()
{
var instance = new Date(); //instance是一個新建立的日期對象
instance.printDate = function(){
document.write("<p> "+instance.toLocaleString()+"</p> ");
} //對instance擴充printDate()方法
return instance; //將instance作為建構函式的傳回值返回
}
var myDate = new MyDate();
dwn(myDate.toGMTString()); //這回成功輸出了正確的時間字串,看來myDate已經是一個Date的執行個體
了,繼承成功
myDate.printDate(); //如果沒有return instance,將不能以下標訪問,因為是私人對象的方法
4,拷貝繼承法例子:
複製代碼 代碼如下:
Function.prototype.extends = function(obj)
{
for(var each in obj)
{
this.prototype[each] = obj[each];
//對對象的屬性進行一對一的複製,但是它又慢又容易引起問題
//所以這種“繼承”方式一般不推薦使用
}
}
var Point2D = function(){
//……
}
Point2D.extends(new Point())
{
//……
}
這種繼承法似乎是用得很少的。
5,混合繼承例子:
複製代碼 代碼如下:
function Point2D(x, y)
{
this.x = x;
this.y = y;
}
function ColorPoint2D(x, y, c)
{
Point2D.call(this, x, y); //這裡是構造繼承,調用了父類的建構函式
//從前面的例子看過來,這裡等價於
//this.base=Point2D;
//this.base.call(this,x,y);
this.color = c;
}
ColorPoint2D.prototype = new Point2D(); //這裡用了原型繼承,讓ColorPoint2D以Point2D對象為原型