类、实例、对象、原型链、继承深度理解

828 阅读11分钟
  1. 面向对象编程是:把事务分解成一个一个对象,然后由对象之间的分工与合作。
  2. 面向对象的特性:封装性、继承性、多态性。
  3. 不同的对象抽取成类,一个类实例成不同的对象。
  4. JS中,对象是一组无序的相关属性和方法的集合,所有事务都是对象。属性是对象的特征,方法是对象的行为。
  5. ES6中的class:类抽象了对象的公共部分。而对象特指某一个,通过类实例化产生的一个具体的对象。
  6. ES6中创建类:
  • 直接创建

    //创建一个类
    class newMan{
    
    }
    //利用类创建对象
    new newMan();
    
  •  加入constructor构造函数——用于传递参数和返回实例对象,通过new调用,类里面定义函数不需要加上function。

    //创建一个类
    class newMan{
       //构造函数
          constructor(uname,age){  
        //接收传递的参数 传递给属性'uname'
            this.uname=uname;
            this.age=age;
            
    }
    }
    //利用类创建对象 传递uname为'刘德华' 传递age为18
    var ldh=new newMan('刘德华'18);
    
    
  •   类中添加方法——类里面的函数不需要写function 多个函数方法之间不需要使用逗号分隔,一般方法定义在类的原型对象上面,供实例调用。通过实例去调用时,里面的this指向类的实例。凡是被static修饰的属性和方法都是静态方法和属性,只能被类名调用,不能被实例化对象调用.同时也不能被子类继承,换句话说它属于当前这个类的.

    //创建一个类
    class newMan{
       //构造函数
          constructor(uname,age){  
        //接收传递的参数 传递给属性'uname'
            this.uname=uname;
            this.age=age;       
    }
          sing(){
             console.log("我在唱歌")
    }
    }
    //利用类创建对象 传递uname为'刘德华' 传递age为18
    var ldh=new newMan('刘德华'18);
    //调用
    ldh.sing();
    
  • 类的继承——extends关键字,继承父类。super关键字能够调用父类的构造函数或者普通函数。继承中,如果实例化输出一个方法,先看子类中有没有这个方法,如果有就可以调用,如果没有就会去父类查看有没有该方法,如果有就调用父类中的方法。(就近 )。如果没有自己独有的属性,可以不加构造函数。

    //父类
    class Father{
       constructor(x,y){
            this.x=x;
            this.y=y;
    }
       money(){
        console.log(100);
    
    }
       sum(){
         return this.x+this.y;
    }
    say(){
        console.log("我是father")
    }
    }
    //子类继承   这里注意 调用super之后才能调用子类this
    class Son extends Father{
       constructor(x,y){ 
         super(x,y);//super回去调用父类的构造函数 对父类的x、y进行初始化 就可以调用sum方法了
         this.x=x;
         this.y=y;    
    }
        say(){
        console.log(super.say()+"的son");//使用super可以调用父类的普通函数
    }}
    //调用  如果没有加入super方法是无法调用sum函数的,因为new Son是把子类的x、y给初始化了,并没有初始化父类
    //父类的x、y,所以想要调用父类的方法,并且传递参数,使用super方法。
    var son=new Son(1,2);
    son.money();
    son.sum();
    son.say();
    
    

 7.ES6中类和对象的三个注意点:

  • 类没有变量提升,必须先定义类,再实例化类。
  • 类里面共有的属性和方法一定要加this使用(函数调用必须指定其调用者,因为实例化后就是this指向这个实例,所以要用this来调用)。
  • 类里面this的指向问题:constructor里面的this是指创建的实例。方法里面的this指向其调用者。(如果需要用到其他作用域中的变量就可以申明一个全局中间对象去接收那个作用域来使用)

