JavaScript 规定,每一个构造函数都有一个 prototype 属性,指向另一个对象。这个对象的所有属性和方法,都会被构造函数的所拥有。

这也就意味着,我们可以把所有对象实例需要共享的属性和方法直接定义在 prototype 对象上。

function Person (name, age) {
this.name = name
this.age = age
}

console.log(Person.prototype)

Person.prototype.type = 'human'

Person.prototype.sayName = function () {
console.log(this.name)
}

var p1 = new Person(...)
var p2 = new Person(...)

console.log(p1.sayName === p2.sayName) // => true

这时所有实例的 type 属性和 sayName() 方法,其实都是同一个内存地址,指向 prototype 对象,因此就提高了运行效率。

构造函数、实例、原型三者之间的关系

任何函数都具有一个 prototype 属性,该属性是一个对象。

function F () {}
console.log(F.prototype) // => object

F.prototype.sayHi = function () {
console.log('hi!')
}

构造函数的 prototype 对象默认都有一个 constructor 属性,指向 prototype 对象所在函数。


console.log(F.prototype.constructor === F) // => true

通过构造函数得到的实例对象内部会包含一个指向构造函数的 prototype 对象的指针 __proto__

var instance = new F()
console.log(instance.__proto__ === F.prototype) // => true

<p class="tip"> __proto__ 是非标准属性。</p>

实例对象可以直接访问原型对象成员。

instance.sayHi() // => hi!

总结:

  • 任何函数都具有一个 prototype 属性,该属性是一个对象

  • 构造函数的 prototype 对象默认都有一个 constructor 属性,指向 prototype 对象所在函数

  • 通过构造函数得到的实例对象内部会包含一个指向构造函数的 prototype 对象的指针 __proto__

  • 所有实例都直接或间接继承了原型对象的成员

属性成员的搜索原则:原型链

了解了 构造函数-实例-原型对象 三者之间的关系后,接下来我们来解释一下为什么实例对象可以访问原型对象中的成员。

每当代码读取某个对象的某个属性时,都会执行一次搜索,目标是具有给定名字的属性

  • 搜索首先从对象实例本身开始

  • 如果在实例中找到了具有给定名字的属性,则返回该属性的值

  • 如果没有找到,则继续搜索指针指向的原型对象,在原型对象中查找具有给定名字的属性

  • 如果在原型对象中找到了这个属性,则返回该属性的值

也就是说,在我们调用 person1.sayName() 的时候,会先后执行两次搜索:

  • 首先,解析器会问:“实例 person1 有 sayName 属性吗?”答:“没有。

  • ”然后,它继续搜索,再问:“ person1 的原型有 sayName 属性吗?”答:“有。

  • ”于是,它就读取那个保存在原型对象中的函数。

  • 当我们调用 person2.sayName() 时,将会重现相同的搜索过程,得到相同的结果。

而这正是多个对象实例共享原型所保存的属性和方法的基本原理。

总结:

  • 先在自己身上找,找到即返回

  • 自己身上找不到,则沿着原型链向上查找,找到即返回

  • 如果一直到原型链的末端还没有找到,则返回 undefined

实例对象读写原型对象成员

读取:

  • 先在自己身上找,找到即返回

  • 自己身上找不到,则沿着原型链向上查找,找到即返回

  • 如果一直到原型链的末端还没有找到,则返回 undefined

值类型成员写入(实例对象.值类型成员 = xx):

  • 当实例期望重写原型对象中的某个普通数据成员时实际上会把该成员添加到自己身上

  • 也就是说该行为实际上会屏蔽掉对原型对象成员的访问

引用类型成员写入(实例对象.引用类型成员 = xx):

  • 同上

