likes
comments
collection
share

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

作者站长头像
站长
· 阅读数 42

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

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

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

我们直接开始:

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

原型: 在JavaScript中,每个对象都有一个关联的原型(prototype)。原型是一个对象,它自身拥有属性和方法,并且其他对象可以通过原型进行属性和方法的继承

原型链: 原型链是JavaScript中实现对象继承的一种机制。它通过对象的原型属性(prototype)在对象之间形成一个链式的关系。

思考?

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

答案就是: 通过__proto__

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

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

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

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

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

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,我们看一下里面到底是什么?

我们创建一个函数

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

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

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

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

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

console.log(testOne.prototype.constructor === testOne)
一文带你深度理解原型和原型链(长达八千字 全面解析)

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

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

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"构造函数提供的所有方法和特性,可以被调用和执行。

// 声明一个普通函数
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实例上的方法呢?答案是显然易见的,我们直接放代码,测试一下

let obj = {}
console.dir(obj.__proto__)
console.log(obj.__proto__ === Object.prototype)

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

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

Function 和 Object 有什么关系呢?

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

console.log(Function instanceof Object)
console.log(Object instanceof Function)

大家猜猜这个结果是什么? 答案是:返回了两个true 是不是,突然就懵掉了。

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

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

Function.prototype.__proto__ === Object.prototype

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

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

Object.constructor===Function       
Object.__proto__ === Function.prototype

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

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

  1. for...in

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

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

思考object.create到底做了什么

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

我们测试一个案例

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源码简化版
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构造函数 举个栗子:

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"表示具体的类型。

-   数组:`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]”

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

转载自:https://juejin.cn/post/7262921723413692475
评论
请登录