JS原形与原型链深入详解


Posted in Javascript onMay 09, 2020

本文实例讲述了JS原形与原型链。分享给大家供大家参考,具体如下:

前言

在JS中,我们经常会遇到原型。字面上的意思会让我们认为,是某个对象的原型,可用来继承。但是其实这样的理解是片面的,下面通过本文来了解原型与原型链的细节,再顺便谈谈继承的几种方式。

原型

在讲到原型之前,我们先来回顾一下JS中的对象。在JS中,万物皆对象,就像字符串、数值、布尔、数组等。ECMA-262把对象定义为:无序属性的集合,其属性可包含基本值、对象或函数。对象是拥有属性和方法的数据,为了描述这些事物,便有了原型的概念。

无论何时,只要创建了一个新函数,就会根据一组特定的规则为该函数创建一个prototype属性,这个属性指向该函数的原型对象。所有原型对象都会获得一个constructor属性,这个属性包含一个指向prototype属性所在函数的指针。

这段话摘自《JS高级程序设计》,很好理解,以创建实例的代码为例。

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

const person1 = new Person("gali", 18);
const person2 = new Person("pig", 20);

JS原形与原型链深入详解

上面例子中的person1跟person2都是构造函数Person()的实例,Person.prototype指向了Person函数的原型对象,而Person.prototype.constructor又指向Person。Person的每一个实例,都含有一个内部属性__proto__,指向Person.prototype,就像上图所示,因此就有下面的关系。

console.log(Person.prototype.constructor === Person); // true
console.log(person1.__proto__ === Person.prototype); // true
console.log(person2.__proto__ === Person.prototype); // true

继承

JS是基于原型的语言,跟基于类的面向对象语言有所不同,JS中并没有类这个概念,有的是原型对象这个概念,原型对象作为一个模板,新对象可从原型对象中获得属性。那么JS具体是怎样继承的呢?

在讲到继承这个话题之前,我们先来理解原型链这个概念。

原型链

构造函数,原型和实例的关系已经很清楚了。每个构造函数都有一个原型对象,原型对象都包含一个指向构造函数的指针,而实例对象都包含一个指向与原型对象的指针。这样的关系非常好理解,但是如果我们想让原型对象等于另一个类型的实例对象呢?那么就会衍生出相同的关系,此时的原型对象就会含有一个指向另一个原型对象的指针,而另一个原型对象会含有一个指向另一个构造函数的指针。如果另一个原型对象又是另一个类型的实例对象呢?这样就构成了原型链。文字可能有点难理解,下面用代码举例。

function SuperType() {
  this.name = "张三";
}
SuperType.prototype.getSuperName = function() {
  return this.name;
};

function SubType() {
  this.subname = "李四";
}
SubType.prototype = new SuperType();
SubType.prototype.getSubName = function() {
  return this.subname;
};

const instance = new SubType();
console.log(instance.getSuperName()); // 张三

上述例子中,SubType的原型对象作为SuperType构造函数的实例对象,此时,SubType的原型对象就会有一个__proto__属性指向SuperType的原型对象,instance作为SubType的实例对象,必然能共享SubType的原型对象的属性,又因为SubType的原型对象又指向SuperType原型对象的属性,因此可得,instance继承了SuperType原型的所有属性。

我们都知道,所有函数的默认原型都是Object的实例,所以也能得出,SuperType的默认原型必然有一个__proto__指向Object.prototype。

图中由__proto__属性组成的链子,就是原型链,原型链的终点就是null

JS原形与原型链深入详解

上图可很清晰的看出原型链的结构,这不禁让我想到JS的一个运算符instanceof,instanceof可用来判断一个实例对象是否属于一个构造函数。

A instanceof B; // true

实现原理其实就是在A的原型链上寻找是否有原型等于B.prototype,如果一直找到A原型链的顶端null,仍然找不到原型等于B.prototype,那么就可返回false。下面手写一个instanceof,这个也是很多大厂常用的手写面试题。

function Instance(left, right) {
  left = left.__proto__;
  right = right.prototype;
  while (true) {
    if (left === null) return false;
    if (left === right) return true;
    // 继续在left的原型链向上找
    left = left.__propo__;
  }
}
原型链继承