复杂类型修改(实例对象.成员.xx = xx):

  • 同样会先在自己身上找该成员,如果自己身上找到则直接修改

  • 如果自己身上找不到,则沿着原型链继续查找,如果找到则修改

  • 如果一直到原型链的末端还没有找到该成员,则报错(实例对象.undefined.xx = xx

图例:

更简单的原型语法

我们注意到,前面例子中每添加一个属性和方法就要敲一遍 Person.prototype 。为减少不必要的输入,更常见的做法是用一个包含所有属性和方法的对象字面量来重写整个原型对象:

function Person (name, age) {
this.name = name
this.age = age
}

Person.prototype = {
type: 'human',
sayHello: function () {
console.log('我叫' + this.name + ',我今年' + this.age + '岁了')
}
}

在该示例中,我们将 Person.prototype 重置到了一个新的对象。这样做的好处就是为 Person.prototype 添加成员简单了,但是也会带来一个问题,那就是原型对象丢失了 constructor 成员。

所以,我们为了保持 constructor 的指向正确,建议的写法是:


function Person (name, age) {
this.name = name
this.age = age
}

Person.prototype = {
constructor: Person, // => 手动将 constructor 指向正确的构造函数
type: 'human',
sayHello: function () {
console.log('我叫' + this.name + ',我今年' + this.age + '岁了')
}
}

原生对象的原型

<p class="tip"> 所有函数都有 prototype 属性对象。</p>

  • Object.prototype

  • Function.prototype

  • Array.prototype

  • String.prototype

  • Number.prototype

  • Date.prototype

  • ...

练习:为数组对象和字符串对象扩展原型方法。

原型对象使用建议

  • 私有成员(一般就是非函数成员)放到构造函数中

  • 共享成员(一般就是函数)放到原型对象中

  • 如果重置了 prototype 记得修正 constructor 的指向

js对象原型链的更多相关文章

  1. 前端基本知识(二):JS的原型链的理解

    之前一直对于前端的基本知识不是了解很详细,基本功不扎实,但是前端开发中的基本知识才是以后职业发展的根基,虽然自己总是以一种实践是检验真理的唯一标准,写代码实践项目才是唯一,但是经常遇到知道怎么去解决这 ...

  2. js javascript 原型链详解

    看了许多大神的博文,才少许明白了js 中原型链的概念,下面给大家浅谈一下,顺便也是为了巩固自己 首先看原型链之前先来了解一下new关键字的作用,在许多高级语言中,new是必不可少的关键字,其作用是为了 ...

  3. 怎么理解js的原型链继承?

    前言 了解java等面向对象语言的童鞋应该知道.面向对象的三大特性就是:封装,继承,多态. 今天,我们就来聊一聊继承.但是,注意,我们现在说的是js的继承. 在js的es6语法出来之前,我们想实现js ...

  4. 关于JS对象原型prototype与继承,ES6的class和extends · kesheng's personal blog

    传统方式:通过function关键字来定义一个对象类型 1234567891011 function People(name) { this.name = name}People.prototype. ...

  5. javascript 创建对象及对象原型链属性介绍

    我们知道javascript里定义一个普通对象的方法,如: let obj = {}; obj.num = 1; obj.string = 'string'; obj.func = function( ...

  6. react-native-pg-utils(对react-native全局进行配置,对内置对象原型链增加方法,增加常用全局方法.)

    react-native-pg-utils 对react-native全局进行配置,对内置对象原型链增加方法,增加常用全局方法. 每次新建react-native项目之后都会发现有一些很常用的方法在这 ...

  7. [js高手之路]一步步图解javascript的原型(prototype)对象,原型链

    我们接着上文继续,我们通过原型方式,解决了多个实例的方法共享问题,接下来,我们就来搞清楚原型(prototype),原型链的来龙去脉. function CreateObj(uName) { this ...

  8. 自己对js对原型链的理解

    js对象分为2种 函数对象和普通对象 函数对象 比如 function Show(){}var x=function Show2(){}var b=new Function("show3&q ...

  9. JS中原型链继承

    当我们通过构造函数A来实现一项功能的时候,而构造函数B中需要用到构造函数A中的属性或者方法,如果我们对B中的属性或者方法进行重写就会出现冗杂的代码,同时写出来也很是麻烦.而在js中每个函数都有个原型, ...

随机推荐

  1. CF715E Complete the Permutations(第一类斯特林数)

    题目 CF715E Complete the Permutations 做法 先考虑无\(0\)排列的最小花费,其实就是沿着置换交换,花费:\(n-\)环个数,所以我们主要是要求出规定环的个数 考虑连 ...

  2. jQuery多功能日历插件 带事件记录功能

    在线演示 本地下载

  3. zabbix监控php-fpm的性能

    zabbix监控php-fpm主要是通过nginx配置php-fpm的状态输出页面,在正则取值 要nginx能输出php-fpm的状态必须要先修改php-fpm的配置,这个配置没有开启nginx 就没 ...

  4. python的正则表达式捕获组命名问题

  5. Python学习之:pycharm配置

    最近需要做一些小工具,听说Python不错,就学习一下.工欲善其事必先利其器,一个好的IDE对于学习一门新知识是很有帮助的,边写代码边换IDE,纠结了几天,最终还是选择了pycharm,之前觉得不够好 ...

  6. 使用Kali Linux 破解无线网

    用到的工具 airmon-ngairodump-ngaireplay-ngaircrack-ng 过程 123456789101112131415161718192021222324 root@lm: ...

  7. ElasticSearch高可用集群环境搭建和分片原理

    1.ES是如何实现分布式高并发全文检索 2.简单介绍ES分片Shards分片技术 3.为什么ES主分片对应的备分片不在同一台节点存放 4.索引的主分片定义好后为什么不能做修改 5.ES如何实现高可用容 ...

  8. mac 安装python3

    Python有两个版本,一个是2.x版,一个是3.x版,这两个版本是不兼容的. 现在 Mac 上默认安装的 python 版本为  2.7 版本,若 安装 新版本需要 通过 该地址进行下载: http ...

  9. chrome关闭后还在进程中运行

    1.网上搜到信息: 设置 “即使关闭浏览器也后台运行” 取消打勾 2.然后我找了一下,应该是这个选项:“关闭 Google Chrome 后继续运行后台应用” 3. 4. 5.

  10. scala学习手记20 - 方法返回类型推断

    除了推演变量的类型,scala也会推演方法的返回类型.不过这里有一处需要注意:方法返回类型的推演依赖于方法的定义方式.如果用等号"="定义方法,scala就会推演方法返回类型:否则 ...