原型和原型链-基础,但是非常重要

6/24/2020 Javascript
  • 我们创造的每一个函数都有一个 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 属性)
1
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__:{}}
1
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
1
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 //我们刚刚才在上面验证的,你别又忘记了
}
1
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
1
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 对象赋值了 nameage。并且 Animal函数对象。拥有protorype属性,eatrun 方法都挂载在了 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() // 报错
1
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方法
1
2
3

上面代码并没有错,这时候就很容易绕进去,dog原型是Animal。而Animal是从Functionnew出来的(只是用了字面量形式,实际还是 new 的方法)。

那原型链不应该是 dog 找到 Animal 找到 Function 然后 Function 在找到 Object,最后到 null 吗?

错!!

既然是原型链,那应该是一条链条下去,问题出在这里: Animal.prototypeAnimal.__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() // 方法不存在
1
2
3
4
5
6
7
8
9
10
11
12
13
14
Last Updated: 5/9/2021, 11:13:04 PM