原型和原型链的理解:(面试题)

  • 原型:每个函数都有 prototype 属性,该属性指向原型对象;使用原型对象的好处是所有对象实例共享它所包含的属性和方法。
  • 原型链:主要解决了继承的问题;每个对象都拥有一个原型对象,通过__proto__ 指针指向其原型对象,并从中继承方法和属性,同时原型对象也可能拥有原型,这样一层一层,最终指向 null。

原型的作用:
1.数据共享 节约内存内存空间
2.实现继承
注意:函数也是一个对象,对象不一定是函数。(对象有__proto__属性,函数有prototype属性)此处说明,方便大家理解下文。
下面我将举例说明为什么要使用原型
例1:

function Person(name) {this.name=name;this.eat=function () {console.log(this.name+"吃东西");}this.sleep=function () {console.log(this.name+"睡觉");}}var p1=new Person("小明");p1.eat();//小明吃东西p1.sleep();//小明睡觉var p2=new Person("小利");p2.eat();//小利吃东西p2.sleep();//小利睡觉console.dir(p1);//dir()打印结构console.dir(p2);

每次使用构造函数Person()实例化出对象的时候,就会给每个实例对象的eat()方法和sleep()方法开辟空间。可是当实例化许多对象的时候,就会浪费大量的空间,因为每个实例对象的eat()方法和sleep()的功能都是一样的,所以我们没必要为每个实例对象添加eat()方法和sleep()方法。

这时原型就派上用场了,看下面经过改造的例子:

function Person(name) {this.name=name;}Person.prototype.eat=function () {console.log(this.name+"吃东西");};Person.prototype.sleep=function () {console.log(this.name+"睡觉");}var p1=new Person("小明");p1.eat();//小明吃东西p1.sleep();//小明睡觉var p2=new Person("小利");p2.eat();//小利吃东西p2.sleep();//小利睡觉console.dir(p1);console.dir(p2);

eat()方法和sleep()被添加到了Person()构造函数的原型(prototype)上了。因此只有一份eat()方法和sleep()方法。当实例对象需要使用这些方法的时候就在自己的__proto__属性中找到并调用这些方法实现相应的功能。

现在我们来捋一下构造函数,实例对象,原型对象之间的关系。

如以下代码和图所示:

Person()构造函数的prototype属性是一个对象,实例对象p1的__proto__属性也是一个对象,并且prototype对象和__proto__对象的指向相同。那么我们再回过头来理解一下为什么添加到原型的方法可以是共享的。因为prototype对象和__proto__对象的指向相同,所以将eat()方法和sleep()添加到Person()构造函数的prototype属性上之后,实例对象就可以通过自己__proto__属性去访问eat()方法和sleep()了。

console.dir(Person);console.dir(p1);console.log(typeof p1.__proto__);//objectconsole.log(typeof Person.prototype);//objectconsole.log(p1.__proto__ === Person.prototype);//true

__proto__指向该对象所在的构造函数的原型对象。

实例对象和构造函数之间没用直接的关系。原型对象与实例对象之间用原型(__proto__)关联,这种关系叫做原型链。

我是这样理解原型链的(可能不是很准确)我向我爸要零花钱,我爸也没有钱,那么我就向我奶奶要,奶奶要是也没有,就继续找别人要。

那么原型的指向可以改变吗?答案是可以的。

举个例子:

 
  1. function Person(name) {

  2. this.name=name;

  3. }

  4. Person.prototype.eat=function () {

  5. console.log(this.name+"吃东西");

  6. };

  7. Person.prototype.sleep=function () {

  8. console.log(this.name+"睡觉");

  9. }

  10. function Student(school) {

  11. this.school=school;

  12. }

  13. Student.prototype.write=function () {

  14. console.log("写作业");

  15. }

  16. Student.prototype=new Person("小华");//改变Student()构造函数的指向,让Student()构造函数的原型对象指向Person的实例对象

  17. var s1=new Student("某某高中");

  18. s1.eat();//小华吃东西

  19. s1.sleep();//小华睡觉

  20. s1.write();//Uncaught TypeError: s1.write is not a function,因为Student()的原型的指向改变,所以找不到write()方法

  21. console.dir(Student);

  22. console.dir(s1);

