《无所不能的JavaScript · prototype 原型链》

2024-07-09 1268阅读

📢 大家好,我是 【战神刘玉栋】,有10多年的研发经验,致力于前后端技术栈的知识沉淀和传播。 💗

🌻 近期刚转战 CSDN,会严格把控文章质量,绝不滥竽充数,欢迎多多交流。👍

文章目录

    • 写在前面的话
    • 快速了解
    • 知识介绍
      • prototype 与原型链
      • 关于 prototype 继承
      • 使用 prototype 添加属性
      • 原型链的性能和优化
      • Java 和 JavaScript 面向对象
      • 用法拓展
        • 创建对象的N种方式
        • prototype 实现继承
        • 扩展原有对象的方法 Demo
        • prototype 基础测试 Demo
        • 总结陈词

          写在前面的话

          又回到前端系列了,之前写后端和其他内容去了,进度落下,赶紧补一下。

          本篇文章介绍一下 JavaScript 里面的一些经典知识点,先以prototype原型链开篇。

          让我们开始!


          快速了解

          【概念说明】

          在JavaScript中,每个对象都有一个原型prototype属性,它指向另一个对象。这个被指向的对象也有自己的原型,以此类推,最终形成了一个原型链。原型链的顶端是Object.prototype,它是所有对象的根原型。

          当我们访问一个对象的属性时,如果该对象自身没有这个属性,JavaScript会沿着原型链向上查找,直到找到匹配的属性或者到达原型链的末端。

          【用代码说话】

          直接上一段示例代码:

          function Person(name, age) {
          	this.name = name;
          	this.age = age;
          }
          Person.prototype.sayHello = function() {
          	console.log(`Hello, my name is ${this.name} and I am ${this.age} years old.`);
          };
          const alice = new Person('Alice', 20);
          alice.sayHello();  // 输出:Hello, my name is Alice and I am 20 years old.
          

          浏览器运行该代码,F12控制台查看alice对象,如下图所示:

          《无所不能的JavaScript · prototype 原型链》

          分析一下上图的这些内容:

          • alice 对象包含 age 和 name 两个属性和一个原型对象
          • 原型对象可以通过 alice.proto 访问,或 Person.prototype,两者是相等的
          • 原型对象的再上一次层指向 Object.prototype,再往上一层就是 null
          • 对,没有错,你看到了一个类似链式调用的东西,这是原型链

            《无所不能的JavaScript · prototype 原型链》

            Tips:学习前端,特别是JavaScript,直接运行Demo,F12查看效果是一个快速学习方式。


            知识介绍

            prototype 与原型链

            在 JavaScript 中,prototype 是用于实现继承和共享属性与方法的一种机制。每一个 JavaScript 对象(除了 null)在创建时都与另一个对象(称为其 “prototype”)相关联。这个 “prototype” 对象本身也有一个 “prototype”,这样一直到一个对象的 prototype 为 null 为止,这形成了一个链条,被称为 “原型链”(prototype chain)。


            关于 prototype 继承

            所有的 JavaScript 对象都会从一个 prototype(原型对象)中继承属性和方法:

            Date 对象从 Date.prototype 继承。

            Array 对象从 Array.prototype 继承。

            Person 对象从 Person.prototype 继承。

            所有 JavaScript 中的对象都是位于原型链顶端的 Object 的实例。

            JavaScript 对象有一个指向一个原型对象的链。当试图访问一个对象的属性时,它不仅仅在该对象上搜寻,还会搜寻该对象的原型,以及该对象的原型的原型,依次层层向上搜索,直到找到一个名字匹配的属性或到达原型链的末尾。

            Date 对象、Array 对象、以及 Person 对象从 Object.prototype 继承。

            console.log(alice.__proto__ === Person.prototype);  // 输出:true
            console.log(Person.prototype.__proto__ === Object.prototype);  // 输出:true
            console.log(Object.prototype.__proto__ === null);  // 输出:true
            

            使用 prototype 添加属性

            JavaScript 对象要添加新的属性或方法,很简单。

            有点像 Java 的Map那边随意,直接追加即可,类似 alice.xxx = ‘1111’

            但如果想追加的属性是和其他对象,继承和共享的,那就可以用到 prototype 方式。

            Person.prototype.nationality = "English";
            console.log(alice.__proto__.nationality);
            

            使用 prototype 属性就可以给对象的构造函数添加新的属性和方法。


            关键词 constructor

            前面示例一定看到了原型对象上有一个 constructor 属性,这是何物?

            其实就是和 Java 中的构造函数类似的概念,用下面代码可以看出来,原型的 constructor就是指向类本身。

            alice.__proto__.constructor === Person  // true
            

            结合前面说的内容, 整个概念是否绕来绕去,找了下面这张图,感觉能比较直观了看明白之间的调用关系。

            《无所不能的JavaScript · prototype 原型链》

            几个概念复述一下:

            • prototype 是 JavaScript 中实现继承和共享属性与方法的基础。
            • 构造函数的 prototype 属性用于定义所有实例共享的方法和属性。
            • 每个对象都有一个 proto 属性,对象通过该属性引用其原型,形成原型链。
            • 每个函数在定义时会自动获得一个 prototype 属性,这个属性是一个对象,包含一个 constructor 属性指向函数本身。
            • 当访问对象的一个属性或方法时,JavaScript 会首先在对象本身查找。如果没有找到,它会沿着原型链向上查找,直到找到或到达链的末端(即 null)。

              Tips:理解 prototype 和原型链是掌握 JavaScript 面向对象编程的关键。通过这种机制,可以实现代码的重用和更高效的内存使用。


              原型链的性能和优化

              原型链在JavaScript中的运作会带来一定的性能开销。在访问属性时,查找过程需要沿着原型链逐级查找,直到找到属性或者到达原型链末端。因此,过深的原型链结构可能导致性能下降。为了优化性能,可以合理设计对象的原型链,避免过于庞大和复杂的结构。

              Tips:简单来说,就是不要太复杂,但一般前端设计也不应该很重,最多父子类的结构了,损耗可以忽略不计。


              Java 和 JavaScript 面向对象

              JavaScript 和 Java 都是面向对象编程语言,但它们在类和对象的实现和使用上有一些显著的差异。以下是对 JavaScript 中类和对象的介绍,并通过 Java 的概念进行对比说明。

              类:

              JavaScript ES6 引入类,使得类定义更加简洁和直观,但其底层依然基于原型链。

              Java 中类是核心概念,定义了对象的属性和行为。

              对象:

              JavaScript 对象是键值对的集合,可以动态修改。

              Java 对象是类的实例,属性和方法在类中定义。

              继承:

              JavaScript 通过 extends 关键字实现类的继承,使用 super 调用父类的构造函数和方法。

              Java 通过 extends 关键字实现类的继承,使用 super 关键字调用父类的构造函数和方法。

              总结:

              尽管 JavaScript 和 Java 在语法和特性上有很多不同,但它们都支持面向对象编程的基本概念,如类、对象和继承。JavaScript 的类和对象模型更加灵活和动态,而 Java 的类和对象模型更为静态和严格。


              用法拓展

              创建对象的N种方式

              对于 Java 后端程序猿而言,类与对象不是 Java 面向对象的概念吗,前端怎么也有?

              其实 JavaScript 已经不是后端眼中随意简单的脚本语言了,Java 有的概念它也有,Java 能做的很多事情,它也能做。

              我们有多种方式可以创建对象,根据不同的创建方式,JavaScript对象的原型链也会有所不同。

              构造函数方式

              使用构造函数方式创建对象的原型是构造函数的原型对象(prototype)。

              这个前面章节都在介绍这种方式,示例代码再贴一下?

              function Person(name, age) {
              	this.name = name;
              	this.age = age;
              }
              Person.prototype.sayHello = function() {
              	console.log(`Hello, my name is ${this.name} and I am ${this.age} years old.`);
              };
              const alice = new Person('Alice', 20);
              alice.sayHello();  // 输出:Hello, my name is Alice and I am 20 years old.
              console.log(alice.__proto__ === Person.prototype);  // 输出:true
              

              字面量方式创建对象

              使用字面量方式创建对象的原型是 Object.prototype,即所有字面量创建的对象都是 Object 的实例。

              const lwObj = {
              	name: '战神',
              	age: 32,
              	sayHello() {
              		console.log(`Hello, my name is ${this.name} and I am ${this.age} years old.`);
              	}
              };
              console.log(lwObj.__proto__ === Object.prototype);// 输出:true
              

              Class 方式创建对象

              在 ES6 之前,JavaScript 没有类的概念,开发者主要通过构造函数和原型链来实现面向对象编程。ES6 引入了类的语法,使得定义和继承类更加直观和类似于其他面向对象语言(如 Java)。

              Class 方式创建的对象和构造函数方式创建的对象一样,其原型是Class构造函数的prototype属性所指向的对象。

              Tips:这也是推荐方式,不过一般复杂的前端逻辑才会去专门设计和定义类。

              class PersonClass {
              	constructor(name, age) {
              		this.name = name;
              		this.age = age;
              	}
              	sayHello() {
              		console.log(`Hello, my name is ${this.name} and I am ${this.age} years old.`);
              	}
              }
              const hanMei = new PersonClass('HanMei', 25);
              hanMei.sayHello();  // 输出:Hello, my name is HanMei and I am 25 years old.	
              console.log(hanMei.__proto__ === PersonClass.prototype);  // 输出:true
              

              prototype 实现继承

              直接看下面的代码,就是基于原型链的特性。

              // 定义父类函数
              function Company(){  
                  this.boss = 'lw';  
              	  this.name = 'cjwmy';  
                  this.show = function(){  
                      return this.boss + ',' + this.name;  
                  }  
              }  
              // 定义子类函数
              function Zysoft(){  
                  this.soft = ['zyhip','zycomm'];  
              }  
              // 绑定原型链
              Zysoft.prototype = new Company();  
              let one = new Zysoft();  
              console.log(one.show());  //lw,cjwmy
              

              扩展原有对象的方法 Demo

              首先,我们要先了解一下类的概念,JavaScript 本身是一种面向对象的语言,它所涉及的元素根据其属性的不同都依附于某一个特定的类。我们所常见的类包括:数组变量(Array)、逻辑变量(Boolean)、日期变量(Date)、结构变量(Function)、数值变量(Number)、对象变量(Object)、字符串变量(String) 等,而相关的类的方法,也是程序员经常用到的(在这里要区分一下类的注意和属性发方法),例如数组的push方法、日期的get系列方法、字符串的split方法等等,但是在实际的编程过程中不知道有没有感觉到现有方法的不足?prototype 方法应运而生!

              【最简单的例子,自己写add等方法,了解 prototype】

              是不是很简单?这一节仅仅是告诉读者又这么一种方法,这种方法是这样运用的。

              (1) Number.add(num):作用,数字相加
              实现方法:Number.prototype.add = function(num){return(this+num);}
              试验:alert((3).add(15)) -> 显示 18
              (2) Boolean.rev(): 作用,布尔变量取反
              实现方法:Boolean.prototype.rev = function(){return(!this);}
              试验:alert((true).rev()) -> 显示 false
              (3)测试demo
              String.prototype.test = function(value) {
                  alert(1)
              };
              

              【已有方法的实现和增强,初识 prototype】

              (1) Array.push(new_element)
              //作用:在数组末尾加入一个新的元素
              Array.prototype.push = function(new_element){
                  this[this.length]=new_element;
                  return this.length;
              }
              //让我们进一步来增强他,让他可以一次增加多个元素!
              Array.prototype.pushPro = function() {
                  var currentLength = this.length;
                  for (var i = 0; i  显示 16
              

              【新功能的实现,深入 prototype】

              在实际编程中所用到的肯定不只是已有方法的增强,更多的实行的功能的要求。

              (1) String.left()
              //问题:用过 vb 的应该都知道left函数,从字符串左边取 n 个字符,但是不足是将全角、半角均视为是一个字符,造成在中英文混排的版面中不能截取等长的字符串
              //作用:从字符串左边截取 n 个字符,并支持全角半角字符的区分
              String.prototype.left = function(num,mode){
                  if(!/\d+/.test(num))return(this);
                  var str = this.substr(0,num);
                  if(!mode) return str;
                  var n = str.Tlength() - str.length;
                  num = num - parseInt(n/2);
                  return this.substr(0,num);
              }
              alert("EaseWe空间Spaces".left(8)) -> 显示 EaseWe空间
              alert("EaseWe空间Spaces".left(8,true)) -> 显示 EaseWe空
              //本方法用到了上面所提到的String.Tlength()方法,自定义方法之间也能组合出一些不错的新方法呀!
              (2) Date.DayDiff()
              作用:计算出两个日期型变量的间隔时间(年、月、日、周)
              Date.prototype.DayDiff = function(cDate,mode){
                     try{
                         cDate.getYear();
                     }catch(e){
                         return(0);
                     }
                     var base =60*60*24*1000;
                     var result = Math.abs(this - cDate);
                     switch(mode){
                         case "y":
                             result/=base*365;
                             break;
                         case "m":
                             result/=base*365/12;
                             break;
                         case "w":
                             result/=base*7;
                             break;
                         default:
                             result/=base;
                             break;
                     }
                     return(Math.floor(result));
                 }
              alert((new Date()).DayDiff((new Date(2002,0,1)))) -> 显示 329
              alert((new Date()).DayDiff((new Date(2002,0,1)),"m")) -> 显示 10
              //当然,也可以进一步扩充,得出响应的小时、分钟,甚至是秒。
              (3) Number.fact()
              //作用:某一数字的阶乘
              Number.prototype.fact=function(){
                 var num = Math.floor(this);
                 if(num 显示 24
              //这个方法主要是说明了递归的方法在 prototype 方法中也是可行的!
              (4) Array.indexof()
              //下面是给js中的Array加的一个方法(本来js中Array是没有返回下标的方法的)
              Array.prototype.indexof = function(value) {
                  var a = this;
                  for (var i = 0; i  
              

              prototype 基础测试 Demo

              本章节,是博主早期针对 prototype 做的一些测试代码,可以跳过。

              ===== 第一例,prototype初使用--为类型增加额外的方法和属性  
              Object.prototype.Property = 1;
              Object.prototype.Method = function (){ 
                  alert(1);
              }
              var obj = new Object();
              alert(obj.Property);
              obj.Method();
              结论:可以在类型上使用proptotype来为类型添加行为,这些行为只能在类型的实例上体现,并且在增加之前实例化的对象也享有这些变化。
              规则1:JS中允许的内置类型有Array, Boolean, Date, Enumerator, Error, Function, Number, Object, RegExp, String;
              规则2:直接用Object.Method()也可以调用(但一般不这么使用),因为Object是所有对象(包括函数对象)的基础,其他内置类型是不可以。
              ===== 第二例,在实例对象上不能使用prototype,否则发生编译错误 
              var obj = new Object();
              obj.prototype.Property = 1; //Error
              obj.prototype.Method = function() //Error
              {
                  alert(1);
              }
              ===== 第三例,直接给类型定义"静态"属性和方法,只能使用类型本身调用,对象无法调用,同Java的static关键字声明的类变量
              Object.Property = 1;
              Object.Method = function()
              {
                  alert(1);
              }
              alert(Object.Property);
              Object.Method();    //正常调用
              var obj = new Object();
              alert(obj.Property);  //Error实例不能调用类型的静态属性或方法,否则发生对象未定义的错误。
              ===== 第四例,演示通常在JavaScript中定义一个自定义类型的方法
              function Aclass()
              {
              this.Property = 1;
              this.Method = function()
              {
                  alert(1);
              }
              }
              var obj = new Aclass();
              alert(obj.Property);
              obj.Method();
              ===== 第五例,接第四例,可以在外部使用prototype为自定义的类型添加属性和方法。
              Aclass.prototype.Property2 = 2;
              Aclass.prototype.Method2 = function
              {
                  alert(2);
              }
              var obj = new Aclass();
              alert(obj.Property2);
              obj.Method2();
              ===== 第六例,接第四例,在外部不能通过prototype改变自定义类型的属性或方法,不会报错但改变无效;
              但如果属性本身就是通过prototype扩展的,则可以继续用prototype修改。
              Aclass.prototype.Property = 2;
              Aclass.prototype.Method = function
              {
                  alert(2);
              }
              var obj = new Aclass();
              alert(obj.Property); //仍然弹出1
              obj.Method();
              ===== 第七例,接第四例,可以在对象上改变属性。(这个是肯定的)
              var obj = new Aclass();
              obj.Property = 2;
              obj.Method = function()
              {
                  alert(2);
              }
              alert(obj.Property);
              obj.Method();
              也可以在对象上改变方法。(和普遍的面向对象的概念不同)
              ===== 第八例,接第四例,可以在对象上增加属性或方法   
              var obj = new Aclass();
              obj.Property2 = 2;
              obj.Method2 = function()
              {
                  alert(2);
              }
              alert(obj.Property2);
              obj.Method2();
              ===== 第九例,接第四例,这个例子说明了一个类型如何从另一个类型继承。  
              function AClass2()
              {
                     this.Property2 = 2;
                     this.Method2 = function()
                     {
                            alert(2);
                     }
              }
              AClass2.prototype = new AClass();
               
              var obj = new AClass2();
              alert(obj.Property);
              obj.Method();
              alert(obj.Property2);
              obj.Method2();
              ===== 第十例,接上例, 这个例子说明了子类如何重写父类的属性或方法。但不能改变自身的属性和方法。
              AClass2.prototype = new AClass();()
              AClass2.prototype.Property = 3;
              AClass2.prototype.Method = function()
              {
                     alert(4);
              }
              var obj = new AClass2();
              alert(obj.Property);
              obj.Method();
              可见JavaScript能够实现的面向对象的特征有:·公有属性(public field)·公有方法(public Method)·私有属性(private field)·私有方法(private field)·方法重载(method overload)·构造函数(constructor)·事件(event)·单一继承(single inherit)·子类重写父类的属性或方法(override)·静态属性或方法(static member)
              

              总结陈词

              本篇文章分享一下 JS 世界中prototype的用法,希望能帮助到大家。

              后端程序猿如果想往全栈工程狮发展,那JavaScript必须和Java玩得一样明白,接下来系列文章,带你一起探索,无所不能的JavaScript。

              💗 后续会逐步分享企业实际开发中的实战经验,有需要交流的可以联系博主。

VPS购买请点击我

免责声明:我们致力于保护作者版权,注重分享,被刊用文章因无法核实真实出处,未能及时与作者取得联系,或有版权异议的,请联系管理员,我们会立即处理! 部分文章是来自自研大数据AI进行生成,内容摘自(百度百科,百度知道,头条百科,中国民法典,刑法,牛津词典,新华词典,汉语词典,国家院校,科普平台)等数据,内容仅供学习参考,不准确地方联系删除处理! 图片声明:本站部分配图来自人工智能系统AI生成,觅知网授权图片,PxHere摄影无版权图库和百度,360,搜狗等多加搜索引擎自动关键词搜索配图,如有侵权的图片,请第一时间联系我们,邮箱:ciyunidc@ciyunshuju.com。本站只作为美观性配图使用,无任何非法侵犯第三方意图,一切解释权归图片著作权方,本站不承担任何责任。如有恶意碰瓷者,必当奉陪到底严惩不贷!

目录[+]