JS高级-面向对象三大特征(继承)

185 阅读10分钟

1.1-面向对象三大特征介绍

  1. 封装:将某个具体功能封装在对象中,只对外部暴露指定接口,外界在使用的时候,只考虑接口怎么用,不用考虑内部怎么实现(前面学习的api其实就是一种封装思想)
  2. b.继承:一个对象拥有其他对象的属性和方法
  3. c.多态:一个对象在不同情况下的多种状态(java语言使用较多,js语言基本不涉及)

1.2-替换原型继承

继承:让一个对象拥有另一个对象的属性和方法

原型继承 :把父对象作为子对象构造函数的原型

/* 替换原型继承: 把父对象作为子对象构造函数的原型*/

Son.prototype = father

//父对象
        let father = {
            house: {
                address: '深圳湾一号',
                price: 20000000
            },
            car: {
                brand: '劳斯莱斯幻影',
                price: 15000000
            }
        }

        //子对象
        //构造函数
        function Son(name, age) {
            this.name = name
            this.age = age
        }
        //原型继承: 把父对象 作为子对象构造函数的原型
        Son.prototype = father
        //可选 : 原型继承之后,由于父对象覆盖原来的 子对象构造函数原型, 就会导致constructor消失.
        //解决办法: 手动添加。(对开发几乎没有影响,也可以不加)
        Son.prototype.constructor = Son


        //实例对象
        let s1 = new Son('ikun', 30)
        let s2 = new Son('班长', 20)
        console.log(s1, s2)

02-原型链

==1.1-原型链介绍==

  • 1.原型链:每一个对象都有原型,原型本身又是对象,所以原型又有原型,以此类推形成一个链式结构,称为原型链

2.对象访问原型链中的成员规则:就近原则

对象先访问自己的,自己没有就找原型的,原型没有就找原型的原型,一直到原型链终点null.如果还找不到。 属性则获取undefined, 方法则会报错 xxx is not function

xxx is not a function

 //1.构造函数
       function Person(name,age){
           this.name = name
           this.age = age
        //    this.type = '学生'//如果自己有type,优先访问自己的
       }

       //2.原型对象 : 存储具有共同特征的数据
       Person.prototype.type = '哺乳动物'
       Person.prototype.country = '中国'
       Person.prototype.eat = function(){
           console.log(this.name + '吃东西')
       }

       //3.实例对象
       let p1 = new Person('班长',20)
       let p2 = new Person('ikun',20)
       console.log(p1)
       

       /* 小测试 */
       console.log( p1.name )//班长  p1自己有name属性
       console.log( p1.age )//20   p1自己有age
       console.log( p1.type )//哺乳动物  p1自己没有type,但是p1的原型有
       console.log( p1.girlFrined )//undefined  p1自己没有girlFrined, p1的原型也没有girlFrined

       p1.eat()// 吃东西
      //    p1.learn()//报错   undefined is not a function
      /* 思考:  p1自己没有toString, p1的原型也没有toString, 但是为什么不报错呢?
      原因:  p1的原型的原型有toString
      */
       p1.toString()

       /* 如何查看实例对象原型 : 两行 */

       //查看p1的原型
       console.log( p1.__proto__.constructor )//Person
       console.log( Person.prototype === p1.__proto__ )//true
       //查看p1的原型的原型
       console.log( p1.__proto__.__proto__.constructor )//Object
       console.log( Object.prototype === p1.__proto__.__proto__ )//true
       //查看p1的原型的原型的原型
       console.log( p1.__proto__.__proto__.__proto__ )//null

1.2-原型链详解:内置对象的原型链

  • 本小节知识点:内置对象的原型链(附图解说明)
    • 1.通过查看Array的原型链
      • 了解构造函数的原型本身是一个对象,只要是对象就有原型
    • 2.通过查看Date的原型链
      • 学会举一反三,所有的内置对象(Math Array 基本包装类型等)的原型链都是一样的,最终都指向Object
    • 3.通过查看String的原型链:了解这里的String值得是内置对象String(是一个基本包装类型),其他的Number、Boolean原型链和String是一样的
      • 只有对象才有原型,这里一定要把基本数据类型string、number、boolean,和基本包装类型(特殊的引用类型对象)String、Number、Boolean区分开来,不要搞混淆

1.Array的原型链

