博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
js 原型链(转)
阅读量:6423 次
发布时间:2019-06-23

本文共 8816 字,大约阅读时间需要 29 分钟。

1.1 理解原型链

JavaScript中几乎所有的东西都是对象,我们说数组是对象、DOM节点是对象、函数等也是对象,创建对象的Object也是对象(本身是构造函数),那么有一个重要的问题:对象从哪里来?

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

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

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

请看刺激的推导过程

前提 所有对象都由构造函数实例化而来,构造函数默认拥有与之相关联的原型对象

❒ ① 构造函数的原型对象也是对象,因此也有自己的构造函数
❒ ② 构造函数原型对象的构造函数,也有与之相关连的原型对象
❒ ③ 构造函数原型对象的原型对象(__proto__)也有自己的构造函数,其也拥有关联的原型对象
☞ 以上就形成了一种链式的访问结构,是为原型链

 

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

 

1.2 原型链结构

现在我们基本上把原型链的由来说清楚了,那么接下来通过具体的代码来分析原型链的整体结构。

示例代码

1  //01 自定义构造函数Person和Animal2 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构造函数的实例对象。理解以上几点后,我们就可以来看一下这几行代码对应的原型链结构图了。

原型链结构图说明:

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

 

验证原型链相关的代码

1  //[1] 验证p1、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);    //true12 console.log(Animal.prototype.constructor == Object);    //true13 console.log(Person.prototype.__proto__ == Object.prototype);    //true14 console.log(Animal.prototype.__proto__ == Object.prototype);    //true15 //[3] 验证Person和Animal的构造函数为Function16 //    验证Person和Animal构造函数的原型对象为空函数17 console.log(Person.constructor == Function);                //true18 console.log(Animal.constructor == Function);                //true19 console.log(Person.__proto__ == Function.prototype);        //true20 console.log(Animal.__proto__ == Function.prototype);        //true21 //[4] 验证Function.prototype的构造函数为Function22 console.log(Function.prototype.constructor == Function);    //true23 //[5] 验证Function和Object的构造函数为Function24 console.log(Function.constructor == Function);              //true25 console.log(Object.constructor == Function);                //true26 //[6] 验证Function.prototype的原型对象为Object.prototype而不是它自己27 console.log(Function.prototype.__proto__ == Object.prototype);//true28 //[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);     //true10 //[4] 验证Array.prototype的构造函数为Object,原型对象为Object.prototype11 delete Array.prototype.constructor;12 console.log(Array.prototype.constructor == Object);         //true13 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属性:wendingding13 p.showName();          //打印wendingding14 console.log(p.age);    //该属性原型链中并不存在,返回undefined15 p.showAge();           //该属性原型链中并不存在,报错

概念和访问原则说明

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

1.4 getPrototypeOf、isPrototypeOf和instanceof

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

1  //01 声明构造函数F2 function F() {}3 //02 使用构造函数F获取实例对象f4 var f = new F();5 //03 测试getPrototypeOf方法的使用6 console.log(Object.getPrototypeOf(f));  //打印的结果为一个对象,该对象是F相关联的原型对象7 console.log(Object.getPrototypeOf(f) === F.prototype);  //true8 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));    //true10 console.log(Object.prototype.isPrototypeOf(arr));   //true11 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); //true10 //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 重置了构造函数原型对象之后,因为Person15 console.log(p1 instanceof Person); //false16 //06 在Person构造函数重置了原型对象后重新创建实例化对象17 var p2 = new Person();18 console.log(p2 instanceof Person);   //true19 //==> 建议开发中,总是先设置构造函数的原型对象,之后在创建实例化对象

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

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的打印结果


  • Posted by 博客园· ~ 
  • 联系作者 简书· 新浪微博·
  • 原创文章,版权声明:自由转载-非商用-非衍生-保持署名 | 
你可能感兴趣的文章
Go语言的接口
查看>>
DBA_实践指南系列9_Oracle Erp R12应用补丁AutoPatch/AutoControl/AutoConfig(案例)
查看>>
Python语言学习 (六)1.2
查看>>
数据库设计三大范式
查看>>
ionic 字体的导入方法
查看>>
IP路由原理
查看>>
内部类详解
查看>>
洛谷P2726 阶乘 Factorials 数学
查看>>
类加载机制
查看>>
火柴棒等式(2008年NOIP全国联赛提高组)
查看>>
mongodb int型id 自增
查看>>
【转】关于大型网站技术演进的思考(十八)--网站静态化处理—反向代理(10)...
查看>>
绝大部分项目都是跟金融创新、互联网、移动互联网、社区经济、分享经济、互联网金融有关...
查看>>
Java中的4种代码块
查看>>
Ocelot(七)- 入门
查看>>
生成水杯热气
查看>>
程序员工作心法
查看>>
三个常用的PHP图表类库
查看>>
python中异常处理--raise的使用
查看>>
JavaScript中call,apply,bind方法的总结
查看>>