原型----《你不知道的js》

1、[[Prototype]]

js中的对象有一个特殊的[[Prototype]]内置属性,即对于其余对象的引用。几乎全部的对象在建立时[[Prototype]]属性都会被赋予一个非空值。对象的[[Prototype]]连接能够为空,但不多见。浏览器

var myObject = {
    a:2
};
myObject.a; // 2
复制代码

当你试图引用对象的属性时会触发[[Get]]操做,如myObject.a。对于默认的[[Get]]操做来讲,第一步是检查对象自己是否有这个属性,如有就使用。bash

若是a不在myObject中,就须要使用对象的[[Prototype]]链了。函数

对于默认的[[Get]]操做来讲,若是没法在对象自己找到须要的属性,就会继续访问[[Prototype]]链:性能

var anotherObject = {
    a:2
};
// 建立一个关联到anotherObject的对象
var myObject = Object.create(anotherObject);
myObject.a; // 2
复制代码

myObject对象的[[Prototype]]关联到了anotherObject。myObject.a并不存在,但属性能在anotherObject中找到2。ui

若是anotherObject也找不到a而且[[Prototype]]不为空,则继续查找下去。this

这个过程会持续到找到匹配的属性名或查找完整条[[Prototype]]链。如果后者[[Get]]操做返回undefined。spa

使用for..in遍历对象时原理和查找[[Prototype]]链相似,任何能够经过原型链访问到的属性都会被枚举。使用in操做符来检查属性在对象中是否存在,一样会查找对象的整条原型链(不管对象是否可枚举):prototype

var anotherObject = {
    a:2
};
// 建立一个关联到anotherObject的对象
var myObject = Object.create(anotherObject);
for (var k in myObject) {
    console.log("found: " + k);
}
// found: a
("a" in myObject); // true
复制代码

一、Object.prototype

全部普通的[[Prototype]]链最终对吼指向内置的Object.prototype。因为全部“普通”(内置)对象都“源于”Object.prototype,因此它包含了js中许多通用功能日志

二、属性设置和屏蔽

myObject.foo = "far";
复制代码

若是myObject对象包含名为foo的普通数据访问属性,这条赋值语句只会修改已有的属性值。code

若是foo不是直接存在于myObject中[[Prototype]]链就会被遍历,相似[[Get]]操做。若是原型链上找不到foo,foo就会被直接添加到myObject上。

若是foo存在于原型链上层,赋值语句myObject.foo = "bar"的行为就会有些不一样。

若是属性名foo既出如今myObject的[[Prototype]]链上层,则会发横屏蔽。myObject中包含的foo属性会屏蔽原型链上层的全部foo属性,由于myObject.foo老是会选择原型链中最底层的foo属性。

下面直接分析若是foo不直接存在于myObject中而是存在于原型链上层时myObject.foo = "bar"会出现的三种状况。

一、若是在[[Prototype]]链上层存在名为foo的普通数据访问属性而且没有被标记为只读(writable:false)则直接在myObject中添加一个名为foo的属性,它是屏蔽属性。

二、若是在[[Prototype]]链上层存在foo,但被标记为只读(writable:false),则没法需改已有属性或在myObject上建立屏蔽属性。若是在严格模式下,代码则会跑出一个错误。不然,这条赋值语句会被忽略。总之,不会发生屏蔽。

三、若是在[[Prototype]]链上层存在foo而且它是一个setter,则会调用这个setter。foo不会被添加到或屏蔽于myObject,也不会从新定义foo这个setter。

若是但愿在第二种第三种状况下也屏蔽foo,就不能用=操做符来赋值,而使用Object.defineProperty(..)来向myObject添加foo。

一般应当尽可能避免使用屏蔽

有些状况下会隐式产生屏蔽:

var anotherObject = {
    a:2
};
var myObject = Object.create(anotherObject);
anotherObject.a; // 2
myObject.a; // 2
anotherObject.hasOwnProperty("a"); // true
myObject.a++; // 隐式屏蔽
anotherObject.a; // 2
myObject.a; // 3
myObject.hasOwnProperty("a"); // true
复制代码

++操做首先会经过[[Prototype]]查找属性a并从anotherObject.a获取当前属性值2,而后给这个值加1,接着用[[Put]]将值3赋给myObject中新建的屏蔽属性a

2、“类”

一、“类”函数