8.进一步理解ES5中的构造函数、原型:

  • ES6之前没有类的概念。
  • 创建对象的三种方式:利用new Object()、利用对象字面量、构造函数。
  1. ES6之前是通过构造函数来创建对象的。总与new一起使用,我们把一类对象的公共属性和方法封装到这个函数里面。一般首字母大写。

     function Yyx(uname,age){
           this.name=uname;
           this.age=age;
           this.ismale=function(){
               return true;
        }
    }
    var yyx=new Yyx('Allen',24);
    
  2. new的作用:在内存空间创建一个新的对象、让this指向这个对象、执行构造函数添加属性和方法、返回这个新对象。(与全局对象作为同级的对比)

  3. 实例成员就是构造函数内部通过this添加新的成员 ,实例成员只能通过实例化的对象来访问。

  4. 静态成员就是直接在构造函数本身上添加的成员。只能自己调用,实例不能调用。

  5. 构造函数方法存在内存问题:因为一般方法是复杂数据类型,每一次实例化都会为它开辟一个内存(地址不同),如果多次创建实例就会开辟多个内存,造成内存浪费。

  6. 正是因为上述的内存问题就引出了构造函数的原型概念——prototype:无论在哪里直接给构造函数新建属性和方法,都会放到该构造函数的原型里面作为原型的属性和方法,构造函数也是原型构造函数中的一个方法。也被称为原型对象,其的方法和属性用于实例的共享。

  7. 一般公共属性放在构造函数里面、公共的方法定义放到原型对象上面。

  8. 对象有一个_proto_属性(系统自己添加),指向构造函数的prototype对象。

  9. 方法的查找方法:先在实例化的对象上面查找,如果有就直接调用,如果没有,因为有_proto_属性(称为对象的原型),就去构造函数的原型对象里面去找,如果有就直接调用。关系如下图:

10.constructor指向的是构造函数本身。如果我们修改了原来的原型对象,给原型对象赋值一个对象,则必须手动的利用constructor指回原来的构造函数。如下图:

9.ES5原型链:

  • 新建类里的原型对象的对象的原型是指向Object原型对象(Object.prototype)。如图:

    所有的__proto__组成了原型链。

  • 查找原则:沿着原型链查找。

  • 如果有同名的定义,那么就近原则取值。

  • 找不到就输出undefined。

  • this指向问题:构造函数中this指向对象实例、原型对象中的函数中的this是也指向的是对象实例或者自己本身(因为是指向调用者的,其本身也可能调用自己的方法,不过一般我们不这样使用)。

10.原型对象可以添加方法来扩展内置对象。

  • 注意不能使用字面量创建对象的方式,只能用.属性的方式,这样才不会被覆盖。——尤其是JS内置的类,如Array、String等。

11.ES5继承相关:构造函数和原型对象实现继承,称为组合继承。

  • call()函数,调用一个函数(可以传入参数)并且修改函数运行时的this指向。将函数用于这个this指向。
  • 利用父构造函数和call方法来将运行this指向子构造函数来实现构造函数的继承。
  • 继承方法的时候,不能直接将父原型对象赋值给子原型对象,这样修改子原型对象就会把父原型对象一起修改了。
  • 所以使用将父类实例赋值给子类原型对象的方法来规避上述问题,因为实例的地址和原型对象的地址是不一样的,修改一个不会修改另一个,并且可以通过原型链继承原型对象中的方法。但是,请注意,还是得使用constructor手动指向子类构造函数。形成了一个闭环
  • 父类构造函数继承到子类构造函数,创建子类实例,子类实例__proto__指向子类原型对象====父类实例(并且将子类原型对象,也就是父类实例里面添加constructor指向子类自己的构造函数),所以子类原型对象的__proto__指向父类的原型对象(父类实例的__proto__当然指向父类的原型对象),那么通过原型链,子类实例即可继承到父类原型对象中共享的方法了。并且在子类原型对象中添加自己的方法时,因为此时指向的是父类实例,所以相当于是在父类实例里面添加方法,不会影响父类的原型对象。

