浅析js中的原型和原型链及其使用场景

一、前言

最近浏览网站学习的时候,看到了这个话题,感觉面试里也经常会被问到;所以查阅了不少文章,想总结一下,方便以后翻看。理解的会比较浅显,希望能多多交流,以后理解更深层的也会持续在这里更新。

CDN文档 javaScript中的继承与原型链

二、原型的使用场景

场景1:在vue项目中,我们通常会将对象公共属性放在vue原型上;或者使用插件,将其挂载到vue原型上;这都是利用原型来实现的。

下面案例是将公共属性添加到vue原型上,使用插件的话同理。

javascript 复制代码
// main.js
import Vue from "vue";
//发现少了一个 imgBaseUrl 属性,可以使用原型对象进行扩展
Vue.prototype.imgBaseUrl = config.imgBaseUrl;
javascript 复制代码
// 其他页面使用
<template>
  <div>
    <image :src="imgBaseUrl + 'supply/purchase_tag.png'"></image>
  </div>
</template>
<script>
export default {
  data() {
    const _this = this;
    return {
      imgBaseUrl: _this.imgBaseUrl,
    };
  }
};
</script>

场景2:js中判断数据类型Object.prototype.toString.call()方法, instanceof方法。

instanceof 是用来 判断数据是否是某个对象的实例,返回一个布尔值。

详细内容可以查看我之前的文章:js中判断数据类型的几种实用方法

三、原型和原型对象

在JS中,我们所说的原型通常是针对于函数而言的,当然构造函数也是一个函数。

函数也是一个对象,是对象那么它就有属性,在JS中,我们所创建的每一个函数自带一个属性prototype,我们就把prototype称为原型,也有人把它称之为"显示原型",反正就一个意思。

这个prototype它指向了一个对象,可以把prototype想象成一个指针,或者更简单的理解为prototype的属性值(键值对)。prototype指向的这个对象我们就称之为原型对象,通常大家就直接将prototype理解为原型对象。

请注意,以下的代码是独立的(出于严谨,假定页面没有其他的 JavaScript 代码)。为了最佳的学习体验,强烈建议打开浏览器的控制台(在 Chrome 和火狐浏览器中,按 Ctrl+Shift+I 即可),进入"console"选项卡,然后把如下的 JavaScript 代码复制粘贴到窗口中,最后通过按下回车键运行代码。

javascript 复制代码
  function Person() { }
  console.log(Person.prototype)

打印结果:

可以看到prototype它确实指向了一个对象,原型对象prototype里面有一个constructor属性,它指向了Person构造函数。

总结

1)每个函数都有一个prototype属性,被称作原型。
2)prototype原型指向一个对象,所以也被称原型对象

四、prototype和__proto__不能混淆

很多人把 prototype和__proto__混为一潭,其实这是两个维度的东西。
prototype的维度是函数,__proto__的维度是对象
__proto__是每个对象都有的属性,我们通常把它称为"隐式原型",把prototype称为"显式原型"。

另外,函数也是一个对象,所以它既有prototype属性又有__proto__属性。

控制台验证一下:我们可以看到函数有prototype和__proto__两个属性,而对象只有__proto__属性。

Function:

object对象:

接下来我们再来看看__proto__属性,控制台打印看看。

发现了一个新的属性:[[Prototype]],官方对于这个属性其实有解释,这里通俗的解释一下:

[[prototype]]其实就是隐式原型__proto__,因为各大浏览器厂家不同,所以取了别名罢了,大家只需记住这个和__proto__一样即可。

总结

1)prototype和__proto__不太一样,一个是函数拥有的显式原型,一个是对象拥有的隐式原型。
2) __proto__通常被称作隐式原型,每个对象都拥有该属性。
3)[[prototype]] 其实就是__proto__。

五、原型链

接着我们可以给 Person 函数的原型对象添加新属性,如下:

javascript 复制代码
function Person(){}
Person.prototype.name= "铁锤妹妹"; // 往函数的原型上添加变量和方法
Person.prototype.age = '18'
Person.prototype.getAge= function() 
  console.log('我18岁了')
}
var obj = new Person()
console.log( obj.name )  //"铁锤妹妹"
console.log( obj.age ) //"18"

上面的代码,我们声明了一个构造函数Person,其实就是一个函数。我们知道函数的prototype是一个对象,我们就可以往这个对象上添加东西,所以我们就直接往函数的原型上添加了变量和方法。

接着我们使用new关键词创建一个Person构造函数的 实例对象 ,分别打印name和age,会发现obj这个实例是可以调用构造函数Person原型属性和方法的。这个就叫做 继承

其实这就和我们的原型链有关了,我们把obj打印出来看看:

console.log(obj)

我们会发现obj对象上并没有name和age属性,但是在他的隐式原型[[Prototype]]上有name和age,而且我们会发现obj的[[prototype]]中的constructor指向的是它的构造函数Person。

然后我们再修改一下我们的代码,我们在obj对象上添加一个name属性,看看会输出什么。

javascript 复制代码
function Person(){}
Person.prototype.name= "铁锤妹妹"; // 往函数的原型上添加变量和方法
Person.prototype.age = '18'
Person.prototype.getAge= function() 
  console.log('我18岁了')
}
var obj = new Person()
obj.name = '铁柱哥'
console.log( obj.name )  //"铁柱哥"
console.log( obj.age ) //"18"

如上打印所示, obj.__proto__ = Person.prototype 。但这是做什么的呢?当访问obj中的一个属性,浏览器首先会查看obj中是否存在这个属性。

