当前位置: 移动技术网 > IT编程>开发语言>JavaScript > 原来你是这样的---原型和原型链

原来你是这样的---原型和原型链

2019年06月04日  | 移动技术网IT编程  | 我要评论

  把js的原型和原型链重新梳理了一遍,然后动手绘制了一张流程图,原型和原型链的秘密就藏在这张图上。绘制流程图的好处就是在绘制的过程中,既检验自己对这个知识点的掌握状况,同时在绘制过程中会对这个知识点印象更深刻,理解更透彻,建议每个感兴趣的小伙都来身体力行一次。

  为了更清晰的了解原型链的走向,先创建三个构造函数,建立多重继承关系,分别为person、chinaperson、provinceperson,它们之间的继承关系为:provinceperson 继承了 chinaperson, chinaperson继承了person。接下来,我们

  1. 先贴上这份多重继承的代码;
  2. 绘制流程图,根据流程图剖析原型链的秘密;
  3. 写些测试代码验证;

  先贴上代码,每个子构造函数会在继承父级的基础上,分别在构造函数里面和原型里面,自定义添加自己的属性和方法;另外在person原型上写上和构造函数里面同名的属性和方法,用来验证同名方法名时,构造函数里面的方法和原型上的方法哪个优先执行;在provinceperson上会重写从父级继承的方法,侧面粗略展示下面向对象的多态特性。

/**
 * javascript的多级继承和多态、原型和原型链的体现
 * */
//1.1 构造函数:人(person)
function person(name){
    this.name = name ? name : "人类";
    this.methodperson = function(){
        console.log("person构造函数里面的方法methodperson-->我的标签:" + this.name);
    }

    console.log("********person 构造函数 初始化********");
}
//给person的原型上添加属性和方法
person.prototype.age = 18;
person.prototype.run = function(){
    console.log("person原型方法run-->name: " + this.name + ", age: " + this.age + ", 欢快的run");
}
//问题:如果构造函数的原型里面的属性和方法,和构造函数的属性和方法同名,实例对象调用属性和方法时执行哪个?
//person原型的name和person构造函数的name,实例对象调用哪个?
person.prototype.name = "炎黄子孙";
person.prototype.methodperson = function(){
    console.log("person原型的methodperson方法-->标签:" + this.name + ", age: " + this.age);
}


//1.2 构造函数:中国人(chinaperson),继承于person
function chinaperson(name, skin){
    person.call(this, name); //调用父级person构造函数

    this.skin = skin ? skin : "黄色";
    this.methodchinaperson = function(){
        console.log("chinaperson构造函数里面的方法methodchinaperson-->肤色:" + this.skin + ", 标签: " + this.name);
    }

    console.log("********chinaperson 构造函数 初始化********");
}
//设置chinaperson原型指向person原型,相当于chinaperson继承person
chinaperson.prototype = object.create(person.prototype);
//设置新原型的构造函数指向自己
chinaperson.prototype.constructor = chinaperson;

//给chinaperson的原型自定义添加属性和方法
chinaperson.prototype.hero = "谭嗣同";
chinaperson.prototype.write = function(){
    console.log("chinaperson原型里面的方法write-->我自横刀向天笑,去留肝胆两昆仑!is who? " + this.hero + ", 标签: " + this.name + ", skin: " + this.skin);
}


//1.3 构造函数:provinceperson, 继承于chinaperson
function provinceperson(name, skin, count){
    chinaperson.call(this, name, skin);  //调用父级chinaperson构造函数

    this.count = count ? count : 8000;  
    this.methodprovinceperson = function(){
        console.log("provinceperson构造函数里面的方法methodprovinceperson-->数量:" + this.count + "万, 肤色:"+ this.skin + ", 标签:" + this.name);
    }
    //重写从父级继承下来的方法methodchinaperson
    this.methodchinaperson = function(){
        console.log("provinceperson构造函数里面重写父级方法methodchinaperson....");
    }
    console.log("********provinceperson 构造函数 初始化********")
}
//设置provinceperson原型指向chinaperson原型,相当于构造函数provinceperson继承于chinaperson
provinceperson.prototype = object.create(chinaperson.prototype);
//设置新原型的构造函数指向自己
provinceperson.prototype.constructor = provinceperson;
//给provinceperson的原型上自定义添加属性和方法
provinceperson.prototype.feature = "长沙臭豆腐";
provinceperson.prototype.eat = function(){
    console.log("provinceperson原型里面的方法eat-->标签:" + this.name + ", 地方小吃是:" + this.feature + ", hero: " + this.hero + ", skin: " + this.skin);
}
//重写从父级原型继承下来的方法
provinceperson.prototype.write = function(){
    console.log("provinceperson原型里面重写从父级原型继承的write方法-->。。。。^_^");
}

 

  结合以上代码,绘制构造函数原型链关系,如下图:

  

