原文地址: https://www.jeremyjone.com/745/,转载请注明。


上一篇文章已经总结了关于原型的两个属性,那么接下来所有原型和原型链,乃至后面的继承,都与这两个属性有关系。

原型和原型链

理解继承,首先要搞懂什么是原型和原型链。

理解原型和原型链

上面已经介绍了关于原型的两个属性:

  • __proto__
  • prototype

那么这里再小小总结一下,

1、什么是原型

原型即一个对象的构造器(prototype),可以通过该原型构造器创造无数实例,每一个实例都具有指向该原型的属性(__proto__)。

2、什么是原型链

在对象中通过原型,一层一层向上查找父级引用,直到没有父级(null)。整个这条引用链,即为原型链。原型链定义了对象可以继承的的属性/方法和相互之间的关系。

理解原型继承

有了这两个概念,尝试理解一下原型继承。

回到最开始的例子

让我们重新来看一开始那个数组的例子:

let arr = [];

我们尝试使用数组的方法:

arr.concat(["jeremyjone"]);

它会成功,但是我们并没有给 arr 添加 concat 的方法。它调用的是其父级 --也就是 Array 构造器-- 中的方法。

这其实就是继承。我们创建的 arr 数组实例,调用了其原型链父级的方法。

一个原型继承的例子

如果是函数呢?再来看一个例子:

function User() {}

这时候我们都知道这个 User 没有方法任何自己的方法。

万物都是从 Object 继承的。此时,User 也是从 Object 继承的。

那么现在给 Object 添加一个方法:

Object.prototype.print = function () {console.log("Hi jeremyjone, this is Object print.");
};
原型本身也可以继承

我们首先尝试让 User 调用这个 print 方法,要明确 User 是一个原型,但是当我们直接使用 User 的时候,它本身也是一个对象:

没有错,它可以被调用。这说明在原型链上,是可以找到该方法,这说明了 User 确实是从 Object 继承的。

从图中可以看到在继承的原型中有 print 方法,即有如下等式关系:

User.__proto__.__proto__ === Object.prototype; // true
原型创建的实例来继承

同样还是刚才的例子,现在我们实例化一个 user,那么 User 现在作为一个原型,被 user 实例对象继承:

// 接上例
let user = new User();

这样得到一个 user 实例。它也可以使用其原型属性和方法。

该 user 实例中,只有一个 __proto__ 属性,它指向了其原型,也就是 User.prototype,而其原型的父级正是 Object,即:

user.__proto__ === User.prototype; // true
user.__proto__.__proto__ === Object.prototype; // true

这个例子也印证了我们上面提到的 __proto__ 属性是作用于对象的。所以,实例和原型的 __proto__ 的值是不一样的:

user.__proto__ === User.__proto__; // false

同时也可以得出,最顶级的 Object 一定是一致的(Object 里面确实比较混乱,可以不用考虑),即:

user.__proto__.__proto__ === User.__proto__.__proto__; // true。它们都指向 Object.prototype

针对该例子,可以作如下理解:

原型链中的优先级

现在我们知道,对象的属性/方法都是一层一层向上查找,那么如果遇到相同的内容,它的优先级是如何的呢?

举一个例子:

function User(name) {this.name = name;
}let user = new User("jeremyjone");

现在创建了一个对象,并生成一个对象实例,它现在的样子应该是:

现在往 Object 的原型中添加几个属性:

Object.prototype.a = "a";
Object.prototype.b = "b";
Object.prototype.c = "c";

添加之后,这条原型链应该是这样的:

根据上面提过的,现在:

console.log(user.a); // "a"
console.log(user.b); // "b"
console.log(user.c); // "c"

应该是可用的。

现在分别修改值:

user.a = "aa"; // 修改 user 对象的 a 属性
User.b = "bb"; // 修改 User 模型的 b 属性
User.prototype.c = "cc"; // 修改 User 原型的 c 属性

其结果如下:

console.log(user.a); // "aa"
console.log(user.b); // "b"
console.log(user.c); // "cc"console.log(User.a); // "a"
console.log(User.b); // "bb"
console.log(User.c); // "c"

分析一下三条执行语句:

1、当执行 user.a = "aa" 语句时,user 对象中没有 a 属性,所以添加了一个值为 "aa"a 属性。这样,当读取 a 属性时,就不会从原型中查找。

