內容:
建立物件的幾種模式以及創建的過程
原型鏈prototype的理解,以及prototype
與 __proto__
([[Prototype]]
)的關係
繼承的幾種實作
a.建構子建立
function Test() {// }
流程
建立函數的時候會預設為Test建立一個prototype屬性,Test.prototype
包含一個指標指向的是Object.prototype
prototype預設會有一個constructor,且Test.prototype.constructor = Test
prototype裡的其它方法都是從Object繼承而來
// 调用构造函数创建实例 var instance = new Test()
這裡的instance包含了一個指標指向建構函式的原型,(這裡的指標在chrome裡叫 __proto__
,也等於[[Prototype]]
)
b.原型模式
由上我們可以知道,預設建立的prototype屬性只擁有constructor和繼承至Object的屬性,原型模式就是為prototype添加屬性和方法
Test.prototype.getName = ()=> { alert('name') }
此時的instance實例就擁有了getName方法,因為實例的指標是指向Test.prototype的
instance.__proto__ === Test.prototype
如下圖所示
#這裡我們可得知:實例instance與建構子之間是透過原型prototype
來相關聯的。
c.組合模式
這種模式我們用的最多,其實也是原型模式的另一種寫法,只不過有一點小區別而已
function Test() {} Test.prototype = { getName() { alert('name') } }
我們常常會這麼直接重寫prototype方法,由上我們可知,prototype會預設自帶constructor屬性指向建構函式本身,那麼重寫以後呢?
Test.prototype.constructor === Object // 而并不等于Test了// 因为重写以后相当于利用字面量方式创建一个实例对象,这个实例的构造函数是指向Object本身的
當然我們也可以手動賦值constructor
Test.prototype = { constructor: Test, getName() { alert('name') } }
那麼又會有疑問了constructor
要不要有何意義?我覺得constructor意義只是為了來鑑別原型所屬的建構子。
當需要取得某個屬性的時候,會先從實例中查找,沒有就根據指標所指向的原型去查找,依序向上,直到實例的指標__proto__
指向為null時停止查找,例如:
// 1 读取nameinstance.name // 2 instance.__proto__ === Test.prototypeTest.prototype.name // 3 Test.prototype.__proto__ === Object.prototypeObject.prototype.name // 4Object.prototype.__proto__ === null
當找到了這個屬性就會直接返回,而不會繼續查找,即使這個屬性值為null,想要繼續查找,我們可以透過delete
操作符來實作。
由這裡我們自然可以想到Array, Date, Function, String
,都是一個建構函數,他們的原型的指標都是指向Object.prototype
,它們就像我在這裡定義的Test
一樣,只不過是原生自帶而已
d.幾個有用的方法
Object.getPrototypeOf()
取得某個實例的指標所指向的原型
Object.getPrototypeOf(instance) === Test.prototype
hasOwnProperty
判斷一個屬性是存在於實例中還是存在於原型中,如圖所示:
in
操作符,無論該屬性是否可枚舉
'name' in instance // true 'getName' in instance // true
無論屬性是在實例中,或是在原型中都會傳回true,所以當我們需要判斷一個屬性存在與實例中,還是原型中有2種辦法
// 一种就是使用hasOwnProperty判断在实例中 // 另一种判断在原型中 instance.hasOwnProperty('getName') === false && 'getName' in instance === true
for ... in
操作符也是一樣的,但只會列出可枚舉的屬性,ie8版本的bug是無論該屬性是否可列舉,都會列出
name是在实例中定义的,getName是在原型中定义的
Object.keys()
则不一样,它返回一个对象上所有可枚举的属性,仅仅是该实例中的
Object.keys(instance)// ["name"]
e.总结
以上讨论了构造函数,原型和实例的关系:
每个构造函数都有原型对象
每个原型对象都有一个constructor
指针指向构造函数
每个实例都有一个__proto__
指针指向原型
其实是一个道理,这里我们不难想到,将Child.prototype指向parent实例,就是利用原型实现的继承,而为了每个实例都拥有各自的colors和name,也就是基础属性,在Child的构造函数中call调用了Parent的构造函数,相当于每次实例化的时候都初始化一遍colors和name,而不是所有实例共享原型链中的colors和name。
继承的实质是利用构造函数的原型 = 某个构造函数的实例,以此来形成原型链。例如
// 定义父类function Parent() {}Parent.prototype.getName = ()=> { console.log('parent') }// 实例化父类let parent = new Parent()// 定义子类function Child() {} Child.prototype = parent // 实例化子类let child = new Child() child.getName() // parent// 此时 child.constructor === parent.constructor === Parent
a.最经典的继承模式
function Parent(name) {this.name = namethis.colors = ['red'] } Parent.prototype.getName = function() {console.log(this.name) }// 实例化父类let parent = new Parent()function Child(age, name) { Parent.call(this, name)this.age = age } Child.prototype = parent // 实例化子类let child = new Child(1, 'aaa') child.getName() // parent
这里会让我想到ES6中的class继承
class Parent { constructor(name) {this.name = namethis.colors = ['red'] } getName() { console.log(this.name) } }class Child extends Parent { constructor(age, name) {super(name) } } let child = new Child(1, 'aaa') child.getName() // parent
以上是常見模式與原型鏈的理解的詳細內容。更多資訊請關注PHP中文網其他相關文章!