概念

原型和原型链这俩小妖精绝对是js学习路上的一块绊脚石~如果能深入理解原型和原型链这两个概念绝对能让我们深入理解js

原型

  • 原型是 function 对象的一个属性,它定义了构造函数制造出来的对象的公共祖先
  • 通过构造函数产生的对象,可以继承该原型的属性和方法
  • 原型也是对象 ```javascript // Person.prototype 原型 // Person.prototype = {} 祖先

// 定义Person构造函数 function Person(){} // Person的原型 Person.prototype

// 定义函数的属性 Person.prototype.num = 12; Person.prototype = { name: “小明”, age: 18 }

// 定义原型上的方法 Person.prototype.fun = function(){ console.log(‘原型方法’); }

  1. <a name="qda7n"></a>
  2. ## prototype
  3. `prototype` 能找到构造函数的原型,我们可以通过 `Person.prototype` 找到它的原型,并且能在它的原型上设置公共的属性和方法。
  4. ```javascript
  5. // 定义原型上的属性
  6. Car.prototype = {
  7. name: "小明",
  8. age: 18
  9. }
  10. // 构造函数
  11. function Car() { }
  12. let car = new Car()
  13. // 通过Prototype直接找到了Person的原型上面的属性
  14. console.log(Car.prototype);
  15. /**
  16. * {name: "小明", age: 18, fun: ƒ}
  17. * age: 18
  18. * fun: ƒ ()
  19. * name: "小明"
  20. * __proto__: Object
  21. * */

proto

__proto__ 属性的指向所创建它的原型对象。通过 new 创建出来的实例,可以通过 __proto__ 属性直接找到创建该实例的原型对象。
具体的proto在下面的原型链中介绍

//  定义原型上的属性
Car.prototype = {
  name: "小明",
  age: 18
}

// 构造函数
function Car() { }
let car = new Car()

//  实例通过__proto__直接找它的原型
console.log(car.__proto__);
 /**
  * {name: "小明", age: 18, fun: ƒ}
  * age: 18
  * fun: ƒ ()
  * name: "小明"
  * __proto__: Object
  * */

原型图解

通过下图可以看出构造函数 Person.prototype 和实例的 person1.__proto__ 都是指向原型对象 Person.prototype 。也就是说他们两是对等的,那我们测试一下

console.log(Car.prototype == car.__proto__);    //    true

timg.jpg

原型链

JavaScript的原型对象自身也是一个对象,它也有自己的原型对象,通过 __proto__ 属性层层上溯,就形成了一个类似链表的结构,这就是 原型链 。原型链的顶层是 Object 对象,如果你还想往上找,那么找到的将会是 null

new的作用

  • 创建一个空的简单JavaScript对象(即{});
  • 链接该对象(即设置该对象的构造函数)到另一个对象 ;
  • 将步骤1新创建的对象作为this的上下文 ;
  • 如果该函数没有返回对象,则返回this。

其实我们在new的第一步,创建是空对象。其实不然,而是创建的是一个 只有__proto__属性 的空对象。

再谈proto

如果我们想要了解原型链就要先了解 __proto__ 属性。我们在使用某些属性的时候,发现自己本身没有这个属性,通过 __proto__ 一层一层在往上查找,直到找到 Object 对象。如果还要拼命往上找那么 null 就会出现在眼前~

  function Person() {
    this.name = '我是构造函数 - 前端伪大叔'
  }
  Person.prototype.protoName = '我是原型 - 前端伪大叔';
  let person = new Person();

  console.log(person.name);     //  我是构造函数 - 前端伪大叔
  console.log(person.protoName);  //  我是原型 - 前端伪大叔

沃特~按理说Person构造函数上没有protoName属性,为什么会找到Person.prototype原型呢?那么开始我们的主题。

原型查找

  console.log(person.__proto__);  //  {protoName: "我是原型 - 前端伪大叔", constructor: ƒ}
  console.log(person.__proto__.protoName);  //  我是原型 - 前端伪大叔

我们看到了, __proto__ 找到了 Person 的原型,直接在他的原型上找到的protoName。我们再来

原型链查找

  console.log(person.toString); //  toString() { [native code] }
  console.log(person.__proto__.__proto__);  //  {constructor: ƒ, __defineGetter__: ƒ, __defineSetter__: ƒ, hasOwnProperty: ƒ, __lookupGetter__: ƒ, …}
  console.log(person.__proto__.__proto__.toString); //  toString() { [native code] }

我们在定义方法的时候并没有定义 toString 方法,但是它出来了~为什么呢?因为我们通过 person.__proto__.__proto__ 找到原型链顶层的 Object 对象,在 Object 对象上发现有 toString() 方法。所以person.tostring是调用的 Object.toString() 方法。这就是原型链的魅力~。

原型链的顶层

还有一种情况,如果我们再次调用 __proto__ 那么将会是null,因为 Object 对象是原型链的顶层, Object 上不存在proto

console.log(person.__proto__.__proto__); //    上面没有__proto__ 所有下面输出是null

console.log(person.__proto__.__proto__.__proto__);  //  null

原型链图解

JavaScript原型、原型链解读 - 图2

原型链模拟

通过继承实例的方式,模拟原型链查找方便里面原型链是如何查找。

  //  原型链模拟
  function Grandpa() {
    Grandpa.prototype.gName = "叶叶"
  }
  let grandpa = new Grandpa()
  //  继承上面
  Father.prototype = grandpa;
  function Father() {
    this.fName = "巴巴"
  }
  let father = new Father();

  //  继承于上面
  My.prototype = father;
  function My() {
    this.mName = '我'
  }
  let my = new My()
  //  自己有 用自己属性
  console.log(my.mName);    //    我
  //  自己没有查找父亲属性
  console.log(my.fName);    //    巴巴
  //  自己和父亲都没有查找祖先属性
  console.log(my.gName);    //    叶叶

构造器

至于我们在上一直看到的 constructor 是什么呢?其实他就是个构造器。可以通过原型查找到它的 构造函数

  function Person() {
    this.name = '前端伪大叔'
  }
    //    原型查找构造函数
  console.log(Person.prototype.constructor);
  console.log(Person);
    //    ƒ Person() {
  //      this.name = '前端伪大叔'
  //    }
    //    查看原型链顶层Object对象的构造函数
  console.log(Object.prototype.constructor);
  console.log(Object);
    //    ƒ Object() { [native code] }

    //    实例通过__proto__同样可以找到创建自己的构造函数
  console.log(person.constructor);
  console.log(person.__proto__.constructor);
    //    ƒ Person() {
  //      this.name = '前端伪大叔'
  //    }