如果 obj 不包含属性信息,那么浏览器会在 obj__proto__ 中进行查找 (同 Person.prototype). 如属性在 obj__proto__ 中查找到,则使用 obj__proto__ 的属性。

否则,如果 obj__proto__ 不具有该属性,则检查 obj__proto____proto__ 是否具有该属性。默认情况下,任何函数的原型属性 __proto__ 都是 window.Object.prototype. 因此,通过 obj__proto____proto__ ( 同 Peron.prototype 的 __proto__ (同 Object.prototype)) 来查找要搜索的属性。

如果属性不存在 obj__proto____proto__ 中,那么就会在obj__proto____proto____proto__ 中查找。然而,这里存在个问题:obj__proto____proto____proto__ 其实不存在。因此,只有这样,在 __proto__ 的整个原型链被查看之后,这里没有更多的 __proto__ ,浏览器断言该属性不存在,并给出属性值为 undefined 的结论。

上面的查找过程是不是很像链式查找!而这就是我们所说的 原型链 ,而且我们发现查找的过程主要是通过 __proto__ 原型来进行的,所以 __proto__ 就是我们原型链中的 连接点

原型链流程图:

总结:

1)当访问某个对象的属性时,会先在这个对象本身属性上查找,如果没有找到,就去它的原型(_proto_)去找,即它的构造函数的prototype查找,如果没有找到,就到原型的原型去找(构造函数的prototype._proto_)。如果直到最顶层的 Object.prototype 还是找不到,是null,则返回undefined。这样一层一层的查找就会形成一个链式结构,这就是原型链。
2)如果对象自身和它的原型,都定义了一个同名属性,那么优先读取对象自身的属性,这叫做"覆盖"。
3)所有引用类型的 _proto_都指向它的构造函数的prototype。

想要理解原型链,我们还得理解__proto__指向哪儿,也就是说它指向那个构造函数,比如上面的obj对象的__proto__指向的就是Person构造函数,所以我们继续往Person上查找。

六、 性能

在原型链上查找属性比较耗时,对性能有副作用,这在性能要求苛刻的情况下很重要。另外,试图访问不存在的属性时会遍历整个原型链。

遍历对象的属性时,原型链上的每个可枚举属性都会被枚举出来。要检查对象是否具有自己定义的属性,而不是其原型链上的某个属性,则必须使用所有对象从 Object.prototype 继承的 hasOwnProperty 方法。

javascript 复制代码
function Person(name) {
    this.name = name;
}

let p1 = new Person("John");

console.log(p1.hasOwnProperty("name")); // true
console.log(p1.hasOwnProperty("toString")); // false
// 而toString属性是从Object.prototype原型链上继承来的属性。

七、拓展

  • 对象的 hasOwnPrototype() 来检查对象自身中是否含有该属性。
  • 使用 in 检查对象中是否含有某个属性时,如果对象中没有但是原型链中有,会继续检查原型链,也会返回true。

八、 手写代码实现 instanceof (考察对原型链的理解)

  • 步骤1:先取得当前类的原型,当前实例对象的原型链
  • 步骤2:一直循环(执行原型链的查找机制)
    获取当前实例对象原型链的原型链(proto = proto. _proto_,沿着原型链一直往上找)
    如果 当前实例的原型链 _proto_ 上找到了当前类的原型 prototype,则返回true
    如果 一直找到 object.prototype._proto_ == null,object的基类 null 上面都没找,就返回false
javascript 复制代码
 // 实例._proto_ === 构造函数.prototype
    function _instanceof (instance, constructor) {
      // 由于 instanceof 要检测的是某对象,需要有一个前置判断条件
      // 基本数据类型直接返回 false
      if (typeof instance !== 'object' || instance == null) return false
      let proto = Object.getPrototypeOf(instance) // 等价于 instance._proto_; 如果该对象没有原型,则返回 null。
      // 当proto == null时,说明已经找到了Object的基类null 退出循环
      while (proto !== null) {
        //  实例的原型等于当前构造函数的原型
        if (proto === constructor.prototype) return true
        // 沿着原型链_proto_一层层向上找
        proto = Object.getPrototypeOf(proto) // 等价于 proto._proto_
      }
      return false
    }

    console.log('test', _instanceof(null, Array)) // false
    console.log('test', _instanceof({}, Array)) // false
    console.log('test', _instanceof([1,2,3,4], Array)) // true
    console.log('test', _instanceof('', Array)) // false

可参考:
一文搞懂原型和原型链!
浅谈原型和原型链以及其应用案例
【web前端面试必问2】js原型和原型链的理解(透彻)

相关推荐
拉不动的猪几秒前
多窗口数据实时同步常规方案举例
前端·javascript·vue.js
南清的coding日记31 分钟前
Java 程序员的 Vue 指南 - Vue 万字速览(01)
java·开发语言·前端·javascript·vue.js·css3·html5
yoyoma1 小时前
object 、 map 、weakmap区别
前端·javascript
shyshi1 小时前
vercel 部署 node 服务和解决 vercel 不可访问的问题
前端·javascript
.生产的驴1 小时前
React 模块化Axios封装请求 统一响应格式 请求统一处理
前端·javascript·react.js·前端框架·json·ecmascript·html5
雾岛听风来1 小时前
Android开发中常用高效数据结构
前端·javascript·后端
思考着亮2 小时前
formData
javascript
yoyoma3 小时前
彻底搞懂 JavaScript 闭包:原理、陷阱与内存优化全解析
前端·javascript
茄汁面3 小时前
Angular(TypeScript ) 中基于 ExcelJS 实现导入模板下载功能(带样式与数据验证)
前端·javascript·node.js
前端九哥3 小时前
老板:就是你小子删光了try-catch?
前端·javascript