把以前的文章重寫了一遍,以爲真的把繼承講的很透徹了,引個流,你們能夠看看。vue
明確一點:JavaScript並非真正的面嚮對象語言,沒有真正的類,因此咱們也沒有類繼承es6
實現繼承==有且僅有兩種方式,call和原型鏈==編程
在介紹繼承前咱們先介紹下其餘概念數組
一個函數,有三種角色。
當成普通函數,當成構造函數(類),當成對象babel
function Person (nickname) { var age = 15 //當普通函數使 私有屬性 this.age = 30 //當構造函數使 實例屬性 } Person.prototype.age = 50 //當構造函數使 原型屬性 Person.age =100 //當對象使 靜態屬性(類屬性)
舉例:
Array.isArray是類上的方法
Array.push是Array原型上的方法
Array.toString是沿着原型鏈查找到的object類上的原型方法app
繼承原則:
使用call繼承實例上的屬性
使用原型鏈繼承原型上的屬性mvvm
const Person = function (name) { this.name = name } Person.prototype.introduce = function(){ Object.entries(this).forEach((item)=>{ console.log(`my ${item[0]} is ${item[1]}`) }) } const Student = function (name,age) { Person.call(this,name) this.age = age } Student.prototype = new Person() //這裏new了父類一次,增長了額外開銷 Student.prototype.constructor = Student //這一句可讓student.constructor.name由Person變爲Student 方便確認構造函數 let student = new Student('小明',15) student.introduce() 繼承父類原型方法的同時繼承父類實例上的屬性 //my name is 小明 //my age is 15
組合繼承有一個缺點,會額外new父類一次,增長了額外開銷(想想若是父類特別大這消耗會有多大)函數
咱們仔細研究一下這一句話,爲何它就能實現原型鏈繼承優化
在上一篇文章中咱們學過,實例能訪問類上的原型this
若是子類實例能訪問父類的原型,那麼咱們是否是能夠說子類繼承了父類?
可是子類實例只能訪問子類原型呀,因此可我可讓子類的原型等於父類的實例,由於父類的實例能夠訪問父類的原型,這就至關於子類實例能夠訪問父類原型了
這裏你可能會問,爲何不直接這麼寫student.prototype = Person.prototype,這樣子實例也能夠訪問父實例呀
**沒錯!單從訪問上來講,你是對的。可是若是我後面先重寫子類的原型,
好比我想寫student.prototype = null,由於如今子類父類原型共用同一地址,父類也被改了,這個不符合咱們的初衷 **
還記得3.1說的原型鏈繼承有個地方能夠優化嗎?在咱們知道了3.2原型鏈繼承的寫法後,咱們產生這樣一個疑問,
這也是優化的兩個方向
自動生成_proto_,改的是類的原型的指向
先說第二種優化,使用new自動生成_proto_,可是確定不能直接new父類吧,咱們new出一個空對象,而後改變這個類的原型指向咱們須要繼承的
好比咱們須要繼承obj
也就是能訪問obj
實例能訪問類的原型,讓類的原型的地址指向Obj,實現繼承
類爲空類,減小開銷
var create = function(obj){
var fn = funcion(){} //空類 fn.prototype = obj //改變類的原型的指向,指向要繼承的對象 reurturn new fn() //自動生成_proto_
}
var A = create(B) //A能找到B(經過_proto_)
這個方法被es6實現了
Object.create()方法建立一個新對象,使用現有的對象來提供新建立的對象的__proto__。
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(); //My name is Matthew. Am I human? true
不用new,直接改_proto_
不就是讓子類原型指向父類的原型嗎
student.prototype 直接指向 Person.prototype 有問題,那咱就不讓它直接指向了,讓它間接指向
student.prototype指針不變,它找不到屬性,會找它的_proto_吧,我讓它的_proto_指向Person.prototype不就好了
也就是 student.prototype._proto_ = Person.prototype
因此對於這句話 Student.prototype = new Person() 的優化,
重心放在了怎麼減小開銷來創建聯繫上
咱們能夠既經過增長一箇中間空對象(減小開銷),來完成優化
Student.prototype = Object.create(Person.prototype)
也能夠增長一箇中間屬性來完成優化
Student.prototype.__proto__ = Person.prototype
都能創建父類子類的聯繫
既然new在「類」的建立裏面必須使用,那麼咱們就說一下new到底幹了啥事情題外話,new幹了啥事,必定要從new完之後實例和類的關係來入手記憶,實例和類啥關係?兩個關係實例是否是又類上面的實例屬性,同時_proto_的指向關係
因此new 辦了三件事
1.建立一個對象o繼承構造函數
2.讓構造函數的this變爲o,並執行構造函數,將返回值設置爲k
3.若是k是對象則返回對象,若是不是則返回o
//仿寫new function new1(func) { var o = Object.create(func.prototype) var k = func.apply(o,arguments[1]) return typeof k === 'object'? k: o } const x = new1(Student,['張三']) x.name //'張三' x.eat //'i am hungry,i want to eat!'
咱們回過頭再分析一下構造函數模式繼承
const Person = function (name) { this.name = name } const Students = function (name) { Person.call(this,name) //this是student實例 } const xm = new Students('小明') //分析這裏幹了什麼 console.log(xm) //Students {name: "小明"}
1.讓空對象o繼承Students(o能訪問Students的原型)
2.student執行,執行Person的代碼,this是o,而且傳入name, o.name='小明'返回的k是undefined
3.返回o,也就是返回{name:'小明'}
class Person { } class Student extends person{ }
在babel es2015-loose模式下編譯後的源碼以下
"use strict"; function _inheritsLoose(subClass, superClass) { subClass.prototype = Object.create(superClass.prototype); subClass.prototype.constructor = subClass; //修正constructor,避免constructor判斷的不對,也通常用不到 subClass.__proto__ = superClass; //這句話看不懂,感受沒啥用呀 } var Person = function Person() { }; var Student = /*#__PURE__*/ function (_person) { _inheritsLoose(Student, _person); function Student() { return _person.apply(this, arguments) || this; } return Student; }(person);
嚴格模式下,高級單例模式返回一個Student, 能夠看到Person的實例屬性用的Person的構造函數+apply繼承的
原型屬性用的_inheritsLoose這個方法繼承的
_inheritsLoose方法貌似就是咱們以前說的寄生組合繼承
咱們知道vue裏面的數組有變異方法,變異方法有啥功能呢,就拿push來講,一方面數組會變,另一方面有響應式(假設觸發render方法)
思路:APO編程思想
數組之因此有push方法,是由於Array.prototype上有push方法
咱們須要實現本身的push方法,掛在Array.prototype上對原型鏈追蹤進行攔截,可是呢又不能改變原型鏈上對非變異方法
原型鏈示意:
Vue裏面添加過監控的數組實例--->咱們本身實現的變異方法-->原來的Array.prototype
const arrList = ['push','pop','shift','unshfit','reverse','sort','splice'] const render = ()=>{console.log('響應式,渲染視圖')} const proto = Object.create(Array) arrList.forEach((method)=>{ proto[method] = function(){ render() Array.prototype[method].call(this,...arguments) } }) var data = [1,2,3] data.__proto__ = proto //mvvm響應式原理,若是添加響應式的目標是數組,我就執行這個操做 data.push(4) // 響應式,渲染視圖,(data[1,2,3,4])
本節詳細介紹了繼承的原理以及優化,對於es6的繼承語法糖也作了剖析。同時介紹了一下mvvm下數組借用繼承實現響應式的用法,因爲本人水平有限,若是有什麼不對的地方,歡迎留言指出。