ES5有几种方式可以实现继承?分别有哪些优缺点?
最近公司在招外面包,面试也是一项体力活,得所有的问题梳理一遍。你得理解更深入,希望能和被面试者一起探讨问题,通过面试能学到一些知识,疫情时期,招人不易,找工作也不容易呀!
也是查了很多资料,若有整理不对之处欢迎纠正!
ES5有几种方式可以实现继承?分别有哪些优缺点?
1. 原型链继承 原型链继承的基本思想是利用原型让一个引用类型继承另一个引用类型的属性和方法。 原型链继承
function People() { this.name = 'sophia'; this.hobbies=['swimming', 'skiing'] } People.prototype.getName = function () { return this.name; }; function Man() { this.sex = 'male' } Man.prototype = new People(); Man.prototype.getSex = function(){ return this.sex } Man.prototype.constructor = Man; let person1 = new Man(); person1.hobbies.push('basketball') console.log(1101, person1) console.log(11, person1.getName()) //"sophia" console.log(22, person1.hobbies) // ["swimming", "skiing", "basketball"] let person2 = new Man(); person1.hobbies.push('football') console.log(22, person2.hobbies)
缺点
通过原型来实现继承时,原型会变成另一个类型的实例,原先的实例属性变成了现在的原型属性,该原型的引用类型属性会被所有的实例共享。
在创建子类型的实例时,没有办法在不影响所有对象实例的情况下给超类型的构造函数中传递参数
2. 借用构造函数
借用构造函数的技术,其基本思想为: 在子类型的构造函数中调用超类型构造函数。
function People(name) { this.name = name; this.hobbies=['swimming', 'skiing'] } function Man(name) { People.call(this, name) this.sex = 'male' } let person1 = new Man('sophia'); person1.hobbies.push('basketball') console.log(11, person1.name) console.log(22, person1.hobbies) let person2 = new Man('jack') person2.hobbies.push('football') console.log(33, person2.name) console.log(44, person2.hobbies)
优点:可以向超类传递参数解决了原型中包含引用类型值被所有实例共享的问题
缺点: 方法都在构造函数中定义,函数复用无从谈起,另外超类型原型中定义的方法对于子类型而言都是不可见的。
3. 组合继承(原型链 + 借用构造函数)
组合继承指的是将原型链和借用构造函数技术组合到一块,从而发挥二者之长的一种继承模式。基本思路:
使用原型链实现对原型属性和方法的继承,通过借用构造函数来实现对实例属性的继承,既通过在原型上定义方法来实现了函数复用,又保证了每个实例都有自己的属性。
function People(name) { this.name = name; this.hobbies=['swimming', 'skiing'] } People.prototype.getName = function () { return this.name; }; function Man(name) { People.call(this, name) this.sex = 'male' } Man.prototype = new People(); Man.prototype.getSex = function(){ return this.sex } Man.prototype.constructor = Man; let person1 = new Man('lily'); person1.hobbies.push('basket') console.log(11, person1.getName()) //"lily" console.log(22, person1.hobbies) // ["swimming", "skiing", "basket"] let person2 = new Man('lucy'); person2.hobbies.push('foot') console.log(33, person2.getName()) // "lucy" console.log(44, person2.hobbies) //["swimming", "skiing", "foot"]
缺点: 无论什么情况下,都会调用两次超类型构造函数:一次是在创建子类型原型的时候,另一次是在子类型构造函数内部。
优点:可以向超类传递参数,每个实例都有自己的属性,实现了函数复用
4、 原型式继承
定义:这种继承借助原型并基于已有的对象创建新对象,同时还不用创建自定义类型的方式称为原型式继承。 直接看代码更清晰:
function createObj(o) { console.log(11, o) function F() { } F.prototype = o; console.log(22, F) return new F(); } let parent = { name: 'lily', arr: ['hello', 'world'] }; var child1 = createObj(parent); child1.arr.push('apple1') console.log('----->', child1) console.log('------>', child1.name, '----->', child1.arr) let child2 = createObj(parent); child2.arr.push('banana') console.log('------>', child2.name, '----->', child2.arr)
5、ECMAScript5通过新增 Object.create()方法规范了原型式继承。
Object.create(proto[, propertiesObject])
proto
新创建对象的原型对象。
propertiesObject
可选。如果没有指定为 undefined,则是要添加到新创建对象的不可枚举(默认)属性(即其自身定义的属性,而不是其原型链上的枚举属性)对象的属性描述符以及相应的属性名称。这些属性对应Object.defineProperties()的第二个参数
const person = { isHuman: false, printIntroduction: function () { console.log(`My name is ${this.name}. Am I human? ${this.isHuman}`); } }; const me = Object.create(person); me.name = "Matthew"; // "name" is a property set on "me", but not on "person" me.isHuman = true; // inherited properties can be overwritten me.printIntroduction(); // expected output: "My name is Matthew. Am I human? true"
用 Object.create实现类式继承
下面的例子演示了如何使用Object.create()来实现类式继承。这是一个所有版本JavaScript都支持的单继承。
// Shape - 父类(superclass) function Shape() { this.x = 0; this.y = 0; } // 父类的方法 Shape.prototype.move = function(x, y) { this.x += x; this.y += y; console.info('Shape moved.'); }; // Rectangle - 子类(subclass) function Rectangle() { Shape.call(this); // call super constructor. } // 子类续承父类 Rectangle.prototype = Object.create(Shape.prototype); Rectangle.prototype.constructor = Rectangle; var rect = new Rectangle(); console.log('Is rect an instance of Rectangle?', rect instanceof Rectangle); // true console.log('Is rect an instance of Shape?', rect instanceof Shape); // true rect.move(1, 1); // Outputs, 'Shape moved.'
如果你希望能继承到多个对象,则可以使用混入的方式。
function MyClass() { SuperClass.call(this); OtherSuperClass.call(this); } // 继承一个类 MyClass.prototype = Object.create(SuperClass.prototype); // 混合其它 Object.assign(MyClass.prototype, OtherSuperClass.prototype); // 重新指定constructor MyClass.prototype.constructor = MyClass; MyClass.prototype.myMethod = function() { // do a thing };
使用 Object.create 的 propertyObject参数
var o; // 创建一个原型为null的空对象 o = Object.create(null); o = {}; // 以字面量方式创建的空对象就相当于: o = Object.create(Object.prototype); o = Object.create(Object.prototype, { // foo会成为所创建对象的数据属性 foo: { writable:true, configurable:true, value: "hello" }, // bar会成为所创建对象的访问器属性 bar: { configurable: false, get: function() { return 10 }, set: function(value) { console.log("Setting `o.bar` to", value); } } }); function Constructor(){} o = new Constructor(); // 上面的一句就相当于: o = Object.create(Constructor.prototype); // 当然,如果在Constructor函数中有一些初始化代码,Object.create不能执行那些代码 // 创建一个以另一个空对象为原型,且拥有一个属性p的对象 o = Object.create({}, { p: { value: 42 } }) // 省略了的属性特性默认为false,所以属性p是不可写,不可枚举,不可配置的: o.p = 24 o.p //42 o.q = 12 for (var prop in o) { console.log(prop) } //"q" delete o.p //false //创建一个可写的,可枚举的,可配置的属性p o2 = Object.create({}, { p: { value: 42, writable: true, enumerable: true, configurable: true } });