首页 > 编程语言 >一文让你搞懂javascript如何实现继承

一文让你搞懂javascript如何实现继承

时间:2023-05-11 23:56:00浏览次数:52  
标签:function 一文 age javascript Person 原型 Student 搞懂 name

一、本文想给你聊的东西包含一下几个方面:(仅限于es6之前的语法哈,因为es6里面class这关键字用上了。。)

1.原型是啥?原型链是啥?

2.继承的通用概念。

3.Javascript实现继承的方式有哪些?

 

二、原型是啥?原型链是啥?

1.原型是函数本身的prototype属性。

首先js和java不一样,js顶多算是一个基于对象的语言,而不是标准的面向对象的语言。

所以我们谈继承,只能是基于new关键字作用域构造函数的场景。

上代码:

    function Person(name,age) {
        this.name = name;
        this.age = age;
    }

    console.log(Person.prototype);

                                                                                                                                               代码 1

                                          图1

 定义一个构造函数,默认起原型就是一个Object对象,相当于一个new Object()。

而且还有一个new出来的对象有一个隐式原型属性__proto__,也指向了构造函数的原型。

也就是说: Person.prototype   === new Person().__proto__。

用图来表示就是:

                                                                                图2

 

在上图中,我把Object.prototype 叫rootObject,那么rootObject中就有了所有对象都共享的方法,如下图:

                                           图3

 如果person.toString()方法调用,那么起自身没有toString方法,就是走__proto__指向的原型对象中找,而object中也没有

所有就找到了根对象。所以构造函数原型对象存在的意义是使得该构造函数产生的对象可以共享属性和方法

所以原型对象中的属性和方法就类似于java类中定义的static属性和方法,所有对象都可以共享。

那么如上图2所示,person  ->  object  -> rootObject之间就形成了原型链

 

二、继承的通用概念

如果一个类B继承了类A,在java中这些写:class B extends A{}  

那么类B就拥有了A中的所有属性和方法。

继承是面向对象编程的一大特性,目的很简单,就是复用。

 

三、javascript中实现继承的方式有哪些?

1.原型链

假如有个构造函数Student想要继承Person函数,想拥有Person中的属性和方法,可以使用原型链来实现。

上代码

// 定义Person
function Person(name,age) {
// 保证属性有初始值
this.name = name ? name : "";
this.age = (age || age === 0) ? age : 0;
this.setName = function (name) {
this.name = name;
}
this.setAge = function (age) {
this.age = age;
}
this.getPersonInfo = function () {
return "[Person]: " + this.name + "_" + this.age;
}
}
// 定义一个所有Person对象都能共享的属性和方法
Person.prototype.typeDesc = "人类";
Person.prototype.hello = function () {
console.log("hello");
}
function Student(score) {
this.score = score;
this.setScore = function (score) {
this.score = score;
}
this.getStudentInfo = function () {
return "[Student:]: " + this.score;
}
}
// 修改Student的原型
Student.prototype = new Person();

let student1 = new Student(90);
let student2 = new Student(80);
let student3 = new Student(70);

console.log(student1.typeDesc); // 能访问

student1.setName("aa");
student1.setAge(99);
console.log(student1.getPersonInfo()); // 能访问

console.log(student1.getStudentInfo()); // 能访问

                                                                                                                                                  代码2

给你一张图吧  更清楚

                                                                                                                     图 4

 

老铁,你思考下?虽然看似student1对象能访问了能访问了Person中定义的方法和属性,但是有没有问题呢?

 本来name,age是对象的私有属性,不属于“类级别”,但是他们却出现在了Student的原型对象中,而且此时如果你

console.log(student2.name),发现其访问到了原型person对象的name属性了,是个初始的空字符串,这里为什么要在Person函数中使用初始值,

这个在工作中是很常见的,对象创建出来一般属性都是需要初始值的。

所以原型链实现继承,缺点是:原型对象中多出了一些没必要的属性。

而且由于student2和student3等其他Student的对象仍然能访问到原型对象person中的属性,这会让人产生错觉,以为他们也拥有name,age的私有属性。

于是,你接着看下面的方式。

 

2.复用构造方法

这东西严格来讲,我感觉不太像继承,但是好像用起来还挺好用,起码省事了。。。。

