JavaScript中的原型、原型链、构造器全面解读,如果还不懂@我

概念

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

原型

  • 原型是 function 对象的一个属性,它定义了构造函数制造出来的对象的公共祖先

  • 通过构造函数产生的对象,可以继承该原型的属性和方法

  • 原型也是对象


//  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('原型方法');
}

prototype

prototype 能找到构造函数的原型,我们可以通过 Person.prototype 找到它的原型,并且能在它的原型上设置公共的属性和方法。

//  定义原型上的属性
Car.prototype = {
  name: "小明",
  age: 18
}
//  构造函数
function Car() { }
let car = new Car()
//  通过Prototype直接找到了Person的原型上面的属性
console.log(Car.prototype);
 /**
  * {name: "小明", age: 18, fun: ƒ}
  * age: 18
  * fun: ƒ ()
  * name: "小明"
  * __proto__: Object
  * */

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
image

原型链

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

原型链图解

image

原型链模拟

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


  //  原型链模拟
  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 = '前端伪大叔'
  //    }
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。