面嚮對象語言都有類的概念,可是ECMAScript沒有類的概念,因此它的對象與基於類的語言中的對象有所不一樣。app
var person = { name:'Zhangsan', age:20, gender: 'male', sayName: function(){ console.log(this.name); } }
var person = new Object(); person.name = 'Zhangsan'; person.age = 20; person.gender = 'male'; person.sayName = function(){ console.log(this.name); }
缺點:使用同一個接口建立不少對象,會產生大量的重複代碼函數
由於ECMAScript沒有類的概念,因此用函數來封裝建立對象的細節this
function createPerson(name,age,gender){ var o = new Object(); o.name = name; o.age = age; o.gender = gender; o.sayName = function(){ console.log(this.name); } return o; } var person1 = createPerson('Zhangsan',20,'male'); var person2 = createPerson('Lisi',24,'male'); console.log(person1 instanceof Object); //true
缺點:工廠模式解決了建立多個類似對象的重複代碼問題,但沒有解決對象類型識別的問題spa
function Person(name, age, gender){ this.name = name; this.age = age; this.gender = gender; this.sayName = function(){ console.log(this.name); } } var person1 = new Person('Zhangsan',20,'male'); var person2 = new Person('Lisi',24,'male'); console.log(person1 instanceof Person); //true console.log(person1 instanceof Object); //true console.log(person1.constructor == Person); //true console.log(person1.sayName == person2.sayName); //false
以這種方式調用構造函數會經歷如下四步:prototype
任何函數,只要經過new操做符來調用,那它就能夠做爲構造函數;若是不經過new操做符來調用,那它就是普通函數指針
var person = new Person('Zhangsan',20,'male'); person.sayName(); //'Zhangsan' Person('Lisi',24,'male'); window.sayName(); //'Lisi' var o = new Object(); Person.call(o,'Wangwu',22,'female'); o.sayName(); //'Wangwu'
缺點:每一個方法都要在每一個實例上從新建立一遍,上述例子中的sayName方法就會在每一個實例中從新建立一遍code
function Person(name, age, gender){ this.name = name; this.age = age; this.gender = gender; this.sayName = sayName; } function sayName(){ console.log(this.name); } var person1 = new Person('Zhangsan',20,'male'); var person2 = new Person('Lisi',24,'male');
上述代碼將sayName()函數的定義轉移到構造函數外部,這樣構造函數中的的sayName是一個指向函數的指針,所以person一、person2就共享了在全局做用域定義的同一個sayName()函數。這麼作雖然解決了問題但卻帶來了新問題:若是對象須要定義不少方法那麼就須要定義不少全局函數,那麼自定義的引用類型就絲毫沒有封裝性可言了對象
每一個函數都有一個prototype屬性,這個屬性是一個指針指向一個對象,而這個對象包含能夠由特定類型的全部實例共享的屬性和方法。使用原型對象的好處就是讓全部對象實例共享它所包含的屬性和方法blog
function Person(){ } Person.prototype.name = 'Nicholas'; Person.prototype.age = 29; Person.prototype.gender = 'male'; Person.prototype.sayName = function(){ console.log(this.name); }; var person1 = new Person(); person1.sayName(); //Nicholas var person2 = new Person(); person2.sayName(); //Nicholas console.log(Person.prototype.isPrototypeOf(person1));//true console.log(Person.prototype.isPrototypeOf(person2));//true console.log(Object.getPrototypeOf(person1) == Person.prototype);//true console.log(Object.getPrototypeOf(person1).name);//Nicholas
當代碼讀取某個對象的某個屬性時,先從對象實例自己開始搜索,若是找到給定名稱的屬性則返回該屬性的值;若是沒找到則搜索其指針指向的原型。當爲對象實例添加一個屬性時,則這個屬性就會屏蔽原型對象中保存的同名屬性繼承
person1.name = 'Zhangsan'; console.log(person1.name); //Zhangsan console.log(person1.hasOwnProperty('name')); //true console.log(person2.hasOwnProperty('name')); //false delete person1.name; console.log(person1.name); //Nicholas
function Person(){ } Person.prototype = { name:'Nicholas', age:29, gender:'male', sayName:function(){ console.log(this.name); } } var person = new Person(); console.log(person instanceof Object); //true console.log(person instanceof Person); //true console.log(person.constructor == Person); //false console.log(person.constructor == Object); //true Person.prototype.constructor = Person; console.log(person.constructor == Person); //true
使用原型對象賦值操做是會覆蓋原型對象中的constructor屬性,就會切斷原型與構造函數之間的關聯
function Person(){ } var person = new Person(); Person.prototype = { name:'Nicholas', age:29, gender:'male', sayName:function(){ console.log(this.name); } } person.sayName(); //error
缺點:因爲原型中的全部屬性和方法都是共享的,因此對於引用類型屬性問題就比較突出
function Person(){ } Person.prototype = { constructor:Person, name:'Nicholas', age:29, gender:'male', love:['swimming','running'], sayName:function(){ console.log(this.name); } } var person1 = new Person(); var person2 = new Person(); person1.love.push('playing games'); console.log(person1.love); //["swimming", "running", "playing games"] console.log(person2.love); //["swimming", "running", "playing games"] console.log(person1.love == person2.love); //true
function Person(name,age,gender){ this.name = name; this.age = age; this.gender = gender; this.love = ['swimming','running']; } Person.prototype = { constructor:Person, sayName:function(){ console.log(this.name); } } var person1 = new Person('Zhangsan',20,'male'); var person2 = new Person('Lisi',24,'male'); person1.love.push('playing games'); console.log(person1.love); //["swimming", "running", "playing games"] console.log(person2.love); //["swimming", "running"] console.log(person1.love == person2.love); //false
這種模式是使用最普遍、認同度最高的一種建立自定義類型的方法
function SpecialArray(name,age,gender){ var array = new Array(); array.push.apply(array,arguments); array.toPipedString = function(){ return this.join('|'); } return array; }
這種模式能夠用來爲原生引用類型作擴展,寄生構造函數模式返回的對象與構造函數或者與構造函數原型之間沒有關係,所以不能依賴instanceof操做符來肯定對象類型
function SuperType(){ this.property = true; } SuperType.prototype.getSuperValue = function (){ return this.property; } function SubType(){ this.subproperty = false; } SubType.prototype = new SuperType(); SubType.prototype.getSubValue = function (){ return this.subproperty; } var instance = new SubType(); console.log(instance.getSuperValue()); //true
原型鏈雖然很強大,能夠用它來實現繼承,但它也存在一些問題,其中,最主要的問題來自包含引用類型值的原型;第二個問題是建立子類型的實例時不能向超類的構造函數中傳遞參數。
function SuperType(){ this.colors = ['red','yellow','blue']; } function SubType(){ } SubType.prototype = new SuperType(); var instance1 = new SubType(); instance1.colors.push('green'); console.log(instance1.colors); //["red", "yellow", "blue", "green"] var instance2 = new SubType(); console.log(instance2.colors); //["red", "yellow", "blue", "green"]
借用構造函數用於解決原型鏈中包含引用類型值所帶來的問題
function SuperType(){ this.colors = ['red','yellow','blue']; } function SubType(){ SuperType.call(this); } var instance1 = new SubType(); instance1.colors.push('green'); console.log(instance1.colors); //["red", "yellow", "blue", "green"] var instance2 = new SubType(); console.log(instance2.colors); //["red", "yellow", "blue"]
問題:方法都在構造函數中定義,所以函數複用無從談起,並且在超類原型中定義的方法對子類而言也是不可見的,結果全部類型都只能使用構造函數模式
function SuperType(name){ this.name = name; this.colors = ["red", "yellow", "blue"]; } SuperType.prototype.sayName = function(){ console.log(this.name); } function SubType(name, age){ SuperType.call(this,name); this.age = age; } SubType.prototype = new SuperType(); SubType.prototype.constructor = SubType; SubType.prototype.sayAge = function(){ console.log(this.age); } var instance1 = new SubType('Zhangsan',20); instance1.colors.push('green'); console.log(instance1.colors); //["red", "yellow", "blue", "green"] instance1.sayName(); //Zhangsan instance1.sayAge(); //20 var instance2 = new SubType('Lisi', 24); console.log(instance2.colors); //["red", "yellow", "blue"] instance2.sayName(); //Lisi instance2.sayAge(); //24
組合繼承避免了原型鏈和借用構造函數的缺陷,融合了他們的優勢,成爲JavaScript中最經常使用的繼承模式
var person = { name : 'Zhangsan', colors : ["red", "yellow", "blue"] } var anotherPerson = Object.create(person); anotherPerson.name = "Lisi"; anotherPerson.colors.push("green"); var otherPerson = Object.create(person); otherPerson.name = "Wangwu"; otherPerson.colors.push("black"); console.log(person.colors); //["red", "yellow", "blue", "green", "black"] console.log(person.name); //Zhangsan
這種繼承方式在想讓一個對象與另外一個對象保持相似的狀況下是徹底能夠勝任的
var person = { name : 'Zhangsan', colors : ["red", "yellow", "blue"] } var anotherPerson = Object.create(person); anotherPerson.sayHi = function(){ console.log("hi"); } anotherPerson.sayHi();
使用寄生式繼承不能作到函數複用而下降效率
組合繼承最大的問題就在於不管什麼狀況下都會調用兩次超類型構造函數:一次是在建立子類型原型的時候,另外一次是在子類型構造函數內部。子類型最終回報寒潮類型對象的所有實例屬性,可是咱們不得不在調用子類型構造函數時重寫這些屬性(處理引用類型共用問題)
組合式繼承代碼以下:
function SuperType(name){ this.name = name; this.colors = ["red", "yellow", "blue"]; } SuperType.prototype.sayName = function(){ console.log(this.name); } function SubType(name, age){ SuperType.call(this,name); //第二次調用 this.age = age; } SubType.prototype = new SuperType(); //第一次調用 SubType.prototype.constructor = SubType; SubType.prototype.sayAge = function(){ console.log(this.age); }
寄生組合式繼承代碼以下:
function SuperType(name){ this.name = name; this.colors = ["red", "yellow", "blue"]; } SuperType.prototype.sayName = function(){ console.log(this.name); } function SubType(name, age){ SuperType.call(this,name); this.age = age; } SubType.prototype = Object.create(SuperType.prototype); SubType.prototype.constructor = SubType; SubType.prototype.sayAge = function(){ console.log(this.age); }