对上图进行说明,和原型链知识点进行归纳:

  • provinceperson、chinaperson、person三个是自定义构造函数,function、object两个是系统构造函数;
  • 原型链方向(寻找父级方向)为:provinceperson --> chinaperson --> person --> object --> null ;   function -->  object --> null ;
  • 上图中obj是构造函数provinceperson的实例对象;矩形代表构造函数,六边形代表构造函数的原型对象,红色虚线代表实例对象通过其私有原型属性__proto__寻找父级原型走向;
  • prototype是构造函数的属性,__proto__是构造函数的实例对象的属性
    • 实例对象的__proto__属性指向该对象的构造函数的prototype属性,即 实例对象.__proto__ = 构造函数.prototype ; 
    • __proto__是隐式原型,平常不建议直接使用,通常用object.getprototypeof(对象)来获取实例对象的原型;
    • 构造函数的prototype 和 实例对象的__proto__ 都是对象
  • 敲重点了:
    • 函数调用时,使用new关键字,叫构造函数,比如var obj = new provinceperson() 。这时provinceperson叫构造函数,是一个类模板;
    • 当函数不使用new关键字,直接调用时,就是一个普通函数,比如provinceperson()、object()、function() 等等,这样使用它们都叫普通函数;
    • 所有的普通函数都是构造函数function的实例对象,比如object、function作为普通函数调用时它们都是function的实例对象。
    • 这就是为什么函数既有prototype属性,也有__proto__属性,因为它们都有双重身份:
    • 第一重身份是它们有可能会使用new关键字,这时它们是构造函数,有prototype属性;
    • 第二重身份是它们不使用new关键字,直接调用,这时候它们都是构造函数function的实例对象,所以这时候它们有__proto__属性。
    • function作为一个特殊的存在,特殊之处在于 function.prototype = function.__proto__ ,即它作为构造函数的原型(prototype) 和 它作为普通函数的实例对象的原型(__proto__) 指向同一个对象;
  • 构造函数的原型的constructor属性指向构造函数,实例对象的constructor也指向构造函数,即 构造函数.prototype.constructor = 构造函数  = 该构造函数的实例对象.constructor 
  • 一个构造函数继承自父级构造函数,会拥有父级所有对外的,包括构造函数的属性和方方法,和父级原型的属性和方法;
  • 子级构造函数可以对继承的属性和方法进行重写;如果构造函数里面的方法或属性,和它的原型上的方法或属性同名,则调用时优先构造函数里面的方法或属性;
  • 所有的对象或构造函数通过原型链,追本溯源,最后的老祖宗都是object。即所有的构造函数都是object的子级或间接子级。object的原型的原型是null,到这里就是终极大结局了!

 

  大概知识点就是这些,在上面代码的基础上,再来一些测试代码,验证一下。先上一份测试原型链关系的代码:

//测试一下
var pro1 = person.prototype, pro2 = chinaperson.prototype, pro3 = provinceperson.prototype;
//provinceperson原型的原型 === chinaperson的原型
var pro3_china = object.getprototypeof(pro3);

//provinceperson原型的原型的原型 === person的原型
var pro3_person = object.getprototypeof(pro3_china);

//provinceperson原型的原型的原型的原型 === object的原型
var pro3_object = object.getprototypeof(pro3_person);

//function和object作为普通函数时是构造函数function的实例对象,获取这两个实例对象的原型
var pro_function = object.getprototypeof(function), pro_object = object.getprototypeof(object);

console.log("************* 原型链测试 start **********")
console.log("构造函数provinceperson继承自chinaperson, 构造函数chinaperson继承自person, person继承自object")
console.log("object --> person --> chinaperson --> provinceperson")
console.log("person.原型:", pro1);
console.log("chinaperson.原型:", pro2);
console.log("provinceperson.原型:", pro3);
console.log("provinceperson.原型.原型: ", pro3_china);
console.log("provinceperson.原型.原型.原型: ", pro3_person);
console.log("provinceperson.原型.原型.原型.原型:", pro3_object);
console.log("provinceperson.原型.原型 === chinaperson.原型 --> ", pro3_china === pro2);
console.log("provinceperson.原型.原型.原型 === person.原型 --> ", pro3_person === pro1);
console.log("provinceperson.原型.原型.原型.原型 === object.原型 --> ", pro3_object === object.prototype);
console.log("function.prototype === function.__proto__ --> ", function.prototype === pro_function);
console.log("object.__proto__ === function.prototype --> ", pro_object === function.prototype);
console.log("************ 测试 end ************\n")

