博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
探究JavaScript中的继承
阅读量:7237 次
发布时间:2019-06-29

本文共 5462 字,大约阅读时间需要 18 分钟。

  在开始讲继承之前,我们首先来了解一下JavaScript中关于构造函数、原型和原型链的一些知识,因为JavaScript是中的继承和这几者之间息息相关。

一、构造函数、原型和原型链的三角关系

  首先来看一段简单的代码:

//构造函数Foofunction Foo(name) {    this.name = name;};//Foo的原型对象prototype中的方法Foo.prototype.sayHello = function() {    console.log('Hello,my name is ' + this.name + "!");};//使用Foo的constructor构造器实例化一个f1对象var f1 = new Foo('Chen');f1.sayHello();//输出:Hello,my name is Chen!//_proto_指向原型对象prototypeconsole.log(f1.__proto__ === Foo.prototype); //true复制代码

  接下来通过上面代码来讲一下构造函数、原型和原型链分别是什么,有什么作用:

  • 构造函数:在JavaScript中,用new关键字来调用定义的构造函数。默认通过constructor构造器实例化并返回的是一个新对象,这个新对象具有构造函数定义的变量/属性和方法,包括prototype,如Foo(大写);
  • 原型:每个构造函数都会有一个原型对象prototype,该对象上定义的所有属性和方法都会被实例对象所继承,如f1中继承的sayHello方法,我们将在下文中继续谈到;
  • 原型链:每个对象都会在其内部初始化一个隐式属性——_proto_,当我们访问一个对象的属性 时,如果这个对象内部不存在这个属性/方法,如f1中内部不存在sayHello,那么他就会去_proto_里找这个属性/方法。_proto_是一个指向其构造函数原型对象的指针,而原型对象也有自己的原型,通过各自的_proto_一直指向各自的原型对象,直到某个对象的原型为null,这种一级一级的链结构称为原型链。

  他们的关系可以用下面这个图简单表示:

  通过对几个概念以及他们之间的关系的认识后,相信你对"JavaScript是一门原型语言"这句话也会有一定的理解。接下来我们进入下一个重点——继承。

二、什么是JavaScript继承,而又怎么样继承呢?

继承概念

  相信许多人对继承都有自己的理解和定义,在这里我也提一下我对继承的认识:A对象能够访问B对象的属性,同时,A对象也能够添加自己的新属性、方法或者覆盖已存在的B对象的属性、方法,以上这种方式就叫做继承。

继承方式

1. 对象冒充
// 父类构造函数var Parent = function(name){    this.name = name;    this.sayHello = function(){        console.log("Hello, " + this.name + "!");    }};// 子类构造函数var Children = function(name){    this.method = Parent;    this.method(name); // 实现继承的关键    this.getName = function(){        console.log(this.name);    }};var p = new Parent("parentName");var c = new Children("childrenName");p.sayHello(); // 输出: Hello, parentName!c.sayHello(); // 输出: Hello, childrenName!c.getName(); // 输出: childrenName复制代码

分析:

  构造函数使用this关键字给所有属性和方法赋值(即采用类声明的构造函数方式)。因为构造函数只是一个函数,所以可使Parent成为Children的方法,然后调用它。Children就会收到Parent的中定义的属性和方法。那为什么不直接执行,非要转个弯把Parent赋值给Childrenmethod属性再执行呢?这跟this的指向有关,在函数内this是指向window的。当将Parent赋值给Childrenmethod时,this就指向了Children类的实例。

2. 使用call、applay、bind改变this方法
// 父类构造函数var Parent = function(name){    this.name = name;    this.sayHello = function(){        console.log("Hello, " + this.name + "!");    }};// 子类构造函数var Children = function(name){    Parent.call(this, name); // 实现继承的关键    this.getName = function(){        console.log(this.name);    }};var p = new Parent("parentName");var c = new Children("childrenName");p.sayHello(); // 输出: Hello, parentName!c.sayHello(); // 输出: Hello, childrenName!c.getName(); // 输出: childrenName复制代码

分析:

  这种方法是与对象冒充方法相似的方法,因为它也是通过callapplybind改变了this的指向而实现继承。这里使用call(),其他两个类似,具体不细讲。

3. 原型链继承
// 父类构造函数var Parent = function(){    this.name = 'parentName';    this.sayHello = function(){        console.log("Hello, " + this.name + "!");    }};// 子类构造函数var Children = function(){};Children.prototype = new Parent(); // 实现继承的关键var p = new Parent();var c = new Children();p.sayHello(); // 输出: Hello,parentName!c.sayHello(); // 输出: Hello, parentName!复制代码

分析:

  一开始我们提到,如果我们每个构造函数都有一个prototype,而这里我们将Parent作为Children的原型对象,Children通过_proto_来找到原型Parent,并调用sayHello,实现了继承。注意这里实例对象时候没有传参。

