前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >如何使用 javascript 面向对象编程来唬住面试官(part 2)

如何使用 javascript 面向对象编程来唬住面试官(part 2)

作者头像
前端正义联盟
发布2018-09-21 16:59:50
7040
发布2018-09-21 16:59:50
举报
文章被收录于专栏:前端正义联盟前端正义联盟

续上一集内容,通过构造函数的方式,成功地更新了生产技术,老板笑呵呵,工人少奔波,只是问题总比办法多,又遇到一个新问题,就是会造成一些资源的重复和浪费,那么经过工程师们的智慧交流,他们产生了一个新技术,原型模式

一、使用原型模式

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();

console.log(food1.sayName == food2.sayName); // 返回 true
  • 将所有属性和方法,包括sayName 方法都放到原型Food的原型上去
  • 跟之前构造函数创建新对象的方式一样,使用new来创建

这样就完成了原型模式的使用了,能够将函数进行共享,不用每次都重复创建不同的函数实例了,而且所有的属性共享,也能够很方便节省代码和简化结构。

但是比较懵逼,为什么这样就可以了呢?原型是个什么?怎么起作用的呢?

理解什么是原型

javascript 的原型是一个属性,一般我们叫他原型属性 prototype,这个属性是一个内存指针,指向一个对象,而这个对象的用途是包含可以由特定类型的所有实例共享的属性和方法。

换句大白话来说:

  • 原型就是根,所有东西都有根,是来自于哪里,是被谁创造出来的,并且能够通过这个根去追溯父辈祖辈的信息。
  • 全部东西都会由原型创造,所以都会带有一个原型属性,只是不同的原型创造出来的东西带有不同的原型属性。

例如这里,可以粗犷地理解为蛋是鸡生的,所以蛋的原型是鸡。

图片引用来自:https://hackernoon.com/understand-nodejs-javascript-object-inheritance-proto-prototype-class-9bd951700b29

为了更方便理解,如下图,在 javascript 里面,所有的东西都是对象,这是一个类树状结构的组织:

图片引用自:http://rohitnsit08.blogspot.com/2011/06/javascript-object-system.html

  • 这里的global object 的意思在后面有解释,他会分出不同的对象,有 string objectobjecr objectfunction objectarray object、等等。
  • 这里要特别注意一下,以上说到的对象的原型很多都来自 Function.prototype,而不只是Function.prototype,还有其他的对象原型,又都来自于Object.prototype,所以也这就是平时大家常说的,javascript 里面一切都是对象的原因了。

在 javascript 里面,global object 有4种:1. 在浏览器里面,windows 被称作是 global object2. 在 nodejs 里面,nodejs 的运行本身也是一个 global objec3. 在 Worker 线程下, WorkerGlobalScope 也叫 global object4. 在一般 javascript 运行过程中,在所有对象被创建之前,会预先创建一个 global object,里面包含了所有这个 javascript 引擎里面拥有的属性和方法,这个也叫做 global object,并且 javascript 的对象系统都是基于这个 global object 建立的。

为什么能够通过原型模式来解决问题呢?

  1. 在 javascript 里面,创建一个新函数(对象),都会在创建过程里面增加一个prototype属性,也就是原型属性,这个属性指向函数的原型对象,例如food1 指向Food
  2. 而这个被指向的原型对象里面也会自动获得一个constuctor构造函数属性,这个属性里面包含了一个指向,指向之前被创建的对象的prototype属性的所在位置,相当于原型对象是母体,被创建的对象会关联到母体身上,并且是一对多的关联,即一个母体对多个子体。
  3. javascript 解析器读取到对象之后,会执行一次搜索,如果在当前对象上没有搜索到目标属性的话,就会继续搜索指针指向的原型对象。

这里有2个图帮助理解:

  • person1 和 person2 都是实例, Person 是构造函数,Person Prototype 是 Person 是构造函数的原型属性。
  • Person 是构造函数的prototype 指向了Person Prototype,而Person Prototype的 constructor 也指向了Person 是构造函数。
  • 例如,要确认 person1有没有 sayName 方法,那么javascript 引擎会先对person1对象本身进行搜索,如果有就直接返回,如果没有就继续搜索他的原型对象 Person Prototype,如果有就返回,如果没有就报无法找到。

类比到我们的 Food 例子里面去,food1和 Food 和 Food Prototype的关系。

这是一个图, 对象->动物->狗->bichong(狗的大种类)->Foo(狗的小种类)->foo(我家的狗),这就是所谓的原型的链图的一种情况,也是原型链的一个很形象的介绍。

图片来源于:https://hackernoon.com/understand-nodejs-javascript-object-inheritance-proto-prototype-class-9bd951700b29

prototype属性有可能叫做[[prototype]] 或者_proto_

对于原型的一些使用技巧

① 如果需要查找这个实例对象的原型的话,可以使用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.prototypeconstructor就不再指向 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); // 返回 苹果

二、文末我们又遇到了新问题了

用了原型模式之后,虽然解决了遇到的一系列问题,但也带来了一些新的副作用(怎么副作用那么多。。。。。),原型模式的共享特性带来了方便之余,也造成了一些困扰,如果我们需要一些不想共享的信息,例如 food1 的原产地是巴西,印度,非洲,food2的原产地是巴西,印度,俄罗斯,他们之间有一些区别,不能完全共享,那么怎么办呢?

会通过组合使用构造函数模式和原型模式或者动态原型模式来解决,下回分解。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2018-08-14,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 前端正义联盟 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、使用原型模式
    • 理解什么是原型
      • 为什么能够通过原型模式来解决问题呢?
        • 对于原型的一些使用技巧
        • 二、文末我们又遇到了新问题了
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档