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 
  } 
});

 

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM