资讯专栏INFORMATION COLUMN

一起学习面向对象——继承

MycLambert / 2787人阅读

摘要:缺陷在子类构造函数中执行了一遍父类构造函数,在实现子类原型的类式继承时又调用了一遍父类构造函数,因此调用了两遍构造函数。

类式继承 原理

类的原型对象的作用就是为类的原型添加公有属性和公有方法,但类不能直接访问这些属性和方法,必须通过原型prototype来访问。而我们实例化一个父类的时候,新创建的对象复制了父类的构造函数内的属性与方法,并且将原型__proto__指向了父类的原型对象,这样就拥有了父类原型对象上的属性和方法,并且这个新创建的对象可直接访问到父类原型对象上的属性与方法,同样也可以访问从父类构造函数中复制的属性和方法。

var Parent = function() {
    this.member = ["father", "mother"];
};

var Child = function() {};
Child.prototype = new Parent();

// test:
var child1 = new Child();
var child2 = new Child();

console.log(child1.member);     // ["father", "mother"];
console.log(child2.member);        // ["father", "mother"];

child1.member.push("uncle");

console.log(child1.member);        // ["father", "mother", "uncle"];
console.log(child2.member);        // ["father", "mother", "uncle"];
缺陷

1、由于子类通过其原型prototype对父类实例化,继承了父类,所以说父类中的公有属性要是引用类型,就会在子类中被所有实例共用,因此一个子类的实例更改子类原型从父类构造函数中继承出来的公有属性就会影响到其他子类。

2、由于子类实现的继承是靠其原型prototype对父类的实例化实现,因此在创建父类的时候,是无法向父类传递参数的,因而实例化父类的时候也无法对父类构造函数内的属性进行初始化。

构造函数(窃取)继承 原理

由于call方法可以更改函数的作用域,因此在子类中,对父类调用这个方法就是将子类中的变量在父类中执行一遍,
由于父类中是给this绑定属性的,因此子类自然就继承了父类中的公有属性。

var Parent = function() {
    this.member = ["father", "mother"];
    this.speak = function() {
        console.log("Chinese!");
    }
};
Parent.prototype = {
    constructor: Parent,
    say: function() {
        console.log("Hi!");
    }
};

var Child = function() {
    Parent.call(this);
};

// test:
var child1 = new Child();
var child2 = new Child();

console.log(child1.member);     // ["father", "mother"];
console.log(child2.member);        // ["father", "mother"];


console.log(child1.speak());    // Chinese!
console.log(child1.say());        // Uncaught TypeError: child1.say is not a function


child1.member.push("uncle");

console.log(child1.member);        // ["father", "mother", "uncle"];
console.log(child2.member);        // ["father", "mother"];
缺陷

由于这种类型的继承没有涉及原型prototype,所以父类的原型方法自然不会被子类继承,而如果要想被子类继承就必须要放在构造函数中,这样创建出来的每个实例都会多带带拥有一份而不能共用,这样就违背了代码复用的原则.

组合继承 原理

在子类构造函数中执行父类构造函数,在子类原型上实例化父类,融合了类式继承和构造函数继承两者的优点。并过滤了其缺点。

var Parent = function() {
    this.member = ["father", "mother"];
    this.speak = function() {
        console.log("Chinese!");
    }
};
Parent.prototype = {
    constructor: Parent,
    say: function() {
        console.log("Hi!");
    }
};

var Child = function() {
    Parent.call(this);
};
Child.prototype = new Parent();

// test:
var child1 = new Child();
var child2 = new Child();

console.log(child1.member);     // ["father", "mother"];
console.log(child2.member);        // ["father", "mother"];


console.log(child1.speak());    // Chinese!
console.log(child1.say());        // Hi!


child1.member.push("uncle");

console.log(child1.member);        // ["father", "mother", "uncle"];
console.log(child2.member);        // ["father", "mother"];
缺陷

在子类构造函数中执行了一遍父类构造函数,在实现子类原型的类式继承时又调用了一遍父类构造函数,因此调用了两遍构造函数。

原型式继承 原理

对类式继承的一个封装

// 声明一个过渡对象继承父对象, 并返回过渡对象的实例
function inheritObject(o) {
    function F() {};
    F.prototype = o;
    return new F();
};

var book = {
    name: "web",
    type: ["html", "css"]
};

// test:
var html5Book = inheritObject(book);
html5Book.name = "html5Book";
html5Book.type.push("html5");

var jsBook = inheritObject(book);
jsBook.name = "jsBook";
jsBook.type.push("js");

console.log(html5Book.name);
console.log(html5Book.type);    // ["html", "css", "html5", "js"];

console.log(jsBook.name);
console.log(jsBook.type);        // ["html", "css", "html5", "js"];
缺陷

与类式继承一样, 父类对象中的值类型被复制, 引用类型的属性被共用.

寄生式继承 原理

对原型继承的第二次封装, 并且在第二次封装过程中对继承的对象进行扩展,
这样新创建的对象不仅仅有父类中的属性和方法, 而且还添加新的属性和方法.
寄生式继承依托于原型继承模式同时也是为了寄生组合式继承模式的实现。

function inheritObject(o) {
    // 声明一个过渡对象继承父对象, 并返回过渡对象的实例
    function F() {};
    F.prototype = o;
    return new F();
}

var book = {
    name: "web",
    type: ["html", "css"]
};

function createBook(obj) {
    var o = new inheritObject(obj);
    o.getName = function() {
        console.log("webBook");
    };
    return o;
}

