什么时候创建了原型对象
我们创建的每一个函数,不光是构造函数,都会有一个prototype属性,当我们创建了一个函数/声明了一个函数,它就会根据一组特定的规则为该函数创建一个prototype属性指向的对象。
原型对象
原型对象就是我们创建了一个新函数的时候,自动创建的一个对象,当我们仅仅是创建了函数的时候,这个原型对象是这样的:
原型对象是会自动获得一个constructor属性,这个属性指向我们创建的函数。
原型对象还有很多从Object继承来的属性和方法。
但是当我们创建了一个新实例后,该实例内部将包含一个指针,指向构造函数的原型对象~ [[prototype]]
[[prototype]]
这个是一个指针,这指针指向原型对象。 脚本没有标准的方式访问这个[[prototype]],但FireFox Safari Chrome 都支持一个属性称为__proto__
ppt2.__proto__
真正重要的是:连接存在于实例和原型对象之间,而不是实例与构造函数。
Person–> Person.prototype <–person1
构造函数的prototype属性指向原型对象,原型对象的constructor属性又指回了构造函数!
我们访问原型中的方法是通过搜索查找对象属性的过程实现的。
isPrototypeOf
判断某个实例和某个原型的对应关系。
Person.prototype.isPrototypeOf(person1);//true
它就是判断实例的[[prototype]]是否指向调用这个方法的对象。
Object.getPrototypeOf
ES5新方法,用来获取一个实例的原型。
Object.getPrototypeOf(person1)==Person.prototype;
返回的原型,可以访问这个原型中的属性方法值。
我们在使用原型继承的时候会用到这个方法。
原型链
原理: 我们每次读取某个对象的某个属性时,都会执行一次搜索,目标是具有给定名字的属性,从对象的这个当前实例开始,如果找到这个属性就停止。
否则继续搜索它的原型对象,如果搜索到了属性就停止。仍然搜索不到,就搜索这个原型对象的上一级的原型对象,直到Object.prototype,这个是所有对象的根原型对象,它的上一级是null。
弊端
我们不能通过对象实例重写原型中的属性。
只能是覆盖 屏蔽,因为原型链搜索会因此停止。
它搜索到同名的属性就不会继续搜索了。
delete 这个属性,可以恢复原型链的搜索
使用hasOwnProperty
这个方法可以区分 原型链上的属性和 实例的属性
实例属性才返回true
原型与for in 与 in操作符
for in循环返回的是包括原型链上的所有属性!
用hasOwnProperty可以返回所有实例属性
如果要判断所有原型链上的属性呢?
function hasProtoProperty(object,name){
return !object.hasOwnProperty(name)&&(name in object);
}
还有就是for - in 循环中的所有属性是能过通过对象访问,并且是可枚举的属性!!!也就是说有些浏览器内置的属性无法访问,Object.defineProperty改为不可枚举的属性也是。
但是 in 操作符
判断这个属性是否存在,不管是否是可枚举的,只要是原型链或实例属性中存在!
BUG
但是一般定义的属性都是可枚举的,IE8及更早的除外。
ES5将constructor和prototype属性的 [[Enumerable]]设置为false~!
取得对象上所有可枚举实例属性
Object.keys()方法。
仅仅返回所有可枚举的实例属性
var p1=new Person();
p1.name='sd';
pa.age=23;
var keys=Object.keys(p1);
//name age
如果返回所有实例属性不管是否可枚举:
var keys=Object.getOwnPropertyNames(Person.prototype);
你可以把此处的Person.prototype理解为一个它上级原型对象的实例属性,它返回//constructor ,name,age
也就是说把不可枚举的属性也包含了
ES5两个新方法 IE9+
原型的动态性
任何对原型对象的修改都会在实例对象上立即反映出来。
JS多态的天然性!!!!
创建了实例之后,再修改原型!!!也是一样反映
实质原因: 原型与实例的连接不过是一个指针,而不是一个副本
关键还是搜索原型链这个机制!!!
function Person(name,age,job){
this.name=name;
this.age=age;
this.job=job;
}
var friend =new Person();;
Person.prototype={
constuctor:Person,
name:"Nicolas",
age:29,
friend:["SH","WW"],
job:"Soft Engineer"
sayName:function(){
}
};
如果我们修改了能够立即在所有对象实例中反映出来
上面我们让friend的原型对象为Person这个构造函数的prototype,但是后面我们又更新了这个Person的原型对象。重写了原型对象,这样就切断了和friend这个实例的联系。
friend指向的是原来的,旧的那个原型对象!
重点:实例中的指针仅指向原型,而不指向构造函数
原型的问题
对于共享的引用类型来说,改变其中一个,就会改变所有实例的这个属性。
属性设置和屏蔽
前面说过给实例属性赋值,会屏蔽同名的原型中的属性。即屏蔽~ 类似的[[GET]]操作总是选择最底层的属性
对于实例中不存在的属性,而在原型链中存在的话
执行这个语句 myobj.foo=”bar”; (添加屏蔽属性)
其实有三种情况
前提都是 如果在原型链上有这个属性~
- 如果这个属性没有被标记为writable:false,只读的,那么就会在这个对象实例中添加这个属性,它是屏蔽属性
- 如果在[[prototype]]链上层存在这个属性,但它只是被标记为只读,writable:false,那么无法修改已有属性或在当前对象上创建屏蔽属性。 严格模式下报错
- 如果它是一个setter,那么就一定会调用这个setter
foo不会被添加到myobj,也不会重新定义foo这个setter
如果你希望第二第三种情况下也能屏蔽这个foo属性,就要使用Object.defineProperty也不是 =
NEW操作与原型
function Foo(){}
var a=new Foo();
Object.getPrototypeOf(a)===Foo.prototype; //true
new操作会产生一个对象a,其中一步就是将a内部的[[prototype]]链接到Foo.prototype所指向的对象。
面向类的语言在那个,类可以被实例化多次,就像模具制作。(类把行为复制到物理对象中,对每个对象来说都是这样)而JS,没有类似的复制机制。你只能创建多个对象,但是它们的[[prototype]]关联的是同一个对象。
我们通过原型把对象关联起来,但是仅仅是关联起来,不会复制,只是创建了一个关联。
本质来说
在JS中,我们没有构造函数,关键是 new调用它,会劫持这个普通函数并用构造对象的形式来调用它~!
function Nothing(){
console.log('sss');
}
var a=new Nothing();
//a {}
a是一个空对象,Nothing是一个普通的函数,但是被new调用的时候,它就会构造一个对象并赋值给a。这看起来像是new的一个副作用。
constructor的问题
之前讨论constructor属性时,其实对constructor的理解有点问题,因为我们可能认为这个属性指向的函数是对象的构造函数。 这么理解确实很容易。
constructor会被隔断联系
Foo.prototype的.constructor 属性只是Foo函数在声明时的默认属性。如果你创建了一个新对象并替换了函数默认的.prototype对象引用,那么新对象不会自动获得constructor属性。
function Foo(){}
Foo.prototype={
//?
};//创建一个新的原型对象
var a1=new Foo();
a1.constructor===Foo; //false
a1.constructor===Object; //true!
这个新建的对象a1,看起来是用Foo构造的,但是呢,从上面的例子看出来,a1.constructor却不是Foo。
其实a1并没有constructor属性,这个属性是委托[[prototype]]链上的Foo.prototype来查找的!
但是它上面也没有constructor属性(默认的Foo.prototype有这个属性)
可以看出来,我们如果在声明了
function Foo(){} 后去重写它的prototype,那么constructor属性就会消失了
如何修复?
Object.defineProperty(Foo.prototype,"constructor",{
enumerable:false,
writable:true,
configurable:true,
value:Foo //让这个.constructor指向FOO
})
实际上对象的.constructor会默认指向一个函数,这个函数可以通过对象的.prototype引用!!
constructor并不是一个不可变的属性,它是不可枚举的,但是它的值可写。
所以说这个属性是非常随意的,不可靠,不安全的引用。
我们要尽量避免使用这些引用!
它的行为非常难以捕捉,我们可以随时修改,所以一般不要用这个引用。