继续哈,上代码啊(改变一下代码2)

    // 定义Person
    function Person(name,age) {
        // 保证属性有初始值
        this.name = name ? name : "";
        this.age = (age || age === 0) ? age : 0;
        this.setName = function (name) {
            this.name = name;
        }
        this.setAge = function (age) {
            this.age = age;
        }
        this.getPersonInfo = function () {
            return "[Person]: " + this.name + "_" + this.age;
        }
    }
    // 定义一个所有Person对象都能共享的属性和方法
    Person.prototype.typeDesc = "人类";
    Person.prototype.hello = function () {
        console.log("hello");
    }
    function Student(name, age, score) {
        // 使用call调用函数,可以改变this指向,服用了父类的构造方法
        Person.call(this, name,age);
        this.score = score;
        this.setScore = function (score) {
            this.score = score;
        }
        this.getStudentInfo = function () {
            return "[Student:]: " + this.score;
        }
    }
    let student1 = new Student("aa", 99, 99);
    console.log(student1.typeDesc);  // undefined
    console.log(student1.hello);   // undefined
    console.log(student1.getStudentInfo());  // 能访问
    console.log(student1.getPersonInfo());   // 能访问

                                                                                                                          代码 3

此时虽然,虽然复用了Person构造函数,但是原型Person的原型student1无法访问到。

缺点很明显:虽然复用了Person的构造函数,但是却没有继承Person的原型

好了,我们演变一下。。

 

3.共享原型

基于上述代码3,在Student函数后面加入如下代码:

Student.prototype = Person.prototype;

                                代码 4

其实就是两个构造函数都指向同一原型。。

此时发现,student1能访问Person原型上的内容了。

还是要问一下,这样就行了吗?

问题:一旦Student向原型里面加了变量或者函数,或者修改原型中的变量内容时,哪怕是Person构造出来的对象,

同样也感知到了。。。。  这样互相影响的话,两个构造函数的原型中的变量和函数掺杂在一起,确实不合适

那怎么办呢?

来吧,看看下面的变种。

 

4.圣杯模式

说实话我也不知道为啥取名叫圣杯模式,感觉也不是官方的命名,有些人还叫其他名字。

把代码4替换成如下代码:

    // 定义空函数
    function F() {}
    // 空函数和Person共享原型
    F.prototype = Person.prototype;
    // 改变Student的原型
    Student.prototype = new F();
    // 添加原型上的构造函数
    Student.prototype.constructor = Student;

                                                                                                             代码 5

这样做Student的原型和Person的原型就不是一个对象了,而且不像原型链那样,由于new Person()作为Student.prototype导致该原型对象中包含了Person对象的私有属性。

来吧,给你个最终版本的代码,希望能帮助到你,能力有限,相互借鉴哈。。

 

5.圣杯模式+复用构造函数(算是比较完美了)

 

    // 定义Person
    function Person(name,age) {
        // 保证属性有初始值
        this.name = name ? name : "";
        this.age = (age || age === 0) ? age : 0;
        this.setName = function (name) {
            this.name = name;
        }
        this.setAge = function (age) {
            this.age = age;
        }
        this.getPersonInfo = function () {
            return "[Person]: " + this.name + "_" + this.age;
        }
    }
    // 定义一个所有Person对象都能共享的属性和方法
    Person.prototype.typeDesc = "人类";
    Person.prototype.hello = function () {
        console.log("hello");
    }
    function Student(name, age, score) {
        // 使用call调用函数,可以改变this指向,服用了父类的构造方法
        Person.call(this, name,age);
        this.score = score;
        this.setScore = function (score) {
            this.score = score;
        }
        this.getStudentInfo = function () {
            return "[Student:]: " + this.score;
        }
    }
    // 定义空函数
    function F() {}
    // 空函数和Person共享原型
    F.prototype = Person.prototype;
    // 改变Student的原型
    Student.prototype = new F();
    // 添加原型上的构造函数
    Student.prototype.constructor = Student;


    let student1 = new Student("aa", 99, 99);
    console.log(student1.typeDesc);  // 人类
    student1.hello();   // hello
    console.log(student1.getStudentInfo());  // 能访问
    console.log(student1.getPersonInfo());   // 能访问

    let student2 = new Student("bb", 33, 88);
    student2.setScore(89);
    // student2和student1都各自有自己的私有属性,并不会受影响。
    console.log(student1.getStudentInfo());
    console.log(student2.getStudentInfo());

    Student.prototype.temp = "新加属性";
    console.log(Person.prototype.temp);  // undefined

                                                                                                               代码 6

