美文网首页前端
原型与原型链

原型与原型链

作者: lyp82nkl | 来源:发表于2019-06-15 00:31 被阅读0次
    MDN解释:

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

    方方解释:

    所有对象都有 toString 和 valueOf 属性,那么我们是否有必要给每个对象一个 toString 和 valueOf 呢?
    明显不需要。
    JS 的做法是把 toString 和 valueOf 放在一个对象里(暂且叫做公用属性组成的对象)
    然后让每一个对象的 __ proto __ 存储这个「公用属性组成的对象」的地址。
    原型就是共有属性

    公用属性(原型)

    用String赋值的变量s1,她的__ proto __ 指向了String共有属性(原型),String的原型里面还有 __ proto __的原型指向Object共有属性,Object共有属性最后指向了null,结构图如下:


    • Numer的共有属性:Number.prototype
    • Object的共有属性:Object.prototype
    • String的共有属性:String.prototype
    • Boolean的共有属性:Boolean.prototype

    上面的代码Object.prototype就是上边结构图的Object的共有属性(原型),
    o1的__ proto __指向Object里的原型,所以是true

    n1的下划线原型指向的是Number的原型,然后Number原型里面的下划线原型指向Object的原型,所以就可以写成

    n1.__proto__.__proto__ === Object.prototype
    或者:Number.prototype.__proto__ === Object.prototype
    

    注意:prototype是浏览器自动生成的,你就算不写代码也有

    上面的图当你什么代码也不写的时候,打开浏览器也会有一个window对象,而window对象里有Number、Object、String、Boolean这几个全局函数,又因为函数也是对象,所以他们再次将内容存放到一个堆里,又因为是函数所以有prototype的原型,分别指向自己对应的类型,而最后prototype也是一个对象,只要是对象就有__ proto __这个下划线的原型,通过他指向了Object公用属性。就拿Number这个全局函数来举例

    上面没有执行任何操作,直接打印了Number这个函数,它里面的prototype就指向了Number的公用属性,而她的prototype下面还有一个下划线的原型指向了Object的公用属性。

    上面的代码,var 后面的肯定是对象,new后面的是函数对象

    对象的下划线原型指向该实例对象的构造函数的原型也就是说:

    var n = new Object();
    n.__proto__ === Object.prototype
    //new后面的就是构造函数名,以new方法生成的函数就是构造函数
    

    Object.prototype.proto ===null

    对象由构造函数创建的

    数组、函数、正则都是对象

    对象的proto等于创建者的prototype

    prototype上有个属性constructor 指向创建者(构造函数)

    数组的构造函数是 Array

    函数的构造函数是 Function(当构造函数作为对象时,这个构造函数的构造函数就是Function)

    正则的构造函数是 RegExp

    而String,Number,Boolean,Object,Function这几个是全局函数,所以它的构造函数就是Function,故指向Function.prototype:


    这里最需要注意的是Function的构造函数就是Function,只有它的proto是指向自己的:

    另外自己写的构造函数也是一样的,当构造函数作为对象时.__ proto __===Function.prototype

    如:

    function Person(name){
      this.name = name;
    }
    var person = new Person('wang')
    Person.__proto__===Function.prototype  //true
    //因为Person是一个函数,函数也是一个对象所以有__proto__的属性,而函数的构造函数是Function
    

    另外判断一个对象的构造函数可以用
    1.console.log(xxx.constructor),但是注意xxx只能是单独的对象就是xxx里面不可以接prototype或者__ proto __


    第一个Function.constructor是可以正确判断的,但Function.prototype.constructor就不能,它显示的也是Function可实际上它的构造函数是Object。另外判断一个构造函数是什么其实上述方法并不可取,因为不排除有人为改变构造函数的可能,所以暂且只需记住就行,另外只要是(对象.prototype)或者(对象.__ proto __)作为对象的时候他们的构造函数都是Object(这里排除特例)
    2.实例对象.constructor(构造器里的prototype始终指向构造函数本身)

    实例对象的proto和构造函数中的prototype相等--->true
    因为实例对象是通过构造函数来创建的,构造函数中有原型对象prototype
    实例对象的proto指向了构造函数的原型对象prototype
    只要是对象就有proto原型
    又因为prototype也是对象,所以,prototype也有proto

    原型(对象)、构造函数、实例、原型链

    关系图如下:


    1.构造函数:就是函数,名字首字母应该大写
    function Foo(name,age){
      this.name = name
      this.age = age
      this.class = 'class-1'
      // return this // 默认有这行
    }
    
    2.实例对象:new 函数() => 实例
    // 创建多个实例对象
    var f1 = new Foo('zhangsan',21)// new 就是实例化的过程
    var f2 = new Foo('lisi',22)
    var f3 = new Foo('wangwu',23)
    实例对象.__proto__ === 构造函数.prototype
    

    实例对象.proto === 构造函数.prototype

    描述 new 一个对象的过程
    • 创建一个新对象,它继承自构造函数Foo.prototype
    • 执行构造函数Foo。执行时,相应的参数会被传入,同时上下文 this,指向这个新实例。在不传递参数时,new Foo等同于new Foo()
    • 执行代码,即对 this 赋值(this.key = value)
    • 构造函数 Foo,return一个对象,那么这个对象会取代整个 new 出来的结果。如果构造函数没有return对象,那么 new 出来的结果,就是步骤1创建的新对象
    3.原型对象:prototype,每个函数都有这个属性,也是用于扩展构造函数的
    Foo.prototype.alertName = function(){
      alert(this.name)
    }
    
    f1.printName = function(){
      console.log(this.name)
    }
    
    f1.printName()
    f1.alertName()
    

    构造器:constructor

    在原型对象(prototype)下有个constructor属性,它的作用是指向声明的那个函数(就是构造函数)

    Foo.prototype.constructor === Foo返回true
    
    原型5条规则:
    1. 所有的引用类型(对象、函数、数组),都具有对象特性,即可自由扩展属性(除null以外)
    2. 所有的引用类型(对象、函数、数组),都有一个__ proto __属性,属性值是一个普通对象(除null以外)—— 隐式原型
    3. 所有的函数都有一个prototype属性,属性值是一个普通对象 —— 显示原型
    4. 所有的引用类型(对象、函数、数组),__ proto __属性值指向(全等)它的构造函数的prototype属性值
    5. 当试图得到一个对象的某个属性时,如果这个对象本身没有这个属性,那么会去它的__ proto __中寻找,(也就是去它的构造函数的 prototype)
    4.原型链
    var obj = { name: 'obj' } 
    

    在我们没对obj进行任何操作之前,obj就已经有toString()方法,那么toString()方法是怎么来的哪?


    答:这跟 proto 有关。

    当我们「读取」 obj.toString 时,JS 引擎会做下面的事情:

    1. 看看 obj 对象本身有没有 toString 属性。没有就走到下一步。
    2. 看看 obj.__ proto __ 对象有没有 toString 属性,发现 obj.__ proto __ 有 toString 属性,于是找到了,就会退出
      所以 obj.toString 实际上就是第 2 步中找到的 obj.__ proto __.toString。
      可以想象,
    3. 如果 obj.__ proto __ 没有,那么浏览器会继续查看 obj.__ proto . proto __
    4. 如果 obj.__ proto . proto __ 也没有,那么浏览器会继续查看 obj.__ proto . proto . proto __
    5. 直到找到 toString 或者 __ proto __ 为 null。
      上面的过程,就是「读」属性的「搜索过程」。

    而这个「搜索过程」,是连着由 __ proto __ 组成的链子一直走的。

    这个链子就叫原型链。

    通过下图我们可以更好的理解:

    典型例题:
    function Fn(){
        this.a = function(){
            console.log(1)
        }
    }
    Fn.prototype.a = function(){
        console.log(2)
    }
    new Fn().a()  //1
    

    上面的代码先从实例对象本身开始搜索有没有a这个方法,因为本身就有所以直接退出搜索
    参考文章:什么是 JS 原型链?

    相关文章

      网友评论

        本文标题:原型与原型链

        本文链接:https://www.haomeiwen.com/subject/zipmfctx.html