深入剖析prototype、constructor、_proto_原理

JavaScript中比较难理解的点之【prototype、_ _ proto _ _、constructor】,通常不明白这三者关系的同学都有个毛病:继承也不懂!
深刻理解这个知识点不仅可以对学习继承有帮助,而且对new关键字、this、性能方面都会有更好的认识。最关键是,几乎作为面试必考题目之前,没啥理由不好好看完~

友情提示:文章相对枯燥且绕,一定要耐心。这篇文章会尽量按照我这段时间所产生过的疑惑,以懵逼当事人之一的角度去一一解开谜底。


在这个复杂三角恋的关系中,我认为最让人容易混淆的有如下几个点:

  • Function及Object在原型链中所扮演的角色
  • 这三者存在相互引用,且呈链式这样骚里骚气的关系
  • Function及Object这两个内置对象的特殊性
  • 函数即对象
  • construtor、prototype、__proto __具体存在哪个对象当中

接下来我们对劈腿三件套【prototype、_ _ proto _ _、constructor】 依次从不同的角度去分析?

  1. 为何有?
  2. 是什么?
  3. 有何用?
  4. 怎么用?

真正剖析三角恋【prototype、_ proto _、contrutor】是本文最核心的地方,如果你对该知识点有了一定的了解之后,也建议先从这里读起


为何有?

这你应该听过:JS一开始设计的初衷是一个给浏览器做简单交互用的,作者估计也万万没想到时至今日能发展到这么强大!后来的JS慢慢的更像一个Java这样的面向对象程序语言。而在Java中有类Class,对象Object,举个例:

// Java

// 一种类: 人
public class Person {
	String name = '狗子';
}
// 男人
public class Man extend Person {
	int jj = 18;
}
// 一个具体的男人
Man tony = new Man();     // 托尼老师 - 一个拥有18cm男人的实例

而在Java中,有new、extend关键字来完成了继承的关系,

那么问题来了,JS如何实现?
这个问题就是【为何有?】的答案,为了让js更好的写出面向对象的代码,实现继承。


是什么?

【prototype、_ _ proto _ _、constructor】 中我认为先解释清楚【construtor、prototype】之间的关系尤为重要,所以,我决定先从【consrtutor】作为切入点展开。

如果你学过Java那么就可以找到js模拟类、继承有很多相似的地方

construtor-构造函数

如果你学过Java之类的语言,那么理解这点会非常轻松。因为construtor跟Java当中的一样,就是一个生成具体对象的函数。
代码:

function Person (name) {
	this.name = name;
}
let person = new Person('js bigname');

// 对比 Java
public class Person {
	String name = '';
	// java构造函数
	Person (name) {
		this.name = name;
	}
}
Person person = new Person('java bigname');

对吧几乎一样的意思跟写法。

construtor是什么?

这里就可以下定义了,用来创建对象的函数!Js构造函数本身并无特殊,仅仅是约定首字母大写而已。真正给函数带来不同的是 new关键字。

那么对象可以是可以创建了,但是如何实现继承?往下看

prototype-原型对象

假如让你当js语言的设计者,你会如何去实现继承的功能呢?

带着这个问题思考会非常有用,而我首先会这么思考:

要实现继承,第一个要解决的问题:
son如何去访问father的方法?son中拥有father!

模拟实现继承效果的伪代码:

	function Father (name) {
        this.name = name;
    }

    function Son (name) {
        this.showName = function () {
            console.log(this.name || 'empty')
        };
    };

    let son = new Son('son1');
    son.showName();  // empty 

此时Son与Father一点关系都没有,怎么办?

 function Father (name) {
        this.name = name;
    }

    function Son (name) {
        this.father = new Father(name); // 改变的地方
        this.showName = function () {
            console.log(this.name || this.father.name || 'empty') // 改变的地方
        };
    };

    let son = new Son('son1');
    son.showName();  // son1

在son中增加父类对象father,如果在son中找不到该属性,则从father中去读取,而father的父类其实是Object,万物皆Object嘛,逃不掉的。

	function Object (firstName) {
        this.firstName = firstName;
    }

    function Father (name , firstName) {
        this.father = new Object(firstName);
        this.name = name;
    }

    function Son (name, firstName) {
        this.father = new Father(name , firstName);
        this.showName = function () {
            // son本身没有firstName则从father上找,fathter没有则从father的father上找(也就是object)
            // 这一条的关系链其实也就是平常所讲的原型链
            console.log(this.firstName || this.father.firstName || this.father.father.firstName || 'empty')
            console.log(this.name || this.father.name || 'empty')
        };
    };

    let son = new Son('son1', 'liu');
    son.showName();

将这一个设想结合到真实的prototype中,son实例中以及father实例中的father对象其实就是一个简易版的prototype!!!它指明了各个对象之间的关系。

这里先做一个基于上面代码总结出来的关系图:
在这里插入图片描述
JS中这种继承关系的实现,其实也基本跟上面一致。

prototype是什么?

prototype是在【创建对象过程中】自动为对象添加的【内置属性(对象类型)】;你可以先这么理解,后面还会讲到prototype的真正位置。

_proto _-原型对象的引用

_ proto _是什么?

proto是指向prototype对象的变量。(他指向的是他的构造函数的prototype)

看代码:

function Person () {
	this.name = 'tony';
}
let person = new Person(); 

调试模式下观察person对象:
在这里插入图片描述
我们只定义了name属性,但却被自动添加了proto属性,并且指向的prototype是Object类型,这其实就是Js中对象默认自动继承Object的意思了。当执行person.age会先从person对象查找,没有则从proto对象中查找。


