【初窺javascript奧秘之面向對象】封裝與繼承


前言

javascript是一種基於對象的語言,意思是我們遇到的所有東西幾乎都是對象(函數也是)。

雖然class為js的保留字,但是他沒有任何實際意義(當他有實際意義了,我們很多代碼又要重新寫啦。。。),因為js並不是真正意義上的變相對象編程語言,所以class名存實亡。

js面向對象的路在何方?

本人才疏學淺所以文中會有一些錯誤請各位指出,因為是邊寫做實驗的,所以會有我的思考過程,行文可能會有點亂,請見諒。

最簡單的封裝

我們來看一個“面向對象”的例子:

//        var Person = new Object();
var Person = {
    name: '葉小釵',
    getName: function () {
        return this.name;
    }
};

var n = Person.getName();
alert(n);

這種寫法讓我們感覺一目了然,他讓我們感覺到了面向對象,但是他的缺點也很明顯:

按道理說name應該是私有變量,但我們這里可以無恥的訪問之,修改之。

另外就是想要用這種方法生產多個實例就是一個傳說(雖說可以實現)

雖說如此,他還是完成了js最簡單的封裝,具有面向對象的特性了。

但是這種方式其實並不適合用來定義程序相關流程,而應該用來保存數據,比如json相關,所以曾經這么做的同學以后建議不要這么做了。

隱藏私有變量

 1 var info = {
 2     name: '葉小釵',
 3     age: 105
 4 };
 5 
 6 var Person = function (cfg) {
 7     this.name = cfg.name || '';
 8     function getName() {
 9         return getName;
10     }
11 }
12 
13 var name = Person.getName();

以上代碼,我就是想說明會報錯罷了,因為在js中只有函數具有作用域,函數外部是無法訪問還是內部的變量的,函數內部卻可以訪問函數外部的東西,在函數內存在內部函數,且內部函數用到了外部函數變量便會延長作用域鏈,形成傳說中的閉包。

但是,在以上情況下,若我要訪問其中的方法有以下做法:

1 將getName做返回值返回

2 在外部定義全局變量,在函數內部將getName賦予他(最簡單window.getName = getName)

這兩種方法各有各的問題,方法一函數每次都會創建,用完便會銷毀;方法二永遠不會銷毀

由於以上原因便因此了今天的主角——構造函數。

new與構造函數

當一個函數被構建時,Function構造器產生的函數會被隱式賦予一個prototype屬性
prototype包含一個constructor對象,而constructor便是該新函數對象
constructor意義不大,但記住每個函數都會擁有prototype屬性

我們這里再來理一理這個prototype:

在js中函數都會有一個prototype屬性
該屬性指向另一對象(亦包含prototype),如此便形成了原型鏈,並最終指向object對象
在某個對象調用某個方法時,若是他沒有該方法就會向上查找直到object
新聲明函數的prototype指向object對象,其constructor指向函數對象本身
PS:所以根據構造函數創造的對象,其原型指向構造函數的原型

算了,通俗點來說便是:
prototype就是一模板,新創建的模板就是對他的一個拷貝(雖說本質是指針的指向。。。)

即構造函數擁有prototype屬性(指向一個對象,包括方法與屬性),這些都會被構造函數實例繼承。

 1 var Person = function (name) {
 2     this.name = name;
 3 };
 4 Person.prototype.getName = function () {
 5     return this.name;
 6 };
 7 
 8 var y = new Person('葉小釵');
 9 var n = y.getName();
10 var s = '';

以上便是一個簡單的應用,但我們還是來理一理:

 1 (function () {
 2     var Person = function (name) {
 3         this.name = name;
 4     };
 5     Person.prototype.getName = function () {
 6         return this.name;
 7     };
 8     var y = new Person('葉小釵');
 9     var s0 = Person.constructor.constructor === Function;
10     var s1 = Person.constructor === Function;
11     var s2 = Person.prototype.constructor === Person;
12     var s3 = Person.prototype === Object;
13     var s4 = Person.constructor === Object;
14     var s5 = y.constructor === Person;
15     
16     var s = '';
17 })()

如圖所示:

1 對象y的構造函數指向Person構造函數

2 構造函數Person原型中包含了指向自己的constructor屬性

3 構造函數的constructor屬性指向Function

4 Function的constructor最終指向object

我們拋開其他的不說,就說y對象:

我們根據new操作符實例化了y對象,按照之前說的,y對象便會對Person構造函數的prototype屬性做一次拷貝

而拷貝的屬性中暗藏一個constructor屬性,而該constructor屬性指向了Person,所以他們產生了聯系,我們這里可以做個小小的變動:

instanceof:如果obj對象是構造函數Fun的一個實例,則 obj instanceof Fun 返回 true
 1 (function () {
 2     var Person = function (name) {
 3         this.name = name;
 4     };
 5 
 6     Person.prototype = {
 7         getName: function () {
 8             return this.name;
 9         }
10     };
11     var y = new Person('葉小釵');
12     var s1 = Person.prototype.constructor === Person;
13     var s2 = y.constructor === Person;
14     var s3 = y instanceof Person;
15 
16     var s = '';
17 })()