全部的函数默认都会拥有一个名为prototype的共有而且不可枚举的属性,他会指向另外一个对象:

function Foo() {
    // ...
}
Foo.prototype; // {}
复制代码

这个对象一般被称为Foo的原型。这个对象是在调用new Foo()时建立的,最后会被关联到“Foo.prototype”对象上

function Foo() {
    // ...
}
var a = new Foo();
Object.getPrototypeOf(a) === Foo.prototype; // true
复制代码

调用new Foo()时会建立a,其中一步就是将a内部的[[Prototype]]连接到Foo.prototype所指向的对象。

二、“构造函数”

function Foo() {
    // ...
}
// constructor公有不可枚举的属性
Foo.prototype.constructor === Foo; // true
var a = new Foo();
a.constructor === Foo; // true
复制代码

a自己没有constructor属性,虽然a.constructor确实指向Foo函数,可是这个属性并非表示a由Foo“构造”

实际上.constructor引用一样被委托给了Foo.prototype,而Foo.prototype.constructor默认指向Foo。

a.constructor只是经过默认的[[Prototype]]委托指向Foo,这和“构造”毫无关系。

function Foo() { /* .. */ }
Foo.prototype = { /* .. */ }; //建立一个新原型对象
var a1 = new Foo();
a1.constructor === Foo; // false
a1.constructor === Object; // true
复制代码

a1并无.constructor属性,因此它会委托[[Prototype]]链上的Foo.prototype。但这个对象也没有.constructor属性(不过默认的Foo.prototype对象有这个属性),因此它会继续委托,此次会委托给委托链顶端的Object.prototype。这个对象有.constructor属性,指向内置的Object(..)函数

固然你能够给Foo.prototype添加一个.constructor属性,不过须要手动添加一个符合正常行为不可枚举的属性。

function Foo() { /* .. */ }
Foo.prototype = { /* .. */ }; //建立一个新原型对象
// 须要在Foo.prototype上“修复”丢失的.constructor属性
// 新对象属性起到Foo.prototype的做用
// 关于defineProperty(..)
Object.defineProperty( Foo.prototype, "constructor", {
    enumerable: false,
    writable: true,
    configurable: true,
    value: Foo // 让.constructor指向Foo
})
复制代码

.constructor并不表示被构造,也不是一个不可变属性。他是不可枚举的,但值是可写的

1)构造函数仍是调用

function NothingSpecial() {
    console.log("Don't mind me!");
}
var a = new NothingSpecial();
// Don't mind me! a; // {} 复制代码

NothingSpecial只是一个普通的函数,但使用new调用时,就会构造一个对象并赋值给a。这个调用时一个构造函数调用,但NothingSpecial自己不是构造函数

函数不是构造函数,当且仅当使用new时,函数调用会变成“构造函数调用”

三、技术

function Foo(name) {
    this.name = name;
}
Foo.prototype.myName = function() {
    return this.name;
}
var a = new Foo("a");
var b = new Foo("b");
a.myName(); // "a"
b.myName(); // "b"
复制代码

这段diamante展现了另外两种“面向类”的技巧:

一、this.name = name给每一个对象,即a,b都添加了.name属性

二、Foo.prototype.myName = ...会给Foo.prototype对象添加一个属性(函数)

在建立过程当中,a、b的内部[[Prototype]]都会关联到Foo.prototype上。当a、b中没法找到myName时,它会经过委托在Foo.prototype上找到

3、(原型)继承

function Foo(name) {
    this.name = name;
}
Foo.prototype.myName = function() {
    return this.name;
}
function Bar(name, label) {
    Foo.call(this, name);
}
// 建立一个新的Bar.prototype对象并关联到Foo.prototype
Bar.prototype = Object.create(Foo.prototype); // 核心部分

// 注意如今没有Bar.prototype.constructor了
// 若你须要则手动修复
Bar.prototype.myLabel = function() {
    return this.label;
}
var a = new Bar("a", "obj a");
a.myName(); // "a"
a.myLabel(); // 'obj a'
复制代码

建立一个新对象并把它关联到咱们但愿的对象上有两种常见的作法:

// 和你想要的机制不同
Bar.prototype = Foo.prototype;
复制代码

此代码让Bar.prototype直接引用Foo.prototype对象。所以当你执行类型Bar.prototype.myLabel = ... 的赋值语句时会修改Foo.prototype对象自己

