zl程序教程

您现在的位置是:首页 >  后端

当前栏目

【JS高级】js面向对象三大特性之继承_06

JS继承 特性 高级 面向对象 三大 06
2023-09-14 09:13:39 时间

目录

一. 封装

二. 继承

1. 存在问题

2. 如何解决

3. 什么是继承

4. 为什么使用继承

5. 何时使用继承

6. 如何使用继承

7. 示例: 在原型对象中保存子对象的方法 ⏬

8. 自有属性和共有属性

9. 内置类型的原型对象

10. 原型链

💥 扩展:this判断—8种指向

💠 总结:知识点提炼 


 🆙【前文回顾】👉  js面向对象三大特性之封装—如何创建对象_05


一. 封装

二. 继承

1. 存在问题

构造函数虽然可以重用代码,但是无法节约内存!凡是放在构造函数中定义的方法,每new一个对象时,都会重复创建这个方法的副本!——浪费内存!

2. 如何解决

继承!

3. 什么是继承

父对象中的成员,子对象无需重复创建,就可直接使用!

4. 为什么使用继承

即代码重用,又节约内存!

5. 何时使用继承

如果同一类型的所有子对象都需要一个公共的方法或属性值时,都应该使用继承方式,节约内存。

6. 如何使用继承

         (1). 所有的方法定义不要放在构造函数内!

         (2). 其实每个构造函数都有一个原型对象与之配对儿。

         a. 无需自己创建。

         b. 只要定义构造函数,就会自动附赠一个空的原型对象

         c. 构造函数中都会自动包含一个特殊属性prototype指向和自己配对儿的原型对象

         (3). new一个对象,new的第二步都会自动为新对象添加 _ _proto_ _属性,自动指向构造函数的原型对象。凡是从_ _proto_ _属性指出的关系,都是继承关系!

         (4). 结果: 今后子对象访问任何属性或方法时:

         a. 先在子对象自己内部查找使用。如果子对象中不包含要使用的属性和方法,则js引擎会自动延__proto__去父对象中查找!

         b. 如果在父对象中找到想要的属性和方法,可直接调用!就像访问子对象自己的成员一模一样!

         (5). 因为赠送的原型对象几乎是空的!所以,需要我们通过强行赋值的方式,将所有子对象共用的方法和属性值,添加到原型对象中:

         构造函数.prototype.属性名或方法名=值或function(){ ... }

         (6). 原型对象方法中的this:

         (7). 为什么父对象称为原型对象prototype: 因为英文中prototype是原型的意思。原型就是为今后所有子对象保存公共的属性和功能的第一个事物。原型中有什么属性和功能,将来基于这个原型创建出来的孩子,也都自动拥有了什么属性和功能。所以,程序中,为所有子对象集中保存公共的属性值和功能的对象,也称为原型对象:

7. 示例: 在原型对象中保存子对象的方法 ⏬

⏬ 示例: 在原型对象中保存所有学生共用的自我介绍方法 ⏬

1_prototype.html

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <script>
    //定义描述学生类型的构造函数
    //       类型名
    //       ↓
    function Student(sname, sage){
      //要求: 每个学生都要有
      //*学生姓名*
      this.sname=sname;
      //和*学生年龄*属性
      this.sage=sage;      
    }
    // console.log(Student);//函数内容
    console.dir(Student);//函数在内存中的存储结构
    
    //因为所有的学生都要求会做自我介绍,所以,自我介绍的功能应该集中保存在构造函数的原型对象中一份即可!所有子对象通过继承关系,共用一个函数!——节约内存!
    //并且每个学生都要*会做自我介绍*! 
    Student.prototype.intr=function(){
      console.log(`I'm ${this.sname}, I'm ${this.sage}`)
    };
    
    //2. 反复用new调用构造函数,反复创建多个相同结构的对象,并传入每个对象各不相同的属性值
    var lilei=new Student("Li Lei",11);
    var hmm=new Student("Han Meimei",12);

    console.log(lilei);
    console.log(hmm);

    //亲子鉴定: 
    //李磊的爹是不是Student的老公: true            
    console.log(
      lilei.__proto__==Student.prototype
    )//         爹                老公
    //hmm的爹是不是Student的老公: true
    console.log(
      hmm.__proto__==Student.prototype
    )
  </script>
</body>
</html>

运行结果:

