构造函数&原型对象&实例对象

JavaScript 对象

因为JavaScript是基于原型(prototype)的,没有类的概念(ES6有了,暂且不谈),我们能接触到的都是对象,真正做到了一切皆为对象

function People(name){
    this.name = name;
    this.printName = function(){
        console.log(name);
    };
}

JS中的函数即可以是构造函数又可以当作普通函数来调用,当使用new来创建对象时,对应的函数就是构造函数,通过对象来调用时就是普通函数。

var p1 = new People('Byron');

p1是People类new出来的对象,我们称之为实例。
在Java中类不能称之为对象,但是在JavaScript中,本身没有类的概念,我们需要用对象模拟出类,然后用类去创建对象

在JavaScript中使用对象很简单,使用new操作符执行Obejct函数就可以构建一个最基本的对象

var obj = new Object();

我们称new 调用的函数为构造函数,构造函数和普通函数区别仅仅在于是否使用了new来调用,它们的返回值也会不同

定义对象,让代码变得优雅

封装性+避免使用全局变量


实现上图效果的js代码如下

var tabs = document.querySelectorAll('.tab-ct .header>li')
var panels = document.querySelectorAll('.tab-ct .content>li')

tabs.forEach(function(tab){
    tab.addEventListener('click',function(){
    tabs.forEach(function(node){
        node.classList.remove('active')
    })
    this.classList.add('active')
    var index = [].indexOf.call(tabs,this)
    panels.forEach(function(pan){
        pan.classList.remove('active')
    })
    panels[index].classList.add('active')
    })
})

为了避免过多的全局变量引起混淆

var Tab ={
  init:function(){
    var tabs = document.querySelectorAll('.tab-ct .header>li')
    var panels = document.querySelectorAll('.tab-ct .content>li')

    tabs.forEach(function(tab){
        tab.addEventListener('click',function(){
        tabs.forEach(function(node){
            node.classList.remove('active')
        })
        this.classList.add('active')
        var index = [].indexOf.call(tabs,this)
        panels.forEach(function(pan){
            pan.classList.remove('active')
        })
        panels[index].classList.add('active')
        })
    })
  }
}

 Tab.init() //调用方法启动

但这种方法的缺点是,当html结构中有多个tab时,绑定事件后执行就会出现混乱,可以采用构造对象的方式解决

构造对象

我们可以抛开类,使用字面量来构造一个对象


var obj1 = {
    nick: 'Byron',
    age: 20,
    printName: function(){
        console.log(obj1.nick);
    }
}
var obj2 = {
    nick: 'Casper',
    age: 25,
    printName: function(){
        console.log(obj2.nick);
    }
}
问题

1.太麻烦了,每次构建一个对象都是复制一遍代码
2.如果想个性化,只能通过手工赋值,使用者必需了解对象详细
这两个问题其实也是我们不能抛开类的重要原因,也是类的作用

使用函数做自动化

function createObj(nick, age){
  var obj = {
      nick: nick,
      age: age,
      printName: function(){
            console.log(this.nick);
        }
  };
  return obj;
}

var obj3 = createObj('Byron', 30);
obj3.printName();
问题

构造出来的对象类型都是Object,没有识别度

构造函数的方式去创建对象(通过new操作符调用)

//构造函数习惯性首字母大写
function People(name){
    this.name = name
    this.sayName = function(){
        console.log(this.name)
    }
}

var p1 = new People('hunger')

使用new运算符创建对象时分别经历了以下几个步骤
1.创建类的实例 2.初始化实例 3.返回实例

1.创建类的实例 p1 = {}
2.初始化实例 p1.name = 'hunger'
           p1.sayName = function(){}
3.返回实例  return p1

用构造对象解决tab问题

function Tab(tabNode){
    this.init = function(tabNode){
    this.tabs = tabNode.querySelectorAll('.tab-ct .header>li')
    this.panels = tabNode.querySelectorAll('.tab-ct .content>li')
    }
    var self = this//用self保存tabNode
    this.bind = function(){
        self.tabs.forEach(function(tab){
            tab.addEventListener('click',function(){
                //在事件函数中。this代表click 事件绑定的dom对象,点击的是谁,对应的dom对象就是谁,所以此处this代表tab
                self.tabs.forEach(function(node){
                    node.classList.remove('active')
                })
                this.classList.add('active')
                var index = [].indexOf.call(self.tabs,this)
                self.panels.forEach(function(pan){
                pan.classList.remove('active')
                })
                self.panels[index].classList.add('active')
            })
        })
    }
    this.init(tabNode)
    this.bind()

}

var tabNode1 = document.querySelectorAll('.tab-ct')[0]
var tabNode2 = document.querySelectorAll('.tab-ct')[1]

