JS對象與原型

一. JS的對象

1.1 建立對象的幾種方式

1.1.1 經過字面量建立對象

在js中,一對{} 其實就是一個對象vue

var person = {
            name: "tom",
            age: 23,
            read: function () {
                console.log(name, ": read book")
            }
        }

1.1.2 經過系統的構造函數

經過系統的構造函數建立一個空的對象,而後用js動態語言的特性,若是一個對象沒有某個屬性或者方法,那麼咱們點一下再附上值就行了程序員

var person2 = new Object()
        person2.name = "jerry"
        person2.age = 23
        person2.say = function () {
            console.log(person2.name, ": say hello")
        }

1.1.3 經過自定義構造方法

自定義構造方法通常都是首字母大寫的函數瀏覽器

function Person(name, age, sex) {
            this.name = name
            this.age = age
            this.sex = sex
            this.say = function () {
                console.log(this.name, " :say hello")
            }
        }

				// 建立對象時,使用 new 關鍵字
		    p = new Person("tom", 23, "man")
        console.log(p instanceof Person)

自定義的構造方法建立對象,會經歷以下幾個步驟函數

  1. 開闢空間
  2. 將this設置成當前對象
  3. 初始化屬性和方法
  4. 將this返回

1.1.4 工廠模式建立對象

function Person(name,age,sex) {
            // new Object 做爲當前的返回值
            var obj  = new Object()
            obj.name = name
            obj.age = age
            obj.sex = sex
            obj.say = function () {
                console.log(this.name," :say hello")
            }
       			// 手動將對象返回出去
            return obj
        }
				// 工廠模式建立對象,不須要使用new 關鍵字
        var p =  Person("tom",23,"man")
        console.log(p instanceof Person) // false

1.2 構造函數與實例對象

看下面的例子:this

// 構造函數和實例的關係
        function Person(name) {
            this.name = name
            this.say = function () {
                console.log(this.name," :say hello")
            }
        }

        // 對象p是經過 自定義的構造函數Person建立出來的
        var p = new Person("tom")

        console.dir(p)
        console.dir(Person)

打印的結果以下:prototype

  • 實例對象的__proto__屬性中有constructor屬性,上面記錄着本身的構造方法。
  • Person是構造方法,也是對象,咱們直接打印Person獲得的結果中有個屬性prototype,它裏面也有個屬性叫作 constructor。裏面記錄着構造方法就是本身自己。
  • 結合上面的例子,咱們其實能夠獲得這樣的推斷,實例對象的原型屬性 和 構造函數的原型屬性中的constructor都指向了同一個構造方法 ,而後能夠進一步推斷 p是Person類型

__prototype__實際上就是原型對象,在下文中會詳細的說code

仍是上面的例子,看以下的輸出也就能理解了對象

console.log(p.constructor === Person) // true
        console.log(p.__proto__.constructor == Person) // true
        console.log(p.__proto__.constructor == Person.prototype.constructor) // true
        // 由此推斷出,p === Person
        console.log(p instanceof Person) // true

其實有個小問題,看上面代碼的第一行console.log(p.constructor === Person) 咱們經過上面的代碼也看不到實例對象p constructor屬性啊,怎麼就能用,也不報錯undefined呢?blog

其實這就是牽扯到js對象的原型鏈了,(下面的章節會說),總的來講,就是js的對象會優先使用構造方法中的屬性和方法,若是構造函數中不存在咱們使用的屬性和方法的話,就嘗試去這個對象所對應的構造方法中的原型對象中的屬性和方法,再沒有就會報錯。事件

二. JS的原型

2.1 引入原型的必要性

爲何會忽然再來看js的原型呢?

由於看到了vue的源碼中,大量的方法都被添加再vm的原型上,因此,回顧一下原型確定是躲不過去了。

通常咱們使用原型就是爲了節省空間。

想理解節省了什麼空間? 那就看看下面這個不節省空間的例子。

