在 JavaScript 中,每当定义一个对象(函数也是对象)时候,对象中都会包含一些预定义的属性,Javascript规定,每一个函数都有一个prototype对象属性,指向函数的原型对象(原型链上面的),使用原型对象的好处是所有对象实例共享它所包含的属性和方法。
prototype(对象属性)的所有属性和方法,都会被构造函数的实例继承。这意味着,我们可以把那些不变(公用)的属性和方法,直接定义在prototype对象属性上。prototype就是调用构造函数所创建的那个实例对象的原型(proto)。
prototype可以让所有对象实例共享它所包含的属性和方法。也就是说,不必在构造函数中定义对象信息,而是可以直接将这些信息添加到原型中。
1)原型是定义了一些公用的属性和方法,利用原型创建出来的新对象实例会共享原型的所有属性和方法。
实例代码:
2)严格模式下,原型的属性和方法还是会被原型实例所共享的。
实例代码:
3)通过原型创建的新对象实例是相互独立的,为新对象实例添加的方法只有该实例拥有这个方法,其它实例是没有这个方法的。
实例代码:
4)原型的总结:
所有引用类型都有一个__proto__(隐式原型)属性,属性值是一个普通的对象
所有函数都有一个prototype(原型)属性,属性值是一个普通的对象
所有引用类型的__proto__属性指向它构造函数的prototype
5)函数的原型prototype:函数才有prototype,prototype是一个对象,指向了当前构造函数的引用地址
6)函数的原型对象__proto__:所有对象都有__proto__属性, 当用构造函数实例化(new)一个对象时,会将新对象的__proto__属性指向 构造函数的prototype
7)原型对象和函数的原型的关系
说明:
所有函数的__proto__都是指向Function的prototype
构造函数new出来的对象__proto__指向构造函数的prototype
非构造函数实例化出的对象或者对象的prototype的__proto__指向Object的prototype
Object的prototype指向null
8)所有的原型对象都会自动获得一个 constructor(构造函数)属性,这个属性(是一个指针)指向 prototype 属性所在的函数(Person)
9)实例的构造函数属性(constructor)指向构造函数 :person1.constructor == Person
10)原型对象(Person.prototype)是 构造函数(Person)的一个实例
11)原型的分类:
隐式原型(proto):上面说的这个原型是JavaScript中的内置属性[[prototype]],此属性继承自object对象,在脚本中没有标准的方式访问[[prototype]],但Firefox、Safari和Chrome在每个对象上都支持一个属性_proto_,隐式原型的作用是用来构成原型链,实现基于原型的继承
显示原型(prototype):每一个函数在创建之后,便会拥有一个prototype属性,这个属性指向函数的原型对象,显示原型的作用是用来实现基于原型的继承与属性的共享
12)原型的使用方式:
通过给Calculator对象的prototype属性赋值对象字面量来设定Calculator对象的原型
在赋值原型prototype的时候使用function立即执行的表达式来赋值,可以封装私有的function,通过return的形式暴露出简单的使用名称,以达到public/private的效果
每个对象都可以有一个原型_proto_,这个原型还可以有它自己的原型,以此类推,形成一个原型链。查找特定属性的时候,我们先去这个对象里去找,如果没有的话就去它的原型对象里面去,如果还是没有的话再去向原型对象的原型对象里去寻找… 这个操作被委托在整个原型链上,这个就是我们说的原型链了。
我们知道 JS 有对象,比如
var obj = { name: 'obj' }我们可以对 obj 进行一些操作,包括
「读」属性
「新增」属性
「更新」属性
「删除」属性
下面我们主要来看一下「读」和「新增」属性。
为什么有 valueOf / toString 属性呢?
在我们没有对 obj 进行任何其他操作之前,发现 obj 已经有几个属性(方法)了:
那么问题来了:valueOf / toString / constructor 是怎么来?我们并没有给 obj.valueOf 赋值呀。
要搞清楚 valueOf / toString / constructor 是怎么来的,就要用到 console.dir 了。
上面这个图有点难懂,我手画一个示意图:
我们发现 console.dir(obj) 打出来的结果是:
obj 本身有一个属性 name(这是我们给它加的)
obj 还有一个属性叫做 proto(它是一个对象)
obj.proto 有很多属性,包括 valueOf、toString、constructor 等
obj.proto 其实也有一个叫做 proto 的属性(console.log 没有显示),值为 null
现在回到我们的问题:obj 为什么会拥有 valueOf / toString / constructor 这几个属性?
答案:
这跟 proto 有关。
当我们「读取」 obj.toString 时,JS 引擎会做下面的事情:
看看 obj 对象本身有没有 toString 属性。没有就走到下一步。
看看 obj.proto 对象有没有 toString 属性,发现 obj.proto 有 toString 属性,于是找到了
所以 obj.toString 实际上就是第 2 步中找到的 obj.proto.toString。
可以想象,
如果 obj.proto 没有,那么浏览器会继续查看 obj.proto.proto
如果 obj.proto.proto 也没有,那么浏览器会继续查看 obj.proto.proto.proto__
直到找到 toString 或者 proto 为 null。
上面的过程,就是「读」属性的「搜索过程」。
而这个「搜索过程」,是连着由 proto 组成的链子一直走的。
这个链子,就叫做「原型链」。
共享原型现在我们有另一个对象
var obj2 = { name: 'obj2' }如图:
那么 obj.toString 和 obj2.toString 其实是同一个东西,也就是 obj2.proto.toString。
这有什么意义呢?
如果我们改写 obj2.proto.toString,那么 obj.toString 其实也会变!
这样 obj 和 obj2 就是具有某些相同行为的对象,这就是意义所在。
差异化如果我们想让 obj.toString 和 obj2.toString 的行为不同怎么做呢?
直接赋值就好了:
obj.toString = function(){ return ‘新的 toString 方法’ }
总结:
「读」属性时会沿着原型链搜索。
「新增」属性时不会去看原型链(但是如果你给这个属性加了一些配置,则不一样)。
原型链解决的是什么问题1)原型链解决的主要是继承问题
2)每个对象拥有一个原型对象,通过 proto 指针指向其原型对象,并从中继承方法和属性,同时原型对象也可能拥有原型,这样一层一层,最终指向 null(Object.proptotype.__proto__指向的是null)。这种关系被称为原型链(prototype chain),通过原型链一个对象可以拥有定义在其他对象中的属性和方法
3)构造函数 Parent、Parent.prototype 和 实例 p 的关系如下:(p.proto === Parent.prototype)
1)prototype是构造函数的属性
2)__proto__是每个实例都有的属性,可以访问 [[prototype]] 属性
3)实例的__proto__与其构造函数的prototype指向的是同一个对象