new Tab(tabNode1)
new Tab(tabNode2)

instanceof

instanceof是一个操作符,可以判断对象是否为某个类型的实例

p1 instanceof Person; // true
p1 instanceof Object;// true

构造函数的问题

#之前出现的构造函数例子
function People(name){
    this.name = name
    this.sayName = function(){
        console.log(this.name)
    }
}
var p1 = new People('hunger')

构造函数在解决了上面所有问题,同时为实例带来了类型,但可以注意到每个实例sayName方法实际上作用一样,但是每个实例要重复一遍,大量对象存在的时候是浪费内存

构造函数,原型对象prototype,实例对象三者之间的关系

原型对象

我们创建的每个函数都有一个prototype属性,这个属性是一个指针,指向一个对象,而这个对象的用途是包含所有实例共享的属性和方法(称为原型对象)。

如果按照字面意思来理解,那么prototype就是通过调用构造函数而创建的那个对象实例的原型对象。通常我们可以将实例对象的公共属性和方法放在prototype对象中。好处是节省空间,当有很多对象时,不用每次有一个对象就去重复创建一份方法。

特性:
1、每个函数都有一个prototype属性,指向一个对象,叫做原型对象
2、所有对象都有 __proto__
3、对象.__proto__=== 构造函数.prototype
4、访问一个对象的属性时,如果对象有这个属性,就获取到了,如果没有这个属性,则从proto里面去找,如果还是没有找到,则从原型对象prototype里的proto中去寻找。
5、原型对象上默认有一个属性constructor,该属性也是一个指针,指向其相关联的构造函数。
6、每个用new新建的实例对象都有一个内部属性__proto__(规范中没有指定这个名称,但是浏览器都这么实现的) 指向类的prototype属性(原型对象),使实例对象能够访问原型对象上的所有属性和方法。类的实例也是对象,其__proto__属性指向“类”的prototype。

  • 任何函数使用new表达式就是构造函数

构造函数

JS中的函数即可以是构造函数又可以当作普通函数来调用,当使用new来创建对象时,对应的函数就是构造函数,通过对象来调用时就是普通函数。

总结:三者的关系

每个构造函数都有一个原型对象(prototype属性),原型对象上包含着一个指向构造函数的指针(constructor),而实例都包含着一个指向原型对象的内部指针(实例._proto_===类.prototype)。通俗的说,实例可以通过内部指针访问到原型对象,原型对象可以通过constructor找到构造函数。

当新建一个对象p,p._proto_ 就等于People.prototype
事实上,所有新建的对象p1,p2,p3,.....
都具有一个相同的_proto_ 
XXX._proto_  = People.prototype

prototype


实例可以通过__proto__访问到其类型的prototype属性,这就意味着类的prototype对象可以作为一个公共容器,供所有实例访问。

抽象重复

由于prototype相当于特定类型所有实例都可以访问到的一个公共容器,所以重复的东西移动到公共容器prototype里放一份就可以了

#代码修改为
function Person(nick, age){
    this.nick = nick;
    this.age = age;
}
Person.prototype.sayName = function(){
    console.log(this.nick);
}
var people = new Person();
//调用原型对象上面的方法
people.sayName();

这时候对应的关系是这样的


原型链的好处
节省时间空间,当有很多对象时,不用每次有一个对象就去重复创建一份方法。

用prototype优化tab问题

function Tab(tabNode){
    this.init(tabNode)
    this.bind()

}


Tab.prototype.init = function(tabNode){

    this.tabs = tabNode.querySelectorAll('.tab-ct .header>li')
    this.panels = tabNode.querySelectorAll('.tab-ct .content>li')
}
Tab.prototype.bind = function(){
    var self = this//用self保存tabNode
    self.tabs.forEach(function(tab){
        tab.addEventListener('click',function(){
            //在事件函数中。this代表click 事件绑定的dom对象,点击的是谁,对应的dom对象就是谁,所以此处this代表tab
            self.tabs.forEach(function(node){
                node.classList.remove('active')
            })
            this.classList.add('active')
            var index = [].indexOf.call(self.tabs,this)
            self.panels.forEach(function(pan){
            pan.classList.remove('active')
            })
            self.panels[index].classList.add('active')
        })
    })
}


var tabNode1 = document.querySelectorAll('.tab-ct')[0]
var tabNode2 = document.querySelectorAll('.tab-ct')[1]

var tab1 = new Tab(tabNode1)
var tab2 = new Tab(tabNode2)

tab例子的代码

new运算符

当通过new来创建一个新对象时,JS底层将新对象的原型链指向了构造函数的原型对象,于是就在新对象和函数对象之间建立了一条原型链,通过新对象可以访问到函数对象原型prototype中的方法和属性。

