图解原型和原型链

什么是原型(prototype)?

我们知道原生的js中并没有类(Class)这个概念, 因此在js中创建对象主要是通过以下两种方式实现:

js 复制代码
//1.对象字面量
var xm = {name:"小明",age:18};

//2.构造函数
function Person(name,age){
    this.name = name;
    this.age = age;
}
let xm = new Person("小明",18);

其中前者适用于创建单个对象,当需要批量创建对象时,我们往往采取后者,即通过构造函数创建对象。

原型(prototype) 是函数的一个特定属性,该属性可看做一个指针,指向一个对象,该对象包含了构造函数的所有共有属性和函数。

简单来说原型相当于一个共享窗口,凡是放在原型里的属性都能共享给子代使用,但子代只有其使用权没有编辑和所有权,任何对象实例不能修改其父类的原型。

下面我们带入到一个简单的例子中具体感受一下。

js 复制代码
  function Person(name, age) {
    //实例属性
    this.name = name;
  }

  let xm = new Person("小明");    //实例一
  let xh = new Person("小红");     //实例二

  //原型函数
  Person.prototype.greet = function(){
    console.log("hello",this.name);
  }
  //原型属性
  Person.prototype.age = 18;
  xm.gender = "男";     //xm实例的自有属性
  
js 复制代码
  console.log(xm);
  console.log(xh);

打印结果如下:

如图,xm实例有2个属性,xh实例只有1个属性。

其中多出的gender属性是xm特有的,只有xm实例有权访问, 这种只有实例本身可以访问的属性我们称为自有属性

相反的,属于同一个构造函数创建出来的对象属性则称为共有属性,共有属性包含原型属性和实例属性两部分。

实例属性 :在构造函数内部通过this定义的属性,每个实例对象都有其独立的实例属性。 例如:this.name = name;
原型属性: 通过在构造函数的prototype上定义的属性,例如:Person.prototype.age = 18;

js 复制代码
//Person的实例对象均可以使用定义在原型上的属性age
console.log(xm.age);  //18
console.log(xh.age);  //18

//Person的实例对象均可以使用定义在原型上的函数greet()
xm.greet();    //hello 小明
xh.greet();    //hello 小红

打印结果如下:

由上两次打印结果可知,添加在原型上的属性不属于任一实例,但是由该构造函数创建的实例均可以使用原型上的属性/函数

js 复制代码
console.log("Person.prototype",Person.prototype);

打印结果如下:

如图,打印Person.prototype返回了一个对象,其内部包含了原型属性age,原型函数greet(),以及构造器创建的实例属性name,age。即该对象含了构造函数的所有共有属性和函数。

prototype、constuctor和__proto__的关系

prototype的定义已在上面有详细介绍,这里便不再赘述,主要介绍一下后两者的定义:

constructor: 在 JavaScript 中, constructor 属性返回对象的构造函数。 说白了就是告诉你某个对象是通过哪个构造函数创建出来的。
__proto__: 在 JavaScript 中,任何对象都有其隐式原型,对象的隐式原型指向创建该对象的父级的原型。

prototype和__proto__的区别:

  1. 任何对象都有__proto__,而prototype则是函数特有的属性
  2. 函数一定有原型,对象可能有原型,因为函数也是一个对象。

下面我们简单梳理一下构造函数,原型和隐式原型之间的关系:

  • 实例的__proto__指向构造函数原型,告诉实例可以访问哪些共有属性
  • 原型的constructor又指向构造函数本身,告诉实例是被哪个构造函数创建的
  • 同时构造函数的prototype指向其原型,告诉其有哪些可被子实例访问的共有属性

通过prototype、constuctor和__proto__三个指针的共同作用,实现了js中的继承机制。

原型链

了解了prototype、constuctor和__proto__的关系,我们试着来看一张解释原型链的经典老图。

