美文网首页
构造函数&原型对象&实例对象

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

作者: yuhuan121 | 来源:发表于2017-08-08 08:40 被阅读0次

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>

相关文章

  • 12.如何查找构造函数和原型的属性

    构造函数.prototype 查看构造函数的原型属性实例对象.ptoto 查看实例对象的构造函数的原型实例对象...

  • javascirpt复习

    实例中访问构造函数原型的指针,指向的是构造函数原型,不是构造函数; 所以重写构造函数原型对象,【实例对象】访问还是...

  • javascript中面向对象编程-创建对象之原型模式

    理解名词:对象 原型对象 原型属性 函数 构造函数 实例 对象: Object,创建对象,对象属性方法原型对象:...

  • 原型链

    构造函数、原型、实例的关系 通过构造函数创建实例 每个构造函数都有一个原型对象 原型对象到包含一个指向构造函数的指...

  • 继承

    原型链 将一个构造函数的实例作为子构造函数的原型对象,这样,子构造函数的原型对象中会继承到父构造函数实例上的属性方...

  • js继承

    继承 构造函数 原型对象 对象实例 三者之间的关系 每个构造函数都拥有一个原型对象,构造函数.prototy...

  • 原型指向可以改变

    构造函数中的this就是实例对象原型对象中方法中的this就是实例对象 原型指向可以改变实例对象的原型proto指...

  • 总结构造函数和实例对象和原型对象之间的关系

    1、构造函数可以实例对象 2、构造函数中有一个属性叫prototype,是构造函数的原型对象。 3、构造函数的原型...

  • 继承的几种方式

    构造函数,原型和实例的关系每个构造函数都有一个原型对象prototype,原型对象中有个constructor属性...

  • 继承

    构造函数、原型和实例的关系: 每个构造函数都有其对应的原型对象;每个原型对象都有一个构造函数指针construct...

网友评论

      本文标题:构造函数&原型对象&实例对象

      本文链接:https://www.haomeiwen.com/subject/bhcylxtx.html