前端开发系列018-基础篇之JavaScript原型链
本文旨在花很少的篇幅讲清楚JavaScript语言中的原型链结构,很多朋友认为JavaScript中的原型链复杂难懂,其实不然,它们就像树上的一串猴子。
一、理解原型链
JavaScript中几乎所有的东西都是对象,我们说数组是对象、DOM节点是对象、函数等也是对象,创建对象的Object也是对象(本身是构造函数),那么有一个重要的问题:对象从哪里来?
这是一句废话,对象当然是通过一定方式创建出来的,根据实际类型不同,对象的创建方式也千差万别。比如函数,我们可以声明函数、使用Function构造函数创建等,比如数组,我们可以直接通过var arr = [] 的方式创建空数组,也可以通过new Array的方式创建,比如普通的对象,我们可以字面量创建、使用内置构造函数创建等等,花样太多了,以至于我们学习的时候头昏脑涨、不得要领。
其实,归根结底所有“类型”的对象都可以认为是由相应构造函数创建出来的。 函数由Function构造函数实例化而来,普通对象由Object构造函数实例化而来,数组对象由Array构造函数实例化而来,至于Object | Array | Function等他们本身是函数,当然也有自己的构造函数。
理解了上面一点,那么接下来我们在理解原型链的时候就会容易得多。
请看刺激的推导过程
>前提 所有对象都由构造函数实例化而来,构造函数默认拥有与之相关联的原型对象
>❒ ① 构造函数的原型对象也是对象,因此也有自己的构造函数
>❒ ② 构造函数原型对象的构造函数,也有与之相关连的原型对象
>❒ ③ 构造函数原型对象的原型对象(`__proto__`)也有自己的构造函数,其也拥有关联的原型对象
>☞ 以上就形成了一种链式的访问结构,是为`原型链`。
二、原型链结构
现在我们基本上把原型链的由来说清楚了,那么接下来通过具体的代码来分析原型链的整体结构。
示例代码
//01 自定义构造函数Person和Animal
function Person() {}
function Animal() {}
//02 使用构造函数创建实例对象
var p1 = new Person();
var p2 = new Person();
var a = new Animal();
//03 创建数组对象
var arrM = ["demoA","demoB"];
上面的代码非常简单,其中p1,p2和a它们是自定义构造函数的实例化对象。其次,我们采用快捷方式创建了arrM数组,arrM其实是内置构造函数Array的实例化对象。另外,Person和Animal这两个构造函数其实是Function构造函数的实例对象。理解以上几点后,我们就可以来看一下这几行代码对应的原型链结构图了。

原型链结构图说明:
① 因为复杂度关系,arrM对象的原型链结构图单独给出。
② Object.prototype是所有原型链的顶端,终点为null。
验证原型链相关的代码
//[1] 验证p1、p2的原型对象为Person.prototype
// 验证a 的原型对象为Animal.prototype
console.log(p1.__proto__ == Person.prototype); //true
console.log(p2.__proto__ == Person.prototype); //true
console.log(a.__proto__ == Animal.prototype); //true
//[2] 获取Person.prototype|Animal.prototype构造函数
// 验证Person.prototype|Animal.prototype原型对象为Object.prototype
// 先删除实例成员,通过原型成员访问
delete Person.prototype.constructor;
delete Animal.prototype.constructor;
console.log(Person.prototype.constructor == Object); //true
console.log(Animal.prototype.constructor == Object); //true
console.log(Person.prototype.__proto__ == Object.prototype); //true
console.log(Animal.prototype.__proto__ == Object.prototype); //true
//[3] 验证Person和Animal的构造函数为Function
// 验证Person和Animal构造函数的原型对象为空函数
console.log(Person.constructor == Function); //true
console.log(Animal.constructor == Function); //true
console.log(Person.__proto__ == Function.prototype); //true
console.log(Animal.__proto__ == Function.prototype); //true
//[4] 验证Function.prototype的构造函数为Function
console.log(Function.prototype.constructor == Function); //true
//[5] 验证Function和Object的构造函数为Function
console.log(Function.constructor == Function); //true
console.log(Object.constructor == Function); //true
//[6] 验证Function.prototype的原型对象为Object.prototype而不是它自己
console.log(Function.prototype.__proto__ == Object.prototype);//true
//[7] 获取原型链的终点
console.log(Object.prototype.__proto__); //null
下面贴出数组对象的原型链结构图

