在与和我的后端朋友交流链表概念时,联想到了原型链。想到自己对原型链没什么了解,在参考一些文章和视频后(感谢网上的各位大佬!!!),带着自己的理解整理笔记如下,大家一起学习,最后还有原型链的应用场景
一、prototype
引言
情景:如果现在有两个对象,比如一个动物对象,和一个猫咪对象,那么最好是猫咪对象可以通过继承动物对象,拥有动物对象所有的属性和方法,这对于代码的复用是非常有效的,并且也可以减少资源的浪费
大多面向对象的编程语言,比如java,都是通过类(class)实现对象的继承。JS的语言的继承不通过class,而是通过原型对象(prototype)实现,es6当中有类的继承,但是本质还是通过原型对象,为什么说继承可以减少资源的浪费呢,我们来看下面这个例子
function Animal(name, age) {
this.name = name
this.age = age
this.shout = function () {
console.log('发出叫声');
}
}
let cat = new Animal('咪咪', 4)
let dog = new Animal('旺财', 5)
console.log(cat, dog);
console.log(cat.shout == dog.shout); // false
可以看到,按照常理来说,不同实例对象的属性值不同这很正常。但是即使方法一样,在不同实例之间也不能共享(打印出来是false)。因为地址不同,每生成一个实例对象, 就要创造一个相同行为的方法,这样就造成了资源的浪费
这时候就需要祭出我们的prototype属性,什么东西有属性,是不是对象,所以说万物皆可对象。每个函数都有一个prototype属性,指向一个对象,如下所示
function fn() { }
console.dir(fn)
console.log(fn.prototype, typeof fn.prototype);
二、原型对象,隐式原型和显式原型
1. 原型对象
所以此时我们可以把上面的例子改造一下了
function Animal(name, age) {
this.name = name
this.age = age
}
Animal.prototype.shout = function () {
console.log('发出叫声');
}
let cat1 = new Animal('小咪', 6)
let dog1 = new Animal('阿黄', 7)
cat1.shout()
dog1.shout()
console.log(Animal.prototype.constructor === Animal) // true
console.log(cat1.constructor === Animal); // true
前面提到,在JS中,每个函数都有一个prototype属性,指向一个对象(Animal.prototype)被称为原型对象, 原型对象是用来共享属性和方法的
打印Animal.prototype可以看到上图中原型对象存在一个constructor属性,指向Animal
对于通过构造函数创建的对象实例来说,这个constructor属性会指向相应的构造函数
2. 隐式原型和显式原型
在JS中,每个对象都有一个“ __proto__ ”属性(左右两边两个短下划线),这个__proto__就被称为隐式原型,xxx.prototype就是显式原型。 在下方代码打印cat1.__proto__ === Animal.prototype 为 true,并且可以看到共享的方法shout,在不同实例(cat1,dog1)之间也可以共用了
所以啊,原型对象的属性和方法可以被实例对象共享,不仅节省了内存,还体现了实例对象之间的联系
console.dir(Animal.prototype)
console.dir(cat1.__proto__) // 也可以写成这样 console.dir(Object.getPrototypeOf(cat1));
console.log(cat1.__proto__ === Animal.prototype); // true
console.log(cat1.shout == dog1.shout); // true
-
Object.getPrototypeOf()
方法:这是获取对象[[Prototype]]的标准方式,Object.getPrototypeOf(cat1)
同样返回Animal.prototype
稍微总结一下
- 每个对象在创建时都会关联一个内部属性,称为[[Prototype]],它通常通过
__proto__
访问器属性来引用。这个[[Prototype]]指向了创建该对象的构造函数的prototype
属性所指定的对象。因此,当通过构造函数(如Animal
)创建对象实例(如cat
)时,实例的[[Prototype]]会链接到构造函数的原型对象(即Animal.prototype
)。__proto__
的存在意义在于支持原型链查找机制:当尝试访问对象的一个属性或方法时,如果该对象本身没有定义此属性,则JavaScript引擎会沿着[[Prototype]]链向上查找
原型对象的属性(方法)不是实例对象自身的属性(方法),原型对象属性一但修改,实例对象也会响应变化,如下:
Animal.prototype.shout = function () {
console.log(this.name + '发出叫声');
}
Animal.prototype.color = 'yellow'
console.log(cat1.color);
cat1.shout()
dog1.shout()
原型对象就是用来定义共享的属性和方法,一般用来共享方法,实例对象可以视作从原型对象上衍生出来的子对象
三、原型链
JS规定,所有对象都有自己的原型对象,函数也是对象,所以也不例外,函数实际是由构造函数Function实例化而来的
let fn = new Function
console.dir(fn)
Function.prototype.a = 111
console.log(fn.a);
console.log(fn.__proto__ === Function.prototype); // true
即使将上面代码构造函数的形式从new function()
换成function Fn() { }
,打印出来的结果也是一样的
所以结合以上内容,我们可以画出这样一张图
不过呢,这个链条并不是无穷尽的,在JS中,
Object
是所有对象的基类,所有的对象最终都会链接到Object.prototype
。这意味着即使是最简单的自定义对象或函数,它们的原型链也会终止于Object.prototype
,在往后就是null
所以最后这张图可以完善成这样
let obj = new Object()
在接下来的内容中,我将通过几个打印继续讲解这个图的一些细节,大家也可以停下来先思考一下
console.log(obj.__proto__ === Object.prototype);
console.log(obj.constructor === Object);
console.log(Animal.prototype.__proto__ === Object.prototype);
console.log(Function.__proto__ === Function.prototype);
console.log(Function.constructor === Object);
console.log(Function.prototype.__proto__ === Object.prototype);
console.log(Object.__proto__ === Object.prototype);
console.log(Object.prototype.__proto__); // null原型链的尽头
console.log(Object.__proto__ === Function.prototype);
console.log(Animal.prototype.__proto__ === Object.prototype); // true
因为Animal.prototype是一个对象,对象的隐式原型指向的就是顶级对象的Object.prototype
console.log(Function.__proto__ === Function.prototype); // true
console.log(Function.constructor === Object); // false
console.log(Function.prototype.__proto__ === Object.prototype);
打印这两个原型,会发现二者互相相等 Function.prototype === Function.__proto__ 是不是很神奇? 看起来像是自己创造了自己
实际上,
Function.prototype === Function.__proto__
为true
并不是表示Function
构造函数“创造了自己”。实际上,Function
是一个内置对象,在代码运行前就已经由宿主环境提供。为了保持一致性,Function
的[[Prototype]](即__proto__
)被设置为Function.prototype
。这种设计确保了Function
构造函数作为一个函数对象,遵循与其他所有函数相同的原型链规则,体现了语言内部的一致性。所以理所应当,Function.constructor 指向的也应该要是 Function
但是自己指向自己并没有意义。 别忘记Object.prototype才是原型链的顶点,Function.prototype存在于原型链中必然会与Object.prototype存在关联,Function.prototype是一个对象,当然会拥有隐式原型(__proto__),指向顶级对象Object.prototype
上面有关Object的打印判断当然也是同理
console.log(Object.__proto__ === Function.prototype);
至于最后一条,再往下深究一点,Object也是函数对象,Object.__proto__ 也会指向Function.prototype
现在对于上面那个图会不会更清晰一点了
四、应用场景
1. jQuery使用原型链来提供链式调用。
相信JQ库大家一定不会陌生,下面是一小部分的JQ代码
(function(global) {
function $() { /* jQuery implementation */ }
// 创建$.fn并将其指向$.prototype
$.fn = $.prototype = {
init: function(selector, context) {
// 初始化逻辑,例如选择DOM元素
this.elements = document.querySelectorAll(selector);
return this; // 返回this以支持链式调用
},
css: function(property, value) {
for (let el of this.elements) {
el.style[property] = value;
}
return this; // 返回this以支持链式调用
},
show: function() {
for (let el of this.elements) {
el.style.display = 'block';
}
return this; // 返回this以支持链式调用
}
// 其他方法...
};
global.$ = $;
})(window);
// 使用时:
$('#element').css('color', 'red').show();
在这段代码中,原型链主要体现在以下几个方面:
共享方法:所有通过
$
创建的对象实例都可以访问$.prototype
上定义的方法(如css
和show
),因为它们的[[Prototype]]都指向同一个原型对象。动态扩展:你可以在运行时动态地向
$.prototype
添加新方法,所有现有的和未来的实例都会自动获得这些新方法。链式调用:通过确保每个方法返回
this
,你可以实现链式调用,提高代码的简洁性和可读性。
2. Vue 组件继承
// BaseComponent.vue
export default {
methods: {
baseMethod() {
console.log('基础组件的方法');
}
}
};
// ExtendedComponent.vue
export default {
extends: BaseComponent,
methods: {
extendedMethod() {
console.log('继承组件的方法');
}
},
mounted() {
this.baseMethod(); // 调用基类的方法
this.extendedMethod();
}
};
在现代前端框架(如 Vue 和 React)中,虽然它们更倾向于使用 ES6 类语法或函数式组件,但底层仍然依赖于原型链来实现继承和组件复用。例如,在 Vue 中,你可以通过
extends
或mixins
来实现组件之间的代码复用,这实际上是在利用原型链的概念。
本文中如果有描述不对的地方,可以私信或评论博主修改哈哈哈哈哈哈 【doge】
标签:__,Function,console,log,JS,理解,原型,prototype From: https://blog.csdn.net/2302_80633106/article/details/144872193