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 iframe中打开文件,并检测iframe存在否
Dec 28 Javascript
仅用[]()+!等符号就足以实现几乎任意Javascript代码
Mar 01 Javascript
HTML中的setCapture和releaseCapture使用介绍
Mar 21 Javascript
jquery动态改变onclick属性导致失效的问题解决方法
Dec 04 Javascript
jQuery简单实现禁用右键菜单
Mar 10 Javascript
轻松学习jQuery插件EasyUI EasyUI实现拖放商品放置购物车
Nov 30 Javascript
ionic环境配置及问题详解
Jun 27 Javascript
基于javascript 显式转换与隐式转换(详解)
Dec 15 Javascript
vue使用axios时关于this的指向问题详解
Dec 22 Javascript
vue路由懒加载的实现方法
Mar 12 Javascript
关于js陀螺仪的理解分析
Apr 11 Javascript
vue实现移动端拖动排序
Aug 21 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
dedecms系统的广告设置代码 基础版本
2010/04/09 PHP
支持中文的PHP按字符串长度分割成数组代码
2015/05/17 PHP
PHP json_encode() 函数详解及中文乱码问题
2015/11/05 PHP
thinkPHP实现签到功能的方法
2017/03/15 PHP
PHPMailer ThinkPHP实现自动发送邮件功能
2018/06/10 PHP
PHP封装的完整分页类示例
2018/08/21 PHP
php函数式编程简单示例
2019/08/08 PHP
jquery HotKeys轻松搞定键盘事件代码
2008/08/30 Javascript
jQuery 相关控件的事件操作分解
2009/08/03 Javascript
下拉列表select 由左边框移动到右边示例
2013/12/04 Javascript
弹出窗口并且此窗口带有半透明的遮罩层效果
2014/03/13 Javascript
分享一个常用的javascript静态类
2014/12/31 Javascript
浅析Ajax语法
2016/12/05 Javascript
基于vue实现swipe轮播组件实例代码
2017/05/24 Javascript
JS中精巧的自动柯里化实现方法
2017/12/12 Javascript
js计算两个日期间的天数月的实例代码
2018/09/20 Javascript
Vue表单控件绑定图文详解
2019/02/11 Javascript
微信小程序自定义导航栏实例代码
2019/04/05 Javascript
跟老齐学Python之有容乃大的list(3)
2014/09/15 Python
详解Python3.1版本带来的核心变化
2015/04/07 Python
浅谈Python 对象内存占用
2016/07/15 Python
查看django执行的sql语句及消耗时间的两种方法
2018/05/29 Python
机器学习之KNN算法原理及Python实现方法详解
2018/07/09 Python
python自动化之Ansible的安装教程
2019/06/13 Python
python经典趣味24点游戏程序设计
2019/07/26 Python
HTML5之web workers_动力节点Java学院整理
2017/07/17 HTML / CSS
开办加工厂创业计划书
2014/01/03 职场文书
酒店总经理欢迎词
2014/01/15 职场文书
搞笑征婚广告词
2014/03/17 职场文书
广场舞大赛策划方案
2014/05/31 职场文书
教师求职简历自我评价
2015/03/10 职场文书
2015年幼儿园安全工作总结
2015/05/12 职场文书
2015年安全生产管理工作总结
2015/05/25 职场文书
奶茶店的创业计划书该怎么写?
2019/07/15 职场文书
Mysql如何实现不存在则插入,存在则更新
2022/03/25 MySQL
CSS的calc函数用法小结
2022/06/25 HTML / CSS