多应用+插件架构,代码干净,二开方便,首家独创一键云编译技术,文档视频完善,免费商用码云13.8K 广告
[TOC] # 原型链类 当谈到继承时,JavaScript 只有一种结构:对象。每个实例对象(object)都有一个私有属性(称之为`__proto__`)指向它的原型对象(`prototype`)。该原型对象也有一个自己的原型对象(`__proto__`) ,层层向上直到一个对象的原型对象为 null。根据定义,null 没有原型,并作为这个原型链中的最后一个环节。 几乎所有 JavaScript 中的对象都是位于原型链顶端的Object的实例。 <br> <br> ## 创建对象有几种方法 ### 字面量 ~~~ var o1 = {name: 'o1'}; // Object {name: "o1"} ~~~ <br> ### 构造函数 ~~~ var o2 = new Object({name: 'o2'}); // Object {name: "o1"} var M = function (name) { this.name = name; }; var o3 = new M('o3'); // M {name: "o3"} ~~~ <br> ### Object.create ~~~ var p = {name: 'p'}; var o4 = Object.create(p); // o4 {} ~~~ <br> ## 原型 ### prototype prototype属性,别忘了一点,就是我们前面提到要牢记的两点中的第二点,它是函数所独有的,它是从一个函数指向一个对象。它的含义是函数的原型对象,也就是这个函数(其实所有函数都可以作为构造函数)所创建的实例的原型对象,由此可知:f1.__proto__ === Foo.prototype,它们两个完全一样。那prototype属性的作用又是什么呢?它的作用就是包含可以由特定类型的所有实例共享的属性和方法,也就是让该函数所实例化的对象们都可以找到公用的属性和方法。任何函数在创建的时候,其实会默认同时创建该函数的prototype对象。 > prototype是函数才会有的属性 ~~~ function Person() { } Person.prototype.name = 'Kevin'; var person1 = new Person(); var person2 = new Person(); console.log(person1.name) // Kevin console.log(person2.name) // Kevin ~~~ <br> <br> ![](https://img.kancloud.cn/9e/54/9e54d8115a4886bc710e8d8547932899_1761x968.png) <br> <br> ### `__proto__` 这是每一个JavaScript对象(除了 null )都具有的一个属性,叫__proto__,这个属性会指向该对象的原型。 ~~~ function Person() { } var person = new Person(); console.log(person.__proto__ === Person.prototype); // true ~~~ <br> ![](https://img.kancloud.cn/fb/36/fb368aba91b6e44c57e528100cb2126c_1790x952.png) <br> > 第一,这里我们仅留下 __proto__ 属性,它是对象所独有的,可以看到__proto__属性都是由一个对象指向一个对象,即指向它们的原型对象(也可以理解为父对象),那么这个属性的作用是什么呢?它的作用就是当访问一个对象的属性时,如果该对象内部不存在这个属性,那么就会去它的__proto__属性所指向的那个对象(可以理解为父对象)里找,如果父对象也不存在这个属性,则继续往父对象的__proto__属性所指向的那个对象(可以理解为爷爷对象)里找,如果还没找到,则继续往上找…直到原型链顶端null,再往上找就相当于在null上取值,会报错(可以理解为,再往上就已经不是“人”的范畴了,找不到了,到此结束,null为原型链的终点),由以上这种通过__proto__属性来连接对象直到null的一条链即为我们所谓的原型链。 其实我们平时调用的字符串方法、数组方法、对象方法、函数方法等都是靠__proto__继承而来的。 <br> <br> ### constructor constructor属性也是对象才拥有的,它是从一个对象指向一个函数,含义就是指向该对象的构造函数,每个对象都有构造函数(本身拥有或继承而来,继承而来的要结合__proto__属性查看会更清楚点,如下图所示),从上图中可以看出Function这个对象比较特殊,它的构造函数就是它自己(因为Function可以看成是一个函数,也可以是一个对象),**所有函数和对象最终都是由Function构造函数得来,所以constructor属性的终点就是Function这个函数**。 ~~~ function Person() { } var person = new Person(); console.log(Person === Person.prototype.constructor); // true console.log(Person === person.constructor); // true ~~~ 当获取 person.constructor 时,其实 person 中并没有 constructor 属性,当不能读取到constructor 属性时,会从 person 的原型也就是 Person.prototype 中读取,正好原型中有该属性。 <br> ![](https://img.kancloud.cn/e8/1c/e81c502c9d50cd3d0d62ccc4719acc0b_1736x945.png) <br> ![](https://img.kancloud.cn/65/52/6552747f5dae4eeee59e8e75ada6b662_1040x603.png) <br> ### 实例与原型 当读取**实例**的属性时,如果找不到,就会查找与对象关联的**原型**中的属性,如果还查不到,就去找**原型的原型**,一直找到最顶层为止。 ~~~ function Person() { } Person.prototype.name = 'Kevin'; var person = new Person(); person.name = 'Daisy'; console.log(person.name) // Daisy delete person.name; console.log(person.name) // Kevin ~~~ <br> 在这个例子中,我们给实例对象 `person` 添加了 `name` 属性,当我们打印 `person.name` 的时候,结果自然为 `Daisy`。 <br> 但是当我们删除了` person` 的 `name` 属性时,读取 `person.name`,从 `person` 对象中找不到 `name` 属性就会从 `person` 的原型也就是 `person.__proto__ `,也就是 `Person.prototype`中查找,幸运的是我们找到了 `name` 属性,结果为 `Kevin`。 <br> <br> ### 原型的原型 在前面,我们已经讲了原型也是一个对象,既然是对象,我们就可以用最原始的方式创建它,那就是: ~~~ var obj = new Object(); obj.name = 'Kevin' console.log(obj.name) // Kevin ~~~ 其实原型对象就是通过 `Object` 构造函数生成的,结合之前所讲,实例的 `__proto__` 指向构造函数的 `prototype` ,所以我们再更新下关系图: <br> ![](images/screenshot_1545753039539.png) <br> <br> ### 原型链 `Object.prototype.__proto__` 的值为 `null` 即 `Object.prototype` 没有原型,所以查找属性的时候查到 `Object.prototype` 就可以停止查找了。 <br> ~~~ console.log(Object.prototype.__proto__ === null) // true ~~~ <br> 图中由相互关联的原型组成的链状结构就是原型链,也就是蓝色的线。 <br> ![](https://box.kancloud.cn/d5145b0d9fc5e529fa9a7b035eaf4d1a_1024x272.png) <br> <br> ### 总结 1. 我们需要牢记两点:①__proto__和constructor属性是对象所独有的;② prototype属性是函数所独有的,因为函数也是一种对象,所以函数也拥有__proto__和constructor属性。 2. __proto__属性的作用就是当访问一个对象的属性时,如果该对象内部不存在这个属性,那么就会去它的__proto__属性所指向的那个对象(父对象)里找,一直找,直到__proto__属性的终点null,再往上找就相当于在null上取值,会报错。通过__proto__属性将对象连接起来的这条链路即我们所谓的原型链。 3. prototype属性的作用就是让该函数所实例化的对象们都可以找到公用的属性和方法,即f1.__proto__ === Foo.prototype。 4. constructor属性的含义就是指向该对象的构造函数,所有函数(此时看成对象了)最终的构造函数都指向Function <br> <br> ## instanceof ### 概念 `instanceof` 运算符用于测试 **构造函数** 的 `prototype` 属性是否出现在对象的原型链中的**任何位置** ~~~ function M() {} var obj = new M() obj.__proto__ === M.prototype // true obj instanceof M // true M.prototype.__proto__ === Object.prototype // true M instanceof Object // true ~~~ <br> <br> ~~~ // 定义构造函数 function C(){} function D(){} var o = new C(); o instanceof C; // true,因为 Object.getPrototypeOf(o) === C.prototype o instanceof D; // false,因为 D.prototype不在o的原型链上 o instanceof Object; // true,因为Object.prototype.isPrototypeOf(o)返回true C.prototype instanceof Object // true,同上 C.prototype = {}; var o2 = new C(); o2 instanceof C; // true o instanceof C; // false,C.prototype指向了一个空对象,这个空对象不在o的原型链上. D.prototype = new C(); // 继承 var o3 = new D(); o3 instanceof D; // true o3 instanceof C; // true 因为C.prototype现在在o3的原型链上 ~~~ <br> ![](https://box.kancloud.cn/3ee04ff512a68800c014b66de0f6b3f2_488x590.png) ### 判断对象是否是某个构造函数的实例 ~~~ function M() {} var obj = new M() obj.__proto__.constructor === M //true obj.__proto__.constructor === Object //true ~~~ <br> <br> # 参考资料 * [JavaScript深入之从原型到原型链](https://github.com/mqyqingfeng/Blog/issues/2) * [继承与原型链 | MDN](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Inheritance_and_the_prototype_chain) * [instanceof | MDN](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Operators/instanceof) * [深入理解javascript原型和闭包(5)——instanceof](https://www.cnblogs.com/wangfupeng1988/p/3979533.html) * [帮你彻底搞懂JS中的prototype、\_\_proto\_\_与constructor(图解)](https://blog.csdn.net/cc18868876837/article/details/81211729)