一文搞懂JavaScript原型链(看完绝对懂)

学习目标

  • 原型
  • 原型链
  • 原型指向改变后是如何添加方法和属性
  • 原型指向改变后的原型链
  • 实例对象的属性和原型对象的属性重名
  • 经过原型继承
  • 组合继承
  • 拷贝继承

一,原型

问题: 请看如下代码,若是咱们要建立100个对象,应该怎么建立?前端

function Person(name, sex) {
    this.name = name;
    this.sex = sex;
    this.drink () {
        console.log('我想喝手磨咖啡!!')
    }
}
for (let i = 0; i < 100; i++) {
    var per = new Person('苏大强', '男');
    per.drink();
}
复制代码

从上面的代码能够看出,若是咱们要建立100个Person对象,这样要开一百个内存空间,每次都要调用drink()函数,因为drink()函数都是同样的,每一个内存空间里都有它太过于浪费空间,那咱们怎样才能避免这种状况,减小内存呢?咱们接下来引入原型prototype程序员

function Person(name, sex) {
    this.name = name;
    this.sex = sex;
}
//为原型添加方法
Person.prototype.drink = function () {
        console.log('我想喝手磨咖啡!!')
    }
    //实例化对象
let per = new Person('苏大强', '男');
per.drink();
复制代码

咱们运用了原型prototype,能够共享数据,减小内存空间。浏览器

二,原型链

咱们既然清楚了原型,那咱们再来看看原型链。首先咱们打印一下构造函数Person和实例对象per。bash

console.dir(Person);//构造函数
console.dir(per);//实例对象
复制代码

从图上看,构造函数中的prototype中的属行和实例对象per中的__proto__中的属性如出一辙,那咱们想一想它们相等吗?咱们能够验证一下。

console.log(per.__proto__ === Person.prototype);
复制代码

由此咱们能够判断出,构造函数Person中的prototype原型和实例对象per中的__proto__原型指向是相同的,咱们通常是先有构造函数再有实例对象,实例对象由构造函数建立,因此说实例对象中的__proto__原型指向的是构造函数中的原型prototype

实例对象中__proto__是原型,浏览器使用的。构造函数中的prototype是原型,程序员使用的函数

那接下来咱们看一幅图来看看原型链究竟是什么?学习

咱们来分析分析整张图

  • 首先,构造函数中的prototype属性指向本身的原型对象
  • 而后,原型对象中的构造器指向的是,原型对象所在的构造函数
  • 再而后,实例对象中的__proto__指向的是,它所在构造函数中prototype属性所指向的原型对象

因此从上图咱们能够获得如下几点:ui

  • 实例对象的原型指向了构造函数中prototype属性所指向的原型对象,因此实例对象和原型对象之间有关系,它和构造函数是一个间接的关系。
  • 咱们从代码中也能够得出,实例对象能够直接访问原型对象中的属性或方法。
  • 实例对象和原型对象之间有关系,它们的关系是经过原型__proto__来链接的。

最终咱们能够得出,原型链:它是一种关系,实例对象和原型对象之间的关系,关系是经过原型__proto__来联系的this

三,原型指向改变后是如何添加方法和属性

原型改变添加方法也无非就是两种:1.在原型改变前添加加方法。2.在原型改变之后添加方法。spa

首先,咱们来看第一种:prototype

function Person(name, sex) {
    this.name = name;
    this.sex = sex;
}
Person.prototype.drink = function () {
        console.log('我想喝水!!')
    }
function Student(name, sex) {
    this.name = name;
    this.sex = sex;
}
Student.prototype.eat = function () {
    console.log('我想吃东西!!')
}

//改变原型指向
Student.prototype = new Person('人', '男');
let stu = new Student('学生', '女');
stu.drink();
stu.eat();
复制代码

咱们来运行如下:

咱们能够看到图中的信息,stu.eat()不是一个函数,刚才咱们明明将eat()添加到了Student的原型上,怎么如今报错了? 缘由是:因为Student的原型指向改变了,它指向了new Person('人', '男'),而且Person的原型上并无eat(),因此报错,那么第一种状况在原型改变以前添加是错误的!

咱们再来看第二种状况:在原型改变以后添加方法。

function Person(name, sex) {
    this.name = name;
    this.sex = sex;
}
Person.prototype.drink = function () {
        console.log('我想喝水!!')
    }
function Student(name, sex) {
    this.name = name;
    this.sex = sex;
}

//改变原型指向
Student.prototype = new Person('人', '男');
//为原型添加方法
Student.prototype.eat = function () {
    console.log('我想吃东西!!')
}
let stu = new Student('学生', '女');
stu.drink();
stu.eat();
复制代码

咱们来运行如下:

能够看出来,两个方法都被成功的调运了,因此说:若是原型指向改变了,那么就应该在原型改变指向以后添加原型方法。

四,原型指向改变后的原型链

那么,当原型指向改变以后,原型链会发生怎样的改变呢? 那咱们来们分析如下:

  • 原型指向改变以前
  • 原型指向改变以后

咱们先来分析原型指向改变以前:

//人的构造函数
function Person(name) {
    this.name = name;
}
//为原型添加方法
Person.prototype.drink = function () {
        console.log('我想喝水!!')
    }
//学生的构造函数
function Student(name) {
    this.name = name;
}
//为原型添加方法
Student.prototype.eat = function () {
    console.log('我想吃东西!!')
}
//实例对象
let per = new Person('老师');
let stu = new Student('学生');

console.dir(Person);//构造函数
console.dir(per);//实例对象
console.dir(Student);//构造函数
console.dir(stu);//实例对象
复制代码

