JS對象繼承與原型鏈
阿新 • • 發佈:2019-03-13
通過 直接 eof ava 淺拷貝 home 構造器 存在 n)
1.以復制方式實現的繼承
1.1淺拷貝
基本類型的復制
1 var parent = { 2 lanage: "chinese" 3 } 4 5 var child = { 6 name: "xxx", 7 age: 12 8 } 9 10 function extend(parent, child) { 11 var child = child || {}; 12 for (const propertype in parent) { 13 child[propertype] = parent[propertype];14 } 15 } 16 17 extend(parent, child); 18 19 console.log(child);//{ name: ‘xxx‘, age: 12, lanage: ‘chinese‘ }
以上代碼中,通過一個extend()函數,將父對象parent的屬性遍歷賦給子對象child,從而實現繼承。
但是這種字面量復制的方式存在巨大的缺陷,當父對象有引用類型的屬性時,通過這麽復制的方式,就像上一節中的var b = a一樣,只會將a對象的引用賦給b對象,結果兩者是指向同一個對象內存的,繼承出來的子對象的屬性是同一個,顯然是不能滿足需求的,(基本類型之間畫等號是值的復制,引用類型之間畫等號是引用的復制)所以就需要另謀出路。
1.2.深拷貝
利用遞歸加類型判斷復制引用類型
1 var parent = { 2 lanage: "chinese", 3 address: { 4 home: "gansu", 5 office: "xian" 6 }, 7 schools: ["xiaoxue", "zhongxue", "daxue"] 8 } 9 10 var child = { 11 name: "xxx", 12 age: 12 13 } 14 15 function extendDeeply(parent, child) {16 var child = child || {}; 17 for (const propertype in parent) { 18 //首先判斷父對象的當前屬性是不是引用類型 19 if (typeof parent[propertype] === "object") { 20 //再判斷該引用類型是不是數組,是則給子對象初始化為空數組,否則初始化為空對象 21 child[propertype] = Array.isArray(parent[propertype]) ? [] : {}; 22 //遞歸調用自己,將父對象的屬性復制到子對象 23 extendDeeply(parent[propertype], child[propertype]); 24 } else { 25 //基本類型直接復制 26 child[propertype] = parent[propertype]; 27 } 28 } 29 } 30 31 extendDeeply(parent, child); 32 console.log(child); 33 child.schools[0] = "qinghua"; //改變子對象的屬性值 34 child.address.home = "tianjin"; 35 36 console.log(child.schools[0]); //qinghua 37 console.log(parent.schools[0]); //xiaoxue 38 console.log(child.address.home); //tianjin 39 console.log(parent.address.home); //gansu
可見此時子對象的改變並不會影響到父對象,父子徹底決裂。這便是深拷貝實現的繼承。
註意:for in 是可以遍歷數組的,結果為數組元素下標。
2.以構造函數實現繼承
1 function Parent() { 2 this.name = "name"; 3 this.age = 12; 4 } 5 6 function Child() { 7 Parent.call(this); 8 this.language = "chinese"; 9 } 10 11 var child = new Child(); 12 console.log(child);//{ name: ‘name‘, age: 12, language: ‘chinese‘ } 13 child.name = "swk"; 14 console.log(new Parent().name);//name
如上所示,通過在子類構造函數中調用父類構造函數,將父類屬性繼承到子類對象上,new出來的每一個子類對象都是獨立的實例,這種方式更符合面向對象的思想。
3.以原型方式實現繼承
1 var person = {name: "pname"}; 2 3 function myCreate(person) { 4 var ins; 5 function F() { }; 6 F.prototype = person; 7 ins = new F(); 8 return ins; 9 } 10 11 var c = new myCreate(person); 12 console.log(c.name); //pname 13 console.log(c); //{}
如上所示,將一個構造函數的原型替換為父對象,然後返回該構造函數的實例,如此一來創建的子對象就是F的實例,並且其原型為父對象,所以c.name可以被查找到,但子對象c本身是沒有屬性的。這種直接重寫prototype原型對象的弊端在於會斷掉原本的原型鏈,即沒有了繼承自Object的方法,和給原型對象prototype上添加屬性是不同的。
4.JS原型鏈
盜一波大神的經典圖
從該圖可以看出兩點
4.1從原型prototype層面:
- 不論是 new Foo()自定義構造函數,new Object()系統構造函數,還是對象字面量創建出來的對象,只要是對象,其原型最終都指向Object.prototype對象,自定義的多繞了一步,就像Java裏說的,Object.prototype是一切JS對象的根對象。
- 函數的原型都指向Function.prototype,而在JS中,函數也是對象的一種,所以符合第一條。
4.2從構造器constructor層面
- 首先對象的構造器肯定是指向他的構造函數,f1, f2指向Foo(), o1, o2指向Object()。
- 而構造函數Foo()和Object()的constructor屬性最終指向函數構造器Function()。
JS對象繼承與原型鏈