上面例子中,instance继承了SuperType原型的属性,其继承的原理其实就是通过原型链实现的。原型链很强大,可用来实现继承。可是单纯的原型链继承也是有问题存在的。

  • 实例属性变成原型属性,影响其他实例
  • 创建子类型的实例时,不能向超类型的构造函数传递参数
function SuperType() {
  this.colorArr = ["red", "blue", "green"];
}
function SubType() {}
SubType.prototype = new SuperType();

const instance1 = new SubType();
instance1.colorArr.push("black");
console.log(instance1.colorArr); // ["red", "blue", "green", "black"]

const instance2 = new SubType();
console.log(instance2.colorArr); // ["red", "blue", "green", "black"]

当SubType的原型作为SuperType的实例时,此时SubType的实例对象通过原型链继承到colorArr属性,当修改了其中一个实例对象从原型链中继承到的原型属性时,便会影响到其他实例。对instance1.colorArr的修改,在instance2.colorArr便能体现出来。

组合继承

组合继承指的是组合原型链和构造函数的技术,通过原型链实现对原型属性和方法的继承,而通过借用构造函数实现对实例属性的继承。

function SuperType(name) {
  this.name = name;
  this.colors = ["red", "blue", "green"];
}
SuperType.prototype.sayName = function() {
  console.log(this.name);
};

function SubType(name, age) {
  // 继承属性,借用构造函数实现对实例属性的继承
  SuperType.call(this, name);
  this.age = age;
}

// 继承原型属性及方法
SubType.prototype = new SuperType();
SubType.prototype.constructor = SubType;
SubType.prototype.sayAge = function() {
  console.log(this.age);
};

const instance1 = new SubType("gali", 18);
instance1.colors.push("black");
console.log(instance1.colors); // ["red", "blue", "green", "black"]
instance1.sayName(); // gali
instance1.sayAge(); // 18

const instance2 = new SubType("pig", 20);
console.log(instance2.colors); // ["red", "blue", "green"]
instance2.sayName(); // pig
instance2.sayAge(); // 20

上述例子中,借用构造函数继承实例属性,通过原型继承原型属性与方法。这样就可让不同的实例分别拥有自己的属性,又可共享相同的方法。而不会像原型继承那样,对实例属性的修改影响到了其他实例。组合继承是JS最常用的继承方式。

寄生组合式继承

虽然说组合继承是最常用的继承方式,但是有没有发现,就上面的例子中,组合继承中调用了2次SuperType函数。回忆一下,在第一次调用SubType时。

SubType.prototype = new SuperType();

这里调用完之后,SubType.prototype会从SuperType继承到2个属性:name和colors。这2个属性存在SubType的原型中。而在第二次调用时,就是在创造实例对象时,调用了SubType构造函数,也就会再调用一次SuperType构造函数。

SuperType.call(this, name);

第二次调用之后,便会在新的实例对象上创建了实例属性:name和colors。也就是说,这个时候,实例对象跟原型对象拥有2个同名属性。这样实在是浪费,效率又低。

为了解决这个问题,引入了寄生组合继承方式。重点就在于,不需要为了定义SubType的原型而去调用SuperType构造函数,此时只需要SuperType原型的一个副本,并将其赋值给SubType的原型即可。

function InheritPrototype(subType, superType) {
  // 创建超类型原型的一个副本
  const prototype = Object(superType.prototype);
  // 添加constructor属性,因为重写原型会失去constructor属性
  prototype.constructor = subType;
  subType.prototype = prototype;
}

将组合继承中的:

SubType.prototype = new SuperType();
SubType.prototype.constructor = SubType;

替换成:

InheritPrototype(SubType, SuperType);

寄生组合继承的优点在于,只需要调用一次SuperType构造函数。避免了在SubType的原型上创建多余的不必要的属性。

总结

温故而知新,再次看回《JS高级程序设计》这本书的原型与原型链部分,发现很多以前忽略掉的知识点。而这次回看这个知识点,并输出了一篇文章,对我来说受益匪浅。写文章往往不是为了写出怎样的文章,其实中间学习的过程才是最享受的。

感兴趣的朋友可以使用在线HTML/CSS/JavaScript代码运行工具:http://tools.3water.com/code/HtmlJsRun测试上述代码运行效果。

希望本文所述对大家JavaScript程序设计有所帮助。