// 基本上知足你的需求,但可能会产生一些反作用
Bar.prototype = new Foo();
复制代码

此语句会建立一个关联到Bar.prototype的新对象。但使用了Foo(..)的构造函数调用。若是函数Foo有一些反作用(如:写日志、修改转态等)就会影响到Bar()的“后代”,后果不堪设想

要建立一个合适的关联对象,最好使用Object.create(..)而不是具备徐做用的Foo(..)。但这须要建立一个新对象而后把旧对象抛弃掉,不能直接修改已有的默认对象。

// ES6以前须要抛弃默认的Bar.prototype
Bar.prototype = Object.create(Foo.prototype); 
// ES6能够直接修改现有的Bar.prototype
Object.setPrototypeOf(Bar.prototype, Foo.prototype);
复制代码

检查“类”关系

内省(反射):检查一个实例的继承祖先(js的委托关联)

function Foo() {
    // ...
}
Foo.prototype.blah = ...;
var a = new Foo();
复制代码

如何经过内省找出a的“祖先”(委托关联)

1)a instanceof Foo; 在a的整条[[Prototype]]链中是否有指向Foo.prototype的对象

缺点:只能处理对象(a)和函数(带.prototype引用的Foo)之间的关系

若是使用内置的.bind(..)函数来生成一个硬绑定函数的话,该函数没有.prototype属性。在这样的函数上使用instanceof的话目标函数的.prototype会代替硬绑定函数的.prototype。

function isRelated(o1, o2) {
    function F() {}
    F.prototype = o2;
    return o1 instanceof F;
}
var a = {};
var b = Object.create(a);
isRelated(b, a); // true
复制代码

2)判断[[Prototype]]反射的方法

Foo.prototype.isPrototypeOf(a); // true
复制代码

在a的整条[[Prototype]]链,中是否出现过Foo.prototype

此方法不须要间接引用函数(Foo),它的.prototype属性会被自动访问

// 很是简单:b是否出如今c的[[Prototype]]链中
b.isPrototypeOf(c);
复制代码

这个方法并不须要使用函数(“类”),它直接使用b和c之间的对象引用来判断他们的关系。换而言之,语言内置的isPrototypeOf(..)函数就是咱们的isRelatedTo(..)函数

咱们也能够直接获取一个对象[[Prototype]]链,在ES5中标准方法:

Object.getPrototypeOf(a);
Object.getPrototypeOf(a) === Foo.prototype; // true
a.__proto__ === Foo.prototype; // true  大多数浏览器也支持这种方法
复制代码

.__proto__存在于内置的Object.prototype中(不可枚举)

Object.defineProperty(Object.prototype, "__proto__", {
    get: function() {
        return Object.getPrototypeOf(this);
    },
    set: function(o) {
        Object.setPrototypeOf(this, o);
        return 0;
    }
})
复制代码

所以,访问(获取值)a.__proto__时,其实是调用了a.proto()(调用getter函数)。虽然getter函数存在于Object.prototype对象中,但它的this指向对象a,因此Object.getPrototypeOf(a)结果相同。

.__proto__是可设置属性,但一般来讲不须要修改已有对象的[[Prototype]],。

在特殊状况下须要设置函数默认.prototype对象的[[Prototype]],让它引用其余对象(除了Object.prototype)这样可避免使用全新对象替换默认对象。此外最好把[[Prototype]]对象关联看作是只读特性,从而增长代码可读性

4、对象关联

一、建立关联

var foo = {
    something:function() {
        console.log("Tell me sometihng good ...");
    }
};
var bar = Object.create(foo);
bar.something(); // Tell me sometihng good ...
复制代码

Object.create(..)会建立一个新对象(bar)并把它关联到咱们指定的对象(foo),这样能够避免没必要要的麻烦。

Object.create(..)的polyfill代码

Object.create(..)是在ES5中新增的函数,因此在ES5以前的环境中要支持这个功能的话就须要使用一段简单的polyfill代码。

if (!Object.create) {
    Object.create = function(o) {
        function F(){}
        F.prototype = o;
        return new F();
    }
}
复制代码

这段polyfill代码使用了一个一次性函数F,咱们经过改写它的.prototype属性使其指向想要关联的对象,而后再使用new F()来构造一个新对象进行关联。