美文网首页
原型及原型链

原型及原型链

作者: 晨曦_f83d | 来源:发表于2021-04-21 20:04 被阅读0次
    原型是什么

    原型:每个函数都有prototype(原型)属性,这个属性是一个指针,指向一个对象,这个对象的用途是包含特定类型的所有实例共享的属性和方法,这个对象(原型对象)是用来给实例共享属性和方法的。

    在原型上添加属性和方法的好处

    通过原型继承的方法并不是自身的,我们要在原型链上一层一层的查找,这样创建的好处是只在内存中创建一次,实例化的对象都会指向这个 prototype 对象。

    怎么找到原型对象
    function Player(color) {
      this.color = color;
    }
    
    Player.prototype.start = function () {
      console.log(color + "下棋");
    };
    
    const whitePlayer = new Player("white");
    const blackPlayer = new Player("black");
    console.log(blackPlayer.__proto__); // Player {}
    console.log(Object.getPrototypeOf(blackPlayer)); // Player {},可以通过Object.getPrototypeOf来获取__proto__
    console.log(Player.prototype); // Player {}
    console.log(Player.__proto__); // [Function]
    

    关系图可以为下图


    image.png
    new关键字做了什么
    1. 一个继承自 Player.prototype 的新对象 whitePlayer 被创建
    2. whitePlayer.proto[图片上传失败...(image-c4e084-1618934433846)]
      指向 Player.prototype,即 whitePlayer._proto _= Player.prototype
    3. 将 this 指向新创建的对象 whitePlayer
    4. 返回新对象
      4.1 如果构造函数没有显式返回值,则返回 this
      4.2 如果构造函数有显式返回值,是基本类型,比如 number,string,boolean, 那么还是返回 this
      4.3 如果构造函数有显式返回值,是对象类型,比如{ a: 1 }, 则返回这个对象{ a: 1 }

    下面看一下怎么手写实现 new 函数

    // 1. 用new Object() 的方式新建了一个对象 obj
    // 2. 取出第一个参数,就是我们要传入的构造函数。此外因为 shift 会修改原数组,所以 arguments 会被去除第一个参数
    // 3. 将 obj 的原型指向构造函数,这样 obj 就可以访问到构造函数原型中的属性
    // 4. 使用 apply,改变构造函数 this 的指向到新建的对象,这样 obj 就可以访问到构造函数中的属性
    // 5. 返回 obj
    function objectFactory() {
      let obj = new Object();
      let Constructor = [].shift.call(arguments);
      obj.__proto__ = Constructor.prototype;
      let ret = Constructor.apply(obj, arguments);
      return typeof ret === "object" ? ret : obj;
    }
    
    原型链是什么

    我们都知道当读取实例的属性时,如果找不到,就会查找与对象关联的原型中的属性,如果还查不到,就去找原型的原型,一直找到最顶层为止。
    举个例子

    function Player() {}
    
    Player.prototype.name = "Kevin";
    
    var p1 = new Player();
    
    p1.name = "Daisy";
    // 查找p1对象中的name属性,因为上面添加了name,所以会输出“Daisy”
    console.log(p1.name); // Daisy
    
    delete p1.name;
    // 删除了p1.name,然后查找p1发现没有name属性,就会从p1的原型p1.__proto__中去找,也就是Player.prototype,然后找到了name,输出"Kevin"
    console.log(p1.name); // Kevin
    
    鸡生蛋问题

    我们看下面这段代码

    Object instanceof Function      // true
    Function instanceof Object      // true
    
    Object instanceof Object            // true
    Function instanceof Function    // true
    

    于是可以得出

    // Object instanceof Function   即
    Object.__proto__ === Function.prototype                     // true
    
    // Function instanceof Object   即
    Function.__proto__.__proto__ === Object.prototype   // true
    
    // Object instanceof Object         即           
    Object.__proto__.__proto__ === Object.prototype     // true
    
    // Function instanceof Function 即   
    Function.__proto__ === Function.prototype                   // true
    

    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 上的方法。

    ----- the end

    参考
    原型和原型链

    相关文章

      网友评论

          本文标题:原型及原型链

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