// 数组对象  
        //实例化对象
        let arr = [10,20,30]//new Array(10,20,30)
        console.log( arr )
        //1.1 查看arr的原型
        console.log( arr.__proto__.constructor )//Array
        console.log( arr.__proto__ === Array.prototype )//true
        //1.2 查看arr的原型的原型
        console.log( arr.__proto__.__proto__.constructor )//Object
        console.log( arr.__proto__.__proto__ === Object.prototype )//true
        
        // 字符串对象
        let str = new String('abc')
        console.log( str )
        //2.1 查看str的原型
        console.log( str.__proto__.constructor )//String
        console.log( str.__proto__ === String.prototype )//true
        //2.2 查看arr的原型的原型
        console.log( str.__proto__.__proto__.constructor )//Object
        console.log( str.__proto__.__proto__ === Object.prototype )//true
        
        // 日期对象
        let date = new Date()
        /* js有几个特殊的对象 无法使用 log来打印的,需要用dir来打印: function date dom对象 */
        console.dir( date )
        //3.1 查看date的原型
        console.log( date.__proto__.constructor )//Date
        console.log( date.__proto__ === Date.prototype )//true
        //3.2 查看date的原型的原型
        console.log( date.__proto__.__proto__.constructor )//Object
        console.log( date.__proto__.__proto__ === Object.prototype )//true

2-Date的原型链

 <div>我是div</div>
    <p>我是pp</p>
    <a href="#">我是aa</a>

    <script>
        let div = document.querySelector('div')
        let p = document.querySelector('p')
        let a = document.querySelector('a')

3-String对象原型链

//4.界面元素    let div1 = document.getElementById('div1');    let p1 = document.getElementById('p1');

完整代码

1.3-instanceof运算符

instanceof语法: 对象 instanceof 构造函数

作用:检测构造函数的原型prototype在不在这个对象的原型链上

  1. instanceof(关键字): 运算符。 用于检测 构造函数的prototype在不在实例对象的原型链中

说人话: 亲子鉴定,鉴定两个对象之间有没有血缘关系

  1. 实例对象 instanceof 构造函数

检测 右边构造函数的prototype 在不在 左边实例对象的原型链中

  1. 应用 : 某些函数为了限制你的数据类型,在内部需要用instanceof进行判断是否是正确的数据类型
let arr = [10,20,30]
        // arr-> Array.prototype -> Object.prototype -> null
        console.log( arr instanceof Array )//true
        console.log( arr instanceof Object )//true
        console.log( arr instanceof String )//false

        //封装一个函数,要求这个函数必须要传数组类型、 传其他类型不可以
        function fn(arr){
            if( arr instanceof Array){
                console.log( arr.reverse() )
            }else{
                console.log('数据类型错误')
            }
        }

        fn( [10,20,30] )
        fn( 'abc' )

03-ES6类与继承

1.1-class关键字介绍


1.1 class关键字作用: 声明一个类函数

  • 相当于ES5的构造函数,只是代码的阅读性大大提高

  • a. 把构造函数和原型全部写在一个大括号里面,提高代码阅读性

  • b. 必须要使用new才可以调用class函数,提高代码规范

1.2 class关键字语法:

class 构造函数名{
                    constructor(){
                        //(1)这里写构造函数代码
                    };

                    //(2)原型里面的方法写在下面
                    eat(){

                    };

                    play(){

                    };
                };

        2.学习路线:对比法
            2.1 ES5 构造函数与原型语法
            2.2 ES6 构造函数与原型语法
        */


        //1.ES5得构造函数与原型写法

        // //(1)构造函数
        // function Person(name,age){
        //     this.name = name;
        //     this.age = age;
        // };

        // //(2)给原型添加方法
        // Person.prototype.eat = function(){
        //     console.log('大吉大利今晚吃鸡');
        // };

        // Person.prototype.sayHi = function(){
        //     console.log('你好,我是四大天王黎明');
        // };

        // //(3)实例对象
        // let p1 = new Person('ikun',30);

        // console.log(p1);


        //2. ES6的构造函数写法

        //(1)声明类Person
        class Person {
            //(1) 构造函数  : 名字必须叫做 constructor
            constructor(name, age) {
                this.name = name;
                this.age = age;
            };

            //(2) 给原型添加方法  : 方法名(){}
            sayHi() {
                console.log('你好,我是四大天王黎明');
            };

            eat() {
                console.log('大吉大利今晚吃鸡');
            };
        };

        /* 细节: class本质其实就是构造函数的另一种写法,本质还是给prototype添加成员
        使用了class,一样可以继续使用ES5的prototype
        */
        Person.prototype.type = '人类';

        //(3)实例对象
        let p1 = new Person('ikun', 30);
        console.log(p1);

1.2-extends关键字介绍


 学习目标:  extends关键字
            1.作用:  继承
            2.底层原理: 替换原型继承
        */
        //(1)声明类Person
        class Person {
            //(1) 构造函数  : 名字必须叫做 constructor
            constructor(name, age) {
                this.name = name;
                this.age = age;
            };

            //(2) 给原型添加方法  : 方法名(){}
            sayHi() {
                console.log('你好,我是四大天王黎明');
            };

            eat() {
                console.log('大吉大利今晚吃鸡');
            };
        };

        Person.prototype.type = '人类';
        //(3)实例对象
        let p1 = new Person('ikun', 30);
        console.log(p1);