我們看到,雖然我們強制取消了構造函數的constructor屬性,但是y還是屬於Person的實例,所以constructor真沒什么用了。。。

繼承

我們所謂繼承,便是子類擁有父類公開出來的方法罷了,到這里理解就變成了子函數擁有父函數所有prototype屬性,於是:

 1 (function () {
 2     var Person = function (name) {
 3         this.name = name;
 4     };
 5     //Person.prototype = {};//這句將影響十分具有constructor屬性
 6     Person.prototype.getName = function () {
 7         return this.name;
 8     };
 9 
10     var Student = function (name, sex, id) {
11         this.name = name || '無名氏';
12         this.sex = sex || '不明';
13         this.id = id || '未填'; //學號
14     };
15     //相當於將其prototype復制了一次,若是包含constructor的話將指向Person
16     Student.prototype = new Person();
17     Student.prototype.getId = function () {
18         return this.id;
19     }
20     var y = new Person();
21     var s = new Student;
22     var s1 = y instanceof Person;
23     var s2 = s instanceof Student;
24     var s3 = s instanceof Person;
25     var s4 = Student.prototype.constructor === Person;
26     var s5 = Student.constructor === Person;
27     var s6 = Student.constructor === Function;
28 
29     var s = '';
30 })();

根據此例子,他們彼此間的關系還是比較明顯了,我這里單獨說下prototype下的屬性:

一般情況下,prototype下用於定義函數,但並不是說他屬性沒用,若是給他定義屬性的話相當於每個子對象共享着一個數據!

一個重要的暗示便是:我們可以根據該數據(數組、對象字面量)來傳遞信息,比如我們會遇到這個場景的。

我們做了一個用於驗證表單輸入的控件,他對每個元素進行單獨驗證,但是當我們提交表單時我們會需要知道整個表單上哪些元素可以,哪些不可以,這個時候我們的prototype屬性可能就可以派上用場了!(個人理解,有誤請指出

又如,我前面寫過一篇文章:基於jQuery的下拉菜單菜單【02】,諸位上眼!!!,里面就用到了這個東西,具體便不展開了。

isPrototypeOf/hasOwnProperty

isrototypeOf該方法擁有判斷某個prototype對象和某個實例之間的關系:

 1 (function () {
 2     var Person = function (name) {
 3         this.name = name;
 4     };
 5     //Person.prototype = {};//這句將影響十分具有constructor屬性
 6     Person.prototype.getName = function () {
 7         return this.name;
 8     };
 9 
10     var Student = function (name, sex, id) {
11         this.name = name || '無名氏';
12         this.sex = sex || '不明';
13         this.id = id || '未填'; //學號
14     };
15     //相當於將其prototype復制了一次,若是包含constructor的話將指向Person
16     Student.prototype = new Person();
17     Student.prototype.getId = function () {
18         return this.id;
19     }
20     var y = new Person();
21     var s = new Student;
22 
23     var s1 = Student.prototype.isPrototypeOf(s);
24     var s2 = Student.prototype.isPrototypeOf(y);
25     var s3 = Person.prototype.isPrototypeOf(y);
26     var s4 = Person.prototype.isPrototypeOf(Student);
27     var s5 = Person.prototype.isPrototypeOf(Student.prototype);
28 
29 
30     var s = '';
31 })();

說白了就是看某個實例是否是屬於自己,這里隨便提一個問題大家看看:

    var s6 = Function.prototype.isPrototypeOf(Person);
    var s7 = Function.prototype.isPrototypeOf(Student);

hasOwnPrototype用來判斷一個屬性到底是本地屬性還是繼承自prototype,這里就不舉例了

語法糖

關於以上繼承的寫法也許不太“面向對象”,所以我們提供看這個語法糖:

 1 (function () {
 2     Function.prototype.method = function (name, func) {
 3         this.prototype[name] = func;
 4         return this;
 5     };
 6     Function.method('inherits', function (Parent) {
 7         this.prototype = new Parent();
 8         return this;
 9     });
10 
11     var Person = function (name) {
12         this.name = name;
13     };
14 
15     Person.method('getName', function () {
16         return this.name;
17     });
18 
19     var Student = function (name, sex, id) {
20         this.name = name || '無名氏';
21         this.sex = sex || '不明';
22         this.id = id || '未填'; //學號
23     };
24 
25     Student.inherits(Person);
26     Student.method('getId', function () {
27         return this.id;
28     });
29 
30     var y = new Person();
31     var s = new Student;
32 
33     var s = '';
34 })();

 

結語

小弟自知對面向對象相關了解不夠透徹,今天便寫到這里吧,希望某天能帶來面向對象的作品。

如果你覺得這篇文章還不錯,請幫忙點擊一下推薦,謝謝!

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM