理解 javascript 的繼承 - es5

javascript是一種基於原型鏈的語言,繼承是在平時的工做遇到最多的一個知識點下面討論下在es5中的幾種繼承方式.
1.原型繼承
原型繼承便是根據原型鏈的規則把父類掛在子類的prototype上面,原型繼承有兩種形式:繼承到實例對象,繼承到原型對象
注意:原型繼承在改變原型對象的時候,須要把原型裏面的constructor從新指向原構造函數,以避免破壞原型鏈規則。
第一種:繼承到實例對象
function Person() {
  this.name = "person";
  this.arr = [1];
  this.sayName = function() {
    return this.name;
  };
}
Person.prototype.gender = "man";
 
function Student() {
  this.age = "12";
}
Student.prototype = new Person();
console.log(Student.prototype.constructor); // f Person()
Student.prototype.constructor = Student; //改變原型對象的指向後,會破壞原型鏈的繼承規則,必須手動糾正,要把原型對象的構造函數指向自己的構造函數,
var student = new Student();
var person = new Student();
console.log(student.sayName()); //person
console.log(student.name); //person`
student.name = "student";
console.log(student.name); //student
console.log(person.name); //person
student.arr.push(2); //
console.log(student.arr); //[1,2]
console.log(person.arr); //[1,2]
缺點:
1.由於原型對象是公用一個對象,因此當原型對象裏面的值是引用類型時候,改變會發生相互影響。
2.建立子類實例時,沒法向父類構造函數傳參
 
第二種:繼承到原型對象
Student.prototype = Person.prototype;
Student.prototype.constructor = Student;
var student = new Student();
var person = new Person();
console.log(student.gender); //man`
Student.prototype.gender = "felman"; // 由於這裏的Student.prototype和Person.prototype是共同的引用,因此改變其中一個互相都有形象
console.log(student.gender); //felman
console.log(person.gender); //felman
 
解決辦法:取用一個空的函數做爲構造函數過分
var F = function() {};
F.prototype = Person.prototype;
Student.prototype = new F();
Student.prototype.constructor = Student;
var student = new Student();
var person = new Person();
console.log(student.gender); //man`
Student.prototype.gender = "felman"; 
console.log(student.gender); //felman
console.log(student.name); //undefined
console.log(person.gender); //man
 
// 封裝一個基於原型的繼承函數
function extend(Child, Person) {
  var F = function() {};
  F.prototype = Person.prototype;
  Child.prototype = new F();
  Child.prototype.constructor = Child;
  Child.uber = Parent.prototype; // 建立一個繼承指針
}
缺點:只繼承了父類的原型對象上的屬性方法,沒法繼承其構造函數裏面的屬性方法。
 
2.構造函數/綁定繼承
利用借用父類的構造函數來加強子類的實例,至關於複製一份父類的構造函數到子類
function Person(val) {
  this.name = val;
  this.arr = [1];
  this.sayName = function() {
    return this.name;
};
}
Person.prototype.gender = "man";
function Student(val) {
  Person.call(this, val);
  this.age = "12」; 
}
var student = new Student("student");
var student1 = new Student("student");
console.log(student.name); //student
console.log(student.age); //12
console.log(student.sayName()); //student
console.log(student.gender) //undefined
console.log(student.sayName === student1.sayName); //false
缺點:沒建立一個實例都要存一份sayName函數,沒法實現函數複用(沒有用到原型)
 
3.構造函數+原型=組合繼承
前面兩種繼承,一種沒法繼承父類的原型對象,一種沒法繼承父類的構造函數,因此選擇兩種結合的方式進行互相彌補。
function Person(val) {
  this.name = val;
  this.arr = [1];
}
Person.prototype.gender = "man";
Person.prototype.sayName = function() {
  return this.name;
};
 
function Student(val) {
  Person.call(this, val);
  this.age = "12";
}
Student.prototype = new Person();
Student.prototype.constructor = Student;
var student = new Student("student");
var student1 = new Student("student");
console.log(student.name); //student
console.log(student.age); //12
console.log(student.gender); //man
console.log(student.sayName()); //student
console.log(student.sayName === student1.sayName); //true
利用call實現構造函數的複製,和原型對象對父類實例的繼承實現對父類的完全繼承。
缺點:這種方式有兩個缺陷,一個是原型繼承的缺陷沒有避免,二是會重複繼承父類的構造函數裏面的屬性方法。
 
4.寄生組合繼承
爲了解決上面方法的缺點,並實現對父類的完美繼承,咱們就去要對其進行改進
function Person(val) {
  this.name = val;
  this.arr = [1];
}
Person.prototype.gender = "man";
Person.prototype.sayName = function() {
  return this.name;
};
function Student(val) {
  Person.call(this, val); // 繼承構造函數的屬性
  this.age = "12";
}
var F=function(){}
F.prototype=Person.prototype
Student.prototype=new F() // 只繼承原型上的屬性
Student.prototype.constructor = Student;
var student = new Student("student");
var student1 = new Student("student");
console.log(student.name); //student
console.log(student.age); //12
console.log(student.gender); //man
console.log(student.sayName()); //student
console.log(student.sayName === student1.sayName); //true
利用一個空對象繼承父類的原型,而後讓子類去繼承這個對象實現對父類原型的繼承,利用call來實現子類構造函數對父類構造函數的繼承。
缺點:有點麻煩,不容易理解。
 
5.拷貝繼承
實現繼承的原則就是要讓子類用到父類的屬性和方法,這些方法和屬性都存在父類的內存中,複製一份到子類是一種簡單粗暴的方式,鑑於淺拷貝會形成共享引用類型的值的改變會產生影響。咱們直接用深拷貝來完成繼承。
檢查類型
function checkType(target) {
  return Object.toString(target).slice(8, -1);
}
 
function deepClone(target) {
  let result;
  let targetType = checkType(target);
  if (targetType === "array") {
    result = [];
  } else if (targetType === "object") {
    result = {};
  } else {
    return target;
  }
  // 遍歷
  for (let i in target) {
    let value = target[i];
// 遞歸的判斷條件爲引用類型的時候遞歸
    if (checkType(value) === "Object" || checkType(value) === "Array") {
       result[i] = deepClone(value);
    } else {
       result[i] = value;
    }
  }
  return result;
 }
 
function Person() {
  this.name = "person";
  this.arr = [1];
  this.sayName = function() {
    return this.name;
  };
}
Person.prototype.gender = "man";
var person = new Person();
var student = deepClone(person);
console.log(student.gender); //man
console.log(student.name); //person
缺點:沒有用到原型帶來的好處,對象較複雜狀況用深拷貝帶來的性能問題。
 
總結:不一樣的繼承方式有適用的場景,繼承的原理基本是基於原型鏈,改變引用對象(call)或者拷貝,理解繼承的原理和優化繼承的方式就能更好的理解繼承。
相關文章
相關標籤/搜索