js面向对象三大特性之系列二 继承

    技术2023-12-01  92

    继承,js面向对象的三大特点之一

    1、何谓继承

    定义: 继承就是子类可以使用父类的所有功能,并且能够对这些功能进行扩展。继承的优点: 继承可以使得子类具有父类的各种属性和方法,而不需要再次编写相同的代码,同时,还可以重新定义某些属性、方法,即覆盖父类原有的属性和方法,使其获得与父类不同的功能

    2、实现继承的方式

    构造函数实现继承 ---------- call/apply原型链实现继承 --------- prototype组合继承 -------- prototype + call/applyes6的extends实现继承------extends方法原型式继承----- Object.create()寄生继承-----对原型继承的增强寄生组合式继承 ---- 是对原型式继承与原型链继承的结合

    2.1、借助构造函数实现继承(call、apply)---- 数据私有化

    借助 call调用Parent函数

    优点: 实例化后,被继承的构造函数内的属性与方法,相互独立,互不影响 (私有对私有) 缺点: 被继承的构造函数的原型对象(Parent.prototype)上的方法和属性,不会被子类不会被继承。

    function Parent() { this.age = 100 this.arr = [] } Parent.prototype.say = function() { alert(this.age) }; function Child() { this.love = 'learn English' Parent.call(this) } let obj0 = new Child() let obj1 = new Child() obj0.age = 51; obj1.age = 10000; obj0.arr.push(100); obj1.arr.push(100000); console.log(obj0.age,obj1.age) // 51 10000 相互独立 console.log(obj0.arr,obj1.arr) //[100] [100000] 相互独立 obj.say() //报错:obj.say is not a function

    上面代码相当于函数Child继承了函数Parent的私有属性,通过改变父类的this实现继承

    2.2、借助原型链实现继承(prototype)---- 数据公有化

    优点: 被继承的构造函数的原型对象(Parent.prototype)上的方法和属性,可以被子类不会被继承。

    缺点: 由于是通过prototype实现继承,所以被继承的构造函数上的引用类型的属性是共有的,改变一个,另一个也跟着改变,eg:obj2.info,obj3.info

    function Parent1() { this.age = 30; this.arr = []; } Parent1.prototype.getName = function() { return '你好' } Parent1.prototype.info = {}; function Child1() { this.name = 'Tony' } Child1.prototype = new Parent1() //把父类的实例挂载到子类的原型链上 Child1.constructor = Child1 //把该构造函数的构造方法指向自身 let obj2 = new Child1() let obj3 = new Child1() obj2.age = 70; obj3.age = 100; obj3.arr.push(100); obj2.arr.push(500); console.log(obj2.age,obj3.age) // 70 100 基本类型相互独立 console.log(obj2.arr,obj3.arr) // [100, 500] [100, 500] 引用类型的属性则是共有的,一个改变,别一个也改变 obj2.info['name'] = 'test'; obj3.info['sex'] = 'man'; console.log(obj2.info,obj3.info) console.log(obj2.getName()) // 你好 可以访问到被构造函数的原型对象上属性和方法

    原型继承通过将父类的实例赋值给子类的原型,这种方法子类可以继承父类的私有属性也可以继承父类的私有方法

    2.3、组合继承( prototype + call/apply ) — 私对私,公对公(推荐!!!)

    实例化对象后, 公有数据通过prototype继承(改变一个,另一个也跟着改变,不过只适用于引用类型的数据), 私有数据通过call/apply继承,在其被继承的构造函数内创建后被继承

    function Parent5() { this.name = 'name'; this.list = [] } Parent5.prototype.arr = []; function Child5() { Parent5.call(this); //私有对私有 this.type = 'child5'; } Child5.prototype = new Parent5(); // 使用原型链实现继承,公有对公有 Child5.prototype.constructor = Child5; //修改其构造函数为其自身 var s7 = new Child5(); var s8 = new Child5(); s7.name = 's7-name'; s8.name = 's8-name'; console.log(s7.name,s8.name); // s7-name s8-name s7.list.push(100) s8.list.push(1000); console.log(s7.list,s8.list); // [100] [1000] s7.arr.push(100); s8.arr.push(1000); console.log(s7.arr,s8.arr); // [100, 1000] [100, 1000]

    寄生组合继承就是 1、使用call继承改变this,实现私有继承私有, 2、使用原型链实现公有继承公有

    这种方式看起来就没什么问题,方式一和方式二的问题都解决了,但是从上面代码我们也可以看到Parent5执行了两次,造成了多构造一次的性能开销

    2.4、es6 extends 继承

    class parent{ constructor(){ this.a=1 } name(){ return 2 } } class child extends parent{ } let A = new child() console.log(A.a) // 1 console.log(A.name()) // 2

    这里通过关键字extends实现子类继承父类的私有和公有

    这里需要注意如果子类里面写了constructor,就必须写super否则会报错 例子如下:

    class parent{ constructor(){ this.a=1 } name(){ return 2 } } class child extends parent{ constructor(){ super(); // 这里不写super会报错,就会报错,报错信息如下 Uncaught ReferenceError: Must call super constructor in derived class before accessing 'this' or returning from derived constructor } } let A = new child() console.log(A.a) // 1 console.log(A.name()) // 2

    2.5、原型式继承----- Object.create()

    这里主要借助Object.create方法实现普通对象的继承

    let parent4 = { name: "parent4", friends: ["p1", "p2", "p3"], getName: function() { return this.name; } }; let person4 = Object.create(parent4); person4.name = "tom"; person4.friends.push("jerry"); let person5 = Object.create(parent4); person5.friends.push("lucy"); console.log(person4.name); // tom console.log(person4.name === person4.getName()); // true console.log(person5.name); // parent4 console.log(person4.friends); // ["p1", "p2", "p3","jerry","lucy"] console.log(person5.friends); // ["p1", "p2", "p3","jerry","lucy"]

    这种继承方式的缺点也很明显,因为Object.create方法实现的是浅拷贝,多个实例的引用类型属性指向相同的内存,存在篡改的可能。

    2.6、寄生继承

    寄生式继承在原型式继承的基础上进行优化,利用这个浅拷贝的能力再进行增强,添加一些方法。

    寄生继承首先将一个普通对象克隆进一个叫that的对象,然后扩展that对象,添加更多的属性,最后返回that对象。

    function createAnother(original){ var that= Object.create(original); //通过调用函数创建一个新对象 that.sayHi = function(){ //以某种方式来增强这个对象 alert("Hi"); }; return that; //返回这个对象 } var person = { name: "Bob", friends: ["Shelby", "Court", "Van"] }; var anotherPerson = createAnother(person); anotherPerson.sayHi();

    2.7、寄生组合式继承

    寄生组合式继承是js最常用的继承模式,也是这几种中最好的继承方式。

    组合继承最大的问题就是无论在什么情况下,都会调用两次构造函数:一次是在创建子类型原型时,另一次是在子类型构造函数内部。而寄生组合式继承只须调用一次

    function clone (parent, child) { // 这里改用 Object.create 就可以减少组合继承中多进行一次构造的过程 child.prototype = Object.create(parent.prototype); child.prototype.constructor = child; } // 构造函数Parent6 function Parent6() { this.name = 'parent6'; this.play = [1, 2, 3]; } Parent6.prototype.getName = function () { return this.name; } // 构造函数Child6 function Child6() { Parent6.call(this); this.friends = 'child5'; } // 使Child6 继承 Parent6 clone(Parent6, Child6); // 注意:这时先使Child6实现继承,再为Child6添加原型链,否则原型链会被修改 Child6.prototype.getFriends = function () { return this.friends; } // 实例化Child6 let person6 = new Child6(); console.log(person6); //{friends:"child5",name:"child5",play:[1,2,3],__proto__:Parent6} console.log(person6.getName()); // parent6 console.log(person6.getFriends()); // child5

    参考链接:https://www.cnblogs.com/mengxiangji/p/10399066.html 参考链接:https://mp.weixin.qq.com/s/mnQde8T6frvYautX4Ajdxg

    Processed: 0.012, SQL: 10