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 相关文章推荐
Jquery Ajax学习实例6 向WebService发出请求,返回DataSet(XML) 异步调用
Mar 18 Javascript
JQuery插件开发示例代码
Nov 06 Javascript
JavaScript 开发工具webstrom使用指南
Dec 09 Javascript
jQuery使用fadein方法实现渐出效果实例
Mar 27 Javascript
jQuery实现在下拉列表选择时获取json数据的方法
Apr 16 Javascript
Boostrap入门准备之border box
May 09 Javascript
浅谈JavaScript 浏览器对象
Jun 03 Javascript
js下载文件并修改文件名
May 08 Javascript
js编写选项卡效果
May 23 Javascript
纯JS实现的读取excel文件内容功能示例【支持所有浏览器】
Jun 23 Javascript
JavaScript使用Math.random()生成简单的验证码
Jan 21 Javascript
详解vue-router的Import异步加载模块问题的解决方案
May 13 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
基于mysql的论坛(5)
2006/10/09 PHP
php float不四舍五入截取浮点型字符串方法总结
2013/10/28 PHP
discuz图片顺序混乱解决方案
2015/07/29 PHP
php使用CURL模拟GET与POST向微信接口提交及获取数据的方法
2016/09/23 PHP
php实现的pdo公共类定义与用法示例
2017/07/19 PHP
BOOM vs RR BO3 第二场2.13
2021/03/10 DOTA
灵活应用js调试技巧解决样式问题的步骤分享
2012/03/15 Javascript
jquery插件NProgress.js制作网页加载进度条
2015/06/05 Javascript
JS本地刷新返回上一页代码
2016/07/25 Javascript
jQuery EasyUI tree 使用拖拽时遇到的错误小结
2016/10/10 Javascript
关于webpack代码拆分的解析
2017/07/20 Javascript
node.js中axios使用心得总结
2017/11/29 Javascript
微信小程序实现动态设置页面标题的方法【附源码下载】
2017/11/29 Javascript
深入理解nodejs搭建静态服务器(实现命令行)
2019/02/05 NodeJs
微信小程序实现锚点功能
2019/11/20 Javascript
javascript 设计模式之享元模式原理与应用详解
2020/04/08 Javascript
使用React-Router实现前端路由鉴权的示例代码
2020/07/26 Javascript
[46:23]完美世界DOTA2联赛PWL S2 FTD vs Magma 第一场 11.20
2020/11/23 DOTA
10款最好的Web开发的 Python 框架
2015/03/18 Python
浅谈Python中的数据类型
2015/05/05 Python
详解Python中contextlib上下文管理模块的用法
2016/06/28 Python
Python Paramiko模块的安装与使用详解
2016/11/18 Python
Python生成任意范围任意精度的随机数方法
2018/04/09 Python
使用Python实现在Windows下安装Django
2018/10/17 Python
Python flask路由间传递变量实例详解
2020/06/03 Python
Python实现验证码识别
2020/06/15 Python
Python pickle模块常用方法代码实例
2020/10/10 Python
eBay法国购物网站:eBay.fr
2017/10/21 全球购物
英语教育专业自荐信
2014/05/29 职场文书
2014年护士长工作总结
2014/11/11 职场文书
2014年学校体育工作总结
2014/12/08 职场文书
因个人原因离职的辞职信范文
2015/05/12 职场文书
经济纠纷起诉状
2015/05/20 职场文书
社区结对共建协议书
2016/03/23 职场文书
python基于tkinter实现gif录屏功能
2021/05/19 Python
C3 线性化算法与 MRO之Python中的多继承
2021/10/05 Python