原型继承
上一篇我们学到了「原型链继承」
Professor.prototype = {name: "Mr.Zhang",tSkill: "JAVA",};function Professor() {this.age = "40";}// 继承 ProfessorTeacher.prototype = new Professor();function Teacher() {this.name = "Mr.Wang";this.mSkill = "JAVASCRIPT";}// 继承 TeacherStudent.prototype = new Teacher();function Student() {this.name = "Mr.Li";this.pSkill = "HTML/CSS";}const student = new Student();console.log(student);

但这样其实有个弊端,假设Student不想继承Teacher和Professor的name属性应该怎么办呢?
call 继承
那我们用call方法来指定属性试试
Teacher.prototype.wife = "Ms.Liu";function Teacher(name, mSkill) {this.name = name;this.mSkill = mSkill;}function Student(name, mSkill, age, major) {// 实际上也算不上继承,只是借用 Teacher 的属性Teacher.call(this, name, mSkill);this.age = age;this.major = major;}var student = new Student("Mr.Zhang", "JS/JQ", 18, "Computer");console.log(student.wife); // undefind 无法获取到 Teacher 的 prototype

使用call来继承Teacher的属性但是又获取不到Teacher的原型了。
原型+call 继承
如果我们把「原型继承」和「call继承」结合起来会怎么样呢?
Teacher.prototype = {pSkill: "JS/JQ",};function Teacher(name) {this.name = name;this.tSktill = "JAVA";}// 没有原型,那就加一个Student.prototype = Teacher.prototype;// Student 设置原型的 age 属性也会反应到 Teacher.prototypeStudent.prototype.age = 18;function Student() {Teacher.call(this, "Mr.Wang");}console.log(Teacher.prototype);console.log(new Student());

给Student.prototype最大的问题就是给Student.prototype赋值也会反映到Teacher.prototype上面。
圣杯模式
那我们就想有没有既可以实现继承又不会相互影响原型的办法呢?
要不我们加个「中转构造函数」试试?
// 隔离但是又可以继承Teacher.prototype = {pSkill: "JS/JQ",};function Teacher() {this.name = "Mr.Li";this.tSkill = "Java";}// 定义一个中转构造函数function Buffer() {}// 赋值原型,实现继承的效果Buffer.prototype = Teacher.prototype;Student.prototype = new Buffer();function Student() {this.name = "Mr.Wang";}// 当 Student.prototype.age 的时候其实是在 Buffer 的实例上赋值了一个 age 属性// Buffer:{// age: 20// prototype: Teacher.prototype// }// 这样就不会影响到 Teacher.prototypeStudent.prototype.age = 20;console.log(new Student());

我们创建了一个「中转构造函数」完美的实现了继承的关系,既可以互相不打扰,又可以获取到Teacher.prototype原型。
接下来优化封装一下「中转构造函数」。
function inherit(target, origin) {function Buffer() {}Buffer.prototype = origin.prototype;target.prototype = new Buffer();// 设置构造器和继承源target.prototype.constructor = target;target.prototype.super_class = origin;}function Teacher() {}function Student() {}inherit(Student, Teacher);var s = new Student();console.log(s);
利用立即执行函数封装:
var inherit = (function () {var Buffer = function () {};return function (target, origin) {Buffer.prototype = origin.prototype;target.prototype = new Buffer();// 设置构造器和继承源target.prototype.constructor = target;target.prototype.super_class = origin;};})();inherit(Teacher, Student);function Teacher() {}function Student() {}var s = new Student();console.log(s);
立即执行函数的作用是:防止全局变量污染,立即执行函数内变量命名比较随意,更利于后期的维护。
最后献上最经典的原型面试题:
function Foo() {getName = function () {console.log(1);};return this;}Foo.getName = function () {console.log(2);};Foo.prototype.getName = function () {console.log(3);};var getName = function () {console.log(4);};function getName() {console.log(5);}Foo.getName();getName();Foo().getName();getName();new Foo.getName();new Foo().getName();new new Foo().getName();
Foo.getName(); // 2,函数的属性可以直接访问getName(); // 4,作用域执行的时候,getName: undefind => function(){console.log(5)} => function(){console.log(4)}Foo().getName(); // 1,方法执行后函数返回this,普通函数 this 指向 window,函数内 getName 是全局的变量,所以重写了 getName: undefind => function(){console.log(5)} => function(){console.log(4)} => function(){console.log(1)}getName(); // 1,因为上面的AO被赋值为了 function(){console.log(1)}new Foo.getName(); // 2,. 的优先级高于 new ,所以函数访问的是函数的属性new Foo().getName(); // 3,() 的优先级比 . 高,函数实例化后返回this对象,this对象指向实例化对象,实例化对象本身没有getName()方法就会到Foo函数的prototype上寻找new new Foo().getName(); // 3,先执行 new Foo() 然后 .getName(),最后的 new 语句没有意义// 或者参考该博主解题思路:https://blog.csdn.net/RedaTao/article/details/107955687
