继承
对于使用过面向对象语言【基于类 class】的语言的开发者来说, javascript有点一言难尽,因为 js是动态的,不具备正真意义class 语法,并且不具备一个class实现, es2016/es6 之后引入了 class这个概念,但也只是一个语法糖的结构,本身还是基于原型的。
当谈到继承时,JavaScript 只有一种结构:对象。每个构造函数实例对象[会自动继承原型中的属性和方法]( object )都有一个私有属性【隐式原型】(称之为 __proto__ )指向它的构造函数的原型对象(prototype【显示原型】,函数这个特殊对象除了有__proto__ 这个原型属性外,还有一个 prototype,指向函数的原型对象!!! )。该原型对象也有一个自己的原型对象( __proto__ ) ,层层向上直到一个对象的原型对象为 null
。根据定义,null
没有原型,并作为这个原型链中的最后一个环节。
几乎所有 JavaScript 中的对象都是位于原型链顶端的 Object
的实例。
结合生活中的例子,原型就可以理解为 现实生活中的 父与子这种关系的 父, 每个javascript 对象 a{null 除外,null
没有原型,并作为这个原型链中的最后一个环节} 在被定义出来,自动就会有一个 原型b与之关联,这个对象就是子, 原型就是父。这种关系, 这个b原型可能也会有父亲 原型c, 那么c也就相当于a的爷爷, a b c 这种链式关系就组成了 原型链 , a的原型b以及 a原型b的原型c 中 有的属性和方法 a都可以访问使用。 就和现实生活中 你没有属于自己的车,但是你父亲有 你就可以使用你父亲的车来代步。 有可能你父亲和你都没有房子,你爷爷有, 那么你和你父亲都有你爷爷的房子的使用权。
用实际例子来说明的话:
根据div的id 获取到div DOM, console.dir() 打印dom ,__proto__ 指向的是 HTMLDIVELEMENT
HTMLDIVELEMENT 也是一个对象,对象也有自己的原型 也就是 HTMLElement
HTMLElement 原型是 Node
Node 原型是 EventTarget
EventTarget 原型是 Object
Object 原型 没有 为 null
这一层层的嵌套 就形成一条原型链!!!
访问 a对象上的属性 a.attr /a.func() 时, 如果自身没有 这个属性或者方法, 那么就会形成原型链攀升, 会去a的原型b中找是否存在属性或者方法, b没有 就会去b的原型c中找,直至原型链顶端,都找不到就报错 ,找到了就执行。 注意 并且 this对象始终指向访问这个属性或者方法的对象!!!
原型的继承 才算做正在意义上的继承。 改变构造函数的原型不叫继承
function User2(){} //普通用户函数
let user22 = new User2();
// console.dir(User2);
User2.prototype.view = function(){
console.log('给 user2 原型加上view方法');
}
// console.dir(user22);
/* 假如还有一个Admin构造函数,也想用veiw方法,那么可以使用继承,
这里就会有 改变构造函数的原型的操作
Admin.prototype = User2.prototype;
这样其实就把 -- Admin构造函数原来的原型换成User2的原型了 -- ,
虽然Admin new 出来的实例确实可以用到view()方法了,但是会有问题
当管理员中要新增一个获取只有管理员能获取到的信息的方法
getAdminInfo()方法时,你也想写在原型中,避免每次实例化 getInfo()方法都会占用内存
这里其实就和普通用户的view() 写到一起了,那这样普通用户执行这个GetAdminInfo()就也能获取到权限之外的数据,!!!
这是有问题的,因为 直接改变了构造函数的原型其实不是继承。
【可以这么理解,张三继承李四的财产,但是自己的财产应该得到保留,而改变构造函数的原型,就相当于继承别人的 ,自己的却给丢弃了,这不叫真正的继承!!】
/* 正确的继承
1 Admin.prototype.__proto__ = User2.prototype;
把admin的原型的原型设置为 User2的原型,这样 admin实例就可以用到view()方法,构造函数原型也不会被改变!!!
2 Admin.prototype = Object.create(User2.prototype);
这里跟直接把User2.prototype 赋值给Admin.prototype 不一样,直接赋值由于对象赋值都是引用地址赋值,地址一样,那么添加删除方法都会在一块内存区域导致逻辑混乱,权限失效。
Object.create()时开辟一块新的地址,然后把User2.prototype放进去,这样不会改变原本的构造函数原型。只是Admin的原型指针暂时到新的新的地址中的对象上,但是会丢失 constructor 不能通过constructor 找到构造函数,除非补上这个属性【 补也不能乱补, 关于 for-in 遍历 in关键字会有原型攀升,所以基本上会把constructor等原型链上的属性设置为不可遍历【DefineProperty(Admin.prototype,"constructor",{在这里设置enumerable:false不可遍历})】】
而且在这个 Object.create(User2.prototype); 这个操作之前的Admin实例的prototype不会随着改变!
*/
多态:
/*继承=》多态!!! 面对不同的对象,响应不同的结果 */
function Dt(name,age){
this.name = name;
this.age = age;
}
Dt.prototype.show = function(){
console.log(this.description());
console.log(this.name,this.age);
};
console.dir(Dt);
function Aadmin(...args){
Dt.apply(this,args);
/*实现在继承Dt的子级中调用父级Dt中的方法
就不用每个子级要用的时候都去 重复定义
this.name = name;
this.age = age;
}
Aadmin.prototype = Object.create(Dt.prototype);
Aadmin.prototype.description = function(){
return '管理员态';
}
function Member(...args){
Dt.apply(this,args);
}
Member.prototype = Object.create(Dt.prototype);
Member.prototype.description = function(){
return '组员态';
}
function Enterprise(...args){
Dt.apply(this,args);//因为 对象中this指向的就是调用方法的那个对象,
/* 下面new 一个实例时,this就指向的是那个实例对象, Dt.apply(this,args)就实现把这个实例对象传入到Dt构造方法中,因为直接调用函数的话【非严格模式下,严格模式this为undefined】,this指向就是window对象 */
/* 这样就可以实现在继承Dt的子级中调用父级Dt中的方法!!! */
}
Enterprise.prototype = Object.create(Dt.prototype);
Enterprise.prototype.description = function (){
return '企业账户态';
}
/* 让aadmin。menber。enterprise 函数的原型 继承dt的原型 ,然后同一个父级方法面对不同的子级状态,相应不同的结果
【就好比大儿子,二儿子,还有小儿子同时找你要零花钱买零食,你会根据年龄分别给他们不同数量的钱,大儿子买的东西贵,就相应的多给一点,最小的儿子可能就要个仔仔棒,你可能就给的最少!!!】 */
for (const obj of [new Aadmin("管理员",100), new Member("组员",25), new Enterprise("百度",20)]) {
obj.show() ;
}
/* 没有继承特性的话,就需要分别在三个构造函数的原型上定义不同名字方法,然后分别判断实例是哪个构造函数实现的【obj instanceOf 构造函数name 】然后调用相应的方法 */
/* js 没有多继承这一特性,
a继承了b,你就没办法让a取继承c的同时,保留a继承b,
有一种办法就是a先继承b,然后让b继承c 这样 a就可以同时用到b,c中功能了,
但是这会有个问题,本来b,c是两个毫无关联的两个模块,为了方便a,硬给b,c加上了一层关联,这是我们不希望看到的!!!
可以使用 mixin 这个混合功能解决这个问题,因为原型也就是对象,可以往里面压入属性
具体实现 :就是把某个模块定义为一个对象,
const b = {
bfunc(){}
};
const c = {
__proto__:b 设置c的原型为b
cfunc(){
this._proto__.bfunc();b 设置c的原型为b后,在c中就可以使用b中的方法!!!
== super.bfunc(); super == this._proto__ super指向的是当前对象的原型
}
};
然后 a需要用什么功能就用 a.prototype = Object.Assign(a.prototype,b,...);
就把什么功能模块中的对象方法压入到 a的原型中,
这样 b,c,等其他模块之间就不会出现强行被加上关联的情况!!!
*/