一文带你深度理解原型和原型链(长达八千字 全面解析)

原型与原型链不应该仅仅是概念性的,而应该是具有逻辑性推进的,本篇文章带你了解原型链并深入原型链,希望能够帮到大家

读完这篇文章你能收获到什么?

  • 理清原型和原型链的概念和逻辑
  • 理解原型链的常见用法
  • 有关原型链相关方法的深度解析

我们直接开始:

首先,什么是原型?什么是原型链?

原型: 在JavaScript中,每个对象都有一个关联的原型(prototype)。原型是一个对象,它自身拥有属性和方法,并且其他对象可以通过原型进行属性和方法的继承
原型链 : 原型链是JavaScript中实现对象继承的一种机制。它通过对象的原型属性(prototype)在对象之间形成一个链式的关系。

思考?

对象是怎么通过原型属性prototype在对象之间形成了一个链式的关系? 一个实例对象又是怎么连接到原型链呢?

答案就是: 通过__proto__

让我们先来看看__proto__的概念:

proto: 一个对象的__proto__属性指向的就是这个对象的构造函数的prototype属性

所以__proto__可以让实例对象直接访问到构造函数的prototype属性!!!(那么我们可以联想一下创建实例对象的时候都发生什么了)

我们都知道new方法可以创建一个实例,并使该实例获取到构造函数原型上的方法以及函数内使用this声明的一些属性

那new方法做了什么呢? 我们一起来看一下!

JavaScript 复制代码
function New () {
	var obj = new Object ();
	//获得构造函数(代码使用会传入参数,第一个参数是构造函数)
	var constructor = Array.prototype.shift.call(arguments);
	//连接原型,新对象可以访问原型中的属性
	obj._proto_ = constructor.prototype;   
-------------------------------------------------
**这段代码实际上建立了原型链 使得实例对象可以访问到构造函数的prototype**
        
        
	// 执行构造函数,即绑定 this,并且为这个新对象添加属性
	var result = constructor.apply(obj,arguments);
-------------------------------------------------
绑定this指向,对象构造函数内使用this写的一些属性可以绑定到当前对象上


	return typeof result === "object" ? result :obj ;
}

这就是一个实例对象是如何获取到构造函数的原型对象的prototype属性里面的方法

我们再来看一下prototype的定义

prototype:

在 JavaScript 中,每个函数 都有一个特殊的属性叫做 prototype。这个 prototype 属性是一个对象,它被用作构造函数创建的对象的原型(也称为原型对象)。

每一个函数都有一个特殊的属性叫prototype,我们看一下里面到底是什么?

我们创建一个函数

javascript 复制代码
function testOne(){}
console.log('testOne.prototype',testOne.prototype)// 函数的原型对象  

我们把他打印出来,看看他里面有什么属性,他里面的prototype是什么?

看,我们得到了一个对象,里面有一个constructor属性,这个constructor属性里面是什么呢?

我们知道 我们的prototype属性是这个函数的原型对象,我们也可以手动创建,比如:函数.prototype去赋值,那么prototype本身就是该函数的一个属性罢了。 那么他的constructor属性的指向也应该就是本身了。我们来验证一下。

js 复制代码
console.log(testOne.prototype.constructor === testOne)

那么 我们就有了结论: 函数的原型对象的构造函数指向该函数

还有呢?对象是怎么通过原型属性prototype在对象之间形成了一个链式的关系?是怎么使用prototype的属性的呢?

javascript 复制代码
const testOne = function(){

}
testOne.prototype.methods = function(){
    console.log('this is a funtion in prototype')
}

const instance = new testOne()
console.log(instance.__proto__ === testOne.prototype)
instance.methods()

这样 对象就通过原型属性的prototype访问到了原型上面的方法!

(注意:testOne不能直接拿到methods方法,例如testOne.methods,这样得到的值是undefined,因为js并没有对这个处理过,需要通过属性prototype去调用,即 testOne.prototype.methods)

函数和对象是怎么实现原型链继承的,Function和Object之间有什么关系

为什么声明一个对象或者函数之后,就可以直接访问原型链上的方法了

函数

我们需要想一下? 那函数到底是如何实现原型链的继承的呢,其中发生了什么,他为什么可以调用apply、bind等这些方法呢?是和实例对象一样,继承的东西都是在__proto__里面么? 那么 我通过这样的方法创建的函数,他的__proto__是什么呢?我们直接看结果(这里我使用console.dir(testOne.proto)输出,为了更好的查看输出的结构和细节)