8. 自有属性和共有属性

         (1). 自有属性: 直接保存在当前子对象内,只归当前子对象自己独有的属性

         (2). 共有属性: 保存在原型对象中的,供所有子对象共用的属性

         (3). 获取属性值或调用方法时: 都可用子对象加.直接访问

         "子对象.属性名或方法名"方式来访问

         (4). 修改属性值:

         a. 修改自有属性值: 也可用子对象直接修改:

         b. 修改共有属性值:

                  1). 错误的做法: 不能用子对象直接修改共有属性:

                  i. "子对象.共有属性=属性值"

                  ii. 原因: js中禁止子对象修改原型对象中的共有属性值,因为牵一发而动全身!

                  iii. 如果强行用子对象尝试修改共有属性值,js会悄悄的在这个子对象身上自动添加同名自有属性来保存新属性值.

                  iv. 结果: 从此,这个子对象,优先使用自己的自有属性,不再使用共有属性。——分道扬镳!

                  2). 正确的做法: 今后只要需要修改共有属性值,都必须通过原型对象自身才能修改!

                  构造函数.prototype.共有属性名=新属性值

         (5). 示例:比较读取和修改自有属性和共有属性有什么差别

         2_property.html

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <script>
    //定义描述学生类型的构造函数
    function Student(sname, sage){
      //要求: 每个学生都要有
      //*学生姓名*
      this.sname=sname;
      //和*学生年龄*属性
      this.sage=sage;
      
    }
    //所有学生都有共用的班级属性,要求当前程序中的所有学生,班级保持一致!应该将班级属性统一保存在原型对象中一份
    Student.prototype.className="初一2班";
    
    
    //2. 反复用new调用构造函数,反复创建多个相同结构的对象,并传入每个对象各不相同的属性值
    var lilei=new Student("Li Lei",11);
    var hmm=new Student("Han Meimei",12);

    //读取自有属性值
    console.log(lilei.sname);
    console.log(hmm.sname);
    //读取共有属性
    console.log(lilei.className, hmm.className);

    //修改自有属性: 
    //给hmm的年龄+1
    hmm.sage++;
    console.log(hmm.sage);
    console.log(lilei.sage);

    //过了一年,两个同学希望同时升初二2班
    //错误: 
    //lilei.className="初二2班";
    //正确: 
    Student.prototype.className="初二2班"
    console.log(lilei);
    console.log(hmm);
    console.log(lilei.className, hmm.className);
    //                初二2班         初二2班
    
  </script>
</body>
</html>

运行结果:

9. 内置类型的原型对象

         (1). 问题:

          a. js基础阶段已经学过很多new:

         new Array()  new Date()   new Object()  new RegExp()

         为什么他们也能new呢?

          b. 为什么随便创建一个数组arr1, arr2, arr3, ...,都可以使用数组家提供的函数!arr1.sort()  arr2.sort()  arr3.sort()sort()函数到底保存在哪里了?数组家还有没有其它更好用的函数呢?!

         (2). 其实: Array, Date, RegExp, Object...也都是一种类型。只不过这些类型是浏览器提前定义好的!我们直接使用!——内置类型!

         (3). ECMAScript标准中规定了11种内置类型和对象:

         String   Number   Boolean

         Array    Date     RegExp     Math

         Error

         Function    Object

         global

         其中:

         Math,已经是一个直接可用的对象了!不需要也不能new!

   🙋‍♂️ 因为Math本身已经是对象了,可以从浏览器控制台输入Math直接查看

         global,全局作用域对象,在浏览器中被window代替了!也不能new

         除了Mathglobal之外,其余9种都是一种类型。

         (4). 今后只要一提到"类型",一定包含2部分组成:

         a. 构造函数: 专门负责创建这个类型的新的子对象

         比如: function Array (){ [native code] }

                  function Date (){ [native code ] }

         b. 原型对象: 专门负责保存这个类型将来所有子对象共有的方法和属性

         比如: 所有数组家孩子可用的函数,都保存在
                  Array.prototype对象中

                  所有日期家孩子可用的函数,都保存在

                  Date.prototype对象中

         (5). 问题: 如果内置类型的原型对象中提供的方法不够用,怎么办?

         (6). 解决: 自定义一个新函数,添加到这个类型的原型对象中

         构造函数.prototype.新方法=function(){ ... }

         (7). 示例: 为数组家添加求和的函数sum()

         3_Array.prototype.html

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <script>
    //先数组类型的原型对象中添加自定义的sum函数
    Array.prototype.sum=function(){
      console.log(`调用了一次数组的原型对象中的自定义的sum()`);
      //定义变量准备累加数组中的元素值
      var total=0;
      //遍历当前数组中每个元素
      //将来如果arr1.sum(),希望遍历arr1中的元素
      //将来如果arr2.sum(),希望遍历arr2中的元素
      //只有this才能自动获得将来.sum()前的数组对象
      //所以,原型对象方法中的"数组"都应该用this代替
      //因为不能写死数组,将来有可能还有可能是其他数组调用自定义sum函数
      for(var i=0;i<this.length;i++){
        //取出数组中当前i位置的元素值,累加到变量total上
        total+=this[i];
      }
      //返回计算结果
      return total;
    }

    var arr1=[1,2,3];
    var arr2=[1,2,3,4,5];

    console.log(Array.prototype);
    console.log(arr1.sum());//6
    console.log(arr2.sum());//15
  </script>
