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)
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引擎执行这句代码时,在内部做了很多工作,用伪代码模拟其工作流程如下:
- 创建一个空对象obj;
- 把obj的proto指向构造函数Animal的原型对象prototype,此时便建立了obj对象的原型链:obj->Animal.prototype->Object.prototype->null
- 在obj对象的执行环境调用Animal构造函数函数并传递参数“cat”。 相当于var result = obj.Animal(“cat”)。
- 考察第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方法, 用于在容器内创建节点
<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>