原型继承

上一篇我们学到了「原型链继承」

  1. Professor.prototype = {
  2. name: "Mr.Zhang",
  3. tSkill: "JAVA",
  4. };
  5. function Professor() {
  6. this.age = "40";
  7. }
  8. // 继承 Professor
  9. Teacher.prototype = new Professor();
  10. function Teacher() {
  11. this.name = "Mr.Wang";
  12. this.mSkill = "JAVASCRIPT";
  13. }
  14. // 继承 Teacher
  15. Student.prototype = new Teacher();
  16. function Student() {
  17. this.name = "Mr.Li";
  18. this.pSkill = "HTML/CSS";
  19. }
  20. const student = new Student();
  21. console.log(student);

image.png

但这样其实有个弊端,假设Student不想继承TeacherProfessorname属性应该怎么办呢?

call 继承

那我们用call方法来指定属性试试

  1. Teacher.prototype.wife = "Ms.Liu";
  2. function Teacher(name, mSkill) {
  3. this.name = name;
  4. this.mSkill = mSkill;
  5. }
  6. function Student(name, mSkill, age, major) {
  7. // 实际上也算不上继承,只是借用 Teacher 的属性
  8. Teacher.call(this, name, mSkill);
  9. this.age = age;
  10. this.major = major;
  11. }
  12. var student = new Student("Mr.Zhang", "JS/JQ", 18, "Computer");
  13. console.log(student.wife); // undefind 无法获取到 Teacher 的 prototype

image.png
使用call来继承Teacher的属性但是又获取不到Teacher的原型了。

原型+call 继承

如果我们把「原型继承」和「call继承」结合起来会怎么样呢?

  1. Teacher.prototype = {
  2. pSkill: "JS/JQ",
  3. };
  4. function Teacher(name) {
  5. this.name = name;
  6. this.tSktill = "JAVA";
  7. }
  8. // 没有原型,那就加一个
  9. Student.prototype = Teacher.prototype;
  10. // Student 设置原型的 age 属性也会反应到 Teacher.prototype
  11. Student.prototype.age = 18;
  12. function Student() {
  13. Teacher.call(this, "Mr.Wang");
  14. }
  15. console.log(Teacher.prototype);
  16. console.log(new Student());

image.png
Student.prototype最大的问题就是给Student.prototype赋值也会反映到Teacher.prototype上面。

圣杯模式

那我们就想有没有既可以实现继承又不会相互影响原型的办法呢?
要不我们加个「中转构造函数」试试?

  1. // 隔离但是又可以继承
  2. Teacher.prototype = {
  3. pSkill: "JS/JQ",
  4. };
  5. function Teacher() {
  6. this.name = "Mr.Li";
  7. this.tSkill = "Java";
  8. }
  9. // 定义一个中转构造函数
  10. function Buffer() {}
  11. // 赋值原型,实现继承的效果
  12. Buffer.prototype = Teacher.prototype;
  13. Student.prototype = new Buffer();
  14. function Student() {
  15. this.name = "Mr.Wang";
  16. }
  17. // 当 Student.prototype.age 的时候其实是在 Buffer 的实例上赋值了一个 age 属性
  18. // Buffer:{
  19. // age: 20
  20. // prototype: Teacher.prototype
  21. // }
  22. // 这样就不会影响到 Teacher.prototype
  23. Student.prototype.age = 20;
  24. console.log(new Student());

image.png
我们创建了一个「中转构造函数」完美的实现了继承的关系,既可以互相不打扰,又可以获取到Teacher.prototype原型。

接下来优化封装一下「中转构造函数」。

  1. function inherit(target, origin) {
  2. function Buffer() {}
  3. Buffer.prototype = origin.prototype;
  4. target.prototype = new Buffer();
  5. // 设置构造器和继承源
  6. target.prototype.constructor = target;
  7. target.prototype.super_class = origin;
  8. }
  9. function Teacher() {}
  10. function Student() {}
  11. inherit(Student, Teacher);
  12. var s = new Student();
  13. console.log(s);

利用立即执行函数封装:

  1. var inherit = (function () {
  2. var Buffer = function () {};
  3. return function (target, origin) {
  4. Buffer.prototype = origin.prototype;
  5. target.prototype = new Buffer();
  6. // 设置构造器和继承源
  7. target.prototype.constructor = target;
  8. target.prototype.super_class = origin;
  9. };
  10. })();
  11. inherit(Teacher, Student);
  12. function Teacher() {}
  13. function Student() {}
  14. var s = new Student();
  15. console.log(s);

立即执行函数的作用是:防止全局变量污染,立即执行函数内变量命名比较随意,更利于后期的维护。

最后献上最经典的原型面试题:

  1. function Foo() {
  2. getName = function () {
  3. console.log(1);
  4. };
  5. return this;
  6. }
  7. Foo.getName = function () {
  8. console.log(2);
  9. };
  10. Foo.prototype.getName = function () {
  11. console.log(3);
  12. };
  13. var getName = function () {
  14. console.log(4);
  15. };
  16. function getName() {
  17. console.log(5);
  18. }
  19. Foo.getName();
  20. getName();
  21. Foo().getName();
  22. getName();
  23. new Foo.getName();
  24. new Foo().getName();
  25. new new Foo().getName();
  1. Foo.getName(); // 2,函数的属性可以直接访问
  2. getName(); // 4,作用域执行的时候,getName: undefind => function(){console.log(5)} => function(){console.log(4)}
  3. Foo().getName(); // 1,方法执行后函数返回this,普通函数 this 指向 window,函数内 getName 是全局的变量,所以重写了 getName: undefind => function(){console.log(5)} => function(){console.log(4)} => function(){console.log(1)}
  4. getName(); // 1,因为上面的AO被赋值为了 function(){console.log(1)}
  5. new Foo.getName(); // 2,. 的优先级高于 new ,所以函数访问的是函数的属性
  6. new Foo().getName(); // 3,() 的优先级比 . 高,函数实例化后返回this对象,this对象指向实例化对象,实例化对象本身没有getName()方法就会到Foo函数的prototype上寻找
  7. new new Foo().getName(); // 3,先执行 new Foo() 然后 .getName(),最后的 new 语句没有意义
  8. // 或者参考该博主解题思路:https://blog.csdn.net/RedaTao/article/details/107955687