js原型 && 繼承

js原型 && 繼承

原由是被學妹問起原型鏈,發現只是曾經記得,一些概念不敢咬的那麼準了,因而決定溫故一下,力求能知新~~es6

從如下幾個方面着手數組

  • 建立對象的方式
  • 記住原型鏈的小竅門
  • instanceof 模擬實現
  • new關鍵字 模擬實現
  • 繼承的實現(逐步實現)

建立對象的方式

1.對象字面量app

var o1 = {name: 'o1'}
var o2 = new Object({name: 'o2'})
複製代碼

2.經過構造函數函數

var M = function(name){
	this.name = name
}
var o3 = new M('o3')
複製代碼

3.Object.create優化

var o4 = Object.create(p)
複製代碼

記住原型鏈的小竅門

記憶老是有規律的,如高中時期學的三角函數,須要背公式不少,強行去背所有的公式是容易混亂的。不過若是把核心的幾點背牢,其他的公式只須要稍加推導便可。關於原型鏈也是同樣,有幾點在最開始就記住的話,後面就不會亂了。原型鏈中關鍵概念:構造函數 實例 constructor __ proto__ prototype, 首先要記住他們的關係this

  • 實例(對象)有__proto__ , 實例(對象)沒有prototype
  • 構造函數有 prototype ,同時prototype又是對象,那麼prototype即知足上面一條,除了擁有__proto__外,還含有constructor
  • 構造函數的prototype的constructor就是指向構造函數自己,即上例子中 M.prototype.constructor === M

上面3點請先牢記,後面所總結的完整繼承和這有緊密的關聯spa

其實 構造函數 實例 constructor __ proto__ prototype 的關係已經在上面的例子和3點介紹中介紹完了。不妨再回顧一下prototype

  1. 構造函數即普通函數,只不過前邊有 new 關鍵字code

  2. 經過 new構造函數 ,生成的對象即爲實例。對象

  3. 以上面生成o3實例爲例子

    o3.proto === M.prototype //true o3.prototype //undefined o3.proto === M.prototype //true

  4. o3實例自己並沒有constructor,不過會藉助原型鏈向上查找,即,

    o3.constructor === M.prototype.constructor // true o3.constructor === M //true

小結 理清這幾個關鍵詞的關係後,原型鏈就明朗不少了

instanceof 模擬實現

instanceof 的原理是什麼呢? 先來看一下使用

[] instanceof Array  // true
複製代碼

即左邊是對象,右邊是類型,instanceof 就是要判斷右邊類型的prototype,是否在左邊實例的原型鏈上,以下例子所示

[].__proto__ === Array.prototype //true
Array.prototype.__proto__ === Object.prototype //true
Object.prototype__proto__ //undefined
複製代碼

那麼依據這個思想來實現一下instanceof吧,必定會印象更加深入

function myInstanceof2(left, right){
	if(left === null || left === undefined){
		return false
	}
	if(right.prototype === left) {
		return true
	}

	left = left.__proto__
	return myInstanceof2(left, right)
}

console.log(myInstanceof2([], Array))
複製代碼

new 模擬實現(簡要版)

new的過程發生了什麼?

  1. 生成空對象

  2. 這個空對象的__proto__賦值爲構造函數的prototype

  3. 綁定this指向

  4. 返回這個對象

    // 構造函數 function M(name){ this.name = name } // 原生new var obj = new M('123')

    // 模擬實現 function create() { // 生成空對象 let obj = {} // 拿到傳進來參數的第一項,並改變參數類數組 let Con = [].shift.call(arguments) // 對空對象的原型指向賦值 obj.proto = Con.prototype // 綁定this (對應下面使用來講明:Con是參數第一項M,arguments是參數['123'],就是 M方法執行,參數是'123',執行這個函數的this是obj) let result = Con.apply(obj, arguments) return result instanceof Object ? result : obj }

    var testObj = create(M, '123') console.log('testObj', testObj)

