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对象原型链的更多相关文章
-
前端基本知识(二):JS的原型链的理解
之前一直对于前端的基本知识不是了解很详细,基本功不扎实,但是前端开发中的基本知识才是以后职业发展的根基,虽然自己总是以一种实践是检验真理的唯一标准,写代码实践项目才是唯一,但是经常遇到知道怎么去解决这 ...
-
js javascript 原型链详解
看了许多大神的博文,才少许明白了js 中原型链的概念,下面给大家浅谈一下,顺便也是为了巩固自己 首先看原型链之前先来了解一下new关键字的作用,在许多高级语言中,new是必不可少的关键字,其作用是为了 ...
-
怎么理解js的原型链继承?
前言 了解java等面向对象语言的童鞋应该知道.面向对象的三大特性就是:封装,继承,多态. 今天,我们就来聊一聊继承.但是,注意,我们现在说的是js的继承. 在js的es6语法出来之前,我们想实现js ...
-
关于JS对象原型prototype与继承,ES6的class和extends ·; kesheng's personal blog
传统方式:通过function关键字来定义一个对象类型 1234567891011 function People(name) { this.name = name}People.prototype. ...
-
javascript 创建对象及对象原型链属性介绍
我们知道javascript里定义一个普通对象的方法,如: let obj = {}; obj.num = 1; obj.string = 'string'; obj.func = function( ...
-
react-native-pg-utils(对react-native全局进行配置,对内置对象原型链增加方法,增加常用全局方法.)
react-native-pg-utils 对react-native全局进行配置,对内置对象原型链增加方法,增加常用全局方法. 每次新建react-native项目之后都会发现有一些很常用的方法在这 ...
-
[js高手之路]一步步图解javascript的原型(prototype)对象,原型链
我们接着上文继续,我们通过原型方式,解决了多个实例的方法共享问题,接下来,我们就来搞清楚原型(prototype),原型链的来龙去脉. function CreateObj(uName) { this ...
-
自己对js对原型链的理解
js对象分为2种 函数对象和普通对象 函数对象 比如 function Show(){}var x=function Show2(){}var b=new Function("show3&q ...
-
JS中原型链继承
当我们通过构造函数A来实现一项功能的时候,而构造函数B中需要用到构造函数A中的属性或者方法,如果我们对B中的属性或者方法进行重写就会出现冗杂的代码,同时写出来也很是麻烦.而在js中每个函数都有个原型, ...
随机推荐
-
linux platform设备与驱动
struct platform_driver { int (*probe)(struct platform_device *); int (*remove)(struct platform_devic ...
-
Windows下QT Creator工程中添加文件夹
在QT项目,常常会有很多头文件和源文件,但是QT Creator中却没有添加文件夹的功能,造成项目代码混乱. 下面是建立文件的步骤: 1.打开工程目录,在目录下建立文件夹,如建立文件SerialP ...
-
nginx ssi 配置小细节(一)
最近工作需要使用nginx的ssi (server side include)技术,在这里,将使用中的一点心得分享一下,也是一种备忘! 首先,nginx的ssi启用很简单,就只有三个最基本的指令: s ...
-
Part 3 talking about constraint in sql
What is Foreign key and how to create a Foreign key constraint? Note:Foreign Keys are used to enforc ...
-
迁移web.py项目至git@osc的项目演示平台
1. 开启演示平台 选择WSGI,输入应用名称,即是演示网页的网址. 2. web.py代码迁移 将Python的site-packages目录下的web文件夹复制到代码目录下,与网页程序在同一个文件 ...
-
DataTable与Linq相互转换
DataTable通过dt.AsEnumerable()方法转换可用Linq查询,反之,Linq也可以转化为DataTableDataTable newDt = query1.CopyToDataTa ...
-
九、Sql Server 基础培训《进度9-复杂查询练习》(实际操作)
知识点: 复杂查询1:统计全校有多少个男生.有多少个女生? 写法1(分组): select sex as 性别,count(*) as 人数 from student group by sex 写法2 ...
-
【剑指offer】反转链表
输入一个链表,反转链表后,输出新链表的表头. *与之前的问题不同,这里需要修改链表的指向(之前的问题,不需要修改结点的指针,只需使用栈保存每个结点的值) *注意非空处理以及最后一个结点指针的修改 /* ...
-
linux shell实现 URL 编码/解码方法
(1)编码的两种方法 # echo '手机' | tr -d '\n' | xxd -plain | sed 's/\(..\)/%\1/g' # echo '手机' |tr -d '\n' |od ...
-
js匹配日期和时间的正则表达式
自己写比较头疼,copy下来留着以后用 //日期的正则表达式 -]\d{}-([-]|[-])-([-]|[-][-]|[-])$/; var regExp = new RegExp(reg); if ...