T-JS核心-day06-继承、多态、自定义继承、ES5-严格模式

五、面向对象

1. 继承

  1. 自有属性和共有属性
    1. 什么是自有属性:保存在子对象中,只归子对象独有的属性

    2. 什么是共有属性:保存在原型对象中,归当前类型下所有子对象共有的属性

    3. 获取属性值时:无论获取自有属性值,还是获得共有属性值,都可用子对象.属性名,无差别

    4. 修改属性值时:

      1. 如果修改一个子对象的自有属性,才可以子对象.自有属性=新值

      2. 如果修改多个子对象共有的属性必须使用原型对象亲自修改:
        构造函数.prototype.共有属性=新值
        在这里插入图片描述

      3. 错误做法:如果强行用子对象直接修改共有属性:结果,原型对象中的共有属性保持不变,而是只给当前这一个子对象添加一个新的同名的自有属性。从此,这个子对象,因为已经有了同名的自有属性,就不会再使用同名的共有属性。从此,共有属性发生变化,当前子对象的这个同名自有属性也不会跟随变化。从此,这个子对象和其他子对象,在这个属性的使用上,分道扬镳。
        在这里插入图片描述

    5. 示例:修改共有属性

        <script>
          function Student(sname,sage){
            this.sname=sname;
            this.sage=sage;
          }
          // 希望给所有学生添加一个班级名属性
          Student.prototype.className="初一2班";
      
          var lilei=new Student("Li Lei",11);
          var hmm=new Student("Han Meimei",12);
          console.log(lilei);
          console.log(hmm);
          console.log(lilei.className,hmm.className);
          //            "初一2班"       "初一2班"
          // 修改自有属性
          lilei.className="初二2班";
          console.log(lilei.className,hmm.className);
          //            "初二2班"       "初一2班"
          // 过了一年,同学们升了一级
          // 修改原型对象中共有属性:
          Student.prototype.className="初二2班";
          console.log(lilei.className,hmm.className);
          //            "初二2班"       "初二2班"
          // 又过了一年,同学们又升了一级
          // 修改原型对象中共有属性:
          Student.prototype.className="初三2班";
          console.log(lilei.className,hmm.className);
          //            "初二2班"       "初三2班"
          // 此时lilei的className就跟原型对象中的className属性分道扬镳
        </script>
      
  2. 内置类型的原型对象:
    1. 什么是内置类型/对象:ES标准中已经规定的,浏览器已经定义好的,我们可以直接使用的类型/对象

    2. 11种内置类型对象
      String Number Boolean----包装类型(扩展)
      Array Date RegExp Math(Math不是类型,而是一个对象,不能用new)
      Error
      Function Object
      global(不是类型,是一个对象,不能new,且在浏览器中被window代替)
      除了Math和global两种外,其余9中都可以new

    3. 今后凡是可以new的,都是构造函数。只要有构造函数,都会牵扯出一个大家庭,每个大家庭中,至少包含2个:

      1. 构造函数(妈妈)
        1. 负责反复创建多个相同结构的子对象
        2. 构造函数肚子里的属性,会成为将来子对象中的自由属性
      2. 原型对象(爸爸)
        1. 负责替该类型所有子对象集中保管共有的方法
        2. 原型对象有什么方法,子对象也就有什么方法----继承
          在这里插入图片描述
    4. 比如:内置类型Array:包含2部分

      1. function Array(){... 内部代码 看不见 ...}
      2. 所有数组共用的函数,都放在Array.prototype中
      3. 如果想用的数组函数,原型对象中没有,可以自己手动想数组的原型对象中添加一个新函数,结果,将来所有数组的子对象,都可用这个自定义的公共函数
      4. 示例:为数组原型对象中添加求和函数sum
          <script>
            // 需求:为数组添加一个可对数组内容求和的函数
            Array.prototype.sum=function(){
              // this:指将来调用这个函数的某个数组的子对象
              var result=0;
              for(var i=0;i<this.length;i++){
                result+=this[i];
              }
              return result;
            }
            console.log(Array.prototype);
            //[sum: ƒ, constructor: ƒ, concat: ƒ, copyWithin: ƒ, fill: ƒ, …]
            var arr1=[1,2,3];
            console.log(arr1.sum());//6
            var arr2=[1,2,3,4,5];
            console.log(arr2.sum());//15
          </script>
        
  3. 原型链
    1. 什么是原型链:由多级父元素逐级继承形成的链式结构

    2. 保存着一个对象可用的所有属性和方法

    3. 控制着成员的使用顺序:先自有,再共有
      在这里插入图片描述

    4. 示例:验证原型链

        <script>
          function Student(sname,sage){
            this.sname=sname;
            this.sage=sage;
          }
          Student.prototype.intr=function(){
            console.log(`I'm ${this.sname},I'm ${this.sage}`)
          }
          var lilei=new Student("Li Lei",11);
          console.log(lilei);//Student {sname: "Li Lei", sage: 11}
          console.log(lilei.__proto__==Student.prototype);//true
          console.log(lilei.__proto__.__proto__==Object.prototype);//true
          console.log(Student.__proto__==Function.prototype);//true
          console.log(Student.__proto__.__proto__==Object.prototype);//true
        </script>
      

2. 多态

  1. 什么是多态:同一个函数,在不同情况下,表现出不同的状态

  2. 包括2种情况:重载和重写(override)

  3. 什么是重写:子对象中定义了和父对象中重名的成员

  4. 为什么重写:因为从父对象中继承来的属性或方法并不总是好用的

  5. 何时重写:只要从父对象继承来的某个成员不好用,就可以重写

  6. 如何:只要在子对象中,定义和父对象中名称相同的一个成员,从此子对象再使用这个成员时,都会优先使用自己定义的成员,而不再使用父对象的成员
    在这里插入图片描述

  7. 示例:在自定义类型和对象中重写Object原型对象里的toString()方法

      <script>
        function Student(sname,sage){
          this.sname=sname;
          this.sage=sage;
        }
        // 希望所有学生类型的对象,都有toString()可用,可输出学生的属性值
        // 在Student的原型对象中重写和父对象中同名的toString()方法
        Student.prototype.toString=function(){
          return `{ sname:${this.sname},sage:${this.sage} }`
        }
        var lilei=new Student("Li Lei",11);
        var obj={
          x:1,
          y:2,
          // 希望obj也有好用的toString()
          toString:function(){
            return `{x:${this.x},y:${this.y}}`
          }
        }
        var arr=[1,2,3];
        var now=new Date();
        console.log(lilei.toString());//{ sname:Li Lei,sage:11 }
        console.log(lilei);//Student {sname: "Li Lei", sage: 11}
        console.log(obj.toString());//{x:1,y:2}
        console.log(arr.toString());//1,2,3
        console.log(now.toString());//Tue May 05 2020 20:38:23 GMT+0800 (中国标准时间)
      </script>
    

3. 自定义继承

如果子对象觉得整个父对象都不好用,可以换父对象:2种方法

  1. 只修改一个对象的父对象
    1. 不推荐写法:子对象.__proto__=新父对象
      问题:不是所有浏览器都开放__proto__让我们随意使用

    2. 推荐的等效做法:Object.setPrototypeOf(子对象,父对象)
      在这里插入图片描述

    3. 示例:仅修改hmm的父对象

        <script>
          function Student(sname,sage){
            this.sname=sname;
            this.sage=sage;
          }
          Student.prototype.bal=9.9;
          Student.prototype.car="none";
          var lilei=new Student("Li Lei",11);
          var hmm=new Student("Han Meimei",12);
          var father={
            bal:100000000000000,
            car:"infiniti"
          }
          // hmm想用father中的成员,可让hmm继承father
          // hmm.__proto__=father;
          Object.setPrototypeOf(hmm,father);
          console.log(hmm.bal,hmm.car);//100000000000000 "infiniti"
          console.log(lilei.bal,lilei.car);//9.9 "none"
        </script>
      
  2. 同时修改多个子对象的父对象
    1. 只要更换构造函数的prototype属性,指向新的原型对象即可
      构造函数.prototype=新原型对象;

    2. 时机:应该在创建子对象之前就要更换
      好处:之后再创建的子对象都自动继承新的父对象
      而已经创建的子对象所继承的父对象不变,仍是旧父对象
      在这里插入图片描述

    3. 示例:同时更换lilei和hmm的父对象

        <script>
          var father={
            bal:100000000,
            car:"infiniti"
          }
          function Student(sname,sage){
            this.sname=sname;
            this.sage=sage;
          }
          Student.prototype.bal=9.9;
          Student.prototype.car="none";
          // 创建更换原型对象之前的子对象
          var xiaoming=new Student("Xiao Ming",13);
          // 将构造函数的原型对象更换
          Student.prototype=father;
          // 新原型对象的构造函数更换为Student
          father.constructor=Student;
          // 创建子对象
          var lilei=new Student("Li Lei",11);
          var hmm=new Student("Han Meimei",12);
          console.log(xiaoming.bal,xiaoming.car);//9.9 "none"
          console.log(lilei.bal,lilei.car);//100000000 "infiniti"
          console.log(hmm.bal,hmm.car);//100000000 "infiniti"
        </script>
      

六、ES5

ECMAScript语言(js语言的核心语法)标准的第五个升级版本.

1. 严格模式

  1. 什么是严格模式:比普通js运行机制要求更严格的模式
  2. 为什么:旧的js语言存在很多广受诟病的缺陷
  3. 何时:所有的js程序,都需要运行在严格模式下
  4. 如何开启严格模式:在当前代码段的顶部添加字符串:
    "use strict"
  5. 新要求:4个
    1. 禁止给未声明的变量赋值
      1. 旧js中:强行给未声明的变量赋值,会自动在全局创建该变量。-----极容易造成全局污染
      2. 示例:旧js中,非严格模式下给未声明的变量赋值
          <script>
            // "use strict"//启用严格模式
            function send(){
              var gf;
              // 假设不小心把gf写成了qgf
              qgf="今晚308,w84u";
              console.log(gf);
            }
            send();
            console.log(qgf);
            // 如果未启用严格模式
            // 输出undefined和今晚308,w84u
            // 如果启用严格模式
            // 报错:qgf未定义
          </script>
        
    2. 静默失败升级为错误
      1. 静默失败:执行不成功,但是还不报错
      2. 缺点:静默失败极其不便于调试
      3. 严格模式下:将所有的静默失败都升级为错误
      4. 优点:极其便于调试,避免歧义
      5. 示例:对比严格、非严格模式下执行错误的操作
          <script>
            "use strict"//启用严格模式
            var eric={
              eid:1001,//只读
              ename:"埃里克"
            }
            // 设置eid为只读
            Object.defineProperty(eric,"eid",{
              writable:false,//设置eric的eid属性为不可修改--只读
              configurable:false//设置不允许再修改writable
            })
            // 尝试修改eric的eid
            eric.eid=1002;
            console.log(eric);
            // 不启用严格模式
            // 不报错 输出{eid: 1001, ename: "埃里克"}
            // 启用严格模式
            // 报错:提示不可修改只读属性eid
          </script>
        
    3. 普通函数调用和匿名函数自调中的this默认值undefined,而不再指window
      1. 旧js:普通函数调用和匿名函数自调中的this默认指window
      2. 问题:容易导致全局污染
      3. 严格模式下:普通函数调用和匿名函数自调中的this指undefined,而不再指window
      4. 好处:大大减少了因为this导致的全局污染
      5. 示例:对比严格、非严格模式下的错误使用构造函数
          <script>
            "use strict"//启用严格模式
            function Student(sname,sage){
              this.sname=sname;
              this.sage=sage;
            }
            // 正确使用构造函数的用法
            // var lilei=new Student("Li Lei",11);
            // console.log(lilei);
            // 错误的使用构造函数的用法
            var hmm=Student("Han Meimei",12);
            // Student前没有.也没有new,所以Student中的this暂时指向window
            // 相当于:window.sname="Han Meimei";
            // 相当于:window.sage=12;
            console.log(hmm);
            console.log(window.sname);
            console.log(window.sage);
            // 非严格模式下输出:
            // undefined
            // Han Meimei
            // 12
            // 严格模式下输出:
            // 报错:无法设置Student中未定义的属性sname
          </script>
        
    4. 禁止使用arguments.callee
      1. 什么是arguments.callee:是在函数内部,获得当前函数本身的一种关键词
      2. 何时:递归
      3. 问题:如果在函数内递归调用时,写死当前函数的函数名,则一旦当前函数名改变,就必须同时修改函数体中写死的函数名,一旦漏写就报错----紧耦合
      4. 解决:在函数内用arguments.callee自动获得当前函数对象本身,直接用当前函数对象进行递归调用,与函数名无关
      5. 为什么严格模式要禁用arguments.callee:因为递归调用效率极低(重复计算量太大)
      6. 所以严格模式强力不建议使用递归调用arguments.callee,使用就会报错
      7. 解决:多数递归调用都可以用循环来解决----但是难度较大
      8. 总结:但是改用递归还是首先选择递归----因为简单
        除非递归在项目中确实影响效率了,才被迫找循环的方法替代
      9. 示例:使用递归实现斐波那契数列
          <script>
            "use strict"//启用严格模式 
            // 斐波那契数列
            // 1 1 2 3 5 8 13 21 34 55
            // 1 2 3 4 5 6  7  8  9 10
            // 数学公式:f(1)=f(2)=1,n>2是f(n)=f(n-1)+f(n-2)
            function f(n){
              if(n<3){
                return 1;
              }else{
                var fun=arguments.callee;//自动获得当前函数本身
                return fun(n-1)+fun(n-2);
              }
            }
            // 测试
            console.log(f(10));
            // 非严格模式下输出:55
            // 严格模式下:报错
          </script>
        

