• 作者:老汪软件技巧
  • 发表时间:2024-12-15 17:03
  • 浏览量:

作为一个初入前端的小白,原型链的概念太多,一直觉得难以理解,对整个原型链的了解非常模糊。理解原型链是深入学习js的一小步,在参考诸多大佬文章后,整理笔记如下:一,原型

原型与原型链,首先我们要知道什么是原型。但在开始了解原型之前,先认识下js中的对象。

对象

对象(Object)是一种复合数据类型,它是一种无序的键值对集合。对象用于存储和传递多个值,每个值都有一个键(key)与之关联。

对象的特点:

1. 创建对象

有多种方式可以创建对象:

1.1 对象字面量方式

这是最常见、最简洁的方式,用 {} 包裹键值对:

let person = {
  name: 'John',
  age: 30,
  greet: function() {
    console.log('Hello ' + this.name);
  }
};

1.2 使用 new Object() 创建对象

你也可以使用 new Object() 来创建对象:

let person = new Object();
person.name = 'John';
person.age = 30;
person.greet = function() {
  console.log('Hello ' + this.name);
};

这种方式与字面量方式相比少用了几行代码,但不常用,因为字面量语法简洁直观。

1.3 使用构造函数创建对象

你可以通过定义构造函数来创建对象。构造函数是一个用于初始化对象的特殊函数:

function Person(name, age) {
  this.name = name;
  this.age = age;
  this.greet = function() {
    console.log('Hello ' + this.name);
  };
}
let person1 = new Person('John', 30);
let person2 = new Person('Alice', 25);

日常中我们最常用的应该是字面量方式,那为什么会出现第三种构造函数方式呢?

想想看我们需要多个obj1,obj2的时候,字面量方式我们需要重复代码去创建对象;而使用构造函数的方式只需要写一遍属性和方法,我们就可以通过new关键字,new出多个不同的对象。

试试在控制台打印如下person1.greet === person.greet,false 看起来调用的是同一个函数方法,实际并不相等。因为他们的内存地址是不同的,每个new出来的person1 和 person2 都包含一份独立的属性和方法(可能导致浪费内存)

function Test(){
    this.name = 'rose'
    this.say = function(){
        console.log('我能说话')
    }
}
var obj3 = new Test()
var obj4 = new Test()
 
obj3.say === obj4.say // false
obj3.name === obj4.name // true

你可能会问为什么obj3.name和obj4.name是相等的呢?刚才不是说的内存不同独立的属性和方法吗?

为什么是 true:比较:基本类型 和 引用类型

引用类型(例如 object、array、function) :

那就是说每实例化一个对象都会创建一个新的say这未免太浪费内存了吧!有没有什么解决方法?

原型

上面的例子中, obj3 和 obj4 都需要调用 Test 中的say()方法,我们有没有办法将公共方法放到一个公共的地方呢? 这时候有请公共的原型(prototype)登场

在js中,每一个对象(函数也是对象)都有一个特殊的属性叫做原型(prototype),它指向另一个对象,这个对象(Test.prototype)被称为原型对象, 原型对象是用来共享属性和方法的

原型对象:

(1)原型对象有一个constructor属性指向构造函数本身(Test)。

(2),原型对象是一个普通的对象,它包含属性和方法。

(3),原型对象的属性和方法会被继承到所有通过原型链与它相连的对象。

简单来说,原型对象是一个普通对象,属性和方法会被继承到其他对象,而每个对象都有一个原型(prototype),用来继承其他对象的属性和方法此时,我们就可以把say方法放到这个原型对象上, obj3 和 obj4 就可以访问这个方法,再不用写到Test中去重复占用内存,所有new出来的实例都可以使用此方法

function Test() {
 this.name = 'rose';
}

js原型与原型链(精心总结)(一文搞懂)(建议收藏)❗❗❗__js原型与原型链(精心总结)(一文搞懂)(建议收藏)❗❗❗

// 将 say 方法添加到原型上 Test.prototype.say = function() { console.log('我能说话'); }; var obj3 = new Test(); var obj4 = new Test(); console.log(obj3.say === obj4.say); // 输出: true