Javascript 相关文章推荐
Javascript 通过json自动生成Dom的代码
Apr 01 Javascript
jQuery.get、jQuery.getJSON、jQuery.post无法返回JSON问题的解决方法
Jul 28 Javascript
简单的两种Extjs formpanel加载数据的方式
Nov 09 Javascript
浅谈JSON和JSONP区别及jQuery的ajax jsonp的使用
Nov 23 Javascript
JavaScript中的逻辑判断符&&、||与!介绍
Dec 31 Javascript
javascript中sort()的用法实例分析
Jan 30 Javascript
JavaScript实现的encode64加密算法实例分析
Apr 15 Javascript
Vue.js项目模板搭建图文教程
Sep 20 Javascript
浅谈gulp创建完整的项目流程
Dec 20 Javascript
详解如何在你的Vue项目配置vux
Jun 04 Javascript
layui操作列按钮个数和文字颜色的判断实例
Sep 11 Javascript
解决echarts数据二次渲染不成功的问题
Jul 20 Javascript
JavaScript中的this妙用实例分析
May 09 #Javascript
JavaScript中继承原理与用法实例入门
May 09 #Javascript
jQuery三组基本动画与自定义动画操作实例总结
May 09 #jQuery
JavaScript进阶(四)原型与原型链用法实例分析
May 09 #Javascript
JavaScript进阶(三)闭包原理与用法详解
May 09 #Javascript
JavaScript进阶(二)词法作用域与作用域链实例分析
May 09 #Javascript
JavaScript进阶(一)变量声明提升实例分析
May 09 #Javascript
You might like
PHP脚本的10个技巧(2)
2006/10/09 PHP
PHP中文件读、写、删的操作(PHP中对文件和目录操作)
2012/03/06 PHP
php 备份数据库代码(生成word,excel,json,xml,sql)
2013/06/23 PHP
Yii框架获取当前controlle和action对应id的方法
2014/12/03 PHP
PHP实现的链式队列结构示例
2017/09/15 PHP
CentOS7编译安装php7.1的教程详解
2019/04/18 PHP
TP3.2.3框架文件上传操作实例详解
2020/01/23 PHP
js 弹出菜单/窗口效果
2011/10/30 Javascript
jquery队列queue与原生模仿其实现方法分享
2014/03/25 Javascript
JQuery中层次选择器用法实例详解
2015/05/18 Javascript
关于JS中prototype的理解
2015/09/07 Javascript
jQuery easyui的validatebox校验规则扩展及easyui校验框validatebox用法
2016/01/18 Javascript
JavaScript统计字符串中每个字符出现次数完整实例
2016/01/28 Javascript
jquery.zclip轻量级复制失效问题
2017/01/08 Javascript
Bootstrap模态框使用详解
2017/02/15 Javascript
JavaScript之面向对象_动力节点Java学院整理
2017/06/29 Javascript
vue服务端渲染的实例代码
2017/08/28 Javascript
JS库之Three.js 简易入门教程(详解之一)
2017/09/13 Javascript
详解vue渲染函数render的使用
2017/12/12 Javascript
create-react-app构建项目慢的解决方法
2018/03/14 Javascript
js构建二叉树进行数值数组的去重与优化详解
2018/03/26 Javascript
vue页面加载闪烁问题的解决方法
2018/03/28 Javascript
在vue中使用Autoprefixed的方法
2018/07/27 Javascript
vue+iview/elementUi实现城市多选
2019/03/28 Javascript
详解Vue.js和layui日期控件冲突问题解决办法
2019/07/25 Javascript
纯Python开发的nosql数据库CodernityDB介绍和使用实例
2014/10/23 Python
python自动化报告的输出用例详解
2018/05/30 Python
python对Excel的读取的示例代码
2020/02/14 Python
在python3中使用shuffle函数要注意的地方
2020/02/28 Python
使用canvas生成含有微信头像的邀请海报没有微信头像问题
2019/10/29 HTML / CSS
荷兰多品牌网上鞋店:Stoute Schoenen
2017/08/24 全球购物
英国家喻户晓的高街品牌:River Island
2017/11/28 全球购物
2014审计局领导班子民主生活会对照检查材料思想汇报
2014/09/20 职场文书
教师信息技术学习心得体会
2016/01/21 职场文书
python设置 matplotlib 正确显示中文的四种方式
2021/05/10 Python
Python如何解决secure_filename对中文不支持问题
2021/07/16 Python