12**.**函数进阶:

  • 自定义函数、匿名函数、new function定义的函数。

    funcion fn(){}
    var fun=function(){}
    var f=new Function('a','b','console.log(abc)');
    
  • 所有函数都是Function的实例(对象)。

  • 关系如图:

  • 函数调用:

  1. 普通函数:函数()进行调用。或者call方法调用。
  2. 对象的方法:对象.方法进行使用。
  3. 构造函数:new 构造函数使用。
  4. 绑定事件函数:触发事件即可调用。
  5. 定时器函数:定时器自动调用。
  6. 立即执行函数:自动调用。
  • this指向问题:
  1. 一般情况下,this指向函数的调用者。
  2. 全局普通函数里面:this指向window。
  3. 对象调用function定义的方法:this指向这个对象。
  4. 构造函数:this指向这个实例对象。同时,原型对象中的this也是指向这个实例对象。
  5. 绑定事件:this指向这个触发元素。
  6. 定时器:this指向window对象。
  7. 立即执行函数:this指向window对象。
  • 改变函数内部的this指向方法:
  1. call方法:可以调用函数,并且指定函数中this的指向。call方法还有一个主要方法是可以实现继承——利用把父类构造函数中的this指向改为子类的this来调用父类的构造函数来实现继承。
  2. apply方法也可以调用函数并且改变函数内部this指向,但是传递参数时必须以数组的形式传递,传递过去时会自动进行数组的解构,可以用于调用JS中Math类中的各种方法。
  3. bind方法:改变函数内部this指向,传递参数,不调用函数,返回一个拷贝函数。有时候不需要立即调用函数,又需要改变函数内部this值指向,就可以利用bind方法。
  • 严格模式:
  1. 严格模式是JS运行环境严格,修复一些行为。
  2. 安全运行。
  3. 为未来JS版本保护内置变量名。
  4. 不同的严格模式(ES5\IE10之后的版本才能识别):
  • 脚本的严格模式:'use strict'。如果想让脚本处于一个独立的运行环境,所有脚本内容应包含在一个立即执行函数里面。
  • 函数的严格模式:函数体里面第一行加入:'use strict'。

  5.严格模式的变化:

  • 变量必须先声明再使用。
  • 不能删除以及申明好的变量。
  • 全局作用域的this是undefined。
  • 构造函数不加上new使用,this会报错。
  • 定时器里面this还是指向window。
  • 函数里面不能有重名的参数。
  • 严格模式下,不能在非函数的代码块中定义函数。

 6.高阶函数:对其他函数进行操作的函数,以函数为传入参数或者以函数作为返回值输出。

 7.递归函数:函数内部自己调用自己。

  • 递归函数里面必须加入停止条件,让其退出。
  • 递归可以用来解决数学上的一些有规律的计算问题。如阶乘问题、斐波那契数列问题。
  • 递归用于访问数据,访问外层数据和内层数据。定义局部变量来获取访问到的值,但是需要注意:及时拿回每一层递归的返回值。
  • 注意foreach方法不能break,所以考虑替用some(返回true时break)和every方法(返回false时break)。
  • null和{}的区别:定义对象时,

8.箭头函数的重认识:

  • 为什么对象中定义的箭头函数中的this指向的是Window,因为对象不能产生作用域。箭头函数的this实际上是被定义在了全局作用域下面

9.JS模块化理解:

13.小结:

  • ES6之前通过构造函数、原型实现面向对象编程。特点有:
  1. 构造函数。
  2. 构造函数的prototype属性指向构造函数的原型对象。
  3. 原型对象的constructor指回构造函数。
  4. 构造函数的实例的__proto__属性指向构造函数的原型对象。
  5. 构造函数可以通过原型对象添加共享方法。
  • ES6通过类实现面向对象编程
  • class类的本质是一个函数,类就是构造函数的另一种写法。
  • ES6类的绝大部分功能都可以通过ES5实现,新的class写法只是让对象原型的方法写起来更加清晰,更加的面向对象——是一个语法糖,就是一种更便捷的方法。
  • ES5的继承,实质是先创造子类的实例对象this,然后再将父类的方法添加到this上(Parent.apply(this)),ES6的继承机制完全不同,实质是先创造父类的实例对象this(所以必须先调用super方法),然后再用子类的构造函数修改this。