function Animal(name){
    this.name = name;
}
Animal.color = 'black';
Animal.prototype.say = function(){
    console.log("I'm " + this.name)
}

var cat = new Animal('cat');

cat.name//cat
cat.color //undefined
cat.say()// I'm cat
Animal.name //"Animal"
Animal.color //"black"
Animal.say();  //Animal.say is not a function
Animal.prototype.say() //I'm undefined

分析输出结果:
cat的原型链是:cat->Animal.prototype->Object.prototype->null
cat上新增了一个属性:name

  • cat.color -> cat会先查找自身的color,没有找到便会沿着原型链查找,在上述例子中,我们仅在Animal对象上定义了color,并没有在其原型链上定义,因此找不到。
  • cat.say -> cat会先查找自身的say方法,没有找到便会沿着原型链查找,在上述例子中,我们在Animal的prototype上定义了say,因此在原型链上找到了say方法。另外,在say方法中还访问this.name,这里的this指的是其调用者obj,因此输出的是obj.name的值。
  • 对于Animal来说,它本身也是一个对象,因此,它在访问属性和方法时也遵守上述查找规则,所以:Animal.color -> “black”
  • Animal.name -> “Animal”, Animal先查找自身的name,找到了name, 但这个name不是我们定义的name,而是函数对象内置的属性。
    一般情况下,函数对象在产生时会内置name属性并将函数名作为赋值(仅函数对象)。
  • Animal.say -> Animal在自身没有找到say方法,也会沿着其原型链查找,从测试结果看:Animal的原型链是这样的:
  • Animal->Function.prototype->Object.prototype->null
    因此Animal的原型链上没有定义say方法!

以下代码是关键:
var cat = new Animal("cat");
Animal 本身是一个普通函数,但当通过new来创建对象时,Animal就是构造函数。
JS引擎执行这句代码时,在内部做了很多工作,用伪代码模拟其工作流程如下:

  1. 创建一个空对象obj;
  2. 把obj的proto指向构造函数Animal的原型对象prototype,此时便建立了obj对象的原型链:obj->Animal.prototype->Object.prototype->null
  3. 在obj对象的执行环境调用Animal构造函数函数并传递参数“cat”。 相当于var result = obj.Animal(“cat”)。
  4. 考察第3步返回的返回值,如果无返回值或者返回一个非对象值,则将obj返回作为新对象;否则会将返回值作为新对象返回。
new Animal("cat") = {
  var obj = {};
  obj.__proto__ = Animal.prototype;
  var result = Animal.call(obj,"cat");  //相当于var result = obj.Animal('cat')
  return typeof result === 'object'? result : obj;
}

JS使用原型链实现继承(new存在的意义)

JS中万物皆对象,为什么还要通过new来产生对象?
要弄明白这个问题,我们首先要搞清楚cat和Animal的关系:

cat instanceof Animal; //true

cat确实是Animal实例,cat继承了Animal中的部分属性。
要想证实这个结果,我们再来了解一下JS中instanceof的判断规则:

var L = A.__proto__;
var R = B.prototype;
if(L === R)
return true;

在Javascript中, 通过new可以产生原对象的一个实例对象,而这个实例对象继承了原对象的属性和方法。因此,new存在的意义在于它实现了Javascript中的继承,而不仅仅是实例化了一个对象。

例子: 创建一个 GoTop 对象,当 new 一个 GotTop 对象则会在页面上创建一个回到顶部的元素,点击页面滚动到顶部。拥有以下属性和方法

1. ct属性,GoTop 对应的 DOM 元素的容器
2. target属性, GoTop 对应的 DOM 元素
3. bindEvent方法, 用于绑定事件
4. createNode方法, 用于在容器内创建节点

GoTop代码

<body>
<style>
    h1{
        margin-bottom: 1500px;
    }
</style>
<div class="ct">
    <h1>hi,this is top</h1>
</div>
<script>
    function GoTop(ct){
        this.ct = ct;
        this.createNode();
        this.bindEvent();
    }
    GoTop.prototype.bindEvent = function(){
        this.target.on('click',function(){
            $(window).scrollTop(0);
        })
    }
    GoTop.prototype.createNode = function(){
        this.target = $('<button>click to top</button>')
        this.ct.append(this.target)
    }
    var go = new GoTop($('.ct'))
</script>
    
</body>
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,123评论 6 490
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,031评论 2 384
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 156,723评论 0 345
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,357评论 1 283
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,412评论 5 384
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,760评论 1 289
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,904评论 3 405
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,672评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,118评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,456评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,599评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,264评论 4 328
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,857评论 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,731评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,956评论 1 264
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,286评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,465评论 2 348

推荐阅读更多精彩内容