JavaScript面向对象—继承的实现( 二 )

  • 当父类中的属性为引用类型时,子类的多个实例对象会共用这个引用类型,如果进行修改,会相互影响;
  • 在使用该方案实现继承时,属性都是写死的,不支持动态传入参数来定制化属性值;
  • 3.2.方案二:借用构造函数实现继承
    针对方案一的缺点,可以借用构造函数来进行优化 。
    • 在子类中通过call调用父类,这样在实例化子类时,每个实例就可以创建自己单独属性了;
    // 定义Person父类公共的属性function Person(name, age) {this.name = namethis.age = age}// 定义Person父类的公共方法Person.prototype.say = function() {console.log('I am ' + this.name)}// 定义Student子类特有的属性function Student(name, age, sno) {// 通过call调用Person父类,创建自己的name和age属性Person.call(this, name, age)this.sno = sno}// 实现继承的核心:将父类的实例化对象赋值给子类的原型Student.prototype = new Person()// 定义Student子类特有的方法Student.prototype.studying = function() {console.log(this.name + ' studying')}// 实例化Studentconst stu1 = new Student('curry', 30, 101111)const stu2 = new Student('kobe', 24, 101112)console.log(stu1) // Person { name: 'curry', age: 30, sno: 101111 }console.log(stu2) // Person { name: 'kobe', age: 24, sno: 101112 }内存表现:
    JavaScript面向对象—继承的实现

    文章插图
     
    缺点:
    • 在实现继承的过程中,Person构造函数被调用了两次,一次在new Person(),一次在Person.call();
    • 在Person的实例化对象上,也就是stu1和stu2的原型上,多出来了没有使用的属性name和age;
    3.3.方案三:寄生组合式继承
    通过上面两种方案,我们想实现继承的目的是重复利用另外一个对象的属性和方法,如果想解决方案二中的缺点,那么就要减少Person的调用次数,避免去执行new Person(),而解决的办法就是可以新增一个对象,让该对象的原型指向Person的原型即可 。
    (1)对象的原型式继承
    将对象的原型指向构造函数的原型的过程就叫做对象的原型式继承,主要可以通过以下三种方式实现:
    • 封装一个函数,将传入的对象赋值给构造函数的原型,最后将构造函数的实例化对象返回;
    • function createObj(o) { // 定义一个Fn构造函数 function Fn() {} // 将传入的对象赋值给Fn的原型 Fn.prototype = o // 返回Fn的实例化对象 return new Fn() } const protoObj = { name: 'curry', age: 30 } const obj = createObj(protoObj) // 得到的obj对象的原型已经指向了protoObj console.log(obj.name) // curry console.log(obj.age) // 30 console.log(obj.__proto__ === protoObj) // true
    • 改变上面方法中的函数体实现,使用Object.setPrototypeOf()方法来实现,该方法设置一个指定的对象的原型到另一个对象或null;
    • function createObj(o) { // 定义一个空对象 const newObj = {} // 将传入的对象赋值给该空对象的原型 Object.setPrototypeOf(newObj, o) // 返回该空对象 return newObj }
    • 直接使用Object.create()方法,该方法可以创建一个新对象,使用现有的对象来提供新创建的对象的__proto__;
    • const protoObj = { name: 'curry', age: 30 } const obj = Object.create(protoObj) console.log(obj.name) // curry console.log(obj.age) // 30 console.log(obj.__proto__ === protoObj) // true
    (2)寄生组合式继承的实现
    寄生式继承就是将对象的原型式继承和工厂模式进行结合,即封装一个函数来实现继承的过程 。而这样结合起来实现的继承,又可以称之为寄生组合式继承 。下面就看看具体的实现过程吧 。
    • 创建一个原型指向Person父类的对象,将其赋值给Student子类的原型;
    • 在上面的实现方案中,Student子类的实例对象的类型都是Person,可以通过重新定义constructor来优化;
    // 定义Person父类公共的属性function Person(name, age) {this.name = namethis.age = age}// 定义Person父类的公共方法Person.prototype.say = function() {console.log('I am ' + this.name)}// 定义Student子类特有的属性function Student(name, age, sno) {// 通过call调用Person父类,创建自己的name和age属性Person.call(this, name, age)this.sno = sno}// 调用Object.create方法生成一个原型指向Person原型的对象,并将这个对象赋值给Student的原型Student.prototype = Object.create(Person.prototype)// 定义Student原型上constructor的值为StudentObject.defineProperty(Student.prototype, 'constructor', {configurable: true,enumerable: false,writable: true,value: Student})// 定义Student子类特有的方法Student.prototype.studying = function() {console.log(this.name + ' studying')}// 实例化Studentconst stu1 = new Student('curry', 30, 101111)const stu2 = new Student('kobe', 24, 101112)console.log(stu1) // Student { name: 'curry', age: 30, sno: 101111 }console.log(stu2) // Student { name: 'kobe', age: 24, sno: 101112 }


    推荐阅读