在这节,老师讲了面向对象的三大特性:1、全局变量;2、封装;3、继承;
现在我就我自己的理解总结一下这节课的内容,并提出相应的疑惑,望老师解答
其一:全局变量
声明变量的方法有三:1,在全局对象中var a一个变量;2,window.a一个变量;3.直接去掉var,如:a=1一个变量。
第一种声明方式是我们声明变量的标准形式。现在我主要说说第二种和第三种声明变量的方式:
对于第二种方式和第三种方式声明的变量,不同于标准声明1:
其实实质上就是创建了全局对象的一个新属性,本质上不是变量。并且这两种方式声明的变量只在代码执行阶段才会出现,在预解析代码时是不会出现在执行环境中(变量对象)中。
对于第二种方式和第三种方式声明的变量,不同于标准声明2:
这两种声明变量的方式是可以运用delete操作符删除的,而标准声明方式却不能,原因在于标准声明的变量有一个Donot delete属性。(也除例外情况,如:在eval上下文中,标准声明的方式没有Donot delete这个属性,所以也是可以删除的)
注:例外在声明变量时应少声明全局变量,会引发一些不必要的影响
其二:封装
说到封装,先讲讲三大声明的形式:1、private;2、protected;3、public
js中并没有这些特性,但我们可以通过代码人为实现这三种特性
对于私有类型(private),我们便可以采用封装的形式,将函数中一部分信息隐藏,但为了给外部引用,提供相应的接口(我觉得这就是引用闭包的特性,来实现这些接口)
对于保护类型(protected),虽然没有其实现方法,但我们可以通过人为的书写规范来辨别,如:将受保护类型的变量,可以在变量名前加下划线来区分它与公有类型。
其三:继承(困扰我好久)
其实,老师上课说的继承的两种实现方式,只说了其实现过程,并没有具体和我们分析分析,所以我就找了好多资料,来做一个总结吧!
先说说普通的原型链继承:
function sub(){
this.proterty=true;
}
sup.prototype.getValue=function(){
return this.property;
}
function sub(){
this.subValue=false;
}
sub.prototype=new sup();
var instance=new sub();
alert(instance.getValue());
原型链继承:
原理:
就是通过原型链来实现的继承,就是你的sub函数的原型对象的_proto_属性指向sup的原型对象,而sub实例化的instance对象的_proto_属性指向sub的原型对象,通过这条链来查找所需方法和属性。
优点:可以在sup的原型对象中添加的方法可以实现共享,利于代码的复用
缺点:如果sub中的引用类型的属性会被所有实例化的对象共享,不能保证各实例化对象的不同状态。
为了解决原型链继承这个缺点,我们引用了借用构造函数继承的方法:
function sup(){
this.num=[1,2,3];
}
function sub(){
sup.call(this);
}
var instance1=new sub();
instance1.num.push(‘4‘);
var instance2=new sub();
instanc2.num.push(‘5‘);
借用构造函数继承:
原理:
就是通过这行关键代码来实现继“sup.call(this);”,这行代码的意思就是,在sub的词法环境中我们在全局作用域中调用sup函数(借用sup的函数),所以执行完这行代码一行以后,sub就继承了sup。之后在实例化sub时,每个实例化对象,都会有num属性的副本,这其中就是通过this的指向来完成的。如:实例化instance1时,sub中的this就会指向instance1,而sub会借用sup的this,所以每个实例化的num属性都会不一样,这样就可以实现各实例化对象的不同状态。
优点:可以实现各实例化对象的不同状态;
缺点:不利于代码的复用
综合上面两种继承方法的优缺点,就引出了第三种继承方案:组合继承
function sup(name){
this.name=name;
this.num=[1,2,3]
}
sup.prototype.sayName=function(){
alert(this.name);
}
function sub(){
sup.call(this);
}
sub.prototype=new sup();
sub.prototype.construcor=sub;
var instance1=new sub();
instance1.num.push(‘4‘);
instance1.sayName();
var instance2=new sub();
instanc2.num.push(‘5‘);
instance2.sayName();
组合继承:
原理:综合以上两种方式的原理来实现;
优点:可以让sub的不同实例化对象分别有自己的状态属性,而且又可以使用相同的方法
缺点:两次调用sup构造函数,不利于性能优化
为了解决组合继承这个缺点,我们引用了寄生式组合继承的方法:
function imp(sub,sup){
var proto=Object(sup.prototype);
proto.constructor=sup;
sub.prototype=proto;
}
function sup(name){
this.name=name;
this.num=[1,2,3];
}
sup.prototype.sayName=function(){
alert(this.name);
}
function sub(){
sup.call(this);
}
imp(sub,sup);
寄生式组合继承:
原理,之前说的两次调用sup构造函数,第一次是sup.call(this);第二次是创建sub对象原型的时候;分析第二次调用sup构造函数,其实实质上就是我们需要的只是sup原型对象的一个副本,说白了就是希望sub原型对象的_proto_属性指向sup的原型对象。为了解决这个问题,我们可以用一个函数封装来实现
function imp(sub,sup){
var proto=Object(sup.prototype);
proto.constructor=sup;
sub.prototype=proto;
}
只要我们创建一个sup的原型对象,在把sup原型对象的construct指或sup,最后在把sub的原型对象指向sup的原型对象。我们就可以实现继承sup原型对象上的方法了
优点:避免在sub的原型对象上创建不必要的属性。
说完了我对高程上继承的理解,我说说老师课上继承的两种方式:1、类继承;2、原型继承
其一:类继承
(function(){
function classA(){};
classA.classMethod=function(){};
classA.prototype.api=function(){};
function classB(){
classA.apply(this,arguments);
}
classB.prototype=new classA();
classB.prototype.constructor=classB;
classB.prototype.api=function(){
classA.prototype.api.call(this,arguments);
}
var b=new classB()
})()
关于这段代码,我提出两个疑问:
疑惑一:
classB.prototype.constructor=classB;
关于这句语句:
我的理解:将classB原型对象上的constructor指回classB;
原因是执行完classB.prototype=new classA();以后classB的原型对象指向了classA的原型对象,而classA的原型对象中的constructor属性是指向classA的
我的疑惑:如果不加这句语句,那么classB原型对象上的constructor属性指向哪里?是classA吗?(我理解的constructor属性指向prototype属性所在的构造函数)如果不加这句语句,那么通过classB实例化的对象b,b的构造器会指向classA不会指向classB吗?对原形链有什么影响吗?请老师好好帮我分析分析,我实在想不出!
疑惑二:
classA.prototype.api.call(this,arguments);
关于这句语句:
我的理解:就是在classB的原型对象上添加api方法。我认为在classA的原型对象中已经添加了api方法,我们可以通过原型继承(原型链),在classB实例化的对象中访问到api方法,不需要再在classA的原型对象上在调用一次这个方法.请问老师这样写还有其他目的吗?还是我的理解不准确?
其二:原型继承
(function(){
var proto={
action1:function(){}
}
var obj=Object.create(proto);
})()
对于原型继承我的理解是:它和之前说的原型链继承有同样的弊端,不利于每个实例对象的属性状态的不同的保存。
这种继承还存在兼容性,我们可以封装一个函数解决。
以上就是我对继承的理解,因为还是学生,没有实践经历,理解层次还处于理论阶段,可以在以后实践会有不同吧!慢慢来。
在前端自学的路上,遇到好多问题,想对继承方法的命名不同,导致我不同的理解,弄混之前的理解。后现在觉得名字什么的只是一个说法,理解才是最重要的。所以我们可以在应用中针对不同的案例实现不同的继承方法