验证数组对象原型链结构的代码示例
//[1] 验证arrM的构造函数为Array
//方法1
console.log(arrM.constructor == Array); //true
//方法2
console.log(Object.prototype.toString.call(arrM)); //[object Array]
//[2] 验证Array的构造函数为Function
console.log(Array.constructor == Function); //true
//[3] 验证Array构造函数的原型对象为Function.prototype(空函数)
console.log(Array.__proto__ == Function.prototype); //true
//[4] 验证Array.prototype的构造函数为Object,原型对象为Object.prototype
delete Array.prototype.constructor;
console.log(Array.prototype.constructor == Object); //true
console.log(Array.prototype.__proto__ == Object.prototype); //true
三、原型链的访问
原型链的访问规则
对象在访问属性或方法的时候,先检查自己的实例成员,如果存在那么就直接使用,如果不存在那么找到该对象的原型对象,查找原型对象上面是否有对应的成员,如果有那么就直接使用,如果没有那么就顺着原型链一直向上查找,如果找到则使用,找不到就重复该过程直到原型链的顶端,此时如果访问的是属性就返回undefined,方法则报错。
function Person() {
this.name = "wendingding";
}
Person.prototype = {
constructor:Person,
name:"自来熟",
showName:function () {
this.name.lastIndexOf()
}
};
var p = new Person();
console.log(p.name); //访问的是实例成员上面的name属性:wendingding
p.showName(); //打印wendingding
console.log(p.age); //该属性原型链中并不存在,返回undefined
p.showAge(); //该属性原型链中并不存在,报错
概念和访问原则说明
>❐ 实例成员:实例对象的属性或者是方法
>❐ 原型成员:实例对象的原型对象的属性或者是方法
>❐ 访问原则:就近原则
四、getPrototypeOf、isPrototypeOf和instanceof
Object.getPrototypeOf方法用于获取指定实例对象的原型对象,用法非常简单,只需要把实例对象作为参数传递,该方法就会把当前实例对象的原型对象返回给我们。说白了,Object的这个静态方法其作用就是返回实例对象__proto__属性指向的原型prototype。
//01 声明构造函数F
function F() {}
//02 使用构造函数F获取实例对象f
var f = new F();
//03 测试getPrototypeOf方法的使用
console.log(Object.getPrototypeOf(f)); //打印的结果为一个对象,该对象是F相关联的原型对象
console.log(Object.getPrototypeOf(f) === F.prototype); //true
console.log(Object.getPrototypeOf(f) === f.__proto__); //true
isPrototypeOf方法用于检查某对象是否在指定对象的原型链中,如果在,那么返回结果true,否则返回结果false。
//01 声明构造函数Person
function Person() {}
//02 获取实例化对象p
var p = new Person();
//03 测试isPrototypeOf的使用
console.log(Person.prototype.isPrototypeOf(p)); //true
console.log(Object.prototype.isPrototypeOf(p)); //true
var arr = [1,2,3];
console.log(Array.prototype.isPrototypeOf(arr)); //true
console.log(Object.prototype.isPrototypeOf(arr)); //true
console.log(Object.prototype.isPrototypeOf(Person));//true
上述代码的原型链
> ① p-->Person.prototype -->Object.prototype -->null
> ② arr-->Array.prototype -->Object.prototype -->null
> Object.prototype因处于所有原型链的顶端,故所有实例对象都继承于Object.prototype
instanceof运算符的作用跟isPrototypeOf方法类似,左操作数是待检测的实例对象,右操作数是用于检测的构造函数。如果右操作数指定构造函数的原型对象在左操作数实例对象的原型链上面,则返回结果true,否则返回结果false。
//01 声明构造函数Person
function Person() {}
//02 获取实例化对象p
var p = new Person();
//03 测试isPrototypeOf的使用
console.log(p instanceof Person); //true
console.log(p instanceof Object); //true
//04 Object构造函数的原型对象在Function这个实例对象的原型链中
console.log(Function instanceof Object); //true
//05 Function构造函数的原型对象在Object这个实例对象的原型链中
console.log(Object instanceof Function); //true
我们可以通过下面给出的代码示例来进一步理解
//01 声明构造函数Person
function Person() {}
//02 获取实例化对象p
var p1 = new Person();
//03 测试isPrototypeOf的使用
console.log(p1 instanceof Person); //true
//04 替换Person默认的原型对象
Person.prototype = {
constructor:Person,
showInfo:function () {
console.log("xxx");
}
};
//05 重置了构造函数原型对象之后,因为Person
console.log(p1 instanceof Person); //false
//06 在Person构造函数重置了原型对象后重新创建实例化对象
var p2 = new Person();
console.log(p2 instanceof Person); //true
//==> 建议开发中,总是先设置构造函数的原型对象,之后在创建实例化对象
贴出上面代码的原型链结构图(部分)

