前言
原型链与继承、作用域与闭包、单线程与异步 并称为前端的三座大山,均属于 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__
属性,指向的是 Function
的 prototype
,Function
的 prototype
也是对象,属于 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__
的门路,就是原型链。 - 原型链的起点是
null
(Object.prototype['__proto__']
) - 所有函数在创立时都会主动创立它的原型对象,调配给函数的
prototype
属性。 - 所有函数也都是
Function
的实例,它们的__proto__
属性指向Function
的原型对象。 - 能够通过扩大函数的原型为其实例增加属性与办法。
- 通过
Object.create(null)
创立的对象不具备任何属性;es6 新增的箭头函数不具备prototype
属性,不能作为构造函数,但仍是Function
的实例。
结语
如果文中有谬误或不谨严的中央,请务必给予斧正,非常感激。
如果喜爱或者有所启发,欢送点赞关注,激励一下新人作者。