JS对象继承与原型链

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()。
posted @ 2019-03-12 23:03  jixhua  阅读(1018)  评论(0编辑  收藏  举报