var newBook = createBook(book);
console.log(newBook.name);            // web
console.log(newBook.type);            // ["html", "css"]
console.log( newBook.getName() );    // webBook
寄生组合式继承 原理

对子类赋予父类原型的一个引用.即需要父类的原型对象的一个副本, 而这副本可以通过原型继承得到,
但因为这样直接赋值给子类会造成父类原型对象复制得到的复制对象p中的constructor指向不是子类对象,
因此需要对复制对象p做一次增强, 修复其constructor属性指向不正确的问题, 最后将得到的复制对象p赋值给子类的原型, 这样子类的原型就继承了父类的原型并且没有执行父类的构造函数.

function inheritObject(o) {
    // 声明一个过渡对象继承父对象, 并返回过渡对象的实例
    function F() {};
    F.prototype = o;
    return new F();
}

function inheritPrototype(subClass, superClass) {
    // 复制一份父类的原型副本保存在变量中
    var p = inheritObject(superClass.prototype);
    // 修正因为重写子类原型导致子类的constructor属性被修改
    p.constructor = subClass;
    // 设置子类原型
    subClass.prototype = p;
}

var Parent = function(language) {
    this.language = language;
    this.member = ["father", "mother"];
    this.speak = function() {
        console.log(this.language);
    }
};
Parent.prototype = {
    constructor: Parent,
    say: function() {
        console.log("Hi!");
    }
};

var Child = function(language, name) {
    Parent.call(this, language);
    this.name = name;
};

inheritPrototype(Child, Parent);

// test:
var child1 = new Child("English", "xiaoming");
var child2 = new Child("japanese", "xiaoli");

child1.member.push("uncle");

console.log( child1.speak() );        // English
console.log( child1.say() );        // Hi!
console.log( child1.member );        // ["father", "mother", "uncle"]

console.log( child2.speak() );        // English
console.log( child2.say() );        // Hi!
console.log( child2.member );        // ["father", "mother"]

Child.prototype.getName = function() {
    console.log("child~");
};

var child3 = new Child();
console.log( child3.getName() );    // child~
console.log( child3.say() );        // Hi~

Child.prototype = {
    getMember: function() {
        console.log(this.member);
    }
};

var child4 = new Child();
console.log( child4.getMember() );    // ["father", "mother"]
console.log( child4.say() );        // Uncaught TypeError: child3.say is not a function
缺陷

子类再想添加方法必须通过prototype.对象, 通过点语法的形式一个一个添加方法, 否则直接赋予对象就会覆盖从父类原型继承的对象.

单继承

单继承 属性复制

var extend = function(target, source) {
    // 遍历源对象的属性
    for(var property in source) {
        // 将源对象中的属性复制到目标对象中
        target[property] = source[property];
    }
    //返回目标对象
    return target;
};
多继承

多继承 属性复制

var mix = function() {
    var i = 1,                        // 从第二个参数起为被继承的对象
        len = arguments.length,        // 获取参数长度
        target = arguments[0],        // 第一个对象为目标对象
        arg;                        // 缓存参数对象
    for(; i < len; i++) {
        // 缓存当前对象
        arg = arguments[i];
        // 遍历被继承对象中的属性
        for(var property in arg) {
        // 将被继承对象中的属性复制到目标对象中
            target[property] = arg[property];
        }
    }
    // 返回目标对象
    return target;
};

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/87880.html

相关文章

  • 重新认识JavaScript面向对象: 从ES5到ES6

    摘要:基于原型的面向对象在基于原型的语言中如并不存在这种区别它只有对象不论是构造函数,实例,原型本身都是对象。允许动态地向单个的对象或者整个对象集中添加或移除属性。为了解决以上两个问题,提供了构造函数创建对象的方式。 showImg(https://segmentfault.com/img/remote/1460000013229218); 一. 重新认识面向对象 1. JavaScript...

    VishKozus 评论0 收藏0
  • 重新认识JavaScript面向对象: 从ES5到ES6

    摘要:基于原型的面向对象在基于原型的语言中如并不存在这种区别它只有对象不论是构造函数,实例,原型本身都是对象。允许动态地向单个的对象或者整个对象集中添加或移除属性。为了解决以上两个问题,提供了构造函数创建对象的方式。 showImg(https://segmentfault.com/img/remote/1460000013229218); 一. 重新认识面向对象 1. JavaScript...

    用户83 评论0 收藏0
  • 【译】每个JavaScript 开发者应该了解的10个面试题

    摘要:避免脆弱的基类问题。红牌警告没有提到上述任何问题。单向数据流意味着模型是单一的事实来源。单向数据流是确定性的,而双向绑定可能导致更难以遵循和理解的副作用。原文地址 1. 你能说出两种对 JavaScript 应用开发者而言的编程范式吗? 希望听到: 2. 什么是函数编程? 希望听到: 3. 类继承和原型继承的不同? 希望听到 4. 函数式编程和面向对象编程的优缺点? ...

    mykurisu 评论0 收藏0
  • 谈谈我所理解的面向对象

    摘要:众多面向对象的编程思想虽不尽一致,但是无论哪种面向对象编程语言都具有以下的共通功能。原型编程以类为中心的传统面向对象编程,是以类为基础生成新对象。而原型模式的面向对象编程语言没有类这样一个概念。 什么是面向对象?这个问题往往会问到刚毕业的新手or实习生上,也是往往作为一个技术面试的开头题。在这里我们不去谈如何答(fu)好(yan)问(guo)题(qu),仅谈谈我所理解的面向对象。 从历...

    avwu 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<