Javascript外掛封裝的基礎知識
引言
由於一些特殊原因所以,這次就講解下基礎的js外掛封裝。
既然需要封裝,那麼就需要一個封裝的容器,那這個容器是什麼?
1.什麼是物件
物件就是類似於 類的一個例項 比如同事是一個類,那我身邊的一個同事就是一個物件。
如果舉個簡單的例子:
var a =0;var b = “javascript”;var c = [1,2,3];var d = { key:11,value:22}
這些都是一個物件,但是光有物件還不行,還需要有構造。
2.什麼是建構函式
簡單說建構函式是類函式,函式名與類名完全相同,且無返回值。建構函式是類的一個特殊成員函式。
js建立構造的幾種方式:
1.工廠模式
考慮到在 ECMAScript 中無法建立類,開發人員就發明了一種函式,用函式來封裝以特定介面建立物件的細節,如下面的例子所示:
function createPerson(id,name){ var o = new Object(); o.id = id; o.name = name; o.sayName = function(){ alert(this.name); } return o; } var person1 = createPerson(1,27);
函式 createPerson()能夠根據接受的引數來構建一個包含所有必要資訊的 Person 物件。可以無數次地呼叫這個函式,而每次它都會返回一個包含三個屬性一個方法的物件。工廠模式雖然解決了建立\多個相似物件的問題,但卻沒有解決物件識別的問題(即怎樣知道一個物件的型別)。
主要好處就是可以消除物件間的耦合,通過使用工程方法而不是new關鍵字。將所有例項化的程式碼集中在一個位置防止程式碼重複。
工廠模式解決了重複例項化的問題 ,但還有一個問題,那就是識別問題,因為根本無法 搞清楚他們到底是哪個物件的例項。
2.建構函式模式
ECMAScript中的建構函式可用來建立特定型別的物件,像Array和Object這樣的原生建構函式,在執行時會自動出現在執行環境中。此外,也可以建立自定義的建構函式,從而定義自定義物件的屬性和方法。使用建構函式的方法,既解決了重複例項化的問題,又解決了物件識別的問題。例如,可以使用建構函式模式將前面的例子重寫如下:
function Person(name,age,job){ this.name = name; this.age = age; this.job = job; this.sayName = function(){ alert(this.name); } } var person1 = new Person("Nicholas", 29, "Software Engineer"); var person2 = new Person('Grey',27,'Doctor');
Person()中的程式碼除了與 createPerson()中相同的部分外,還存在以下不同之處:
沒有顯式地建立物件;
直接將屬性和方法賦給了 this 物件;
沒有 return 語句。
3.原型模式
我們建立的每個函式都有一個 prototype(原型)屬性,這個屬性是一個指標,指向一個物件,而這個物件的用途是包含可以由特定型別的所有例項共享的屬性和方法。
如果按字面意思來理解,那麼prototype就是通過呼叫建構函式而建立的那個物件例項的原型物件。使用原型物件的好處是可以讓所有物件例項共享它所包含的屬性和方法。換句話說,不必在建構函式中定義物件例項的資訊,而是可以將這些資訊直接新增到原型物件中。例如:
function Person(){ } Person.prototype.name = 'Nicholas'; Person.prototype.age = 29; Person.prototype.job = 'Software Engineer'; Person.prototype.sayName = function(){ alert(this.name); } var person1 = new Person(); person1.sayName();//"Nicholas" var person2 = new Person(); person2.sayName(); //"Nicholas" alert(person1.sayName == person2.sayName); //true
在此,我們將 sayName()方法和所有屬性直接新增到了 Person 的 prototype 屬性中,建構函式變成了空函式。
即使如此,也仍然可以通過呼叫建構函式來建立新物件,而且新物件還會具有相同的屬性和方法。但與建構函式模式不同的是,新物件的這些屬性和方法是由所有例項共享的。換句話說,person1 和 person2 訪問的都是同一組屬性和同一個 sayName()函式。要理解原型模式的工作原理,必須先理解 ECMAScript 中原型物件的性質。
3 繼承
既然要實現繼承,那麼首先我們得有一個父類,程式碼如下:
// 定義一個動物類 function Animal (name) { // 屬性 this.name = name || 'Animal'; // 例項方法 this.sleep = function(){ console.log(this.name + '正在睡覺!'); } } // 原型方法 Animal.prototype.eat = function(food) { console.log(this.name + '正在吃:' + food); };
1、原型鏈繼承
核心: 將父類的例項作為子類的原型
function Cat(){ } Cat.prototype = new Animal(); Cat.prototype.name = 'cat'; // Test Code var cat = new Cat(); console.log(cat.name); console.log(cat.eat('fish')); console.log(cat.sleep()); console.log(cat instanceof Animal); //true console.log(cat instanceof Cat); //true
特點:
- 非常純粹的繼承關係,例項是子類的例項,也是父類的例項
- 父類新增原型方法/原型屬性,子類都能訪問到
- 簡單,易於實現
缺點:
- 要想為子類新增屬性和方法,必須要在
new Animal()
這樣的語句之後執行,不能放到構造器中 - 無法實現多繼承
- 來自原型物件的所有屬性被所有例項共享(來自原型物件的引用屬性是所有例項共享的)(詳細請看附錄程式碼: 示例1)
- 建立子類例項時,無法向父類建構函式傳參
2、構造繼承
核心:使用父類的建構函式來增強子類例項,等於是複製父類的例項屬性給子類(沒用到原型)
function Cat(name){ Animal.call(this); this.name = name || 'Tom'; } // Test Code var cat = new Cat(); console.log(cat.name); console.log(cat.sleep()); console.log(cat instanceof Animal); // false console.log(cat instanceof Cat); // true
特點:
- 解決了1中,子類例項共享父類引用屬性的問題
- 建立子類例項時,可以向父類傳遞引數
- 可以實現多繼承(call多個父類物件)
缺點:
- 例項並不是父類的例項,只是子類的例項
- 只能繼承父類的例項屬性和方法,不能繼承原型屬性/方法
- 無法實現函式複用,每個子類都有父類例項函式的副本,影響效能
3、例項繼承
核心:為父類例項新增新特性,作為子類例項返回
function Cat(name){ var instance = new Animal(); instance.name = name || 'Tom'; return instance; } // Test Code var cat = new Cat(); console.log(cat.name); console.log(cat.sleep()); console.log(cat instanceof Animal); // true console.log(cat instanceof Cat); // false
特點:
- 不限制呼叫方式,不管是
new 子類()
還是子類()
,返回的物件具有相同的效果
缺點:
- 例項是父類的例項,不是子類的例項
- 不支援多繼承
4、拷貝繼承
function Cat(name){ var animal = new Animal(); for(var p in animal){ Cat.prototype[p] = animal[p]; } Cat.prototype.name = name || 'Tom'; } // Test Code var cat = new Cat(); console.log(cat.name); console.log(cat.sleep()); console.log(cat instanceof Animal); // false console.log(cat instanceof Cat); // true
特點:
- 支援多繼承
缺點:
- 效率較低,記憶體佔用高(因為要拷貝父類的屬性)
- 無法獲取父類不可列舉的方法(不可列舉方法,不能使用for in 訪問到)
5、組合繼承
核心:通過呼叫父類構造,繼承父類的屬性並保留傳參的優點,然後通過將父類例項作為子類原型,實現函式複用
function Cat(name){ Animal.call(this); this.name = name || 'Tom'; } Cat.prototype = new Animal(); Cat.prototype.constructor = Cat; // Test Code var cat = new Cat(); console.log(cat.name); console.log(cat.sleep()); console.log(cat instanceof Animal); // true console.log(cat instanceof Cat); // true
特點:
- 彌補了方式2的缺陷,可以繼承例項屬性/方法,也可以繼承原型屬性/方法
- 既是子類的例項,也是父類的例項
- 不存在引用屬性共享問題
- 可傳參
- 函式可複用
缺點:
- 呼叫了兩次父類建構函式,生成了兩份例項(子類例項將子類原型上的那份遮蔽了)
6、寄生組合繼承
核心:通過寄生方式,砍掉父類的例項屬性,這樣,在呼叫兩次父類的構造的時候,就不會初始化兩次例項方法/屬性,避免的組合繼承的缺點
function Cat(name){ Animal.call(this); this.name = name || 'Tom'; } (function(){ // 建立一個沒有例項方法的類 var Super = function(){}; Super.prototype = Animal.prototype; //將例項作為子類的原型 Cat.prototype = new Super(); })(); // Test Code var cat = new Cat(); console.log(cat.name); console.log(cat.sleep()); console.log(cat instanceof Animal); // true console.log(cat instanceof Cat); //true Cat.prototype.constructor = Cat; // 需要修復下建構函式
特點:
- 堪稱完美
缺點:
- 實現較為複雜