</body>
</html>

运行结果:

调用了一次数组的原型对象中的自定义的sum()

6

调用了一次数组的原型对象中的自定义的sum()

15 

10. 原型链

         (1). 问题: 原型对象有没有爹

         (2). 其实所有对象都有一个_ _proto_ _指向自己的父对象(原型对象)

         (3). 什么是原型链: 由多级父对象,逐级继承,形成的链式结构,就叫原型链

         (4). 保存了一个对象所有可用的成员(属性和方法)

         (5). 控制着成员的使用顺序: (就近原则)

         a. 在当前对象内找自有属性使用。

         b. 如果自己没有,__proto__向父级对象继续查找共有属性使用。

💥 扩展:this判断—8种指向

this  8种指向: 判断this,一定不要看定义在哪儿!只看调用时!

1. obj.fun()   this->obj

2. fun() 或 (function(){ ... })() 或 多数回调函数 或 定时器函数   this->window

3. new Fun()   this->new正在创建的新对象

4. 类型名.prototype.共有方法=function(){ ... }   this->将来谁调用指谁,同第一种情况

5. DOM或jq中事件处理函数中的this->当前正在触发事件的DOM元素对象

                               如果需要使用简化版函数,必须$(this)

6. 箭头函数中的this->箭头函数外部作用域中的this

7. jQuery.fn.自定义函数=function(){ ... }   this->将来调用这个自定义函数的.前的jQuery子对象,不用再$(this)

8. new Vue()中methods中的函数中的this->当前new Vue()对象


💠 总结:知识点提炼 

1. 只要验证字符串格式或查找、屏蔽敏感词时都要用正则

(1). 最简单的正则: 一个敏感词的原文

(2). 某一位字符上可能有多种备选字时用: [备选字列表]

(3). 如果[]中部分字符是连续的,可用: [x-x]

         a. 一位小写字母: [a-z]

         b. 一位大写字母: [A-Z]

         c. 一位字母(大小写都行): [A-Za-z]

         d. 一位字母或数字都行: [0-9A-Za-z]

         e. 一位汉字: [\u4e00-\u9fa5]

(4). 预定义字符集:

         a.  \d 一位数字

         b.  \w 一位数字、字母或_

         c.  \s  空格、tab、换行等空字符

         d.  .   任意字符

(5). 如果规定一个字符集或子规则反复出现的次数时就用量词:

         a. 有明确数量边界的量词:

      1). {n}  =n 必须n个,不能多也不能少

      2). {n,m}  n个<=    <=m个

      3). {n,}    n个<=   多了不限

         b. 没有明确数量边界的量词:

      1). *   0个<= 可有可无,多了不限

      2). ?   0个或1个  可有可无,最多一个

      3). +   1个<=  至少一个,多个不限

(6). 两个规则中选其一匹配即可: 规则1|规则2

(7).希望将多个子规则分为一组先联合匹配,再和分组外的其他规则联合匹配:

  (多个子规则)

(8). 匹配特殊位置: 3个

         a. 字符串的开头位置: ^

         b. 字符串的结尾位置: $

         c. 英文句子中的单词的左右边界: \b

2. String家提供的正则相关函数: 3件事

(1). 查找敏感词: 4种情况

      a. 查找一个固定的敏感词出现的位置:
         var i=str.indexOf("敏感词")

         // 如果找不到,返回-1

      b. 用正则查找多种敏感词出现的位置:
         var i=str.search(/正则/i)

         // 如果找不到,返回-1

      c. 查找敏感词的内容:

      1). 查找第一个敏感词的内容和位置:
      var arr=str.match(/正则/i)

      // arr: [ 0:"敏感词内容", index:敏感词位置 ]

      // 如果找不到返回null

      2). 查找所有敏感词的内容,不关心位置:
      var arr=str.match(/正则/ig)

      // arr: [ 敏感词1, 敏感词2, ...  ]

      // 如果找不到返回null

     d. 查找每个敏感词的内容和位置: reg.exec

