搞懂__proto__与prototype

2019-10-09 admin

一,前言

对一个知识点是否完全把握,最好的校验方法就是能否用自己的语言将其表述出来。

原型与原型链一直是学习 JS 绕不过的知识点,其中protoprototype 最为让人头疼,这里简单的写下我自己的理解,从原型与原型链中拆解 protoprototype ,希望能对大家有所帮助。

一,原型

1,定义

在javascript中,函数可以有属性。 每个函数都有一个特殊的属性叫作原型(prototype)

注:prototype属性是函数特有,对象没有该属性

原型(prototype) 是什么东西呢,里面又有哪些属性呢?来,让我们拿个具体例子看下:

function fn() {};

console.dir(fn)

clipboard.png

这里我们可以看出 原型(prototype) 是一个对象,对于当前例子来说,里面有 constructorproto两个属性。那这个原型对象有什么作用呢?来,让我们继续往下看~

2,使用

现在我们知道了原型是一个对象,那原型对象有什么作用呢?实际上,原型是 ECMAScript 实现继承的过程中产生的一个概念。这里我们简单拿 ES5 的对象来举例子:

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

Person.prototype.say = function() {
    console.log(`My name is ${this.name}`);
}

let person = new Person('小明');
person.say();  // My name is 小明

让我们来逐步解释下这个例子:

  1. 声明了一个构造函数 Person,其有一个属性 name
  2. 在其原型上声明了一个函数 say
  3. 实例一个 Person 类——person
  4. 调用 person 的 say 方法

我们发现,person可以调用其构造函数原型里的say方法,why?让我们看下 person 里都有什么:

clipboard.png

实例 person 虽然自身没有 say 方法,但是通过 proto 属性访问到了其原型中的 say 方法。

为什么 proto 属性会指向其构造函数的原型 prototype 呢,他们之间是什么关系呢?让我们继续往下看~

二,原型链

1,proto

在介绍原型链前,让我们先看一个属性:proto,这是一个与原型 prototype 很相似的属性,这次让我们来彻底搞懂他们之间的关系。

让我们先来看MDN上的定义:

Object.prototype 的 proto 属性是一个访问器属性(一个getter函数和一个setter函数), 暴露了通过它访问的对象的内部[[Prototype]] (一个对象或 null)。

注:函数也是一种对象,所以其同时具有 prototype 与 __proto__ 两个属性

看不懂定义没关系,让我们举例说明:

let obj = {a: 1};

console.log(obj);

clipboard.png

我们可以看出 proto 指向了一个对象,这个对象是什么呢?来,让我们继续看

class Parent {
    constructor(name) {
        this.name = name;
    }

    print() {
        console.log(this.name);
    }
}

let parent = new Parent('小明');

console.dir(parent);
console.dir(Parent);

clipboard.png

clipboard.png

咦,有没有发现 parent.proto 与 Parent.prototype所指向的对象很相似,它们知否是同一个对象呢?

parent.__proto__ == Parent.prototype   // true

结果是同一个引用,这个时候我们可以得出一个结论:实例的__proto__属性指向其构造函数的原型。那他们之间的这种关联关系有什么作用呢?这便涉及到了原型链。

2,原型链

JavaScript 对象有一个指向一个原型对象的链。当试图访问一个对象的属性时,它不仅仅在该对象上搜寻,还会搜寻该对象的原型,以及该对象的原型的原型,依次层层向上搜索,直到找到一个名字匹配的属性或到达原型链的末尾。

注:因为对象没有prototyp属性,所以通过__proto__属性与原型prototype进行关联

文字描述很抽象,让我们通过ES5的继承来具体分析:

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

Parent.prototype.print = function() {
    console.log(this.name);
}

function Child(name) {
    Parent.call(this, name);
}

Child.prototype = Object.create(Parent.prototype);
Child.prototype.constructor = Child;

let child = new Child('小明');
console.log(child);

让我们来看下 child 里都有什么

clipboard.png

这里可能有的同学对Object.create()这个函数不太熟悉,不明白其做了什么,不要急,让我们来看下其源码实现:

function create(proto) {
    function F();
    F.prototype = proto;
    return new F();
}

声明了一个构造函数F,然后将其原型指向参数proto,返回构造函数的实例。好,让我们将整个过程串起来看一下:

1,声明 Parent 构造函数
2,声明 Child 构造函数,并手动绑定 this 指向
3,执行 Object.create(Parent.prototype):声明一个构造函数 F,更改其原型prototype指向(F.prototype = Parent.prototype),然后返回 F 的实例 f,注意这一步,实际上是 f.__proto__ == F.prototype
4,将 Child 的 prototype 指向 f.prototype
5,绑定 Child 的构造函数

让我们再来看下 child.print() 的调用过程

1,child对象里没有 print 函数,于是便在其原型上寻找:child.__proto__  ——>  f.prototype
2,进入 f.prototype 中寻找 print 函数,发现没有,于是去其原型上寻找:f.__proto__   ——>  F.prototype
3,F.prototype == Parent.prototype,于是便进入 Parent.prototype 中寻找 print 函数,有 print 函数,调用成功

