構造函數的寫法其實和普通函數並無區別,區別只是調用的時候,構造函數能夠經過new建立對象,爲了加以區分,咱們一般約定構造函數首字母大寫:javascript
function Person(name){ this.name=name this.say = function() { console.log(this.name) } }
構造函數都會有一個屬性prototype,能夠打印下Person.proptotype,打印出能夠發現是一個對象,這就是原型對象了。每一個原型對象上都會有一個屬性constructor,這個屬性指向的是構造函數。java
接下來經過new Person建立一個實例對象,打印這個對象,會發現他有一個__proto__屬性,這個屬性指向的是原型對象,但這個屬性不是一個標準屬性,只是實例對象和原型之間的一個橋樑,是實例對象內部找原型對象使用的,代碼中不使用,總結一下構造函數、原型、實例對象的三者關係:web
接下來,咱們建立兩個實例對象(new 構造函數出來的對象就是實例對象):svg
var per1 = new Person("張三") var per2 = new Person("李四") per1.say() per2.say()
new的意思其實就是建立一個空對象,再把構造函數中的屬性和方法,放到這個空對象中,而後返回這個對象,上述代碼可以正常執行,可是有個問題,函數屬於複雜數據類型,會開闢內存空間存放,上述那種寫法會致使每建立一個對象就多一個say函數的空間,這實際上是一種浪費,由於函數功能都同樣,咱們只須要一塊空間就行,因此一般方法咱們都定義在原型上:函數
Person.prototype.say = function() { console.log(this.name) }
定義在原型上,實例對象爲何可以訪問到呢?緣由在於咱們上文說過的__proto__屬性,執行的時候會先尋找per對象上有沒有say方法,發現沒有,就會去原型對象上找,而__proto__屬性就是找到原型的橋樑。this
如此一來,原型上可能有不少方法,例如:spa
Person.prototype.say1 = function() { console.log(this.name) } Person.prototype.say2 = function() { console.log(this.name) } Person.prototype.say = function() { console.log(this.name) } ...
每一個方法都這樣定義,可能比較繁瑣,咱們能夠簡化寫法,由於prototype自己就是對象,因此能夠寫成:prototype
Person.prototype = { say1: function () {}, say2: function () {}, say: function () { console.log(this.name) } }
看上去好像沒問題,可是執行 per.say3() 發現並無效果。緣由在於咱們前面說過,每一個原型對象都有一個constructor屬性指向構造函數,因此做出以下修改:code
Person.prototype = { constructor: Person, say1: function () {}, say2: function () {}, say: function () { console.log(this.name) } } var per = new Person("xz") per.say3()
如今執行上述代碼,可以正常運行。orm
繼承的意思,就是孩子可以使用父親的屬性和方法,同時又有本身獨特的屬性和方法。先定義一個Child類:
function Child() {}
可能有同窗會想,繼承直接讓Child.prototype=Person.prototype不就能直接使用父類的原型上的方法了嗎,是能夠用,但這有個問題,若是Child想有本身的方法,這會致使Person的原型會跟着同步改變,這顯然是不合理的。
咱們上文說過,實例對象的__proto__指向的是原型對象,那利用這點咱們可讓
Child.prototype = new Person("張三") var child1 = new Child() child1.say() // 張三 var child2 = new Child() child2.say() // 張三
能夠發現經過這種方法,雖然繼承了父類的方法,可是全部name都是「張三」,這不合理,因此屬性的繼承通常使用call方法實現繼承:
function Child(name) { Person.call(this, name) } Child.prototype = new Person("張三") var child1 = new Child("老王") child1.say() // 老王 var child2 = new Child("小吳") child2.say() // 小吳
js中的繼承通常是屬性使用call 方法使用父類實例對象這種組合繼承。Child也能夠有本身的屬性和方法:
function Child(name, sex) { Person.call(this, name) this.sex = sex } Child.prototype = new Person("張三") Child.prototype.say = function () { console.log(this.sex) } var child = new Child("老王", "male") child.say() //male
雖然是同名方法,但咱們能夠發現,child的say是調用的本身的say方法,由於js的機制是先在本身的對象上找,找不到纔會經過__proto__一層層往下找,建議打印child觀察一下結構。
同理,Child也能夠有本身的子類,他們之間也是經過__proto__繼承,這樣就造成了原型鏈。調用方法會沿着這個原型鏈一直找,這是咱們爲何能夠調用.toString()之類方法的緣由,由於String.prototype上定義了toString方法。
既然這樣,咱們能夠擴展內置對象,例如:
String.prototype.say = function () { console.log("hello") } var str = "xz" str.say() // hello
這個擴展方法固然沒有任何意義,只是演示用,一般處理Date對象時,能夠擴展format方法。