来面向对象吧

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
相关文章
相关标签/搜索