1. 继承(inherit)
js本身是没有继承这个特性或语法的,所以js的继承比较特殊。
本质:拿来主义,原本不是自己的,但是自己可以直接用,好像就是自己的一样,这个就是继承。
因此在js中,凡是符合上述特征的都是继承(js中继承没有特定的语法)。
(1)最简单的继承就是
原型式继承
就是写一个构造函数,在原型中添加成员,那么实例对象就含有了该成员,这就称为实例对象继承自原型。
(2)
混入式继承(mix)
在js中混入就是将一个对象的成员加到另一个对象中。
典型的语法结构:
//将obj2混入到obj1中,也称obj1继承自obj2
function mix(obj1,obj2){
for(var k in obj2){
obj1[k] = obj2[k];
}
}
一般在混入的时候,都是混入的函数,所以考虑深拷贝的机会较少,在jq框架中,有一个方法叫 extend ,该方法实现 jq 中的混入。
该方法具有的特征是:
① 将多个对象混入到一个对象中
mix ( dest, obj1, obj2, obj3, ... )
② 实现深拷贝
③ 实现原型式继承
④ 实现对象的构造(不使用构造函数创建对象)
(3)在实际开发的时候,常常是这两种方法混合使用(
混合式继承)
在构造函数的原型上使用混入,那么原型对象就具有了很多方法属性等成员,那么构造函数的实例对象就自动的继承了这些成员,而且还有共享。
// 1.准备一个构造函数
function Role( name ) {
this.name = name;
}
// 2.在原型中准备一个 extend 方法
Role.prototype.extend = function ( obj ) {
// 将 obj 的成员混入到 原型对象中
for ( var k in obj ) {
this[ k ] = obj[ k ];
}
};
// 3.尽情的扩展( 继承 )
// 为了简单这么处理
Role.skill = Role.prototype;
// 法术攻击
Role.skill.extend({
fireBall: function () {
console.log( '火球之术' );
},
bigFireBall: fucntion () ...
});
// 雷电 thunder
Role.skill.extend({
qidoli: fucntion() { .... }
...
});
// 增加技能
Role.skill.extend({
iceSkill: fucntion() { .... }
...
});
// 4.创建角色
var sasigi = new Role( 'f' );
2. 经典继承语法
在ES5出现以前,就有人模拟了继承的方式,就是使用一个对象,创建出另一个对象出来,保证被创建出来的对象的原型就是这个指定的对象。
//调用该函数,可以创建一个继承自参数给定的对象的对象
function create(baseObj){
function F(){}
F.prototype = baseObj;
return new F();
}
//可以创建一个对象,该对象继承自一个数组
var myArray = create( [] );//可以将myArray作为数组使用,是真数组
/*增*/
myArray.push('第一个被push进来的数据');
myArray.unshift('第二个被unshift加入的数据');
myArray[myArray.length++]='利用数组索引加入的数据';
/*删*/
myArray.pop();//删并返回最后一个
myArray.shift();//删并返回第一个
myArray.splice(1,1);
/*改*/
myArray[1] = '哈哈哈';
myArray.splice(1,1,'a','b','c');//从下标为1开始,删掉1个元素,把后面的数据插入此位置
/*查*/
var i1 = myArray.indexOf('b');
在较新的 ES5 的规范中已经内置了该算法,使用
Object.create 来实现该功能。
所谓的简单对象,首先要保证简单,没有多余的复杂数据:
var obj = Object.create(null);
console.log(obj); //空的简单对象,里面什么都没有
没有写构造函数,obj 就是 Object 创建出来的。
var obj = Object.create(base);//此时是一种特殊情况,obj 没有构造函数,是内部创建的
3. 比较高级的继承方法
Object.create(base) => sub
该方法有两大缺点:
① 没有构造函数,无法复用
② 没有扩展,创建的子对象与父对象其实一模一样,只是层级结构不同
我们需要提供一个函数,满足以下功能:
① 可配置构造函数,由用户决定构造函数应该如何定义
② 可返回构造函数
③ 应该有继承
要求可以配置,其实就是可以自己定义属性和方法
function createClass( options ){
return function(){ //这个函数就应该是最终的构造函数
}
}
由于需要重新配置构造函数的内容与原型,也要配置方法,可以让 options 中必须带有方法数据,因此代码可以修改为另一种形式:
function createClass( options ){
//要求 options中必须包含 constructor 和 methods
//constructor是构造函数,它需要被返回
//methods是该对象应该具有的方法,应该将方法加到原型中
options.constructor.prototype = options.methods;
return options.constructor;
}
简单优化后:
function createClass( options ){
var ctr = options.constructor;
ctr.prototype = options.methods;//用替换原型的办法
return ctr;
}
使用:
属性应该放在实例对象中,方法应该放在原型中。
因此,两个参数,第一个参数用于配置实例对象的成员,第二个参数用于描述原型。
var Person = createClass({
constructor: function(name,age,gender){
this.name = name;
this.age = age;
this.gender = gender;
},
methods: {
sayHello: function(){
console.log( '你好,我是'+ this.name );
},
run: function(){
console.log( '你好,'+ this.name + '在跑' );
}
}
});
var p = new Person('jim', 19, '男');
p.sayHello();
p.run();
此时已经有Person了,希望再派生一个Student出来
应该提供需要继承的目标(对象?函数?)
实现如下形式:
那么将需要继承的对象传入
要求options提供同一个base属性,表明需要继承谁
代码中的ctr的原型应该由base提供
function createClass( options ){
var ctr = options.constructor,
base = options.base || Object.prototype;
ctr.prototype = Object.create(base);//如果直接把base赋给原型,会出bug,派生对象中增加的东西在原型对象中也会增加
//方法的加入
for(var k in options.methods){
ctr.prototype[k] = options.methods[k];
}
return ctr;
}
正确的(ctr.prototype = Object.create(base);):
错误的(ctr.prototype = base;):