一、原型链继承
子类型的原型为父类型的一个实例对象。
// 父类型 function Parent(name, age) { this.name = name; this.age = age; this.showName = function() { console.log(‘parentName‘, this.name); } } Parent.prototype.showAge = function() { console.log(‘parentAge‘, this.age) } // 子类型 function Children(sex) { this.sex = sex; this.showSex = function() { console.log(‘childrenSex‘, this.sex); } } // Children.prototype.sayHello = function () { }//在这里写子类的原型方法和属性是无效的, // 因为会改变原型的指向,所以应该放到重新指定之后 Children.prototype = new Parent(); // 子类型的原型为父类型的实例对象 Children.prototype.sayHello = function () { console.log(‘children.sayHello‘) } // 注:在这里写 var boy = new Children(‘boy‘); boy.showSex(); // childrenSex boy boy.showAge(); // parentAge undefined boy.showName(); // parentName undefined boy.sayHello(); // children.sayHello console.log(‘boy‘, boy); // Children{sex:"boy", showSex:f(){}} console.log(‘boy.sex‘, boy.sex); // boy console.log(‘boy.name‘, boy.name); // undefined console.log(‘boy.age‘, boy.age); // undefined
特点:
1.父类新增原型方法/原型属性,子类都能访问到
2.简单,易于实现
缺点:
1.无法实现多继承
2.来自原型对象的所有属性被所有实例共享
3.创建子类实例时,无法向父类构造函数传参
4.要想为子类新增属性和方法,必须要在Children.prototype = new Parent() 之后执行,不能放到构造器中
二、借用构造函数继承
在子类型构造函数中通用call()调用父类型构造函数
function Parent(name, age) { this.name = name; this.age = age; this.showName = function() { console.log(‘parentName‘, this.name); } } Parent.prototype.showAge = function() { console.log(‘parentAge‘, this.age); } function Children(name, age, sex) { Parent.call(this, name, age, sex); // 相当于 this.Parent(name, age) // this.name = name; // this.age = age; this.sex = sex; this.showSex = function() { console.log(‘childrenSex‘, this.sex); } } Children.prototype.sayHello = function() { console.log(‘children.sayHello‘) } var boy = new Children(‘cb‘, 21, ‘boy‘) boy.showSex(); // childrenSex boy boy.showAge(); // boy.showAge is not a function boy.showName(); // parentName cb boy.sayHello(); // children.sayHello console.log(‘boy‘, boy); // Children{age:21, name:"cb", sex:"boy",showName: f(){}, showSex:f(){}} console.log(‘boy.sex‘, boy.sex); // boy console.log(‘boy.name‘, boy.name); // cb console.log(‘boy.age‘, boy.age); // 21
特点:
1.解决了原型链继承中子类实例共享父类引用属性的问题
2.创建子类实例时,可以向父类传递参数
3.可以实现多继承(call多个父类对象)
缺点:
1.实例并不是父类的实例,只是子类的实例
2.只能继承父类的实例属性和方法,不能继承原型属性和方法
3.无法实现函数复用,每个子类都有父类实例函数的副本,影响性能
三:原型链+借用构造函数的组合继承
通过调用父类构造,继承父类的属性并保留传参的优点,然后通过将父类实例作为子类原型,实现函数复用。
function Parent(name, age) { this.name = name; this.age = age; this.showName = function() { console.log(‘parentName‘, this.name); } } Parent.prototype.showAge = function() { console.log(‘parentAge‘, this.age); } function Children(name, age, sex) { Parent.call(this, name, age, sex); this.sex = sex; this.showSex = function() { console.log(‘childrenSex‘, this.sex); } } Children.prototype = new Parent(); Children.prototype.constructor = Children; // 相比方法二 修复构造函数指向的 Children.prototype.sayHello = function() { console.log(‘children.sayHello‘) } var boy = new Children(‘cb‘, 21, ‘boy‘) var p1 = new Parent(‘cb‘, 21) boy.sayHello(); // children.sayHello boy.showAge(); // parentAge 21 console.log(boy.constructor) // Children console.log(p1.constructor) // Parent
特点:
1.可以继承实例属性/方法,也可以继承原型属性/方法
2.不存在引用属性共享问题
3.可传参
4.函数可服用
缺点:
调用了两次父类构造函数,生成两份实例
一次是在创建子类型原型的时候,另一次是在子类型构造函数的内部,子类型最终会包含父类型对象的全部实例属性,但我们不得不在调用子类构造函数时重写这些属性。
四、组合继承优化1
这种方式通过父类原型和子类原型指向同一对象,子类可以继承到父类的公有方法当做自己的公有方法,而且不会初始化两次实例方法/属性,避免的组合继承的缺点。
function Parent(name, age) { this.name = name; this.age = age; this.showName = function() { console.log(‘parentName‘, this.name); } } Parent.prototype.showAge = function() { console.log(‘parentAge‘, this.age); } function Children(name, age, sex) { Parent.call(this, name, age, sex); this.sex = sex; this.showSex = function() { console.log(‘childrenSex‘, this.sex); } } Children.prototype = Parent.prototype; // 不会初始化两次实例方法/属性 但是没办法辨别是实例是子类还是父类创造的 Children.prototype.sayHello = function() { console.log(‘children.sayHello‘) } var boy = new Children(‘cb‘, 21, ‘boy‘); var p1 = new Parent(‘cb‘, 21) boy.sayHello(); // children.sayHello boy.showAge(); // parentAge 21 console.log(boy.constructor) // Parent console.log(p1.constructor) // Parent console.log(boy instanceof Parent) // true console.log(boy instanceof Children) // true
特点:不会初始化两次实例方法/属性,避免的组合继承的缺点
缺点:没办法辨别是实例是子类还是父类创造的,子类和父类的构造函数指向是同一个。
五、组合继承优化2
借助原型可以基于已有的对象来创建对象, var B = Object.create(A)
以A对象为原型,生成了B对象。B继承了A的所有属性和方法。
function Parent(name, age) { this.name = name; this.age = age; this.showName = function() { console.log(‘parentName‘, this.name); } } Parent.prototype.showAge = function() { console.log(‘parentAge‘, this.age); } function Children(name, age, sex) { Parent.call(this, name, age, sex); this.sex = sex; this.showSex = function() { console.log(‘childrenSex‘, this.sex); } } Children.prototype = Object.create(Parent.prototype); // 核心代码 Children.prototype.constructor = Children; // 核心代码 Children.prototype.sayHello = function() { console.log(‘children.sayHello‘) } var boy = new Children(‘cb‘, 21, ‘boy‘); var p1 = new Parent(‘cb‘, 21) boy.sayHello(); // children.sayHello boy.showAge(); // parentAge 21 console.log(boy.constructor) // Children console.log(p1.constructor) // Parent console.log(boy instanceof Parent) // true console.log(boy instanceof Children) // true
特点:完美继承
六、ES6中class的继承
ES6中引入了class关键字,class可以通过extends关键字实现继承,还可以通过static关键字定义类的静态方法,这比 ES5 的通过修改原型链实现继承,要清晰和方便很多。
ES5 的继承,实质是先创造子类的实例对象this,然后再将父类的方法添加到this上面(Parent.apply(this))。ES6 的继承机制完全不同,实质是先将父类实例对象的属性和方法,加到this上面(所以必须先调用super方法),然后再用子类的构造函数修改this。
需要注意的是,class关键字只是原型的语法糖,JavaScript继承仍然是基于原型实现的。
class Parent { // 调用类的构造方法 constructor(name, age) { this.name = name this.age = age } // 定义一般的方法 showName () { console.log("调用父类的方法") console.log(this.name, this.age); } } let p1 = new Parent(‘cb1‘, 100) console.log(p1) // Parent {age: 100, name: "cb1"} // 定义一个子类 class Children extends Parent { constructor(name, age, salary) { super(name, age) // 通过super调用父类的构造方法 this.salary = salary } showName () { // 在子类自身定义方法 console.log(this.name, this.age, this.salary); } } let s1 = new Children(‘cb2‘, 21, ‘20k‘) console.log(s1) // Children {age: 21, name: "cb2", salary: "20k"} s1.showName() // cb2, 21, 20k console.log(p1.constructor) /* class Parent { // 调用类的构造方法 constructor(name, age) { this.name = name this.age = age } // 定义一般的方法 showName () { console.log("调用父类的方法") console.log(this.name, this.age); } } */ console.log(s1.constructor) /* class Children extends Parent { constructor(name, age, salary) { super(name, age) // 通过super调用父类的构造方法 this.salary = salary } showName () { // 在子类自身定义方法 console.log(this.name, this.age, this.salary); } } */
特点:语法简单易懂,操作更方便
缺点:并不是所有的浏览器都支持class关键字
思考:Children.__proto__ === Parent ???
原文:https://www.cnblogs.com/chengbin1117/p/12563409.html