美文网首页
JavaScript - 继承

JavaScript - 继承

作者: Hyso | 来源:发表于2019-04-03 11:07 被阅读0次

JavaScript 中继承的概念

通过某种方式,让一个对象可以访问到另一个对象中的属性和方法,我们把这种方式称之为继承(并不是所谓的 xxx extends yyy)。

为什么要使用继承

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
</style>
</style>
</head>
<body>
    <script type="text/javascript">
        function Student(name, grade) {
            this.name = name;
            this.grade = grade;
            this.say = function() {
            };
        }

        var student1 = new Student('小白', '初二');
        var student2 = new Student('小黑', '初二');

        console.log(student1.say === student2.say);
    </script>
</body>
</html>

由上述例子可见,对象 student1 和对象 student2 都有 say 方法,这两个 say 方法功能相似,但不是同一个方法(console.log(student1.say === student2.say) 的结果为false),因此会造成内存浪费(没有指向同一块内存)。

解决方案:把 say 方法写在他们共同的父对象(Student.prototype)中

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
</style>
</style>
</head>
<body>
    <script type="text/javascript">
        function Student(name, grade) {
            this.name = name;
            this.grade = grade;
        }

        Student.prototype.say = function() {
        }

        var student1 = new Student('小白', '初二');
        var student2 = new Student('小黑', '初二');

        console.log(student1.say === student2.say);
    </script>
</body>
</html>

结论:只要往某个构造函数的 prototype 对象中添加某个属性、方法,那么这样的属性、方法都可以被通过构造函数创建的实例所共享。

继承的实现方式

  • 原型链继承
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
</style>
</style>
</head>
<body>
    <script type="text/javascript">
        function Student() {
        }

        var student1 = new Student();

        Student.prototype.say = function(str) {
            console.log(str);
        }
    </script>
</body>
</html>

上述代码中,为了让 student1 有一个 say 的方法,将 say 方法放在了 Student.prototype (student1 的原型对象)中。添加1、2个方法无所谓,但是如果方法很多会导致代码冗余。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
</style>
</style>
</head>
<body>
    <script type="text/javascript">
        function Student() {
        }

        var student1 = new Student();

        Student.prototype.say1 = function() {}
        Student.prototype.say2 = function() {}
        Student.prototype.say3 = function() {}
        Student.prototype.say4 = function() {}
    </script>
</body>
</html>

上述代码中,给 student 的原型对象添加了好多方法,代码产生了不少冗余。为了减少这些冗余,可以这样写:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
</style>
</style>
</head>
<body>
    <script type="text/javascript">
        function Student() {
        }

        var student1 = new Student();

        Student.prototype = {
            say1:function() {},
            say2:function() {},
            say3:function() {},
            say4:function() {}
        }

        // undefined
        console.log(student1.say1);
    </script>
</body>
</html>

上述代码中,虽然减少了冗余,但是 console.log(student1.say1) 的属性为 undefined。原因是:student1 对象在创建的时候已经有了一个确定的原型对象(旧原型对象),而 Student.prototype 后面被重新赋值(新原型对象),但是 student1 对象的原型对象没有改变,所以 student1 对象并不能访问到新原型对象中的方法(say1、say2、say3、say4)。

解决办法:先改变原型,再创建对象

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
</style>
</style>
</head>
<body>
    <script type="text/javascript">
        function Student() {
        }

        // 改变原型
        Student.prototype = {
            say1:function() {},
            say2:function() {},
            say3:function() {},
            say4:function() {}
        }

        // 创建对象
        var student1 = new Student();

        console.log(student1.say1);
    </script>
</body>
</html>

注意:一般情况下,对于新原型对象,会添加一个 constructor 属性(属性值为对象名),从而不破坏原有的原型对象的结构

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
</style>
</style>
</head>
<body>
    <script type="text/javascript">
        function Student() {
        }

        // 改变原型
        Student.prototype = {
            constructor: Student,
            say1:function() {},
            say2:function() {},
            say3:function() {},
            say4:function() {}
        }

        // 创建对象
        var student1 = new Student();

        console.log(student1.say1);
    </script>
</body>
</html>
  • 拷贝继承

场景:有时候想使用某个对象中的属性,但是又不能直接去修改它,于是就可以创建一个该对象的拷贝,然后再修改该属性。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
</style>
</style>
</head>
<body>
    <script type="text/javascript">
        var source = {
            name: "小白",
            age: 15
        };

        // target 对象和 source 对象指向的是同一个内存地址,若改变 target 对象中属性的值,source 对象的属性值也会改变
        // var target = source;
        // console.log(target === source);

        // 创建一个该对象的拷贝
        var target = {};
        for (var key in source) {
            target[key] = source[key];
        }

        // 修改拷貝的对象
         target.name = "小黑";
    </script>
</body>
</html>