在这种情况下,obj3.say 和 obj4.say 都指向同一个函数实例,因此 obj3.say === obj4.say 返回 true。

构造函数和实例之间就初步构成了这样一个关系,如图:

屏幕截图 2024-12-14 155206.png

二,隐式原型__proto__

1 proto在js中,每个对象都有一个“ proto ”属性(左右两边两个短下划线),这个__proto__就被称为隐式原型。(记住这点)

实例对象当然也是对象,也存在__proto__属性

console.log(obj3.proto === Test.prototype)// true打印以上obj3.proto === Test.prototype结果为true,所以:

(1)每个js对象都有一个隐藏的原型对象属性__proto__,它指向创建它的构造函数的原型对象(Test.prototype)

(2)proto__存在的意义在于为原型链查找提供方向,原型链查找靠的是__proto,而不是prototype(画重点,后面要考!!!)

实例对象obj3通过__proto__指向了Test的原型对象(Test.prototype),如图:

屏幕截图 2024-12-14 154755.png

来我们练习一下

function Test(name, age){
    this.name = name
    this.age = age
}
Test.prototype.say = function(){
    console.log('我能说话')
}
var obj3 = new Test('Jack', 26)
 
1, 构造函数是? 实例是?
2, obj3.constructor === Test   true or false?
3, obj3.__proto__ === Test ?
4, Test.prototype === obj3.__proto__ ?
5, obj3.__proto__.constructor === Test ?
 
// 1, Test  obj3  2,true  3,false  4,true  5,true

怎么样,都答对了吗?

三,原型链1,Object.prototype

在上面第二点中,每个js对象都有一个隐藏的原型对象属性__proto__

那Test的原型对象Test.prototype会不会也有一个隐式原型__proto__呢? 控制台输出如下:

image.png

Test.prototype当然也存在一个属性__proto__,而这个Test.prototype.__proto__到底是谁呢?

image.png

Test.prototype.__proto__ === Object.prototype
// true

(1) Test.prototype的隐式原型(proto)就是Object.prototype

(2) 所有的对象,包括构造函数的原型对象,最终都继承自Object.prototype,这是js原型链的顶点

(3) 由于所以的”普通“对象都”源于“这个Object.prototype对象,所以它包含JavaScript中许多通用的功能。

比如:.toString()和.valueOf(),.hasOwnProperty(..)等

Object.prototype是从哪里来的呢?当然是由Object的属性prototype指向来的。Object.prototype同样也会存在属性 constructor指回Object(【目录 2,原型 原型对象】中提到)

此时的关系图:

image.png

2,链

在控制台打印Object.prototype,会发现 Object.prototype也是一个对象

那么也就是说它也存在隐式属性__proto__。想想看,如果Object.prototype.__proto__再去指向某个对象的原型(prototype),那整条线就显得无穷无尽,一直找下去让我们来试试呢

js代码的开发者当然考虑到了,Object.prototype作为原型链的顶端,位于原型链的最末端。因此,它不再有自己的原型,所以Object.prototype.proto 指向null,表示原型链的终点

原型链的终点是null

Object.prototype.proto === null

这时候我们的关系图就变成了:

image.png

每个对象都有一个原型(prototype),它指向另外一个对象,而指向的对象又存在属性(proto)指向另外一个对象。当我们访问对象(obj3)的属性时,会先在对象定义的属性中进行查找,没找到就会沿着__proto__一路向上查找,最终形成一个链式结构,这整个链式结构就叫做原型链

如果在原型链中找到了这个属性,就返回找到的属性值;如果整个原型链都没找到这个属性值,则返回 undefined,没找到方法直接报错(not a function)

总结

原型和原型链是 JavaScript 继承机制的核心,对于理解 JavaScript 的对象模型和继承特性至关重要。如果你有更多问题,或者想深入了解某个部分,可以在评论区进行讨论哦!

比心:


上一条查看详情 +React Fiber调度调和流程原理
下一条 查看详情 +没有了