// 構造函數建立對象帶來的問題
        function Person(name) {
            this.name = name
            this.say = function () {
                console.log(this.name,": say hello")
            }
        }

        var p1 = new Person("tom")
        var p2 = new Person("jerry")

        p1.say()  // tom : say hello
        p2.say() //  jerry : say hello

        // todo 返回false, 表示說,p1和p2的say方法,並非同一份, 其實這並非一件好事
        console.log(p1.say == p2.say)

上面的p1 和 p2 都是經過一個構造函數建立出來的不一樣對象,他們裏面都有say這個函數,當咱們輸出 p1.say == p2.say 時,返回了false,說明每一個對象中都有一份say方法,那假設有1000個對象,豈不是就有1000個say方法了? 這確定是浪費空間的。

那麼有沒有辦法可讓每次new出來的對象都使用一份say方法呢?

固然,以下:

// 共享函數,引出原型
        function Say() {
            console.log(this.name, ": say hellp")
        }

        function Person(name) {
            this.name = name
            this.say = Say
        }

        var p1 = new Person("tom")
        var p2 = new Person("jerry")

        p1.say()// tom : say hellp
        p2.say()// jerry : say hellp

        // 這樣的話,確實能實現節省空間,可是容易出問題
        console.log(p1.say == p2.say) // ture

如今確實實現了咱們的需求,可是不夠優雅,並且統一出現問題,js是動態類型的語言,那咱們像下面這樣,假設不知道已經有Say這個函數了,而後將var Say = "hello"放置在第Say函數以後,就會產生覆蓋。

2.2 認識原型

看下的例子:咱們往構造方法的原型對象上添加一個say方法。

其實這塊也不是很差理解,你想啊,js的對象經過構造方法建立出來,咱們把公共的方法,屬性放在構造方法的原型對象中,是否是就可讓他們共享這些方法和屬性呢?

function Person(name) {
            this.name = name
        }

        // 在原型上添加方法
        // 爲何能夠說原型是對象呢? 想一想js中一個對象能夠經過 點 , 動態點添加屬性和方法?
        Person.prototype.say = function () {
            console.log(this.name,":say hello")
        }

        var p1 = new Person("tom")
        var p2 = new Person("jerry")

        p1.say()//tom :say hello
        p2.say()//jerry :say hello
        console.log(p1.say == p2.say) // true

經過console.dir()打印下上面的實例對象和構造函數,獲得以下圖:

console.dir(p1)
        console.dir(p2)
        console.dir(Person)

經過上圖能夠看到,能夠獲得下面的結論:

  • 實例對象中的直接擁有的標準屬性,好比name, 這些都是直接出如今構造方法中的屬性,並且這些屬性是js對象所私有的。
  • 上圖中實例對象有個屬性叫作:__proto__ , 這個屬性是用來給瀏覽器使用的,而不是給程序員使用,因此咱們稱它爲非標準屬性。 此外谷歌瀏覽器是支持這個屬性的,可是在IE8瀏覽器中,咱們執行這句console.log(p1.__proto__) 會報錯,說undefined

2.3 原型,實例對象,構造函數之間究竟是什麼關係呢?

  1. 實例對象是經過 new 構造函數建立出來的,因此構造函數是建立實例對象的模版。

  2. 構造函數就是那個首字母大寫的函數,在js裏面咱們能直接console.log(構造函數) 由於這個構造函數其實也是個對象。

  3. 原型的做用咱們說了,就是爲了將公共的方法抽取出來,所有存放在構造函數的原型對象中,而實現數據的共享,節省內存。

  4. 實例對象的·__proto__, 是個非標準屬性,也是個對象,這個對象指向了 構造方法的prototype 屬性。

  5. 構造方法的 prototype屬性是個標準屬性, 同時也是個對象,咱們對經過 構造方法.prototype.屬性/方法 = XXX 的方式爲其添加屬性和方法。

  6. 咱們經過 對象.屬性/方法時, 會優先從對象的構造方法中查找,若是找不到的會再嘗試從原型中查找,這也是爲何會出現一個明明沒有爲一個對象添加相應的屬性或者方法可是對象卻能點出來,而且能正常使用。固然若是原型中也不存在的話,就會報錯說 undefined

2.4 關於this對象

  • 看下面的第一個例子

