JavaScript的对象和原型链

2019-03-30  本文已影响0人  司超

首先了解一下创建对象的几种方式,介绍以下三种。

// 第一种方式:字面量
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);
原型链1.png

在上面的例子中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原理.png

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中而存在于原型链上层时,会出现下面三种情况:

  1. 如果原型链上层存在名为foo的普通数据访问属性并且没有被标记为只读(writable: false),那就会直接在myObj上添加foo属性,它是屏蔽属性。
  2. 如果原型链上层存在名为foo的普通数据访问属性并且被标记为只读(writable: false),那么无法修改已有属性或创建屏蔽属性。严格模式下会报错。
  3. 如果原型链上层存在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
上一篇 下一篇

猜你喜欢

热点阅读