JavaScript原型与继承的秘密
在GitHub上看到的关于JavaScript原型与继承的讲解,感觉很有用,为方便以后阅读,copy到自己的随笔中。
原文地址:https://github.com/dreamapplehappy/blog/blob/master/2018/12/30/README.md
首先我们需要知道的是,JavaScript是一种动态语言,本质上说它是没有Class
(类)的;但是它也需要一种继承的方式, 那就是原型继承;JavaScript对象的一些属性和方法都是继承自别的对象。
很多同学对JavaScript的原型和继承不是很理解,一个重要的原因就是大家没有理解__proto__
和prototype
这两个属性的意思。 接下来我们先来好好梳理一下这两个属性,看看它们存在哪里,代表了什么意义,又有什么作用。
首先来说一下__proto__
这个属性吧,我们需要知道的是,除了null
和undefined
,JavaScript中的所有数据类型都有这个属性; 它表示的意义是:当我们访问一个对象的某个属性的时候,如果这个对象自身不存在这个属性, 那么就从这个对象的__proto__
(为了方便下面描述,这里暂且把这个属性称作p0
)属性上面 继续查找这个属性,如果p0
上面还存在__proto__
(p1)属性的话,那么就会继续在p1
上面查找响应的属性, 直到查找到这个属性,或者没有__proto__
属性为止。
我们可以用下面这两幅图来表示:
上面这幅图表示在obj
的原型链
上面找到了属性名字是a
的值
上面这幅图表示在obj
的原型链
上面没有找到属性名字是a
的值
我们把一个对象的__proto__
属性所指向的对象,叫做这个对象的原型
;我们可以修改一个对象的原型
来让这个对象拥有某种属性,或者某个方法。
// 修改一个Number类型的值的原型
const num = 1;
num.__proto__.name = "My name is 1";
console.log(num.name); // My name is 1 // 修改一个对象的原型
const obj = {};
obj.__proto__.name = "dreamapple";
console.log(obj.name); // dreamapple
这里需要特别注意的是,__proto__
这个属性虽然被大多数的浏览器支持,但是其实它仅在ECMAScript 2015 规范
中被准确的定义, 目的是为了给这个传统的功能定制一个标准,以确保浏览器之间的兼容性。通过使用__proto__
属性来修改一个对象的原型是非常慢且影响性能的一种操作。 所以,现在如果我们想要获取一个对象的原型,推荐使用Object.getPrototypeOf
或者Reflect.getPrototypeOf
,设置一个对象的原型推荐使用Object.setPrototypeOf
或者是Reflect.setPrototypeOf
。
到这里为止,我们来对__proto__
属性做一个总结:
- 存在哪里? 除了
null
和undefined
所有其他的JavaScript对象或者原始类型都有这个属性 - 代表了什么? 表示了一个对象的原型
- 有什么作用? 可以获取和修改一个对象的原型
说完__proto__
属性,接下来我们就要好好的来理解一下prototype
属性了;首先我们需要记住的是,这个属性一般只存在于函数对象上面; 只要是能够作为构造器的函数,他们都包含这个属性。也就是说,只要这个函数能够通过使用new
操作符来生成一个新的对象, 那么这个函数肯定具有prototype
属性。因为我们自定义的函数都可以通过new
操作符生成一个对象,所以我们自定义的函数都有prototype
这个属性。
// 函数字面量
console.log((function(){}).prototype); // {constructor: ƒ} // Date构造器
console.log(Date.prototype); // {constructor: ƒ, toString: ƒ, toDateString: ƒ, toTimeString: ƒ, toISOString: ƒ, …} // Math.abs 不是构造器,不能通过new操作符生成一个新的对象,所以不含有prototype属性
console.log(Math.abs.prototype); // undefined
那这个prototype
属性有什么作用呢?这个prototype
属性的作用就是:函数通过使用new
操作符生成的一个对象, 这个对象的原型(也就是__proto__
)指向该函数的prototype
属性。 那么一个比较简洁的表示__proto__
和prototype
属性之间关系的等式也就出来了,如下所示:
// 其中F表示一个自定义的函数或者是含有prototype属性的内置函数
new F().__proto__ === F.prototype // true
我们可以使用下面这张图来更加形象的表示上面这种关系:
看到上面等式,我想大家对于__proto__
和prototype
之间关系的理解应该会更深一层了。
好,接下来我们对prototype
属性也做一个总结:
- 存在哪里? 自定义的函数,或者能够通过
new
操作符生成一个对象的内置函数 - 代表了什么? 它表示了某个函数通过
new
操作符生成的对象的原型 - 有什么作用? 可以让一个函数通过
new
操作符生成的许多对象共享一些方法和属性
其实到这里为止,关于JavaScript的原型和继承已经讲得差不多了;下面的内容是一些基于上面的一些拓展, 可以让你更好地理解我们上面所说的。
当我们理解了上面的知识点之后,我们就可以对下面的表达式做一个判断了:
// 因为Object是一个函数,函数的构造器都是Function
Object.__proto__ === Function.prototype // true // 通过函数字面量定义的函数的__proto__属性都指向Function.prototype
(function(){}).__proto__ === Function.prototype // true // 通过对象字面量定义的对象的__proto__属性都是指向Object.prototype
({}).__proto__ === Object.prototype // true // Object函数的原型的__proto__属性指向null
Object.prototype.__proto__ === null // true // 因为Function本身也是一个函数,所以Function函数的__proto__属性指向它自身的prototype
Function.__proto__ === Function.prototype // true // 因为Function的prototype是一个对象,所以Function.prototype的__proto__属性指向Object.prototype
Function.prototype.__proto__ === Object.prototype // true
如果你能够把上面的表达式都梳理清楚的话,那么说明你对这部分知识掌握的还是不错的。
谈及JavaScript的原型和继承,那么我们还需要知道另一个概念;那就是constructor
,那什么是constructor
呢?constructor
表示一个对象的构造函数,除了null
和undefined
以外,JavaScript中的所有数据类型都有这个属性; 我们可以通过下面的代码来验证一下:
null.constructor // Uncaught TypeError: Cannot read property 'constructor' of null ...
undefined.constructor // Uncaught TypeError: Cannot read property 'constructor' of undefined ... (true).constructor // ƒ Boolean() { [native code] }
(1).constructor // ƒ Number() { [native code] }
"hello".constructor // ƒ String() { [native code] }
我们还可以使用下面的图来更加具体的表现:
但是其实上面这张图的表示并不算准确,因为一个对象的constructor
属性确切地说并不是存在这个对象上面的; 而是存在这个对象的原型上面的(如果是多级继承需要手动修改原型的constructor
属性,见文章末尾的代码),我们可以使用下面的代码来解释一下:
const F = function() {};
// 当我们定义一个函数的时候,这个函数的prototype属性上面的constructor属性指向自己本身
F.prototype.constructor === F; // true
下面的图片形象的展示了上面的代码所表示的内容:
关于constructor
还有一些需要注意的问题,对与JavaScript的原始类型来说,它们的constructor
属性是只读的,不可以修改。 我们可以通过下面的代码来验证一下:
(1).constructor = "something";
console.log((1).constructor); // 输出 ƒ Number() { [native code] }
当然,如果你真的想更改这些原始类型的constructor
属性的话,也不是不可以,你可以通过下面的方式来进行修改:
Number.prototype.constructor = "number constructor";
(1).constructor = 1;
console.log((1).constructor); // 输出 number constructor
当然上面的方式我们是不推荐你在真实的开发中去使用的,如果你想要了解更多关于constructor
的内容,可以看看Object.prototype.constructor。
接下来,我会使用一些代码来把今天讲解的知识再大致的回顾一下:
function Animal(name) {
this.name = name;
} Animal.prototype.setName = function(name) {
this.name = name;
};
Animal.prototype.getName = function(name) {
return this.name;
}; function Dog(name, breed) {
Animal.call(this, name);
this.breed = breed;
}
// 将Dog的prototype的指向修改为Animal.prototype
Dog.prototype = Object.create(Animal.prototype); // 因为上面的语句将我们原来的prototype的指向修改了,所以我们要重新定义Dog的prototype属性的constructor属性
Reflect.defineProperty(Dog.prototype, "constructor", {
value: Dog,
enumerable: false, // 不可枚举
writable: true
}); const animal = new Animal("potato");
console.log(animal.__proto__ === Animal.prototype); // true
console.log(animal.constructor === Animal); // true
console.log(animal.name); // potato const dog = new Dog("potato", "labrador");
console.log(dog.name); // potato
console.log(dog.breed); // labrador
console.log(dog.__proto__ === Dog.prototype); // true
console.log(dog.constructor === Dog); // true
JavaScript原型与继承的秘密的更多相关文章
- 深入理解:JavaScript原型与继承
深入理解:JavaScript原型与继承 看过不少书籍,不少文章,对于原型与继承的说明基本上让人不明觉厉,特别是对于习惯了面向对象编程的人来说更难理解,这里我就给大家说说我的理解. 首先JavaScr ...
- JavaScript原型与继承
JavaScript原型与继承 原型 在JavaScript中,每个函数都有一个prototype属性,这个属性是一个指针,指向该函数的原型对象.这个原型对象为所有该实例所共享.在默认情况下,原型对象 ...
- JavaScript 原型与继承
JavaScript 原型与继承 JavaScript 中函数原型是实现继承的基础.prototype.construct.原型链以及基于原型链的继承是面向对象的重要内容 prototype 原型即 ...
- javascript原型链继承
一.关于javascript原型的基本概念: prototype属性:每个函数都一个prototype属性,这个属性指向函数的原型对象.原型对象主要用于共享实例中所包含的的属性和方法. constru ...
- JavaScript 原型与继承机制详解
引言 初识 JavaScript 对象的时候,我以为 JS 是没有继承这种说法的,虽说 JS 是一门面向对象语言,可是面向对象的一些特性在 JS 中并不存在(比如多态,不过严格来说也没有继承).这就困 ...
- 8条规则图解JavaScript原型链继承原理
原形链是JS难点之一,而且很多书都喜欢用一大堆的文字解释给你听什么什么是原型链,就算有图配上讲解,有的图也是点到为止,很难让人不产生疑惑. 我们先来看一段程序,友情提示sublimeText看更爽: ...
- 【Javascript】Javascript原型与继承
一切都是对象! 以下的四种(undefined, number, string, boolean)属于简单的值类型,不是对象.剩下的几种情况——函数.数组.对象.null.new Number(10) ...
- 【前端知识体系-JS相关】深入理解JavaScript原型(继承)和原型链
1. Javascript继承 1.1 原型链继承 function Parent() { this.name = 'zhangsan'; this.children = ['A', 'B', 'C' ...
- JavaScript原型及继承
一.浅谈原型 首先我们要知道创建对象的方法有两种: 1.通过字面量的方式直接创建 var obj = { name:'baimao', age:21 } 2.通过构造函数创建对象 function P ...
随机推荐
- Find Peak Element(ARRAY - Devide-and-Conquer)
QUESTION A peak element is an element that is greater than its neighbors. Given an input array where ...
- LeetCode包括main函数的答题框架(Java+Eclipse)
http://zhangnai.xin/2016/09/20/LeetCode-Framework/ 目录结构: LeetCode ——项目名称,方便Eclipse内置Git对代码进行管理和多终端同步 ...
- 1-QT-文件操作
Qt文本文件的读写操作 Qt文件操作详解(创建.写入.删除.INI.XML文件等 二进制文件的读写文件可以使用QFile类.QStream文本文件的读写建议使用QTextStream类,它操作文件更加 ...
- 利用redis完成自动补全搜索功能(一)
最近要做一个搜索自动补全的功能(目前只要求做最前匹配),自动补全就是自动提示,类似于搜索引擎,再上面输入一个字符,下面会提示多个关键词供参考,比如你输入 nb 2字符, 会自动提示nba,nba录像, ...
- UI设计如何做好排版?你可以学习一下格式塔原理
格式塔是一种视觉感知的理论,是研究人们视觉如何将元素组织成群体或整体,从而视觉上进行分类,在设计中,我们使用格式原理能使得我们设计更科学性,更具吸引力.通过格式塔效应,去处理设计中的点.线.面.颜色. ...
- 前后台交互(打开前端页面,不传递任何数据,发送ajax请求)
1.打开前端,不传递任何数据 <script src="./jquery.min.js"></script> <script> $(docume ...
- laravel-excel文档翻译笔记
1.安装 1>composer 安装 "maatwebsite/excel": "~2.1.0" 2>app/config/ap ...
- word2vec相关资源
word2vec官网:https://code.google.com/p/word2vec/ 利用中文数据跑Google开源项目word2vec:http://www.cnblogs.com/hebi ...
- 新电脑的操作系统win10的所有设置问题汇总
上来改的win7发现很多驱动没法装,装了也不能用,后来只能改win10了,另外win7的风扇声音也很大. 1.关闭win10自动更新.在服务里面禁用winupdate 2.注销改成了点头像,然后点注销 ...
- NSNotificationCenter 注意
成对出现 意思很简单,NSNotificationCenter消息的接受线程是基于发送消息的线程的.也就是同步的,因此,有时候,你发送的消息可能不在主线程,而大家都知道操作UI必须在主线程,不然会出现 ...