- 作者:老汪软件技巧
- 发表时间: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';
}
// 将 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。
构造函数和实例之间就初步构成了这样一个关系,如图:
二,隐式原型__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),如图:
来我们练习一下
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__呢? 控制台输出如下:
Test.prototype当然也存在一个属性__proto__,而这个Test.prototype.__proto__到底是谁呢?
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,原型 原型对象】中提到)
此时的关系图:
2,链
在控制台打印Object.prototype,会发现 Object.prototype也是一个对象
那么也就是说它也存在隐式属性__proto__。想想看,如果Object.prototype.__proto__再去指向某个对象的原型(prototype),那整条线就显得无穷无尽,一直找下去让我们来试试呢
js代码的开发者当然考虑到了,Object.prototype作为原型链的顶端,位于原型链的最末端。因此,它不再有自己的原型,所以Object.prototype.proto 指向null,表示原型链的终点
原型链的终点是null
Object.prototype.proto === null
这时候我们的关系图就变成了:
每个对象都有一个原型(prototype),它指向另外一个对象,而指向的对象又存在属性(proto)指向另外一个对象。当我们访问对象(obj3)的属性时,会先在对象定义的属性中进行查找,没找到就会沿着__proto__一路向上查找,最终形成一个链式结构,这整个链式结构就叫做原型链
如果在原型链中找到了这个属性,就返回找到的属性值;如果整个原型链都没找到这个属性值,则返回 undefined,没找到方法直接报错(not a function)
总结
原型和原型链是 JavaScript 继承机制的核心,对于理解 JavaScript 的对象模型和继承特性至关重要。如果你有更多问题,或者想深入了解某个部分,可以在评论区进行讨论哦!
比心: