前端開發之原型與原型鏈
前端開發之原型與原型鏈
原型和原型鏈估計做前端開發的人都聽說過,而且這個是一道很常見的面試題,但是想要真正弄清楚“什麼是原型”,”什麼是原型鏈“”,”他們又有什麼用“”,“適合在哪些實際場景中使用”這些問題還真不是一件很容易的事兒。今天我就寫下自己的理解,一方面加深理解,一方面作為備忘。
-
什麼是原型和原型鏈
1、原型分為顯式原型和隱式原型。顯示原型對應的是prototype屬性,隱式原型對應的是__proto__屬性。
2、所有物件(萬物皆物件)都有__proto__屬性,包括函式、示例等,只有函式才有prototype屬性。
3、prototype屬性值其實也是一個物件,型別為Object。它有一個自帶屬性constructor,這個constructor是一個指標,指向函式本身。比如 function Animal(){}。 Animal是一個函式指標,可以叫函式物件。Animal.prototype是一個object物件。Animal.prototype.constructor == Animal。
4、一般情況下,一個例項的__proto__屬性等於例項的型別的原型。這句話比較抽象。舉個例子,var animal = new Animal(),這裡 animal.__proto__ = Animal.prototype。通俗的說法可以為,animal的原型為Animal.prototype,但是這裡其實涉及到顯式和隱式原型的概念,很容易用混。
5、如果訪問一個物件的屬性,其查詢順序是,先查詢物件本身的屬性(可以理解為給this新增的屬性),然後查詢例項的__proto__(即原型)中的屬性,在然後查詢原型的__proto__裡的屬性,就這這樣一直查詢下去,直到找到。最終找到的是Object.prototype.__proto__ ,它等於 null。 其實這個過程就是原型鏈。通俗點就是以__proto__屬性為媒介,把物件相關的原型以鏈式方式串聯起來,以方便屬性和方法的訪問。prototype就是要串的原型。可以把__proto__理解為線,而prototype是珠子。
6、constructor屬性指向原型的構造方法。其實這個屬性在示例new的過程中是沒有作用的。在例項的instance of 方法也不是以它為依據的。後來我想到這個屬性有一個作用。比如,我們有一個例項,但是無法獲取到例項的類(匿名自執行函式),可以通過例項的constructor獲取到這個類,然後給類新增方法或屬性。不過這種使用方法好像也沒有什麼必要。只是為了加深理解。
//constructor的作用,匿名自執行函式場景。
var bydCar,ytCar ;
(function () {
function Car() {
}
bydCar = new Car();
ytCar= new Car();
})();
bydCar.constructor.prototype.name = 'BYD';
console.log("bydCar車的品牌:" + bydCar.name);
console.log("ytCar車的品牌:" + bydCar.name);
7、new方法都做了哪些事情。這個還是以var animal = new Animal();為例。
首先是 建立一個空物件。
然後把物件的__proto__屬性指向原型。
接下來,設定函式的this指向上邊的物件,並執行函式。
//new的過程。
var obj = {};
obj.__proto__ = Animal.prototype;
Animal.call(obj);//如果Animal中有return 就是把return結果作為new的物件,否則就是上邊建立的obj物件。
-
原型鏈和原型有什麼用,哪些場景用
理解了上邊的內容之後,你有沒有感覺原型鏈的方式很像面相物件程式設計裡邊用到得繼承的思想呢。下面就是把我用原型鏈實現的繼承示例。使用面相物件的思想編寫js程式碼,會使得程式碼的複用性更好,抽象程度更深,邏輯也更清晰。
/**原型鏈繼承,修改子類的原型為父類的例項,那麼cat.__proto__ == Cat.prototype == animal,原型鏈查詢
* 就是cat----->cat.__proto__-------->animal------>animal__proto__---->Animal.prototype--->Animal.prototype.__proto__------>Object.prototype----->Object.prototype.__proto__------->null*/
function Animal(name) {
this.name = name;
this.sleep = function () {
console.log(name + "正在睡覺");
}
this.getName = function () {
return this.name;
}
this.setName = function (newName) {
this.name = newName;
}
}
Animal.prototype.eat = function () {
console.log(this.name + "正在吃");
}
/*var animal = new Animal('小羊');
animal.__proto__
animal.eat();
animal.sleep();*/
//原型鏈繼承
//缺點1、無法實現多繼承。2、所有例項共享同一個父例項。 3、建立子類時無法給父類傳遞引數。
function Cat(catName) {
this.catName = catName;
}
Cat.prototype = new Animal('動物');
var cat = new Cat('小貓');
var cat1 = new Cat('小貓1');
cat.setName('1111');
var aaa = cat.getName();
//構造繼承 1、解決建立時無法給子類傳遞引數的問題。2、解決多繼承問題。
//缺點:1、例項並不是父例項 2、不能繼承父親的原型方法。3、每個子類中都有父類函式的副本。
function Dog(dogName) {
Animal.call(this);
this.dogName = dogName;
}
var dog = new Dog("小狗");
dog.sleep();
//dog.eat();//沒有此方法。
//例項繼承 此種方式個人覺得沒有任何意思,new出來的直接是父例項。只不過在構造方法中可以定義一些屬性和方法。
function Cow(cowName) {
var cow = new Animal(cowName);
cow.cowName = cowName;
cow.cowF = function () {
console.log('cowF');
}
return cow;
}
var cow = new Cow('牛');
if(cow instanceof Animal){
console.log('cow 其實不是Cow,而是一個animal');
}
cow.cowName;
//拷貝繼承 1支援多繼承 2 效率低
function Fish(name) {
var animal = new Animal(name);
for(var p in animal){
Fish.prototype[p] = animal[p];
}
}
var fish = new Fish("魚");
fish.eat();
fish.name;
//組合繼承 推薦,僅僅是呼叫了兩次父類的構造方法而已。
function Tiger(name) {
Animal.call(this);
}
Tiger.prototype = new Animal();
//Tiger.prototype.constructor = Tiger;
Tiger.prototype.tigerName = "老虎";
var tiger = new Tiger();
if(tiger instanceof Animal){
}
if(tiger instanceof Tiger){
}
//寄生組合繼承
function Shark(name) {
Animal.call(this,name)
}
(function () {
var Super = function () {
}
Super.prototype = Animal.prototype;
Shark.prototype = new Super();
})();
var shark = new Shark("鯊魚");
Shark.prototype.constructor = Shark;
Shark.prototype.bite = function () {
console.log('鯊魚咬合');
}
if(shark instanceof Shark){
debugger;
}
if(shark instanceof Animal){
debugger;
}
var aaa = typeof(shark);
debugger;
shark.bite();
//https://www.cnblogs.com/luvx/p/5833844.html
//https://www.cnblogs.com/humin/p/4556820.html