/*
测试结果:

************* 原型链测试 start **********
构造函数provinceperson继承自chinaperson, 构造函数chinaperson继承自person, person继承自object
object --> person --> chinaperson --> provinceperson
person.原型: {age: 18, run: ƒ, name: "炎黄子孙", methodperson: ƒ, constructor: ƒ}
chinaperson.原型: person {constructor: ƒ, hero: "谭嗣同", write: ƒ}
provinceperson.原型: chinaperson {constructor: ƒ, feature: "长沙臭豆腐", eat: ƒ, write: ƒ}
provinceperson.原型.原型:  person {constructor: ƒ, hero: "谭嗣同", write: ƒ}
provinceperson.原型.原型.原型:  {age: 18, run: ƒ, name: "炎黄子孙", methodperson: ƒ, constructor: ƒ}
provinceperson.原型.原型.原型.原型: {constructor: ƒ, __definegetter__: ƒ, __definesetter__: ƒ, hasownproperty: ƒ, __lookupgetter__: ƒ, …}
provinceperson.原型.原型 === chinaperson.原型 -->  true
provinceperson.原型.原型.原型 === person.原型 -->  true
provinceperson.原型.原型.原型.原型 === object.原型 -->  true
function.prototype === function.__proto__ -->  true
object.__proto__ === function.prototype -->  true
************ 测试 end ************
*/

   测试结果截图:

 

  再来一份对于多级继承和重写展示的测试代码:

//第二波测试,测试构造函数的继承 和 多态(重写从父级继承下来的属性或方法)
console.log("\n************* 继承和重写 start ************");
console.log(">>>>>>准备创建一个person实例对象>>>>>");
var per = new person("王大锤");  
per.methodperson();
per.run();
console.log("*****person实例对象测试结论:构造函数和原型有同名属性或方法,实例对象优先调用构造函数的属性或方法*****\n");

console.log("\n>>>>>准备创建一个chinaperson实例对象,chinaperson继承了person >>>>>");
var chobj = new chinaperson("中国人", "黄色");
chobj.methodchinaperson();
chobj.write();
chobj.methodperson();
chobj.run();
console.log("*****chinaperson实例对象测试结论:继承自父类person, 拥有父类所有对外的构造函数里面和原型里面的属性和方法\n");

console.log("\n>>>>>准备创建一个provinceperson实例对象,provinceperson继承了chinaperson>>>>>");
var proobj = new provinceperson("湖南人", "黄色", 888);
proobj.methodprovinceperson();
proobj.eat();
proobj.methodchinaperson();
proobj.write();
proobj.methodperson();
proobj.run();
console.log("*****provinceperson实例对象测试结论:拥有父级和父级的父级的所有对外的,包括构造函数里面和原型里面的属性和方法;另外也可以对父级属性或方法进行重写");
console.log("************  测试 end ************\n");

/*
测试结果打印日志:

************* 继承和重写 start ************
>>>>>>准备创建一个person实例对象>>>>>
********person 构造函数 初始化********
person构造函数里面的方法methodperson-->我的标签:王大锤
person原型方法run-->name: 王大锤, age: 18, 欢快的run
*****person实例对象测试结论:构造函数和原型有同名属性或方法,实例对象优先调用构造函数的属性或方法*****

>>>>>准备创建一个chinaperson实例对象,chinaperson继承了person >>>>>
********person 构造函数 初始化********
********chinaperson 构造函数 初始化********
chinaperson构造函数里面的方法methodchinaperson-->肤色:黄色, 标签: 中国人
chinaperson原型里面的方法write-->我自横刀向天笑,去留肝胆两昆仑!is who? 谭嗣同, 标签: 中国人, skin: 黄色
person构造函数里面的方法methodperson-->我的标签:中国人
person原型方法run-->name: 中国人, age: 18, 欢快的run
*****chinaperson实例对象测试结论:继承自父类person, 拥有父类所有对外的构造函数里面和原型里面的属性和方法

>>>>>准备创建一个provinceperson实例对象,provinceperson继承了chinaperson>>>>>
********person 构造函数 初始化********
********chinaperson 构造函数 初始化********
********provinceperson 构造函数 初始化********
provinceperson构造函数里面的方法methodprovinceperson-->数量:888万, 肤色:黄色, 标签:湖南人
provinceperson原型里面的方法eat-->标签:湖南人, 地方小吃是:长沙臭豆腐, hero: 谭嗣同, skin: 黄色
provinceperson构造函数里面重写父级方法methodchinaperson....
provinceperson原型里面重写从父级原型继承的write方法-->。。。。^_^
person构造函数里面的方法methodperson-->我的标签:湖南人
person原型方法run-->name: 湖南人, age: 18, 欢快的run
*****provinceperson实例对象测试结论:拥有父级和父级的父级的所有对外的,包括构造函数里面和原型里面的属性和方法;另外也可以对父级属性或方法进行重写
************  测试 end ************
*/

 

如对本文有疑问, 点击进行留言回复!!

相关文章:

验证码:
移动技术网