五、原型链相关的继承
继承是面向对象编程的基本特征之一,JavaScript支持面向对象编程,在实现继承的时候,有多种可行方案。接下来,我们分别来认识下原型式继承、原型链继承以及在此基础上演变出来的组合继承。
原型式继承基本写法
//01 提供超类型|父类型构造函数
function SuperClass() {}
//02 设置父类型的原型属性和原型方法
SuperClass.prototype.info = 'SuperClass的信息';
SuperClass.prototype.showInfo = function () {
console.log(this.info);
};
//03 提供子类型
function SubClass() {}
//04 设置继承(原型对象继承)
SubClass.prototype = SuperClass.prototype;
SubClass.prototype.constructor = SubClass;
var sub = new SubClass();
console.log(sub.info); //SuperClass的信息
sub.showInfo(); //SuperClass的信息
贴出原型式继承结构图

原型链继承
实现思想
> 核心:把父类的实例对象设置为子类的原型对象 SubClass.prototype = new SuperClass();
> 问题:无法为父构造函数(SuperClass)传递参数
原型链继承基本写法
//01 提供超类型|父类型
function SuperClass() {
this.name = 'SuperClass的名称';
this.showName = function () {
console.log(this.name);
}
}
//02 设置父类型的原型属性和原型方法
SuperClass.prototype.info = 'SuperClass的信息';
SuperClass.prototype.showInfo = function () {
console.log(this.info);
};
//03 提供子类型
function SubClass() {}
//04 设置继承(原型对象继承)
var sup = new SuperClass();
SubClass.prototype = sup;
SubClass.prototype.constructor = SubClass;
var sub = new SubClass();
console.log(sub.name); //SuperClass的名称
console.log(sub.info); //SuperClass的信息
sub.showInfo(); //SuperClass的信息
sub.showName(); //SuperClass的名称
贴出原型链继承结构图

组合继承
实现思想
>① 使用原型链实现对原型属性和方法的继承
>② 通过伪造(冒充)构造函数来实现对实例成员的继承,并且解决了父构造函数传参问题
组合继承基本写法
//01 提供超类型|父类型
function SuperClass(name) {
this.name = name;
this.showName = function () {
console.log(this.name);
}
}
//02 设置父类型的原型属性和原型方法
SuperClass.prototype.info = 'SuperClass的信息';
SuperClass.prototype.showInfo = function () {
console.log(this.info);
};
//03 提供子类型
function SubClass(name) {
SuperClass.call(this,name);
}
//(1)获取父构造函数的实例成员 Person.call(this,name);
//(2)获取父构造函数的原型成员 SubClass.prototype = SuperClass.prototype;
SubClass.prototype = SuperClass.prototype;
SubClass.prototype.constructor = SubClass;
var sub_one = new SubClass("zhangsan");
var sub_two = new SubClass("lisi");
console.log(sub_one);
console.log(sub_two);
最后,贴出实例对象sub_one和sub_two的打印结果