2、而 User.b = "bb" 语句是给 User 模型添加一个属性,该属性没有添加在原型中,而是添加在了 constructor 中,根据上面我们讲过的内容,constructor 保存私有属性/方法,而 prototype 保存共享的属性/方法,所以 User.b 属于私有,并不被 user 所继承。

3、最后 User.prototype.c = "cc" 语句,是在 User 的原型中添加一个值为 ccc 属性,所以它可以被继承到 user,也就有了打印的结果。

现在对象 user 的原型如下:

而 User 的原型如下:

图中可以看到和分析的一致。

这与主流语言的继承相似,即当前对象属性/方法的优先级最高。隐约看到了多态的影子~

操作对象的原型关系

前面说不建议直接操作 __proto__ 属性,那么我们需要通过更规范的方式进行操作,用到两个方法:

  • Object.setPrototypeOf
  • Object.getPrototypeOf

除了名字长一些,其实还是很规范的。

Object.setPrototypeOf

该方法修改对象的原型关系,可以变更当前对象的从属关系。

let a = { a: "-a" };
let b = { b: "-b" };Object.setPrototypeOf(a, b); // 将 a 从属于 b
// 这等同于 a.__proto__ = b;

得到如下关系:

这样就改变了两个对象之间的从属关系,那么现在 a 已经继承了 b 的属性:

console.log(a.b); // -b

Object.getPrototypeOf

使用该方法可以查看一个对象的原型。

// 续上例
Object.getPrototypeOf(a) === a.__proto__; // trueconsole.log(Object.getPrototypeOf(a)); // {name: "bb", b: "-b"}

原型链的检测

instanceof 运算符

使用 instanceof 运算符,可以向上查找原型链中的从属关系。

function A() {}
function B() {}let a = new A();
let b = new B();console.log(a instanceof A); // true
console.log(a instanceof B); // false
console.log(a instanceof Object); // true

稍微改变一下:

// 接上例
A.prototype.__proto__ = B.prototype; // 修改 A 的原型关系,现在 B 是 A 的父级console.log(a instanceof A); // true
console.log(a instanceof B); // true
console.log(a instanceof Object); // true
console.log(b instanceof B); // true
console.log(b instanceof A); // false

可以看到,当 A 的原型链发生改变的时候,下面的判断也会发生改变,a 已经从属于 B 了。

甚至我们添加方法,也可以使用了:

a.show(); // 报错,因为没有方法B.prototype.show = function () {console.log("B show");
};// 此时,对象 a 可以调用 show 方法了。它在原型链中
a.show(); // B show

Object.isPrototypeOf 方法

作为与上面 instanceof 运算符的区别,使用 Object.isPrototypeOf 方式,可以明确检测一个对象是否在另一个对象的原型链上。

let a = {};
let b = {};
let c = {};Object.setPrototypeOf(a, b);console.log(a.isPrototypeOf(b)); // false
console.log(b.isPrototypeOf(a)); // true
console.log(c.isPrototypeOf(a)); // false
console.log(Object.prototype.isPrototypeOf(a)); // true
console.log(Object.isPrototypeOf(a)); // false

检测是否存在属性/方法

in 运算符检测原型链

使用 in 运算符检测原型链中是否存在属性/方法,该方法会在原型链中逐级检测。

let a = { name: "jeremyjone" };
Object.prototype.url = "jeremyjone.com";console.log("name" in a); // true
console.log("a" in a); // false
console.log("url" in a); // true
Object.hasOwnProperty 方法检测

使用 Object.hasOwnProperty 方法只检测当前对象的属性。

let a = { name: "jeremyjone" };
Object.prototype.url = "jeremyjone.com";for (const key in a) {if (a.hasOwnProperty(key)) {console.log(a[key]);}
}
// 只有一条 "jeremyjone"