总结:

一、面向对象

  1. 封装: 创建对象 3种方法:

    1. 只创建一个对象,且已经知道对象的成员是什么:
      var 对象名={
              属性名: 属性值, 	
                  ... : ... ,
              方法名: function(){
                  ... this.属性名 ...
              }
          }
      
    2. 只创建一个对象,但是暂时不知道对象的成员: 2步
      1. 先创建一个空对象等着:
        var 对象名={} //new Object()的简写
      2. 等知道对象的成员之后,再为对象强行添加新属性和方法:
        对象.属性名=属性值
        对象.方法=function(){ ... this.属性名 ...}
    3. 反复创建多个相同结构的对象时: 2步:
      1. 先定义构造函数:
        //因为构造函数是为了描述同一类型的多个对象统一的属性结构
        //所以,构造函数名通常是一种类型的名称,比如学生,商品,订单,用户...
                function 类型名(形参变量列表){
        
                    this.属性名=形参变量;
                        ... = ...
                    //今后构造函数中不要包含方法定义
                }
        
      2. 用new调用构造函数创建对象
        var 对象名=new 类型名(属性值列表);

      new 做了4件事:
      1. 创建一个新的空对象
      2. 让子对象继承构造函数的原型对象(自动设置子对象的_ proto _指向构造函数的prototype对象)
      3. 调用构造函数时,同时将构造函数中的this临时替换为new正在创建的这个新对象
        结果构造函数中的每一句话,都会强行给新对象添加构造函数早就规定好的统一的新属性
      4. 返回新对象地址,保存到等号左边的变量中

  2. 继承:

    1. 何时: 今后只要多个子对象共用的方法或属性值,只要集中放在原型对象中一份,所有子对象即可共用。
    2. 向原型对象中为所有子对象添加共有的方法
      构造函数.prototype.共有方法名=function(){
              ... this.属性名 ... 
          }
      
  3. 多态:
    重写: 如果从父对象继承来的个别成员不好用!就可在子对象中定义同名成员。结果,子对象再使用同名成员时,优先使用自己自有的同名成员。不再使用父对象不好用的同名成员。

  4. 自定义继承: 如果子对象觉得整个父对象都不好用,可认别的父对象当爹

    1. 只修改一个子对象的父对象:
      1. 不推荐: 子对象.__proto__=新父对象
      2. 推荐: Object.setPrototypeOf(子对象, 新父对象)
    2. 如果更换该类型下所有子对象的父对象
      1. 构造函数.prototype=新父对象
      2. 时机: 最好在创建子对象之前,就要替换!

      this的指向: 4种:
      注意:判断this一定不要看定义在哪儿!!只看调用时.前是谁。
      1. obj.fun() fun中的this->.前的obj对象
      2. new Fun() Fun中的this-> new正在创建的新对象
      3. 构造函数.prototype.fun=function(){ … } fun中的this->将来调用这个fun()函数的当前类型的子对象
      4. (function(){ … })() 或 普通函数调用fun() this默认->window

二、ES5

  1. 严格模式: 4个新要求:
    (1). 禁止给未声明的变量赋值
    (2). 静默失败升级为错误
    (3). 匿名函数自调和普通函数调用中的this不再指window,而是undefined
    (4). 禁用了arguments.callee,不推荐使用递归,但该写还是要写。

补充: 函数也是一个对象,既保存函数体,同时也有自己的属性
console.log(Student) 仅输出函数内容
console.dir(Student) 仅输出函数对象在内存中的属性
以上两个输出的内容合起来,才是一个完整的函数对象。
在这里插入图片描述

补充: 不要用for in来遍历索引数组
因为in不仅遍历当前对象的所有自有成员,而且会延_ _proto_ _继续遍历原型对象中深紫色的共有成员(忽略浅紫色的成员)。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值