提及面向對象,大部分程序員首先會想到 類 。經過類能夠建立許多具備共同屬性以及方法的實例或者說對象。可是JavaScript並無類的概念,並且在JavaScript中幾乎一切皆對象,問題來了,JavaScript中如何面向對象?程序員
JavaScript中將對象定義爲:一組無序的 鍵值對的集合,屬性以及方法的名稱就是鍵,鍵的值能夠是任何類型(字符串,數字,函數……)編程
在JavaScript中,全部對象繼承自Object,全部對象繼承自Object,全部對象繼承自Object!函數
建立this
1 簡單建立對象:prototype
使用上面的方法雖然能夠建立對象,但缺點也很明顯,假如再次建立一個具備相同屬性以及方法的對象,還得把代碼複製修改一遍,會產生大量的重複代碼。指針
2 工廠模式對象
這種模式抽象了建立對象的具體過程blog
function createperson(name,age){ var o = new Object(); o.name = name; o.age = age; o.sayName = function(){ alert(this.name); }; return o; } var p1 = createperson('mncu',120); var p2 = createperson('linghuchong',120); p1.sayName(); //'mncu' p2.sayName(); //'linghuchong'
console.log(typeof p1); //object
p1 instanceof createperson // false ,沒法判斷類型
但這種方式有一個缺點:沒法判斷某個對象是什麼類型。繼承
3 構造函數模式ip
function Person(name,age){ // 按照慣例,構造函數的首字母要大寫 this.name = name; this.age = age; this.sayName = function(){ alert(this.name); }; } var p1 = new Person('mncu',120); // 必須使用new關鍵字建立對象 var p2 = new Person('linghuchong',120); p1.sayName(); //'mncu' alert(p1 instanceof Person); // true,可判斷類型
構造函數模式解決了工廠模式中不能判斷對象類型的問題。
在使用構造函數模式時要注意:
--必須使用new關鍵字建立對象! new 關鍵字至關於作了如下幾步:
1 建立一個新對象
2 將構造函數的做用域賦值給這個新對象(所以this就指向了這個新對象)
3 執行構造函數的代碼(爲這個新對象添加屬性)
4 返回新對象
--使用構造函數建立的對象都有一個constructor屬性,該屬性能夠標識對象類型,但通常咱們仍是經常使用instanceof來判斷對象類型, 由於constructor屬性僅返回構造函數類型。
p1.constructor === Person //true
p1.constructor === Object //false
p1 instanceof Object // true p1 instanceof Person //true
--構造函數實際上和普通函數並沒有多大區別,由於其不存在特殊的語法,任何函數,只要經過new調用,那麼他就能夠做爲構造函數。
var p3 = Person('dongfangbubai',120); // 不使用new時,函數的做用域會是全局做用域,this就會指向window對象。 p3.sayName() //報錯 sayName() // 'dongfangbubai'
--構造函數也存在問題,每一個方法都要在實例上建立一遍。也就是說p1和p2的sayName()方法雖然做用相同,但這兩個方法並非同一個函數。
p1.sayName == p2.sayName // false
--無new建立:
function Person(name,age){ //alert(this); if(!(this instanceof Person)){ return new Person(name,age); } this.name = name; this.age = age; this.sayName = function(){ alert(this.name); } } var p1 = Person('hah',20); console.log(p1.name)
4 原型模式
原型模式解決了構造函數模式中同功能的方法的代碼沒法重用的問題。
咱們建立的每一個函數都有一個名爲prototype的屬性,這個屬性是一個指針,指向一個對象,這個對象被稱爲原型對象。原型對象有一個名叫constructor的屬性,這個屬性是一個指針,指向構造函數。默認狀況下,全部函數的原型都是Object的實例。
使用原型模式建立對象:
function Person(){ Person.prototype.name = 'noOne'; Person.prototype.age = 'noAge'; Person.prototype.sayName = function(){ return this.name; }; } var p1 = new Person(); p1.name; // 'noOne'
var p2 = new Person()
p2.name; //'noOne'
在本例中,構造函數建立的對象p1的name屬性是如何展示的?
首先p1會查找自身有沒有name屬性,若是有的話,就返回自身的name屬性的值,若是沒有的話,則查找原型對象中有沒有name屬性,若原型對象中有name屬性,則返回其值,不然,就報錯。
p1.name = 'mncu'; p1.name; // 'mncu' p2.name; //'noOne'
咱們能夠經過hasOwnProperty()方法檢測一個屬性是存在於具體的對象中,仍是存在於該對象的原型對象中
p1.hasOwnProperty('name') //true p2.hasOwnProperty('name') //false
咱們也能夠經過 in 關鍵字來判斷一個屬性是否存在於具體的對象或者該對象的原型對象中
'name' in p1 // true 'name' in p2 // true
原型對象的簡寫格式:
function Person(){ } Person.prototype={ // 將原型對象重寫,但重寫後原型對象的constructor就不會指向Person了(指向Object)。因此咱們通常會添加:constructor:Person constructor:Person, name:'noOne', age:'noAge', sayName:function(){ alert(this.name); } };
原型模式存在的問題:
function Person(){ } Person.prototype={ constructor:Person, name:'noOne', age:'noAge', brothers : ['xiaoming'], sayName:function(){ alert(this.name); } }; var p1 = new Person(); var p2 = new Person(); p1.brothers.push('xiaohong'); console.log(p2.brothers) // ['xiaoming','xiaohong']
當咱們改變 值爲引用類型的對象的屬性 時,這個改變的結果會被其餘對象共享。
5 將構造函數模式和原型模式結合
構造函數模式的屬性沒毛病。缺點是:沒法共享方法
原型模式的方法沒毛病。缺點是:當原形對象的屬性的值爲引用類型時,對其進行修改會反映到全部實例中
那咱們就將二者的結合,對象的屬性使用構造函數模式建立,方法則使用原型模式建立
這種方式是最爲常見的一種面向對象編程模式
function Person(name,age){ this.name = name; this.age = age; } Person.prototype={ constructor:Person, sayName:function(){ alert(this.name); } }; var p1 = new Person('mncu',120); p1.name; // 'mncu'
繼承
1 原型鏈
JavaScript中引入了原型鏈的概念,具體思想: 子構造函數的原型對象初始化爲父構造函數的實例,孫構造函數的原型對象初始化爲子構造函數的實例…… ,這樣子對象就能夠經過原型鏈一級一級向上查找,訪問父構造函數中的屬性以及方法。
構建原型鏈:
原型鏈繼承的問題:
function SuperObject(){ this.colors = ['red','blue']; } function SubObject(){ } SubObject.prototype = new SuperObject(); var instance1 = new SubObject(); instance1.colors.push('yellow'); var instance2 = new SubObject(); console.log(instance2.colors) // ["red", "blue", "yellow"]
當咱們改變 值爲引用類型的原型對象的屬性 時,這個改變的結果會被全部子對象共享。這個缺點某些時候至關致命,因此咱們不多使用這種方法來繼承
2 借用構造函數繼承
function SuperObject(){ this.colors = ['red','blue']; this.sayBye= function(){ console.log('Bye') } } function SubObject(){ SuperObject.call(this); // 在子類中調用父類的構造方法,實際上子類和父類已經沒有上下級關係了 } var instance1 = new SubObject(); instance1.colors.push('yellow'); var instance2 = new SubObject(); console.log(instance2.colors); //['red','blue'] console.log(instance2 instanceof SuperObject); // false console.log(instance1.sayBye === instance2.sayBye) // false
這個方法雖然彌補了原型鏈的缺點,可是又暴露出了新的缺點:
1 子類和父類沒有上下級關係,instance2 instanceof SuperObject 結果是false
2 父類中的方法在每一個子類中都會生成一遍,父類中的方法沒有被複用。
3 組合繼承
組合繼承就是將原型鏈繼承和借用構造方法繼承組合,發揮二者之長。
function SuperObject(){ this.colors = ['red','blue']; } SuperObject.prototype.sayBye= function(){ console.log('bye') }; function SubObject(){ // 引用父類型的屬性,又調用了一次父函數 SuperObject.call(this); } // 繼承父類型的方法,調用了一次父函數 SubObject.prototype = new SuperObject(); var instance1 = new SubObject(); instance1.colors.push('yellow'); var instance2 = new SubObject(); console.log(instance2.colors); //['red','blue'] console.log(instance2 instanceof SuperObject); // true console.log(instance1.sayBye === instance2.sayBye); // true
4 寄生組合式繼承----道格拉斯方法
雖然組合繼承沒啥大缺點,可是愛搞事情的有強迫症的程序猿們以爲,組合繼承會調用兩次父類型函數(在上面的代碼中標註了),不夠完美。因而道格拉斯就提出了寄生組合繼承。
思路是構造一箇中間函數,將中間函數的prototype指向父函數的原型對象,將子函數的prototype指向中間函數,並將中間函數的constructor屬性指向子函數。
function inherits(Child, Parent) { var F = function () {}; F.prototype = Parent.prototype; Child.prototype = new F(); Child.prototype.constructor = Child; } function Student(props) { this.name = props.name || 'Unnamed'; } Student.prototype.hello = function () { alert('Hello, ' + this.name + '!'); } function PrimaryStudent(props) { Student.call(this, props); this.grade = props.grade || 1; } // 實現原型繼承鏈: inherits(PrimaryStudent, Student); // 綁定其餘方法到PrimaryStudent原型: PrimaryStudent.prototype.getGrade = function () { return this.grade; };
這個方法只調用了一次父構造函數,並所以避免了在父函數的原型對象上建立沒必要要的、多餘的屬性。
開發人員都表示:這種方法是最理想的繼承方式(我沒看出來,只以爲這是最燒腦的繼承方式,看來我離開發人員還有必定距離。。。)