來面向對象吧

github連接git

a = a || {} 是什麼意思&&命名空間

面向對象的面向二字不是咱們現實中的面向的意思,而是以。。。爲主的意思github

說一下JS裏面的"&&"和"||":數據庫

一段代碼若是都是用&&鏈接的那麼它就按從前日後的順序返回第一個falsy值(記住5個falsy值:0 NaN '' null undefind),若是找到第一個falsy值,就截止了後面的代碼再也不看了數組

一段代碼若是都是用||鏈接的那麼它就按從前日後的順序返回第一個trusy值(除了falsy值以外的值),若是找到第一個trusy值,就截止了後面的代碼就再也不看了瀏覽器

a = b || {}t它的意思就是,若是b是trusy那麼a =b,若是b不是一個trusy那麼a = {},也就是等價於bash

if(b){
    a = b
}else {
    a = {}
}
複製代碼

咱們平時寫: var a = 1其實這是很是危險的操做,是有可能被辭退的,由於你知道a之前的值嗎?就直接將a的值從新賦值爲1閉包

var a = a || {},它等價於下面這樣寫,它是一種兜底的寫法app

if(a){
    a = a
}else{
    a = {}
}
複製代碼

a = a || {}這種寫法的用途是什麼?函數

// 全局命名空間

var MYAPP = MYAPP || {}

複製代碼

它的意思是首先檢查MYAPP是否已經被定義,若是是的話使用現有的全局對象,不然建立一個名爲MYAPP的空對象用來封裝方法、函數、變量和對象佈局

命名空間有什麼用呢》之後全部的變量都掛在MYAPP上,MYAPP包含了因此的命名,它就像一個房間同樣,裝了全部的屬性,因此它叫命名空間。命名空間的英文很好理解叫namespace,就是name所在的space,現實中咱們電腦上的不少文件夾就是相似命名空間,文件夾下面有不少文件

咱們也能夠建立子命名空間:

// 子命名空間
MYAPP.event = {};
複製代碼

下面是用於建立命名空間和添加變量,函數和方法的代碼寫法:

// 給普通方法和屬性建立一個叫作MYAPP.commonMethod的容器
MYAPP.commonMethod = {
  regExForName: "", // 定義名字的正則驗證
  regExForPhone: "", // 定義電話的正則驗證
  validateName: function(name){
    // 對名字name作些操做,你能夠經過使用「this.regExForname」
    // 訪問regExForName變量
  },
 
  validatePhoneNo: function(phoneNo){
    // 對電話號碼作操做
  }
}

// 對象和方法一塊兒申明
MYAPP.event = {
    addListener: function(el, type, fn) {
    //  代碼
    },
   removeListener: function(el, type, fn) {
    // 代碼
   },
   getEvent: function(e) {
   // 代碼
   }
  
   // 還能夠添加其餘的屬性和方法
}

//使用addListener方法的寫法:
MYAPP.event.addListener("yourel", "type", callback);
複製代碼

給對象一個類

接續優化簡歷的代碼:

前面將雜亂的代碼按照各自的功能劃分了幾個模塊也就是幾個js文件,每一個模塊裏面都使用了MVC,若是咱們單從每一個文件看代碼是沒有問題的,可是若是咱們站在一個更高的角度去看全部的模塊,就會發現不一樣模塊之間既然都有MVC那爲何要寫那麼屢次,也就是說代碼出現了跨文件或者跨模塊的重複。

那怎麼辦呢?

就是分別把model對象、view對象。controller對象封裝成模板,之後使用的時候直接基於模板去修改部分個性化的東西就能夠了

類就是一種模板,這個模板就是寫一個函數而後把屬於同一類特色的的對象高度歸納起來或者高度抽象化,你須要具體執行什麼只須要給這個抽象的函數傳具體的參數而後調用這個函數便可,模板就是具備高度歸納性的、高度抽象性的函數(函數特色就是高度歸納性和高度抽象性),它的功能就是用一個函數高度歸納出具備相同特徵的某一類對象。

簡單點說,類就是模板,模板就是把共用的代碼抽出來塞進函數裏面,把非共用的代碼空着(參數),調用這個函數的時候你只需根據實際狀況填空就能夠了

舉個不太恰當的例子,類就像是設計師給家裏設計的一套裝修風格的設計圖,選擇這一風格的不少人就至關於屬於具備相同特色,具體裝修的時候,咱們給我的不必定都要和設計師給的設計圖如出一轍的去改造房子,每一個人會根據本身的喜愛去更改部分的傢俱啊、地板啊、牆紙啊、電器啊等等等,可是這些人均可以使用這一套模板。由於總體的裝修佈局和風格是同樣的,這個設計師給的設計圖就至關於類或者JS中的模板

