首页 > 其他分享 >带你理解JS原型和原型链

带你理解JS原型和原型链

时间:2025-01-01 21:27:16浏览次数:3  
标签:__ Function console log JS 理解 原型 prototype

在与和我的后端朋友交流链表概念时,联想到了原型链。想到自己对原型链没什么了解,在参考一些文章和视频后(感谢网上的各位大佬!!!),带着自己的理解整理笔记如下,大家一起学习,最后还有原型链的应用场景

一、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();

     在这段代码中,原型链主要体现在以下几个方面:

  1. 共享方法:所有通过 $ 创建的对象实例都可以访问 $.prototype 上定义的方法(如 cssshow),因为它们的[[Prototype]]都指向同一个原型对象。

  2. 动态扩展:你可以在运行时动态地向 $.prototype 添加新方法,所有现有的和未来的实例都会自动获得这些新方法。

  3. 链式调用:通过确保每个方法返回 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 中,你可以通过 extendsmixins 来实现组件之间的代码复用,这实际上是在利用原型链的概念。

本文中如果有描述不对的地方,可以私信或评论博主修改哈哈哈哈哈哈 【doge】

标签:__,Function,console,log,JS,理解,原型,prototype
From: https://blog.csdn.net/2302_80633106/article/details/144872193

相关文章

  • Python 虚拟环境:原理解析与最佳实践
    从一个困境说起小王最近遇到了一个棘手的问题:他在维护两个Python项目,一个是去年开发的数据分析系统,依赖TensorFlow1.x;另一个是最近在做的预测模型,需要用到TensorFlow2.x的新特性。每次切换项目时,他都要手动更改Python包的版本,这不仅繁琐,而且经常出错。"难道就没有办法......
  • Notes.js的安装和配置
    一、安装1、下载https://nodejs.org/en/download/2、安装  3、测试打开cmd查看node和npm版本node-vnpm-v 二、环境配置1、找到安装目录,新建node_cache和node_global文件夹 2、以管理员权限打开cmd输入npmconfigsetprefix"D:\nodejs\node_gl......
  • JSP复制WORD图片粘贴上传控件
    编辑器:百度ueditor前端:vue2,vue3,vue-cli,react,html5需求:复制粘贴word内容图片,word图片转存交互,导入pdf,导入PowerPoint(PPT),web截屏要求:开源,免费,技术支持用户体验:Ctrl+V快捷键操作平台:Windows,macOS,Linux,RedHat,CentOS,Ubuntu,中标麒麟,银河麒麟,统信UOS,信创国......
  • package.json 里面 sideEffects 属性的作用
    一、sideEffects的定义和目的在package.json中的sideEffects属性用于告诉构建工具(如Webpack4+),在打包过程中哪些文件具有副作用(sideeffects),哪些文件没有副作用。副作用是指当导入一个模块时,除了导出模块外,该模块是否会对其他模块或全局环境产生额外的影响,例如修改全局变量......
  • 用 nodejs 实现一个命令行工具,统计输入目录下面指定代码的行数
    以下是一个使用Node.js实现的命令行工具,用于统计输入目录下指定代码文件的行数。实现思路接收命令行参数,获取输入目录和文件扩展名(例如,.js、.html、.css等)。递归遍历输入目录,查找所有符合指定扩展名的文件。对于每个找到的文件,读取文件内容并统计行数。输出统计结果。......
  • nodejs安装之npm ERR! code CERT_HAS_EXPIREDnpm ERR! errno CERT_HAS_EXPIRED reason
    nodejs安装之npmERR!codeCERT_HAS_EXPIREDnpmERR!errnoCERT_HAS_EXPIREDreason:certificatehasexpired-证书错误通用问题解决方案-优雅草央千澈问题背景$npminstallelectron-gnpmERR!codeCERT_HAS_EXPIREDnpmERR!errnoCERT_HAS_EXPIREDnpmERR!reque......
  • 人工智能短视频内容理解与生成技术在美团的创新实践15
     1.背景美团围绕丰富的本地生活服务电商场景,积累了丰富的视频数据。美团场景下的短视频示例上面展示了美团业务场景下的一个菜品评论示例。可以看到,视频相较于文本和图像可以提供更加丰富的信息,创意菜“冰与火之歌”中火焰与巧克力和冰淇淋的动态交互,通过短视频形式进......
  • 深入理解计算机系统 4.3 Y86-64 的顺序实现
    4.3.1将处理组织成阶段通常,处理一条指令包括很多操作。将它们组织成某个特殊的阶段序列,即使指令的动作差异很大,但所有的指令都遵循统一的序列。每一步的具体处理取决于正在执行的指令。创建这样一个框架,我们就能够设计一个充分利用硬件的处理器。下面是关于各个阶段以及各阶......
  • JS Proxy对象使用的两个案例:校验器和属性私有化
    校验器consttarget={_id:'1024',name:'vuejs',}//校验器constvalidators={name(val){returnObject.prototype.toString.call(val)==='string';},_id(val){returnObject.prototype.toString.call(v......
  • node.js 浅析 与 了解
    文章目录node.js与javascript浅析一、概念层面二、应用场景层面三、运行环境和模块系统层面node.js基础知识介绍1.模块系统2.事件驱动和异步编程3.文件系统操作4.HTTP服务器和客户端5.进程和子进程管理node.js=》方向1.学习方向2.学习方式node.js与......