众所周知,Javascript是一门面向对象的语言,若是说针对面向对象来发问的话,我会想到两个问题,在js中,类与实例对象是如何建立的,类与实例对象又是如何实现继承的。es6
ES5中,尚未类的概念,而是经过函数来声明;到了ES6,有了class关键字,则经过class来声明app
// 类的声明 var Animal = function () { this.name = 'Animal'; }; // es6中class的声明 class Animal2 { constructor () { this.name = 'Animal2'; }
1.字面量对象
2.显示的构造函数
3.Object.create函数
// 第一种方式:字面量 var o1 = {name: 'o1'}; var o2 = new Object({name: 'o2'}); // 第二种方式:构造函数 var M = function (name) { this.name = name; }; var o3 = new M('o3'); // 第三种方式:Object.create var p = {name: 'p'}; var o4 = Object.create(p);
如何实现继承?
继承的本质就是原型链优化
/** * 借助构造函数实现继承 */ function Parent1 () { this.name = 'parent1'; } Parent1.prototype.say = function () { }; function Child1 () { Parent1.call(this); // 或Parent1.apply(this,arguments) this.type = 'child1'; } console.log(new Child1(), new Child1().say());
重点是这句:Parent1.call(this); 在子类的构造函数里执行父类的构造函数,经过call/apply改变this指向,从而致使父类构造函数执行时的这些属性都会挂载到子类实例上去。
问题: 只能继承父类构造函数中声明的实例属性,并无继承父类原型的属性和方法this
/** * 借助原型链实现继承 */ function Parent2 () { this.name = 'parent2'; this.play = [1, 2, 3]; } function Child2 () { this.type = 'child2'; } Child2.prototype = new Parent2(); var s1 = new Child2(); var s2 = new Child2(); console.log(s1.play, s2.play); s1.play.push(4);
重点就是这句: Child2.prototype = new Parent2(); 就是说 new 一个父类的实例,而后赋给子类的原型 也就是说 new Child2().__proto__ === Child2.prototype === new Parent2()当咱们在new Child2()中找不到属性/方法,顺着原型链就能找到new Parent2(),这样就实现了继承。
问题: 原型链中的原型对象是共用的,子类没法经过父类建立私有属性
好比当你new两个子类s一、s2的时候,改s1的属性,s2的属性也跟着改变prototype
/** * 组合方式 */ function Parent3 () { this.name = 'parent3'; this.play = [1, 2, 3]; } function Child3 () { Parent3.call(this); // 父类构造函数执行了 this.type = 'child3'; } Child3.prototype = new Parent3(); // 父类构造函数执行了 var s3 = new Child3(); var s4 = new Child3(); s3.play.push(4); console.log(s3.play, s4.play);
组合式就是原型链+构造函数继承,解决了前两种方法的问题,但也有不足:子类实例化时,父类构造函数执行了两次,因此有了下面的组合继承的优化1code
/** * 组合继承的优化1 * @type {String} */ function Parent4 () { this.name = 'parent4'; this.play = [1, 2, 3]; } function Child4 () { Parent4.call(this); this.type = 'child4'; } Child4.prototype = Parent4.prototype; var s5 = new Child4(); var s6 = new Child4(); console.log(s5, s6); console.log(s5 instanceof Child4, s5 instanceof Parent4); console.log(s5.constructor);
其实就是把原型链继承的那句 Child4.prototype = new Parent4(); 改成 Child4.prototype = Parent4.prototype; 这样虽然父类构造函数只执行了一次了,但又有了新的问题: 没法判断s5是Child4的实例仍是Parent4的实例 由于Child4.prototype.constructor指向了Parent4的实例;若是直接加一句 Child4.prototype.constructor = Child4 也不行,这样Parent4.prototype.constructor也指向Child4,就没法区分父类实例了。对象
若要判断a是A的实例 用constructor
a.__proto__.constructor === A
用instanceof则不许确, instanceof 判断 实例对象的__proto__ 是否是和 构造函数的prototype 是同一个引用。若A 继承 B, B 继承 C 在该原型链上的对象 用instanceof判断都返回ture
/** * 组合继承的优化2 */ function Parent5 () { this.name = 'parent5'; this.play = [1, 2, 3]; } function Child5 () { Parent5.call(this); this.type = 'child5'; } //注意此处,用到了Object.creat(obj)方法,该方法会对传入的obj对象进行浅拷贝 //这个方法做为一个桥梁,达到父类和子类的一个隔离 Child5.prototype = Object.create(Parent5.prototype); //修改构造函数指向 Child5.prototype.constructor = Child5
构造函数属性继承和创建子类和父类原型的连接继承
引入了class、extends、super关键字,在子类构造函数里调用super()方法来调用父类的构造函数。
在子类的构造函数中,只有调用super以后,才可使用this关键字,不然会报错。这是由于子类实例的构建,是基于对父类实例加工,只有super方法才能返回父类实例。ip
class Child6 extends Parent6 { constructor(x, y, color) { super(x, y); // 调用父类的constructor(x, y) this.color = color; } toString() { return this.color + ' ' + super.toString(); // super表明父类原型,调用父类的toString() } }
Class充当了ES5中构造函数在继承实现过程当中的做用
有prototype属性,有__proto__属性,这个属性在ES6中的指向有一些主动的修改。
同时存在两条继承链:一条实现属性继承,一条实现方法继承。
class A extends B {} A.__proto__ === B; //继承属性 A.prototype.__proto__ === B.prototype; //继承方法
ES6的子类的__proto__是父类,子类的原型的__proto__是父类的原型。
可是在ES5中 A.__proto__是指向Function.prototype的,由于每个构造函数其实都是Function这个对象构造的,ES6中子类的__proto__指向父类能够实现属性的继承。
只有函数有prototype属性,只有对象有__proto__属性 ;但函数也有__proto__属性,由于函数也是一个对象,函数的__proto__等于 Function.prototype。
//原型链接 Man.prototype = Object.create(Person.prototype); // B继承A的静态属性 Object.setPrototypeOf(Man, Person); //绑定this Person.call(this);
前两句实现了原型链上的继承,最后一句实现构造函数上的继承。