💎一站式轻松地调用各大LLM模型接口,支持GPT4、智谱、星火、月之暗面及文生图 广告
# :-: **JS的继承** [TOC] ## 1、js 继承的六种方式: 定义一个父类Person: ~~~ function Person(name){ this.name=name; } Person.prototype.age=25; Person.prototype.show=function(){   console.log(this.name); } ~~~ **1.原型链继承** 子类的原型是父类的实例。 ~~~ function subType(){} subType.prototype=new Person(); // 测试 let p1=new subType(); p1.name; //undefined,可以访问,但是name未初始化所以为undefined p1.age; //25 ~~~ 特点:父类的构造函数中的属性和方法,以及原型中的属性和方法都放入了子类的原型中,都可以访问得到。 问题: 1.原型中的属性和方法,在实例中是共享的。构造函数中的属性和方法在实例中不共享。这说明,父类中的所有属性,在子类的实例中都将共享,假如父类有一个数组,那么子类的实例将共享这个属性,当有一个子类实例改变这个数组的项时,所有的实例中的这个属性都会随之改变。 2.创建子类实例时,不能向超类型构造函数中传递参数。 **2.借用构造函数** 在子类构造函数中调用父类构造函数。 ~~~ function subType(name){ Person.call(this,name); } // 测试 let p2=new subPerson("zhangsan"); p2.name; //"zhangsan" p2.age; //undefined ~~~ 特点:解决了方法一的问题,所有的子类实例都将不共享属性和方法,互不影响,并且可以传值。 问题: 1.构造函数中的方法不能共享复用,每个实例都将拥有相同的方法。 2.父类的原型中的方法和属性在子类中不可见。 **3.组合继承** 组合原型链方法和借用构造函数方法 ~~~ function subType(name){ Person.call(this,name); } subType.prototype=new Person(); //测试 let p3=new subType("lisi"); p3.name; //"lisi" p3.show(); //"lisi" p3.age; //25 ~~~ 特点:既可以继承父类原型中的属性和方法,又能将父类的构造函数放入子类构造函数中。 缺点:父类的实例属性在子类的实例和原型中都有,属性重复了。 **4.原型式继承** 以一个对象为基础,创建一个新对象,并将旧对象作为新对象的原型,返回这个新对象的实例。 ~~~ function clone(o){ function fn(){}; fn.prototype=o; return new fn(); } var subType=clone(superType); //测试 let person=new Person("wangwu"); var subType=clone(Person); let p4=new subType(); p4.name; //"wangwu" p4.age; //25 ~~~ 特点:要求必须有一个对象可以作为另一个对象的基础。返回的是一个对象。可以向超类型构造函数中传递参数。 缺点:父类的实例属性不能被继承。 **5.寄生式继承** 构造函数中通过某种方式创建父类对象,然后为这个对象添加属性和函数,并返回这个对象。 ~~~ function clone(o){ function fn(){}; fn.prototype=o; return new fn(); } function inhire(original){ let obj=clone(original); obj.sayHi=function(){ alert("Hi"); }   return obj; } var suubType=inheir(SuperType); ~~~ 特点:创建对象部分不必要固定方式,只要可以返回一个父类对象就可。返回的是一个加入了自定义方法和属性的对象。 缺点:自定义的方法不能共享复用。 **6.寄生组合式继承** 使用寄生式继承来继承超类型的原型。使用借用构造函数的方法继承父类的构造函数属性和方法,并添加自己的构造函数属性和方法。 ~~~ function object(o){ function fn(){}; fn.prototype=o; return new fn(); } function inheritPrototype(subType,superType){ let p=object(superType.prototype); p.constructor=subType; subType.prototype=p; } //子类 function subType(name){ Person.call(this,name); } inheritPrototype(subType,Person); ~~~ 特点:避免了组合式继承中原型和构造函数中重复继承父类的构造函数中的属性和方法。 ## 2、es6 中的class ### *2.1、Class简述* **1、ES6中的class可以看作是一个语法糖,它的绝大部分功能,ES5都可以做到,新的class写法只是让对象原型的写法更加清晰,更加面向对象编程的语法而已。** ~~~javascript class Point { // 定义一个Point类 constructor(x,y){ // 构造方法 this.x=x; this.y=y; } // =>类的所有方法都定义在类的prototype属性上。 // =>在类的实例上调用方法,其实就是调用原型的方法。 // =>类的内部所有方法都是不可枚举的 toString(){ // 定义toString方法 return '('+this.x+','+this.y+')'; } } // =>Object.assign方法可以方便地一次向类添加多个方法。 Object.assign(Point.prototype,{ toValue1(){ return "添加方法1"}, toValue2(){ return "添加方法2"} }) console.log(typeof Point); // =>function =>类的数据类型就是函数 console.log(Point === Point.prototype.constructor) // =>true =>类的本身就指向构造函数 var point1=new Point(); // 使用的时候也是直接对类使用new命令,跟构造函数的用法完全一致。 console.log(point1.toString()); // =>(undefined,undefined) var point2=new Point(1,1); // 使用的时候也是直接对类使用new命令,跟构造函数的用法完全一致。 console.log(point2.toString()); // =>(1,1) ~~~ **2、类和模块的内部默认使用严格模式,所以不需要使用 use strict 指定运行模式。只要将代码写在类或模块之中,那么就只有严格模式可用。** **3、constructor 方法是类的默认方法,通过 new 命令生成对象实例时自动调用该方法。一个类必须有 constructor 方法,如果没有显示定义,一个空的 constructor 方法会被默认添加。** **4、类必须使用 new 来调用,否则就会报错。** **5、实例的属性除非显示定义在其本身(即 this 对象)上,否则都是定义在原型(即 Class )上。 ** **6、类的实例共享一个原型对象。** **7、Class表达式** ~~~javascript // =>Class也可以使用表达式的形式定义。 // =>注意这个类的名字是MyClass而不是Me,Me只在Class的内部代码可用,指当前类。 const MyClass = class Me{ getClassName(){ return Me.name; } } ~~~ **8、类不存在变量提升** **9、ES6不提供私有方法,不支持私有属性。目前有一个提案为class加私有属性。方法是在属性名前,使用#来表示。** **10、类的方法内部如果含有 this,它将默认指向类的实例。** **11、name 属性返回 class 后面的类名。** ~~~javascript class Point{ } console1.log(Point.name); //=>"Point" ~~~ **12、在类的内部可以使用 get 和 set 关键字对某个属性设置存储函数和取值函数,拦截该属性的存取行为。** ~~~javascript class MyClass{ constructor(){ // ... } get prop(){ return 'getter'; } set prop(value){ console.log('setter'+value); } } let inst=new MyClass(); inst.prop=123; // =>setter123 console.log(inst.prop); // =>getter ~~~ **13、类相当于实例中的原型,所有在类中定义的方法都会被实例继承。如果在一个方法前加上static关键字,就表示该方法不会被实例继承,而是直接通过类调用,称之为“静态方法”。** ~~~javascript class Foo{ static classMethod(){ return 'hello'; } } console.log(Foo.classMethod()); // =>hello let foo=new Foo(); foo.classMethod(); // =>Uncaught TypeError: foo.classMethod is not a function ~~~ **14、父类的静态方法可以被子类继承。** ~~~javascript class Foo{ static classMethod(){ return 'hello'; } } class Bar extends Foo{ } console.log(Bar.classMethod()); // =>hello ~~~ **15、 静态方法也可以从super对象上调用。** ~~~javascript class Foo{ static classMethod(){ return 'hello'; } } class Bar extends Foo{ static classMethod(){ return super.classMethod()+',too'; } } console.log(Bar.classMethod()); // =>hello,too ~~~ **16、静态属性指的是Class本身的属性,即Class.propname,而不是定义在实例对象(this)上的属性。Class内部只有静态方法,没有静态属性。正确写法如下:** ~~~javascriptclass Foo{ } Foo.prop=1; console.log(Foo.prop); // =>1 ~~~ **17、Class的实例属性可以直接用等式写入类的定义之中。** **18、Class的静态属性只要在实例属性写法前面加上 static 关键字就可以了。** **19、new.target属性用于确定构造函数是怎么调用的。** ### 2.2、Class的继承 **首先我们要知道一下这些要点:** **1、Class通过 extends 关键字实现继承。** **2、super 关键字既可以当作函数使用,也可以当作对象使用。** **        ① super 作为函数调用时表示父类的构造函数,用来新建父类的this对象。** **        同时需要注意:** **        a、子类必须在 constructor 方法中调用 super 方法,否则新建实例时会报错。** **        b、如果子类没有定义 constructor 方法,那么这个方法就会被默认添加。** **        c、在子类的构造函数中,只有调用 super 方法之后才能够使用 this 关键字,否则会报错。** **        d、super 虽然代表了父类的构造函数,但是返回的是子类的实例,即 super 内部的 this 指定的是子类。** **        e、super () 只能用在子类的构造函数之中,用在其他地方会报错。** **        ② super 作为对象时在普通方法中指向父类的原型对象;在静态方法中指向父类。** **        同时需要注意:** **        a、由于 super 指向父类的原型对象,定义在父类实例上的方法或属性是无法通过 super 调用的。如果定义在父类的原型上,super 就可以取到。** **        b、ES6规定,通过 super 调用父类的方法时,super 会绑定子类的 this。** **        c、如果通过 super 对某个属性赋值,这时 super 就是 this,赋值的属性会变成子类实例的属性。** **        d、如果 super 作为对象用在静态方法之中,这时 super 将指向父类,在普通方法之中指向父类的原型对象。** ## 3、面试题 第一题:class 继承 ~~~javascriptclass class Point{ // 定义了一个名字为Point的类 constructor(x,y){ // constructor是一个构造方法,用来接收参数 this.x=x; this.y=y; this.z=1; this.a=3; } toString(){ return "颜色"; } print(){ console.log(this.z); } static myMethod(msg){ console.log('static',msg); } myMethod(msg){ console.log('instance',msg); } } class ColorPoint extends Point{ constructor(x,y,color){ super(x,y); // 调用父类的constructor(x,y) this.z=2; this.a=4; super.a=5; this.color=color; console.log(super.a); console.log(this.a); } toString(){ // 这是一个类的方法,注意前面没有function return this.color+' '+super.toString(); // 调用父类的toString() } m(){ super.print(); } static myMethod(msg){ super.myMethod(msg); } myMethod(msg){ super.myMethod(msg); } } let color1=new ColorPoint(1,1,"red"); let color2=new ColorPoint(2,2,"blue"); console.log(color1); console.log(color1.toString()); color1.m(); ColorPoint.myMethod("你好"); color1.myMethod("你好"); console.log("==================="); console.log(color2); console.log(color2.toString()); color2.m(); ~~~ 下面是答案 下面是答案 下面是答案 下面是答案 下面是答案 下面是答案 下面是答案 下面是答案 下面是答案 ![](https://img.kancloud.cn/18/73/1873db8a8526fa3e86049f00cfaf3329_442x374.png) 第二题:js 继承的继承 有哪几种方式 有什么优缺点;