原型和原型链-基础,但是非常重要
我们创造的每一个函数都有一个
prototype(原型)属性
。这个属性是一个指针,指向原型对 象。在默认情况下,所有的原型对象都会有一个 constructor(构造函数)属性
,这个属性包含一个指向 prototype 属相所在的指针。当调用构造函数创建一个新实例之后,该实例内部将包含一个指针(内部属性),指向构造函数的原型对象。当我们访问一个对象的属性时,如果这个对象内部不存在这个属性,那么他就会去 prototype 里找这个属性,这个
prototype 又会有自己的 prototype
,于是就这样一直找下去
,也就是我们平时所说的原型链
的概念
# 简单总结:
- prototype 是一个对象的
原型
- prototype(原型其实也是一个对象,那对象就还有他自己的原型) 找 prototype 如此下去的关系链,称为原型链
- 抽象为代码就是:
instance.constructor.prototype = instance.__proto__
- 原型链最顶端是
null
。
# 1. 原型(最下面有流程图,字不如图好理解,不过字一定要看完)
要了解以下概念:__proto__
、prototype
、普通对象
、函数对象(内置对象)
不清楚
普通对象
和函数对象
?看这里内置对象有哪些-内置对象-函数对象-和普通对象区别
对象类型 | __proto__ | prototype |
---|---|---|
普通对象 | ✅ | ❌ |
函数对象 | ✅ | ✅ |
# 1.1 结论
- 只有函数对象有
prototype
属性,普通对象 没有这个属性。 prototype
和__proto__
都是在创建一个函数或者对象会自动生成的属性。
用代码验证下结论:
//func称为构造函数
function func() {}
console.log(typeof func.prototype) // object
console.log(typeof func.__proto__) // function
const obj = {}
console.log(typeof obj.__proto__) //object
console.log(typeof obj.prototype) //undefined (看见了吧,普通对象真的没有 prototype 属性)
2
3
4
5
6
7
8
再看一段代码
console.log(obj.__proto__ === Object.prototype) // true
console.log(func.__proto__ === Function.prototype) // true
console.log(func.constructor.prototype === func.__proto__) // true
// native code的意思就是浏览器内置的函数。又涉及到内置函数,不懂得一定要看懂
console.log(func.__proto__) // ƒ () { [native code] }
console.log(func.constructor) // ƒ Function() { [native code] }
console.log(func.prototype) // {constructor:{},__proto__:{}}
2
3
4
5
6
7
8
# 1.2 结论
- 实例的
__proto__
属性主动指向构造的 prototype
; prototype
属性被__proto__
属性 所指向。- 这就是原型和原型链的关联:原型不断找到他上一级的原型,找的过程形成的链条就是原型链。而
__proto__
就是 2 个节点中的链条
那么问题来了,既然 func
是一个函数对象,函数对象是有 prototype
属性的,那么 func.prototype.__proto__
等于啥呢?
验证一下:
function func() {}
console.log(typeof func.prototype) // object
console.log(func.prototype.__proto__ === Object.prototype) //true
2
3
# 1.3 结论
func.prototype
是一个对象- 既然是对象,那么他的原型就是
Object.prototype
- 所以当创建一个构造函数的时候,JS 自动执行了下面的代码:
//我们手动创建func函数
function func() {}
//javascript悄悄咪咪执行以下代码:
func.__proto__ = Function.prototype //实例的 __proto__ 属性主动指向构造的 prototype
func.prototype = {
constructor: func,
__proto__: Object.prototype //我们刚刚才在上面验证的,你别又忘记了
}
2
3
4
5
6
7
8
基于上面的结论,画了一张图:(PS:这只是入门级的第一步图,这个图一定要记清楚,后面的图会更复杂)
new 原理不懂的可以看这里:new-一个对象的过程中发生了什么
图中的步骤已经标清楚,可以根据步骤一点点看,而不是一下子看到那么大的流程图无从入手
# 2. 原型链
上图中已经体现出一些原型链的东西:
func
--__proto__
-->Function.prototype
--__proto__
-->Object.prototy
--__proty__
-->null
# 这样的链条,有什么作用呢?用处体现在哪里?
来看这段代码:
// 定义一个动物对象
function Animal(name, age) {
this.name = name
this.age = age
this.home = '宠物店'
}
Function.prototype.functionSay = function() {
console.log('调用了Function - say方法')
}
Object.prototype.objectSay = function() {
console.log('调用了Object - say方法')
}
Animal.prototype.eat = function() {
console.log(this.name + ' Eating')
}
Animal.prototype.run = function() {
console.log(this.name + ' running')
}
const dog = new Animal('旺财', 1) // 创建一个动物对象
console.log(dog) // Animal:{name:'旺财',age:1,home:'宠物店'}
dog.eat() // 旺财 Eating
dog.run() // 旺财 running
dog.sayHello() // Uncaught TypeError: dog.sayHello is not a function
console.log(dog.name) // 旺财
console.log(dog.sex) // undefined
console.log(dog.home) // 宠物店
const cat = new Animal('咪咪', 2)
cat.sayHello = function() {
console.log('Hello,my name is ' + this.name)
}
cat.eat() // 咪咪 Eating
cat.eat = function() {
console.log(this.name + ' 在吃猫粮')
}
cat.eat() // 咪咪 在吃猫粮
cat.sayHello() // Hello,my name is 咪咪
const bosi = new cat() // Uncaught TypeError: cat is not a constructor
// Cannot set property 'play' of undefined
cat.prototype.play = function() {
console.log(this.name + ' playing')
}
dog.objectSay() // 打印了什么?
cat.functionSay() // 打印了什么?
cat.__proto__.sleep = function() {
console.log(this.name + ' sleeping')
}
dog.sleep() // 旺财 sellpinig
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
以下问题从 JS 的原型和原型链原理解答:全部答对,说明原型和原型链已经过关了
1. dog 为什么会有`eat` 和 `run` 方法?
- dog 对象是从
new Animal
得来。根据 new 的原理,Animal
是一个构造函数,接收 2 个参数,所以为 dog 对象赋值了name
和age
。并且Animal
是函数对象
。拥有protorype
属性,eat
。run
方法都挂载在了protorype
上。 - 调用
dog.eat
的时候,会先查询 dog 对象是否存在 eat 方法(不存在)。于是就往原型链上面找:dog.__proto__.eat
- 由于
dog.__proto__ === Animal.prototype
。最后调用到了Animal.prototype.eat()
。run
同理
2. dog 调用 sayHello 为什么会报错?
- 理由和 1 差不多,在 dog 自身对象上找,
找不到 sayHello
。往原型链上找,最后找到原型链顶端null
。执行null.sayHello()
自然就报错:dog.sayHello is not a function
3. dog 的`home`是从哪里来的?
- dog 自身并不存在
home
属性。于是在 Animal 对象上找到了 Home 属性。这个称之为继承
。new 出来的对象中 继承了 Animal 的所有属性和方法
4. cat 能调用 `sayHello`?
- 因为 cat 对象在创建后,在 cat 对象上赋值了一个 sayHello 方法。调用方法的时候第一个就找到了 cat 上的方法,所以就可以执行
5. cat 2 次 `eat` 方法为什么不一样?
- 第一次执行:第一次执行,cat 方法还没挂载 eat 方法。于是找到原型链上的 eat
- 第二次执行:这时候 cat 已经挂载了 eat 方法,所以就直接执行自己的 eat 方法,这成为
方法重写
6. `new cat` 为什么会报错?错在哪里?
- new 的操作实际上是
被new的对象的prototype
复制给新的对象的__proto__
。这时候的cat
只是一个对象
。对象不存在prototype
对象。所以就 new 失败了
7. 为什么不能在 cat 的 `prototype` 上挂载方法?
- cat 只是一个对象,new Animal 生成的一个对象,对象不存在
prototype
属性。
8. dog 为什么可以调用 `sleep`?
- 首先,
cat.__proto__
挂载了一个sleep
方法。cat.__proto__ === Animal.prototype
。相当于 sleep 挂载在了 Animal 的原型上(当然极度不推荐这么去用) - dog.sleep 调用的时候,自身没有 sleep 方法。在原型上(Animal)中找到了。所以就可以调用
9. 最后 objectSay、functionSay 方法打印了什么?为什么
- objectSay 打印了
调用了Object - say方法
- functionSay 报错:
Uncaught TypeError: cat.functionSay is not a function
因为根据原型链,dog 上没有 objectSay、functionSay
。就到 Animal
上找,Animal.prototype
上也没有这 2 个方法。就继续往上一层去找 Animal.prototype.__proto__
=== Object.protytype
。找到了 Object 上有 objectSay
方法,调用成功,而 functionSay
还没找到。继续找 Object.protytype.__proto__
,原型链顶层是null
、于是还没找到 functionSay
。最后报错
# 关于 问题9
的一点拓展
来简化下代码:
function Animal() {}
Function.prototype.functionSay = function() {
console.log('调用了Function - say方法')
}
Object.prototype.objectSay = function() {
console.log('调用了Object - say方法')
}
var dog = new Animal()
dog.objectSay() // 可以打印
dog.functionSay() // 报错
2
3
4
5
6
7
8
9
10
11
12
13
14
基于原型链的思考:
console.log(dog.__proto__ === Animal.prototype) // true
console.log(Animal.__proto__ === Function.prototype) // true
console.log(Animal.__proto__.functionSay()) // 调用了Function - say方法
2
3
上面代码并没有错,这时候就很容易绕进去,dog
原型是Animal
。而Animal
是从Function
去new
出来的(只是用了字面量形式,实际还是 new 的方法)。
那原型链不应该是 dog
找到 Animal
找到 Function
然后 Function 在找到 Object
,最后到 null
吗?
错!!
既然是原型链,那应该是一条链条下去,问题出在这里:
Animal.prototype
和 Animal.__proto__
并不在同一链条上,如果从 Animal.prototype
跳到 Animal.__proto__
去找到Function.prototype
那就不是一条链条,而是一条分岔路了!
来个图:
注意看红色执行流程 而蓝色的流程一直没执行到
代码解析:
dog.__proto__ === Animal.prototype // true
Animal.prototype !== Animal.__proto__ // true 他们并不相等
Animal.prototype.__proto__ === Object.prototype // true
// 按上面的强行理解的话
// 最终的原型链应该是:
dog.__proto__.__proto__.objectSay()
dog.__proto__.__proto__.functionSay()
// 根据上面的等式。
// 他们最后调用的其实还是Object的原型
Object.prototype.objectSay() // 方法存在
Object.prototype.functionSay() // 方法不存在
2
3
4
5
6
7
8
9
10
11
12
13
14