这里我们要去思考了,我们声明一个函数的时候到底发生了什么,这个函数继承了来自哪里的方法? 其实在JavaScript中,"Function"是一个内置的构造函数,用于创建函数对象。它是所有函数的父对象,包括声明的普通函数。 当你声明一个普通函数时,实际上是创建了一个函数对象,并将其赋值给一个变量或将其作为属性赋值给一个对象。这个函数对象具有"Function"构造函数提供的所有方法和特性,可以被调用和执行。

javascript 复制代码
// 声明一个普通函数
function greet(name) {
  console.log("Hello, " + name + "!");
}
// 使用 Function 构造函数创建函数对象
var greetFunction = new Function("name", "console.log("Hello, " + name + "!");");
console.log(greet.__proto__ === greetFunction.__proto__)  //true
console.log(greet.__proto__=== Function)  //false
// 谨记__proto__的定义 获取的是该对象构造函数的原型对象  Function是一个特殊的内置的构造函数

console.log(greet.__proto__=== Function.prototype )  // true

这两种方法本质是一样的,我们看看这两种方法的__proto__属性是否一样?

打印结果均为true false true

为什么第二个值返回false

在JavaScript中,Function.prototype是一个函数对象的原型,而Function是一个特殊的内置函数构造函数。 当我们声明一个普通函数时,它的__proto__属性指向的是该函数的原型对象,而不是构造函数本身。

备注: 比较特殊的是 console.log(typeof Function.prototype === function) //返回值为true

我们可以得到 声明一个函数为什么可以调用一些Function原型对象上的方法,因为声明的时候就类似于 new Function()这样,我们继承了Function原型对象上面的方法。

对象呢?

那么声明一个对象,为什么可以调用Object实例上的方法呢?答案是显然易见的,我们直接放代码,测试一下

js 复制代码
let obj = {}
console.dir(obj.__proto__)
console.log(obj.__proto__ === Object.prototype)

我们可以得到 声明一个对象为什么可以调用一些Object原型对象上的方法,因为声明的时候就类似于 new Function()这样,我们继承了Object原型对象上面的方法。

Function 和 Object 有什么关系呢?

我们这里用的instanceof 检测对象之间实例的关系

js 复制代码
console.log(Function instanceof Object)
console.log(Object instanceof Function)

大家猜猜这个结果是什么? 答案是:返回了两个true

是不是,突然就懵掉了。

Object.prototypeFunction 函数的原型对象,并由 Function 构造函数创建。Function 本身是一个函数对象,同时也拥有 Object.prototype 提供的属性和方法。它们之间存在关系,反映了 JavaScript 中的原型链继承机制。所以他们两者的话是相互关联的。

那么就有人要问了?博主啊,到底是先有Function还是先有Object呢 我们直接判断一波:

js 复制代码
Function.prototype.__proto__ === Object.prototype

返回结果是true Object的原型对象是Function的原型对象的构造函数的原型对象

那么显而易见的 是先有的Object.prototype,那么Object本身是一个构造函数,所以Object就是由Function构造出来的,也就是Object继承了Function的原型对象上的方法。口说无凭,我们来判断一下

js 复制代码
Object.constructor===Function       
Object.__proto__ === Function.prototype

返回结果都是true 我们的结论就成立了!

都有什么方法跟原型链有关呢? 我们要注意什么呢?

  1. for...in

检查属性是否为对象自身的属性:由于 for...in 循环会遍历对象继承的属性,因此在处理属性时,你需要使用 hasOwnProperty() 方法来检查属性是否为对象自身的属性。这是为了避免遍历到继承的属性或方法。

js 复制代码
for (var key in xxx) {
  // 检查属性是否为对象自身的属性
  if (xxx.hasOwnProperty(key)) {
    console.log(key + ": " + xxx[key]);
  }
}
  1. Object.create()

思考object.create到底做了什么

JavaScript 复制代码
//源码
function create(prototype) {
  // 创建一个临时的构造函数
  function Temp() {}
  // 将临时构造函数的原型设置为指定的原型对象
  Temp.prototype = prototype;
  // 创建一个新对象实例
  var obj = new Temp();
  // 清空临时构造函数的引用
  Temp.prototype = null;
  // 返回新对象实例
  return obj;
}

我们测试一个案例

js 复制代码
let obj = {name:'zbz', age:18}
 let a = Object.create(obj)