浅拷贝与深拷贝:
浅拷贝只是拷贝一层属性。若当对象中的属性内部也是一个对象时,根据自己需要,可以使用深拷贝。深拷贝其实是利用递归的原理,将对象的若干层属性都拷贝出来。

  • 三点运算符(es6)
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
</style>
</style>
</head>
<body>
    <script type="text/javascript">
        var source = {
            name: "小白",
            age: 15
        };

        var target1 = {...source};
        // {name: "小白", age: 15}
        console.log(target1);
        
        var target2 = {...source, name: "小黑"};
         // {name: "小黑", age: 15}
        console.log(target2);

        var target3 = {gender: "女", ...source, name: "小红"};
        // {gender: "女", name: "小红", age: 15}
        console.log(target3);
    </script>
</body>
</html>
  • assign() 函数(es6)
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
</head>
<body>
    <script type="text/javascript">
    var source = {name:'Tom', age: 18};

    var target1 = Object.assign({}, source);
    // {name: "Tom", age: 18}
    console.log(target1);

    var target2 = Object.assign({name:'Petter', gender: '男'}, source);
    // {name: "Tom", gender: "男", age: 18}
    console.log(target2);

    var target3 = Object.assign(source, {name:'Petter', gender: '男'});
    // {name: "Petter", age: 18, gender: "男"}
    console.log(target3);
    </script>
</body>
</html>
  • 原型式继承

场景:
1)创建一个纯洁的对象:对象中没有任何属性(constructor、hasOwnProperty、toStirng...)

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
</style>
</style>
</head>
<body>
    <script type="text/javascript">
        var target = Object.create(null);
        console.log(target);
    </script>
</body>
</html>

2)创建一个继承自某个父对象的子对象

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
</style>
</style>
</head>
<body>
    <script type="text/javascript">
        var source = {
            name: "小白",
            age: 15,
            say: function() {
                console.log("My name is"+ this.name);
            }
        };

        var target = Object.create(source);
        target.name = "小黑";
        target.say();
    </script>
</body>
</html>
  • 借用构造函数实现继承

原理:函数的 call、apply 调用方式
局限性:父类构造函数代码必须完全适用于子类。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
</style>
</style>
</head>
<body>
    <script type="text/javascript">
        function Student(name, age, gender) {
            this.name = name;
            this.age = age;
            this.gender = gender;
        }

        function Teacher(name, age, gender) {
            this.name = name;
            this.age = age;
            this.gender = gender;
        }
    </script>
</body>
</html>

在上述代码中,Student 对象和 Teacher 对象都有 name 属性、age 属性、gender 属性,代码显得冗余,可以这样优化:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
</style>
</style>
</head>
<body>
    <script type="text/javascript">
        function Student(name, age, gender) {
            this.name = name;
            this.age = age;
            this.gender = gender;
        }

        function Teacher(name, age, gender) {
            //  这种函数的调用方式,函数内部的 this 指向的是 window,相当于给 window 对象的 name属性、age属性、gender属性赋了值
            // Student(name, age, gender);

            // 先将 Student 函数内部的 this 指向 Teacher 对象的实例
            // 然后再给 Teacher 对象的实例的name属性、age属性、gender属性赋值
            // Student.call(this, name, age, gender) 等价于 Student.apply(this, [name, age, gender])
            Student.call(this, name, age, gender);

            this.salary = 6000;
        }

        var teacher1 = new Teacher("大白", 30, '男');
    </script>
</body>
</html>

相关文章

  • 前端面试题目(二)

    javascript对象的几种创建方式 javascript继承的6种方法 详情:[JavaScript继承方式详...

  • 函数的原型对象

    什么是原型? 原型是Javascript中的继承的继承,JavaScript的继承就是基于原型的继承。 函数的原型...

  • 005|JavaScript ES6新特性之Classes

    在过去,需要像 053|JavaScript 继承详解 那样实现继承。JavaScript这种继承实现方式与其它面...

  • Web前端经典面试试题及答案2

    javascript面向对象中继承实现? 面向对象的基本特征有:封闭、继承、多态。在JavaScript中实现继承...

  • 一文带你彻底理解 JavaScript 原型对象

    一、什么是原型 原型是Javascript中的继承的基础,JavaScript的继承就是基于原型的继承。 1.1 ...

  • javascript代码积累

    一、javascript实现继承 1.基于原型链实现继承 2.基于属性和方法复制实现继承 二、javascript...

  • 理解 JavaScript 中的原型链

    JavaScript 作为一门面对对象语言,但是却不支持接口继承,只支持实现继承。JavaScript 中实现继承...

  • Javascript原型和原型链

    JavaScript在ES6之前没有类似class,extend的继承机制,JavaScript的继承主要是通过原...

  • JavaScript--对象创建和继承方法

    JavaScript创建对象方法总结精彩博文javascript继承讲解精彩博文于江水 继承讲解 JavaScri...

  • JavaScript 继承

    继承是JS中非常内容,原因就是JS没有地道的继承方式,我们只能通过各种方式来模拟面向对象中的继承。下面介绍几种常见...

网友评论

      本文标题:JavaScript - 继承

      本文链接:https://www.haomeiwen.com/subject/gtaobqtx.html