1、理解对象+对象属性
面向对象语言有一个标志,就是他们都有类的概念。通过类来创建多个具有相同属性的方法和对象。但是!!!js中没有类的概念,所以js中所谓的面向对象有些不同。
js中的对象可以理解成一组组的名值对(属性 + 值),其中的值可以是数据或者函数。每个对象都是基于一个引用类型创建的,详见第5章。
创建对象可以直接用原生类型(new array,new function....) 也可以自己创建(new object)
属性
1、分类
对象的属性,可以分为两种:数据属性、访问器属性1、数据属性
即在对象中显式的写出来的属性。可以用Object.defineProperty()函数来对其进行设置,例如该属性是否可删除,可修改等等var person = { };
Object.defineProperty(person,"name",{
writable:false, //设置不可写,就是只读的
value:"jack"});
writeable(只读),configurable(删除,注意这个属性一旦改成了false就改不回来了),enumerable(可列举)
若想同时该多个属性的设置,可用defineProperties()函数。
2、访问器属性
这种属性不在对象定义中显式的写出来,而且该属性都是方法函数,不是值。只能通过Object.defineProperty()函数来设置,有点像后台处理的方法有get和set可以设置,分别对应写入时和读取时的操作。
2、创建对象
当要写一个类来创建多个对象时:分类:工厂模式
构造函数模式
原型模式
组合使用(构造+原型)
动态原型模式
.....
① 工厂模式:
“工厂模式” 是一种设计模式,就是把创建过程整个进行封装,直接上代码:
function creatPerson(name , age){
var o = new Object();
o.name = name;
o.age = age;
o.say = function(){
alert(this.name);
};
return o;
}
var person1 = creatPerson(jack,20);
var person2 = creatPerson(tom.30);
可以看出来在这种模式下,是最无脑的简单的批量创建方法,但是最大的问题是这样创建的对象仅仅属于Object的实例,算是原生对象的实例,不算是用户自定义的类的实例。
所以进行改进——构造函数模式
② 构造函数模式
function Person (name , age) {
this.name = name;
this.age = age;
this.say = function(){
alert (this.name);
};
}
var person1 = new Person(jack,20);
var person2 = new Person(tom.30);
改进之处:
1、在function中没有显示的创建对象,即new object() ,在想创建对象时再new
2、在function中用this来指代对象(因为function中没有显式创建)
3、function中不用return
4、函数名一般开头字母大写,算是惯例
优点:
1、 这种方法创建的实例算是自定义类的实例,此时person1 与person2 不仅仅是object的实例,还是Person的实例 。(所有对象都要继承自object啊)
2、不算优点的优点,构造函数还可以当成普通函数用。
当构造函数用时,要new来创建
当普通函数时,不加new,直接Person(jack,20); 此时函数变成了全局函数,this指代的是window,所以window会被添加name,age。
当普通函数用时,就可以配合call,apply来调用,比如作用在一个对象上,也相当于为这个对象添加 了Person的属性和方法
缺点:
每次实例化,函数中所有的属性和方法都会被重新实例化——即每个新对象中的name,age,function say,都是彼此不同的,不会互相干扰。
对于属性来说可能这点是好处,这样可以对每个不同的对象进行后续的修改
但是对于方法来说不好,每次完成同样功能,但是却每次都被重置
一个改进:
把类中的方法拿到构造函数外面写。
function Person(name,age) {
...
...
var say = this.say; //在构造函数内部只声明不实现
}
function say(){
alert(this.name);
}
这样就可以避免方法也跟着被重置了。
但是!!! 还是不好!! 当类中有很多方法时,全放到外面写,就破坏了对象的封装性
继续改进——原型模式
③ 原型模式
关键词:共享
我们创建一个函数都会自带一个prototype(原型)属性,这个属性是一个指针,指向这个函数的原型对象。
这样的话,我们给对象的原型添加的属性,方法,在实例化的时候就可以被继承共享了。
function Person(){
} //不必在构造函数中定义任何属性,方法
Person.portotype.name = "jack"; //在构造函数外面,用prototype添加
Person.portotype.age = 20;
Person.portotype.say = function(){
alert(this.name);
};
var person1 = new Person();
var person2 = new Person();
此时person1 与person2 所有的属性,方法,全是指向同一个,即原型对象中的name,age,function say。
1、理解原型对象
函数的原型对象最初都有一个属性:constructor。 (默认是不可枚举的,就是在for-in中不会被列出)
这个属性代表这个原型对象的构造函数,可以显式的设置一下,例如Person.prototype.constructor = Person。 双重保障。
用prototype方法添加的属性,方法,实际上都不属于Person类,但是new Person的实例可以用,这是因为会自动向上索引。Person中没有,会自动往上原型类中找。Person中有,则用它的。
若用户为Person类添加了与prototype类中同名的属性,方法,那么调用时会优先用Person类的值。 注意prototype中的值没有变,当Person类中这个属性被delete时,还是会向上索引至prototype类。
2、in操作符
单独使用:
语法: “属性” in 对象 // “name” in person1;
返回:只要这个属性在这个对象中能调用,就返回true。无论它是在Person中,还是在Person.prototype中
for-in
会返回所有通过这个对象能使用的,可枚举(之前说过可以将enumerated = false)的属性。无论Person还是Person.prototype中的
3、简化写法
利用字面量的写法
function Person(){
}
Person.prototype = {
name:"jack",
age:20,
say: function(){
alert(this.name);
};
}
缺点:
这种写法实际上完全重写了prototype对象,这导致了constructor属性不会指向原Person类,此时值没有了。 虽然这对创建对象没什么影响,但是若使用顺序颠倒时,会出错,详见下面的动态性。
如果constructor的值很重要,那么可以在字面量中显式的添加 constructor:Person,此时constructor会变为可枚举的,在for-in中可见。注意,即使用constructor写了指向Person,这个Person也是新Person,非原来的!!这种方法就是会切断,连接不回来!!
4、原型的动态性
当不用字面量方法简写时,创建对象可以放在创建类的前面。因为js中实际没有类的概念,是索引,遇到创建对象它会自动在上下文索引。
但是当用字面量方法简写时,顺序就不能颠倒了,因为会重新创建一个Person类。
5、原生对象的原型
prototype不仅仅可以用在自定义的类,还可以用在原生的类中,比如array,string。
可以为他们添加新方法。 Array.Prototype.newmethod = function(){....};
6、优缺点
① 由于在prototype中统一设置属性和值,导致所有实例在默认状况下初始化的值都一样② 最大的问题:引用类型值的原型属性会被所有实例共享。即,若属性的值时引用类型,例如数组时,当有一个实例化对象对其进行修改,那么prototype中的值也会被修改,其他实例化的这个属性也会被改。 之前说的不改的情况是普通数值类型值。
但是prototype的共享,特别适用于对象的方法,即函数,但是并不适合属性,即值。
所以继续改进——组合使用
④组合使用(构造+原型)
这种方式是用户创建自定义类型最常见的。
用构造函数方法定义属性——构造函数式每一次实例化都会为不相同的,所以各个实例的属性可以再次自由定义
用原型模式定义方法 + 一些可以共享(即不做个性化设置的)属性——原型模式对于方法共享,每次实例化不用重新创建,节省内存。
//构造函数中的属性可以被分别再赋值
function Person(name , age , job){
this.name = name;
this.age = age;
}
//原型模式中的方法可以被共享
Person.prototype = {
constructer : Person; //保险起见,可以显式的写一下归属
say : function(){
alert(this.name);
}
}
优点:
节省内存,集两个方法的优点于一身
缺点:
其实没啥缺点,硬要说的话就是把一个类拆成了两段写,不方便看。所以又有了进一步的改进——动态原型模式
⑤动态原型模式
由于之前分段写不好看,这个模式其实就是简单的把这两段(构造函数部分+原型模式部分)捏在了一起。
通过一个if语句判断。如果对象中没有该方法,就用原型模式创建一个,接下来每次再实例化的时候继续判断,如果已经有了这个方法,就不再创建了。见下:
function Person(name , age ){
//正常在构造函数中写属性
this.name = name;
this.age = age;
// 接下来判断,用原型模式加方法
if(typeof this.say != "function"){ //只有初次创建时这个方法不存在,在原型中创建。接下来再创建对象,在原型中已有,就跳过了
Person.prototype.say = function(){
alert(this.name);
}
}
}
var friend = new Person("jack", 20);
friend.say();
当有多个方法时,判断一个就可以,其他的都在这个方法的判断下添加,反正都是一起在初次被创建。
注意:
在这种方式下,原型模式不可以用简写模式(即字面量重写),因为会重新创建一个Person,与已经写好的name,age分别属于不同的Person,切断了联系。