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

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

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

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

我们直接开始:

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

原型
在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 ;
}
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 ;
}
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)// 函数的原型对象
function testOne(){}
console.log('testOne.prototype',testOne.prototype)// 函数的原型对象  
function testOne(){} console.log('testOne.prototype',testOne.prototype)// 函数的原型对象

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

image.png

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

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

console.log(testOne.prototype.constructor === testOne)
console.log(testOne.prototype.constructor === testOne)
console.log(testOne.prototype.constructor === testOne)

image.png

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

还有呢?对象是怎么通过原型属性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()
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()
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()

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

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

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

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

函数

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

image.png

这里我们要去思考了,我们声明一个函数的时候到底发生了什么,这个函数继承了来自哪里的方法?
其实在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
// 声明一个普通函数
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
// 声明一个普通函数 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)
let obj = {}
console.dir(obj.__proto__)
console.log(obj.__proto__ === Object.prototype)
let obj = {} console.dir(obj.__proto__) console.log(obj.__proto__ === Object.prototype)

image.png

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

Function 和 Object 有什么关系呢?

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

console.log(Function instanceof Object)
console.log(Object instanceof Function)
console.log(Function instanceof Object)
console.log(Object instanceof Function)
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
Function.prototype.__proto__ === Object.prototype
Function.prototype.__proto__ === Object.prototype

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

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

Object.constructor===Function
Object.__proto__ === Function.prototype
Object.constructor===Function       
Object.__proto__ === Function.prototype
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]);
}
}
for (var key in xxx) {
  // 检查属性是否为对象自身的属性
  if (xxx.hasOwnProperty(key)) {
    console.log(key + ": " + xxx[key]);
  }
}
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;
}
//源码
function create(prototype) {
  // 创建一个临时的构造函数
  function Temp() {}
  // 将临时构造函数的原型设置为指定的原型对象
  Temp.prototype = prototype;
  // 创建一个新对象实例
  var obj = new Temp();
  // 清空临时构造函数的引用
  Temp.prototype = null;
  // 返回新对象实例
  return obj;
}
//源码 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}
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}
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;
}
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;
}
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 (下一行描述原因)
//原型链在对象构造的时候已经确定即使在之后我改变了 但是也无法生效
-------------------------------------------------------------------
所以要在赋值给原型对象之后 先进行原型对象构造函数的赋值 在去实例话对象
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 (下一行描述原因)
//原型链在对象构造的时候已经确定即使在之后我改变了 但是也无法生效
-------------------------------------------------------------------
所以要在赋值给原型对象之后 先进行原型对象构造函数的赋值 在去实例话对象
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]
-   数组:`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]”
- 数组:`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]”

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

© 版权声明
THE END
喜欢就支持一下吧
点赞0

Warning: mysqli_query(): (HY000/3): Error writing file '/tmp/MYcZpgKz' (Errcode: 28 - No space left on device) in /www/wwwroot/583.cn/wp-includes/class-wpdb.php on line 2345
admin的头像-五八三
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

图形验证码
取消
昵称代码图片