javaScript系列 [04]-javaScript的原型链

[04]-javaScript的原型链

本文旨在花不多的篇幅讲清楚JavaScript语言中的原型链结构,不少朋友认为JavaScript中的原型链复杂难懂,其实否则,它们就像树上的一串猴子。java

1.1 理解原型链

JavaScript中几乎全部的东西都是对象,咱们说数组是对象、DOM节点是对象、函数等也是对象,建立对象的Object也是对象(自己是构造函数),那么有一个重要的问题:对象从哪里来?编程

这是一句废话,对象固然是经过必定方式建立出来的,根据实际类型不一样,对象的建立方式也千差万别。好比函数,咱们能够声明函数、使用Function构造函数建立等,好比数组,咱们能够直接经过var arr = [] 的方式建立空数组,也能够经过new Array的方式建立,好比普通的对象,咱们能够字面量建立、使用内置构造函数建立等等,花样太多了,以致于咱们学习的时候头昏脑涨、不得要领。数组

其实,归根结底全部“类型”的对象均可以认为是由相应构造函数建立出来的。 函数由Function构造函数实例化而来,普通对象由Object构造函数实例化而来,数组对象由Array构造函数实例化而来,至于Object | Array | Function等他们自己是函数,固然也有本身的构造函数。函数

理解了上面一点,那么接下来咱们在理解原型链的时候就会容易得多。学习

请看刺激的推导过程测试

前提 全部对象都由构造函数实例化而来,构造函数默认拥有与之相关联的原型对象
❒ ① 构造函数的原型对象也是对象,所以也有本身的构造函数
❒ ② 构造函数原型对象的构造函数,也有与之相关连的原型对象
❒ ③ 构造函数原型对象的原型对象(__proto__)也有本身的构造函数,其也拥有关联的原型对象
☞ 以上就造成了一种链式的访问结构,是为原型链this

 

其实构造函数也是对象,因此构造函数自己做为对象而言也有本身的构造函数,而这个构造函数也拥有与之相关联的原型对象,以此类推。那么,这就是另外一条原型链了。综上,咱们能够得出 原型链并不孤单的结论。
 

 

1.2 原型链结构

如今咱们基本上把原型链的由来讲清楚了,那么接下来经过具体的代码来分析原型链的总体结构。url

示例代码spa

1  //01 自定义构造函数Person和Animal
2 function Person() {} 3 function Animal() {} 4 //02 使用构造函数建立实例对象
5 var p1 = new Person(); 6 var p2 = new Person(); 7 var a = new Animal(); 8  //03 建立数组对象
9 var arrM = ["demoA","demoB"];

上面的代码很是简单,其中p1,p2和a它们是自定义构造函数的实例化对象。其次,咱们采用快捷方式建立了arrM数组,arrM实际上是内置构造函数Array的实例化对象。另外,Person和Animal这两个构造函数实际上是Function构造函数的实例对象。理解以上几点后,咱们就能够来看一下这几行代码对应的原型链结构图了。.net

原型链结构图说明:

① 由于复杂度关系,arrM对象的原型链结构图单独给出。
② Object.prototype是全部原型链的顶端,终点为null。

 

验证原型链相关的代码

 1  //[1] 验证p一、p2的原型对象为Person.prototype
 2 // 验证a 的原型对象为Animal.prototype
 3 console.log(p1.__proto__ == Person.prototype); //true
 4 console.log(p2.__proto__ == Person.prototype); //true
 5 console.log(a.__proto__ == Animal.prototype);  //true
 6 //[2] 获取Person.prototype|Animal.prototype构造函数
 7 // 验证Person.prototype|Animal.prototype原型对象为Object.prototype
 8 // 先删除实例成员,经过原型成员访问
 9 delete Person.prototype.constructor; 10 delete Animal.prototype.constructor; 11 console.log(Person.prototype.constructor == Object);    //true
12 console.log(Animal.prototype.constructor == Object);    //true
13 console.log(Person.prototype.__proto__ == Object.prototype);    //true
14 console.log(Animal.prototype.__proto__ == Object.prototype);    //true
15 //[3] 验证Person和Animal的构造函数为Function
16 // 验证Person和Animal构造函数的原型对象为空函数
17 console.log(Person.constructor == Function);                //true
18 console.log(Animal.constructor == Function);                //true
19 console.log(Person.__proto__ == Function.prototype);        //true
20 console.log(Animal.__proto__ == Function.prototype);        //true
21 //[4] 验证Function.prototype的构造函数为Function
22 console.log(Function.prototype.constructor == Function);    //true
23 //[5] 验证Function和Object的构造函数为Function
24 console.log(Function.constructor == Function);              //true
25 console.log(Object.constructor == Function);                //true
26 //[6] 验证Function.prototype的原型对象为Object.prototype而不是它本身
27 console.log(Function.prototype.__proto__ == Object.prototype);//true
28 //[7] 获取原型链的终点
29 console.log(Object.prototype.__proto__);                    //null

 