繼承的實現(逐步實現)

一步一步來,從簡到繁,更能直觀發現繼承的原理與缺點

  1. 構造方法方式 核心 Parent1.call(this)

    // 構造方法方式 function Parent1(){ this.name = 'Parent1' } Parent1.prototype.say = function () { alert('say') } function Child1(){ Parent1.call(this) this.type = 'type' }

    var c1 = new Child1() c1.say() //報錯

缺點: 只能繼承父類構造函數內部屬性,沒法繼承父類構造函數原型對象上屬性

思考: 爲何 call 實現了繼承,call本質是什麼?

  1. 只借助原型繼承 核心 Child2.prototype = new Parent2()

    // 原型 function Parent2(){ this.name = 'Parent2' this.arr = [1,2] } Parent2.prototype.say = function () { alert('say') } function Child2(){ // Parent2.call(this) this.type = 'type' } Child2.prototype = new Parent2()

    var c21 = new Child2() var c22 = new Child2()

    c21.say() c21.arr.push('9') console.log('c21.arr : ', c21.arr) console.log('c22.arr : ', c22.arr)

缺點: c21.arr 與c22.arr對應的是同一個引用

思考:爲何這麼寫是同一個引用?

  1. 組合繼承1

把上面兩個繼承方式的優勢合併起來,缺點都拋棄掉

function Parent3(){
	this.name = 'Parent3'
	this.arr = [1,2]
}
Parent3.prototype.say = function () {
	alert('say')
}
function Child3(){
	Parent3.call(this)
	this.type = 'type'
}
Child3.prototype = new Parent3()

var c31 = new Child3()
var c32 = new Child3()

c31.say()
c31.arr.push('9')
console.log('c31.arr : ', c31.arr)
console.log('c31.arr : ', c32.arr)
複製代碼

思考: 這麼寫就沒有問題了嗎?

答 : 生成一個實例要執行 Parent3.call(this) , new Child3(),也就是Parent3執行了兩遍。

  1. 組合繼承2 改變上例子 的

    Child3.prototype = new Parent3()

Child3.prototype = Parent3.prototype
複製代碼

缺點 : 很明顯,沒法定義子類構造函數原型私有的方法

  1. 組合繼承優化3 再次改變上例子 的

    Child3.prototype = Parent3.prototype

Child3.prototype = Object.create(Parent3.prototype)
複製代碼

問題就都解決了。 由於Object.create的原理是:生成一個對象,這個對象的__proto__, 指向所傳的參數。

思考 :是否還有疏漏?一時想不起來的話,能夠看下這幾個結果

console.log(c31 instanceof Child3)
console.log(c31 instanceof Parent3)
console.log(c31.constructor === Child3)
console.log(c31.constructor === Parent3)
複製代碼

因此回想起文章開頭所說的那幾個須要牢記的點,就須要從新賦值一會兒類構造函數的constructor: Child3.prototype.constructor = Child3,完整版以下

function Parent3(){
	this.name = 'Parent3'
	this.arr = [1,2]
}
Parent3.prototype.say = function () {
	alert('say')
}
function Child3(){
	Parent3.call(this)
	this.type = 'type'
}

Child3.prototype = Object.create(Parent3.prototype)
Child3.prototype.constructor = Child3

var c31 = new Child3()
var c32 = new Child3()

c31.say()
c31.arr.push('9')
console.log('c31.arr : ', c31.arr)
console.log('c31.arr : ', c32.arr)

console.log('c31 instanceof Child3 : ', c31 instanceof Child3)
console.log('c31 instanceof Parent3 : ', c31 instanceof Parent3)
console.log('c31.constructor === Child3 : ', c31.constructor === Child3)
console.log('c31.constructor === Parent3 : ', c31.constructor === Parent3)
複製代碼

es6的繼承後續填補~~~

相關文章
相關標籤/搜索