1. 程式人生 > >JS對象繼承與原型鏈

JS對象繼承與原型鏈

通過 直接 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對象繼承與原型鏈