前端开发系列018-基础篇之JavaScript原型链的更多相关文章
- 前端开发【第4篇:JavaScript基础】
JavaScript简述 上一篇文章已经聊过JavaScript的历史了这里不再复述了直接切入正题,JavaScript是一门解释型.动态类型.弱类型语言. 解释型语言和编译型语言就类似看一本书,编译 ...
- 前端开发【第5篇:JavaScript进阶】
语句 复合表达式和空语句 复合表达式意思是把多条表达式连接在一起形成一个表达式 { let a = 100; let b = 200; let c = a + b; } 注意这里不能再块级后面加分号, ...
- 前端开发【第3篇:JavaScript序】
JavaScript历史 聊聊JavaScript的诞生 JavaScirpt鼻祖:Bremdan Eich(布兰登·艾奇),JavaScript的诞生于浏览器的鼻祖网景公司(Netscape),发布 ...
- 前端开发【第6篇:JavaScript客户端(浏览器)】
Web浏览器中的JavaScript 客户端JavaScript时间线 1.Web浏览器创建Document对象,并且开始解析web页面,解析HTML元素和它门的文本内容后添加Element对象和Te ...
- 从0到1用react+antd+redux搭建一个开箱即用的企业级管理后台系列(基础篇)
背景 最近因为要做一个新的管理后台项目,新公司大部分是用vue写的,技术栈这块也是想切到react上面来,所以,这次从0到1重新搭建一个react项目架子,需要考虑的东西的很多,包括目录结构.代码 ...
- 前端开发:css基础知识之盒模型以及浮动布局。
前端开发:css基础知识之盒模型以及浮动布局 前言 楼主的蛮多朋友最近都在学习html5,他们都会问到同一个问题 浮动是什么东西? 为什么这个浮动没有效果? 这个问题楼主已经回答了n遍.今天则是把 ...
- ESP8266开发之旅 基础篇① 走进ESP8266的世界
授人以鱼不如授人以渔,目的不是为了教会你具体项目开发,而是学会学习的能力.希望大家分享给你周边需要的朋友或者同学,说不定大神成长之路有博哥的奠基石... QQ技术互动交流群:ESP8266&3 ...
- ESP8266开发之旅 基础篇② 如何安装ESP8266的Arduino开发环境
授人以鱼不如授人以渔,目的不是为了教会你具体项目开发,而是学会学习的能力.希望大家分享给你周边需要的朋友或者同学,说不定大神成长之路有博哥的奠基石... QQ技术互动交流群:ESP8266&3 ...
- ESP8266开发之旅 基础篇③ ESP8266与Arduino的开发说明
授人以鱼不如授人以渔,目的不是为了教会你具体项目开发,而是学会学习的能力.希望大家分享给你周边需要的朋友或者同学,说不定大神成长之路有博哥的奠基石... QQ技术互动交流群:ESP8266&3 ...
- openlayers5-webpack 入门开发系列一初探篇(附源码下载)
前言 openlayers5-webpack 入门开发系列环境知识点了解: node 安装包下载webpack 打包管理工具需要依赖 node 环境,所以 node 安装包必须安装,上面链接是官网下载 ...
随机推荐
- iOS自动化测试环境搭建总结
1.关于iOS真机和模拟器自动化环境搭建 参考博文: Appium+Python3+iOS真机环境搭建 iOS自动化测试:mac下iOS10 appium测试环境的搭建 IOS + Appium自动化 ...
- MySQL的并发问题的解决方案
怎么解决脏读.不可重复读.幻读这些问题呢?其实有两种可选的解决方案 方案一.读操作利用MVCC(多版本并发控制),写操作进行加锁. 所谓的MVCC,就是生成一个ReadView,通过ReadView找 ...
- linux 安装jdk8
参考:https://www.cnblogs.com/raoyulu/p/13265419.html 一.卸载现用的JDK 1.查看Linux自带的JDK是否已安装 查看是否安装openjdk jav ...
- 开源的java内网穿透 - 维基代理(wiki-proxy)
1.简介 维基代理(wiki-proxy).开源的java内网穿透项目. 技术栈:cdkjFramework(维基框架).JPA.Netty 遵循MIT许可,因此您可以对它进行复制.修改.传播并用于任 ...
- 操作系统综合题之“按要求是个进程协调完成任务,补充完整下列程序,将编号①~⑩处空缺的内容填写(Buffer缓冲区问题-代码补充)”
1.问题:假设某系统有四个进程.input1和input2进程负责从不同设备读取数据,分别表示为data1和data2,存放在缓冲区Buffer中,output1和output2进程负责从Buffer ...
- uniapp跨平台开发HarmonyOS NEXT应用初体验
之前写过使用uniapp开发鸿蒙应用的教程,简单介绍了如何配置开发环境和运行项目.那时候的HbuilderX还是4.22版本,小一年过去了HbuilderX的正式版本已经来到4.64,历经了多个版本的 ...
- SQL 强化练习 (八)
继续练习写sql, 不能停下来. 今天还额外对 Excel 拼接 sql 语句做了一个代码实现, 逻辑是蛮简单的, 发现其实很多东西都是蛮简单的, 只要一点点去做, 明白逻辑过后, 慢慢去调试, 都是 ...
- 代码重构(OOP)-小栗子(PyQt5)
主要是为了练习下 面向对象, 不断提醒自己代码一定要写成 营销风格, 和优雅. 最近在B站上看一下关于 Python GUI 编程的内容. 恰好呢, 前不久的一个 将本地 Ecxcel 数据 发布到 ...
- C#开发的Panel滚动分页控件 - 开源研究系列文章
前些时候因为想拥有一个自己的软件快捷打开软件,于是参考Windows 11的开始菜单,进行了编写这个应用软件,里面有一个功能就是对显示的Panel里的应用对象的分页功能,于是就想写一个对Panel的自 ...
- FreeSWITCH Jitter Buffer 技术解析与应用指南
一.概述 freeswitch是一款简单好用的VOIP开源软交换平台. Jitter Buffer(抖动缓冲器)是实时语音通信中解决网络抖动和延迟问题的核心技术.FreeSWITCH通过STFU(So ...