__proto__指向该对象所在的构造函数的原型对象。如上图所示:Studend()构造函数的原型(prototype)指向了Person()的实例对象(new Person("小华")),所以Studend()的实例对象s1的__proto__也指向了Person()的实例对象((new Person("小华"))。而实例对象((new Person("小华"))的__proto__指向了其所在的构造函数Person()的原型对象在这个原型对象中,找到了eat()方法和sleep()方法。

从这个例子中,可以发现,利用原型可以实现继承。面向对象的编程语言中有(class)类的概念,但是JavaScript不是面向对象的语言,所以js中没有类(class)(ES6中实现了class),但是js可以模拟面向对象的思想编程,js中通过构造函数来模拟类的概念。

改变原型的指向可以实现方法的继承。借用构造函数继承,主要解决属性的问题

function Person(name) {this.name=name;}Person.prototype.eat=function () {console.log(this.name+"吃东西");};Person.prototype.sleep=function () {console.log(this.name+"睡觉");}function Student(name,school) {//name为父类构造器传参。子类构造器可以添加自己特有的属性schoolPerson.call(this,name);//调用父类构造器Person的属性,this.school=school;}Student.prototype.write=function () {console.log("写作业");}Student.prototype=new Person();//改变Student()构造函数的指向,让Student()构造函数的原型对象指向Person的实例对象var s1=new Student("zx","某某高中");s1.eat();//小华吃东西s1.sleep();//小华睡觉console.dir(Student);console.dir(s1);

组合继承就是指:将改变原型的指向和借用构造函数两者结合在一起实现继承。

一、原型模式

我们创建的每一个函数都有一个prototype(原型)属性,这个属性指向的是通过调用构造函数来创建出来的对象实例原型对象,这个原型对象可以让所有对象实例共享它所包含的属性和方法。

function Person () {}Person.prototype.name = "xiao";Person.prototype.sayName = function () {alert('this.name')}var person1 = new Person();var person2 = new Person();person1.sayName()console.log(person1.name == person2.name) // "true"

上面的例子当中我们创建了一个构造函数Person,并通过它的prototype属性在它的原型对象中定义了name属性并赋值,然后通过调用构造函数Person实例化出来两个对象实例,通过打印出来的值我们可以得知,person1person2共享了原型对象中的属性和方法。

构造函数,原型对象和对象实例的关系

我们知道每个函数都有一个prototype属性指向函数的原型对象。在默认情况下,所有原型对象都有一个constructor(构造函数)属性,这个属性指向了prototype属性所在的函数,比如前面的例子中,Person.prototype.constructor就指向Person
另外,当调用构造函数创建一个新实例后,该实例的内部将包含一个__porto__属性(仅在Firefox、Safari、Chrome中支持),这个属性指向的就是构造函数的原型对象。由此我们可以得出以下图示的结论:

通过代码来验证:

# 实例和原型对象之间的关系console.log(person.__proto__ == Person.prototype) // true# 也可以通过isPrototypeOf()和ES5中的Object.getPrototypeOf()方法来判断console.log(Person.prototype.isPrototypeOf(person1)) // trueconsole.log(Object.getPrototypeOf(person) === Person.prototype) // true# 原型对象和构造函数的关系console.log(Person.prototype.constructor == Person) // true

二、原型链

通过前面我们对构造函数,对象实例和原型对象三者关系的描述可知,实例都包含了指向原型对象的内部指针。
那么假如现在我们有两个构造函数AB,我们让构造函数A的原型对象等于构造函数B的实例,根据前面的推论,这个时候A的原型对象就包含指向B的原型对象的指针,再假如又有一个构造函数C,让A的原型对象等于C的实例,上述关系依旧成立,以此类推便形成了实例与原型的链条,即原型链,它主要作为JS中实现继承的主要方法。

原型链的基本实现

function SuperType() {this.property = true;}SuperType.prototype.getSuperValue = function() {return this.property;}# 继承了SuperTypeSubType.prototype = new SuperType();SubType.prototype.getSubValue = function() {return this.subproperty;}var instance = new SubType();console.log(instance.SuperValue()); // true

在上面的代码中,我们没有使用SubType默认的原型,而是将SuperType的实例赋给它,重写了SubType的原型对象;这样一来SubType.prototype的内部便具有一个指向SuperType原型的指针,原来存在于SuperType的实例中的所有属性和方法,现在也存在于SubType.prototype中了。
instance同理,还要注意的是由于SubType的原型指向了SuperType的原型,而SuperType的原型的constructor属性指向的是SuperType构造函数,那么instance.constructor也就指向了SuperType

原型搜索机制:

当访问一个实例属性或方法时,在通过原型链实现继承的情况下,首先会在实例中搜索该属性,在没有找到属性或方法时,便会沿着原型链继续往上搜索,直到原型链末端才会停下来。
这里还有一个重要的点,事实上所有引用类型默认都继承了Object,而这个继承也是通过原型链实现的,也就是说,所有函数的默认原型都是Object的实例,这也是所有自定义类型都会继承toString()valueOf()等默认方法的根本原因。

Object.prototype的原型

既然所有类型默认都继承了Object,那么Object.prototype又指向哪里呢,答案是null,我们可以通过下面的代码打印试试看:

console.log(Object.prototype.__proto__ === null) // true

null即没有值,也就是说属性或方法的查找到Object.prototype就结束了。

本面试题为前端常考面试题,后续有机会继续完善。我是歌谣,一个沉迷于故事的讲述者。

欢迎一起私信交流。

“睡服“面试官系列之各系列目录汇总(建议学习收藏)

“约见”面试官系列之常见面试题第四十二篇之原型和原型链(建议收藏)相关推荐

  1. “约见”面试官系列之常见面试题第三十二篇之async和await(建议收藏)

    一.async和await async和await的概念 1)async 函数是 Generator 函数的语法糖,使用 关键字 async 来表示,在函数内部使用 await 来表示异步 2)ES7 ...

  2. “约见”面试官系列之常见面试题第三十五篇之轮循机制(建议收藏)

    目录 前言 任务队列 事件的概念 回调函数 事件轮询机制Event Loop: 结语 前言 有人称Event Loop为事件循环机制,而我更愿意将其解释为事件轮询机制,在之后的内容中你会感受到这一点的 ...

  3. “约见”面试官系列之常见面试题之第九十九篇之router和route(建议收藏)

    1.router是VueRouter的一个对象,通过Vue.use(VueRouter)和VueRouter构造函数得到一个router的实例对象,这个对象中是一个全局的对象,他包含了所有的路由包含了 ...

  4. “约见”面试官系列之常见面试题之第九十二篇之created和mounted区别(建议收藏)

    beforeCreate 创建之前:已经完成了 初始化事件和生命周期 created 创建完成:已经完成了 初始化注册和响应 beforeMount 挂载之前:已经完成了模板渲染 mounted :挂 ...

  5. “约见”面试官系列之常见面试题之第八十三篇之node.js理解(建议收藏)

    1.模块的引用示例 var math = require('math'): 在common.js规范中,存在require()方法,这个方法接受模块标识,此引引入一个模块的api 到当前的上下文中. ...

  6. “约见”面试官系列之常见面试题之第九十五篇之vue-router的组件组成(建议收藏)

    <router-link :to='' class='active-class'> //路由声明式跳转 ,active-class是标签被点击时的样式<router-view> ...

  7. “约见”面试官系列之常见面试题第四十四篇之webpack打包原理解析?(建议收藏)

    webpack打包是如何运行的 也可以称为,webpack是如何实现模块化的 CommonJS是同步加载模块,一般用于node.因为node应用程序运行在服务器上,程序通过文件系统可以直接读取到各个模 ...

  8. “约见”面试官系列之常见面试题第四十篇之双向绑定以及实现原理(建议收藏)

    目录 MVC模式 MVVM模式 双向绑定原理 1.实现一个Observer 2.实现一个Watcher 3.实现一个Compile 4.实现一个MVVM 最后写一个html测试一下我们的功能 MVC模 ...

  9. “约见”面试官系列之常见面试题第三十九篇之异步更新队列-$nextTick(建议收藏)

    目录 一,前言 二,什么是异步更新队列 三,使用异步更新队列 四,结尾 一,前言 这一篇介绍有关异步更新队列的知识,通过异步更新队列的学习和研究能够更好的理解Vue的更新机制 二,什么是异步更新队列 ...

最新文章

  1. 欧歌赛机器人_本页面内容已永久停止用户浏览!
  2. lazarus php,Lazarus  终于安装成功了
  3. pytorch 1.7.x训练保存的模型在1.4低版本无法加载
  4. win1编辑java环境,在win7下配置java编译环境
  5. HBase shell命令行
  6. c3p0配置及其说明
  7. APP动态界面设计使用的利与弊
  8. Bugzilla简明使用手则
  9. asp.net身份认证
  10. 第八篇:ZTree操作总结
  11. 关于nginx unit服务非正常关闭后,无法重新启动问题的处理
  12. 团队管理心得--建团队,管事理人
  13. 一、数字图像处理分析与概述
  14. 大功率锂电池组BMS(电池管理系统)保护板电路介绍(ACS758/CH704应用案例)
  15. 简述74HC595功能
  16. Oracle | Oracle初级全程学习笔记
  17. Java 多线程学习(1)一些容易被遗忘的基础概念
  18. 【AI人工智能学习】GitHub 上适合初学者的 10 个最佳开源 AI 项目
  19. win10关闭任务栏窗口预览
  20. IDEA设置背景图片

热门文章

  1. 十、input与跳转
  2. Some Principles
  3. equals和=,==的区别
  4. 20160512关于mac安装caffe的记录
  5. ios 制作framework
  6. 一篇极好的 CSS 教程
  7. minio 授予永久访问权限_应对 iOS 14 权限管理 应用手把手教你打开“所有照片”权限...
  8. eval函数 php_PHP的一句话木马代码和函数eval的简介
  9. 四参数坐标转换c++_GPSRTK坐标转换及四参数、七参数适用条件
  10. ifconfig命令找不到_02. Linux命令之查看网络连接