先從封裝View開始

咱們發現內次都要寫document.querySelector('xxx')也是一件很煩的事情,咱們新建js/base/View.js

window.View =function(selector){
    return document.querySelector(selector)
}
複製代碼

將View綁定在window上面就是爲了在全局的環境下能夠訪問到

那麼message.js裏面的view就由:

var view = document.querySelector('#message') 簡化成var view = View('#message')

那麼smooth-jump.js裏面的view就由:

var view = document.querySelectorAll('.menu > ul >li')簡化成var view = View('.menu > ul >li')

那麼sticky.js裏面的view就由:

var view = document.querySelector('#topNavBar')簡化成:View('#topNavBar')

這樣就把3次document。。。簡化成1次

接下來封裝Model

新建js/base/Model.js

以前的Model是這樣的:

var model = {
        // 從數據庫中讀取數據
        fetch: function () {
            // 批量獲取存儲在Message數據庫中的數據
            var query = new AV.Query('Message');
            return query.find()    // find返回的也是一個Promise對象
        },
        //往數據庫中存數據
        save: function (name, content) {
            // 初始化一個叫Message的數據庫
            var Message = AV.Object.extend('Message');
            // 實例化這個數據庫對象
            var message = new Message();
            // 在數據庫中存數據,key是content,value就是用戶在輸入框中輸入的內容
            return message.save({   //save返回的是一個Promise對象
                'name': name,
                'content': content
            })
        },
        init: function () {
            // 初始化
            var APP_ID = 'o76YO2bd1s3xSJFBr3GnwzKu-gzGzoHsz';
            var APP_KEY = 'wgEnjO9GVghERifYXYM9APy5';
            AV.init({ appId: APP_ID, appKey: APP_KEY });
        }
    }
複製代碼

咱們封裝Model:

window.Model = function(options){
    let resourceName = options.resourceName
    return {
        init: function(){
            // 初始化
            var APP_ID = 'o76YO2bd1s3xSJFBr3GnwzKu-gzGzoHsz';
            var APP_KEY = 'wgEnjO9GVghERifYXYM9APy5';
            AV.init({ appId: APP_ID, appKey: APP_KEY });
        },
        fetch: function(){
            // 批量獲取存儲在Message數據庫中的數據
            var query = new AV.Query(resourceName);
            return query.find()    // find返回的也是一個Promise對象
        },
        save: function(object){
            // 初始化一個叫Message的數據庫
            var dataBase = AV.Object.extend(resourceName);
            // 實例化這個數據庫對象
            var example = new dataBase();
            // 在數據庫中存數據,key是content,value就是用戶在輸入框中輸入的內容
            return example.save(object)
        }
    }
}
複製代碼

批註:

1.因爲有各類不一樣的數據庫,所以’Message‘被替換成了參數’resourceName‘

2.因爲存的數據能夠有不少種,因此,save裏面的對象被替換成了一個參數’object‘

3.函數fetch和函數save裏面用到了的外面它外面的變量resourceName這裏構成了閉包

4.很容易看出Model就變成了一個模板,把公共的代碼都寫好了,你只須要會填空就能夠了,什麼叫填空?就是你須要改模板的哪裏直接把參數resourceName、object傳進來就能夠了,參數就是放那些私有的代碼