总结:可能我们在平常工作中很少这样写代码,或者用到这种继承模式,但是框架中很有可能会用到这些思想。

圣杯模式是共享原型模式的一个变种,使用空函数F来作为中间桥梁,巧妙得解决了共享原型模式的问题,同时

也解决了原型链模式的产生多余属性的问题。

 

标签:function,一文,age,javascript,Person,原型,Student,搞懂,name
From: https://www.cnblogs.com/999-ganmaoling/p/17391894.html

相关文章

  • javascript:void(0)
    javascript:void(0),仅仅表示一个死链接,当href=javascript:void(0)的空链接被点击时,页面不会有任何反应。让超链接去执行一个js函数,而不是去跳转到一个地址,而void(0)表示一个空的方法,也就是不执行js函数。javascript:是伪协议,表示url的内容通过javascript执行。void(0)计算结果......
  • JavaScript 中的 `for...in` 和 `for...of`
    JavaScript中的for...in和for...of是两种常用的迭代循环语句。它们的用途和行为是不同的。for...in循环用于循环遍历对象的属性,它会遍历对象的可枚举属性和原型链上的可枚举属性。constobj={a:1,b:2};for(letpropinobj){console.log(prop);//输出'a'......
  • java代码中fastjson生成字符串和解析字符串的方法和javascript文件中字符串和json数组
    1.java代码中fastjson生成字符串和解析字符串的方法List<TemplateFull>templateFulls=newArrayList<TemplateFull>();JSONArrayjsonArr=newJSONArray();jsonArr.addAll(templateFulls);StringjsonStr=jsonArr.toJSONString();System.out.pr......
  • JavaScript 面向对象编程
    面向对象编程ObjectOrientedProgramming面向对象编程用对象把数据和方法聚合起来。面向对象编程的优点能写出模块化的代码能使得代码更灵活能提高代码的可重用性面向对象编程的原则继承(inheritance):子类/派生类从父类/基类/超类中派生,形成继承结构封装(encapsulati......
  • IE 6下 a javascript:void(0)问题
     在购物页面的“立即购买”按钮是用a标签来做的,设置了href="javascript:void(0);",同时在a上面添加了onclick事件,目的是在点击之后处理Cookies并跳转到订单页面。反复测试发现ie6下没有跳转,ff下是正常的。在网上搜到的结果是ie6的bug导致这样的结果。 摘自网络产生这样的结果是......
  • javascript基础练习
     本练习根据w3cschool:https://www.w3cschool.cn/javascript/javascript-conventions.html 1.javascript简介1.1JavaScript是脚本语言JavaScript是一种轻量级的编程语言。JavaScript是可插入HTML页面的编程代码。JavaScript插入HTML页面后,可由所有的现......
  • JavaScript 和浏览器
    模块参见ES6部分DOM参见Web相关部分选择器document.querySelector()来获取网页的对应HTML元素document.querySelectorAll()来获取网页的所有对应HTML元素document.getElementById()根据ID获取元素document.getElementsByClassName()根据类名获取元素docum......
  • 学习JavaScript数据结构与算法 第八章
    八,字典和散列表8.3ES2015Map类ECMAScript2015新增了Map类。constmap=newMap();map.set('Gandalf','[email protected]');map.set('John','[email protected]');map.set('Tyrion','[email protected]');......
  • 学习JavaScript数据结构与算法 第七章
    7.集合7.4ESMAScript2015---Set类ECMAScript2015新增了Set类作为JavaScriptAPI的一部分。我们可以基于ES2015的Set开发我们的Set类。constset=newSet()set.add(1)console.log(set.values())//@iteratorconsole.log(set.has(1))console.log(set......
  • javascript函数声明和函数表达式
    JavaScript中定义函数最常用的方式是函数声明和函数表达式。这两种技术非常相似,有时甚至难以区分,但在后续章节中可以看到,它们之间还是存在着微妙的区别。JavaScript定义函数最基本方式是函数声明,如下图:正如你所见,每个函数声明以强制性的function开头,其后紧接着强制性的函数名,以及......