真丶深入理解 JavaScript 原型和原型链(二):原型和原型链相关推荐

  1. 深入理解JavaScript系列(5):强大的原型和原型链

    前言 JavaScript 不包含传统的类继承模型,而是使用 prototypal 原型模型. 虽然这经常被当作是 JavaScript 的缺点被提及,其实基于原型的继承模型比传统的类继承还要强大.实 ...

  2. 深入理解javascript

    深入理解JavaScript系列(1):编写高质量JavaScript代码的基本要点 深入理解JavaScript系列(2):揭秘命名函数表达式 深入理解JavaScript系列(3):全面解析Mod ...

  3. 深入理解JavaScript系列——汤姆大叔

    原文地址 深入理解JavaScript系列文章,包括了原创,翻译,转载,整理等各类型文章,如果对你有用,请推荐支持一把,给大叔写作的动力. 深入理解JavaScript系列(1):编写高质量JavaS ...

  4. 汤姆大叔深入理解JavaScript系列

    深入理解JavaScript系列文章,包括了原创,翻译,转载,整理等各类型文章,如果对你有用,请推荐支持一把,给大叔写作的动力. 深入理解JavaScript系列(1):编写高质量JavaScript ...

  5. [转载]深入理解JavaScript系列 --汤姆大叔

    深入理解JavaScript系列文章,包括了原创,翻译,转载,整理等各类型文章,如果对你有用,请推荐支持一把,给大叔写作的动力. 深入理解JavaScript系列(1):编写高质量JavaScript ...

  6. 深入理解JavaScript系列(转自汤姆大叔)

    深入理解JavaScript系列(转自汤姆大叔) 深入理解JavaScript系列文章,包括了原创,翻译,转载,整理等各类型文章,如果对你有用,请推荐支持一把,给大叔写作的动力. 深入理解JavaSc ...

  7. 深入理解 JavaScript 原型

    前言 原型,作为前端开发者,或多或少都有听说.你可能一直想了解它,但是由于各种原因还没有了解,现在就跟随我来一起探索它吧.本文将由浅入深,一点一点揭开 JavaScript 原型的神秘面纱.(需要了解 ...

  8. 如何理解JavaScript原型

    Javascript的原型总会给人产生一些困惑,无论是经验丰富的专家,还是作者自己也时常表现出对这个概念某些有限的理解,我认为这样的困惑在我们一开始接触原型时就已经产生了,它们常常和new.const ...

  9. 深入理解javascript原型和闭包(16)——完结

    之前一共用15篇文章,把javascript的原型和闭包. 首先,javascript本来就"不容易学".不是说它有多难,而是学习它的人,往往都是在学会了其他语言之后,又学java ...

最新文章

  1. 你知道吗?OAuth2客户端有两种,认证方式有七种。
  2. 笔记-信息化与系统集成技术-云计算的服务形式(IaaS/PaaS/SaaS/DaaS)
  3. python用os修改文件名_python查找特定文件并修改文件名
  4. 洛谷P2759 奇怪的函数
  5. php程序员应具有的7种能力
  6. tde数据库加密_如何在TDE加密的数据库上配置SQL Server镜像
  7. 《机器学习实战》学习总结(五)K-means算法原理
  8. python爬去新浪微博_Python爬取新浪微博热搜榜
  9. 如何卸载deepfreeze(冰点还原)
  10. 港科夜闻|广州市市长郭永航先生与香港科大校董会廖长城先生一行举行座谈交流...
  11. 用dd测试SD卡的读写速度
  12. PCI DSS安全评估简介
  13. 阿里云物联网平台添加网关设备和子设备
  14. 三角函数π/2转化_高中数学:三角函数知识点
  15. springboot对接第三方微信授权(以及获取用户的头像和昵称等等)
  16. 【哈工大软件构造】学习笔记10 第十章、第十一章、第十二章
  17. 企业进销存进货销售仓储管理系统-JAVA【数据库设计、源码、开题报告】
  18. IT_开发提测标准规范
  19. 搜索引擎关键词快速排名软件_上海搜索引擎关键词排名优化费用-电话
  20. 精灵标记助手(Colabeler)超详细使用教程-最简单标记助手

热门文章

  1. Python AIML搭建聊天机器人(附遇到的问题及解决)
  2. 基于MIMO的滤波器组多载波调制技术(后期将附上MATLAB代码)
  3. Qt的基本控件——显示控件
  4. 【后端学习】后端技术要点总结【一】
  5. display:weston:weston-simple-egl
  6. python做web后端_最简易的python web框架的后端实现
  7. 网络协议分为哪几层---物理层,连接层,网络层,传输层,应用层详解
  8. Macbook电池使用问题——电池充电管理
  9. NoteExpress引用文献出现ADDIN NE.Ref.
  10. 如何应用font Awesome矢量字体图标介绍