浅谈对原型及原型链的理解

在开篇我先总结一下原型的作用:
1.数据共享 节约内存内存空间
2.实现继承
注意:函数也是一个对象,对象不一定是函数。(对象有__proto__属性,函数有prototype属性)此处说明,方便大家理解下文。
下面我将举例说明为什么要使用原型
例1:

function Person(name) {
    this.name = name;
    this.eat = function () {
        console.log(this.name+"吃东西");
    }
    this.sleep = function () {
        console.log(this.name+"睡觉");
    }
}
var p1 = new Person("小明");
p1.eat();//小明吃东西
p1.sleep();//小明睡觉
var p2 = new Person("小利");
p2.eat();//小利吃东西
p2.sleep();//小利睡觉
console.dir(p1);//dir()打印结构
console.dir(p2);

执行结果图

每次使用构造函数Person()实例化出对象的时候,就会给每个实例对象的eat()方法和sleep()方法开辟空间。可是当实例化许多对象的时候,就会浪费大量的空间,因为每个实例对象的eat()方法和sleep()的功能都是一样的,所以我们没必要为每个实例对象添加eat()方法和sleep()方法。

这时原型就派上用场了,看下面经过改造的例子:

function Person(name) {
    this.name = name;
}
Person.prototype.eat = function () {
    console.log(this.name+"吃东西");
};
Person.prototype.sleep = function () {
    console.log(this.name+"睡觉");
}
var p1 = new Person("小明");
p1.eat();//小明吃东西
p1.sleep();//小明睡觉
var p2 = new Person("小利");
p2.eat();//小利吃东西
p2.sleep();//小利睡觉
console.dir(p1);
console.dir(p2);

 

执行结果

eat()方法和sleep()被添加到了Person()构造函数的原型(prototype)上了。因此只有一份eat()方法和sleep()方法。当实例对象需要使用这些方法的时候就在自己的__proto__属性中找到并调用这些方法实现相应的功能。

现在我们来捋一下构造函数,实例对象,原型对象之间的关系。

如以下代码和图所示:

Person()构造函数的prototype属性是一个对象,实例对象p1的__proto__属性也是一个对象,并且prototype对象和__proto__对象的指向相同。那么我们再回过头来理解一下为什么添加到原型的方法可以是共享的。因为prototype对象和__proto__对象的指向相同,所以将eat()方法和sleep()添加到Person()构造函数的prototype属性上之后,实例对象就可以通过自己__proto__属性去访问eat()方法和sleep()了。

console.dir(Person);
console.dir(p1);
console.log(typeof p1.__proto__);//object
console.log(typeof Person.prototype);//object
console.log(p1.__proto__ === Person.prototype);//true

 

执行效果

关系图

__proto__指向该对象所在的构造函数的原型对象。

实例对象和构造函数之间没用直接的关系。原型对象与实例对象之间用原型(__proto__)关联,这种关系叫做原型链。

我是这样理解原型链的(可能不是很准确)我向我爸要零花钱,我爸也没有钱,那么我就向我奶奶要,奶奶要是也没有,就继续找别人要。

那么原型的指向可以改变吗?答案是可以的。

举个例子:

function Person(name) {
    this.name = name;
}
Person.prototype.eat = function () {
    console.log(this.name+"吃东西");
};
Person.prototype.sleep = function () {
    console.log(this.name+"睡觉");
}
function Student(school) {
    this.school = school;
}
Student.prototype.write = function () {
    console.log("写作业");
}
Student.prototype = new Person("小华");//改变Student()构造函数的指向,让Student()构造函数的原型对象指向Person的实例对象

var s1 = new Student("某某高中");
s1.eat();//小华吃东西
s1.sleep();//小华睡觉
s1.write();//Uncaught TypeError: s1.write is not a function,因为Student()的原型的指向改变,所以找不到write()方法
console.dir(Student);
console.dir(s1);

执行结果

代码图解

__proto__指向该对象所在的构造函数的原型对象。如上图所示:Studend()构造函数的原型(prototype)指向了Person()的实例对象(new Person("小华")),所以Studend()的实例对象s1的__proto__也指向了Person()的实例对象((new Person("小华"))。而实例对象((new Person("小华"))的__proto__指向了其所在的构造函数Person()的原型对象在这个原型对象中,找到了eat()方法和sleep()方法。

从这个例子中,可以发现,利用原型可以实现继承。面向对象的编程语言中有(class)类的概念,但是JavaScript不是面向对象的语言,所以js中没有类(class)(ES6中实现了class),但是js可以模拟面向对象的思想编程,js中通过构造函数来模拟类的概念。

改变原型的指向可以实现方法的继承。借用构造函数继承,主要解决属性的问题

function Person(name) {
    this.name = name;
}
Person.prototype.eat = function () {
    console.log(this.name+"吃东西");
};
Person.prototype.sleep = function () {
    console.log(this.name+"睡觉");
}
function Student(name,school) {//name为父类构造器传参。子类构造器可以添加自己特有的属性school
    Person.call(this,name);//调用父类构造器Person的属性,
    this.school = school;
}
Student.prototype.write = function () {
    console.log("写作业");
}
Student.prototype = new Person();//改变Student()构造函数的指向,让Student()构造函数的原型对象指向Person的实例对象
var s1 = new Student("zx","某某高中");
s1.eat();//小华吃东西
s1.sleep();//小华睡觉
console.dir(Student);
console.dir(s1);

组合继承就是指:将改变原型的指向和借用构造函数两者结合在一起实现继承。

本文章是个人对原型的理解,有不准确的地方,还希望各位大神赐教......

  • 23
    点赞
  • 61
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值