首先了解一下创建对象的几种方式,介绍以下三种。
// 第一种方式:字面量
var o1 = {name: 'o1'};
var o2 = new Object({name: 'o2'});
// 第二种方式:构造函数
var M = function (name) { this.name = name; }
var o3 = new M('o3');
// 第三种方式:Object.create
var p = {name: 'p'};
var o4 = Object.create(p);
在上面的例子中o3就是实例,M就是构造函数。从上图中可以知道,实例的__protpo__指向的是构造函数的原型对象。每个对象都有 __proto__ 属性,但只有函数对象才有prototype属性。
使用Object.create()创建关联
var foo = {
something: function() {
console.log('ok');
}
};
var bar = Object.create( foo );
bar.something(); // ok
console.log(bar.__proto__ === foo) // true
Object.create()是ES5新增的函数。Object.create()方法接受两个参数:Object.create(obj,propertiesObject) 。
obj:一个对象,是新创建的对象的原型。
propertiesObject:该参数对象是一组属性与值,该对象的属性名称将是新创建的对象的属性名称,可选。
var o = Object.create(Object.prototype, {
// foo会成为所创建对象的数据属性
foo: {
writable:true,
configurable:true,
value: "hello"
}}),
console.log(o);//{foo:'hello'}
Object.create(null) 创建的对象是一个空对象,在该对象上没有继承 Object.prototype 原型链上的属性或者方法,例如:toString(), hasOwnProperty()等方法。Object.create(null) 适合用来存储字典数据。
var test1 = Object.create(null) ;
console.log(test1);// {} No Properties
Object.create()的polyfill代码
if(!Object.create) {
Object.create = function(o) {
function F() {}
F.prototype = o;
return new F();
}
}
什么是原型链?
简单理解就是原型组成的链,实例的__proto__是构造函数的原型,而原型也是一个对象,也有__proto__属性,就这样可以一直通过__proto__向上找,这就是原型链,当最终找到Object的原型的时候,这条原型链就算到头了。
function Parent() {}
function Child() {}
Child.prototype = new Parent();
var c = new Child();
console.log(c.__proto__ === Child.prototype) // true
console.log(Child.prototype.__proto__ === Parent.prototype) // true
console.log(Parent.prototype.__proto__ === Object.prototype) // true
console.log(Object.prototype.__proto__) // null
使用for...in遍历对象时原理和原型链类似,任何可以通过原型链访问到(enumerable)的属性都会被枚举。
var anotherObj = { a:2 };
// 创建一个关联到anotherObj的对象
var myObj = Object.create(anotherObj);
for(var k in myObj) {
console.log("found: " + k);
}
// found: a
("a" in myObj); // true
instanceof是判断实例对象的_proto__和生成该实例的构造函数的prototype是不是引用的同一个地址,是返回true,否返回false。
注意:实例的instanceof在比较的时候,与原型链上的构造函数相比都是true。
var M = function (name) { this.name = name; }
var o3 = new M('o3')
var o5 = new M()
o3.__proto__.say = function(){
console.log('hello world')
}
o3.say()
o5.say()
只有函数有prototype,对象是没有的。但是函数也是有__proto__的,因为函数也是对象。函数的__proto__指向的是Function.prototype。也就是说普通函数是Function这个构造函数的一个实例。
constructor
function Foo() { /* .. */ }
Foo.prototype = { /* .. */ } // 创建一个新原型对象
var a1 = new Foo();
a1.constructor === Foo; // false!
a1.constructor === Object; // true!
Foo.prototype的constructor属性只是Foo函数在声明时的默认属性。如果创建一个新对象并替换函数默认的.prototype对象引用,那么新对象并不会自动获得.constructor属性。
属性设置和屏蔽
myObj.foo = 'bar';
如果myObj中包含为foo的普通数据访问属性,这条赋值语句只会修改已有的属性值。如果foo不是直接存在于myObj中,原型链就会被遍历,类似[[Get]]操作。如果原型链上找不到foo,foo就会被直接添加到myObj上。
如果foo既出现在myObj中也出现在原型链上层,则会发生屏蔽。myObj中的foo属性会屏蔽原型链上层的所有foo属性,因为myObj.foo总会选择原型链中最底层的foo属性。
如果foo不直接存在于myObj中而存在于原型链上层时,会出现下面三种情况:
- 如果原型链上层存在名为foo的普通数据访问属性并且没有被标记为只读(writable: false),那就会直接在myObj上添加foo属性,它是屏蔽属性。
- 如果原型链上层存在名为foo的普通数据访问属性并且被标记为只读(writable: false),那么无法修改已有属性或创建屏蔽属性。严格模式下会报错。
- 如果原型链上层存在foo并且它是一个setter,那会调用这个setter,foo不会被添加到myObj,也不会重新定义foo。(注:与实际代码不符)
例2
var o = Object.create(Object.prototype, {
foo: {
writable:false,
configurable:true,
value: "hello"
}});
var o2 = Object.create(o);
o2.foo = 123; // 无效
console.log(o2); // {}
例3
var obj2 = {
val:200
};
obj2.__defineGetter__('name',function(){return this.val});
obj2.__defineSetter__('name',function(name){this.val = name;});
var myObj = Object.create(obj2);
myObj.name = '999';
// {val: "999"}
name: "999"
val: "999"
__proto__:
name: "999"
val: 200
get name: ƒ ()
set name: ƒ (name)
__proto__: Object