5.這樣作的好處就是若是其餘文件或者模塊裏還有model咱們只須要var model = Model({'resourceName': 'xxx'})把xxx改成對應的數據庫的名字便可,調用model.save(xxx)方法的時候只須要把參數xxx改成`{key: 變量; key: 變量}的形式便可

封裝以後的message.js的寫法:

!function () {
    var view = View('#message')
    var model = Model({'resourceName': 'Message'})
    var controller = {
        view: null,
        model: null,
        messageList: null,
        myForm: null,
        init: function (view, model) {
            this.view = view
            this.model = model

            this.messageList = view.querySelector('#messageList')
            this.myForm = view.querySelector('#postMessageForm')
            this.model.init()
            this.loadMessages()
            this.bindEvents()
        },
        loadMessages: function () {
            this.model.fetch().then(
                (messages) => {
                    let array = messages.map((item) => item.attributes)
                    array.forEach((item) => {
                        let li = document.createElement('li')
                        li.innerText = `${item.name}:\xa0\xa0\xa0\xa0\xa0\xa0${item.content}`
                        this.messageList.appendChild(li)
                    })
                },
                function (error) {      // 異常處理 
                    alert('數據提交失敗,請稍後再試')
                }).then((x) => { }, (error) => console.log(error))
        },
        bindEvents: function () {       // 留言存儲數據
            // 應該監聽form的submit事件,應爲用戶回車也算是submit
            this.myForm.addEventListener('submit', (e) => {
                e.preventDefault()
                this.saveMessage()
            })
        },
        saveMessage: function () {
            // 拿到用戶在輸入框中輸入的內容
            let myForm = this.myForm
            let content = myForm.querySelector('input[name = content]').value
            let name = myForm.querySelector('input[name = name]').value
            if (myForm.querySelector('input[name = content]').value != '' && myForm.querySelector('input[name = name]').value != '') {
                this.model.save({'name':name, 'content':content}).then(function (object) {
                    let li = document.createElement('li')
                    li.innerText = `${object.attributes.name}:\xa0\xa0\xa0\xa0\xa0\xa0${object.attributes.content}`
                    let messageList = this.messageList
                    messageList.appendChild(li)
                    myForm.querySelector('input[name = content]').value = ''
                    myForm.querySelector('input[name = name]').value = ''
                    // window.location.reload()
                    // console.log('數據存入成功')

                })
            } else { 
                return
            }

        }
    }
    controller.init.call(controller, view, model)
}.call()
複製代碼

注意:

1.這裏的saveMessage方法裏面的調用save方法要傳參數this.model.save({'name':name, 'content':content}) 2. 執行var model = Model({'resourceName': 'Message'})後model就變成了一個包含了3個方法init、fetch、save的對象了,在咱們須要的時候咱們只須要model.init()、model.fetch()、model.save()就能夠實現具體的功能了

說下代碼複雜度守恆定律,無論怎麼寫代碼的負責度是不會下降的只是代碼會變得漂亮一些

下圖展現了咱們封裝了類的狀況:

咱們作的就是下面這樣,Model和Vontroller也是相似的

View = function(){
    return {
        view 代碼
        (留空)
    }
}

View(填空)
複製代碼

接下來封裝相對比較複雜的Controller(須要反覆的看,看懂每一行代碼,做爲新手)

新建js/base/Controller.js

未封裝類以前message.js的代碼:

!function () {
    var view = View('#message')
    var model = Model({'resourceName': 'Message'})
    var controller = {
        view: null,
        model: null,
        messageList: null,
        myForm: null,
        init: function (view, model) {
            this.view = view
            this.model = model

            this.messageList = view.querySelector('#messageList')
            this.myForm = view.querySelector('#postMessageForm')
            this.model.init()
            this.loadMessages()
            this.bindEvents()
        },
        loadMessages: function () {
            this.model.fetch().then(
                (messages) => {
                    let array = messages.map((item) => item.attributes)
                    array.forEach((item) => {
                        let li = document.createElement('li')
                        li.innerText = `${item.name}:\xa0\xa0\xa0\xa0\xa0\xa0${item.content}`
                        this.messageList.appendChild(li)
                    })
                },
                function (error) {      // 異常處理 
                    alert('數據提交失敗,請稍後再試')
                }).then((x) => { }, (error) => console.log(error))
        },
        bindEvents: function () {       // 留言存儲數據
            // 應該監聽form的submit事件,應爲用戶回車也算是submit
            this.myForm.addEventListener('submit', (e) => {
                e.preventDefault()
                this.saveMessage()
            })
        },
        saveMessage: function () {
            // 拿到用戶在輸入框中輸入的內容
            let myForm = this.myForm
            let content = myForm.querySelector('input[name = content]').value
            let name = myForm.querySelector('input[name = name]').value
            if (myForm.querySelector('input[name = content]').value != '' && myForm.querySelector('input[name = name]').value != '') {
                this.model.save({'name':name, 'content':content}).then(function (object) {
                    let li = document.createElement('li')
                    li.innerText = `${object.attributes.name}:\xa0\xa0\xa0\xa0\xa0\xa0${object.attributes.content}`
                    let messageList = this.messageList
                    messageList.appendChild(li)
                    myForm.querySelector('input[name = content]').value = ''
                    myForm.querySelector('input[name = name]').value = ''
                    // window.location.reload()
                    // console.log('數據存入成功')

                })
            } else { 
                return
            }

        }
    }
    controller.init.call(controller, view, model)
}.call()
複製代碼

封裝類就是造一個模板,也就是寫一個函數把公共的代碼放在模板return的對象裏,若是你有特殊的代碼你就本身加(用形參變量空着),調用函數的時候填空(給形參變量傳實參),也就是說調用模板的時候咱們只須要寫咱們特殊的代碼,其餘公共的代碼交給模板去辦事

封裝的Controller類:

window.Controller = function(options){
    let init = options.init 
    return {
        view: null,
        model: null,
        init: function(view,model){
            this.view = view
            this.model = model
            this.model.init()
            init.call(this,view,model)
            options.bindEvents(this)
        }
    }
}
複製代碼

使用Controller類的message.js的寫法:

!function () {
    var view = View('#message')
    var model = Model({ 'resourceName': 'Message' })
    var controller = Controller({
        init: function (view, model) {
            this.messageList = view.querySelector('#messageList')
            this.myForm = view.querySelector('#postMessageForm')
            this.loadMessages()
        },
        loadMessages: function () {
            this.model.fetch().then(
                (messages) => {
                    let array = messages.map((item) => item.attributes)
                    array.forEach((item) => {
                        let li = document.createElement('li')
                        li.innerText = `${item.name}:\xa0\xa0\xa0\xa0\xa0\xa0${item.content}`
                        this.messageList.appendChild(li)
                    })
                },
                function (error) {      // 異常處理 
                    alert('數據提交失敗,請稍後再試')
                }).then((x) => { }, (error) => console.log(error))
        },
        bindEvents: function () {       // 留言存儲數據
            // 應該監聽form的submit事件,應爲用戶回車也算是submit
            this.myForm.addEventListener('submit', (e) => {
                e.preventDefault()
                this.saveMessage()
            })
        },
        saveMessage: function () {
            // 拿到用戶在輸入框中輸入的內容
            let myForm = this.myForm
            let content = myForm.querySelector('input[name = content]').value
            let name = myForm.querySelector('input[name = name]').value
            if (myForm.querySelector('input[name = content]').value != '' && myForm.querySelector('input[name = name]').value != '') {
                this.model.save({ 'name': name, 'content': content }).then(function (object) {
                    let li = document.createElement('li')
                    li.innerText = `${object.attributes.name}:\xa0\xa0\xa0\xa0\xa0\xa0${object.attributes.content}`
                    let messageList = this.messageList
                    messageList.appendChild(li)
                    myForm.querySelector('input[name = content]').value = ''
                    myForm.querySelector('input[name = name]').value = ''
                    // window.location.reload()
                    // console.log('數據存入成功')

                })
            } else {
                return
            }

        }

    })
    controller.init.call(controller, view, model)
}.call()
複製代碼

咱們發現這樣作控制檯會報錯:

message.js:8 Uncaught TypeError: this.loadMessages is not a function

它說的是第八行的this.loadMessages()不是一個函數!!爲何呢?分析下這裏的this是什麼?它就是init.call(this)裏面的this,那麼init是在哪裏被調用的呢?它是在Contrller.js裏的init.call(this,view,model),這個this又是誰呢?這個this就是Controller返回的對象,也就說Controller返回的對象裏面沒有loadMessages方法,那怎麼辦呢?

咱們須要把options傳來的全部東西都放到Controller返回的對象上!由於在調用Controller的時候,init方法裏面的this都會去Controller返回的對象上去找

代碼學到深處的難點就是看大腦中有沒有調用棧,就像本例中須要記住options是什麼,obj是什麼,無論後面怎麼操做,依然能記得options是什麼,obj是什麼,咱們以爲代碼難理解是由於大腦中的內存不夠了,記不住options和obj

分析下過程:首先是 var controller = Controller(options),這個options有它本身的屬性:bindEvents:fn()、init():fn()、saveMessage:fn()、loadMessage:fn()。如今是把message.js和模板的代碼Controller.js裏面的代碼合起來了,模板裏面有一個init方法,先去調用這個模板裏面的init方法而後模板裏面的init再去調用私有的init。那怎麼作呢?在模板文件裏使用閉包用let init = options.initinit記住options裏面的init,由於只有這個函數是特殊的其餘的函數都不是特殊的也就是說是私有的,Controller裏面傳的參數options就是私有的特殊的代碼,也就是說用object裏面的init調用了options裏面的init,調用的時候init.call(this, view, model)傳的對象this就是obj。也就是說object的init被調用的時候,它會調用options的init,同時他會把裏面的this變成objectinit.call(this, view, model)

修改後的模板Controller:

window.Controller = function (options) {
    let init = options.init
    for (let key in options) {
        if (key !== 'init') {
            object[key] = options[key]
        }
    }
    let object = {
        view: null,
        model: null,
        init: function (view, model) {
            this.view = view
            this.model = model
            this.model.init()
            init.call(this, view, model)
            options.bindEvents(this)
        }
    }
    return object
}
複製代碼

再來看一下Controller模板裏面的this是指誰?init.call(this, view,model),看下上面的代碼,這個this就是init被調用的時候裏面傳的東西,那init是怎麼調的呢?回到message.js,(由於這裏的init是私有的init,如何理解私有?就是否是模板裏面的方法也就是否是公共的代碼,也就是調用模板的時候傳的個性化參數),controller.init.call(controller, view, model),因此這個this就是controller對象,controller對象是哪來的呢?就是Controller模板return出去的object,因此這裏的this就是object

或者換一種方法去理解:

1.controller是什麼?

controller是Controller構造出來的,因此他就是Controller return出來的東西就是 object

====> controller === object

2.controller.init.call(controller, view, model),controller調init的時候裏面的this是什麼?固然是controller,也就是1裏面的object 到這裏咱們就肯定了object裏面的init的this就是object

window.Controller = function (options) {
    let init = options.init             // B
    for (let key in options) {
        if (key !== 'init') {
            object[key] = options[key]
        }
    }
    let object = {
        view: null,
        model: null,
        init: function (view, model) {    // A
            this.view = view
            this.model = model
            this.model.init()
            init.call(this, view, model)
            options.bindEvents(this)
        }
    }
    return object
}
複製代碼

3.下面又牽扯到做用域的知識了,請問init.call(this, view, model)這裏的init是哪裏的init? 是A的init仍是B的init?固然是B的init,A的init是屬性,B的init是變量。也就是說這個this就是init也就是B的init也就等價於init(B).call(this, view, model),這裏面的this就是第2條的this也就是object,因此init(B)裏面的this就是object

總結:

1.controller === object

2.controller.init(view, model) controller.init.call(controller, view, model) 那麼 controller.init 裏面的 this 固然 TM 是 controller 也就是這個1裏面的object controller.init 裏面的 this 就是 object object.init 裏面的 this 就是 object

3.initB.call(this) initB 裏面的 this === call 後面的this call 後面 this === 第二條裏的 this 第二條裏面的 this === object => initB 裏面的 this 就是 object

因此init.call(this, view, model)就是以object爲this調用init

那麼init.call(this, view, model)這個init要去看options裏面的init,它是哪來的?它是傳進來的,要去message.js,發現message.js裏面的init只有3行代碼:

this.messageList = view.querySelector('#messageList')
this.myForm = view.querySelector('#postMessageForm')
this.loadMessages()
複製代碼

它裏面的this是什麼,根據上面的第三條推論發現這個this就是object,也就是說他要去object裏面找messageList、myForm、loadMessages()

那麼問題來了:object上有這3個屬性嗎?

發如今不寫for循環以前是沒有的,因此誰有?options上有,因此就從options上拷貝過來,因此纔有Controller.js文件的代碼

for (let key in options) {
        if (key !== 'init') {
            object[key] = options[key]
        }
}
複製代碼

Controller.js裏面的init裏面的因此的this都被咱們搞成了object

init: function (view, model) {   
            this.view = view
            this.model = model
            this.model.init()
            init.call(this, view, model)
            options.bindEvents(this)
        }
複製代碼

致使message.js裏面的this也會被傳爲object

this.messageList = view.querySelector('#messageList')
this.myForm = view.querySelector('#postMessageForm')
this.loadMessages()
複製代碼

可是object裏面沒有messageList、myForm、loadMessages()因此就從options裏面拷過來

這樣咱們在使用類Controller的時候,咱們只須要作咱們個性化的代碼便可,其餘的咱們什麼也不用作了,就算是view和model也能夠直接拿來用,由於它們已經綁定在this上面,你也不須要知道this是什麼 (咱們知道this是咱們返回的object)

controller = Controller({
    init: function(){
        this.view
        this.model
        this.xxx()
        this,yyy()
    },
    xxx: function(){},
    yyy: function(){}
})
複製代碼

面向對象就是刷出一個對象

由上圖知:咱們建立了三個文件,後面2個文件和第一個文件結構都同樣,雖然結構都同樣,可是並無重複的代碼,3個彼此寫各自不一樣的代碼,好比第一個文件的view是‘x’,第二個文件的view是‘y’,第三個文件的view是‘z’,咱們只寫彼此不一樣的代碼,一樣的代碼都放在3個模板對象(或類)裏,小的m、v、c實際上叫大的M、V、C的實例,就比如是把模板經過魔法把它變成能夠用的代碼,小v1小v2小v3都是大V的實例,它們都使用了V上相同的代碼,也彼此各自個性化的代碼,小m1m2m3,小c1cc3也同樣

這就是面向對象!!!不用管名字叫什麼,名字和以上的思路沒有關係

面向對象就是:既然不少對象都有相同的行爲或相同的屬性,爲何不把相同屬性或相同的行爲放在一個地方,你要用的時候就刷一下(實例化),刷出一個對象,因此面向對象的核心就是刷對象,這個對象上有你須要的全部東西,除了你的個性化的自定義的代碼

----------------------------------------------結束---------------------------------------

上面的代碼真的很複雜,由於涉及到了引用、this、做用域、閉包,須要花時間去理解每一行代碼

複習一下this

例子

button.onclick = function f1(){
    console.log(this)
}
複製代碼
button.addEventListener('click',function(){
    console.log(this)
})
複製代碼
$('ul').on('click',function(){
    console.log(this)
})
複製代碼

解答:

1.咱們怎麼知道這個函數被call的時候穿的第一個參數是什麼?

不知道

去看onclick的源碼啊 ---> 作不到

MDN的開發者知道onclick的源碼,f1.call(???)

MDN的開發者寫了文檔

看文檔呀

MDN的文檔是怎麼說的 ========>

因此第一個函數的this,就是觸發事情的元素,也就是button

2.咱們怎麼知道這個函數被call的時候穿的第一個參數是什麼?

不知道

去看addEventListener的源碼啊 ---> 作不到

MDN的開發者知道addEventListener的源碼

MDN的開發者寫了文檔

看文檔呀

MDN的文檔是怎麼說的 ========>

因此第二個函數的的this,是該元素的引用button

3.去看jQuery的源碼啊 ---> 作不到

jQuery的開發者知道addEventListener的源碼

jQuery的開發者寫了文檔

看文檔呀

jQuery的文檔是怎麼說的 ========>

因此,對於代理事件而言,this 則表明了與 selector 相匹配的元素,也就是這裏的li元素

再回到this的定義,this就是函數被call的時候傳的第一個參數,寫on、onclick。addEventListener的人他們想call的時候給第一個參數傳什麼東西,this就是什麼東西,只能看源碼或者看文檔才能知道this 是什麼

this就是函數被call傳的第一個參數,

button.onclick ({name: 'Reagen'}),即便瀏覽器說這個this是button,咱們也可讓它不是button,咱們在調用函數的時候能夠自主的去指定this,由於this就是一個參數或者說一個變量,我傳什麼它就是什麼

再看一道題:

function X(){
    return object = {
        name: 'object',
        f1(x){
            x.f2()
        },
        f2(){
            console.log(this) // A
        }
    }
}

var options = {
    name: 'options',
    f1(){},
    f2(){
        console.log(this) // B
    }
}

var x = X()
x.f1(options)
複製代碼

答案是: B this是options

再看一題:

function X(){
    return object = {
        name: 'object',
        f1(x){
            x.f2.call(this)
        },
        f2(){
            console.log(this) // C object?options
        }
    }
}

var options = {
    name: 'options',
    f1(){},
    f2(){
        console.log(this) // B object?options
    }
}

var x = X()
x.f1(options)
複製代碼

答案是D this是object

x=X(),說明x是object,x.f1(options)就是object.f1.call(this,options),因此f1裏面的this就是object,也就是說object去調f1而且把options做爲參數傳進去,因此x就是options,options.f2.call(this)就是 options.f2.call(object),進入options裏面的f2並執行f2,f2裏面的this就是object

再看一題:

function X(){
    return object = {
        name: 'object',
        options: null,
        f1(x){
            this.options = x
            this.f2()
        },
        f2(){
            this.options.f2.call(this)
        }
    }
}

var options = {
    name: 'options',
    f1(){},
    f2(){
        console.log(this) // this 是啥 ?
    }
}

var x = X()
x.f1(options)
複製代碼

答案是object

object.f1.call(this,options),調用f1,f1裏面的this就是object

this.f2(),也就是object.f2(object,undefined)

object裏面的f2的this仍是object

因爲f1裏面的object.options = options

因此this.options.f2.call(this) 就是options.f2.call(object,undefined)

因此options裏面的f2裏面的this就是object

new是作啥的

原文:JS 的 new 究竟是幹什麼的?

new的起源

例如:造士兵

var 士兵 = {
  ID: 1, // 用於區分每一個士兵
  兵種:"美國大兵",
  攻擊力:5,
  生命值:42, 
  行走:function(){ /*走倆步的代碼*/},
  奔跑:function(){ /*狂奔的代碼*/  },
  死亡:function(){ /*Go die*/    },
  攻擊:function(){ /*糊他熊臉*/   },
  防護:function(){ /*護臉*/       }
}

兵營.製造(士兵) // 如何使用士兵?用兵營去造

複製代碼

咱們如何製造100個士兵?

循環 100 次吧:

var 士兵們 = []
var 士兵
for(var i=0; i<100; i++){
  士兵 = {
    ID: i, // ID 不能重複
    兵種:"美國大兵",
    攻擊力:5,
    生命值:42, 
    行走:function(){ /*走倆步的代碼*/},
    奔跑:function(){ /*狂奔的代碼*/  },
    死亡:function(){ /*Go die*/    },
    攻擊:function(){ /*糊他熊臉*/   },
    防護:function(){ /*護臉*/       }
  }
  士兵們.push(士兵)
}

兵營.批量製造(士兵們)
複製代碼

從內存圖中理解,上面的代碼就很zz,每一個士兵的方法都同樣卻在內存中重複的寫了100次

每一個士兵對象有5個方法(行走、奔跑死亡。。。),100個士兵就要在內存中開闢500個空間去存它們

有沒有好的辦法去解決沒必要要的重複?

有,利用原型,將這5個對象放在一個原型對象上,這樣就不用創造500個對象去存了,只要6個對象就好了,1個原型對象上面存5個函數對象的地址,另外5個對象分別存方法函數,這樣就沒有重複了

下面的士兵共有屬性 === 士兵原型,而且士兵共有屬性上不光能夠放方法函數還能夠放士兵的相關屬性

var 士兵共有屬性 = {
  兵種:"美國大兵",
  攻擊力:5,
  行走:function(){ /*走倆步的代碼*/},
  奔跑:function(){ /*狂奔的代碼*/  },
  死亡:function(){ /*Go die*/    },
  攻擊:function(){ /*糊他熊臉*/   },
  防護:function(){ /*護臉*/       }
}
var 士兵們 = []
var 士兵
for(var i=0; i<100; i++){
  士兵 = {
    ID: i, // ID 不能重複
    生命值:42
  }

  /*實際工做中不要這樣寫,由於 __proto__ 不是標準屬性*/
  士兵.__proto__ = 士兵共有屬性 

  士兵們.push(士兵)
}

兵營.批量製造(士兵們)
複製代碼

有人指出建立一個士兵的代碼分散在兩個地方很不優雅,因而咱們用一個函數把這兩部分聯繫起

仍是存在問題:函數士兵是依賴士兵共有屬性這個對象的也就是說它們是有關聯的,可是從命名來看是看不出來的,那怎麼辦呢?就是把他們倆結合在一塊兒,那麼是把函數士兵放到士兵共有屬性這個對象裏仍是把士兵共有屬性這個對象放到函數士兵裏?

很顯然函數士兵是不能放到士兵共有屬性這個對象上的由於函數士兵上放的不是共有屬性

那麼把士兵共有屬性這個對象直接放到函數上也不行,由於這樣每次生成一個士兵,都會執行士兵共有屬性這個對象了,那麼全部的操做都白費了

那就把士兵共有屬性這個對象放到外面,放到外面又體現不出和函數士兵的關係怎麼辦呢?就是把士兵共有屬性(原型)做爲士兵的一個屬性

function 士兵(ID){
  var 臨時對象 = {}

  臨時對象.__proto__ = 士兵.原型

  臨時對象.ID = ID
  臨時對象.生命值 = 42
  
  return 臨時對象
}

士兵.原型 = {
  兵種:"美國大兵",
  攻擊力:5,
  行走:function(){ /*走倆步的代碼*/},
  奔跑:function(){ /*狂奔的代碼*/  },
  死亡:function(){ /*Go die*/    },
  攻擊:function(){ /*糊他熊臉*/   },
  防護:function(){ /*護臉*/       }
}

// 保存爲文件:士兵.js
而後就能夠愉快地引用「士兵」來建立士兵了:

var 士兵們 = []
for(var i=0; i<100; i++){
  士兵們.push(士兵(i))
}

兵營.批量製造(士兵們)
複製代碼

new關鍵字

JS 之父建立了 new 關鍵字,可讓咱們少寫幾行代碼:

只要你在士兵前面使用 new 關鍵字,那麼能夠少作四件事情:

1.不用建立臨時對象,由於 new 會幫你作(你使用「this」就能夠訪問到臨時對象); 2.不用綁定原型,由於 new 會幫你作(new 爲了知道原型在哪,因此指定原型的名字爲 prototype); 3.不用 return 臨時對象,由於 new 會幫你作; 4.不要給原型想名字了,由於 new 指定名字爲 prototype。

下面註釋的代碼都是new幫咱們作的,咱們要作的就是把士兵的自有屬性寫一下(如:this.ID = id,this.生命值 = 42),而後把士兵的共有屬性寫一下就行了,其餘的交給new去作

function 士兵(ID){
  // var temp = {}                              1
  // this = temp                                2
  // this.__proto__ = 士兵.ptototype            3
 //  士兵.ptototype = {constrouctor: 士兵}      4
  this.ID = id
  this.生命值 = 42
  // return this                                5            
}

士兵.prototype = {
  兵種:"美國大兵",
  攻擊力:5,
  行走:function(){ /*走倆步的代碼*/},
  奔跑:function(){ /*狂奔的代碼*/  },
  死亡:function(){ /*Go die*/    },
  攻擊:function(){ /*糊他熊臉*/   },
  防護:function(){ /*護臉*/       }
}
複製代碼

簡單來記:一個new === 5句話(也叫語法糖)

英文sweet suagar其實不光有甜的意思,它還有貼心的意思

一個約定俗成的事情:因爲new後面的函數已經包含建立一個新的對象的意思,因此這個函數沒有必要再叫createXXX

注意 constructor 屬性:

new 操做爲了記錄「臨時對象是由哪一個函數建立的」,因此預先已經把士兵.prototype指定爲一個對象而且把「士兵.prototype」加了一個 constructor 屬性:

士兵.prototype = {
  constructor: 士兵
}
複製代碼

若是你從新對「士兵.prototype」賦值,那麼這個 constructor 屬性就沒了也就是說new提供的對象由於在內存中沒有被引用就被回收了,而把引用地址改成指向你寫的士兵.prototype這個對象上了,因此你應該這麼寫:

士兵.prototype.兵種 = "美國大兵"
士兵.prototype.攻擊力 = 5
士兵.prototype.行走 = function(){ /*走倆步的代碼*/}
士兵.prototype.奔跑 = function(){ /*狂奔的代碼*/  }
士兵.prototype.死亡 = function(){ /*Go die*/    }
士兵.prototype.攻擊 = function(){ /*糊他熊臉*/   }
士兵.prototype.防護 = function(){ /*護臉*/       }
複製代碼

或者你也能夠本身給 constructor 從新賦值:(推薦)

士兵.prototype = {
  constructor: 士兵,
  兵種:"美國大兵",
  攻擊力:5,
  行走:function(){ /*走倆步的代碼*/},
  奔跑:function(){ /*狂奔的代碼*/  },
  死亡:function(){ /*Go die*/    },
  攻擊:function(){ /*糊他熊臉*/   },
  防護:function(){ /*護臉*/       }
}
複製代碼

總結:

例1:

當咱們寫var object = new Object()的時候到底作了什麼?

  1. 自有屬性爲空的對象
  2. object.proto = Object.prototype(__proto是對象自帶的屬性,prototype是JS之父指定的)

例2:

當咱們寫var array = new Array('a','b','c')的時候到底作了什麼?

1.自有屬性爲 0:'a' , 1: 'b', 2: 'c' 2.array.__ptoto__ === Array.prototype,array就有了push、pop等數組應有的方法了 3.Array.prototype.__proto__ === Object.prototype,Array.prototype是對象因此它的__ptoto__指向了構造出它的‘人’,對象都是由Objec構造出來的

例3:

var fn = new Function('x', 'y', 'return x+y')
自有屬性 length:2, 不可見的函數體: 'return x+y'
fn.__proto__ === Function.prototype
複製代碼

例4:

Array is a function
Array = function(){...}
Array.__proto__ === Function.prototyp
複製代碼

原型鏈

var object = {} object.proto === ???? 1

var fn = function(){} fn.proto === ???? 2 fn.proto.proto === ???? 3

var array = [] array.proto === ????4 array.proto.proto === ???? 5

Function.proto === ???? 6 Array.proto === ???? 7 Object.proto === ???? 8

true.proto === ???? 9

Function.prototype.proto === ???? 10

答案:

  1. Object.prototype
  2. Fuction.prototype
  3. Object.prototype
  4. Array.prototype
  5. Fuction.prototype
  6. Fuction.prototype
  7. Function.prototype
  8. Function.prototype
  9. Boolean.prototype
  10. Object.prototype
相關文章
相關標籤/搜索