在函數內建立一個對象,給對象賦予屬性及方法再將對象返回javascript
function Person() { var People = new Object() People.name = 'CrazyLee' People.age = '25' People.sex = function() { return 'boy' } return People } var a = Person() console.log(a.name) // CrazyLee console.log(a.sex()) // boy
無需在函數內部從新建立對象,而是用 this 指代vue
function Person() { this.name = 'CrazyLee' this.age = '25' this.sex = function() { return 'boy' } } var a = new Person() console.log(a.name) // CrazyLee console.log(a.sex()) // boy
函數中不對屬性進行定義,利用 prototype 屬性對屬性進行定義,可讓全部對象實例共享它所包含的屬性及方法。java
function Parent() { Parent.prototype.name = 'carzy' Parent.prototype.age = '24' Parent.prototype.sex = function() { var s = '女' console.log(s) } } var x = new Parent() console.log(x.name) // crazy console.log(x.sex()) // 女
原型模式+構造函數模式。這種模式中,構造函數模式用於定義實例屬性,而原型模式用於定義方法和共享屬性express
function Parent() { this.name = 'CrazyLee' this.age = 24 } Parent.prototype.sayname = function() { return this.name } var x = new Parent() console.log(x.sayname()) // Crazy  
將全部信息封裝在了構造函數中,而經過構造函數中初始化原型,這個能夠經過判斷該方法是否有效而選擇是否須要初始化原型。設計模式
function Parent() { this.name = 'CrazyLee' this.age = 24 if (typeof Parent._sayname == 'undefined') { Parent.prototype.sayname = function() { return this.name } Parent._sayname = true } } var x = new Parent() console.log(x.sayname())
var arr = [] arr.a = 1
__proto__
屬性(隱式原型),屬性值是一個普通對象;(obj.__proto__ === Object.prototype)
;__proto__
(即它的構造函數的 prototype)中去尋找;__proto__
屬性。 原型的做用就是給這個類的每個對象都添加一個統一的方法,在原型中定義的方法和屬性都是被因此實例對象所共享。var person = function(name){ this.name = name }; person.prototype.getName=function(){ // 經過person.prototype設置函數對象屬性 return this.name; } var crazy= new person(‘crazyLee’); crazy.getName(); // crazyLee//crazy繼承上屬性
__proto__
(即它的構造函數的 prototype)obj.__proto__
中去尋找;當 obj.__proto__
也沒有時,便會在 obj.__proto__.__proto__
(即 obj 的構造函數的 prototype 的構造函數的 prototype)中尋找;[圖片上傳失敗...(image-6e6b90-1570253698607)]api
<figcaption></figcaption>數組
function instance_of(L, R) { //L 表示左表達式,R 表示右表達式 var O = R.prototype // 取 R 的顯示原型 L = L.__proto__ // 取 L 的隱式原型 while (true) { if (L === null) return false if (O === L) // 當 O 顯式原型 嚴格等於 L隱式原型 時,返回true return true L = L.__proto__ } }
原型鏈繼承的基本思想是利用原型讓一個引用類型繼承另外一個引用類型的屬性和方法。babel
function SuperType() { this.name = 'yanxugong' this.colors = ['pink', 'blue', 'green'] } SuperType.prototype.getName = function() { return this.name } function SubType() { this.age = 22 } SubType.prototype = new SuperType() SubType.prototype.getAge = function() { return this.age } SubType.prototype.constructor = SubType let instance1 = new SubType() instance1.colors.push('yellow') console.log(instance1.getName()) // 'yanxugong' console.log(instance1.colors) // ["pink", "blue", "green", "yellow"] let instance2 = new SubType() console.log(instance2.colors) // ["pink", "blue", "green", "yellow"]
缺點:閉包
借用構造函數的技術,其基本思想爲:在子類型的構造函數中調用超類型構造函數。app
function SuperType(name) { this.name = name this.colors = ['pink', 'blue', 'green'] this.getColors = function() { return this.colors } } SuperType.prototype.getName = function() { return this.name } function SubType(name) { SuperType.call(this, name) this.age = 22 } let instance1 = new SubType('yanxugong') instance1.colors.push('yellow') console.log(instancel.colors) // ['pink','blue','green','yellow'] console.log(instancel.getColors()) // ["pink", "blue", "green", "yellow"] console.log(instancel.getName) // undefined let instance2 = new SubType('Jack') console.log(instance2.colors) // ['pink','blue','green'] console.log(instance2.getColors()) // ["pink", "blue", "green"] console.log(instance2.getName) // undefined
優勢:
缺點:
組合繼承指的是將原型鏈和借用構造函數技術組合到一塊,從而發揮兩者之長的一種繼承模式。
基本思路:
使用原型鏈實現對原型屬性和方法的繼承,經過借用構造函數來實現對實例屬性的繼承,既經過在原型上定義方法來實現了函數複用,又保證了每一個實例都有本身的屬性。
function SuperType(name) { this.name = name this.colors = ['pink', 'blue', 'green'] } SuperType.prototype.getName = function() { return this.name } function SubType(name, age) { SuperType.call(this, name) this.age = age } SubType.prototype = new SuperType() SubType.prototype.constructor = SubType SubType.prototype.sayAge = function() { return this.age } let instancel = new SubType('yanxugong', 20) instancel.colors.push('yellow') console.log(instancel.colors) // ['pink','blue','green','yellow'] console.log(instancel.sayAge()) // 20 console.log(instancel.getName()) // yanxugong let instance2 = new SubType('Jack', 18) console.log(instance2.colors) // ['pink','blue','green'] console.log(instance2.sayAge()) // 18 console.log(instance2.getName()) // Jack console.log(new SuperType('po'))
缺點:
優勢:
原型繼承的基本思想:
藉助原型能夠基於已有的對象建立新對象,同時還沒必要所以建立自定義類型。
function object(o) { function F() {} F.prototype = o return new F() }
在 object()函數內部,新建一個臨時性的構造函數,而後將傳入的對象做爲這個構造函數的原型,最後返回了這個臨時類型的一個新實例,從本質上講,object()對傳入的對象執行了一次淺拷貝。
ECMAScript5 經過新增 Object.create()方法規範了原型式繼承。這個方法接收兩個參數:一個用做新對象原型的對象和(可選的)一個爲新對象定義額外屬性的對象(能夠覆蓋原型對象上的同名屬性),在傳入一個參數的狀況下,Object.create()和 object()方法的行爲相同。
var person = { name: 'yanxugong', hobbies: ['reading', 'photography'] } var personl = Object.create(person) personl.name = 'jack' personl.hobbies.push('coding') var person2 = Object.create(person) person2.name = 'Echo' person2.hobbies.push('running') console.log(person.hobbies) // ["reading", "photography", "coding", "running"] console.log(person.name) // yanxugong console.log(personl.hobbies) // ["reading", "photography", "coding", "running"] console.log(personl.name) // jack console.log(person2.hobbies) // ["reading", "photography", "coding", "running"] console.log(person2.name) // Echo
在沒有必要建立構造函數,僅讓一個對象與另外一個對象保持類似的狀況下,原型式繼承是能夠勝任的。
缺點:
寄生式繼承是與原型式繼承緊密相關的一種思路。寄生式繼承的思路與寄生構造函數和工廠模式相似,即建立一個僅用於封裝繼承過程的函數,該函數在內部已某種方式來加強對象,最後再像真地是它作了全部工做同樣返回對象。
function object(o) { function F() {} F.prototype = o return new F() } function createAnother(original) { var clone = object(original) // 經過調用函數建立一個新對象 clone.sayHi = function() { // 以某種方式加強這個對象 console.log('hi') } return clone // 返回這個對象 } var person = { name: 'yanxugong', hobbies: ['reading', 'photography'] } var personl = createAnother(person) personl.sayHi() // hi personl.hobbies.push('coding') console.log(personl.hobbies) // ["reading", "photography", "coding"] console.log(person) // {hobbies:["reading", "photography", "coding"],name: "yanxugong"}
基於 person 返回了一個新對象 personl,新對象不只具備 person 的全部屬性和方法,並且還有本身的 sayHi()方法。在考慮對象而不是自定義類型和構造函數的狀況下,寄生式繼承也是一種有用的模式。
缺點:
所謂寄生組合式繼承,即經過借用構造函數來繼承屬性,經過原型鏈的混成形式來繼承方法。
基本思路:
沒必要爲了指定子類型的原型而調用超類型的構造函數,咱們須要的僅是超類型原型的一個副本,本質上就是使用寄生式繼承來繼承超類型的原型,而後再將結果指定給子類型的原型。寄生組合式繼承的基本模式以下所示:
function object(o) { function F() {} F.prototype = o return new F() } function inheritPrototype(subType, superType) { var prototype = object(superType.prototype) // 建立對象 prototype.constructor = subType // 加強對象 subType.prototype = prototype // 指定對象 }
至此,咱們就能夠經過調用 inheritPrototype 來替換爲子類型原型賦值的語句:
function object(o) { function F() {} F.prototype = o return new F() } function inheritPrototype(subType, superType) { var prototype = object(superType.prototype) // 建立對象 prototype.constructor = subType // 加強對象 subType.prototype = prototype // 指定對象 } function SuperType(name) { this.name = name this.colors = ['pink', 'blue', 'green'] } SuperType.prototype.getName = function() { return this.name } function SubType(name, age) { SuperType.call(this, name) this.age = age } inheritPrototype(SubType, SuperType) SubType.prototype.sayAge = function() { return this.age } let instancel = new SubType('yanxugong', 20) instancel.colors.push('yellow') console.log(instancel.colors) // ['pink','blue','green','yellow'] console.log(instancel.sayAge()) // 20 console.log(instancel.getName()) // yanxugong let instance2 = new SubType('Jack', 18) console.log(instance2.colors) // ['pink','blue','green'] console.log(instance2.sayAge()) // 18 console.log(instance2.getName()) // Jack console.log(new SuperType('po'))
優勢:
參數:
{Object} options
使用基礎 Vue 構造器,建立一個「子類」。參數是一個包含組件選項的對象。
data
選項是特例,須要注意 - 在 Vue.extend()
中它必須是函數
<div id="mount-point"></div>
// 建立構造器 var Profile = Vue.extend({ template: '<p>{{firstName}} {{lastName}} aka {{alias}}</p>', data: function() { return { firstName: 'Walter', lastName: 'White', alias: 'Heisenberg' } } }) // 建立 Profile 實例,並掛載到一個元素上。 new Profile().$mount('#mount-point')
結果以下:
<p>Walter White aka Heisenberg</p>
在 vue 項目中,咱們有了初始化的根實例後,全部頁面基本上都是經過 router 來管理,組件也是經過 import
來進行局部註冊,因此組件的建立咱們不須要去關注,相比 extend
要更省心一點點。可是這樣作會有幾個缺點:
#app
下渲染,註冊組件都是在當前位置渲染。若是我要實現一個相似於 window.alert()
提示組件要求像調用 JS 函數同樣調用它,該怎麼辦?Vue.extend + vm.$mount
組合就派上用場了。先看看 new 操做符都幹了什麼事情,有哪些操做?經過下面的代碼來進行思考:
// 新建一個類(構造函數) function Otaku(name, age) { this.name = name this.age = age // 自身的屬性 this.habit = 'pk' } // 給類的原型上添加屬性和方法 Otaku.prototype.strength = 60 Otaku.prototype.sayYourName = function() { console.log('I am ' + this.name) } // 實例化一個person對象 const person = new Otaku('喬峯', 5000) person.sayYourName() // I am 喬峯 console.log(person) // 打印出構造出來的實例
從控制檯打印出來的結果咱們能夠看出 new 操做符大概作了一下幾件事情:
經過上面的分析展現,能夠知道 new 團伙裏面必定有 Object 的參與,否則對象的產生就有點說不清了。 先來邊寫寫:
// 須要返回一個對象 藉助函數來實現new操做 // 傳入須要的參數: 類 + 屬性 const person = new Otaku('喬峯', 5000) const person1 = objectFactory(Otaku, '鳩摩智', 5000) // 開始來實現objectFactory 方法 function objectFactory(obj, name, age) {} // 這種方法將自身寫死了 如此他只能構造以obj爲原型,而且只有name 和 age 屬性的 obj // 在js中 函數由於arguments 使得函數參數的寫法異常靈活,在函數內部能夠經過arguments來得到函數的參數 function objectFactory() { console.log(arguements) //{ '0': [Function: Otaku], '1': '鳩摩智', '2': 5000 } // 經過arguments類數組打印出的結果,咱們能夠看到其中包含了構造函數以及咱們調用objectfactory時傳入的其餘參數 // 接下來就是要想如何獲得其中這個構造函數和其餘的參數 // 因爲arguments是類數組,沒有直接的方法能夠供其使用,咱們能夠有如下兩種方法: // 1. Array.from(arguments).shift(); //轉換成數組 使用數組的方法shift將第一項彈出 // 2. [].shift().call(arguments); // 經過call() 讓arguments可以借用shift方法 const Constructor = [].shift.call(arguments) const args = arguments // 新建一個空對象 純潔無邪 let obj = new Object() // 接下來的想法 給obj這個新生對象的原型指向它的構造函數的原型 // 給構造函數傳入屬性,注意:構造函數的this屬性 // 參數傳進Constructor對obj的屬性賦值,this要指向obj對象 // 在Coustructor內部手動指定函數執行時的this 使用call、apply實現 let result = Constructor.apply(obj, arguments) //確保new出來的是一個對象 return typeof result === 'object' ? result : obj }
function objectFactory() { let Constructor = [].shift.call(arguments) const obj = new Object() obj.__proto__ = Conctructor.prototype let result = Constructor.apply(obj, arguments) return typeof result === 'object' ? result : obj }
function myNew(Obj, ...args) { var obj = Object.create(Obj.prototype) // 使用指定的原型對象及其屬性去建立一個新的對象 Obj.apply(obj, args) // 綁定 this 到obj, 設置 obj 的屬性 return obj // 返回實例 }
javascript 使用的是原型式繼承,咱們能夠經過原型的特性實現類的繼承,
ES6 爲咱們提供了像面向對象繼承同樣的語法糖。
class Parent { constructor(a) { this.filed1 = a } filed2 = 2 func1 = function() {} } class Child extends Parent { constructor(a, b) { super(a) this.filed3 = b } filed4 = 1 func2 = function() {} }
下面咱們藉助 babel
來探究 ES6 類和繼承的實現原理。
轉換前:
class Parent { constructor(a) { this.filed1 = a } filed2 = 2 func1 = function() {} }
轉換後:
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function') } } var Parent = function Parent(a) { _classCallCheck(this, Parent) this.filed2 = 2 this.func1 = function() {} this.filed1 = a }
可見 class 的底層依然是構造函數:
構造函數執行前有 new 關鍵字,會在構造函數內部建立一個空對象,將構造函數的proptype
指向這個空對象的__proto__
,並將 this 指向這個空對象。如上,\_classCallCheck 中:this instanceof Parent 返回 true。若構造函數前面沒有 new 則構造函數的 proptype 不會不出如今 this 的原型鏈上,返回 false。
轉換前:
class Child extends Parent { constructor(a, b) { super(a) this.filed3 = b } filed4 = 1 func2 = function() {} }
轉換後:
咱們先看 Child 內部的實現,再看內部調用的函數是怎麼實現的:
var Child = (function(_Parent) { _inherits(Child, _Parent) function Child(a, b) { _classCallCheck(this, Child) var _this = _possibleConstructorReturn( this, (Child.__proto__ || Object.getPrototypeOf(Child)).call(this, a) ) _this.filed4 = 1 _this.func2 = function() {} _this.filed3 = b return _this } return Child })(Parent)
_inherits
函數繼承父類的 proptype。_inherits
內部實現:
function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError( 'Super expression must either be null or a function, not ' + typeof superClass ) } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }) if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : (subClass.__proto__ = superClass) }
(1) 校驗父構造函數。
(2) 典型的寄生繼承:用父類構造函數的 proptype 建立一個空對象,並將這個對象指向子類構造函數的 proptype。
(3) 將父構造函數指向子構造函數的__proto__
(這步是作什麼的不太明確,感受沒什麼意義。)
var _this = _possibleConstructorReturn( this, (Child.__proto__ || Object.getPrototypeOf(Child)).call(this, a) )
這裏的 Child.proto || Object.getPrototypeOf(Child)
其實是父構造函數(\_inherits 最後的操做),而後經過 call 將其調用方改成當前 this,並傳遞參數。(這裏感受能夠直接用參數傳過來的 Parent)
function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError( "this hasn't been initialised - super() hasn't been called" ) } return call && (typeof call === 'object' || typeof call === 'function') ? call : self }
校驗 this 是否被初始化,super 是否調用,並返回父類已經賦值完的 this。
可見,ES6 其實是爲咱們提供了一個「組合寄生繼承」的簡單寫法。
super 表明父類構造函數。
super.fun1()
等同於 Parent.fun1()
或 Parent.prototype.fun1()
。
super()
等同於 Parent.prototype.construtor()
當咱們沒有寫子類構造函數時:
var Child = (function(_Parent) { _inherits(Child, _Parent) function Child() { _classCallCheck(this, Child) return _possibleConstructorReturn( this, (Child.__proto__ || Object.getPrototypeOf(Child)).apply(this, arguments) ) } return Child })(Parent)
可見默認的構造函數中會主動調用父類構造函數,並默認把當前 constructor
傳遞的參數傳給了父類。
因此當咱們聲明瞭 constructor
後必須主動調用 super()
,不然沒法調用父構造函數,沒法完成繼承。
典型的例子就是 React 的 Component 中,咱們聲明 constructor
後必須調用 super(props)
,由於父類要在構造函數中對 props 作一些初始化操做。