下面贴出数组对象的原型链结构图

验证数组对象原型链结构的代码示例

 1 //[1] 验证arrM的构造函数为Array
 2 //方法1
 3 console.log(arrM.constructor == Array);                 //true
 4 //方法2
 5 console.log(Object.prototype.toString.call(arrM));      //[object Array]
 6 //[2] 验证Array的构造函数为Function
 7 console.log(Array.constructor == Function);             //true
 8 //[3] 验证Array构造函数的原型对象为Function.prototype(空函数)
 9 console.log(Array.__proto__ == Function.prototype);     //true
10 //[4] 验证Array.prototype的构造函数为Object,原型对象为Object.prototype
11 delete Array.prototype.constructor; 12 console.log(Array.prototype.constructor == Object);         //true
13 console.log(Array.prototype.__proto__ == Object.prototype); //true

1.3 原型链的访问

原型链的访问规则

对象在访问属性或方法的时候,先检查本身的实例成员,若是存在那么就直接使用,若是不存在那么找到该对象的原型对象,查找原型对象上面是否有对应的成员,若是有那么就直接使用,若是没有那么就顺着原型链一直向上查找,若是找到则使用,找不到就重复该过程直到原型链的顶端,此时若是访问的是属性就返回undefined,方法则报错。

 1 function Person() {  2     this.name = "wendingding";  3 }  4 Person.prototype = {  5  constructor:Person,  6     name:"自来熟",  7     showName:function () {  8         this.name.lastIndexOf()  9  } 10 }; 11 var p = new Person(); 12 console.log(p.name);   //访问的是实例成员上面的name属性:wendingding
13 p.showName();          //打印wendingding
14 console.log(p.age);    //该属性原型链中并不存在,返回undefined
15 p.showAge();           //该属性原型链中并不存在,报错

概念和访问原则说明
❐ 实例成员:实例对象的属性或者是方法
❐ 原型成员:实例对象的原型对象的属性或者是方法
❐ 访问原则:就近原则

1.4 getPrototypeOf、isPrototypeOf和instanceof

Object.getPrototypeOf方法用于获取指定实例对象的原型对象,用法很是简单,只须要把实例对象做为参数传递,该方法就会把当前实例对象的原型对象返回给咱们。说白了,Object的这个静态方法其做用就是返回实例对象__proto__属性指向的原型prototype。

1  //01 声明构造函数F
2 function F() {} 3 //02 使用构造函数F获取实例对象f
4 var f = new F(); 5 //03 测试getPrototypeOf方法的使用
6 console.log(Object.getPrototypeOf(f));  //打印的结果为一个对象,该对象是F相关联的原型对象
7 console.log(Object.getPrototypeOf(f) === F.prototype);  //true
8 console.log(Object.getPrototypeOf(f) === f.__proto__);  //true

isPrototypeOf方法用于检查某对象是否在指定对象的原型链中,若是在,那么返回结果true,不然返回结果false。 

 1 //01 声明构造函数Person
 2 function Person() {}  3 //02 获取实例化对象p
 4 var p = new Person();  5 //03 测试isPrototypeOf的使用
 6 console.log(Person.prototype.isPrototypeOf(p)); //true
 7 console.log(Object.prototype.isPrototypeOf(p)); //true
 8 var arr = [1,2,3];  9 console.log(Array.prototype.isPrototypeOf(arr));    //true
10 console.log(Object.prototype.isPrototypeOf(arr));   //true
11 console.log(Object.prototype.isPrototypeOf(Person));//true

上述代码的原型链
① p–>Person.prototype –>Object.prototype –>null
② arr–>Array.prototype –>Object.prototype –>null
Object.prototype因处于全部原型链的顶端,故全部实例对象都继承于Object.prototype

instanceof运算符的做用跟isPrototypeOf方法相似,左操做数是待检测的实例对象,右操做数是用于检测的构造函数。若是右操做数指定构造函数的原型对象在左操做数实例对象的原型链上面,则返回结果true,不然返回结果false。

 1  //01 声明构造函数Person
 2 function Person() {}  3 //02 获取实例化对象p
 4 var p = new Person();  5 //03 测试isPrototypeOf的使用
 6 console.log(p instanceof Person);   //true
 7 console.log(p instanceof Object);   //true
 8 //04 Object构造函数的原型对象在Function这个实例对象的原型链中
 9 console.log(Function instanceof Object); //true