怎么样,是不是豁然开朗!原型链其实就是通过 proto 与 prototype 的关联关系连接起来的,这样对象便可以寻找其原型上的方法与属性。详细的关系描述如下图:

图片描述

实战

让我们来看一道面试题:

var F = function() {};

Object.prototype.a = function() {
  console.log('a');
};

Function.prototype.b = function() {
  console.log('b');
}

var f = new F();

f.a();
f.b();

F.a();
F.b();

解题思路如下:

 1\. f.a() ——> 实例 f 调用 a 方法,自身没有,从其原型中查找:f.__proto__ == F.prototype
 2\. F.prototype中没有 a 方法,于是继续在其原型中查找,F.prototype.__proto__ == Object.prototype
 3\. Object.prototype中有a方法,无b方法,所以f.a()结果为a,f.b()调用会报错:f.b is not a function
 4\. F.a():构造函数调用 a 方法,自身没有,从其原型中查找:F.__proto__ == Funtion.prototype
 5\. Funtion.prototype中有 b 方法,所以F.b()的输出为b。没有 a 方法,继续在其原型中查找,Function.prototype.__proto__ == Object.prototype
 6\. Object.prototype中有 a 方法,所以F.a()的输出为a

三,总结

本文对__proto__与prototype之间的关系进行了简单的梳理,写下了笔者自己的理解,给大家理解提供一个思路,当然可能存在描述不准确或错误的地方,欢迎大家留言交流,以上~

[转载]原文链接:https://segmentfault.com/a/1190000020626977

本站文章除注明转载外,均为本站原创或编译。欢迎任何形式的转载,但请务必注明出处。

转载请注明:文章转载自 JavaScript中文网 [https://www.javascriptcn.com]

本文地址:https://www.javascriptcn.com/read-76963.html

文章标题:搞懂__proto__与prototype

相关文章
性能与生态双突破 HTML5重现爆发曙光
走过早熟的WebAPP,也经历过概念性的“轻应用”,HTML5这个被视作移动互联网未来的技术标准,终于在2015年看到了爆发的曙光。1月 15日,搜狐发布基于HTML5的“手机搜狐网3.0,加上微信几天前开放HTML5接口,HTML5很可能...
2015-11-12
2015年预测:Web体验与以往的五大不同
在过去的一年,我们见证了Uber的崛起、Apple加入了可穿戴设备的竞赛中、以及诸如Facebook收购Whatapp这类大的并购事件。那么在2015年我们将看到哪些巨大的改变?这里列出了五个对未来的预测 更清洁、简单的内容 2013年...
2015-11-11
DOM之通俗易懂讲解
DOM 是所有前端开发每天打交道的东西,但是随着 jQuery 等库的出现,大大简化了 DOM 操作,导致大家慢慢的 “遗忘” 了它的本来面貌。不过,要想深入学习前端知识,对 DOM 的了解是不可或缺的,所以本文力图系统的讲解下 DOM 的...
2016-01-13
详解使用vue-router进行页面切换时滚动条位置与滚动监听事件
按照正常的产品逻辑,我们在进行页面切换时滚动条应该是在页面顶部的,可是。。。在使用vue-router进行页面切换时,发现滚动条所处的位置被自动记录了下来,且在另一个组件内定义的滚动监听事件仍会运行,着实吃了一大惊。。。 说说我的破解方法:...
2017-03-13
JavaScript深入之类数组对象与
类数组对象 所谓的类数组对象: 拥有一个 length 属性和若干索引属性的对象 举个例子: var array = ['name', 'age', 'sex']; var ...
2017-05-27
javascript数据结构与算法之检索算法
查找数据有2种方式,顺序查找和二分查找。顺序查找适用于元素随机排列的列表。二分查找适用于元素已排序的列表。二分查找效率更高,但是必须是已经排好序的列表元素集合。 一:顺序查找 顺序查找是从列表的第一个元素开始对列表元素逐个进行判断,直到找到...
2017-03-22
javascript实现的字符串与十六进制表示字符串相互转换方法
本文实例讲述了javascript实现的字符串与十六进制表示字符串相互转换方法。分享给大家供大家参考。具体如下: 之所以写这个,是因为发现SQL注入和XSS中经常利用十六进制表示的字符串,比如 SELECT CONCAT(0x68656c6...
2017-03-27
JavaScript实现节点的删除与序号重建实例
本文实例讲述了JavaScript实现节点的删除与序号重建。分享给大家供大家参考。具体如下: 这里演示JavaScript节点的删除与重建方法,删除节点后,会自动重新建立节点,序号自动排列,比如删除当前的第3条数据后,第4条的序号会智能变为...
2017-03-29
JavaScript中String.prototype用法实例
本文实例讲述了JavaScript中String.prototype用法。分享给大家供大家参考。具体如下: // 返回字符的长度,一个中文算2个 String.prototype.ChineseLength=functi...
2017-03-23
详解vue-Resource(与后端数据交互)
单来说,vue-resource就像jQuery里的$.ajax,用来和后端交互数据的。可以放在created或者ready里面运行来获取或者更新数据… vue-resource文档:https://github.com/vuejs/vue...
2017-03-07
回到顶部