原型继承
上一篇我们学到了「原型链继承」
Professor.prototype = {
name: "Mr.Zhang",
tSkill: "JAVA",
};
function Professor() {
this.age = "40";
}
// 继承 Professor
Teacher.prototype = new Professor();
function Teacher() {
this.name = "Mr.Wang";
this.mSkill = "JAVASCRIPT";
}
// 继承 Teacher
Student.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.prototype
Student.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.prototype
Student.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