美文网首页
JavaScript原型和原型链深入解析

JavaScript原型和原型链深入解析

作者: 好心情_lyh | 来源:发表于2019-12-05 16:47 被阅读0次

    prototype,proto,constructor之间的关系:

    《JavaScript高级程序设计》定义:

    无论什么时候,只要创建了一个新函数,就会更加一组特定的规则为该函数创建一个prototype属性,这个属性指向函数的原型对象。在默认情况下,所有原型对象都会自动获取一个constructor(构造函数)属性,这个属性包含一个指向prototype属性所在函数的指针。而通过这个构造函数,我们还可以继续为原型对象添加其他属性和方法。
    创建了自定义的构造函数之后,其原型对象默认只会取得constructor属性;至于其他方法,则都是从Object继承而来的。当调用构造函数创建一个新实例后,该实例的内部将包含一个指针(内部属性),指向构造函数的原型对象。ECMA-262第5版中管这个指针叫[[Prototype]]。虽然在脚本中没有标准的方式访问[[Prototype]],但Firefox,Safari和Chrome在每个对象上都支持一个属性__proto__;而在其他实现中,这个属性对脚本则是完全不可见的。不过,要明确的正在重要的一点就是,这个链接存在于实例与构造函数的原型对象之间,而不是存在于实例与构造函数之间。

    总结一下,prototype就是函数的一个属性,实际上是一个指针,指向构造函数的原型对象。prototype只能够被函数调用,通过字面量创建的对象是没有这个属性的。那prototype属性的作用又是什么呢?它的作用就是包含可以由特定类型的所有实例共享的属性和方法,也就是让该函数所实例化的对象们都可以找到公用的属性和方法。任何函数在创建的时候,其实会默认同时创建该函数的prototype对象。

    //对象字面量是没有prototype这个属性的
    var obj = {};
    obj.constructor === Object;//true
    obj.__proto__ === Object.prototype;//true
    console.log(obj.prototype);//undefined
    
    function Foo(){};
    let f1 = new Foo();
    f1.__proto__ === Foo.prototype;//true
    Foo.prototype.constructor === Foo;//true
    

    综上,
    1.我们需要牢记两点:①__proto__和constructor属性是对象所独有的;② prototype属性是函数所独有的,因为函数也是一种对象,所以函数也拥有__proto__和constructor属性。
    2.__proto__属性的作用就是当访问一个对象的属性时,如果该对象内部不存在这个属性,那么就会去它的__proto__属性所指向的那个对象(父对象)里找,一直找,直到__proto__属性的终点null,再往上找就相当于在null上取值,会报错。通过__proto__属性将对象连接起来的这条链路即我们所谓的原型链。
    3.prototype属性的作用就是让该函数所实例化的对象们都可以找到公用的属性和方法,即f1.__proto__ === Foo.prototype。
    4.constructor属性的含义就是指向该对象的构造函数,所有函数(此时看成对象了)最终的构造函数都指向Function。

    Function & Object 鸡蛋问题

    Object instanceof Function;//true
    Object.__proto__ === Function.prototype;//true
    
    Function instanceof Object;//true
    Function.__proto__.__proto__ === Object.prototype;//true
    
    Object instanceof Object;/true
    Object.__proto__.__proto__ === Object.prototype;//true
    
    Function instanceof Function;//true
    Function.__proto__ === Function.prototype;//true
    
    Function.prototype.__proto__ === Object.prototype; // true
    Object.prototype.__proto__ === null; // true
    
    Function.__proto__ === Function.prototype;//true
    

    Object 构造函数继承了 Function.prototype,同时 Function 构造函数继承了Object.prototype。这里就产生了 鸡和蛋 的问题。为什么会出现这种问题,因为 Function.prototype 和 Function.__proto__ 都指向 Function.prototype。

    对于 Function.__proto__ === Function.prototype 这一现象有 2 种解释,争论点在于 Function 对象是不是由 Function 构造函数创建的一个实例?

    解释 1、YES:按照 JavaScript 中“实例”的定义,a 是 b 的实例即 a instanceof b 为 true,默认判断条件就是 b.prototype 在 a 的原型链上。而 Function instanceof Function 为 true,本质上即 Object.getPrototypeOf(Function) === Function.prototype,正符合此定义。

    解释 2、NO:Function 是 built-in 的对象,也就是并不存在“Function对象由Function构造函数创建”这样显然会造成鸡生蛋蛋生鸡的问题。实际上,当你直接写一个函数时(如 function f() {} 或 x => x),也不存在调用 Function 构造器,只有在显式调用 Function 构造器时(如 new Function('x', 'return x') )才有。

    我个人偏向于第二种解释,即先有 Function.prototype 然后有的 function Function() ,所以就不存在鸡生蛋蛋生鸡问题了,把 Function.proto 指向 Function.prototype 是为了保证原型链的完整,让 Function 可以获取定义在 Object.prototype 上的方法。

    对象的继承方式

    1.js原型(prototype)实现继承
    2.构造函数实现继承
    3.call , apply实现继承

    call方式:

    //父类 
    
    function person(){ 
      this.hair = 'black'; 
      this.eye = 'black'; 
      this.skin = 'yellow'; 
      this.view = function(){ 
        return this.hair + ',' + this.eye + ',' + this.skin; 
      } 
    } 
    //子类 
    function man(){ 
      // person.apply(this,new Array()); 
      person.call(this,[]); 
      this.feature = ['beard','strong']; 
    } 
    
    man.prototype = new person(); 
    var one = new man();
    
    console.log(one.feature); //['beard','strong'] 
    console.log(one.hair); //black 
    console.log(one.eye); //black 
    console.log(one.skin); //yellow 
    console.log(one.view()); //black,black,yellow
    

    call方式的实现机制却要多一条 man.prototype = new person(); 为啥呢?
    那是因为call方法只实现了方法的替换而没有作对象属性的复制操作。

    三种方法对比:
    子类man在创建对象的同时传递参数到父类person,prototype的继承方式就不适用了,必须采用apply或者call的方式。

    但是用apply方法也还是有缺点的,为什么?在js中,我们有个非常重要的运算符就是”instanceof”,该运算符用来比较某个对向是否为某种类型。

    对于这个例子,one实例除了是man类型,也应该是person类型,但是apply方式继承之后,one却不属于person类型,即(one instanceof person)的值为false。因此,最好的继承方式就是call+prototype方式。

    function Person(name){   
      this.name = name; 
    } 
    
    Person.prototype.getName = function() { 
      return this.name; 
    } 
    
    function Chinese(name, nation) { 
      Person.call(this, name); 
      this.nation = nation; 
    } 
    
    //继承方法 
    function inherit(subClass, superClass) { 
      function F() {} 
      F.prototype = superClass.prototype; 
      subClass.prototype = new F(); 
      subClass.prototype.constructor = subClass.constructor; 
    } 
    
    inherit(Chinese, Person); 
    
    Chinese.prototype.getNation = function() { 
      return this.nation; 
    }; 
    
    var p = new Person('shijun'); 
    var c = new Chinese("liyatang", "China"); 
    
    console.log(p); // Person {name: "shijun", getName: function} 
    console.log(c); // Chinese {name: "liyatang", nation: "China", constructor: function, getNation: function, getName: function} 
    
    console.log(p.constructor); // function Person(name){} 
    console.log(c.constructor); // function Chinese(){} 
    
    console.log(c instanceof Chinese); // true 
    console.log(c instanceof Person); // true
    

    参考文章:
    帮你彻底搞懂JS中的prototype、__proto__与constructor(图解)
    深入探究 Function & Object 鸡蛋问题
    JavaScript中的call、apply、bind深入理解
    javascript中prototype、apply、call方式的优缺点实例详解

    相关文章

      网友评论

          本文标题:JavaScript原型和原型链深入解析

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