博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
彻底理解Javascript中的原型链与继承
阅读量:6366 次
发布时间:2019-06-23

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

JavaScript语言不像面向对象的编程语言中有类的概念,所以也就没有类之间直接的继承,JavaScript中只有对象,使用函数模拟类,基于对象之间的原型链来实现继承关系,

ES6的语法中新增了class关键字,但也只是语法糖,内部还是通过函数和原型链来对类和继承进行实现。

1 原型链

1.1 原型链定义

JavaScript对象上都有一个内部指针[[Prototype]],指向它的原型对象,而原型对象的内部指针[[Prototype]]也指向它的原型对象,直到原型对象为null,这样形成的链条就称为原型链。

这样在访问对象的属性时,会现在自己的属性中查找,如果不存在则会到上一层原型对象中查找。

注意:

根据 ECMAScript 标准,someObject.[[Prototype]] 符号是用于指派 someObject 的原型。这个等同于 JavaScript 的 proto 属性(现已弃用)。从 ECMAScript 6 开始, [[Prototype]] 可以用Object.getPrototypeOf()和Object.setPrototypeOf()访问器来访问。

例如:

var obj2 = {    height: 170}var obj3 = {    name: 'obj3'}Object.setPrototypeOf(obj3, obj2);console.log(obj3.height); // 170var isproto = Object.getPrototypeOf(obj3) === obj2;console.log(isproto); // true

1.2 不同方法创建对象与生成原型链

1.2.1 使用 Object.create 创建对象

ECMAScript 5 中引入了一个新方法:Object.create()。可以调用这个方法来创建一个新对象。新对象的原型就是调用 create 方法时传入的第一个参数。

例如:

var a = {a: 1};// a ---> Object.prototype ---> null var b = Object.create(a);// b ---> a ---> Object.prototype ---> nullconsole.log(b.a); // 1 (继承而来) var c = Object.create(b);// c ---> b ---> a ---> Object.prototype ---> null var d = Object.create(null);// d ---> nullconsole.log(d.hasOwnProperty); // undefined, 因为d没有继承Object.prototype

1.2.2 使用构造函数创建对象

在 JavaScript 中,构造函数其实就是一个普通的函数,一般函数名首字母大写。当使用 new 操作符 来作用这个函数时,它就可以被称为构造方法(构造函数)。

例如:

function Person (name, age) {    this.name = name;    this.age = age;} Person.prototype = {    sayName: function () {        console.log(this.name);    }}var person1 = new Person('yangyiliang', 23);person1.sayName(); // yangyiliang

使用构造函数创建对象,经历了如下三个关键步骤:

var temp = {}; //1  创建空对象Person.call(temp, 'yangyiliang', 23); //2  以空对象为this执行构造函数Object.setPrototypeOf(temp, Person.prototype); //3  将构造函数的prototype 设置为空对象的原型return temp;

1.2.3 使用字面量方法创建对象

使用字面量方法创建的对象,根据对象的类型,他们的原型都会指向相应JavaScript内置构造函数的prototype,和直接使用内置构造函数创建对象生成的原型链相同,例如:

var o = {a: 1}; // o这个对象继承了Object.prototype上面的所有属性// 所以可以这样使用 o.hasOwnProperty('a').// hasOwnProperty 是Object.prototype的自身属性。// Object.prototype的原型为null。// 原型链如下:// o ---> Object.prototype ---> null var a = ["yo", "whadup", "?"]; // 数组都继承于Array.prototype// (indexOf, forEach等方法都是从它继承而来).// 原型链如下:// a ---> Array.prototype ---> Object.prototype ---> null function f(){  return 2;} // 函数都继承于Function.prototype// (call, bind等方法都是从它继承而来):// f ---> Function.prototype ---> Object.prototype ---> null

2 继承

在面向对象的语言当中,继承关系应该指的是父类和子类之间的关系,子类继承父类的属性和方法,在JavaScript当中是父构造函数和子构造函数之间的关系。

类本身是对象的抽象形式,类的使用价值最后也是在于通过它能够创建对象,

所以子类能够继承父类的属性和方法的意义,就是通过子类创建出来的对象能够继承通过父类创建出来的对象的属性和方法。

clipboard.png

而这种对象之间的继承关系,就是通过原型链实现。

在1.2.2节中,我们学习到了通过构造函数创建对象的三个重要步骤,其中的一步是把构造函数的prototype对象设置为创建对象的原型。

因此我们将父类的实例对象作为子类的prototype即能够达到继承的目的,如下图所示:

继承的实现

function Person (name, age) {    this.name = name;    this.age = age} Person.prototype.sayName = function () {    console.log('my name is ' + this.name);} function Student (name, age, school) {    Person.call(this, name, age);    this.school = school;} Student.prototype = Object.create(Person.prototype); Student.prototype.saySchool = function () {    console.log('my school is ' + this.school);}

上面代码实现的继承,遵循了几个原则:

  • 1、因为构造函数创建的对象将公用同一个原型,所以将每个对象独有的属性写在构造函数中,将对象之间可以公用的方法写在构造函数的prototype中,也就是对象的原型中
  • 2、子构造函数继承父构造函数做了两个地方的工作,一是在子构造函数中利用call,调用父构造函数的方法,二是利用Object.create方法创建一个以父构造函数的prototype为原型的对象。

利用Object.create而不是直接用new 创建一个实例对象的目的是,减少一次调用父构造函数的执行。

  • 3、先通过prototype属性指向父构造函数的实例,然后再向prototype添加想要放在原型上的方法。

最后上一张js高级程序设计第三版中的一张源于原型链继承的图

clipboard.png

利用class实现继承

下面利用ES6引入的新语法糖,class、extends关键字对上述实现继承的代码进行改写:

class Person {    constructor (name, age) {        this.name = name;        this.age = age;    }     sayName () {        console.log('my name is ' + this.name);    }} class Student extends Person {    constructor (name, age, school) {        super(name, age);        this.school = school;    }     saySchool () {        console.log('my school is ' + this.school);    }}
  • class里的constructor 对应原来的构造函数
  • class里面的其他方法都是写在原来构造函数的prototype中的
  • 子类直接通过extends 关键字进行继承
  • 子类中可以通过super来调用父类中的方法

本文部分内容来自

后续

function A() {}//函数默认会有一个prototype对象并且具有constructor属性指向他本身var a = new A()a instanceof A
function A() {}function B() {}var proto = {}B.prototype = protoA.prototype = protovar a = new A()a instanceof B //truea instanceof Object //trueinstanceof  是遍历a 的原型链 寻找是否有和 B.prototype  是同一个对象的__proto__  如果找到就为true

转载地址:http://nvrma.baihongyu.com/

你可能感兴趣的文章
干货 | 分分钟教你用Python创建一个区块链
查看>>
Angular开发实践(八): 使用ng-content进行组件内容投射
查看>>
canvas+websocket+vue做一个完整的你画我猜小游戏
查看>>
android复习清单
查看>>
工作代码备用
查看>>
spring cloud互联网分布式微服务云平台规划分析--spring cloud定时调度平台
查看>>
说说如何配置 Webpack
查看>>
小程序中使用箭头函数的问题
查看>>
走进 JDK 之 Long
查看>>
Android打地鼠游戏的修改和优化
查看>>
Java异常
查看>>
map、reduce、filter、for...of、for...in等总结
查看>>
html2canvas-实现页面截图
查看>>
入门 | 从文本处理到自动驾驶:机器学习最常用的50大免费数据集
查看>>
笔记-从源码角度分析alloc与init的底层
查看>>
消除GitHub上的历史记录
查看>>
自学 JAVA 的几点建议
查看>>
第十三天-企业应用架构模式-对象-关系元数据映射模式
查看>>
k8s与HPA--通过 Prometheus adaptor 来自定义监控指标
查看>>
Python 比特币教程之二: 机器人收发比特币
查看>>