真正剖析三角恋【prototype、_ proto _、contrutor】

文章最值钱的内容,前面做了那么多铺垫就是为了引出这里而铺垫

function Person () {
	this.name = 'tony';
}
let person = new Person(); 

还是刚才的案例,重点看一下person下的_ _ proto_ _内的属性
在这里插入图片描述

插入:先梳理一下函数即对象的概念?
Function作为内置对象之一,不要过多的纠结他,只要知道Function是函数也是一个对象,并且继承自Object。构造函数Person是一个函数,继承自Function,但new出来的person是对象,继承自Object

继续------------------------》》

  • 第一点:construtor始终指向创建自己的函数(记住了)
    如:person 下的 _ _ proto _ _ 的constructor指向Person函数

  • 第二点:_ _proto__始终指向prototype【prototype存在于构造函数Person下,这一点也非常非常重要】

  • 第三点:Person产生的对象可以有很多个,但所产生的protorype只有一个

  function Person () {
    this.name = 'tony';
  }
  Person.prototype.age = 12;

  let person1 = new Person();
  let person2 = new Person();
  console.log(person1 === person2);   // false
  console.log(person1.__proto__ === person2.__proto__); // true
  • 第四点:prototype存在于函数对象中【Person()】
  • 第五点:_proto _存在于对象中【person】
  • 第六点:函数即对象,所以函数也有__proto_ _

记住上面的点:然后带着来理解==let person = new Person()==所发生的事情:
在这里插入图片描述

_纠正上面紫色字体,应该改为:Object构造函数的protype没有__proto __

这个过程中有两点需要注意的:

  • Function函数的__proto __指向自己的prototype
  • Object函数的prototype没有__proto __,所以Object就是原型链的终点

总结

  • Function函数的__proto __指向自己的prototype
  • Object对象没有__proto __,所以Object就是原型链的终点
  • __proto __指向prototype
  • prototype才有construtor,construtor指向构造函数,并且构造函数才有prototype,这相互嵌套的关系理解起来可能会容易混淆
  • prototype只有一个(Person的只有一个)
  • 除了Object构造函数没有prototype外,对象都有__proto __

// Object构造函数是指函数对象Object(){},而不是construtor

那么什么是原型链?
上图中荧光绿【1,2,3,4】就是一天原型链!他规定了对象之间的关系,以及变量访问的查找规则。

先做个小结:
JS千辛万苦做了这么多花里花哨的关系处理,无非就是为了达到继承的效果!也就是处理变量访问的规则,例如person.name,假如person本身没有这个属性咋办?就去person的__proto __里面找呗,同理往下推理。这一条查找的路线就是原型链!


有何用?

到这里应该能理解到,【prototype、_ _ proto _ _、constructor】是为了更好地面向对象,实现继承的解决方案


怎么用?

怎么利用这些属性实现继承?

先来第一种

construtor继承:

 function Father () {
        this.name = 'father';
    }
    function Son () {
        Father.call(this); // 构造继承,将son传递给Father()函数作为上下文,所以this.name实际在son上创建name属性
        this.age = 12;
    }

    let son = new Son();

看下执行之后son的结构:
在这里插入图片描述
看图,在son对象中创建了name属性,并且prototype仍然仍然是Object类型,与Father没有关系。
这种方式的缺点:所有属性都创建在对象当中,试想一下,假如创建了100个son,就会在创建了100次name。如果你想要创建的属性是给所有对象不是独一份,而是共享的怎么办?
回想一下上面提到的,prototype是独一份的,所以只需要将共享的属性创建prototype中,然后将Son的prototype改为指向Father,所以看下一个继承方式。

原型链继承:

   		function Father () {
            this.name = 'father';
        }
        // Father在原型中定义公共方法
		Father.prototype.getName  = function () {
            console.log(this.name);
        };

        function Son () {
            this.age = 12;
        }

        //name属性在son中找不到,则会自动从Son的prototype(即father对象)中寻找
        Son.prototype = new Father(); // 原型链继承

        let son = new Son();

看下执行之后son的结构:
在这里插入图片描述
当然,原型链存在的问题就是,所有son实例对象的prototype指向的father对象都是同一份。所以一旦修改就会影响到所有的对象。

一般的场景就是,既有需要共享的属性,也有独属的。如Person,每个人都有年龄,姓名,但值各不相同,所以适合构造函数继承,而获取名称,获取年龄这样的方法每个人都一模一样,所以应该定义成公共方法,就应该使用适合链继承。

综上述:

组合继承才是上佳选择,欸,感觉有点跑题了,我们讲的主题可不是这个。后面再考虑做个关于一般企业是如何使用js继承的

其实到这里我感觉应该可以结题了,但又想了想,会不会有人看了上面Person与Object的【prototype、_ _ proto _ _、constructor】关系,搞多了个Son继承Person就不知道咋回事的了吧。

我想想应该还真的有,所以我觉得再以上面原型链继承之后【prototype、_ _ proto _ _、constructor】各对象之间的关系:
在这里插入图片描述
这里主要体现了son是如何通过原型链集成之后的关系。

那么到这里就该结题了。

题外话:这篇包括从查阅资料,写demo测试,到这篇文章的编写前前后后花了将近5天时间。但其中是写文章的时候, 用自己的语言重新组织出来,测试案例,才感觉真正有点心领神会的感觉,所以除了看文章之外一定要动手,思考一下,如果要给别人讲解这个知识点你会怎么讲

另外,很感谢非常不错的文章,让我对这个知识点有了不同的认知:

用自己的方式(图)理解constructor、prototype、__proto__和原型链

帮你彻底搞懂JS中的prototype、__proto__与constructor(图解)

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值