关于前端:聊聊原型链与继承

3次阅读

共计 4759 个字符,预计需要花费 12 分钟才能阅读完成。

前言

原型链与继承、作用域与闭包、单线程与异步 并称为前端的三座大山,均属于 JavaScript 中根底却又十分复杂的局部,而且面试中也常常问到。

明天,咱们就来具体介绍一下 原型链与继承,聊聊它的概念、作用与用法。

如果掘友对此局部曾经学过只是稍微忘记,可间接跳转至 原型链图片 看图温习。

上面,让咱们循序渐进的介绍下 原型链与继承

意识原型

在咱们创立函数的同时,都会主动为其创立一个 prototype 属性,指向函数的原型对象。所有的原型对象也会主动取得一个名为 constructor 的属性,指回与之关联的构造函数。

咱们所说的 原型,个别指的都是 __proto__prototype 属性,也有人将 prototype 属性称为显示原型,__proto__ 称为隐式原型,这两个之间有什么区别与关联?

prototype 是咱们在创立函数的同时,主动为该函数增加的属性,会指向该函数的原型对象。

__proto__ 是所有对象都有的一个公有属性,指向它的构造函数的原型对象

看这是不是一头雾水,又多出了两个新的概念:构造函数、原型对象又是什么?

构造函数:构造函数也是函数,与一般函数的惟一区别就是调用形式不同。任何函数只有应用 new 操作符调用就是构造函数,而不实用 new 操作符调用的函数就是一般函数。个别构造函数的首字母会大写,比方 Object() Array() Function() 等。

原型对象:原型对象是随同着构造函数一起被创立的,与一般的对象并无区别,但原型对象在创立时会主动取得一个 constructor 属性,指回与之关联的构造函数。

每次应用构造函数创立一个实例对象,实例外部的 [[Prototype]] 指针就会被赋值为构造函数的原型对象。目前支流浏览器都在每个对象上裸露了 __proto__ 属性,用以拜访该实例的原型。

看看代码或者能更好的了解:

const obj = new Object()

console.log(obj['__proto__'] === Object.prototype) // true

console.log(Object.prototype.constructor === Object) // true

备注:通过 Object.create(null) 创立的对象属于特例,其不具备 __proto__ 属性;es6 新增的箭头函数,其不具备 prototype 属性,也不能作为构造函数;Symbol、BigInt 尽管不能通过 new 调用,但其具备 prototype 属性。

意识继承

你是否会疑难?当咱们创立一个对象的时候,明明身上没有任何属性,但仍能够调用许多办法,如 toString hasOwnProperty……

const obj = {}

console.log(obj.toString()) // '[object Object]'
console.log(obj.hasOwnProperty('a')) // false

其实,所有对象都是 Object 的实例,obj = {}obj = new Object() 齐全等价。

咱们在控制台开展对象,发现其 [[Prototype]] 接口指向的就是 Object,在其中也找到了上述的办法,为了不便辨别,下称一般对象为实例。

测试一下,实例身上的 toString 办法,指向的也就是其原型上的办法,在其原型上增加的属性,也能够通过实例间接拜访。

const obj = {}
console.log(obj.toString === obj['__proto__'].toString) // true

obj['__proto__'].a = 1
console.log(obj.a) // 1

实例能够拜访其原型对象上的属性与办法,这便是 继承

然而,并不能通过实例批改其原型的属性和办法,操作实例的属性只会创立或批改实例身上的属性。

如果实例与其原型有雷同的属性,那么原型对象上的同名属性将被暗藏。

const obj = {}
obj['__proto__'].a = 1
obj.a = 2

console.log(obj['__proto__'].a) // 1
console.log(obj.a) // 2

原型链

咱们晓得了每个实例都有一个原型对象,能够通过实例的 __proto__ 属性拜访原型对象。

但原型对象也是对象,属于上一层构造函数的实例,也会有 __proto__ 属性,指向上一层的原型。

当咱们拜访实例属性的时候,如果实例上没有,就会去拜访它的 __proto__,如果原型对象也没有,就会拜访原型的原型,一层层向上拜访。咱们把这一条由 __proto__ 属性组成的原型门路,称作是 原型链

然而沿着原型始终向上是无穷无尽的。不禁会问:原型链的起点是什么?

想解答这个问题,就要从理论登程,设计原型对象是为了不便咱们的应用,继承一些属性和办法。

Object 是所有对象的基类,咱们平时也基本不会间接去拜访或应用 Object 的原型,所以再为其设计上一级的原型对象毫无意义,所以 Object.prototype 的原型是 null,这便是原型链的起点。

控制台打印 Object.prototype 也能够看出,其 __proto__ 属性值为 null

函数也是对象

函数也是非凡的对象,所有函数都是 Function() 的实例。

