一、对象冒充实现继承
主要原理:构造函数使用this关键字给所有属性和方法赋值.因为构造函数只是一个函数,所以可使ClassA的构造方法称为ClassB的方法,然后调用它.
ClassB就会收到ClassA的构造方法中定义的属性和方法.
function classA(name, age) { this.name = name; this.age = age; this.say = function () { console.log('我叫' + this.name + ',今年' + this.age + '岁了'); }}function classB(name, age, sex) { /** 下面三行代码是关键 ,主要就是 this.classA(name,age) 构造函数的this此时已经指向了classB 的this */ this.classA = classA; this.classA(name, age); delete this.classA; /** */ this.sex = sex; this.saySex = function () { console.log('我叫' + this.name + ',我的性别是' + this.sex); }}var A = new classA('小明', 20);A.say();console.log('对象A', A);var B = new classB('小红', 21, '女');B.saySex();console.log('对象B', B);
对象冒充可以实现多继承,但是,如果ClassX和ClassY中存在两个同名的变量或方法,则ClassY会覆盖ClassX中的变量或方法.
function ClassC() { this.ClassX = ClassX; this.ClassX(); delete this.ClassX; this.ClassY = ClassY; this.ClassY(); delete this.ClassY;}
二、call和apply方式实现对象冒充
apply()和 call()。这两个方法的用途都是在特定的作用域中调用函数,实际上等于设置函数体内 this 对象的值。首先,apply()方法接收两个参数:一个 是在其中运行函数的作用域,另一个是参数数组。其中,第二个参数可以是 Array 的实例,也可以是 arguments 对象
function classA(name, age) { this.name = name; this.age = age; this.say = function () { console.log('我叫' + this.name + ',今年' + this.age + '岁了'); }}function classB(name, age, sex) { classA.call(this, name, age); // call 方法改变了classA构造函数的this的指向,此时的this指向了classB的this // classA.apply(this,new Array(name,age)) this.sex = sex; this.saySex = function () { console.log('我叫' + this.name + ',我的性别是' + this.sex); }}var A = new classA('小明', 20);A.say();console.log('对象A', A);var B = new classB('小红', 21, '女');B.saySex();console.log('对象B', B);
三、原型链继承
原型:每个函数都有一个 prototype(原型)属性,这个属性是一个指针,指向一个对象(原型对象), 而这个对象的用途是包含可以由特定类型的所有实例共享的属性和方法。如果按照字面意思来理解,那 么 prototype 就是通过调用构造函数而创建的那个对象实例的原型对象。使用原型对象的好处是可以 让所有对象实例共享它所包含的属性和方法。换句话说,不必在构造函数中定义对象实例的信息,而是 可以将这些信息直接添加到原型对象中。
原型对象上默认有一个属性 constructor,该属性也是一个指针,指向其相关联的构造函数。
通过调用构造函数产生的实例,都有一个内部属性,指向了原型对象。所以实例能够访问原型对象上的所有属性和方法。
构造函数、原型与实例之间的关系 => 每个构造函数都有一个原型对象,原型对象都包含一个指向构造函数的指针,而实例都包含一个指向原型对象的内部指针。
Person.prototype 指向了原型对象,而 Person.prototype.constructor 又指回了 Person。 原型对象中除了包含 constructor 属性之外,还包括后来添加的其他属性。Person 的每个实例—— person1 和 person2 都包含一个内部属性,该属性仅仅指向了 Person.prototype;
function Person() {}Person.prototype.name = '原型对象';Person.prototype.age = 20;Person.prototype.job = 'coder';Person.prototype.sayName = function () { console.log('say:', Person.prototype.name);}var person1 = new Person();var person2 = new Person();person1.sayName()person2.sayName()
原型链继承:如果Person原型对象变成了某一个类的实例 human, 这个实例human又会指向一个新的原型对象 huamanProto,那么 person1和person2 此时就都能访问 human 的实例属性和 huamanProto 原型对象上的所有属性和方法了。
function Person() { this.name = '人类'; this.skill = '说话';}function Huamn() { this.kind = '原始人'; this.feature = function () { console.log(this.kind + '已经会制造工具'); }}Huamn.prototype.say = function () { console.log('这是Human的原型 say 方法');}Person.prototype = new Huamn();var person1 = new Person();var person2 = new Person();person1.feature()person1.say()person2.feature()person2.say()
注意:原型链继承有一个缺点 => 单纯的原型链继承最大的一个缺点,在于对原型中引用类型值的误修改。
function Person() { this.name = '人类'; this.emotion = ['喜', '怒', '哀', '乐'];}function Worker(workId) { this.workId = workId;}Worker.prototype = new Person();var worker = new Worker(100);console.log(worker.emotion); //['喜', '怒', '哀', '乐']worker.emotion.push('愁');console.log(worker.emotion); //["喜", "怒", "哀", "乐", "愁"]var worker1 = new Worker(101);console.log(worker1.emotion); //["喜", "怒", "哀", "乐", "愁"]
原型上任何非引用类型的属性值都不会通过实例被重写,但是引用类型的属性值会受到实例的影响而修改。
四、混合方式继承
function Person() { this.head = '脑袋瓜子'; this.emotion = ['喜', '怒', '哀', '乐']; }//将 Person 类中需共享的方法放到 prototype 中,实现复用Person.prototype.eat = function () { console.log('吃吃喝喝');}Person.prototype.sleep = function () { console.log('睡觉');}Person.prototype.run = function () { console.log('快跑');}function Worker(workerdentID) { this.workerdentID = workerdentID; Person.call(this);}Worker.prototype = new Person(); //此时 Worker.prototype 中的 constructor 被重写了,会导致 Worker.prototype.constructor === PersonWorker.prototype.constructor = Worker; //将 Worker 原型对象的 constructor 指针重新指向 Worker 本身var worker1 = new Worker(201);console.log(worker1.emotion); //['喜', '怒', '哀', '乐']worker1.emotion.push('愁');console.log(worker1.emotion); //["喜", "怒", "哀", "乐", "愁"]var worker2 = new Worker(202);console.log(worker2.emotion); //["喜", "怒", "哀", "乐"]worker1.eat(); //吃吃喝喝worker2.run(); //快跑console.log(worker1.constructor); //Worker
先将 Person 类中需要复用的方法提取到 Person.prototype 中,然后设置 Worker 的原型对象为 Person 类的一个实例,这样 worker1 就能访问到 Person 原型对象上的属性和方法了。其次,为保证 worker1 和 work2 拥有各自的父类属性副本,我们在 Worker 构造函数中,还是使用了 Person.call ( this ) 方法。如此,结合原型链继承和借用构造函数(使用call或apply方法)继承,就完美地解决了之前这二者各自表现出来的缺点。
五、ES6 extends关键字继承
//类的定义class Animal { //ES6中新型构造器 constructor(name) { this.name = name; } //实例方法 sayName() { console.log('My name is ' + this.name); }}//类的继承class Programmer extends Animal { constructor(name) { //直接调用父类构造器进行初始化 super(name); } program() { console.log("I'm coding..."); }}var animal = new Animal('dummy'), wayou = new Programmer('wayou');animal.sayName(); //输出 ‘My name is dummy’wayou.sayName(); //输出 ‘My name is wayou’wayou.program(); //输出 ‘I'm coding...’