首頁  >  文章  >  web前端  >  常見模式與原型鏈的理解

常見模式與原型鏈的理解

PHP中文网
PHP中文网原創
2017-06-20 09:37:301715瀏覽

內容:

  • 建立物件的幾種模式以及創建的過程

  • 原型鏈prototype的理解,以及prototype 與 __proto__[[Prototype]])的關係

  • 繼承的幾種實作


1.常見模式與原型鏈的理解

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

如下圖所示


897RVF]E5@IX$)`IVJ3BOSY.png

#這裡我們可得知:實例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 判斷一個屬性是存在於實例中還是存在於原型中,如圖所示:


    NY~N}CNR`}8W%4QA$M8LFE4.png
  • in操作符,無論該屬性是否可枚舉

    'name' in instance  // true
    'getName' in instance // true

    無論屬性是在實例中,或是在原型中都會傳回true,所以當我們需要判斷一個屬性存在與實例中,還是原型中有2種辦法

    // 一种就是使用hasOwnProperty判断在实例中
    // 另一种判断在原型中
    instance.hasOwnProperty('getName') === false && 'getName' in instance === true
  • for ... in操作符也是一樣的,但只會列出可枚舉的屬性,ie8版本的bug是無論該屬性是否可列舉,都會列出


    #D(%S__GN8404{H9X6PW$DVK.png


    name是在实例中定义的,getName是在原型中定义的

  • Object.keys()则不一样,它返回一个对象上所有可枚举的属性,仅仅是该实例中的

    Object.keys(instance)// ["name"]

e.总结
以上讨论了构造函数,原型和实例的关系:

  • 每个构造函数都有原型对象

  • 每个原型对象都有一个constructor指针指向构造函数

  • 每个实例都有一个__proto__指针指向原型

2.继承

其实是一个道理,这里我们不难想到,将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中文網其他相關文章!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn