本篇文章就給大家舉例介紹js中常見的繼承方式有哪些? (附範例),有一定的參考價值,有需要的朋友可以參考一下,希望對你們有幫助。
物件導向程式設計很重要的一個面向,就是物件的繼承。 A 物件透過繼承 B 對象,就能直接擁有 B 對象的所有屬性和方法。這對於程式碼的複用是非常有用的。
大部分物件導向的程式語言,都是透過「類別」(class)實現物件的繼承。傳統上,JavaScript 語言的繼承不透過 class(ES6 引入了class 語法),而是透過「原型物件」(prototype)實現。那麼在JS常見的繼承方式有幾種呢?
如需本文源碼,請猛戳常見的六種繼承方式
#方式一、原型鏈繼承
#這種方式關鍵在於:子類型的原型為父類型的一個實例物件。
//父类型 function Person(name, age) { this.name = name, this.age = age, this.play = [1, 2, 3] this.setName = function () { } } Person.prototype.setAge = function () { } //子类型 function Student(price) { this.price = price this.setScore = function () { } } Student.prototype = new Person() // 子类型的原型为父类型的一个实例对象 var s1 = new Student(15000) var s2 = new Student(14000) console.log(s1,s2)
但這種方式實現的本質是透過將子類別的原型指向了父類別的實例,所以子類別的實例就可以透過__proto__存取Student.prototype 也就是Person的實例,這樣就可以存取到父類別的私有方法,然後再透過__proto__指向父類別的prototype就可以獲得到父類別原型上的方法。於是做到了將父類別的私有、公有方法和屬性都當作子類別的公有屬性
子類別繼承父類別的屬性和方法是將父類別的私有屬性和公有方法都作為自己的公有屬性和方法,我們都知道在操作基本資料型別的時候操作的是值,在操作引用資料型別的時候操作的是位址,如果說父類別的私有屬性中有引用型別的屬性,那它被子類別繼承的時候會作為公有屬性,這樣子類別1操作這個屬性的時候,就會影響到子類別2。
s1.play.push(4) console.log(s1.play, s2.play) console.log(s1.__proto__ === s2.__proto__)//true console.log(s1.__proto__.__proto__ === s2.__proto__.__proto__)//true
s1中play屬性發生變化,同時,s2中play屬性也會跟著變化。
另外注意一點的是,我們需要在子類別中加入新的方法或是重寫父類別的方法時候,切記一定要放到替換原型的語句之後
function Person(name, age) { this.name = name, this.age = age } Person.prototype.setAge = function () { console.log("111") } function Student(price) { this.price = price this.setScore = function () { } } // Student.prototype.sayHello = function () { }//在这里写子类的原型方法和属性是无效的, //因为会改变原型的指向,所以应该放到重新指定之后 Student.prototype = new Person() Student.prototype.sayHello = function () { } var s1 = new Student(15000) console.log(s1)
特點:
父類別新增原型方法/原型屬性,子類別都能存取到
#簡單,易於實作
缺點:
無法實作多繼承
#來自原型物件的所有屬性被所有實例共用
建立子類別實例時,無法向父類別建構子傳參
#要為子類別新增屬性和方法,必須在Student.prototype = new Person()
之後執行,不能放到建構器中
方式二: 借用建構函式繼承
這種方式關鍵在於:在子型別建構子中通用call()呼叫父型別建構子
<script> function Person(name, age) { this.name = name, this.age = age, this.setName = function () {} } Person.prototype.setAge = function () {} function Student(name, age, price) { Person.call(this, name, age) // 相当于: this.Person(name, age) /*this.name = name this.age = age*/ this.price = price } var s1 = new Student('Tom', 20, 15000)</script>
這種方式只是實作部分的繼承,如果父類別的原型還有方法和屬性,子類別是拿不到這些方法和屬性的。
console.log(s1.setAge())//Uncaught TypeError: s1.setAge is not a function
特點:
解決了原型鏈繼承中子類別實例共享父類別引用屬性的問題
建立子類別實例時,可以傳遞參數
#缺點
方式三: 原型鏈借用建構函式的組合繼承
#這種方式關鍵在於:透過呼叫父類別構造,繼承父類的屬性並保留傳參的優點,然後透過將父類別實例作為子類別原型,實現函數重複使用。
###function Person(name, age) { this.name = name, this.age = age, this.setAge = function () { } } Person.prototype.setAge = function () { console.log("111") } function Student(name, age, price) { Person.call(this,name,age) this.price = price this.setScore = function () { } } Student.prototype = new Person() Student.prototype.constructor = Student//组合继承也是需要修复构造函数指向的 Student.prototype.sayHello = function () { } var s1 = new Student('Tom', 20, 15000) var s2 = new Student('Jack', 22, 14000) console.log(s1) console.log(s1.constructor) //Student console.log(p1.constructor) //Person
这种方式融合原型链继承和构造函数的优点,是 JavaScript 中最常用的继承模式。不过也存在缺点就是无论在什么情况下,都会调用两次构造函数:一次是在创建子类型原型的时候,另一次是在子类型构造函数的内部,子类型最终会包含父类型对象的全部实例属性,但我们不得不在调用子类构造函数时重写这些属性。
优点:
可以继承实例属性/方法,也可以继承原型属性/方法
不存在引用属性共享问题
可传参
函数可复用
缺点:
调用了两次父类构造函数,生成了两份实例
方式四: 组合继承优化1
这种方式通过父类原型和子类原型指向同一对象,子类可以继承到父类的公有方法当做自己的公有方法,而且不会初始化两次实例方法/属性,避免的组合继承的缺点。
function Person(name, age) { this.name = name, this.age = age, this.setAge = function () { } } Person.prototype.setAge = function () { console.log("111") } function Student(name, age, price) { Person.call(this, name, age) this.price = price this.setScore = function () { } } Student.prototype = Person.prototype Student.prototype.sayHello = function () { } var s1 = new Student('Tom', 20, 15000) console.log(s1)
但这种方式没办法辨别是对象是子类还是父类实例化
console.log(s1 instanceof Student, s1 instanceof Person)//true true console.log(s1.constructor)//Person
优点:
不会初始化两次实例方法/属性,避免的组合继承的缺点
缺点:
没办法辨别是实例是子类还是父类创造的,子类和父类的构造函数指向是同一个。
方式五: 组合继承优化2
借助原型可以基于已有的对象来创建对象,var B = Object.create(A)
以A对象为原型,生成了B对象。B继承了A的所有属性和方法。
function Person(name, age) { this.name = name, this.age = age } Person.prototype.setAge = function () { console.log("111") } function Student(name, age, price) { Person.call(this, name, age) this.price = price this.setScore = function () {} } Student.prototype = Object.create(Person.prototype)//核心代码 Student.prototype.constructor = Student//核心代码 var s1 = new Student('Tom', 20, 15000) console.log(s1 instanceof Student, s1 instanceof Person) // true true console.log(s1.constructor) //Student console.log(s1)
同样的,Student继承了所有的Person原型对象的属性和方法。目前来说,最完美的继承方法!
方式六:ES6中class 的继承
ES6中引入了class关键字,class可以通过extends关键字实现继承,还可以通过static关键字定义类的静态方法,这比 ES5 的通过修改原型链实现继承,要清晰和方便很多。
ES5 的继承,实质是先创造子类的实例对象this,然后再将父类的方法添加到this上面(Parent.apply(this))。ES6 的继承机制完全不同,实质是先将父类实例对象的属性和方法,加到this上面(所以必须先调用super方法),然后再用子类的构造函数修改this。
需要注意的是,class关键字只是原型的语法糖,JavaScript继承仍然是基于原型实现的。
class Person { //调用类的构造方法 constructor(name, age) { this.name = name this.age = age } //定义一般的方法 showName() { console.log("调用父类的方法") console.log(this.name, this.age); } } let p1 = new Person('kobe', 39) console.log(p1) //定义一个子类 class Student extends Person { constructor(name, age, salary) { super(name, age)//通过super调用父类的构造方法 this.salary = salary } showName() {//在子类自身定义方法 console.log("调用子类的方法") console.log(this.name, this.age, this.salary); } } let s1 = new Student('wade', 38, 1000000000) console.log(s1) s1.showName()
优点:
语法简单易懂,操作更方便
缺点:
并不是所有的浏览器都支持class关键字
以上是js中常見的繼承方式有哪些? (附範例)的詳細內容。更多資訊請關注PHP中文網其他相關文章!