/* extends关键字 */
        //声明一个类函数 Student  继承于  Person 类函数
        //底层原理相当于  Student.prototype.__proto__ = Person.prototype
        class Student extends Person{
            
            //Student原型添加方法
            learn(){
                console.log('今天学的很开心');
            };
        };

        let s1 = new Student('班长',20);
        console.log(s1);

        s1.learn();//s1自己没有learn,但是原型有

        s1.eat();//s1自己没有eat,原型没有eat, 但是原型的原型有eat

1.3-super关键字介绍


 /* 
        学习目标:  super关键字
            1.作用:  子类中调用父类的方法
            2.底层原理: 函数上下文调用
        */
        //(1)声明类Person
        class Person {
            //(1) 构造函数  : 名字必须叫做 constructor
            constructor(name, age) {
                this.name = name;
                this.age = age;
            };

            //(2) 给原型添加方法  : 方法名(){}
            sayHi() {
                console.log('你好,我是四大天王黎明');
            };

            eat() {
                console.log('大吉大利今晚吃鸡');
            };
        };

        Person.prototype.type = '人类';
        //(3)实例对象
        let p1 = new Person('ikun', 30);
        console.log(p1);


        /* super关键字 : 子类中调用父类的方法
        如果在子类中,也写了 constructor函数,则必须要调用  super()否则程序报错
        */
        /* 声明子类Student 继承 父类 Person */
        class Student extends Person{
            //子类构造函数
            constructor(name,age,score){
                /* 注意点:如果子类也写了constructor,则必须要调用super */
                super(name,age);
                this.score = score;
            }
            
            //Student原型添加方法
            learn(){
               console.log('1.我要吃饭了');
               //调用Person原型中的eat()
                //底层原理: Person.call(this)
               super.eat();
               console.log('3.我开始学习了'); 
            };
        };

        let s1 = new Student('班长',20,99);
        console.log(s1);

        s1.learn();//s1自己没有learn,但是原型有

        s1.eat();//s1自己没有eat,原型没有eat, 但是原型的原型有eat

04-函数补充(了解即可)

1.1-arguments关键字


1.arguments关键字: 获取函数所有实参

  • 是一个伪数组

  • 只能用于函数

2.场景 : 例如数组push()方法,传多少实参就给数组添加多少元素。底层原理就是使用arguments实现的

function fn(){
            console.log(arguments)
         }

         fn(10,20,30,40)

1.2-剩余参数(rest参数)


1.剩余参数(rest参数) : 获取函数剩余的所有实参

  • 是一个真数组

  • 只能写在函数最后形参位置

2.场景:大多数情况下, rest参数可以替代arguments

arguments是老语法,浏览器兼容性好

rest参数:新语法,有些老的浏览器不支持

 function fn(a,b,...c){
            console.log(arguments)//获取所有实参 10 20 30 40
            console.log(a,b)//10 20
            console.log(c)//[30,40] 获取剩余参数
         }

         fn(10,20,30,40)

1.3-函数默认参数


/*函数默认参数

ES5 : 逻辑或短路

  • 逻辑或口诀: 一真则真

  • 逻辑或短路口诀 : 找真. 左边为真就是左边的结果,否则右边的结果

ES6 : 形参=默认值

function fn(a, b = 20) {
            // b = b || 20
            console.log(a)
            console.log(b)//如果b是undefined, 则 a + b = NaN
            //10 + Number(undefined) = 10 + NaN = NaN
            console.log(a + b)
        }

        fn(10,5)
        fn(10)

今日学习总结

  • 1.面向对象三大特征
    • a.封装:将某个功能封装到对象或函数中
    • b.继承:一个对象拥有另一个对象的所有成员变量(属性和方法)
    • c.多态:一个对象在不同情况的多种状态
  • 2.实现继承的几种方式
    • a.混入式继承
      • 解决方案:遍历父对象的所有属性值,添加给子对象
      • 弊端:每继承一次,就要执行一次循环
      • 应用场景:父对象只有一个子对象
    • b.替换原型
      • 解决方案:将父对象作为子对象构造函数的原型
      • 弊端:会丢失原型之前的成员变量
      • 应用场景:自定义内置对象
    • c.混合式(混入+替换原型)
      • 解决方案:遍历父对象所有的属性值,添加给构造函数的原型
      • 应用场景:父对象有多个子对象
  • 3.原型链
    • 原型链:每一个对象都有原型,原型本身又是对象,所以原型又有原型,以此类推形成一个链式结构,称为原型链
  • 对象在原型链中的访问规则:就近原则
    • 当访问对象成员变量时,会先从自己的属性中查找,如果有就访问,没有就访问自己原型的,如果原型中没有,则访问原型的原型,以此类推,如果访问到原型链的顶端还是没有,则程序报错xxxx is not undefined
    • 特殊情况:Object.prototype的原型对象是null
  • d.函数本身也是对象
    • 构造函数属于函数
  • 5.instanceof运算符
    • 语法:对象 instanceof 构造函数
    • 作用:检测构造函数的原型prototype在不在这个对象的原型链上