4. 混合方式
// 父类构造函数var Parent = function(name){    this.name = name;};Parent.prototype.sayHello = function(){        console.log("Hello, " + this.name + "!");};// 子类构造函数var Children = function(name,age){    Parent.call(this, name); // 实现继承的关键    this.age = age;};Children.prototype = new Parent();// 实现继承的关键Children.prototype.getAge = function(){    console.log(this.age);};var p = new Parent("parentName");var c = new Children("childrenName",18);p.sayHello(); // 输出: Hello, parentName!c.sayHello(); // 输出: Hello, childrenName!c.getAge(); // 输出: childrenName复制代码

分析:

  对象冒充的主要问题是必须使用构造函数方式,这不是最好的选择。不过如果使用原型链,就无法使用带参数的构造函数了。如何选择呢?答案很简单,两者都用。在JavaScript中创建类的最好方式是用构造函数定义属性,用原型定义方法。将两者混合在一起,就要实现将在原型链方式下传参给构造函数实例化对象。

5. 使用Object.create 方法
// 父类构造函数var Parent = function(name){    this.name = name;};Parent.prototype.sayHello = function(){        console.log("Hello, " + this.name + "!");};// 子类构造函数var Children = function(name,age){    Parent.call(this, name); // 实现继承的关键    this.age = age;};Children.prototype=Object.create(Parent.prototype);//实现继承的关键Children.prototype.constructor = Children;Children.prototype.getAge = function(){    console.log(this.age);};var p = new Parent("parentName");var c = new Children("childrenName",18);p.sayHello(); // 输出: Hello, parentName!c.sayHello(); // 输出: Hello, childrenName!c.getAge(); // 输出: childrenName复制代码

分析:

  Object.create方法会使用指定的原型对象及其属性去创建一个新的对象,当执行Children.prototype = Object.create(Parent.prototype)这个语句后,Childrenconstructor就被改变为Parent,因此需要将Children.prototype.constructor重新指定为Children自身。

6. ES6中extends关键字实现继承
// 父类class Parent {//父类构造器  constructor(name) {    this.name = name;  }}// 子构造函数class Children extends Parent {//子类构造器  constructor(name, age) {    this.age = age; // 这里会报错    super(name);//代表父类构造器    this.age = age; // 正确  }    console.log()}复制代码

分析:

  Class可以通过extends关键字实现继承,这比ES5的通过修改原型链实现继承,要清晰和方便很多。子类必须在constructor方法中调用super方法,否则新建实例时会报错。这是因为子类自己的this对象,必须先通过父类的构造函数完成塑造,得到与父类同样的实例属性和方法,然后再对其进行加工,加上子类自己的实例属性和方法。如果不调用super方法,子类就得不到this对象。
  此外,在子类的构造函数中,只有调用super之后,才可以使用this关键字,否则会报错。这是因为子类实例的构建,基于父类实例,只有super方法才能调用父类实例。
  super虽然代表了父类Parent的构造函数,但是返回的是子类Children的实例,即super内部的this指的是Children,因此super()在这里相当于Parent.prototype.constructor.call(this)

三、总结

  结合前面的内容,可以发现JavaScript 中的面向对象部分一直是在向 Java 靠拢的。尤其增加了 class 和 extends 关键字之后,靠拢了一大步。但这些并没有改变JavaScript是基于原型这一实质在JavaScript,继承所做工作实际上是在构造原型链,所有子类的实例共享的是同一个原型。所以JavaScript中调用父类的方法实际上是在不同的对象上调用同一个方法,即“方法借用”,这种行为实际上是委托调用。

转载于:https://juejin.im/post/5c316d62f265da61407f0ebc

你可能感兴趣的文章
今天开始采用的十大大数据技术
查看>>
oracle教程之oralce非归档模式下的恢复(一)
查看>>
我的友情链接
查看>>
远程桌面,身份验证错误:要求的函数不正确等解决办法
查看>>
zookeeper分布式安装
查看>>
我的友情链接
查看>>
Cnyunwei-Cacti+Nagios最新版本 V11发布
查看>>
我的友情链接
查看>>
python 文件操作方法
查看>>
我的友情链接
查看>>
EC2的AMI制作(二):安装tools和制作AMI
查看>>
主浏览器从计算机 X上收到 一个服务器宣告,认为它是传输 XXX 上的域主浏览器...
查看>>
我的友情链接
查看>>
Ubuntu 14.04安装 skype
查看>>
面向对象的拖拽
查看>>
最近的生活[发点牢骚]
查看>>
JSTL I18N 格式标签库
查看>>
canvas做的图片查看器1
查看>>
bootstrap-按钮组
查看>>
网站运营策划之新手入门
查看>>