线条弯弯绕绕,一眼看过去可能还是摸不着头脑,不过没关系,我们把线条拉直了并分块来分析。

前面提到js中创建对象有两种方法:

  • 通过构造函数创建
  • 创建对象字面量

1. 构造函数创建的实例的原型链构成:

  • 如红线所示,f1,f2是构造函数Foo()通过new关键字创造出的两个子实例;其隐式原型指向其构造函数Foo()的原型,原型的constructor又指向构造函数Foo()
  • Foo()自己又是一个由Function()创建出来的子实例,同理可得其prototype、constuctor的指向。但Function已是最根处的构造函数,因此其__proto__无法继续向上指向,而是指向了自己
  • 同时,不论是Function()还是Foo(),它们的prototype本质都是一个对象,因此其__proto__指向它们的构造函数Object()的原型
  • 最终到达一切原型链的尽头Object.prototype.proto** ,其值为null**

2.通过对象字面量创建的实例的原型链构成:

  • 如红线所示,o1,o2是通过对象字面量创建的两个子实例对象,因此其隐式原型指向它们的构造函数Object()的原型,原型的constructor又指向构造函数Object(),最终到达一切原型链的尽头Object.prototype.proto** ,其值为null**
  • Object()自己又是一个由Function()创建出来的子实例,因此其__proto__指向Function()的原型

最后我们将上面两种情况合并到一起,便得到了完整的原型链构成:

原型链的作用

原型链本质上是js中用于实现继承的一套完整的机制。通过原型链,我们可以实现继承。开发者通过修改对象的原型,为该对象添加新的属性和方法,使多个对象可以共享同一个原型对象的属性和方法,从而简化代码量和优化内存空间。

new关键字的简易实现原理

了解了原型链的构成,我们通过new关键字的简易实现原理来更具体的体会原型的应用。

new关键字创建对象时实际上干了哪些事?

  • 1.创建一个空对象 var obj={}
  • 2.改变this指向 call apply
  • 3.给该对象添加属性 this.name = name
  • 4.返回一个对象 return obj

下面是一个简易的new关键字的实现:

js 复制代码
function Person(name,age){
    this.name = name
    this.age = age
}

function _new(){

    //1.创建一个空对象
    var obj = {}
    //2.取出参数
    var params = Array.prototype.slice.call(arguments,1)
    //3.取出构造函数
    var foo = arguments[0]
    //4.修改函数的this指向obj
    foo.apply(obj,params)
    //5.修改原型指向为function.prototype 否则根据原型链会直接指向最根处的Object
    obj.__proto__ = foo.prototype
    obj.__proto__.constructor = foo
    //6.返回一个obj对象
    return obj
}


const xm = new Person("小明",18);
const xiaoming = _new(Person,"小明",18);

console.log("new",xm);
console.log("_new",xiaoming);
相关推荐
半开半落2 分钟前
nuxt3安装pinia报错500[vite-node] [ERR_LOAD_URL]问题解决
前端·javascript·vue.js·nuxt
理想不理想v30 分钟前
vue经典前端面试题
前端·javascript·vue.js
小阮的学习笔记44 分钟前
Vue3中使用LogicFlow实现简单流程图
javascript·vue.js·流程图
YBN娜44 分钟前
Vue实现登录功能
前端·javascript·vue.js
阳光开朗大男孩 = ̄ω ̄=1 小时前
CSS——选择器、PxCook软件、盒子模型
前端·javascript·css
小政爱学习!1 小时前
封装axios、环境变量、api解耦、解决跨域、全局组件注入
开发语言·前端·javascript
魏大帅。1 小时前
Axios 的 responseType 属性详解及 Blob 与 ArrayBuffer 解析
前端·javascript·ajax
花花鱼1 小时前
vue3 基于element-plus进行的一个可拖动改变导航与内容区域大小的简单方法
前端·javascript·elementui
k09331 小时前
sourceTree回滚版本到某次提交
开发语言·前端·javascript