10 //05 Function构造函数的原型对象在Object这个实例对象的原型链中
11 console.log(Object instanceof Function); //true
注意:不要错误的认为instanceof检查的是 该实例对象是否从当前构造函数实例化建立的,其实它检查的是实例对象是否从当前指定构造函数的原型对象继承属性。

 

咱们能够经过下面给出的代码示例来进一步理解

 1  //01 声明构造函数Person
 2 function Person() {}  3 //02 获取实例化对象p
 4 var p1 = new Person();  5 //03 测试isPrototypeOf的使用
 6 console.log(p1 instanceof Person);   //true
 7 //04 替换Person默认的原型对象
 8 Person.prototype = {  9  constructor:Person, 10     showInfo:function () { 11         console.log("xxx"); 12  } 13 }; 14 //05 重置了构造函数原型对象以后,由于Person
15 console.log(p1 instanceof Person); //false
16 //06 在Person构造函数重置了原型对象后从新建立实例化对象
17 var p2 = new Person(); 18 console.log(p2 instanceof Person);   //true
19 //==> 建议开发中,老是先设置构造函数的原型对象,以后在建立实例化对象

贴出上面代码的原型链结构图(部分)

1.5 原型链相关的继承

继承是面向对象编程的基本特征之一,JavaScript支持面向对象编程,在实现继承的时候,有多种可行方案。接下来,咱们分别来认识下原型式继承、原型链继承以及在此基础上演变出来的组合继承

原型式继承基本写法

 1  //01 提供超类型|父类型构造函数
 2 function SuperClass() {}  3 //02 设置父类型的原型属性和原型方法
 4 SuperClass.prototype.info = 'SuperClass的信息';  5 SuperClass.prototype.showInfo = function () {  6     console.log(this.info);  7 };  8 //03 提供子类型
 9 function SubClass() {} 10 //04 设置继承(原型对象继承)
11 SubClass.prototype = SuperClass.prototype; 12 SubClass.prototype.constructor = SubClass; 13 var sub = new SubClass(); 14 console.log(sub.info);          //SuperClass的信息
15 sub.showInfo();                 //SuperClass的信息

贴出原型式继承结构图

 

提示 该方式能够继承超类型中的原型成员,可是存在和超类型原型对象共享的问题
 

 

原型链继承

实现思想

核心:把父类的实例对象设置为子类的原型对象 SubClass.prototype = new SuperClass();
问题:没法为父构造函数(SuperClass)传递参数

原型链继承基本写法

 1 //01 提供超类型|父类型
 2 function SuperClass() {  3     this.name = 'SuperClass的名称';  4     this.showName = function () {  5         console.log(this.name);  6  }  7 }  8 //02 设置父类型的原型属性和原型方法
 9 SuperClass.prototype.info = 'SuperClass的信息'; 10 SuperClass.prototype.showInfo = function () { 11     console.log(this.info); 12 }; 13 //03 提供子类型
14 function SubClass() {} 15 //04 设置继承(原型对象继承)
16 var sup = new SuperClass(); 17 SubClass.prototype = sup; 18 SubClass.prototype.constructor = SubClass; 19 var sub = new SubClass(); 20 console.log(sub.name);          //SuperClass的名称
21 console.log(sub.info);          //SuperClass的信息
22 sub.showInfo();                 //SuperClass的信息
23 sub.showName();                 //SuperClass的名称

贴出原型链继承结构图

组合继承

实现思想

① 使用原型链实现对原型属性和方法的继承
② 经过伪造(冒充)构造函数来实现对实例成员的继承,而且解决了父构造函数传参问题

组合继承基本写法

 1  //01 提供超类型|父类型
 2 function SuperClass(name) {  3     this.name = name;  4     this.showName = function () {  5         console.log(this.name);  6  }  7 }  8 //02 设置父类型的原型属性和原型方法
 9 SuperClass.prototype.info = 'SuperClass的信息'; 10 SuperClass.prototype.showInfo = function () { 11     console.log(this.info); 12 }; 13 //03 提供子类型
14 function SubClass(name) { 15     SuperClass.call(this,name); 16 } 17 //(1)获取父构造函数的实例成员 Person.call(this,name);
18 //(2)获取父构造函数的原型成员 SubClass.prototype = SuperClass.prototype;
19 SubClass.prototype = SuperClass.prototype; 20 SubClass.prototype.constructor = SubClass; 21 var sub_one = new SubClass("zhangsan"); 22 var sub_two = new SubClass("lisi"); 23 console.log(sub_one); 24 console.log(sub_two);

最后,贴出实例对象sub_one和sub_two的打印结果


相关文章
相关标签/搜索