原型[[Prototype]]
JavaScript 中的对象有一个特殊的 [[Prototype]] 内置属性,其实就是对于其他对象的引用。几乎所有的对象在创建时 [[Prototype]] 属性都会被赋予一个非空的值。
注意:对象的 [[Prototype]] 链接当然可以为空,虽然很少见。
定义
1.原型是 function 对象的一个属性,它定义了构造函数制造出的对象的公共祖
先。通过该构造函数产生的对象,可以继承该原型的属性和方法。原型也是对象。
2.利用原型特点和概念,可以提取共有属性。
3.对象属性的增删和原型上属性增删改查。
4.对象如何查看原型 ==> 隐式属性 __proto__。
5.对象如何查看对象的构造函数 ==> constructor。
看看下面代码,思考思考:
var myObject = {
a : 2
}
myObject.a //2
当你引用对象属性的时,例如myObject.a的时候,第一步是看看myObject对象有没有一个叫a的属性,如果有那么就返回值,如果没有就上该对象的原型链上去找。
var anotherObject = {
a:2
};
// 创建一个关联到 anotherObject 的对象
var myObject = Object.create( anotherObject );
myObject.a; // 2
注:Object.create()函数的作用是:创建一个对象并指定该对象的原型
现在 myObject 对象的 [[Prototype]] 关联到了 anotherObject。显然 myObject.a 并不存在,但是尽管如此,属性访问仍然成功地(在 anotherObject 中)找到了值 2。但是,如果 anotherObject 中也找不到 a 并且 [[Prototype]] 链不为空的话,就会继续查找下去。这个过程会持续到找到匹配的属性名或者查找完整条 [[Prototype]] 链。如果没找到,[[Get]] 操作的返回值是 undefined。
注:[[Prototype]]就是原型 !!!
原型链
下面要和大家说一个比较重要的概念就是原型链,原型原型是一个对象,那么这个对象也会有原型,这个对象的原型也是对象,那么也会有一个原型对象。这样就形参了一种线下结构,我们称之为原型链。找一个对象的属性时,如果该对象没有,那么就顺着该原型链往上找。返回原型链上最近的那个属性。那么原型链有终点吗?肯定时
Object.prototype
到哪里是 [[Prototype]](原型链) 的“尽头”呢?
所有普通的 [[Prototype]] 链最终都会指向内置的 Object.prototype。由于所有的“普通”(内置,不是特定主机的扩展)对象都“源于”(或者说把 [[Prototype]] 链的顶端设置为)这个 Object.prototype 对象,所以它包含 JavaScript 中许多通用的功能。有 些 功 能 你 应 该 已 经 很 熟 悉 了, 比 如 说 .toString() 和 .valueOf()。
属性设置和屏蔽
myObject.foo = "bar"
如果 myObject 对象中包含名为 foo 的普通数据访问属性,这条赋值语句只会修改已有的属性值。如果 foo 不是直接存在于 myObject 中,[[Prototype]] 链就会被遍历。如果原型链上找不到 foo,foo 就会被直接添加到 myObject 上。
然而,如果 foo 存在于原型链上层,赋值语句 myObject.foo = "bar" 的行为就会有些不同(而且可能很出人意料)。稍后我们会进行介绍。如果属性名 foo 既出现在 myObject 中也出现在 myObject 的 [[Prototype]] 链上层,那么就会发生屏蔽。myObject 中包含的 foo 属性会屏蔽原型链上层的所有 foo 属性,因为myObject.foo 总是会选择原型链中最底层的 foo 属性。屏蔽比我们想象中更加复杂。
下面我们分析一下如果 foo 不直接存在于 myObject 中而是存在于原型链上层时 myObject.foo = "bar" 会出现的三种情况:
1. 如果在 [[Prototype]] 链上层存在名为 foo 的普通数据访问属性并且没有被标记为只读(writable:false),那就会直接在 myObject 中添加一个名为 foo 的新属性,它是屏蔽属性。
2. 如果在 [[Prototype]] 链上层存在 foo,但是它被标记为只读(writable:false),那么无法修改已有属性或者在 myObject 上创建屏蔽属性。如果运行在严格模式下,代码会抛出一个错误。否则,这条赋值语句会被忽略。总之,不会发生屏蔽。
3. 如果在 [[Prototype]] 链上层存在 foo 并且它是一个 setter,那就一定会调用这个 setter。foo 不会被添加到(或者说屏蔽于)myObject,也不会重新定义 foo 这个 setter。
大多数开发者都认为如果向 [[Prototype]] 链上层已经存在的属性([[Put]])赋值,就一定会触发屏蔽,但是如你所见,三种情况中只有一种(第一种)是这样的。
有些情况下会隐式产生屏蔽,一定要当心。思考下面的代码:
var anotherObject = {
a:2
};
var myObject = Object.create( anotherObject );
anotherObject.a; // 2
myObject.a; // 2
//hasOwnProperty()函数的作用,是判断该对象有没有指向字符串同名的属性,返回Boolean值
anotherObject.hasOwnProperty( "a" ); // true
myObject.hasOwnProperty( "a" ); // false
myObject.a++; // 隐式屏蔽!
anotherObject.a; // 2
myObject.a; // 3
myObject.hasOwnProperty( "a" ); // true
尽管 myObject.a++ 看起来应该(通过委托)查找并增加 anotherObject.a 属性,但是别忘了 ++ 操作相当于 myObject.a = myObject.a + 1。因此 ++ 操作首先会通过 [[Prototype]]查找属性 a 并从 anotherObject.a 获取当前属性值 2,然后给这个值加 1,接着用 [[Put]]将值 3 赋给 myObject 中新建的屏蔽属性 a,天呐!
修改委托属性时一定要小心。如果想让 anotherObject.a 的值增加,唯一的办法是
anotherObject.a++。
构造函数&原型
我们知道对象有一种构建方法是 new 函数名() 来构建一个对象,今天我就讲讲它有什么门道。
首先先看看下面的代码:
function Foo() {
// 若干行代码
}
Foo.prototype; // { }
Foo.prototype就是Foo函数的原型对象,当我们new Foo()创建对象时,该被创建的对象的原型就是Foo.prototype指向的那个对象
上代码,自古dome得人心
function Foo() {
// 若干行代码
}
var a = new Foo();
//Object.getPrototypeOf用来获取a的原型对象,这里是判断a的原型是不是Foo的原型
Object.getPrototypeOf( a ) === Foo.prototype; // true
Foo.prototype的另一个绝招
function Foo() {
// 若干行代码
}
Foo.prototype.constructor === Foo; // true
var a = new Foo();
a.constructor === Foo; // true
这个constructor这个属性在prototype对象中可以找到这个属性。它的作用是引用对象关联的函数(本例中是Foo)。此外此外,
可以看到通过“构造函数”调用 new Foo() 创建的对象也有一个 .constructor 属性,指向 “创建这个对象的函数”。
特批注意:constructor并不一定指向该对象的构造函数,不要被误解了,代价是很大滴
如何查看对象的原型
对象有一个隐式属性 __proto__它就是原型
浅粉色的__proto__是系统的隐式的属性,前面两个_后面两个_,可以修改,尽量不改。
在开发的时候,如果很私人可以写成_private,告诉同事别动。
上面的__proto__放的是原型。__proto__存的对象的原型
上面的 var this ={__proto__:person.prototype};这个对象并不是空的,这个 proto,当你
访问这个对象的属性时,如果对象没有这个属性,那么就会访问 proto 索引,看看有
没有。有一个连接的关系,原型和自己连接到一起
最后推荐一本书,深入学习js有极大帮助:注:该书是一个系列,分为上下中卷。