JavaScript-继承

JavaScript-继承

一、概念

继承是面向对象中一个比较核心的概念。其他正统面向对象语言都会用两种方式实现继承:一个是接口实现,一个是继承。而ECMAScript只支持继承,不支持接口实现,而实现继承的方式依靠原型链完成。

二、原型链继承

【1】基本概念:

1、超类型:被继承的函数叫做超类型(又叫父类、基类)。
2、子类型:继承的函数叫做子类型(又叫子类、派生类)。
3、通过原型链继承,超类型实例化后的对象实例,赋值给子类型的原型属性。子类型的原型,得到的是超类型的构造+原型里的信息

<!DOCTYPE html>
<html>
<head lang="en">
    <meta charset="UTF-8">
    <title>原型链继承</title>
</head>
<body>
    <script>
        function Student(){                 //被继承的函数叫做超类型(父类、基类)
            this.name = 'pangtong';
        }
        function Score(){                   //继承的函数叫做子类型(子类、派生类)
            this.result = 100;
        }

        Score.prototype = new Student();
        var score = new Score();   
        alert(score.result);   //100
        alert(score.name);     //pangtong
    </script>
</body>
</html>
通过原型链继承,超类型实例化后的对象实例,赋值给子类型的原型属性。
子类型的原型,得到的是超类型的构造+原型里的信息。
【2】如果实例和原型中都包含相同属性,结果是什么?
<!DOCTYPE html>
<html>
<head lang="en">
    <meta charset="UTF-8">
    <title>原型链继承</title>
</head>
<body>
    <script>
        function Student(){
            this.name = 'pangtong';
        }
        Student.prototype.result = 150;
        function Score(){
            this.result = 100;
        }

        Score.prototype = new Student();
        var score = new Score();
        alert(score.result);   //100
        alert(score.name);     //pangtong
    </script> 
</body>
</html>
结论:就近原则,实例里有,就返回,没有就去原型中查找。
【3】从属关系
所有的构造函数都继承自Obejct。而继承Object是自动完成的,并不需要手动继承。
经过继承后的实例的从属关系:子类型从属于自己或者他的超类型。
<!DOCTYPE html>
<html>
<head lang="en">
    <meta charset="UTF-8">
    <title>原型链继承</title>
</head>
<body>
    <script>
        function Student(){
            this.name = 'pangtong';
        }
        Student.prototype.result = 150;
        function Score(){
            this.result = 100;
        }

        Score.prototype = new Student();
        var score = new Score();
        var student = new Student();
        alert(score.result);   //100
        alert(score.name);     //pangtong

        //从属关系
        alert(score instanceof Object);          //true
        alert(score instanceof Score);           //true
        alert(score instanceof Student);         //true
        alert(student instanceof Score);         //false
    </script>
</body>
</html>
【4】问题

继承也有之前问题,比如字面量重写原型会中断关系,使用引用类型的原型,并且子类型还无法给超类型传递参数。(引用共享和超类型无法传参的问题)

三、借用构造函数的技术

       为了解决引用共享超类型无法传参的问题,采用一种叫借用构造函数的技术,或者称为对象冒充(伪造对象、经典继承)的技术来解决这两种问题。

【1】实现原理:

在子类的构造函数中,通过apply( ) 或call( )的形式,调用父类构造函数,以实现继承。

【2】call() 方法

1、用法:call()方法调用一个函数, 其具有一个指定的this值和分别地提供的参数(参数的列表)。

语法:
fun.call(thisArg, arg1, arg2, …)
参数:
1)thisArg:在fun函数运行时指定的this值。
需要注意的是,指定的this值并不一定是该函数执行时真正的this值,如果这个函数处于非严格模式下,则指定为null和undefined的this值会自动指向全局对象(浏览器中就是window对象),同时值为原始值(数字,字符串,布尔值)的this会指向该原始值的自动包装对象。
2)arg1, arg2, …:指定的参数列表。

2、返回值
返回值是调用的方法的返回值,若该方法没有返回值,则返回undefined。

3、描述
可以让call()中的对象调用当前对象所拥有的function。可以使用call()来实现继承:写一个方法,然后让另外一个新的对象来继承它(而不是在新对象中再写一次这个方法)。

4、注意:该方法的作用和 apply() 方法类似,只有一个区别,就是call()方法接受的是若干个参数的列表,而apply()方法接受的是一个包含多个参数的数组。

【3】借用构造函数继承用法
<!DOCTYPE html>
<html>
<head lang="en">
    <meta charset="UTF-8">
    <title>借用构造函数继承</title>
</head>
<body>
    <script>
        function Student(age){
            this.name = ['pangtong','xiaotong'];  //引用类型,放在构造里就不会被共享 
            this.age = age;
        }
        function People(age){
            Student.call(this,age);               //对象冒充,给超类型传值
        }
        var student = new Student();
        var people = new People(18);
        alert(people.age);                       //18
        people.name.push('tongtong');            //添加的新的数据,只给子类型
        alert(people.name);                      //pangtong,xiaotong,tongtong
        alert(student.name);                     //pangtong,xiaotong
    </script>