补: js中所有数组底层本质都是关联数组(下标都为字符串)

1. 访问数组中元素值的标注写法:

  arr["下标"]

2. 简写:

  a. 如果下标为自定义字符串名称,可简写为:

  arr.自定义名称的下标

  b. 如果下标为数字内容的字符串,可简写为:

  arr[数字下标]

总结: 查找方法的返回值规律

1. 如果原函数返回的是下标位置i,如果找不到,都返回-1

2. 如果原函数返回的是一个数组arr或一个对象obj,如果找不到,都返回null

3. 如果原函数返回类数组对象,如果找不到返回空类数组对象:

   { length:0 }

(2). 替换敏感词: 2种

         a. 简单替换:
         变量=str.replace(/正则/ig, "新值")

         b. 高级替换:
         变量=str.replace(/正则/ig, function(形参){

               return 根据本次敏感词动态生成一个新值

         })

         c. 删除敏感词:
         变量=str.replace(/正则/ig, "")

(3). 切割字符串:

         a. 简单切割:
         var arr=str.split("切割符")

         b. 复杂切割:
         var arr=str.split(/正则/i)

         c. 打散字符串为字符数组:
         var arr=str.split("")

3. RegExp对象: 

         (1). 创建正则表达式对象:

         a. 如果正则是固定的:
         var reg=/正则/ig

         b. 如果正则需要动态生成:
         var reg=new RegExp("正则",ig)

         (2). 验证字符串格式:
         var bool=reg.test(str)
         reg必须同时前加^后加$

         (3). 既查找每个关键词的内容又查找每个关键词的位置: (待续)

         do{

               var arr=reg.exec(str);

               if(arr!=null){

                              获得本次找到的敏感词的内容(arr[0])和位置(arr.index)

               }

         }while(arr!=null);

4. 函数: 

(1). 创建函数三种方式:

         a. function 函数名(形参列表){ 函数体; return 返回值 } //会被声明提前,不好

         b. var 函数名=function(形参列表){ 函数体; return 返回值 }//不会被声明提前,首选

         c. var 函数名=new Function("形参1", "形参2", ... , "函数体; return 返回值")

函数本质:

  1). 函数也是一个对象,对象中保存着函数的函数体代码

  2). 函数名只是一个普通的变量,函数名通过函数对象地址,引用着函数对象

  3). function在底层等效于new Function()

    function 函数名(){ ... }和var 函数名=function(){}在底层都会被翻译为

    var 函数名=new Function(...)

    只不过function 函数名(){}是先提前,再翻译

    而var 函数名=function(){}是不提前,原地翻译

(2). 重载: 今后,一件事,根据传入不同的参数值,动态执行不同的逻辑时,都用重载

function 一个函数名(不写形参变量){

  //arguments对象自动接住所有实参值

  if(arguments.length==0){

    执行一种逻辑

  }else if(arguments.length==1){

    执行另一种逻辑

  }else{

    执行其它逻辑

  }

}

其中arguments是类数组对象: 和数组相比:

         a. 相同点: 也有下标,length属性,也可for循环遍历

         b. 不同点: 不是数组类型,无法使用数组家的函数

(3). 匿名函数:

         a. 所有回调函数优先使用匿名函数——用完释放,节约内存

         b. 所有js代码都应该保存在匿名函数自调中,禁止使用全局变量,避免全局污染!

         (function(){

               要执行的js代码

         })();

         结果: 匿名函数内的都是局部变量,不会产生全局变量。

         局部变量随匿名函数一起释放。不会污染全局。

(4). 作用域和作用域链: (跟着视频亲自画图!!!)

         a. 作用域:

         1). 全局作用域:window,保存全局变量

         优: 可重用,缺: 随处可用, 极易被污染

         2). 函数作用域: 保存局部变量

         局部变量包括2中: 函数中var出的变量和形参变量

         优: 仅函数内可用,不会被污染,缺: 不可重用

         3). 函数作用域对象原理:

               i. 每个函数定义时都自带好友列表,好友列表里2个格子,一个是空,一个引用window

               ii. 调用函数时临时创建函数作用域对象保存函数局部变量。并将函数作用域对象的地址保存到函数好友列表中离自己近的格子里。

               iii. 函数执行过程中按就近原则先在自己的函数作用域对象中找局部变量使用。如果找不到,才被迫去全局window中找变量使用.

               iv. 函数调用后,好友列表中离自己近的格子清空,导致函数作用域对象以及内部的局部变量被释放!——所以局部变量不可重用!

         b. 作用域链: 保存一个函数所有可用的作用域对象的链式结构(好友列表)学名就叫作用域链。
         1). 作用域链保存着一个函数可用的所有变量

         2). 作用域链控制着变量的使用顺序。先局部后全局。

