## **原型** 我们创建的每个函数都有一个 prototype(原型)属性,这个属性是一个指针,指向一个对象, 使用原型对象的好处是可以 让所有对象实例共享它所包含的属性和方法。换句话说,不必在构造函数中定义对象实例的信息,而是 可以将这些信息直接添加到原型对象中,如下面的例子所示 : ~~~ function Person() { } Person.prototype.name = '小四' Person.prototype.age = 24 Person.prototype.sayName = function () { console.log(this.name) } var person1 = new Person() person1.sayName() // 小四 var person2 = new Person() person2.sayName() // 小四 console.log(person1.sayName === person2.sayName); //true ~~~ > 在此,我们将 sayName()方法和所有属性直接添加到了 Person 的 prototype 属性中,构造函数 变成了空函数。即使如此,也仍然可以通过调用构造函数来创建新对象,而且新对象还会具有相同的属 性和方法。但与构造函数模式不同的是,新对象的这些属性和方法是由所有实例共享的。换句话说, person1 和 person2 访问的都是同一组属性和同一个 sayName()函数。 > 原型:去改写对象下面公用的方法或属性,让同样公用的东西在内存当中只存在一份,好处就是提高性能。比较抽象。 学习方法:把原型看做css中的class,普通方法看做css中的style。 style和class比较: style优先级高于class style样式没有办法复用 #### **原型改写示例** ``` var arr = [1,2,3,4,5]; var arr1 = [11,21,31,41,51]; Array.prototype.sum = function () { var result = 0; for (var i=0;i<this.length;i++) { result+=this[i]; } return result; } console.log(arr.sum()); console.log(arr1.sum()); ``` ### **理解原型对象** - 无论什么时候,只要创建了一个新函数,就会根据一组特定的规则为该函数创建一个 prototype 属性,这个属性指向函数的原型对象。在默认情况下,所有原型对象都会自动获得一个 constructor (构造函数)属性,这个属性包含一个指向 prototype 属性所在函数的指针。就拿前面的例子来说, Person.prototype. constructor 指向 Person。而通过这个构造函数,我们还可继续为原型对象 添加其他属性和方法。 - 创建了自定义的构造函数之后,其原型对象默认只会取得 constructor 属性;至于其他方法,则 都是从 Object 继承而来的。当调用构造函数创建一个新实例后,该实例的内部将包含一个指针(内部 属性),指向构造函数的原型对象,就是__proto__(隐式原型对应图中的[[ Prototype ]]); ![](https://i.vgy.me/0QmAK2.png) > 这幅图展示了 Person 构造函数、Person 的原型属性以及 Person 现有的两个实例之间的关系。 在此,Person.prototype指向了原型对象,而Person.prototype.constructor又指回了Person。 原型对象中除了包含 constructor 属性之外,还包括后来添加的其他属性。Person 的每个实例—— person1 和 person2 都包含一个内部属性,该属性仅仅指向了 Person.prototype;换句话说,它们 与构造函数没有直接的关系。此外,要格外注意的是,虽然这两个实例都不包含属性和方法,但我们却 可以调用 person1.sayName()。这是通过查找对象属性的过程来实现的。 ~~~ console.log(person1.__proto__ === Person.prototype) // true ~~~ > 每当代码读取某个对象的某个属性时,都会执行一次搜索,目标是具有给定名字的属性。搜索首先 从对象实例本身开始。如果在实例中找到了具有给定名字的属性,则返回该属性的值;如果没有找到, 则继续搜索指针指向的原型对象,在原型对象中查找具有给定名字的属性。如果在原型对象中找到了这 个属性,则返回该属性的值。也就是说,在我们调用 person1.sayName()的时候,会先后执行两次搜 索。首先,解析器会问:“实例 person1 有 sayName 属性吗?”答:“没有。”然后,它继续搜索,再 问:“person1 的原型有 sayName 属性吗?”答:“有。”于是,它就读取那个保存在原型对象中的函 数。当我们调用 person2.sayName()时,将会重现相同的搜索过程,得到相同的结果。而这正是多个 对象实例共享原型所保存的属性和方法的基本原理。 ### **重写原型中的值** 虽然可以通过对象实例访问保存在原型中的值,但却不能通过对象实例重写原型中的值。如果我们 在实例中添加了一个属性,而该属性与实例原型中的一个属性同名,那我们就在实例中创建该属性,该 属性将会屏蔽原型中的那个属性。来看下面的例子。 ~~~ function Person() { } Person.prototype.name = '小四' Person.prototype.age = 24 Person.prototype.job = "Software Engineer" Person.prototype.sayName = function () { console.log(this.name) } var person1 = new Person() var person2 = new Person() person1.name = "猪八戒"; console.log(person1.name); //"猪八戒"——来自实例 console.log(person2.name); //"小四"——来自原型 ~~~ 在这个例子中,person1 的 name 被一个新值给屏蔽了。但无论访问[person1.name](http://person1.name/)还是访问[person2.name](http://person2.name/)都能够正常地返回值,即分别是"猪八戒"(来自对象实例)和"小四"(来自原型)。 当在 console.log()中访问[person1.name](http://person1.name/)时,需要读取它的值,因此就会在这个实例上搜索一个名为 name 的属性。这个属性确实存在,于是就返回它的值而不必再搜索原型了。当以同样的方式访问 person2. name 时,并没有在实例上发现该属性,因此就会继续搜索原型,结果在那里找到了 name 属性。 当为对象实例添加一个属性时,这个属性就会屏蔽原型对象中保存的同名属性;换句话说,添加这 个属性只会阻止我们访问原型中的那个属性,但不会修改那个属性。即使将这个属性设置为 null,也 只会在实例中设置这个属性,而不会恢复其指向原型的连接。不过,使用 delete 操作符则可以完全删 除实例属性,从而让我们能够重新访问原型中的属性,如下所示。 ~~~ function Person() { } Person.prototype.name = '小四' Person.prototype.age = 24 Person.prototype.job = "Software Engineer" Person.prototype.sayName = function () { console.log(this.name) } var person1 = new Person() var person2 = new Person() person1.name = "猪八戒"; console.log(person1.name); //"猪八戒"——来自实例 console.log(person2.name); //"小四"——来自原型 delete person1.name; console.log(person1.name); // 小四 --- 来自原型 ~~~ > 在这个修改后的例子中,我们使用 delete 操作符删除了[person1.name](http://person1.name/),之前它保存的"小四" 值屏蔽了同名的原型属性。把它删除以后,就恢复了对原型中 name 属性的连接。因此,接下来再调用[person1.name](http://person1.name/)时,返回的就是原型中 name 属性的值了。 #### **提问一下,看看有没有睡觉的** ``` var arr = []; arr.number = 10; Array.prototype.number = 20; console.log(arr.number); // 10 普通方法优先级高于原型方法 ``` > 解读:首先解析器会问:“实例arr上有number属性吗?” 答:“有”,那么就停止查找,,因为实例比构造函数优先级高。