zoukankan      html  css  js  c++  java
  • JavaScript之原型与原型链

    前言

    JavaScript常被描述为一种「基于原型的语言」——每个对象都拥有一个「原型对象」,对象以其原型为模板、从原型继承属性和放法。原型对象也可能拥有原型,并从中继承属性和方法,一层一层以此类推。这种关系常被称为「原型链」,它解释了为何一个对象会拥有定义在其他对象中的属性和方法。

    准确的说,这些属性和方法定义在Object构造函数prototype属性上,而非对象实例本身。

    四句话道破原型与原型链:

    • 每个函数(类)天生自带一个属性prototype,属性值是一个对象,里面存储了当前类供实例使用的属性和方法 「(显示原型)」
    • 在浏览器默认给原型开辟的堆内存中有一个constructor属性:存储的是当前类本身(⚠️注意:自己开辟的堆内存中默认没有constructor属性,需要自己手动添加)「(构造函数)」
    • 每个对象都有一个__proto__属性,这个属性指向当前实例所属类的原型(不确定所属类,都指向Object.prototype「(隐式原型)」
    • 当你试图获取一个对象的某个属性时,如果这个对象本身没有这个属性,那么它会去它的隐式原型__proto__(也就是它的构造函数的显示原型prototype)中查找。「(原型链)」

    构造函数,原型与实例的关系:

    每个构造函数(constructor)都有一个原型对象(prototype)原型对象(prototype)都包含一个指向构造函数(constructor)的指针,而实例(instance)都包含一个指向原型对象(__proto__)的内部指针

    prototype(显式原型)

    每个函数都有一个prototype属性

    // 构造函数(类)
    function Person(name){
        this.name = name
    }
    // new了一个实例 (对象)
    var person = new Person('南玖')
    console.log(person) //Person { name: '南玖' }
    console.log(Person.prototype)  //构造函数(类)的原型 ----->对象
    Person.prototype.age = 18  // 构造函数原型
    console.log(person.age)  // 18
    

    上面我们把这个函数Person的原型打印出来了,它指向的是一个对象,并且这个对象正是调用该构造函数而创建的实例的原型

    prototype1.png

    上面这张图表示的是构造函数与实例原型之间的关系,所以我们知道了构造函数的prototype属性指向的是一个对象。

    那实例与实例原型之间的关系又是怎样的呢?这里就要提到__proto__属性了

    __proto__(隐式原型)

    从上面四句话中我们可以知道这是每一个Javascript对象(除null)都具有的一个属性,这个属性会指向该对象的原型(也就是实例原型)

    因为在JavaScript中没有类的概念,为了实现类似继承的方式,通过__proto__将对象和原型联系起来组成原型链,的以让对象访问到不属于自己的属性。

    那么我们就能够证明实例与实例原型之间的关系

    console.log(person.__proto__)  //实例(对象)的原型--->对象
    
    console.log(person.__proto__ === Person.prototype)  //实例的原型与构造函数的原型相等
    

    prototype2.png

    从上图我们可以看出实例对象与构造函数都可以指向原型,那么原型能不能指向构造函数或者是实例呢?

    constructor(构造函数)

    原型是没有属性指向实例的,因为一个构造函数可以创建多个实例对象;

    从前面的四句话中我们知道「在浏览器默认给原型开辟的堆内存中有一个constructor属性」,所以原型也是可以指向构造函数的,这个属性就是「constructor」

    于是我们可以证明一下观点:

    console.log(Person.prototype.constructor) //实例的显式原型的构造函数ƒ Person(name){this.name = name}
    console.log(person.__proto__.constructor)  //实例的隐式原型的构造函数 ƒ Person(name){this.name = name}
    console.log(person.__proto__.constructor === Person.prototype.constructor)//true 实例原型的构造函数与类的构造函数相等
    console.log(Person === Person.prototype.constructor)  //true
    

    prototype3.png

    实例对象的__proto__是如何产生的?

    我们知道当我们使用new 操作符时,生成的实例对象就拥有了__proto__属性

    function Foo() {}
    // 这个函数时Function的实例对象
    // function是一个语法糖
    // 内部其实调用了new Function()
    

    所以可以说,在new的过程中,新对象被添加了__proto__属性并且链接到了构造函数的原型上。

    new的原理

    说简单点可以分为以下四步:

    • 新建一个空对象
    • 链接原型
    • 绑定this,执行构造函数
    • 返回新对象
    function myNew() {
    // 1.新建一个空对象
    let obj = {}
    // 2.获得构造函数
    let con = arguments.__proto__.constructor
    // 3.链接原型
    obj.__proto__ = con.prototype
    // 4.绑定this,执行构造函数
    let res = con.apply(obj, arguments)
    // 5.返回新对象
    return typeof res === 'object' ? res : obj
    }
    

    原型链

    说完了原型,我们再来看看什么是原型链?先来看一张图:

    prototype4.png

    这张图中,由__proto__串起来的链式关系,我们就称它为原型链

    原型链的作用

    原型链决定了JavaScript中继承的实现方式,当我们访问一个属性时,它的查找机制如下:

    • 访问对象实例属性,有的话直接返回,没有则通过__proto__去它的原型对象上查找
    • 原型对象上能找到的话则返回,找不到继续通过原型对象的__proto__查找
    • 一直往下找,直到找到Object.prototype,如果能找到则返回,找不到就返回undefined,不会再往下找了,因为Object.prototype.__proto__是null,说明了Object是所有对象的原型链顶层了。

    从图中我们可以发现,所有对象都可以通过原型链最终找到 Object.prototype ,虽然 Object.prototype 也是一个对象,但是这个对象却不是 Object 创造的,而是引擎自己创建了 Object.prototype 。所以可以这样说,所有实例都是对象,但是对象不一定都是实例。

    构造函数的__proto__是什么呢?

    由上面的原型链的解释,我们应该能够理解构造函数的__proto__的,在JavaScript中所有东西都是对象,那么构造函数肯定也是对象,是对象就有__proto__

    function Person(){}
    console.log(Person.__proto__)
    console.log(Function.prototype)
    console.log(Person.__proto__===Function.prototype) // true
    

    「这也说明了所有函数都是Function的实例」

    那这么理解的话,Function.__proto__岂不是等于Function.prototype。。。。我们不妨来打印一下看看

    Function.__proto__ === Function.prototype // true
    

    打印出来确实是这样的。难道 Function.prototype 也是通过 new Function() 产生的吗?

    答案是否定的,这个函数也是引擎自己创建的。首先引擎创建了 Object.prototype ,然后创建了 Function.prototype ,并且通过 __proto__ 将两者联系了起来。这里也很好的解释了上面的一个问题,为什么 let fun = Function.prototype.bind() 没有 prototype 属性。因为 Function.prototype 是引擎创建出来的对象,引擎认为不需要给这个对象添加 prototype 属性。

    总结

    • Object 是所有对象的爸爸,所有对象都可以通过 __proto__ 找到它
    • Function 是所有函数的爸爸,所有函数都可以通过 __proto__ 找到它
    • Function.prototypeObject.prototype 是两个特殊的对象,他们由引擎来创建
    • 除了以上两个特殊对象,其他对象都是通过构造器 new 出来的
    • 函数的 prototype 是一个对象,也就是原型
    • 对象的 __proto__ 指向原型, __proto__ 将对象和原型连接起来组成了原型链
    作者:前端南玖

    -------------------------------------------

    个性签名:智者创造机会,强者把握机会,弱者坐等机会。做一个灵魂有趣的人!

    如果觉得这篇文章对你有小小的帮助的话,可以关注下方公众号,在该公众号同样会推送技术文章给大家,谢谢~

    欢迎加入前端技术交流群:928029210

    逐梦wx
  • 相关阅读:
    Linux中的文件特殊权限
    服务器管理
    程序图表统计知识
    EasyUI 添加tab页(iframe方式)
    打造自己的reset.css
    CI的知识点
    easyui资源
    eval解析JSON中的注意点
    如何在一个frame中调用另一个frame中的javascript函数
    Egret 摇一摇功能
  • 原文地址:https://www.cnblogs.com/songyao666/p/15457276.html
Copyright © 2011-2022 走看看