5. 闭包:  ⏬
         a. 只要希望给一个函数保护一个可反复使用的专属变量,又防止这个变量被外界篡改时,都用闭包。

         b. 闭包三步:

      1). 用外层函数妈妈包裹要保护的变量和内层函数

      2). 外层函数妈妈用return把内层函数孩子返回到外部

      3). 外部想使用内层函数的人,必须调用外层函数,才能获得return出来的内层函数对象。并将内层函数保存在一个变量中反复使用。

         c. 闭包形成的原因: 外层函数调用后,外层函数的作用域对象被内层函数引用着无法释放,形成了闭包对象

         d. 闭包的缺点: 闭包比一般的函数占用多一块内存——外层函数的函数作用域对象。所以,用完闭包后,应该尽快释放:
      保存内层函数的变量=null

6. 面向对象: 封装 继承 多态 

(1). 封装: 3种:

         a. 用{}创建一个对象:

         var 对象名={

               属性名:属性值,

               ... : ... ,

               方法名: function(){

                              ... this.属性名 ...

               }

         }

         b. 用new Object():

         1). 2步:

               i. var 对象名=new Object()

               ii. 对象名.属性名=属性值;

                               对象名.方法名=function(){ ... }

         2). 对象底层也是关联数组:

               i. 都是名值对儿的集合

               ii. 都可用[""]和.方式访问成员。

                               如果属性名来自于变量,就只能用[],不要加""

               iii. 访问不存在的属性,都不报错,返回undefined

                               判断是否包含某个属性:

                           对象.属性名!==undefined

               iv. 强行给不存在的属性赋值,都不报错,而是自动添加该属性

                               给对象添加新属性,唯一办法,强行赋值:

                           对象名.新属性名=新值

               v. 都可用for in遍历

         c. 只要反复创建多个相同结构的对象都用构造函数:

         1). 2步:

               i. 定义构造函数:

               function 类型名(形参1,形参2, ...){

                              this.属性名1=形参1;

                              this.属性名2=形参2;

                              //构造函数中不要再包含方法定义定义!

               }

               ii. 用new 调用构造函数:

               var 对象名=new 类型名(属性值1, 属性值2,...)

         2). new做了4件事:

               i. 创建一个新的空对象

               ii. 让新对象继承(_ _proto_ _)构造函数的原型对象

               iii. 调用构造函数,传入实参,并自动替换构造函数中的this为new正在创建的新对象。构造函数中,通过强行赋值的方式为新对象添加规定的属性,并保存属性值。

               iv. 返回新对象的地址,保存到=左边的变量中。

        3). 优点: 重用对象结构代码

        4). 缺点: 如果构造函数中包含方法定义,则每次创建新对象都会重复创建相同方法的副本。             ——浪费内存!

(2). 继承: ⏬

         a. 今后,只要同一类型所有子对象共用的方法和属性值,都要集中保存在构造函数的原型对象中!

      构造函数.prototype.属性名/共有方法名=属性值/function(){ ... }

         b. 自有属性和共有属性:

        1). 获取属性值:都可用"子对象.属性名"

        2). 修改属性值:

               i. 自有属性: 子对象.自有属性名=新值

               ii. 共有属性: 构造函数.prototype.共有属性名=新值

        c. 内置类型原型对象:

      1). 11种内置类型/对象: String, Number, Boolean, Array, Date, RegExp, Math(对象), Error, Function, Object, global(对象)

        2). 一种类型=构造函数+原型对象

               i. 构造函数: 创建子对象

               ii. 原型对象: 为所有子对象保存共有成员

        3). 查看该类型共有哪些API: 类型名.prototype

        4). 该类型缺少想用的方法: 类型名.prototype.共有新方法=function(){ ... }

        d. 原型链: 保存着一个对象可用的所有属性和方法。控制着属性和方法的使用顺序:先自有再共有——就近原则!


🆕【后文传送门】👉   js面向对象三大特性之多态_07​​​​​​​


​​​

如果这篇【文章】有帮助到你,希望可以给【青春木鱼】点个👍,创作不易,相比官方的陈述,我更喜欢用【通俗易懂】的文笔去讲解每一个知识点,如果有对【前端技术】感兴趣的小可爱,也欢迎关注❤️❤️❤️青春木鱼❤️❤️❤️,我将会给你带来巨大的【收获与惊喜】💕💕!