下面出現的this並不難理解, 就是咱們new 出來的對象自己

function Person(name) {
            // 考慮一下,這個this是誰?
            this.name = name
            console.log(this)
        }

        var p = new Person("tom")
  • 看下面的第二個例子

咱們在構造方法的原型對象上添加一個方法say,在這say方法中使用的this對象指的一樣是 咱們new 出來的對象自己。即方法的調用者。

function Person(name) {
            // 考慮一下,這個this是誰?
            this.name = name
            console.log("n10: ",this)
        }

        Person.prototype.say = function () {
            // todo 這裏的this指的是誰呢?
            // 首先,方法是添加在原型對象上, 那麼this指的是原型對象嗎?
            // 經過控制檯能夠看到,this.name ,其實不是原型對象,而是say()方法的調用者(實例對象)
            console.log("n16: ",this.name,": say hello")
        }

        var p1 = new Person("tom")
        var p2 = new Person("jerry")

        p1.say()
        p2.say()
  • 看下面的第三個例子:

下面在給構造方法的原型對象添加方法時,不只出現了this, 還出現了that。

this對象依然是咱們手動new出來的對象自己。

that一樣是指向了咱們new出來的對象自己,之因此須要中轉一下,是由於在按鈕的點擊事件裏面,this指向的是按鈕自己。

// 用面向對象的方式封裝構造函數
    function ChangeStyle(btnId, dvId, color) {
        this.btnObj = document.getElementById(btnId)
        this.dv = document.getElementById(dvId)
        this.color = color
    }

    // 在構造方法的原型上添加方法
    ChangeStyle.prototype.init = function () {
        // 這裏面的this表示的是 調用init方法的實例對象
        var that = this
        this.btnObj.onclick = function () {
            // todo  爲何原型中的函數中,就不能使用this,而是that呢???
            // todo  或者問下,當前函數中的this是誰呢?
            that.dv.style.backgroundColor = that.color
        }
    }

2.5 其餘原型的寫法

  • 最多見的寫法就是像下面這樣,在當前原型的基礎上添加屬性或者方法
function Person(name) {
        this.name = name
    }

    // 前面的例子中咱們都是像下面這樣寫代碼, 這實際上是對原來的  原型對象屬性的累加
    // 原來的原型對象中有個屬性,叫作consturctor
    Person.prototype.say  = function(){
        //todo
    }
  • 也能夠像下面這樣

這樣設置原型的話,其實是對原來的原型對象的覆蓋,因此說須要像下面這樣從新添加constructor的指向。

固然我也試了一下,若是說覆蓋原來的原型對象,且不添加contructor的指向,咱們使用 instanceof 判斷實例對象是不是對應的構造函數類型時,仍是能獲得正確的結果。

Person.prototype = {
        constructor:Person, // 手動修改構造器的指向
        height:"20",
        weight:"20",
        say:function () {
            // todo
        }
    }

2.6 方法之間的相互訪問

  • 構造函數中的成員方法是能夠相互訪問的。
function Person(name) {
        this.name = name
        this.say = function () {
            console.log("say")
            // 經過這個例子,能夠看到,對象的方法中能夠直接調用對象的方法
            this.eat()
        }
        this.eat = function () {
            console.log("eat")
        }
    }
  • 原型中的方法也是能夠相互訪問的。
function Person(name) {
        this.name = name
    }

    Person.prototype.say  = function(){
        console.log("say")
        // 原型中的方法也能夠相互訪問
        this.eat()
    }

    Person.prototype.eat  = function(){
        console.log("eat")
    }

    var p1 = new Person("tom")
    p1.say()

2.7 覆蓋內置對象原型中的方法

像這樣就能夠實現對原型中的方法進行覆蓋的操做。

固然能夠經過在原型上添加方法實現對原有封裝類的拓展。

// 在現有的js封裝類上幹這件事,也算是在修改源碼
    String.prototype.myReverse = function () {
        for (var i = 0; i < this.length; i++) {
            console.log("發生倒敘")
        }
    }
    var str = "123"
    str.myReverse()
相關文章
相關標籤/搜索