原型六问

  • 我们先用一句话综合描述一下原型,再一层一层剥开他的心。

    所有的函数都有一个prototype属性,存放着一个对象,当这个函数作为构造函数时,prototype中存放的对象就成为了构造函数构造出来的对象的原型。

  • 我们用一个小例子来理解一下这段话,有如下一段代码:

    1. // 声明了一个名为Fn的函数
    2. function Fn() { }
    3. // 用Fn()构造了一个对象,名为a
    4. let a = new Fn();
    5. // 将Fn()的prototype属性的值赋给b
    6. let b = Fn.prototype;
  • 接着我们通过一连串的问题来理解JavaScript中原型继承的实现

  • 不过首先我们先介绍一个需要用到的工具。

    [对象名] instanceof [构造函数名]用来判断一个对象是否为某个构造函数的示例。换句话说,判断是否是使用某个构造函数构造了某个对象。 typeof [变量名]用来查询一个变量的类型

【问题1】是不是Fn() 构造了a?

  1. a instanceof Fn // true

image.png

【问题2】既然Fn()构造了a,Fn.prototype(一个对象)就是a的原型咯?

  1. a.__proto__ === Fn.prototype // true

image.png

【问题3】因为b = Fn.prototype,也就是说,b是a的原型咯?

  1. a.__proto__ === b // true

image.png

【问题4】b是不是一个对象?

  1. b instanceof Object // true

image.png

【问题5】如果b是对象,那么Object.prototype就是b的原型咯?

  1. b.__ptoto__ === Object.prototype // true

image.png

【问题6】a, b和Object.prototype组成了一条原型链对吗?

  • 即原型链是否是:a —-> b —-> Object.prototype

【答】我们给 abObject.prototype 分别加上一个名为 identifier 的属性,然后使用console.dir(a) 打印一下 a 并全部展开看看:

  1. a.identifier = 'a在这儿';
  2. d.identifier = 'b在这儿';
  3. Object.prototype.identifier = 'Object.prototype在这儿';
  4. console.dir(a);

iShot     2020-05-12 下午03.34.34.png

关于Constructor

  • 在刚刚打印出来的b对象中有一个属性叫做 constructor,我们点开它会发现一个非常有意思的现象。

image.png

  • Fn()Fn.prototype呈现出一种“你中有我我中有你”的状态。

giphy.gif

  • 事实也正是如此
    • 构造函数的prototype属性中会存放它所构造对象的原型
    • 这个原型对象中的constructor属性会存放这个构造函数
  • 即:
    1. Fn.prototype.constructor === Fn // true

new 和 Object.create() 的区别

  • 在使用new时,其实包含了以下几步绑定: ```javascript // 执行let a = new Fn() 时发生的绑定:

// 创建一个简单的空对象 var obj = new Object(); // 将这个空对象的proto链接到Fn.prototype上 obj.proto = Fn.prototype; // 把构造函数的this指向obj,并执行构造函数把结果赋给result var result = Fn.call(obj); if (typeof(result) === ‘object’) { a = result; // 构造函数Fn的执行结果是引用类型,就把这个引用类型的对象返回给a } else { a = obj; // 构造函数Fn的执行结果是值类型,就返回obj这个空对象给a }

  1. - 关于newObject.create()的区别,stack overflow上有一个简洁而不简单的回答:
  2. > [【原文】](https://stackoverflow.com/questions/4166616/understanding-the-difference-between-object-create-and-new-somefunction)
  3. > Very simply said, `new X` is `Object.create(X.prototype)` with additionally running the `constructor` function. (And giving the `constructor` the chance to `return` the actual object that should be the result of the expression instead of `this`.)
  4. > 【译文】
  5. > 简单说来,new X 就是在执行 Object.create(X.prototype) 的同时执行constructor中的函数。(使得constructor中的函数有机会返回一个应该作为表达式结果的真正的对象而不是this
  6. <a name="kvXKr"></a>
  7. ### 基于原型的继承
  8. - 在了解了原型链之后,我们终于可以看一下JavaScript中基于原型的继承了。
  9. <a name="xPNVa"></a>
  10. #### 属性的继承
  11. - JavaScript 对象有一个指向一个原型对象的链。当试图访问一个对象的属性时,它**不仅仅在该对象上搜寻**,**还会搜寻该对象的原型**,以及该对象的原型的原型,依次层层向上搜索,直到找到一个名字匹配的属性或到达原型链的末尾。
  12. - 举个例子,还是对于刚才的那段代码,我们对它进行一些修改。
  13. ```javascript
  14. function Fn() {
  15. this.name = 'Barry';
  16. this.age = 24;
  17. }
  18. let a = new Fn();
  19. Fn.prototype.age = 18;
  20. Fn.prototype.gender = 'male';
  21. // 现在的原型链:
  22. // a ---> Fn.prototype ---> Object.prototype ---> null
  23. // 详细写法:
  24. // {name='Barry', age=24} ---> {age=18, gender='male'} ---> Object.prototype ---> null
  25. console.log(a.name);
  26. // name搜寻过程:
  27. // name是自身属性吗?是的!name属性值为'Barry'
  28. console.log(a.age);
  29. // age搜寻过程:
  30. // age是自身属性吗?是的!age属性值为24
  31. // 原型链上的age属性并不会被访问到
  32. // 即发生了属性屏蔽(Property Shadowing)
  33. console.log(a.gender);
  34. // gender搜寻过程:
  35. // gender是自身属性吗?不是?那么就去原型里找找吧!
  36. // gender是Fn.prototype的属性吗?是的!gender属性值为'male'
  37. console.log(a.singing);
  38. // singing搜寻过程:
  39. // singing是自身属性吗?不是?那么就去原型里找找吧!
  40. // singing是Fn.prototype的属性吗?不是?去上一层原型里继续找!
  41. // singing是Object.ptototype的属性吗?不是?去上一层原型里继续找!
  42. // 糟糕!上一层是null了,哪里都找不到singing
  43. // 返回undefined

函数的继承

  • JavaScript中对象中的函数其实也是对象的属性,所以与属性继承规则相同
  • 唯一需要特别注意的就是当函数被调用时,this指向的是当前继承的对象,而不是继承的函数所在的对象。