JS中的原型鏈已是一個老生常談的問題,畢竟也是JS 這門語言的特點之一了。app
首先「萬物皆對象「,雖然這句話一直有爭議,可是有它的道理的,null類型這些的爭論這裏就不說了。
對象中有個屬性__proto__,被稱爲隱式原型,這個隱式原型指向構造改對象的構造函數的原型,這也保證了實例可以訪問在構造函數原型中定義的屬性和方法。這個實例多是如圖中的new Foo()出來的實例。函數
構造該對象的f1,f2構造函數是fuction Foo(),它的原型是Foo.prototype,那麼f1,f2就指向了構造該對象的構造函數的原型,也就是Foo.prototype,那麼構造函數Foo()它的proto指向哪裏了,仍是找它的構造函數,它的構造函數是Function(),那麼它的proto就指向了Fuction.prototype,沿着proto這條路最上就是Object.prototype,Object的proto就是null了。性能
剛剛說的對象有個proto屬性,方法也是對象,方法中除了有proto以外(這個proto指向構造該函數/對象的構造原型,也就是上一層了),還有prototype,這個屬性就是原型屬性,他是一個指針,指向一個對象,這個對象就叫原型對象,這裏放着包含全部實例共享的屬性和方法,這個原型對象裏面有一個屬性constructor,這個屬性也包含一個指針,指回了原構造函數。this
1.構造函數Foo()構造函數的原型屬性Foo.prototype指向了原型對象,在原型對象裏有共有的方法,全部構造函數聲明的實例(這裏是f1,f2)均可以共享這個方法。prototype
2.原型對象Foo.prototypeFoo.prototype保存着實例共享的方法,有一個指針constructor指回構造函數。3d
3.實例f1和f2是Foo這個對象的兩個實例,這兩個對象也有屬性__proto__,指向構造函數的原型對象,這樣子就能夠像上面1所說的訪問原型對象的全部方法。指針
4.構造函數Foo()除了是方法,也是對象,它也有__proto__屬性,指向誰呢?指向它的構造函數的原型對象。函數的構造函數不就是Function嘛,所以這裏的__proto__指向了Function.prototype。其實除了Foo(),Function(), Object()也是同樣的道理。原型對象也是對象,它的__proto__屬性,又指向誰呢?同理,指向它的構造函數的原型對象。這裏是Object.prototype.最後,Object.prototype的__proto__屬性指向null。code
5.對象有屬性__proto__,指向該對象的構造函數的原型對象。
方法除了有屬性__proto__,還有屬性prototype,prototype指向該方法的原型對象。對象
6.再看圖。blog
function Animal(name) { // 屬性 this.name = name || "Animal"; // 實例方法 this.sleep = function() { console.log(this.name + '正在睡覺!'); } } // 原型方法 Animal.prototype.eat = function(food) { console.log(this.name + '正在吃' + food); }; function Cat() { } Cat.prototype = new Animal(); Cat.prototype.name = 'cat'; var cat = new Cat(); console.log(cat.name); console.log(cat.eat('fish')); console.log(cat.sleep()); console.log(cat instanceof Animal); //true console.log(cat instanceof Cat); //true
**cat.__proto__ === Cat.prototype //true**
構造cat對象的構造函數是Cat(),它的原型是Cat.prototype,那麼隱式原型__proto__就指向構造該對象的構造函數的原型對象,那麼cat的__proto__就指向的是Cat.prototype。
**Cat.prototype.__proto__ === Animal.prototype //true**
原型對象也是對象,是對象就有__proto__,Animal的實例返回給了這個原型對象,那麼這個原型對象的隱式原型__proto__就指向的是構造該對象的構造函數的原型,咱們看看這個原型對象的構造函數是誰
那麼Animal()構造函數的原型對象就是Animal.prototype了。天然就有上面true的結果了。
這種方法的繼承的缺點:
注意原型上的方法/屬性是共享的
沒有用到原型,使用父類的構造函數來加強子類實例,等於直接是複製父類的實例屬性給子類。
經典繼承也叫作 「借用構造函數」 或 「僞造對象」 。其基本思想是:在子類型構造函數的內部調用超類型構造函數。函數只不過是在特定環境中執行代碼的對象,所以能夠經過使用apply() 和call() 方法也能夠在新建立的對象上執行構造函數。(JS高程)
function Cat(name){ Animal.call(this); this.name = name || 'Tom'; } // Test Code var cat = new Cat(); console.log(cat.name); console.log(cat.sleep()); console.log(cat instanceof Animal); // false console.log(cat instanceof Cat); // true
特色:
解決了1中,子類實例共享父類引用屬性的問題
建立子類實例時,能夠向父類傳遞參數(經過call的後面參數)
能夠實現多繼承(call多個父類對象)
缺點:
實例並非父類的實例,只是子類的實例
只能繼承父類的實例屬性和方法,不能繼承原型屬性/方法
沒法實現函數複用,每一個子類都有父類實例函數的副本,影響性能
經過調用父類構造,繼承父類的屬性並保留傳參的優勢,而後經過將父類實例做爲子類原型,實現函數複用
function Cat(name){ Animal.call(this); this.name = name || 'Tom'; } Cat.prototype = new Animal(); //組合繼承也是須要修復構造函數指向的。 Cat.prototype.constructor = Cat; // Test Code var cat = new Cat(); console.log(cat.name); console.log(cat.sleep()); console.log(cat instanceof Animal); // true console.log(cat instanceof Cat); // true
這種方式看似是原型繼承和構造繼承的組合,彌補了構造繼承只能繼承實例屬性/方法,不能繼承原型屬性/方法的缺點,也彌補了原型繼承引用屬性共享的問題,可向父類傳參,函數可複用,便是子類的實例,也是父類的實例。
缺點就是調用了兩次父類構造函數,生成了兩份實例(子類實例將子類原型上的那份屏蔽了)