console.log(a.__proto__)    //返回值是 {name:'zbz', age:18}

function A(){//构造函数

}
 A.prototype = Object.create(obj)
 console.log(A.prototype.__proto__) //返回值是 {name:'zbz', age:18}

所以 object.create所做的事情就是连接原型链

  1. instanceof源码简化版
JavaScript 复制代码
function myInstanceof(obj, constructorFunc) {
  let prototype = constructorFunc.prototype;
  //拿到实例对象
  let proto = Object.getPrototypeOf(obj);
  //拿到构造函数的实例对象
  while(proto !== null) {
  //循环判断是否相等 若实例对象相等
  //且构造函数的实例对象的constructor属性与构造函数相等
  //证明该对象是构造函数实例
    if(proto === prototype || proto.constructor === constructorFunc) {
      return true;
    }
    proto = Object.getPrototypeOf(proto);     //获取下一个构造函数的实例对象,类似于取__proto__属性,代码中建议使用getPrototypeOf这种方法
  }
  
  return false;
}

默认的prototype其实是有constructor属性的,该属性指向本构造函数

我们以构造函数A举例,即 A.prototype.constructor === A

但是:当我们对一个构造函数的prototype赋值的时候,请记得给他负一个constructor属性,这个属性指向该 构造函数。 如不手动赋值,则默认的constructor会指向Object构造函数 举个栗子:

javascript 复制代码
function A(){

}
console.log(A.prototype.constructor === A)  //返回true
let a = new A()
console.log(a instanceof A)  // true
A.prototype = Object.create({b:1})
console.log(A.prototype.constructor === A) //false
console.log(A.prototype.constructor === Object) //true  
//这时constructor默认指向Object 这会影响instanceof的判断

console.log(a instanceof A) // false
console.log(a instanceof Object) // true
A.prototype.constructor = A
console.dir(A) 
console.log(A.prototype.constructor === A) //true
let b = new A()
console.log(b instanceof A) // true
console.log(b instanceof Object) // true
console.log(a instanceof A)// false (下一行描述原因)
//原型链在对象构造的时候已经确定即使在之后我改变了 但是也无法生效
-------------------------------------------------------------------
所以要在赋值给原型对象之后 先进行原型对象构造函数的赋值 在去实例话对象
  1. Object.prototype.toString.call()

Object的原型链上的toString方法被所有的对象继承,它返回一个表示对象类型的字符串。!!

当你使用Object.prototype.toString.call(value)时,它会返回一个字符串,格式为"[object Xxx]",其中"Xxx"表示具体的类型。

javascript 复制代码
-   数组:`Object.prototype.toString.call([])` 返回 "[object Array]"
-   对象字面量:`Object.prototype.toString.call({})` 返回 "[object Object]"
-   字符串:`Object.prototype.toString.call("hello")` 返回 "[object String]"
-   数字:`Object.prototype.toString.call(123)` 返回 "[object Number]"
-   布尔值:`Object.prototype.toString.call(true)` 返回 "[object Boolean]"
-   函数:`Object.prototype.toString.call(function(){})` 返回 "[object Function]"
-   正则表达式:`Object.prototype.toString.call(/regex/)` 返回 "[object RegExp]"

结尾: 如果这篇文章有帮助大家加深对原型链的理解,请点个赞支持一下 👍

相关推荐
WeiXiao_Hyy8 分钟前
成为 Top 1% 的工程师
java·开发语言·javascript·经验分享·后端
吃杠碰小鸡25 分钟前
高中数学-数列-导数证明
前端·数学·算法
kingwebo'sZone31 分钟前
C#使用Aspose.Words把 word转成图片
前端·c#·word
xjt_09011 小时前
基于 Vue 3 构建企业级 Web Components 组件库
前端·javascript·vue.js
我是伪码农1 小时前
Vue 2.3
前端·javascript·vue.js
夜郎king1 小时前
HTML5 SVG 实现日出日落动画与实时天气可视化
前端·html5·svg 日出日落
辰风沐阳2 小时前
JavaScript 的宏任务和微任务
javascript
夏幻灵2 小时前
HTML5里最常用的十大标签
前端·html·html5
冰暮流星2 小时前
javascript之二重循环练习
开发语言·javascript·数据库
Mr Xu_3 小时前
Vue 3 中 watch 的使用详解:监听响应式数据变化的利器
前端·javascript·vue.js