你不知道的JS(六):初识原型链

本文详细解析了JavaScript中构造函数、原型对象和实例对象之间的关系,以及原型链的工作原理。重点讲解了new操作如何创建实例并链接原型,原型链的动态性和原型共享的特点。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前言

别再背__proto__prototype,直接理解就完了。

  • 一开始了解到隐式原型和显示原型,以为是两种东西,后来才知道是一样的。
  • 两个都是属性,只是属性名不同,属性值是相同的,属性值保存的都是原型对象的引用。

看完下面内容,你就明白了。

一、原型对象

1. 三者关系

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jlMKEayz-1651567516522)(C:\Users\USER\Desktop\前端面试\D博客文章\image\原型链-2.PNG)]

这张图中有三个主体,分别是实例对象、构造函数和原型对象。主要是理解这三者的关系。

  • 构造函数&原型对象:当代码执行过程创建了函数A,创建这个函数的原型对象B,放在这个函数的__prototype__属性上。原型对象B的constructor指向该函数A。
  • 实例对象&构造函数:对函数A进行new操作,这个函数A就是构造函数,而new操作就是为了生成的对象C。
  • 实例对象&原型链:在上面new操作的过程中,将函数A的原型对象复制到对象C的__proto__属性上,(对象复制的是引用),所以对象C的__proto__属性指向和构造函数指向的是同一个原型对象。

以下同代码配合说明一下:

function A (){...}// 当执行到这段代码时,会创建原型对象B,就会有A.__prototype__ = B
const C = new A() // 对A执行new操作,产生对象C,而且会有C.__proto__ = B

从上面可以看出,三者当中,对象C和构造函数A没有直接联系。只跟变量A有关系。

但是new操作做了什么?对象C的属性生成跟构造函数A有什么关系?

2. new操作

前面提到的new操作,其实并不神秘

内部其实是

  • 生成一个空对象,将构造函数A的原型对象赋值到这个空对象的__proto__属性上。
  • 然后将构造函数作为该对象的方法执行。执行构造函数过程中为该对象添加属性和方法。

代码说明一下:

// 模拟new操作
function myNew(constructor, arg) {
    // 1. 创建一个新对象
    const obj = {};
    
    // 2. 为新对象添加属性__proto__,将该属性链接至构造函数的原型对象
    obj.__proto__ = constructor.prototype;
    
    // 3. 执行构造函数,this被绑定在新对象上,为对象
    const res = constructor.call(obj, ...arg);
    
    // 4. 确保返回一个对象
    return res instanceof Object ? res : obj;
}

继续展开call方法

function myNew(constructor, arg) {
    // 1. 创建一个新对象
    const obj = {};
    
    // 2. 为新对象添加属性__proto__,将该属性链接至构造函数的原型对象
    obj.__proto__ = constructor.prototype;
    
    // 3. 执行构造函数,this被绑定在新对象上,为对象
    // const res = constructor.call(obj, ...arg);
    obj.fn = constructor;
    const res = obj.fn();
    delete context['fn']
    
    // 4. 确保返回一个对象
    return res instanceof Object ? res : obj;
}
  • 其实是将构造函数作为的方法执行,为obj添加属性和方法。

到这里可能还不够清晰,执行构造函数怎么给对象添加属性

那么举个例子分析一下:

function Student (){
    this.name = '张三';
    this.age = '33';
}
const newStudent = new Student()
console.log(newStudent.name) // "张三"
// 也就是构造函数中的this.xxx会变成实例的属性和方法

构造函数的本质

  • 函数本身不是构造函数,当你在普通函数调用前加上new关键字后,就会把这个函数调用变成一个“构造函数调用”。
  • JavaScript中对于“构造函数”最准确的解释是,所有带new的函数调用。

3. 小结

  • 每个函数都有一个公有的不可枚举的 prototype 属性,指向原型对象。(注意,不包括箭头函数,全文其他位置也一样。)
  • 原型对象自动获得一个 constructor 的属性,指回与之关联的构造函数。
  • 对函数执行new操作,就会创建一个新实例,这个实例的内部__proto__属性就会被赋值为构造函数的原型对象。

这也就是上面那副关系图。

二、原型链