</body>
</html>
【5】借用构造函数继承缺点

       借用构造函数虽然解决了引用共享超类型无法传参的问题,但没有原型,复用则无从谈起。
       这种形式的继承,每个子类实例都会拷贝一份父类构造函数中的方法,作为实例自己的方法,放在构造里,每次实例化,都会分配一个内存地址,浪费,所以最好放在原型里,保证多次实例化只有一个地址。
       方法都作为了实例自己的方法,当需求改变,要改动其中的一个方法时,之前所有的实例,他们的该方法都不能及时作出更新,只有后面的实例才能访问到新方法。

四、组合继承(原型链+构造函数)

【1】缺点:

超类型在使用过程中会被调用两次:一次是创建子类型的时候,另一次是在子类型构造函数的内部。

【2】组合继承用法:
<!DOCTYPE html>
<html>
<head lang="en">
    <meta charset="UTF-8">
    <title>组合继承</title>
</head>
<body>
    <script>
        function Student(name,age){
            this.name = name;
            this.age = age;
        }
        Student.prototype.run = function(){
            return this.name+ ' is running';
        }
        function People(name,age){
            Student.call(this,name,age);
        }
        People.prototype = new Student();
        var people = new People('pangtong',18);
        var people1 = new People('pangtong',18);
        alert(people.run());       //pangtong is running
    </script>
</body>
</html>

五、原型式继承

【1】概念:

这种继承借助原型并基于已有的对象创建新对象,同时还不必因此创建自定义类型。

【2】原型式继承用法
<!DOCTYPE html>
<html>
<head lang="en">
    <meta charset="UTF-8">
    <title>原型式继承</title>
</head>
<body>
    <script type="text/javascript">
        function obj(o){    //传递一个字面量函数       
            function F(){}  //创建一个构造函数(F构造是一个临时新建的对象,用来存储传递过来的对象)
            F.prototype=o;  //把字面量函数赋值给构造函数的原型
            return new F(); //最终返回实例化的构造函数
        }
        var a = {
            name:'pangtong',
            age:18,
            family:['gege','jiejie','didi']
        }
        var b = obj(a);
        alert(b.name);     //pangtong
        b.name = 'tongtong';
        alert(b.name);     //tongtong

        alert(b.family);   //gege,jiejie,didi
        b.family.push('meimei');
        alert(b.family);   //gege,jiejie,didi,meimei

        var c = obj(a);
        alert(c.name);     //pangtong
        alert(c.family);   //gege,jiejie,didi,meimei
        //引用类型共享了                           
    </script>
</body>
</html>
【3】缺点

引用类型的值被共享了,原型式继承一般不单独用。

六、寄生式继承

【1】概念:

把原型式+工厂模式结合而来,目的是为了封装创建对象的过程。

【2】寄生式继承用法:

function create(o) { //封装创建过程
var f= obj(o);
return f;
}
//同样也会共享引用类型

<!DOCTYPE html>
<html>
<head lang="en">
    <meta charset="UTF-8">
    <title>寄生式继承</title>
</head>
<body>
    <script type="text/javascript">
        function obj(o){
            function F(){}
            F.prototype=o;
            return new F();
        }
        function create(o){
            var f = obj(o);
            f.sayHello = function(){
                return this.name+this.age;
            }
            return f;
        }
        var a = {
            name:'pangtong',
            age:18,
            family:['gege','jiejie','didi']
        }
        var b = create(a);
        alert(b.name);
        alert(b.sayHello());
    </script>
</body>
</html>

七、寄生组合式继承

【1】产生背景:

在javascript中,使用最多的是组合继承,但是组合继承中超类型在使用过程中会被调用两次:一次是创建子类型的时候,另一次是在子类型构造函数的内部。

【2】寄生组合式继承用法:
<!DOCTYPE html>
<html>
<head lang="en">
    <meta charset="UTF-8">
    <title>寄生组合式继承</title>
</head>
<body>
    <script type="text/javascript">
        //临时中转函数
        function obj(o) {
            function F() {}
            F.prototype = o;
            return new F();
        }
        //寄生函数
        function create(box, desk) {
            var f = obj(box.prototype);
            f.constructor = desk;               //调整原型构造指针
            desk.prototype = f;
        }
        function Box(name, age) {
            this.name = name;
            this.age = age;
            this.family = ['gege','jiejie'];
        }
        Box.prototype.run = function () {
            return this.name + this.age + '运行中...'
        }
        function Desk(name, age) {
            Box.call(this, name, age);              //对象冒充
        }
        //通过寄生组合继承来实现继承
        create(Box, Desk);                          //这句话用来替代Desk.prototype = new Box();

        var desk = new Desk('tong', 100);
        alert(desk.run());          //tong100运行中
        alert(desk.constructor);    //function Desk(){}
        desk.family.push('didi');   //gege,jiejie,didi
        alert(desk.family);
        var desk1 = new Desk('pangtong',50);
        alert(desk1.run());          //pangtong50运行中
        alert(desk1.constructor);    //function Desk(){}
        alert(desk1.family);         //gege,jiejie    只共享了方法,没有共享引用
    </script>
</body>
</html>

猜你喜欢

转载自blog.csdn.net/sinat_38328891/article/details/79932325