你可能在平时基本没有见过 Function()

但其实,以下三种创立函数的形式,除了第一种存在变量晋升外,是齐全等价的,咱们只是习惯应用简写的形式。

function fun1(a, b) {console.log(a, b)
}
fun1(1, 2) // 1 2

const fun2 = function (a, b) {console.log(a, b)
}
fun2(1, 2) // 1 2 

const fun3 = new Function('a', 'b', 'console.log(a,b)')
fun3(1, 2) // 1 2

函数也有 __proto__ 属性,指向的是 FunctionprototypeFunctionprototype 也是对象,属于 Object 的实例。

console.log(fun1['__proto__'] === Function.prototype) // true
console.log(Function.prototype['__proto__'] === Object.prototype) // true

不过除非特地指明,个别所说的函数的原型对象,指的是其 prototype 属性。

应用原型

学会了原型,那么要如何应用呢?

个别会有以下几种用法:

查看类型

instanceof 操作符就是依据原型来运作的,查看某个对象是否为函数的实例,能够用来辨别对象与数组。

const arr = []
const obj = {}

console.log(typeof arr) // 'object'
console.log(typeof obj) // 'object'
console.log(arr instanceof Array) // true
console.log(obj instanceof Array) // false
console.log(arr['__proto__'] === Array.prototype) // true

扩大原型

能够通过原型扩大实例的办法,比方咱们感觉获取一个数字的绝对值,每次都调用 Math.abs() 太麻烦了,能够间接扩大 Number 的原型。

Number.prototype.abs = function () {return Math.abs(this.valueOf())
}

let num = -1
console.log(num.abs()) // 1

以及 Vue2 中的全局事件总线,也是扩大了 Vue 的原型。

// 创立全局事件总线
Vue.prototype.$bus = this

// 注册事件
this.$bus.$on('eventName',(data)=>{})

// 触发事件
this.$bus.$emit('eventName','data')

原型模式

能够通过构造函数来批量创立实例,并使它们共享属性与办法。

function Person(name, age) {if (name != undefined) this.name = name
  if (age != undefined) this.age = age
}
Person.prototype.age = 18
Person.prototype.say = function () {console.log(this.name, this.age)
}

const xiaoming = new Person('小明', 18)
xiaoming.say() // 小明 18
const xiaolan = new Person('小兰', 17)
xiaolan.say() // 小兰 17
const xiaohong = new Person('小红')
xiaohong.say() // 小红 18

console.log(xiaoming instanceof Person 
  && xiaolan instanceof Person 
  && xiaohong instanceof Person) // true

批改原型

能够批改原型以取得办法。

const time = function () {
  // 批改原型为数组,应用 map 办法
  arguments['__proto__'] = Array.prototype
  
  return arguments.map((item) => (String(item).length < 2 ? '0' + item : item)).join('-')
}

console.log(time(2022, 5, 20, 12, 0, 0))// 2022-05-20-12-00-00

然而个别 不举荐 间接批改原型的,因为批改原型容易导致逻辑的凌乱,如果想获取某个原型的办法,倡议应用 Object.create() 继承其实例。

const xiaoming = new Person('小明', 18)
xiaoming.say() // 小明 18

const xiaoming2 = Object.create(xiaoming)
xiaoming2.name = '小名'
xiaoming2.age = '19'
xiaoming2.say() // 小名 19
console.log(xiaoming2['__proto__'] === xiaoming) // true

对于上一个例子,举荐应用 Array.from() 转化为数组。

const time = function () {const arr = Array.from(arguments)
  arr['__proto__'] = Array.prototype
  return arr.map((item) => (String(item).length < 2 ? '0' + item : item)).join('-')
}

原型链图片

用一张图展现原型链的关系,心愿看完本文,你也能轻松画出下图:

总结

各个原型之间的关系或者有点绕,但只有你了解了,其实并不难。

总结一下:

  • 所有对象都有 __proto__ 属性,指向它的原型对象,能够通过对象拜访其原型的属性。
  • 沿着 __proto__ 的门路,就是原型链。
  • 原型链的起点是 nullObject.prototype['__proto__']
  • 所有函数在创立时都会主动创立它的原型对象,调配给函数的 prototype 属性。
  • 所有函数也都是 Function 的实例,它们的 __proto__ 属性指向 Function 的原型对象。
  • 能够通过扩大函数的原型为其实例增加属性与办法。
  • 通过 Object.create(null) 创立的对象不具备任何属性;es6 新增的箭头函数不具备 prototype 属性,不能作为构造函数,但仍是 Function 的实例。

结语

如果文中有谬误或不谨严的中央,请务必给予斧正,非常感激。

如果喜爱或者有所启发,欢送点赞关注,激励一下新人作者。

正文完
 0