理解了原型,理解原型链就简单多了。

1. 理解原型链

如果原型对象C也有自己原型对象,那么就像这样:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kjEY20ht-1651567516530)(C:\Users\USER\Desktop\前端面试\D博客文章\image\原型链-1.PNG)]

图中右边的原型对象也又constructor属性指向构造函数。(两者成对存在)

如果一层一层往下连下去,就形成了原型链。

2. [[Prototype]]属性

  • JavaScript的对象有一个特殊的[[Prototype]]内置属性,其实就是对于其他对象的引用。在浏览器中可以通过__proto__访问到。
  • 所有对象在创建是,[[Prototype]]属性都被赋予一个非空的值。

[[Prototype]]的引用有什么用?

  • 当你试图引用对象的属性时,会触发[[Get]]操作。
  • 对于默认的[[Get]]操作来说,第一步是检查对象本身是否有这个属性,如果有的话就使用它。
  • 如果无法在对象本身找到需要的属性,就会继续访问对象的[[Prototype]]链。

(该讨论在ES6的proxy中不适用)

3. 原型链的顶层

到哪里是尽头呢?

  • 所有普通的prototype链都会指向内置的 Object.prototype

为什么是指向Object.prototype呢?

比如对象a的创建过程:

// 字面量创建对象
const a = {
	tip: '我是对象a'
}
// 构造函数创建对象
const a = new Object() // Object是内置函数,这里a继承了Object().prototype
a.tip = '我是对象a'
  • 对象字面量是对象定义的简写形式,目的是为了简化包含大量属性的对象的创建。
  • 除了Object(),JS中还有其他内置函数,比如:Array(),String(),Math()等,由此产生不同的实例对象:数组、字符串等。

这些其实是new操作的内容。

4. 原型属性共享

前面已经说过,原型对象中的属性可以被实例对象访问到,这里原型的作用。那么对于原型链来说,也是一样。

(1)原理

其实,也就是对象属性查找机制

  • 在通过对象访问属性时,会按照这个属性的名称开始搜索。
  • 如果在这个实例上发现了给定的名称,则返回该名称对应的值。
  • 如果没有找到这个属性,则搜索会进入原型对象,然后在原型对象上找到属性后,再返回对应的值。如果没找到,则会继承搜索实例的原型,直到Object.__prototype__

(2)判断是不是自身属性

因为对象可以访问到原型上的属性,所以有些时候需要判断是自身属性还是原型上的属性。

  • 使用 hasOwnProperty() 来检查对象自身中是否含有该属性
  • 使用 in操作符检查对象中是否含有某个属性时,如果对象中没有但是原型链中有,也会返回 true

如果用 in 返回为true,hasOwnProperty()返回false,则说明属性在原型链上。

5. 原型链的问题

(1)引用值问题

原型中包含的引用值会在所有实例间共享。

  • 就是说,一个实例对象修改了原型上的引用类型,其他实例访问这个属性的时候,只能获取到改变后的。
  • 但,如果属性值是原始类型,实例对象会在自身添加该属性,不会修改原型上的。其他实例获取到的属性只不会改变。

(2)无法传参

  • 子类型在实例化时不能给父类型的构造函数传参。
  • 由于前面这两个问题,导致原型链基本不会被单独使用。

6. 原型的动态性

  • 因为从原型上搜索值的过程是动态的,所以即使实例在修改原型之前已经存在,任何时候对原型对象所做的修改也会在实例上反映出来。
  • 这个在前面在属性共享也有类似说明(一个修改影响到其他的),不过这里可以只有单个实例。

举个例子

let friend = new Person(); 
Person.prototype.sayHi = function() {  // 直接修改原型对象
 console.log("hi"); 
}; 
friend.sayHi(); // "hi",没问题! // 执行了原型对象上新增的属性

其实原型对象本身就是一个对象,原型对象改变了,通过引用获取到的自然也是改变了的。

7. 小结

  • 原型链机制建立起对象与对象之间的联系。在对象自身找不到的属性,会到原型链上查找。
  • 原型链的顶层是Object.prototype,对象属性查找到这里结束。
  • 原型链的属性共享,修改引用类型的属性值会影响到其他实例。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值