前言
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 })();
結語
小弟自知對面向對象相關了解不夠透徹,今天便寫到這里吧,希望某天能帶來面向對象的作品。
如果你覺得這篇文章還不錯,請幫忙點擊一下推薦,謝謝!