咱们运行如下这段代码:

请看每一个prototype和__proto__,咱们能够获得它们的原型链图:

由此图,咱们能够看出,原型没有改变以前,实例对象的__proto__都指向本身构造函数中prototype属性所指向的原型对象。

咱们再来看看原型指向改变以后:

//人的构造函数
function Person(name) {
    this.name = name;
}
//为原型添加方法
Person.prototype.drink = function () {
        console.log('我想喝水!!')
    }
//学生的构造函数
function Student(name) {
    this.name = name;
}
//为原型添加方法
Student.prototype.eat = function () {
    console.log('我想吃东西!!')
}
//改变学生的原型指向
Student.prototype = new Person('老师');
//实例对象
let stu = new Student('学生');

console.dir(Person);//构造函数
console.dir(new Person('老师'))//实例对象
console.dir(Student.prototype)//Student的原型对象
console.dir(Student);//构造函数
console.dir(stu);//实例对象
复制代码

咱们来看看运行结果:

咱们来分析分析:

咱们代码和图结合来看,当Student.prototype = new Person('老师');以后,①学生构造函数的prototype属性会断开指向原型对象,②原型对象中的构造器也会断开指向构造函数,③实例对象的__proto__会断开指向原型对象 这里的序号没有任何意义,至关于起的名字!!! 尚未完,咱们再来看图:

当原型指向改变以后,学生的构造函数中的prototype属性指向了new Person('老师');,随后,学生的实例化对象中的__proto__属性指向了学生构造函数中prototype属性所指向的new Person('老师');

原型链改变完毕!

五,实例对象的属性和原型对象的属性重名

当实例对象中的属性和原型对象中的属性重名时应该先访问那个? 咱们来看一看代码:

//人的构造函数
function Person(age, sex) {
      this.age = age;
      this.sex = sex;
    }
//为原型添加属性
Person.prototype.sex = "女";
//实例化对象
var per = new Person(10,"男");

console.log(per.sex);
复制代码

看图:

从代码的运行结果来看,当实例对象中的属性和原型中的属性重名时,它会先访问实例对象中的属性。

若是在实例对象中找不到呢?咱们来看代码:

function Person(age) {
      this.age = age;
    }
//为原型添加属性
Person.prototype.sex = "女";
//实例化对象
var per = new Person(10);

console.log(per.sex);
复制代码

咱们来看运行结果:

从这段代码,咱们能够看出,当实例对象中访问不到属性时,它会向上查找原型对象上的属性

六,经过原型继承

//js中经过原型来实现继承
    
    //人的构造函数
    function Person(name, age, sex) {
      this.name = name;
      this.sex = sex;
      this.age = age;
    }
    //为原型添加方法
    Person.prototype.eat = function () {
      console.log("人吃东西");
    };
    Person.prototype.sleep = function () {
      console.log("人在睡觉");
    };
    Person.prototype.play = function () {
      console.log("生活就是编代码!");
    };

    
    //学生的构造函数
    function Student(score) {
      this.score = score;
    }
    //改变学生的原型的指向便可==========>学生和人已经发生关系
    Student.prototype = new Person("小明", 10, "男");
    //为原型添加方法
    Student.prototype.study = function () {
      console.log("学习很累很累的哦.");
    };


    var stu = new Student(100);
    console.log(stu.name);
    console.log(stu.age);
    console.log(stu.sex);
    stu.eat();
    stu.play();
    stu.sleep();
    console.log("下面的是学生对象中本身有的");
    console.log(stu.score);
    stu.study();
复制代码

看运行结果:

原型继承的精髓就是:使用原型,改变原型的指向进行继承。

七,组合继承

//组合继承:原型继承+借用构造函数继承
    
    //人的构造函数
    function Person(name, age, sex) {
      this.name = name;
      this.age = age;
      this.sex = sex;
    }
    Person.prototype.sayHi=function () {
      console.log("你好吗?");
    };
    function Student(name, age, sex, score) {
      //借用构造函数:属性值重复的问题
      Person.call(this, name, age, sex);
      this.score = score;
    }
    //改变原型指向----继承
    Student.prototype = new Person();//不传值
    Student.prototype.eat = function () {
      console.log("吃东西");
    };
    //实例对象
    var stu = new Student("金仔", 20, "男", "100分");
    console.log(stu.name, stu.age, stu.sex, stu.score);
    stu.sayHi();
    stu.eat();
    
    var stu2=new Student("含仔", 20, "女", "100分");
    console.log(stu2.name, stu2.age, stu2.sex, stu2.score);
    stu2.sayHi();
    stu2.eat();
复制代码

看运行结果:

组合继承的精髓:首先在Student构造函数中使用call()函数将属性传入Person构造函数中,并改变this,而后改变Student的原型指向

八,拷贝继承

function Person() {};
    Person.prototype.age = 10;
    Person.prototype.sex = "男";
    Person.prototype.height = 100;
    Person.prototype.play = function () {
      console.log("玩耍!");
    };
    var Student = {};
    //Person的构造中有原型prototype,prototype就是一个对象,那么里面,age,sex,height,play都是该对象中的属性或者方法

    for (let key in Person.prototype) {
      Student[key] = Person.prototype[key];
    }
    console.dir(Student);
    Student.play();
复制代码

请看运行结果:

至此,本片文章的所有内容完毕!本人是一个前端新人,本片文章若哪里有不正确的地方,请各位前端大佬不吝斧正!我们携手共同进步!再次感谢!

另外,本人将要参加实习找工做,如果那位大佬以为小学弟能够的话,请给小学弟一个机会。邮箱:1441398660@qq.com

相关文章
相关标签/搜索