续上一集内容,经过构造函数的方式,成功地更新了生产技术,老板笑呵呵,工人少奔波,只是问题总比办法多,又遇到一个新问题,就是会形成一些资源的重复和浪费,那么通过工程师们的智慧交流,他们产生了一个新技术,原型模式。javascript
function Food() {} Food.prototype.name = "苹果"; Food.prototype.sayName = function() { console.log("我是" + this.name); }; var food1 = new Food(); food1.sayName(); var food2 = new Food(); food2.sayName(); // 建立无限多的 food 。。。。。。 console.log(food1.sayName == food2.sayName); // 返回 true
sayName
方法都放到原型Food
的原型上去new
来建立这样就完成了原型模式的使用了,可以将函数进行共享,不用每次都重复建立不一样的函数实例了,并且全部的属性共享,也可以很方便节省代码和简化结构,其余人也能够很方便地进行使用。html
可是比较懵逼,为何这样就能够了呢?原型是个什么东西?怎么起做用的呢?java
在《javascript 高级程序设计》里面是这样说的,咱们建立的每一个函数都有一个 prototype
,这个属性是一个内存指针,指向一个对象,而这个对象的用途是包含能够由特定类型的全部实例共享的属性和方法。node
换句大白话来讲:编程
例如图1,能够粗犷地理解为蛋是鸡生的,因此蛋的原型是鸡。
图片引用来自:https://hackernoon.com/unders...windows
或者用亲属关系来理解,原型就是你的父辈祖辈。
图片引用自http://china.findlaw.cn/info/...浏览器
例如图2,这是一个类树状结构的组织:
图片引用自:http://rohitnsit08.blogspot.c...函数
global object
的意思在后面有解释。string
的原型就是 string.prototype
,function
的原型就是 string.prototype
等等,而这些原型的原型就是Object.prototype
了,因此就有那么一句话,在 javascript 里面,全部的东西都是对象在 javascript 里面,global object 有4种:学习
- 在浏览器里面,windows 被称做是 global object
- 在 nodejs 里面,nodejs 的运行自己也是一个 global objec
- 在 Worker 线程下, WorkerGlobalScope 也叫 global object
- 在通常 javascript 运行过程当中,在全部对象被建立以前,会预先建立一个 global object,里面包含了全部这个 javascript 引擎里面拥有的属性和方法,这个也叫作 global object,而且 javascript 的对象系统都是基于这个 global object 创建的。
其实原型是很好理解的东西,就是原来的形态,例如string
的原型就是 string.prototype
,字符串的原来的形态就是字符串原型,可是还有一些比较影响理解和学习的东西,例如constructor
prototype
[[prototype]]
这些。网站
① 先来讲constructor
:
其实构造函数也有constructor
,原型对象有constructor
,实例有constructor
也有,或者更加笼统的说,全部对象都是有constructor
的。
// 构造函数的constructor,默认构造函数的原型对象是Function对象 function Food() {} console.log(Food.constructor); // 返回[Function: Function] // 实例的constructor是Food var food1 = new Food("苹果"); console.log(food1.constructor); // 返回 [Function: Food] // 构造函数的原型对象Food.prototype 也有constructor,指向构造函数 console.log(Food.prototype.constructor); // 返回 [Function: Food]
constructor
默认会指向[Function: Function]
,也就是函数原型对象是他的原型对象。new
来实例化对象的时候,实例的constructor
会默认指向构造函数,证实是哪个来自于哪个构造函数,可是仅此而已,只是一个标识,因此是 Food
。 Food Prototype
上的constructor
会指向构造函数 Food
。能够看出,经过constructor
能够看到他们之间的关系,可是经过constructor
链接的关系是很脆弱的 (容易变化,不可靠),也由于 javascript 在设计当初并无太多考虑这个状况,因此constructor
比较鸡肋。
另外虽然有这么多constructor
,可是咱们通常讨论的比较多的是,原型对象的constructor
,他会默认指向构造函数的 prototype
属性,仅此而已,若是他没有被改变的话,也能够充当一种标志,表明经过这个构造函数生成的实例是来自哪一个原型的,从而判断对象的类型是哪种(但不可靠)。
举例说明:
function Food() {} console.log(Food.constructor); // 返回[Function: Function] // 当重写原型的时候,实例的constructor 就变成了[Function: Object] Food.prototype = { name: "苹果" }; var food2 = new Food("苹果"); console.log(food2.constructor); // 返回 [Function: Object]
这里实例的constructor
就被改变了,因此通常咱们能够看到重写原型的时候(原型链被切断,会默认指向默认的原型对象),会手动加入一个 constructor
属性来指定它的值,以方便识别。
function Food() {} console.log(Food.constructor); // 返回[Function: Function] // 咱们须要主动标记 constructor属性 Food.prototype = { constructor: Food, name: "苹果" }; var food2 = new Food("苹果"); console.log(food2.constructor); // 返回 [Function: Food]
constructor
其实没有什么用处,他是JavaScript语言设计的历史遗留物。因为constructor
属性是能够变动的,因此未必真的指向对象的构造函数,只是一个提示。不过,从编程习惯上,咱们应该尽可能让对象的constructor
指向其构造函数,以维持这个惯例。--by 贺师俊
② 再来讲prototype
在 javascript 里面,只要是函数,都会有这样一个属性prototype
,这个属性指向函数的原型对象,在默认状况下,全部原型对象都会自动得到一个 constructor
属性,指向prototype
所在的函数。
function Food() {} console.log(Food.prototype) // 返回Food {} var food1 = new Food("苹果"); console.log(food1.prototype) // 返回 undefined
prototype
属性是一个内存指针,最终是指向 Food 的原型对象 Food Prototype
的,这里须要注意个人写法,是 Food Prototype
,这里虽然很类似,但其实不是。prototype
属性没办法直接查看,因此返回 undefined
,须要用别的方法来查看。③ 最后说说[[prototype]]
和__proto__
这是存在于实例身上的 prototype
属性,可是没办法直接查看,只能经过某些方式来获取和判断。不一样的浏览器有不一样的叫法,的属性名字也多是[[prototype]]
或者_proto_
。
// Object.getPrototypeOf会返回原型对象,但看起来跟普通构造函数没区别 console.log(Object.getPrototypeOf(food1)); // 返回 Food {} // 因此通常会使用这个方式来判断原型对象是否一致 console.log(Object.getPrototypeOf(food1) === Food.prototype); // 返回 true // 或者这个方式,isPrototypeOf会直接判断 console.log(Food.prototype.isPrototypeOf(food1)) // 返回 true
原型的做用主流程:
prototype
属性,也就是原型属性,这个属性指向构造函数的原型对象,例如food1
的prototype
属性指向Food
的原型对象Food prototype
。constuctor
构造函数属性,这个属性里面包含了一个指向,指向以前被建立的对象的prototype
属性的所在位置,至关于原型对象是母体,被建立的对象会关联到母体身上,参考前面所说的原型,constructor 和 prototype 的内容来理解
在《javascript 高级程序设计》第三版里面的有一幅图:
Person
是构造函数,Person Prototype
是 Person 构造函数的原型对象。[[Prototype]]
都指向了Person Prototype
。问什么这里会有一条线指向了构造函数?
由于Person
构造函数的prototype
指向了Person Prototype
,而Person Prototype
的 constructor
也指向了Person
构造函数,他们之间经过这样来互相确认“关联状态”,但仅仅互相确认关系而已(由于 constructor
容易被改变)。
类比到咱们的 Food 例子里面去,food1和Food
和Food Prototype
的关系就跟 person1和 person2和Person
和Person Prototype
的关系是同样的。
① 若是须要查找这个实例对象的原型的话,可使用Object.getPrototypeOf
,他会返回整个原型对象。
function Food() {} Food.prototype.name = "苹果"; Food.prototype.sayName = function() { console.log("我是" + this.name); }; var food1 = new Food(); console.log(Object.getPrototypeOf(food1)) // 返回 Food { name: '苹果', sayName: [Function] }
② 只能经过对象实例访问保存在原型的值,不能经过对象实例来重写原型中的值。
③ 对象实例能够重写从原型对象中“继承”过来的同名属性,这时候会切断对象实例和原型对象的某个同名属性的联系,若是想恢复联系即恢复没改过的同名属性的话,可使用delete
删除对象实例的某个属性。
④ hasOwnProperty()
方法能够检测一个属性是存在于实例中仍是存在于原型中。
function Food() {} Food.prototype.name = "苹果"; Food.prototype.sayName = function() { console.log("我是" + this.name); }; var food1 = new Food(); console.log(food1.hasOwnProperty("name")); // 返回 false food1.name = "bilibili"; // 设置 food1的 name 属性(也就是改写从原型对象继承过来的 name 属性) console.log(food1.hasOwnProperty("name")); // 返回 true console.log(food1.name); // 返回 bilibili
⑤ 更简单的原型写法
function Food() {} Food.prototype = { constructor: Food, // 这里须要注意 name: '苹果', };
constructor
的话,Food.prototype
的constructor
就再也不指向 Food
,这样就没办法经过constructor
来识别获得改对象实例是属于哪一个原型对象了。constructor
,对象的[[Enumerable]]
可遍历属性就会被设置为 true,表明能够被遍历。⑥ 在原型对象上直接编辑修改,会即时反应到实例对象上,因此能够随时进行修改,很方便。
⑦ 若是重写原型对象,要注意原型对象的指向问题:
// 原型链会被切断 function Food() { } var food1 = new Food("苹果"); // 继续指向原来的 Food.prototype(最初的那个原型对象) // 重写Food.prototype Food.prototype = { constructor: Food, name: '苹果', }; console.log(food1.name); // 返回 undefined
// 原型链不会被切断 function Food() { } // 先重写Food.prototype Food.prototype = { constructor: Food, name: '苹果', }; // 再实例化对象 var food1 = new Food("苹果"); // 指向新的被重写后的Food.prototype console.log(food1.name); // 返回 苹果
在 new 建立完实例以后,实例的原型对象是构造函数的原型对象,若是在这时候重写了构造函数的原型对象的话,那么原来实例跟原来构造函数的原型对象的连接就会被切断,就没法使用原型对象上的数据了。
用了原型模式以后,虽然解决了遇到的一系列问题,但也带来了一些新的反作用(怎么反作用那么多。。。。。),原型模式的共享特性带来了方便之余,也形成了一些困扰,若是咱们须要一些不想共享的信息,例如 food1 的原产地是巴西,印度,非洲,food2的原产地是巴西,印度,俄罗斯,他们之间有一些区别,不能彻底共享,那么怎么办呢?
会经过组合使用构造函数模式和原型模式或者动态原型模式来解决,下回分解。
做者: 怂如鼠
网站: https://www.whynotbetter.com 本做品著做权归做者全部,商业转载请联系做者得到受权,非商业转载请注明出处。