十二、对象继承深入、call_apply、※圣杯模式、※构造函数和闭包、※企业模块化
对象继承深入
原型链继承
原型链定义
对象沿着__proto__在原型上寻找属性形成一种链条式的继承关系,这种继承关系就叫做原型链。
例如:
Professor.prototype = {
name: 'Mr.Zhang',
tSkill: 'Java'
}
function Professor(){}
var professor = new Professor();
Teacher.prototype = professor;
function Teacher(){
this.mkill = 'JS/JQ';
this.name = 'Mr.Wang'
}
var teacher = new Teacher();
Student.prototype = teacher;
function Student(){
this.name = 'Mr.li'
this.pkill = 'HTML/CSS';
}
var student = new Student();
console.log(student);
通过指定原型的指向,使学生实例继承老师实例,老师实例继承教授实例,而教授实例继承它的原型,这样学生就可以继承原型链上的所有属性。
处在原型链底端的实例继承了原型链上所有的属性,可这样合适吗?有些属性并不需要,有些属性又重复了。
call_apply是怎么解决这个问题的?
call_apply
Teacher.prototype.TSkill = 'Vue';
function Teacher(name, mSkill){
this.name = name;
this.mSkill = mSkill;
}
function Student(name, mSkill, age, major){
Teacher.apply(this,[name, mSkill])
this.age = age;
this.major = major;
}
var student = new Student(
'white', 'JS', 18, 'computer'
);
console.log(student)
通过call和apply更改this指向,借用构造函数的属性和方法,而不能继承构造函数的原型。
一般来说我们更希望去继承这个原型对象,因为原型对象中一般写一些写死的方法,或者是实例化对象的公共方法,所以apply这个借用构造函数的属性也不太合理。
那我们直接把构造函数的原型对象指向另一个构造函数的原型对象,这样不就达到我们继承原型对象的目的了吗?
function Teacher(){
this.name = 'Mr.Liu'
}
Teacher.prototype = {
pSkill: 'JS/Vue'
}
var t1 = new Teacher();
function Student(){
this.name = 'white'
}
Student.prototype = Teacher.prototype;
Student.prototype.sport = 'baskerball';
var student = new Student();
console.log(student);
console.log(t1);
学生实例继承了老师构造函数的原型对象了吗? √
那更改了学生的原型对象,老师的原型的原型对象会更改吗? √
给学生的原型对象增加了一个属性,发现老师的原型对象中也增加了一模一样的一个属性,显然不合理!
能不能借用一个中间变量,通过中间变量,实例化对象既可以继承顶端构造函数的的原型对象,而修改自身的原型对象时,也不会更改所继承的原型对象。
这就是圣杯模式。
※圣杯模式
借用中间变量(圣杯),到达对象继承原型的效果。
实例化对象继承其他构造函数的原型对象属性/方法,并且更改实例化对象的构造函数的原型时,也不会影响其他构造函数的原型。
function Teacher(){
this.name = 'Mr.Liu';
this.tSkill = 'Java'
}
Teacher.prototype = {
pSkill: 'Js/jq'
}
function Student(){
this.name = 'white'
}
//中间构造函数的原型对象指向被继承的构造函数的原型对象
Buffer.prototype = Teacher.prototype;
function Buffer(){}
var buffer = new Buffer();
//底部的构造函数指向中间构造函数的实例
Student.prototype = buffer;
Student.prototype.age = 22;
var stu1 = new Student();
console.log(stu1);
console.log(Student.prototype.constructor);
console.log(buffer);
console.log(Teacher.prototype);
console.log(Teacher.prototype.constructor);
圣杯布局过程:
- 声明一个构造函数作为中间变量
- 中间构造函数的原型指向被继承的构造函数的原型对象
- 底部的构造函数的原型指向中间构造函数的实例
这样底部的实例化对象即可以继承顶部的原型对象,修改底部构造函数的原型对象时,也不会影响其他构造函数的原型对象。
而修改底部构造函数的原型对象时,是中间构造函数的实例化对象被更改了。
上图有一个问题:底部构造函数的原型对象的constructor不见了!!
原型对象的来源不见了,需要我们手动指定,而且我们可以对圣杯模式的这种继承方式进行一种封装:
封装圣杯模式
function inherit(Target, Origin){
function Buffer(){}
Buffer.prototype = Origin.prototype;
// 先写prototype再写new
Target.prototype = new Buffer();
// 还原构造器
// 需要手动写构造器指向本身
Target.prototype.constructor = Target;
// 设置继承源
// 还要很容易看到它所继承的构造函数
// 被继承的那一方(源)我们叫超类
// super是保留字,不能使用,源这个使用什么名字都可以
Target.prototype.super_class = Origin;
// Target.prototype.uber = Origin;
}
封装圣杯布局的过程:
- 声明中间构造函数
- 指定中间构造函数的原型对象的指向->顶端构造函数的原型(继承源)
- 指定底部构造函数的原型对象的指向->中间构造函数的实例
- 手动书写底部构造函数的原型构造器指向->底部构造器
- 在底部构造函数的原型中显示声明ta所继承的构造函数(超类)
※构造函数和闭包
闭包的写法:
- 普通函数形式写闭包:return一个内部函数,或者函数数组;
- 对象形式写闭包:在对象内部书写方法,return这个对象;
- 构造函数形式写闭包,this.xx=function(){},return this,实例化对象使用
自己的方法。
怎么把圣杯模式放到闭包中?
function test(){
var Buffer = function(){}
function inherit(Target, Origin){
Buffer.prototype = Origin.prototype;
Target.prototype = new Buffer();
Target.prototype.constructor = Target;
Target.prototype.super_class = Origin;
}
return inherit;
}
var inherit = test();
既然是执行返回函数,之后再执行,那么可以被IIFE包裹。
var inherit = (function(){
var Buffer = function(){}
return function inherit(Target, Origin){
Buffer.prototype = Origin.prototype;
Target.prototype = new Buffer();
Target.prototype.constructor = Target;
Target.prototype.super_class = Origin;
}
})();
这就是企业级写圣杯模式继承的方式。
为什么要放到闭包+IIFE中?
闭包使Buffer成为了私有变量,防止全局变量污染。
IIFE:页面加载就执行,拥有了自己的命名空间,使函数整体隔离了全局作用域,便于后期维护,二次开发。
※企业协同开发
※企业模块化IIFE+全局变量接收返回值
企业模块化目的:
- 防止全局变量被污染
- 页面一加载就拿到函数的返回值,等待执行
var initProgrammer = (function(){
return;
})();
多个功能怎么实现模块化呢:(原生开发)
- 把自启动函数的执行放到初始化函数中。
- 页面加载时调用初始化函数。
window.onload = function(){
init();
}
function init(){
initProgrammer();
initFunction();
}
var initProgrammer = (function(){
return;
})();
var initFunction = (function(){
return;
})();