一、什么是原型?
原型(prototype)
是一个对象 ,为什么 JS 中的基本类型能调用一些方法?
就是因为它们继承了爸爸们在原型
中定义的属性或方法;比如一个简单的字符串为什么会有 .length
属性呢?
const str = 'Hello,world'
console.log(str.length)
原因是 String.prototype.length
就有这个属性, String
这个原始对象就是 str
的爸爸。
现在我们明白了,原型(prototype)
原来就是定义一些给孩子们用的工具。
二、什么是原型链?
问题来了,上面的 str
字符串是如何继承 String
爸爸的 prototype 对象呢?换句话说,是如何找到爸爸的工具?
答案是 __proto__
俗称原型链
,它指向着上一层 prototype
原型对象(这里是 String.prototype),看例子:
const str = 'Hello,world'
console.log(str.__proto__ === String.prototype) // 打印:true
故事开始变得有趣了,如果把 __proto__
切断换成别的,是不是 length
属性就消失了呢?,看例子:
const str = 'Hello,world'
str.__proto__ = ''
console.log(str.length) // 打印:11
已经将 __prot__
变成空字符串了,咋还能调用 length 方法,剧情不按套路来?
相信眼睛细锐的伙伴应该猜到了,空字符串 ''
本身自带 __proto__
属性呀!所以关系链还存在呢。
有没有办法,能让赋值的东西
不包含 __proto__
这玩意呢?试试 null
?
const str = 'Hello,wolrd'
str.__proto__ = null
console.log(str.length) // 打印:11
结果还是 11,好家伙这就有点奇怪了,莫非 __proto__
不能强行更改?
其实答案部分正确,对于基本类型
,无论怎么赋值,str.__proto__
始终指向 String.prototype
,
于是得出一个结论:基本类型的原型链不能切断
。
然而对于引用类型
,剧情就会按照套路来,我们来验证下:
const obj = {}
obj.toString() // 打印:'[object Object]'
obj.__proto__ = null;
obj.toString() // 报错:toString 不是一个 Function 。
于是又得出一个结论:引用类型的原型链可以切断
。
三、自定义函数可享受原型
基本类型和引用类型都能继承爸爸们的工具,这个 feature 非常高效啊,JS 作者为了让开发人员也能享受这种待遇,于是给自定义函数也加上了 原型 prototype
的功能,前提条件是得使用 new
关键字实例化函数时,这些实例化函数便能享受函数(俗称“构造函数”)自定义的 prototype
工具,来看看代码:
function Animal() {}
Animal.prototype.run = () => console.log('Run run run!')
Animal.prototype.eat = () => console.log('Eat eat eat!')
const dog = new Animal()
dog.run() // 打印:Run run run
console.log(dog.__proto__ === Animal.prototype) // 打印:true
const cat = new Animal()
console.log(cat.__proto__ === Animal.prototype) // 打印:true
cat.run() // 打印:Run run run
Tip: 既然 dog 和 cat 都能享有 run 和 eat 方法,这像什么关系?没错,像继承关系。于是,JS 中的
继承概念
就这么出来了。
四、什么是类?
既然 JS 能模拟 继承
,那制造一个类 Class
出来也没问题吧?于是,基于原型(prototype)和原型链(__proto__) 这俩家伙的组合,便有了现在的类,咋们来看例子:
class Animal{
constructor() {}
run() { console.log('Run run run') }
eat() { console.log('Run run run') }
}
const dog = new Animal()
dog.run() // 打印:Run run run
console.log(dog.__proto__ === Animal.prototype) // 打印:true
看看,类这家伙就是一个语法糖,对不对?
完!